From a338c764be0e2c084ea345302420cd29aa05ff91 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Fri, 17 Apr 2020 12:18:49 -0400 Subject: [PATCH 1/2] bunch o mods --- bipentropy/bipentropy.py | 51 ++++++++++------ requirements.txt | 2 +- setup.py | 2 +- test_entropy.py | 128 +++++++++++++++++++-------------------- 4 files changed, 99 insertions(+), 84 deletions(-) diff --git a/bipentropy/bipentropy.py b/bipentropy/bipentropy.py index f64683b..cf81987 100644 --- a/bipentropy/bipentropy.py +++ b/bipentropy/bipentropy.py @@ -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: + # 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]) diff --git a/requirements.txt b/requirements.txt index 85fa46c..704ca21 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,3 @@ -base58 pycoin mnemonic +pytest diff --git a/setup.py b/setup.py index 896b4b6..3e938b2 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ def read(*path): description="Implementation of Bitcoin BIP Entropy", long_description=read("README.md"), url="https://github.com/ethankosakovsky/bipentropy", - install_requires=["mnemonic", "base58", "pycoin"], + install_requires=["mnemonic", "pycoin"], zip_safe=False, classifiers=[ "License :: OSI Approved :: MIT License", diff --git a/test_entropy.py b/test_entropy.py index 4a20736..d62003d 100644 --- a/test_entropy.py +++ b/test_entropy.py @@ -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' + 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? + 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) + assert result == 'xprv9s21ZrQH143K3KJoGoKpsDsWdDNDBKs1wqFymBpCGJtrYXrfKzykGDBadZq5SrNde22F83X9qhFZr4uyV9TptTgLqCBc6XFN9tssphdxVeg' if __name__ == "__main__": - __main__() + pytest.main() From cedb7765d9638dc65dfcb7a0bf966d27aabdeba7 Mon Sep 17 00:00:00 2001 From: "Peter D. Gray" Date: Fri, 17 Apr 2020 12:33:36 -0400 Subject: [PATCH 2/2] hex cases added --- bipentropy/bipentropy.py | 8 +++++++- test_entropy.py | 10 +++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/bipentropy/bipentropy.py b/bipentropy/bipentropy.py index cf81987..f7c5b27 100644 --- a/bipentropy/bipentropy.py +++ b/bipentropy/bipentropy.py @@ -50,9 +50,15 @@ def bip32_xprv_to_entropy(self, path, xprv_string): raise ValueError('ERROR: Invalid xprv') return self.__hmac_sha512(self.__derive_k(path, xprv)) + def bip32_xprv_to_hex(self, index, width, xprv_string): + # export entropy as hex + path = f"83696968p/128169p/{width}p/{index}p" + ent = self.bip32_xprv_to_entropy(path, xprv_string) + return ent[0:width].hex() + def bip32_xprv_to_xprv(self, index, xprv_string): # app_no = 32 => XPRV to XPRV - path = "83696968p/32p/{index}p".format(index=index) + path = f"83696968p/32p/{index}p" node = BTC.parse.bip32_prv(xprv_string).subkey_for_path(path) # if API to pycoin hadn't been shitcoined: diff --git a/test_entropy.py b/test_entropy.py index d62003d..4136745 100644 --- a/test_entropy.py +++ b/test_entropy.py @@ -84,7 +84,15 @@ def test_xprv_application(): result = e.bip32_xprv_to_xprv(0, XPRV) assert result == 'xprv9s21ZrQH143K3KJoGoKpsDsWdDNDBKs1wqFymBpCGJtrYXrfKzykGDBadZq5SrNde22F83X9qhFZr4uyV9TptTgLqCBc6XFN9tssphdxVeg' +@pytest.mark.parametrize('index, width, expect', [ + (0, 32, '4f4ea2ef43af14e51f2453221d50762fc3767e2287dc524ca58f10e5225a6ead'), + (0, 64, '4fc6759ef9c0e12aed757ad874706a3955ef125c8f8eabb3909aeda028f5e285bf496a23265bac09f537f4cc5e1efa689f5625ded2cd996c042b2657263c6816'), + (1234, 64, 'e1a35aeae27cb3c93b0d437e94b64a891cdff9ac250537ec675d0e6a2680f1d2edf9927f4c5233b19b15fdea4063a8b62f85ff8666176d226741ed192075a8db'), + ]) +def test_hex_application(index, width, expect): + e = bipentropy.BIPEntropy() + assert e.bip32_xprv_to_hex(index, width, XPRV) == expect + if __name__ == "__main__": pytest.main() -