Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
94408e5bc2 | ||
|
|
e0b4684bd6 | ||
|
|
89c0f85778 | ||
|
|
bd853194ed | ||
|
|
0cc3428052 | ||
|
|
cecfc373c2 | ||
|
|
d1d9654c00 | ||
|
|
d179b9a330 | ||
|
|
d53ec6c957 |
3
.gitignore
vendored
3
.gitignore
vendored
@ -1 +1,4 @@
|
||||
__pycache__
|
||||
.pytest_cache
|
||||
dist/
|
||||
build/
|
||||
64
bipentropy/app.py
Normal file
64
bipentropy/app.py
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python
|
||||
#
|
||||
# Copyright (c) 2020 Ethan Kosakovsky <ethankosakovsky@protonmail.com>
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||
# this software and associated documentation files (the "Software"), to deal in
|
||||
# the Software without restriction, including without limitation the rights to
|
||||
# use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||
# of the Software, and to permit persons to whom the Software is furnished to do
|
||||
# so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in all
|
||||
# copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
import bipentropy
|
||||
|
||||
|
||||
def bip39(xprv_string, language, words, index):
|
||||
# 83696968'/39'/language'/words'/index'
|
||||
language_lookup = {
|
||||
'english': 0,
|
||||
'japanese': 1,
|
||||
'korean': 2,
|
||||
'spanish': 3,
|
||||
'chinese_simplified': 4,
|
||||
'chinese_traditional': 5,
|
||||
'french': 6,
|
||||
'italian': 7,
|
||||
'czech': 8
|
||||
}
|
||||
lang_code = language_lookup[language]
|
||||
e = bipentropy.BIPEntropy()
|
||||
path = f"83696968p/39p/{lang_code}p/{words}p/{index}p"
|
||||
|
||||
entropy = e.bip32_xprv_to_entropy(path, xprv_string)
|
||||
return e.entropy_to_bip39(entropy, words, language)
|
||||
|
||||
|
||||
def wif(xprv_string, index):
|
||||
# m/83696968'/2'/index'
|
||||
e = bipentropy.BIPEntropy()
|
||||
path = f"83696968p/2p/{index}p"
|
||||
return e.entropy_to_wif(e.bip32_xprv_to_entropy(path, xprv_string))
|
||||
|
||||
|
||||
def hex(xprv_string, index, width):
|
||||
# m/83696968'/128169p'/index'
|
||||
e = bipentropy.BIPEntropy()
|
||||
path = f"83696968p/128169p/{width}p/{index}p"
|
||||
return e.bip32_xprv_to_hex(path, width, xprv_string)
|
||||
|
||||
|
||||
def xprv(xprv_string, index):
|
||||
# 83696968'/32'/index'
|
||||
e = bipentropy.BIPEntropy()
|
||||
path = f"83696968p/32p/{index}p"
|
||||
return e.bip32_xprv_to_xprv(path, xprv_string)
|
||||
@ -21,12 +21,27 @@
|
||||
|
||||
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
|
||||
import base58
|
||||
|
||||
|
||||
class BIPEntropy(object):
|
||||
def __decorate_path(self, path):
|
||||
return path.replace("m/", "").replace("'", "p")
|
||||
|
||||
def __get_k_from_node(self, node):
|
||||
return to_bytes_32(node.secret_exponent())
|
||||
|
||||
def __derive_k(self, path, xprv):
|
||||
path = self.__decorate_path(path)
|
||||
node = xprv.subkey_for_path(path)
|
||||
return self.__get_k_from_node(node)
|
||||
|
||||
def __hmac_sha512(self, message_k):
|
||||
return hmac.new(key=b'bip-entropy-from-k', msg=message_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 +53,41 @@ class BIPEntropy(object):
|
||||
raise ValueError('ERROR: Invalid xprv')
|
||||
return self.__hmac_sha512(self.__derive_k(path, xprv))
|
||||
|
||||
def bip32_xprv_to_hex(self, path, width, xprv_string):
|
||||
# export entropy as hex
|
||||
path = self.__decorate_path(path)
|
||||
ent = self.bip32_xprv_to_entropy(path, xprv_string)
|
||||
return ent[0:width].hex()
|
||||
|
||||
def bip32_xprv_to_xprv(self, path, xprv_string):
|
||||
path = self.__decorate_path(path)
|
||||
ent = self.bip32_xprv_to_entropy(path, xprv_string)
|
||||
|
||||
# From Peter Gray
|
||||
# Taking 64 bytes of the HMAC digest, the first 32 bytes are the chain code, and second 32 bytes are the private
|
||||
# key for BIP32 XPRV value. Child number, depth, and parent fingerprint are forced to zero.
|
||||
prefix = b'\x04\x88\xad\xe4'
|
||||
depth = b'\x00'
|
||||
parent_fingerprint = b'\x00\x00\x00\x00'
|
||||
child_num = b'\x00\x00\x00\x00'
|
||||
chain_code = ent[:32]
|
||||
private_key = b'\x00' + ent[32:]
|
||||
extended_key = prefix + depth + parent_fingerprint + child_num + chain_code + private_key
|
||||
checksum = hashlib.sha256(hashlib.sha256(extended_key).digest()).digest()[:4]
|
||||
derived_xprv_string = base58.b58encode(extended_key + checksum).decode()
|
||||
node = BTC.parse(derived_xprv_string)
|
||||
|
||||
return node.hwif(as_private=True)
|
||||
|
||||
def entropy_from_wif(self, wif):
|
||||
return self.__hmac_sha512(self.__get_k_from_wif(wif))
|
||||
node = BTC.keys.from_text(wif)
|
||||
return self.__hmac_sha512(self.__get_k_from_node(node))
|
||||
|
||||
def entropy_to_wif(self, entropy):
|
||||
return BTC.keys.private(secret_exponent=int(entropy[:32].hex(), 16)).wif()
|
||||
return BTC.keys.private(secret_exponent=from_bytes_32(entropy[:32])).wif()
|
||||
|
||||
def entropy_to_bip39(self, entropy, words, language='english'):
|
||||
bits = (words - 1) * 11 // 8 + 1
|
||||
width = (words - 1) * 11 // 8 + 1
|
||||
assert 16 <= width <= 32
|
||||
m = bip39(language)
|
||||
return m.to_mnemonic(entropy[:bits])
|
||||
|
||||
def __get_k_from_wif(self, wif):
|
||||
return base58.b58decode(wif)[1:-5]
|
||||
|
||||
def __decorate_path(self, path):
|
||||
return path.replace("m/", "").replace("'", "p")
|
||||
|
||||
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 __hmac_sha512(self, message_k):
|
||||
return hmac.new(b'bip-entropy-from-k', message_k, digestmod=hashlib.sha512).digest()
|
||||
return m.to_mnemonic(entropy[:width])
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
base58
|
||||
pycoin
|
||||
mnemonic
|
||||
pytest
|
||||
base58
|
||||
|
||||
3
setup.py
3
setup.py
@ -20,7 +20,8 @@ setup(
|
||||
description="Implementation of Bitcoin BIP Entropy",
|
||||
long_description=read("README.md"),
|
||||
url="https://github.com/ethankosakovsky/bipentropy",
|
||||
install_requires=["mnemonic", "base58", "pycoin"],
|
||||
download_url="https://github.com/ethankosakovsky/bipentropy/archive/0.1.tar.gz",
|
||||
install_requires=["mnemonic", "pycoin", "base58", "pytest"],
|
||||
zip_safe=False,
|
||||
classifiers=[
|
||||
"License :: OSI Approved :: MIT License",
|
||||
|
||||
143
test_entropy.py
143
test_entropy.py
@ -20,71 +20,92 @@
|
||||
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
from bipentropy import bipentropy
|
||||
import unittest
|
||||
from bipentropy import app
|
||||
import pytest
|
||||
|
||||
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')
|
||||
XPRV = 'xprv9s21ZrQH143K2LBWUUQRFXhucrQqBpKdRRxNVq2zBqsx8HVqFk2uYo8kmbaLLHRdqtQpUm98uKfu3vca1LqdGhUtyoFnCNkfmXRyPXLjbKb'
|
||||
|
||||
|
||||
def __main__():
|
||||
unittest.main()
|
||||
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 = 'efecfbccffea313214232d29e71563d941229afb4338c21f9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869e693da66ce94ac2da570ab7ee48618f7'
|
||||
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 = 'efecfbccffea313214232d29e71563d941229afb4338c21f9517c41aaa0d16f00b83d2a09ef747e7a64e8e2bd5a14869e693da66ce94ac2da570ab7ee48618f7'
|
||||
assert test.hex() == expected
|
||||
|
||||
|
||||
def test_entropy_to_mnemonic():
|
||||
e = bipentropy.BIPEntropy()
|
||||
entropy = e.bip32_xprv_to_entropy("m/83696968'/0'/0'", XPRV)
|
||||
|
||||
words12 = 'useful guitar veteran zone perfect october explain grant clarify december flight recycle'
|
||||
assert e.entropy_to_bip39(entropy, 12) == words12
|
||||
|
||||
words15 = 'useful guitar veteran zone perfect october explain grant clarify december flight raw banana estate uncle'
|
||||
assert e.entropy_to_bip39(entropy, 15) == words15
|
||||
|
||||
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'
|
||||
assert e.entropy_to_bip39(entropy, 24) == words24
|
||||
|
||||
def test_wif_from_entropy():
|
||||
e = bipentropy.BIPEntropy()
|
||||
entropy = e.bip32_xprv_to_entropy("m/83696968'/2'/0'", XPRV)
|
||||
entropy = entropy[:32]
|
||||
assert e.entropy_to_wif(entropy) == 'Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp'
|
||||
|
||||
def test_mnemonic():
|
||||
e = bipentropy.BIPEntropy()
|
||||
entropy = e.bip32_xprv_to_entropy("m/83696968'/39'/0'/12'/0'", XPRV)
|
||||
assert entropy[:16].hex() == '6250b68daf746d12a24d58b4787a714b'
|
||||
assert 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)
|
||||
assert entropy[:24].hex() == '938033ed8b12698449d4bbca3c853c66b293ea1b1ce9d9dc'
|
||||
assert 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)
|
||||
assert entropy[:32].hex() == 'ae131e2312cdc61331542efe0d1077bac5ea803adf24b313a4f0e48e9c51f37f'
|
||||
assert 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 test_xprv():
|
||||
e = bipentropy.BIPEntropy()
|
||||
result = e.bip32_xprv_to_xprv("83696968'/32'/0'", XPRV)
|
||||
assert result == 'xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX'
|
||||
|
||||
@pytest.mark.parametrize('path, width, expect', [
|
||||
("83696968'/128169'/32'/0'", 32, 'ea3ceb0b02ee8e587779c63f4b7b3a21e950a213f1ec53cab608d13e8796e6dc'),
|
||||
("83696968'/128169'/64'/0'", 64, '492db4698cf3b73a5a24998aa3e9d7fa96275d85724a91e71aa2d645442f878555d078fd1f1f67e368976f04137b1f7a0d19232136ca50c44614af72b5582a5c'),
|
||||
("83696968'/128169'/64'/1234'", 64, '61d3c182f7388268463ef327c454a10bc01b3992fa9d2ee1b3891a6b487a5248793e61271066be53660d24e8cb76ff0cfdd0e84e478845d797324c195df9ab8e'),
|
||||
])
|
||||
def test_hex(path, width, expect):
|
||||
e = bipentropy.BIPEntropy()
|
||||
assert e.bip32_xprv_to_hex(path, width, XPRV) == expect
|
||||
|
||||
def test_bipentropy_applications():
|
||||
assert app.bip39(XPRV, 'english', 18, 0) == \
|
||||
'near account window bike charge season chef number sketch tomorrow excuse sniff circle vital hockey outdoor supply token'
|
||||
|
||||
assert app.xprv(XPRV, 0) == \
|
||||
'xprv9s21ZrQH143K2srSbCSg4m4kLvPMzcWydgmKEnMmoZUurYuBuYG46c6P71UGXMzmriLzCCBvKQWBUv3vPB3m1SATMhp3uEjXHJ42jFg7myX'
|
||||
|
||||
assert app.wif(XPRV, 0) == 'Kzyv4uF39d4Jrw2W7UryTHwZr1zQVNk4dAFyqE6BuMrMh1Za7uhp'
|
||||
|
||||
assert app.hex(XPRV, 0, 32) == 'ea3ceb0b02ee8e587779c63f4b7b3a21e950a213f1ec53cab608d13e8796e6dc'
|
||||
|
||||
if __name__ == "__main__":
|
||||
__main__()
|
||||
|
||||
pytest.main()
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user