-
Notifications
You must be signed in to change notification settings - Fork 0
/
HDDerivation.py
206 lines (153 loc) · 6.89 KB
/
HDDerivation.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
import hashlib
import hmac
import base58
import ECDSAUnit
import ToolsUnit
# number of points on the curve we can hit ("order")
n = 115792089237316195423570985008687907852837564279074904382605163141518161494337
# from BIP-32
salt = "Bitcoin seed"
# Serialize a 32-bit unsigned integer i as a 4-byte sequence, most significant byte first.
def ser32(i: int):
return i.to_bytes(4, 'big')
# Serializes the integer i as a 32-byte sequence, most significant byte first.
def ser256(i: int):
return i.to_bytes(32, 'big')
# Serializes the coordinate pair P = (x,y) as a byte sequence using SEC1's compressed form:
# (0x02 or 0x03) || ser256(x), where the header byte depends on the parity of the omitted y coordinate.
def serP(P):
px, py = P
if py % 2 == 0:
return b'\x02' + ser256(px)
else:
return b'\x03' + ser256(px)
# Interprets a 32-byte sequence as a 256-bit number, most significant byte first.
def parse256(p: bytes):
return int.from_bytes(p, 'big')
# Interprets a byte sequence as a int number, most significant byte first
def parse(byte: bytes):
return parse256(byte)
# Create a fingerprint of the serialized pub_key in input (starts w/ 02 or 03)
# Input: bytes
# Output: bytes
def fingerprint(pub_key_serialized):
return ToolsUnit.hash160(pub_key_serialized)[:4]
# Serialization of extended priv keys (priv key + chain code)
# 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
# 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
# 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
# 4 bytes: child number. (0x00000000 if master key)
# 32 bytes: the chain code
# 33 bytes: the public key or private key data
def ser_extended_priv_keys(k: int, chain_code: bytes, index: int, level=0, parent_ser_pub_key=b'\x00', master_key='False',
mainnet='True'):
if mainnet == 'True':
version_byte = b'\x04\x88\xAD\xE4'
else:
version_byte = b'\x04\x35\x83\x94'
depth = level.to_bytes(1, 'big')
if master_key == 'True':
finger_print = b'\x00\x00\x00\x00'
else:
finger_print = fingerprint(parent_ser_pub_key)
child_number = ser32(index)
key = version_byte + depth + finger_print + child_number + chain_code + b'\x00' + ser256(k)
return base58.b58encode_check(key)
# De-serialize serialized extended private key
def parse_extended_priv_key(xpriv: bytes):
decode_key = base58.b58decode_check(xpriv)
version_byte = decode_key[0:4].hex()
depth = parse(decode_key[4:5])
finger_print = decode_key[5:9].hex()
child_number = parse(decode_key[9:13])
chain_code = decode_key[13:45]
private_key = parse256(decode_key[47:]) # byte 45-45 are b'\x00\x00' -> discarded
return version_byte, depth, finger_print, child_number, chain_code, private_key
# Serialization of extended pub keys (pub key + chain code)
# 4 byte: version bytes (mainnet: 0x0488B21E public, 0x0488ADE4 private; testnet: 0x043587CF public, 0x04358394 private)
# 1 byte: depth: 0x00 for master nodes, 0x01 for level-1 derived keys, ....
# 4 bytes: the fingerprint of the parent's key (0x00000000 if master key)
# 4 bytes: child number. (0x00000000 if master key)
# 32 bytes: the chain code
# 33 bytes: the public key or private key data
def ser_extended_pub_keys(K: (int, int), chain_code: bytes, index: int, level=0, parent_ser_pub_key=b'\x00',
master_key='False', mainnet='True'):
if mainnet == 'True':
version_byte = b'\x04\x88\xB2\x1E'
else:
version_byte = b'\x04\x35\x87\xCF'
depth = level.to_bytes(1, 'big')
if master_key == 'True':
finger_print = b'\x00\x00\x00\x00'
else:
finger_print = fingerprint(parent_ser_pub_key)
child_number = ser32(index)
key = version_byte + depth + finger_print + child_number + chain_code + serP(K)
return base58.b58encode_check(key)
# De-serialize serialized extended public key
def parse_extended_pubkey(xpub: bytes):
decode_key = base58.b58decode_check(xpub)
version_byte = decode_key[0:4].hex()
depth = parse(decode_key[4:5])
finger_print = decode_key[5:9].hex()
child_number = parse(decode_key[9:13])
chain_code = decode_key[13:45]
ser_public_key = bytes.hex(decode_key[45:]) # starts w/ 02 or 03
return version_byte, depth, finger_print, child_number, chain_code, ser_public_key
# Generation of extended keys (not serialized) from seed (in bytes)
def master_key_generation(seed_bytes: bytes):
a = hmac.new(salt.encode(), seed_bytes, hashlib.sha512).digest()
aL = a[0:32]
aR = a[32:]
master_secret_key = parse256(aL)
master_chain_code = aR
return master_secret_key, master_chain_code
# The function CKDpriv(k_par, c_par, index) → (k_i, c_i) computes a child
# extended private key from the parent extended private key (not serialized)
def CKDpriv(xpriv, index):
k_par, c_par = xpriv
# check if it's hardened derivation (index >= 2**31)
if index >= 2 ** 31:
h = hmac.new(c_par, b'\x00' + ser256(k_par) + ser32(index), hashlib.sha512).digest()
else:
h = hmac.new(c_par, serP(ECDSAUnit.multiply(k_par)) + ser32(index), hashlib.sha512).digest()
hL = h[0:32]
hR = h[32:]
k_i = (parse256(hL) + k_par) % n
c_i = hR
if parse256(hL) >= n or k_i == 0:
return IOError("ERRORE, NON è VALIDO!")
xpriv_child = k_i, c_i
return xpriv_child
# The function CKDpub(K_par_x, K_par_y, c_par, index) → (K_i, c_i) computes a
# child extended public key from the parent extended public key.
# It is only defined for non-hardened child keys.
# K = pub key in the point coordinate form (not serialized).
def CKDpub(xpub, index):
K_par, c_par = xpub
# Check if i ≥ 2**31, return error if it's true
if index >= 2 ** 31:
return IOError("CKDpub è definita solo per non-hardened keys")
h = hmac.new(c_par, serP(K_par) + ser32(index))
hL = h[0:32]
hR = h[32:]
K_i = ECDSAUnit.multiply(parse256(hL)) + K_par
c_i = hR
if parse256(hL) >= n:
return IOError("ERRORE, NON è VALIDO!")
xpub_child = K_i, c_i
return xpub_child
# Execute some tests
seed_hex = "000102030405060708090a0b0c0d0e0f" # example test
k_master, c_master = master_key_generation(bytes.fromhex(seed_hex))
K_master = ECDSAUnit.multiply(k_master)
K_master_ser = serP(K_master)
ser = ser_extended_priv_keys(k_master, c_master, 0, master_key='True')
serpub = ser_extended_pub_keys(K_master, c_master, 0, 0, master_key='True')
xpriv_m_0h_1_2h = CKDpriv(CKDpriv(CKDpriv((k_master, c_master), 2**31), 1), 2**31 + 2)
k_m_0h_1_2h, c_m_0h_1_2h = xpriv_m_0h_1_2h
K_m_0h_1_2h = ECDSAUnit.multiply(k_m_0h_1_2h)
K_ser_m_0h_1_2h = serP(K_m_0h_1_2h)
k_m_0h_1_2h_2, c_m_0h_1_2h_2 = CKDpriv(xpriv_m_0h_1_2h, 2)
xpriv_ser_m_0h_1_2h = ser_extended_priv_keys(k_m_0h_1_2h_2, c_m_0h_1_2h_2, 2, 4, K_ser_m_0h_1_2h)
# print(xpriv_ser_m_0h_1_2h)