Skip to content

Commit 0939b68

Browse files
committed
bip32: fixup serialization
We wouldn't save the position of our master node in a potential already-existing tree.
1 parent acb94d6 commit 0939b68

3 files changed

Lines changed: 50 additions & 20 deletions

File tree

bip32/bip32.py

Lines changed: 32 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111

1212

1313
class BIP32:
14-
def __init__(self, chaincode, privkey=None, pubkey=None):
14+
def __init__(self, chaincode, privkey=None, pubkey=None, fingerprint=None,
15+
depth=0, index=0):
1516
"""
1617
:param chaincode: The master chaincode, used to derive keys. As bytes.
1718
:param privkey: The master private key for this index (default 0).
@@ -20,6 +21,13 @@ def __init__(self, chaincode, privkey=None, pubkey=None):
2021
:param pubkey: The master public key for this index (default 0).
2122
Can be None if private key is specified.
2223
Compressed format. As bytes.
24+
:param fingeprint: If we are instanciated from an xpub/xpriv, we need
25+
to remember the parent's pubkey fingerprint to
26+
reserialize !
27+
:param depth: If we are instanciated from an existing extended key, we
28+
need this for serialization.
29+
:param index: If we are instanciated from an existing extended key, we
30+
need this for serialization.
2331
"""
2432
assert isinstance(chaincode, bytes)
2533
assert privkey is not None or pubkey is not None
@@ -32,6 +40,9 @@ def __init__(self, chaincode, privkey=None, pubkey=None):
3240
self.master_chaincode = chaincode
3341
self.master_privkey = privkey
3442
self.master_pubkey = pubkey
43+
self.parent_fingerprint = fingerprint
44+
self.depth = depth
45+
self.index = index
3546

3647
def get_extended_privkey_from_path(self, path):
3748
"""Get an extended privkey from a list of indexes (path).
@@ -111,7 +122,7 @@ def get_xpriv_from_path(self, path):
111122
else:
112123
parent_pubkey = self.get_pubkey_from_path(path[:-1])
113124
chaincode, privkey = self.get_extended_privkey_from_path(path)
114-
extended_key = _serialize_extended_key(privkey, len(path),
125+
extended_key = _serialize_extended_key(privkey, self.depth + len(path),
115126
parent_pubkey,
116127
path[-1], chaincode)
117128
return base58.b58encode_check(extended_key).decode()
@@ -130,21 +141,27 @@ def get_xpub_from_path(self, path):
130141
else:
131142
parent_pubkey = self.get_pubkey_from_path(path[:-1])
132143
chaincode, pubkey = self.get_extended_pubkey_from_path(path)
133-
extended_key = _serialize_extended_key(pubkey, len(path),
144+
extended_key = _serialize_extended_key(pubkey, self.depth + len(path),
134145
parent_pubkey,
135146
path[-1], chaincode)
136147
return base58.b58encode_check(extended_key).decode()
137148

138149
def get_master_xpriv(self):
139150
"""Get the encoded extended private key of the master private key"""
140-
extended_key = _serialize_extended_key(self.master_privkey, 0,
141-
None, 0, self.master_chaincode)
151+
extended_key = _serialize_extended_key(self.master_privkey, self.depth,
152+
self.parent_fingerprint,
153+
self.index,
154+
self.master_chaincode)
155+
print(extended_key)
156+
print(base58.b58encode_check(extended_key))
142157
return base58.b58encode_check(extended_key).decode()
143158

144159
def get_master_xpub(self):
145160
"""Get the encoded extended public key of the master public key"""
146-
extended_key = _serialize_extended_key(self.master_pubkey, 0,
147-
None, 0, self.master_chaincode)
161+
extended_key = _serialize_extended_key(self.master_pubkey, self.depth,
162+
self.parent_fingerprint,
163+
self.index,
164+
self.master_chaincode)
148165
return base58.b58encode_check(extended_key).decode()
149166

150167
@classmethod
@@ -153,11 +170,16 @@ def from_xpriv(cls, xpriv):
153170
154171
:param xpriv: (str) The encoded serialized extended private key.
155172
"""
173+
print(xpriv)
156174
extended_key = base58.b58decode_check(xpriv)
157175
(prefix, depth, fingerprint,
158176
index, chaincode, key) = _unserialize_extended_key(extended_key)
177+
serialized = _serialize_extended_key(key[1:], depth, fingerprint, index,
178+
chaincode)
179+
print(extended_key, serialized, extended_key == serialized)
180+
print(base58.b58encode_check(extended_key), base58.b58encode_check(serialized), extended_key == serialized)
159181
# We need to remove the trailing `0` before the actual private key !!
160-
return BIP32(chaincode, privkey=key[1:], pubkey=None)
182+
return BIP32(chaincode, key[1:], None, fingerprint, depth, index)
161183

162184
@classmethod
163185
def from_xpub(cls, xpub):
@@ -168,7 +190,7 @@ def from_xpub(cls, xpub):
168190
extended_key = base58.b58decode_check(xpub)
169191
(prefix, depth, fingerprint,
170192
index, chaincode, key) = _unserialize_extended_key(extended_key)
171-
return BIP32(chaincode, privkey=None, pubkey=key)
193+
return BIP32(chaincode, None, key, fingerprint, depth, index)
172194

173195
@classmethod
174196
def from_seed(cls, seed):
@@ -178,4 +200,4 @@ def from_seed(cls, seed):
178200
"""
179201
secret = hmac.new("Bitcoin seed".encode(), seed,
180202
hashlib.sha512).digest()
181-
return BIP32(secret[32:], secret[:32], None)
203+
return BIP32(secret[32:], secret[:32])

bip32/utils.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,15 +104,15 @@ def _pubkey_to_fingerprint(pubkey):
104104
return rip.digest()[:4]
105105

106106

107-
def _serialize_extended_key(key, depth, parent_pubkey, index, chaincode,
107+
def _serialize_extended_key(key, depth, parent, index, chaincode,
108108
network="main"):
109109
"""Serialize an extended private *OR* public key, as spec by bip-0032.
110110
111111
:param key: The public or private key to serialize. Note that if this is
112112
a public key it MUST be compressed.
113113
:param depth: 0x00 for master nodes, 0x01 for level-1 derived keys, etc..
114-
:param parent_pubkey: The parent pubkey used to derive the fingerprint.
115-
None if master.
114+
:param parent: The parent pubkey used to derive the fingerprint, or the
115+
fingerprint itself None if master.
116116
:param index: The index of the key being serialized. 0x00000000 if master.
117117
:param chaincode: The chain code (not the labs !!).
118118
@@ -122,9 +122,15 @@ def _serialize_extended_key(key, depth, parent_pubkey, index, chaincode,
122122
assert isinstance(param, bytes)
123123
for param in {depth, index}:
124124
assert isinstance(param, int)
125-
if parent_pubkey:
126-
assert isinstance(parent_pubkey, bytes) and len(parent_pubkey) == 33
127-
fingerprint = _pubkey_to_fingerprint(parent_pubkey)
125+
if parent:
126+
assert isinstance(parent, bytes)
127+
if len(parent) == 33:
128+
fingerprint = _pubkey_to_fingerprint(parent)
129+
elif len(parent) == 4:
130+
fingerprint = parent
131+
else:
132+
raise ValueError("Bad parent, a fingerprint or a pubkey is"
133+
" required")
128134
else:
129135
fingerprint = bytes(4) # master
130136
# A privkey or a compressed pubkey
@@ -155,12 +161,11 @@ def _unserialize_extended_key(extended_key):
155161
assert isinstance(extended_key, bytes) and len(extended_key) == 78
156162
prefix = int.from_bytes(extended_key[:4], "big")
157163
depth = extended_key[4]
158-
fingerprint = extended_key[4:9]
164+
fingerprint = extended_key[5:9]
159165
index = int.from_bytes(extended_key[9:13], "big")
160166
chaincode, key = extended_key[13:45], extended_key[45:]
161167
return prefix, depth, fingerprint, index, chaincode, key
162168

163169

164170
def _hardened_index_in_path(path):
165171
return len([i for i in path if i & HARDENED_INDEX]) > 0
166-

tests/test_bip32.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,5 +94,8 @@ def test_sanity_tests():
9494
assert xpub == "xpub6FXqzrT8Uh8gs81aM7xMJPzAEacxEUg7aC8yA4xz4xmPfEoKxJdVT9Hdwm3LwVQrSos2rhGDt8aGGHvdLr5LLAjK8pXFkbSpzGoGTXjd4z9"
9595
# Now if we our master is m/0'/0'/14, we should derive the same keys for
9696
# m/0'/18 !
97-
bip32 = BIP32.from_xpriv(bip32.get_xpriv_from_path([HARDENED_INDEX,
98-
HARDENED_INDEX, 14]))
97+
xpriv2 = bip32.get_xpriv_from_path([HARDENED_INDEX, HARDENED_INDEX, 14])
98+
assert xpriv2 == "xprv9yQJmvQMywM5i7UNuZ4RQ1A9rEMwAJCExPardkmBCB46S3vBqNEatSwLUrwLNLHBu1Kd9aGxGKDD5YAfs6hRzpYthciAHjtGadxgV2PeqY9"
99+
bip32 = BIP32.from_xpriv(xpriv2)
100+
assert bip32.get_master_xpriv() == xpriv2
101+
assert bip32.get_xpriv_from_path([HARDENED_INDEX, 18]) == xpriv

0 commit comments

Comments
 (0)