-
Notifications
You must be signed in to change notification settings - Fork 15
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Various changes #1
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,12 +21,24 @@ | |
|
||
import hmac | ||
import hashlib | ||
import base58 | ||
from mnemonic import Mnemonic as bip39 | ||
from pycoin.symbols.btc import network as BTC | ||
from pycoin.encoding.bytes32 import from_bytes_32, to_bytes_32 | ||
|
||
|
||
class BIPEntropy(object): | ||
|
||
def __get_k_from_node(self, node): | ||
return to_bytes_32(node.secret_exponent()) | ||
|
||
def __derive_k(self, path, xprv): | ||
path = path.replace("m/", "").replace("'", "p") | ||
node = xprv.subkey_for_path(path) | ||
return self.__get_k_from_node(node) | ||
|
||
def __hmac_sha512(self, message_k): | ||
return hmac.new(message_k, msg=b'bip-entropy-from-k', digestmod=hashlib.sha512).digest() | ||
|
||
def bip39_mnemonic_to_entropy(self, path, mnemonic, passphrase=''): | ||
bip39_seed = bip39.to_seed(mnemonic, passphrase=passphrase) | ||
xprv = BTC.keys.bip32_seed(bip39_seed) | ||
|
@@ -38,26 +50,29 @@ def bip32_xprv_to_entropy(self, path, xprv_string): | |
raise ValueError('ERROR: Invalid xprv') | ||
return self.__hmac_sha512(self.__derive_k(path, xprv)) | ||
|
||
def entropy_from_wif(self, wif): | ||
return self.__hmac_sha512(self.__get_k_from_wif(wif)) | ||
def bip32_xprv_to_xprv(self, index, xprv_string): | ||
# app_no = 32 => XPRV to XPRV | ||
path = "83696968p/32p/{index}p".format(index=index) | ||
node = BTC.parse.bip32_prv(xprv_string).subkey_for_path(path) | ||
|
||
def entropy_to_wif(self, entropy): | ||
return BTC.keys.private(secret_exponent=int(entropy[:32].hex(), 16)).wif() | ||
# if API to pycoin hadn't been shitcoined: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This wins the internet today. |
||
# return BIP32Node(node.chain_code(), secret_exponent=node.secret_exponent()).hwif() | ||
|
||
def entropy_to_bip39(self, entropy, words, language='english'): | ||
bits = (words - 1) * 11 // 8 + 1 | ||
m = bip39(language) | ||
return m.to_mnemonic(entropy[:bits]) | ||
node._depth = 0 | ||
node._parent_fingerprint = bytes(4) | ||
node._child_index = 0 | ||
|
||
def __get_k_from_wif(self, wif): | ||
return base58.b58decode(wif)[1:-5] | ||
return node.hwif(as_private=True) | ||
|
||
def __decorate_path(self, path): | ||
return path.replace("m/", "").replace("'", "p") | ||
def entropy_from_wif(self, wif): | ||
node = BTC.keys.from_text(wif) | ||
return self.__hmac_sha512(self.__get_k_from_node(node)) | ||
|
||
def __derive_k(self, path, xprv): | ||
child_wif = xprv.subkey_for_path(self.__decorate_path(path)).wif() | ||
return self.__get_k_from_wif(child_wif) | ||
def entropy_to_wif(self, entropy): | ||
return BTC.keys.private(secret_exponent=from_bytes_32(entropy[:32])).wif() | ||
|
||
def __hmac_sha512(self, message_k): | ||
return hmac.new(b'bip-entropy-from-k', message_k, digestmod=hashlib.sha512).digest() | ||
def entropy_to_bip39(self, entropy, words, language='english'): | ||
width = (words - 1) * 11 // 8 + 1 | ||
assert 16 <= width <= 32 | ||
m = bip39(language) | ||
return m.to_mnemonic(entropy[:width]) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,3 @@ | ||
base58 | ||
pycoin | ||
mnemonic | ||
pytest |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -20,71 +20,71 @@ | |
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | ||
|
||
from bipentropy import bipentropy | ||
import unittest | ||
|
||
class EntropyTest(unittest.TestCase): | ||
def test_mnemonic(self): | ||
e = bipentropy.BIPEntropy() | ||
mnemonic = 'install scatter logic circle pencil average fall shoe quantum disease suspect usage' | ||
test = e.bip39_mnemonic_to_entropy("m/83696968'/0'/0'", mnemonic) | ||
expected = 'efecfbccffea313214232d29e71563d941229afb4338c21f9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869e693da66ce94ac2da570ab7ee48618f7' | ||
self.assertEqual(test.hex(), expected) | ||
|
||
# with password | ||
test = e.bip39_mnemonic_to_entropy("m/83696968'/0'/0'", mnemonic, 'TREZOR') | ||
expected = 'd24cee04c61c4a47751658d078ae9b0cc9550fe43eee643d5c10ac2e3f5edbca757b2bd74d55ff5bcc2b1608d567053660d9c7447ae1eb84b6619282fd391844' | ||
self.assertEqual(test.hex(), expected) | ||
|
||
def test_xprv(self): | ||
e = bipentropy.BIPEntropy() | ||
xprv = 'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb' | ||
test = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", xprv) | ||
expected = 'efecfbccffea313214232d29e71563d941229afb4338c21f9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869e693da66ce94ac2da570ab7ee48618f7' | ||
self.assertEqual(test.hex(), expected) | ||
|
||
def test_entropy_to_mnemonic(self): | ||
e = bipentropy.BIPEntropy() | ||
xprv = 'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb' | ||
entropy = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", xprv) | ||
|
||
words12 = 'useful guitar veteran zone perfect october explain grant clarify december flight recycle' | ||
words15 = 'useful guitar veteran zone perfect october explain grant clarify december flight raw banana estate uncle' | ||
words24 = 'useful guitar veteran zone perfect october explain grant clarify december flight raw banana estate unfair grow search witness echo market primary alley forward boring' | ||
|
||
self.assertEqual(e.entropy_to_bip39(entropy, 12), words12) | ||
self.assertEqual(e.entropy_to_bip39(entropy, 15), words15) | ||
self.assertEqual(e.entropy_to_bip39(entropy, 24), words24) | ||
|
||
def test_wif_from_entropy(self): | ||
e = bipentropy.BIPEntropy() | ||
xprv = 'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb' | ||
entropy = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", xprv) | ||
self.assertEqual(e.entropy_to_wif(entropy), 'L5G6UFMvJaFt1KPvupEtT8TUN2YrFnQJm1LA2nEczWrR7MuoxB1Z') | ||
|
||
def test_applications(self): | ||
e = bipentropy.BIPEntropy() | ||
xprv = 'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb' | ||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/12'/0'", xprv) | ||
self.assertEqual(entropy[:16].hex(), '6250b68daf746d12a24d58b4787a714b') | ||
self.assertEqual(e.entropy_to_bip39(entropy, 12), | ||
'girl mad pet galaxy egg matter matrix prison refuse sense ordinary nose') | ||
|
||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/18'/0'", xprv) | ||
self.assertEqual(entropy[:24].hex(), '938033ed8b12698449d4bbca3c853c66b293ea1b1ce9d9dc') | ||
self.assertEqual(e.entropy_to_bip39(entropy, 18), | ||
'near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token') | ||
|
||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/24'/0'", xprv) | ||
self.assertEqual(entropy[:32].hex(), 'ae131e2312cdc61331542efe0d1077bac5ea803adf24b313a4f0e48e9c51f37f') | ||
self.assertEqual(e.entropy_to_bip39(entropy, 24), | ||
'puppy ocean match cereal symbol another shed magic wrap hammer bulb intact gadget divorce twin tonight reason outdoor destroy simple truth cigar social volcano') | ||
|
||
|
||
def __main__(): | ||
unittest.main() | ||
|
||
import pytest | ||
|
||
XPRV = 'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb' | ||
|
||
def test_mnemonic(): | ||
e = bipentropy.BIPEntropy() | ||
mnemonic = 'install scatter logic circle pencil average fall shoe quantum disease suspect usage' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to revert the test cases because hmac param order is flipped incorrectly elsewhere in this PR here |
||
test = e.bip39_mnemonic_to_entropy("m/83696968'/0'/0'", mnemonic) | ||
expected = '71356cdf2f6851c0499b47fe16ef121883f8c58e7d6fb4c33a9017df71f3b0fe21f92d043ee4f19676384a3d943554904caca131269dcb84151ecb2f7ca31902' | ||
assert test.hex() == expected | ||
|
||
# with password | ||
test = e.bip39_mnemonic_to_entropy("m/83696968'/0'/0'", mnemonic, 'TREZOR') | ||
expected = '5c56a2a19ebe8f86c4cbb788dd264bd96387dac2047dac799c51fb6218da0513da44bc0f4603815cc8c8c456dbd3aae79e334fb19dbeffc43fc236d58368ebdb' | ||
assert test.hex() == expected | ||
|
||
def test_xprv_to_entropy(): | ||
e = bipentropy.BIPEntropy() | ||
test = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", XPRV) | ||
expected = '71356cdf2f6851c0499b47fe16ef121883f8c58e7d6fb4c33a9017df71f3b0fe21f92d043ee4f19676384a3d943554904caca131269dcb84151ecb2f7ca31902' | ||
assert test.hex() == expected | ||
|
||
|
||
def test_entropy_to_mnemonic(): | ||
e = bipentropy.BIPEntropy() | ||
entropy = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", XPRV) | ||
|
||
words12 = 'illness problem daughter gain lunar then chapter happy wrap resist setup corn' | ||
assert e.entropy_to_bip39(entropy, 12) == words12 | ||
|
||
words15 = 'illness problem daughter gain lunar then chapter happy wrap resist setup country display glare debate' | ||
assert e.entropy_to_bip39(entropy, 15) == words15 | ||
|
||
words24 = 'illness problem daughter gain lunar then chapter happy wrap resist setup country display glare delay pupil regular border piano cook warfare what sentence supreme' | ||
assert e.entropy_to_bip39(entropy, 24) == words24 | ||
|
||
def test_wif_from_entropy(): | ||
# PDG: not sure about this? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you elaborate? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I haven't tested this specific case against Coldcard, that's all. |
||
e = bipentropy.BIPEntropy() | ||
entropy = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", XPRV) | ||
assert e.entropy_to_wif(entropy) == 'L11mqGbaozsMYDHS7dfZ2bPGL2viSH6zHr69MKwvpxuw7cCR4M1u' | ||
|
||
def test_applications(): | ||
e = bipentropy.BIPEntropy() | ||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/12'/0'", XPRV) | ||
assert entropy[:16].hex() == 'f0337580e36fd50ef8734cd9dcfb9a78' | ||
assert e.entropy_to_bip39(entropy, 12) == \ | ||
'usual option gasp short wool manual tide hat supply treat track valve' | ||
|
||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/18'/0'", XPRV) | ||
assert entropy[:24].hex() == '60529dbbf2707ea89e4cd41f7e26fcebf2b492b9a99c5e95' | ||
assert e.entropy_to_bip39(entropy, 18) == \ | ||
'gate neutral humble top among february junior once buyer van sand subject clip enable trade crime future protect' | ||
|
||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/24'/0'", XPRV) | ||
assert entropy[:32].hex() == '5166983339d6fc685abe49162327ac2e915fcc17132dad7a2b1e8f324b2f06bd' | ||
assert e.entropy_to_bip39(entropy, 24) == \ | ||
'fabric crumble art inhale hurt crouch helmet since bike bomb twelve frog bicycle toward fox grant pulp spend sibling bunker caution nurse brain prison' | ||
|
||
def test_xprv_application(): | ||
e = bipentropy.BIPEntropy() | ||
result = e.bip32_xprv_to_xprv(0, XPRV) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would definitely prefer the library does not use fixed paths. The underlying is reason for these particular functions is the transformation, and if I want to use an arbitrary path, I shouldn't have to rewrite the underlying function to do so. You can always write a wrapper API layer around this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMHO, the purpose of demo code like this is to fix the paths. It's the embodiment of the BIP. |
||
assert result == 'xprv9s21ZrQH143K3KJoGoKpsDsWdDNDBKs1wqFymBpCGJtrYXrfKzykGDBadZq5SrNde22F83X9qhFZr4uyV9TptTgLqCBc6XFN9tssphdxVeg' | ||
|
||
if __name__ == "__main__": | ||
__main__() | ||
pytest.main() | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is wrong. The format of the python function is
hmac.new(key, msg, digest)
, wherekey
is a shared key or salt, and message is the payload you are hashing like a password etc. See the python reference https://docs.python.org/3/library/hmac.html#hmac.newAnd examples in the wild in pycoin, python-mnemonic, and Bitcoin Core here and here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, okay.