zcash-test-vectors/zcash_test_vectors/transparent/bip_0032.py

280 lines
12 KiB
Python

#!/usr/bin/env python3
import sys; assert sys.version_info[0] >= 3, "Python 3 required."
from binascii import hexlify, unhexlify
import base58
import hashlib
import hmac
import re
from ripemd import ripemd160
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 i2beosp
class ExtendedSecretKey:
def __init__(self, chaincode, sk):
assert len(chaincode) == 32
assert isinstance(sk, PrivateKey)
self.chaincode = chaincode
self.sk = sk
@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(I_R, sk)
def __bytes__(self):
# The extra zero byte is specified in
# <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki#serialization-format>.
return self.chaincode + b'\x00' + self.sk.private_key
def public_key(self):
return ExtendedPublicKey(self.chaincode, self.sk.pubkey)
def child(self, i):
assert 0 <= i and i <= 0xFFFFFFFF
if i >= 0x80000000:
I = hmac.digest(self.chaincode, b'\x00' + self.sk.private_key + i2beosp(32, i), 'sha512')
else:
I = hmac.digest(self.chaincode, self.sk.pubkey.serialize(compressed=True) + i2beosp(32, i), 'sha512')
I_L = I[:32]
I_R = I[32:]
sk_i = PrivateKey(self.sk.tweak_add(I_L), True)
child_i = self.__class__(I_R, sk_i)
if i < 0x80000000:
assert bytes(self.public_key().child(i)) == bytes(child_i.public_key())
return child_i
class ExtendedPublicKey:
def __init__(self, chaincode, pk):
assert len(chaincode) == 32
assert isinstance(pk, PublicKey)
self.chaincode = chaincode
self.pk = pk
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):
h = ripemd160.new()
h.update(hashlib.sha256(self.pubkey_bytes()).digest())
return h.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) + i2beosp(32, i), 'sha512')
I_L = I[:32]
I_R = I[32:]
pk_i = self.pk.tweak_add(I_L)
return self.__class__(I_R, pk_i)
def derive_ovks(self):
return derive_ovks(self.chaincode, self.pk.serialize(compressed=True))
# Test vectors from <https://github.com/bitcoin/bips/blob/master/bip-0032.mediawiki>.
BIP32_TEST_VECTORS = [
{
'seed': unhexlify("000102030405060708090a0b0c0d0e0f"),
'path': 'm/0H/1/2H/2/1000000000',
'vectors': [
{
'path': 'm',
'ext_pub': b'xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8',
'ext_prv': b'xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi',
},
{
'path': 'm/0H',
'ext_pub': b'xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw',
'ext_prv': b'xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7',
},
{
'path': 'm/0H/1',
'ext_pub': b'xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ',
'ext_prv': b'xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs',
},
{
'path': 'm/0H/1/2H',
'ext_pub': b'xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5',
'ext_prv': b'xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM',
},
{
'path': 'm/0H/1/2H/2',
'ext_pub': b'xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV',
'ext_prv': b'xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334',
},
{
'path': 'm/0H/1/2H/2/1000000000',
'ext_pub': b'xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy',
'ext_prv': b'xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76',
}
]
},
{
'seed': unhexlify("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542"),
'path': 'm/0/2147483647H/1/2147483646H/2',
'vectors': [
{
'path': 'm',
'ext_pub': b'xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB',
'ext_prv': b'xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U',
},
{
'path': 'm/0',
'ext_pub': b'xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH',
'ext_prv': b'xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt',
},
{
'path': 'm/0/2147483647H',
'ext_pub': b'xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a',
'ext_prv': b'xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9',
},
{
'path': 'm/0/2147483647H/1',
'ext_pub': b'xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon',
'ext_prv': b'xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef',
},
{
'path': 'm/0/2147483647H/1/2147483646H',
'ext_pub': b'xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL',
'ext_prv': b'xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc',
},
{
'path': 'm/0/2147483647H/1/2147483646H/2',
'ext_pub': b'xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt',
'ext_prv': b'xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j',
},
]
},
# These vectors test for the retention of leading zeros. See bitpay/bitcore-lib#47 and iancoleman/bip39#58 for more information.
{
'seed': unhexlify("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be"),
'path': 'm/0H',
'vectors': [
{
'path': 'm',
'ext_pub': b'xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13',
'ext_prv': b'xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6',
},
{
'path': 'm/0H',
'ext_pub': b'xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y',
'ext_prv': b'xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L',
},
]
},
# These vectors test for the retention of leading zeros. See btcsuite/btcutil#172 for more information.
{
'seed': unhexlify("3ddd5602285899a946114506157c7997e5444528f3003f6134712147db19b678"),
'path': 'm/0H/1H',
'vectors': [
{
'path': 'm',
'ext_pub': b'xpub661MyMwAqRbcGczjuMoRm6dXaLDEhW1u34gKenbeYqAix21mdUKJyuyu5F1rzYGVxyL6tmgBUAEPrEz92mBXjByMRiJdba9wpnN37RLLAXa',
'ext_prv': b'xprv9s21ZrQH143K48vGoLGRPxgo2JNkJ3J3fqkirQC2zVdk5Dgd5w14S7fRDyHH4dWNHUgkvsvNDCkvAwcSHNAQwhwgNMgZhLtQC63zxwhQmRv',
},
{
'path': 'm/0H',
'ext_pub': b'xpub69AUMk3qDBi3uW1sXgjCmVjJ2G6WQoYSnNHyzkmdCHEhSZ4tBok37xfFEqHd2AddP56Tqp4o56AePAgCjYdvpW2PU2jbUPFKsav5ut6Ch1m',
'ext_prv': b'xprv9vB7xEWwNp9kh1wQRfCCQMnZUEG21LpbR9NPCNN1dwhiZkjjeGRnaALmPXCX7SgjFTiCTT6bXes17boXtjq3xLpcDjzEuGLQBM5ohqkao9G',
},
{
'path': 'm/0H/1H',
'ext_pub': b'xpub6BJA1jSqiukeaesWfxe6sNK9CCGaujFFSJLomWHprUL9DePQ4JDkM5d88n49sMGJxrhpjazuXYWdMf17C9T5XnxkopaeS7jGk1GyyVziaMt',
'ext_prv': b'xprv9xJocDuwtYCMNAo3Zw76WENQeAS6WGXQ55RCy7tDJ8oALr4FWkuVoHJeHVAcAqiZLE7Je3vZJHxspZdFHfnBEjHqU5hG1Jaj32dVoS6XLT1',
}
]
}
]
def to_zip32_key_bytes(key_str):
decoded = base58.b58decode_check(key_str)
return decoded[13:]
def assert_keys_match(prv, pub, v):
assert bytes(prv) == to_zip32_key_bytes(v['ext_prv']), (hexlify(bytes(prv)), hexlify(to_zip32_key_bytes(v['ext_prv'])))
assert bytes(pub) == to_zip32_key_bytes(v['ext_pub']), (hexlify(bytes(pub)), hexlify(to_zip32_key_bytes(v['ext_pub'])))
def verify_test_vectors(obj):
seed = obj['seed']
prv = ExtendedSecretKey.master(seed)
pub = prv.public_key()
steps = obj['path'].split('/')
step_pattern = re.compile(r'(\d+)(H?)')
for step, v in zip(steps, obj['vectors']):
if step == 'm':
assert_keys_match(prv, pub, v)
else:
step_parts = step_pattern.match(step)
i = int(step_parts.group(1))
if len(step_parts.group(2)) > 0:
i = hardened(i)
prv = prv.child(i)
pub = prv.public_key()
assert_keys_match(prv, pub, v)
def main():
args = render_args()
for o in BIP32_TEST_VECTORS:
verify_test_vectors(o)
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()