124 lines
3.5 KiB
Python
124 lines
3.5 KiB
Python
#!/usr/bin/env python3
|
|
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
|
|
|
|
import hashlib
|
|
import hmac
|
|
from secp256k1 import PrivateKey, PublicKey
|
|
|
|
from .zip_0316 import derive_ovks
|
|
|
|
from ..hd_common import ZCASH_MAIN_COINTYPE, hardened
|
|
from ..output import render_args, render_tv
|
|
from ..utils import i2leosp
|
|
|
|
|
|
class ExtendedSecretKey:
|
|
def __init__(self, sk, chaincode):
|
|
assert isinstance(sk, PrivateKey)
|
|
assert len(chaincode) == 32
|
|
self.sk = sk
|
|
self.chaincode = chaincode
|
|
|
|
@classmethod
|
|
def master(cls, S):
|
|
I = hmac.digest(b'Bitcoin seed', S, 'sha512')
|
|
I_L = I[:32]
|
|
I_R = I[32:]
|
|
sk = PrivateKey(I_L, True)
|
|
return cls(sk, I_R)
|
|
|
|
def __bytes__(self):
|
|
return self.chaincode + self.sk.private_key
|
|
|
|
def public_key(self):
|
|
return ExtendedPublicKey(self.sk.pubkey, self.chaincode)
|
|
|
|
def child(self, i):
|
|
assert 0 <= i and i <= 0xFFFFFFFF
|
|
|
|
if i >= 0x80000000:
|
|
I = hmac.digest(self.chaincode, b'\x00' + self.sk.private_key + i2leosp(32, i), 'sha512')
|
|
else:
|
|
I = hmac.digest(self.chaincode, self.sk.pubkey.serialize(compressed=True) + i2leosp(32, i), 'sha512')
|
|
|
|
I_L = I[:32]
|
|
I_R = I[32:]
|
|
sk_i = PrivateKey(self.sk.tweak_add(I_L), True)
|
|
return self.__class__(sk_i, I_R)
|
|
|
|
|
|
class ExtendedPublicKey:
|
|
def __init__(self, pk, chaincode):
|
|
assert isinstance(pk, PublicKey)
|
|
assert len(chaincode) == 32
|
|
|
|
self.pk = pk
|
|
self.chaincode = chaincode
|
|
|
|
def pubkey_bytes(self):
|
|
pk_bytes = self.pk.serialize(compressed=True)
|
|
assert len(pk_bytes) == 33
|
|
assert pk_bytes[0] in (0x02, 0x03)
|
|
return pk_bytes
|
|
|
|
def __bytes__(self):
|
|
return self.chaincode + self.pubkey_bytes()
|
|
|
|
def address(self):
|
|
ripemd160 = hashlib.new('ripemd160')
|
|
ripemd160.update(hashlib.sha256(self.pubkey_bytes()).digest())
|
|
return ripemd160.digest()
|
|
|
|
def child(self, i):
|
|
assert 0 <= i and i <= 0xFFFFFFFF
|
|
|
|
assert i < 0x80000000, "cannot derive a hardened child from a public key"
|
|
I = hmac.digest(self.chaincode, self.pk.serialize(compressed=True) + i2leosp(32, i), 'sha512')
|
|
I_L = I[:32]
|
|
I_R = I[32:]
|
|
pk_i = self.pk.tweak_add(I_L)
|
|
return self.__class__(pk_i, I_R)
|
|
|
|
def derive_ovks(self):
|
|
return derive_ovks(self.chaincode, self.pk.serialize(compressed=True))
|
|
|
|
|
|
def main():
|
|
args = render_args()
|
|
|
|
seed = bytes(range(32))
|
|
root_key = ExtendedSecretKey.master(seed)
|
|
purpose_key = root_key.child(hardened(44))
|
|
coin_key = purpose_key.child(hardened(ZCASH_MAIN_COINTYPE))
|
|
|
|
test_vectors = []
|
|
for account in range(10):
|
|
account_key = coin_key.child(hardened(account))
|
|
pubkey = account_key.public_key()
|
|
(external_ovk, internal_ovk) = pubkey.derive_ovks()
|
|
test_vectors.append({
|
|
'c' : pubkey.chaincode,
|
|
'pk': pubkey.pk.serialize(compressed=True),
|
|
'address': pubkey.address(),
|
|
'external_ovk': external_ovk,
|
|
'internal_ovk': internal_ovk,
|
|
'account': account,
|
|
})
|
|
|
|
render_tv(
|
|
args,
|
|
'bip_0032',
|
|
(
|
|
('c', '[u8; 32]'),
|
|
('pk', '[u8; 33]'),
|
|
('address', '[u8; 20]'),
|
|
('external_ovk', '[u8; 32]'),
|
|
('internal_ovk', '[u8; 32]'),
|
|
('account', 'u32'),
|
|
),
|
|
test_vectors,
|
|
)
|
|
|
|
if __file__ == '__main__':
|
|
main()
|