Add bip32.py and tests.
This commit is contained in:
parent
7a77637586
commit
9dfaedc727
61
lib/coins.py
61
lib/coins.py
@ -42,8 +42,9 @@ from lib.script import ScriptPubKey
|
||||
from lib.tx import Deserializer, DeserializerSegWit, DeserializerAuxPow, \
|
||||
DeserializerZcash, DeserializerTxTime, DeserializerReddcoin
|
||||
from server.block_processor import BlockProcessor
|
||||
from server.daemon import Daemon, LegacyRPCDaemon
|
||||
from server.session import ElectrumX
|
||||
from server.daemon import Daemon, DashDaemon, LegacyRPCDaemon
|
||||
from server.session import ElectrumX, DashElectrumX
|
||||
|
||||
|
||||
Block = namedtuple("Block", "header transactions")
|
||||
|
||||
@ -67,6 +68,8 @@ class Coin(object):
|
||||
DESERIALIZER = Deserializer
|
||||
DAEMON = Daemon
|
||||
BLOCK_PROCESSOR = BlockProcessor
|
||||
XPUB_VERBYTES = bytes('????', 'utf-8')
|
||||
XPRV_VERBYTES = bytes('????', 'utf-8')
|
||||
IRC_PREFIX = None
|
||||
IRC_SERVER = "irc.freenode.net"
|
||||
IRC_PORT = 6667
|
||||
@ -153,7 +156,7 @@ class Coin(object):
|
||||
def lookup_xverbytes(verbytes):
|
||||
'''Return a (is_xpub, coin_class) pair given xpub/xprv verbytes.'''
|
||||
# Order means BTC testnet will override NMC testnet
|
||||
for coin in Coin.coin_classes():
|
||||
for coin in util.subclasses(Coin):
|
||||
if verbytes == coin.XPUB_VERBYTES:
|
||||
return True, coin
|
||||
if verbytes == coin.XPRV_VERBYTES:
|
||||
@ -229,7 +232,7 @@ class Coin(object):
|
||||
raise CoinError('invalid address: {}'.format(address))
|
||||
|
||||
@classmethod
|
||||
def prvkey_WIF(cls, privkey_bytes, compressed):
|
||||
def privkey_WIF(cls, privkey_bytes, compressed):
|
||||
'''Return the private key encoded in Wallet Import Format.'''
|
||||
payload = bytearray(cls.WIF_BYTE) + privkey_bytes
|
||||
if compressed:
|
||||
@ -299,10 +302,7 @@ class Coin(object):
|
||||
}
|
||||
|
||||
|
||||
class CoinAuxPow(Coin):
|
||||
# Set NAME and NET to avoid exception in Coin::lookup_coin_class
|
||||
NAME = ''
|
||||
NET = ''
|
||||
class AuxPowMixin(object):
|
||||
STATIC_BLOCK_HEADERS = False
|
||||
DESERIALIZER = DeserializerAuxPow
|
||||
|
||||
@ -411,8 +411,8 @@ class Litecoin(Coin):
|
||||
NAME = "Litecoin"
|
||||
SHORTNAME = "LTC"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
XPUB_VERBYTES = bytes.fromhex("019d9cfe")
|
||||
XPRV_VERBYTES = bytes.fromhex("019da462")
|
||||
P2PKH_VERBYTE = bytes.fromhex("30")
|
||||
P2SH_VERBYTES = [bytes.fromhex("32"), bytes.fromhex("05")]
|
||||
WIF_BYTE = bytes.fromhex("b0")
|
||||
@ -437,8 +437,8 @@ class Litecoin(Coin):
|
||||
class LitecoinTestnet(Litecoin):
|
||||
SHORTNAME = "XLT"
|
||||
NET = "testnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
||||
XPRV_VERBYTES = bytes.fromhex("04358394")
|
||||
XPUB_VERBYTES = bytes.fromhex("0436ef7d")
|
||||
XPRV_VERBYTES = bytes.fromhex("0436f6e1")
|
||||
P2PKH_VERBYTE = bytes.fromhex("6f")
|
||||
P2SH_VERBYTES = [bytes.fromhex("3a"), bytes.fromhex("c4")]
|
||||
WIF_BYTE = bytes.fromhex("ef")
|
||||
@ -456,12 +456,10 @@ class LitecoinTestnet(Litecoin):
|
||||
]
|
||||
|
||||
|
||||
class Viacoin(CoinAuxPow):
|
||||
class Viacoin(AuxPowMixin, Coin):
|
||||
NAME="Viacoin"
|
||||
SHORTNAME = "VIA"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488B21E")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ADE4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("47")
|
||||
P2SH_VERBYTES = [bytes.fromhex("21")]
|
||||
WIF_BYTE = bytes.fromhex("c7")
|
||||
@ -485,8 +483,6 @@ class Viacoin(CoinAuxPow):
|
||||
class ViacoinTestnet(Viacoin):
|
||||
SHORTNAME = "TVI"
|
||||
NET = "testnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("043587CF")
|
||||
XPRV_VERBYTES = bytes.fromhex("04358394")
|
||||
P2PKH_VERBYTE = bytes.fromhex("7f")
|
||||
P2SH_VERBYTES = [bytes.fromhex("c4")]
|
||||
WIF_BYTE = bytes.fromhex("ff")
|
||||
@ -505,7 +501,7 @@ class ViacoinTestnetSegWit(ViacoinTestnet):
|
||||
|
||||
|
||||
# Source: namecoin.org
|
||||
class Namecoin(CoinAuxPow):
|
||||
class Namecoin(AuxPowMixin, Coin):
|
||||
NAME = "Namecoin"
|
||||
SHORTNAME = "NMC"
|
||||
NET = "mainnet"
|
||||
@ -527,8 +523,6 @@ class NamecoinTestnet(Namecoin):
|
||||
NAME = "Namecoin"
|
||||
SHORTNAME = "XNM"
|
||||
NET = "testnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
||||
XPRV_VERBYTES = bytes.fromhex("04358394")
|
||||
P2PKH_VERBYTE = bytes.fromhex("6f")
|
||||
P2SH_VERBYTES = [bytes.fromhex("c4")]
|
||||
WIF_BYTE = bytes.fromhex("ef")
|
||||
@ -536,7 +530,7 @@ class NamecoinTestnet(Namecoin):
|
||||
'a4cccff2a4767a8eee39c11db367b008')
|
||||
|
||||
|
||||
class Dogecoin(CoinAuxPow):
|
||||
class Dogecoin(AuxPowMixin, Coin):
|
||||
NAME = "Dogecoin"
|
||||
SHORTNAME = "DOGE"
|
||||
NET = "mainnet"
|
||||
@ -559,8 +553,6 @@ class DogecoinTestnet(Dogecoin):
|
||||
NAME = "Dogecoin"
|
||||
SHORTNAME = "XDT"
|
||||
NET = "testnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
||||
XPRV_VERBYTES = bytes.fromhex("04358394")
|
||||
P2PKH_VERBYTE = bytes.fromhex("71")
|
||||
P2SH_VERBYTES = [bytes.fromhex("c4")]
|
||||
WIF_BYTE = bytes.fromhex("f1")
|
||||
@ -570,8 +562,6 @@ class DogecoinTestnet(Dogecoin):
|
||||
|
||||
# Source: https://github.com/dashpay/dash
|
||||
class Dash(Coin):
|
||||
from server.session import DashElectrumX
|
||||
from server.daemon import DashDaemon
|
||||
NAME = "Dash"
|
||||
SHORTNAME = "DASH"
|
||||
NET = "mainnet"
|
||||
@ -627,12 +617,10 @@ class DashTestnet(Dash):
|
||||
]
|
||||
|
||||
|
||||
class Argentum(CoinAuxPow):
|
||||
class Argentum(AuxPowMixin, Coin):
|
||||
NAME = "Argentum"
|
||||
SHORTNAME = "ARG"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("17")
|
||||
P2SH_VERBYTES = [bytes.fromhex("05")]
|
||||
WIF_BYTE = bytes.fromhex("97")
|
||||
@ -649,8 +637,6 @@ class Argentum(CoinAuxPow):
|
||||
class ArgentumTestnet(Argentum):
|
||||
SHORTNAME = "XRG"
|
||||
NET = "testnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
||||
XPRV_VERBYTES = bytes.fromhex("04358394")
|
||||
P2PKH_VERBYTE = bytes.fromhex("6f")
|
||||
P2SH_VERBYTES = [bytes.fromhex("c4")]
|
||||
WIF_BYTE = bytes.fromhex("ef")
|
||||
@ -661,8 +647,6 @@ class DigiByte(Coin):
|
||||
NAME = "DigiByte"
|
||||
SHORTNAME = "DGB"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("1E")
|
||||
P2SH_VERBYTES = [bytes.fromhex("05")]
|
||||
WIF_BYTE = bytes.fromhex("80")
|
||||
@ -679,8 +663,6 @@ class DigiByte(Coin):
|
||||
|
||||
class DigiByteTestnet(DigiByte):
|
||||
NET = "testnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("043587cf")
|
||||
XPRV_VERBYTES = bytes.fromhex("04358394")
|
||||
P2PKH_VERBYTE = bytes.fromhex("6f")
|
||||
P2SH_VERBYTES = [bytes.fromhex("c4")]
|
||||
WIF_BYTE = bytes.fromhex("ef")
|
||||
@ -696,8 +678,6 @@ class FairCoin(Coin):
|
||||
NAME = "FairCoin"
|
||||
SHORTNAME = "FAIR"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("5f")
|
||||
P2SH_VERBYTES = [bytes.fromhex("24")]
|
||||
WIF_BYTE = bytes.fromhex("df")
|
||||
@ -745,8 +725,6 @@ class Zcash(Coin):
|
||||
NAME = "Zcash"
|
||||
SHORTNAME = "ZEC"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("1CB8")
|
||||
P2SH_VERBYTES = [bytes.fromhex("1CBD")]
|
||||
WIF_BYTE = bytes.fromhex("80")
|
||||
@ -789,9 +767,6 @@ class Einsteinium(Coin):
|
||||
NAME = "Einsteinium"
|
||||
SHORTNAME = "EMC2"
|
||||
NET = "mainnet"
|
||||
# TODO add correct values for XPUB, XPRIV
|
||||
XPUB_VERBYTES = bytes.fromhex("0488b21e")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ade4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("21")
|
||||
P2SH_VERBYTES = [bytes.fromhex("05")]
|
||||
WIF_BYTE = bytes.fromhex("a1")
|
||||
@ -810,8 +785,6 @@ class Blackcoin(Coin):
|
||||
NAME = "Blackcoin"
|
||||
SHORTNAME = "BLK"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488B21E")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ADE4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("19")
|
||||
P2SH_VERBYTES = [bytes.fromhex("55")]
|
||||
WIF_BYTE = bytes.fromhex("99")
|
||||
@ -866,8 +839,6 @@ class Reddcoin(Coin):
|
||||
NAME = "Reddcoin"
|
||||
SHORTNAME = "RDD"
|
||||
NET = "mainnet"
|
||||
XPUB_VERBYTES = bytes.fromhex("0488B21E")
|
||||
XPRV_VERBYTES = bytes.fromhex("0488ADE4")
|
||||
P2PKH_VERBYTE = bytes.fromhex("3d")
|
||||
P2SH_VERBYTES = [bytes.fromhex("05")]
|
||||
WIF_BYTE = bytes.fromhex("bd")
|
||||
|
||||
367
tests/wallet/test_bip32.py
Normal file
367
tests/wallet/test_bip32.py
Normal file
@ -0,0 +1,367 @@
|
||||
#
|
||||
# Tests of wallet/bip32.py
|
||||
#
|
||||
|
||||
import pytest
|
||||
|
||||
import wallet.bip32 as bip32
|
||||
from lib.coins import Bitcoin, CoinError
|
||||
from lib.hash import Base58
|
||||
|
||||
|
||||
MXPRV = 'xprv9s21ZrQH143K2gMVrSwwojnXigqHgm1khKZGTCm7K8w4PmuDEUrudk11ZBxhGPUiUeVcrfGLoZmt8rFNRDLp18jmKMcVma89z7PJd2Vn7R9'
|
||||
MPRIVKEY = b';\xf4\xbfH\xd20\xea\x94\x01_\x10\x1b\xc3\xb0\xff\xc9\x17$?K\x02\xe5\x82R\xe5\xb3A\xdb\x87&E\x00'
|
||||
MXPUB = 'xpub661MyMwAqRbcFARxxUUxAsjGGifn6Djc4YUsFbAisUU3GaEMn2BABYKVQTHrDtwvSfgY2bK8aFGyCNmB52SKjkFGP18sSRTNn1sCeez7Utd'
|
||||
|
||||
mpubkey, mpubcoin = bip32.from_extended_key_string(MXPUB)
|
||||
mprivkey, mprivcoin = bip32.from_extended_key_string(MXPRV)
|
||||
|
||||
|
||||
def test_from_extended_key():
|
||||
# Tests the failure modes of from_extended_key.
|
||||
with pytest.raises(TypeError):
|
||||
bip32._from_extended_key('')
|
||||
with pytest.raises(ValueError):
|
||||
bip32._from_extended_key(b'')
|
||||
with pytest.raises(CoinError):
|
||||
bip32._from_extended_key(bytes(78))
|
||||
# Invalid prefix byte
|
||||
raw = Base58.decode_check(MXPRV)
|
||||
with pytest.raises(ValueError):
|
||||
bip32._from_extended_key(raw[:45] + b'\1' + raw[46:])
|
||||
|
||||
|
||||
class TestPubKey(object):
|
||||
|
||||
def test_constructor(self):
|
||||
cls = bip32.PubKey
|
||||
raw_pubkey = b'\2' * 33
|
||||
chain_code = bytes(32)
|
||||
|
||||
# Invalid constructions
|
||||
with pytest.raises(TypeError):
|
||||
cls(' ' * 33, chain_code, 0, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(bytes(32), chain_code, -1, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(bytes(33), chain_code, -1, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(chain_code, chain_code, 0, 0)
|
||||
with pytest.raises(TypeError):
|
||||
cls(raw_pubkey, '0' * 32, 0, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(raw_pubkey, bytes(31), 0, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(raw_pubkey, chain_code, -1, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(raw_pubkey, chain_code, 1 << 32, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(raw_pubkey, chain_code, 0, -1)
|
||||
with pytest.raises(ValueError):
|
||||
cls(raw_pubkey, chain_code, 0, 256)
|
||||
|
||||
# These are OK
|
||||
cls(b'\2' + b'\2' * 32, chain_code, 0, 0)
|
||||
cls(b'\3' + b'\2' * 32, chain_code, 0, 0)
|
||||
cls(raw_pubkey, chain_code, (1 << 32) - 1, 0)
|
||||
cls(raw_pubkey, chain_code, 0, 255)
|
||||
cls(raw_pubkey, chain_code, 0, 255, mpubkey)
|
||||
|
||||
# Construction from verifying key
|
||||
dup = cls(mpubkey.verifying_key, chain_code, 0, 0)
|
||||
assert mpubkey.ec_point() == dup.ec_point()
|
||||
|
||||
# Construction from raw pubkey bytes
|
||||
pubkey = mpubkey.pubkey_bytes
|
||||
dup = cls(pubkey, chain_code, 0, 0)
|
||||
assert mpubkey.ec_point() == dup.ec_point()
|
||||
|
||||
# Construction from PubKey
|
||||
with pytest.raises(TypeError):
|
||||
cls(mpubkey, chain_code, 0, 0)
|
||||
|
||||
def test_from_extended_key_string(self):
|
||||
assert mpubcoin == Bitcoin
|
||||
assert mpubkey.n == 0
|
||||
assert mpubkey.depth == 0
|
||||
assert mpubkey.parent is None
|
||||
assert mpubkey.chain_code == b'>V\x83\x92`\r\x17\xb3"\xa6\x7f\xaf\xc0\x930\xf7\x1e\xdc\x12i\x9c\xe4\xc0,a\x1a\x04\xec\x16\x19\xaeK'
|
||||
assert mpubkey.ec_point().x() == 44977109961578369385937116592536468905742111247230478021459394832226142714624
|
||||
|
||||
def test_extended_key_string(self):
|
||||
# Implictly tests extended_key()
|
||||
assert mpubkey.extended_key_string(Bitcoin) == MXPUB
|
||||
chg_master = mpubkey.child(1)
|
||||
chg5 = chg_master.child(5)
|
||||
assert chg5.address(Bitcoin) == '1BsEFqGtcZnVBbPeimcfAFTitQdTLvUXeX'
|
||||
assert chg5.extended_key_string(Bitcoin) == 'xpub6AzPNZ1SAS7zmSnj6gakQ6tAKPzRVdQzieL3eCnoeT3A89nJaJKuUYWoZuYp8xWhCs1gF9yXAwGg7zKYhvCfhk9jrb1bULhLkQCwtB1Nnn1'
|
||||
|
||||
ext_key_base58 = chg5.extended_key_string(Bitcoin)
|
||||
assert ext_key_base58 == 'xpub6AzPNZ1SAS7zmSnj6gakQ6tAKPzRVdQzieL3eCnoeT3A89nJaJKuUYWoZuYp8xWhCs1gF9yXAwGg7zKYhvCfhk9jrb1bULhLkQCwtB1Nnn1'
|
||||
|
||||
# Check can recreate
|
||||
dup, coin = bip32.from_extended_key_string(ext_key_base58)
|
||||
assert coin is Bitcoin
|
||||
assert dup.chain_code == chg5.chain_code
|
||||
assert dup.n == chg5.n == 5
|
||||
assert dup.depth == chg5.depth == 2
|
||||
assert dup.ec_point() == chg5.ec_point()
|
||||
|
||||
def test_child(self):
|
||||
'''Test child derivations agree with Electrum.'''
|
||||
rec_master = mpubkey.child(0)
|
||||
assert rec_master.address(Bitcoin) == '18zW4D1Vxx9jVPGzsFzgXj8KrSLHt7w2cg'
|
||||
chg_master = mpubkey.child(1)
|
||||
assert chg_master.parent is mpubkey
|
||||
assert chg_master.address(Bitcoin) == '1G8YpbkZd7bySHjpdQK3kMcHhc6BvHr5xy'
|
||||
rec0 = rec_master.child(0)
|
||||
assert rec0.address(Bitcoin) == '13nASW7rdE2dnSycrAP9VePhRmaLg9ziaw'
|
||||
rec19 = rec_master.child(19)
|
||||
assert rec19.address(Bitcoin) == '15QrXnPQ8aS8yCpA5tJkyvXfXpw8F8k3fB'
|
||||
chg0 = chg_master.child(0)
|
||||
assert chg0.parent is chg_master
|
||||
assert chg0.address(Bitcoin) == '1L6fNSVhWjuMKNDigA99CweGEWtcqqhzDj'
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
mpubkey.child(-1)
|
||||
with pytest.raises(ValueError):
|
||||
mpubkey.child(1 << 31)
|
||||
# OK
|
||||
mpubkey.child((1 << 31) - 1)
|
||||
|
||||
def test_address(self):
|
||||
assert mpubkey.address(Bitcoin) == '1ENCpq6mbb1KYcaodGG7eTpSpYvPnDjFmU'
|
||||
|
||||
def test_identifier(self):
|
||||
assert mpubkey.identifier() == b'\x92\x9c=\xb8\xd6\xe7\xebR\x90Td\x85\x1c\xa7\x0c\x8aE`\x87\xdd'
|
||||
|
||||
def test_fingerprint(self):
|
||||
assert mpubkey.fingerprint() == b'\x92\x9c=\xb8'
|
||||
|
||||
def test_parent_fingerprint(self):
|
||||
assert mpubkey.parent_fingerprint() == bytes(4)
|
||||
child = mpubkey.child(0)
|
||||
assert child.parent_fingerprint() == mpubkey.fingerprint()
|
||||
|
||||
def test_pubkey_bytes(self):
|
||||
# Also tests _exponent_to_bytes
|
||||
pubkey = mpubkey.pubkey_bytes
|
||||
assert pubkey == b'\x02cp$a\x18\xa7\xc2\x18\xfdUt\x96\xeb\xb2\xb0\x86-Y\xc6Hn\x88\xf8>\x07\xfd\x12\xce\x8a\x88\xfb\x00'
|
||||
|
||||
|
||||
class TestPrivKey(object):
|
||||
|
||||
def test_constructor(self):
|
||||
# Includes full tests of _signing_key_from_privkey and
|
||||
# _privkey_secret_exponent
|
||||
cls = bip32.PrivKey
|
||||
chain_code = bytes(32)
|
||||
|
||||
# These are invalid
|
||||
with pytest.raises(TypeError):
|
||||
cls('0' * 32, chain_code, 0, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(b'0' * 31, chain_code, 0, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(MPRIVKEY, chain_code, -1, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(MPRIVKEY, chain_code, 1 << 32, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(MPRIVKEY, chain_code, 0, -1)
|
||||
with pytest.raises(ValueError):
|
||||
cls(MPRIVKEY, chain_code, 0, 256)
|
||||
# Invalid exponents
|
||||
with pytest.raises(ValueError):
|
||||
cls(bip32._exponent_to_bytes(0), chain_code, 0, 0)
|
||||
with pytest.raises(ValueError):
|
||||
cls(bip32._exponent_to_bytes(cls.CURVE.order), chain_code, 0, 0)
|
||||
|
||||
# These are good
|
||||
cls(MPRIVKEY, chain_code, 0, 0)
|
||||
cls(MPRIVKEY, chain_code, (1 << 32) - 1, 0)
|
||||
cls(MPRIVKEY, chain_code, 0, 0)
|
||||
cls(bip32._exponent_to_bytes(cls.CURVE.order - 1), chain_code, 0, 0)
|
||||
privkey = cls(MPRIVKEY, chain_code, 0, 255)
|
||||
|
||||
# Construction from signing key
|
||||
dup = cls(privkey.signing_key, chain_code, 0, 0)
|
||||
assert dup.ec_point() == privkey.ec_point()
|
||||
|
||||
# Construction from PrivKey
|
||||
with pytest.raises(TypeError):
|
||||
cls(privkey, chain_code, 0, 0)
|
||||
|
||||
def test_secret_exponent(self):
|
||||
assert mprivkey.secret_exponent() == 27118888947022743980605817563635166434451957861641813930891160184742578898176
|
||||
|
||||
def test_identifier(self):
|
||||
assert mprivkey.identifier() == mpubkey.identifier()
|
||||
|
||||
def test_address(self):
|
||||
assert mprivkey.address(Bitcoin) == mpubkey.address(Bitcoin)
|
||||
|
||||
def test_fingerprint(self):
|
||||
assert mprivkey.fingerprint() == mpubkey.fingerprint()
|
||||
|
||||
def test_parent_fingerprint(self):
|
||||
assert mprivkey.parent_fingerprint() == bytes(4)
|
||||
child = mprivkey.child(0)
|
||||
assert child.parent_fingerprint() == mprivkey.fingerprint()
|
||||
|
||||
def test_from_extended_key_string(self):
|
||||
# Also tests privkey_bytes and public_key
|
||||
assert mprivcoin is Bitcoin
|
||||
assert mprivkey.privkey_bytes == MPRIVKEY
|
||||
assert mprivkey.ec_point() == mpubkey.ec_point()
|
||||
assert mprivkey.public_key.chain_code == mpubkey.chain_code
|
||||
assert mprivkey.public_key.n == mpubkey.n
|
||||
assert mprivkey.public_key.depth == mpubkey.depth
|
||||
|
||||
def test_extended_key_string(self):
|
||||
# Also tests extended_key, WIF and privkey_bytes
|
||||
assert mprivkey.extended_key_string(Bitcoin) == MXPRV
|
||||
chg_master = mprivkey.child(1)
|
||||
chg5 = chg_master.child(5)
|
||||
assert chg5.WIF(Bitcoin) == 'L5kTYMuajTGWdYiMoD4V8k6LS4Bg3HFMA5UGTfxG9Wh7UKu9CHFC'
|
||||
ext_key_base58 = chg5.extended_key_string(Bitcoin)
|
||||
assert ext_key_base58 == 'xprv9x12y3UYL4ZhYxiFzf3k2xwRmN9w6Ah9MRQSqpPC67WBFMTA2m1evkCKidz7UYBa5i8QwxmU9Ju7giqEmcPRXKXwzgAJwssNeZNQLPT3LAY'
|
||||
|
||||
# Check can recreate
|
||||
dup, coin = bip32.from_extended_key_string(ext_key_base58)
|
||||
assert coin is Bitcoin
|
||||
assert dup.chain_code == chg5.chain_code
|
||||
assert dup.n == chg5.n == 5
|
||||
assert dup.depth == chg5.depth == 2
|
||||
assert dup.ec_point() == chg5.ec_point()
|
||||
|
||||
def test_child(self):
|
||||
'''Test child derivations agree with Electrum.'''
|
||||
# Also tests WIF, address
|
||||
rec_master = mprivkey.child(0)
|
||||
assert rec_master.address(Bitcoin) == '18zW4D1Vxx9jVPGzsFzgXj8KrSLHt7w2cg'
|
||||
chg_master = mprivkey.child(1)
|
||||
assert chg_master.parent is mprivkey
|
||||
assert chg_master.address(Bitcoin) == '1G8YpbkZd7bySHjpdQK3kMcHhc6BvHr5xy'
|
||||
rec0 = rec_master.child(0)
|
||||
assert rec0.WIF(Bitcoin) == 'L2M6WWMdu3YfWxvLGF76HZgHCA6idwVQx5QL91vfdqeZi8XAgWkz'
|
||||
rec19 = rec_master.child(19)
|
||||
assert rec19.WIF(Bitcoin) == 'KwMHa1fynU2J2iBGCuBZxumM2qDXHe5tVPU9VecNGQv3UCqnET7X'
|
||||
chg0 = chg_master.child(0)
|
||||
assert chg0.parent is chg_master
|
||||
assert chg0.WIF(Bitcoin) == 'L4J1esD4rYuBHXwjg72yi7Rw4G3iF2yUHt7LN9trpC3snCppUbq8'
|
||||
|
||||
with pytest.raises(ValueError):
|
||||
mprivkey.child(-1)
|
||||
with pytest.raises(ValueError):
|
||||
mprivkey.child(1 << 32)
|
||||
# OK
|
||||
mprivkey.child((1 << 32) - 1)
|
||||
|
||||
|
||||
class TestVectors():
|
||||
|
||||
def test_vector1(self):
|
||||
seed = bytes.fromhex("000102030405060708090a0b0c0d0e0f")
|
||||
|
||||
# Chain m
|
||||
m = bip32.PrivKey.from_seed(seed)
|
||||
xprv = m.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi"
|
||||
xpub = m.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub661MyMwAqRbcFtXgS5sYJABqqG9YLmC4Q1Rdap9gSE8NqtwybGhePY2gZ29ESFjqJoCu1Rupje8YtGqsefD265TMg7usUDFdp6W1EGMcet8"
|
||||
|
||||
# Chain m/0H
|
||||
m1 = m.child(0 + m.HARDENED)
|
||||
xprv = m1.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9uHRZZhk6KAJC1avXpDAp4MDc3sQKNxDiPvvkX8Br5ngLNv1TxvUxt4cV1rGL5hj6KCesnDYUhd7oWgT11eZG7XnxHrnYeSvkzY7d2bhkJ7"
|
||||
xpub = m1.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub68Gmy5EdvgibQVfPdqkBBCHxA5htiqg55crXYuXoQRKfDBFA1WEjWgP6LHhwBZeNK1VTsfTFUHCdrfp1bgwQ9xv5ski8PX9rL2dZXvgGDnw"
|
||||
|
||||
# Chain m/0H/1
|
||||
m2 = m1.child(1)
|
||||
xprv = m2.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9wTYmMFdV23N2TdNG573QoEsfRrWKQgWeibmLntzniatZvR9BmLnvSxqu53Kw1UmYPxLgboyZQaXwTCg8MSY3H2EU4pWcQDnRnrVA1xe8fs"
|
||||
xpub = m2.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub6ASuArnXKPbfEwhqN6e3mwBcDTgzisQN1wXN9BJcM47sSikHjJf3UFHKkNAWbWMiGj7Wf5uMash7SyYq527Hqck2AxYysAA7xmALppuCkwQ"
|
||||
|
||||
# Chain m/0H/1/2H
|
||||
m3 = m2.child(2 + m.HARDENED)
|
||||
xprv = m3.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9z4pot5VBttmtdRTWfWQmoH1taj2axGVzFqSb8C9xaxKymcFzXBDptWmT7FwuEzG3ryjH4ktypQSAewRiNMjANTtpgP4mLTj34bhnZX7UiM"
|
||||
xpub = m3.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub6D4BDPcP2GT577Vvch3R8wDkScZWzQzMMUm3PWbmWvVJrZwQY4VUNgqFJPMM3No2dFDFGTsxxpG5uJh7n7epu4trkrX7x7DogT5Uv6fcLW5"
|
||||
|
||||
# Chain m/0H/1/2H/2
|
||||
m4 = m3.child(2)
|
||||
xprv = m4.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprvA2JDeKCSNNZky6uBCviVfJSKyQ1mDYahRjijr5idH2WwLsEd4Hsb2Tyh8RfQMuPh7f7RtyzTtdrbdqqsunu5Mm3wDvUAKRHSC34sJ7in334"
|
||||
xpub = m4.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub6FHa3pjLCk84BayeJxFW2SP4XRrFd1JYnxeLeU8EqN3vDfZmbqBqaGJAyiLjTAwm6ZLRQUMv1ZACTj37sR62cfN7fe5JnJ7dh8zL4fiyLHV"
|
||||
|
||||
# Chain m/0H/1/2H/2/1000000000
|
||||
m5 = m4.child(1000000000)
|
||||
xprv = m5.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprvA41z7zogVVwxVSgdKUHDy1SKmdb533PjDz7J6N6mV6uS3ze1ai8FHa8kmHScGpWmj4WggLyQjgPie1rFSruoUihUZREPSL39UNdE3BBDu76"
|
||||
xpub = m5.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy"
|
||||
|
||||
def test_vector2(self):
|
||||
seed = bytes.fromhex("fffcf9f6f3f0edeae7e4e1dedbd8d5d2cfccc9c6c3c0bdbab7b4b1aeaba8a5a29f9c999693908d8a8784817e7b7875726f6c696663605d5a5754514e4b484542")
|
||||
# Chain m
|
||||
m = bip32.PrivKey.from_seed(seed)
|
||||
xprv = m.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9s21ZrQH143K31xYSDQpPDxsXRTUcvj2iNHm5NUtrGiGG5e2DtALGdso3pGz6ssrdK4PFmM8NSpSBHNqPqm55Qn3LqFtT2emdEXVYsCzC2U"
|
||||
xpub = m.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"
|
||||
|
||||
# Chain m/0
|
||||
m1 = m.child(0)
|
||||
xprv = m1.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9vHkqa6EV4sPZHYqZznhT2NPtPCjKuDKGY38FBWLvgaDx45zo9WQRUT3dKYnjwih2yJD9mkrocEZXo1ex8G81dwSM1fwqWpWkeS3v86pgKt"
|
||||
xpub = m1.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"
|
||||
|
||||
# Chain m/0H/2147483647H
|
||||
m2 = m1.child(2147483647 + m.HARDENED)
|
||||
xprv = m2.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9wSp6B7kry3Vj9m1zSnLvN3xH8RdsPP1Mh7fAaR7aRLcQMKTR2vidYEeEg2mUCTAwCd6vnxVrcjfy2kRgVsFawNzmjuHc2YmYRmagcEPdU9"
|
||||
xpub = m2.public_key.extended_key_string(Bitcoin)
|
||||
assert xpub == "xpub6ASAVgeehLbnwdqV6UKMHVzgqAG8Gr6riv3Fxxpj8ksbH9ebxaEyBLZ85ySDhKiLDBrQSARLq1uNRts8RuJiHjaDMBU4Zn9h8LZNnBC5y4a"
|
||||
|
||||
# Chain m/0H/2147483647H/1
|
||||
m3 = m2.child(1)
|
||||
xprv = m3.extended_key_string(Bitcoin)
|
||||
xpub = m3.public_key.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9zFnWC6h2cLgpmSA46vutJzBcfJ8yaJGg8cX1e5StJh45BBciYTRXSd25UEPVuesF9yog62tGAQtHjXajPPdbRCHuWS6T8XA2ECKADdw4Ef"
|
||||
assert xpub == "xpub6DF8uhdarytz3FWdA8TvFSvvAh8dP3283MY7p2V4SeE2wyWmG5mg5EwVvmdMVCQcoNJxGoWaU9DCWh89LojfZ537wTfunKau47EL2dhHKon"
|
||||
|
||||
# Chain m/0/2147483647H/1/2147483646H
|
||||
m4 = m3.child(2147483646 + m.HARDENED)
|
||||
xprv = m4.extended_key_string(Bitcoin)
|
||||
xpub = m4.public_key.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc"
|
||||
assert xpub == "xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL"
|
||||
|
||||
# Chain m/0/2147483647H/1/2147483646H/2
|
||||
m5 = m4.child(2)
|
||||
xprv = m5.extended_key_string(Bitcoin)
|
||||
xpub = m5.public_key.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprvA2nrNbFZABcdryreWet9Ea4LvTJcGsqrMzxHx98MMrotbir7yrKCEXw7nadnHM8Dq38EGfSh6dqA9QWTyefMLEcBYJUuekgW4BYPJcr9E7j"
|
||||
assert xpub == "xpub6FnCn6nSzZAw5Tw7cgR9bi15UV96gLZhjDstkXXxvCLsUXBGXPdSnLFbdpq8p9HmGsApME5hQTZ3emM2rnY5agb9rXpVGyy3bdW6EEgAtqt"
|
||||
|
||||
def test_vector3(self):
|
||||
seed = bytes.fromhex("4b381541583be4423346c643850da4b320e46a87ae3d2a4e6da11eba819cd4acba45d239319ac14f863b8d5ab5a0d0c64d2e8a1e7d1457df2e5a3c51c73235be")
|
||||
|
||||
# Chain m
|
||||
m = bip32.PrivKey.from_seed(seed)
|
||||
xprv = m.extended_key_string(Bitcoin)
|
||||
xpub = m.public_key.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9s21ZrQH143K25QhxbucbDDuQ4naNntJRi4KUfWT7xo4EKsHt2QJDu7KXp1A3u7Bi1j8ph3EGsZ9Xvz9dGuVrtHHs7pXeTzjuxBrCmmhgC6"
|
||||
assert xpub == "xpub661MyMwAqRbcEZVB4dScxMAdx6d4nFc9nvyvH3v4gJL378CSRZiYmhRoP7mBy6gSPSCYk6SzXPTf3ND1cZAceL7SfJ1Z3GC8vBgp2epUt13"
|
||||
|
||||
# Chain m/0H
|
||||
m1 = m.child(0 + m.HARDENED)
|
||||
xprv = m1.extended_key_string(Bitcoin)
|
||||
xpub = m1.public_key.extended_key_string(Bitcoin)
|
||||
assert xprv == "xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L"
|
||||
assert xpub == "xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y"
|
||||
307
wallet/bip32.py
Normal file
307
wallet/bip32.py
Normal file
@ -0,0 +1,307 @@
|
||||
# Copyright (c) 2017, Neil Booth
|
||||
#
|
||||
# All rights reserved.
|
||||
#
|
||||
# See the file "LICENCE" for information about the copyright
|
||||
# and warranty status of this software.
|
||||
|
||||
'''Logic for BIP32 Hierarchical Key Derviation.'''
|
||||
|
||||
import struct
|
||||
|
||||
import ecdsa
|
||||
import ecdsa.ellipticcurve as EC
|
||||
import ecdsa.numbertheory as NT
|
||||
|
||||
from lib.coins import Coin
|
||||
from lib.hash import Base58, hmac_sha512, hash160
|
||||
from lib.util import cachedproperty, bytes_to_int, int_to_bytes
|
||||
|
||||
|
||||
class DerivationError(Exception):
|
||||
'''Raised when an invalid derivation occurs.'''
|
||||
|
||||
|
||||
class _KeyBase(object):
|
||||
'''A BIP32 Key, public or private.'''
|
||||
|
||||
CURVE = ecdsa.SECP256k1
|
||||
|
||||
def __init__(self, chain_code, n, depth, parent):
|
||||
if not isinstance(chain_code, (bytes, bytearray)):
|
||||
raise TypeError('chain code must be raw bytes')
|
||||
if len(chain_code) != 32:
|
||||
raise ValueError('invalid chain code')
|
||||
if not 0 <= n < 1 << 32:
|
||||
raise ValueError('invalid child number')
|
||||
if not 0 <= depth < 256:
|
||||
raise ValueError('invalid depth')
|
||||
if parent is not None:
|
||||
if not isinstance(parent, type(self)):
|
||||
raise TypeError('parent key has bad type')
|
||||
self.chain_code = chain_code
|
||||
self.n = n
|
||||
self.depth = depth
|
||||
self.parent = parent
|
||||
|
||||
def _hmac_sha512(self, msg):
|
||||
'''Use SHA-512 to provide an HMAC, returned as a pair of 32-byte
|
||||
objects.
|
||||
'''
|
||||
hmac = hmac_sha512(self.chain_code, msg)
|
||||
return hmac[:32], hmac[32:]
|
||||
|
||||
def _extended_key(self, ver_bytes, raw_serkey):
|
||||
'''Return the 78-byte extended key given prefix version bytes and
|
||||
serialized key bytes.
|
||||
'''
|
||||
if not isinstance(ver_bytes, (bytes, bytearray)):
|
||||
raise TypeError('ver_bytes must be raw bytes')
|
||||
if len(ver_bytes) != 4:
|
||||
raise ValueError('ver_bytes must have length 4')
|
||||
if not isinstance(raw_serkey, (bytes, bytearray)):
|
||||
raise TypeError('raw_serkey must be raw bytes')
|
||||
if len(raw_serkey) != 33:
|
||||
raise ValueError('raw_serkey must have length 33')
|
||||
|
||||
return (ver_bytes + bytes([self.depth])
|
||||
+ self.parent_fingerprint() + struct.pack('>I', self.n)
|
||||
+ self.chain_code + raw_serkey)
|
||||
|
||||
def fingerprint(self):
|
||||
'''Return the key's fingerprint as 4 bytes.'''
|
||||
return self.identifier()[:4]
|
||||
|
||||
def parent_fingerprint(self):
|
||||
'''Return the parent key's fingerprint as 4 bytes.'''
|
||||
return self.parent.fingerprint() if self.parent else bytes(4)
|
||||
|
||||
def extended_key_string(self, coin):
|
||||
'''Return an extended key as a base58 string.'''
|
||||
return Base58.encode_check(self.extended_key(coin))
|
||||
|
||||
|
||||
class PubKey(_KeyBase):
|
||||
'''A BIP32 public key.'''
|
||||
|
||||
def __init__(self, pubkey, chain_code, n, depth, parent=None):
|
||||
super().__init__(chain_code, n, depth, parent)
|
||||
if isinstance(pubkey, ecdsa.VerifyingKey):
|
||||
self.verifying_key = pubkey
|
||||
else:
|
||||
self.verifying_key = self._verifying_key_from_pubkey(pubkey)
|
||||
self.addresses = {}
|
||||
|
||||
@classmethod
|
||||
def _verifying_key_from_pubkey(cls, pubkey):
|
||||
'''Converts a 33-byte compressed pubkey into an ecdsa.VerifyingKey
|
||||
object'''
|
||||
if not isinstance(pubkey, (bytes, bytearray)):
|
||||
raise TypeError('pubkey must be raw bytes')
|
||||
if len(pubkey) != 33:
|
||||
raise ValueError('pubkey must be 33 bytes')
|
||||
if pubkey[0] not in (2, 3):
|
||||
raise ValueError('invalid pubkey prefix byte')
|
||||
curve = cls.CURVE.curve
|
||||
|
||||
is_odd = pubkey[0] == 3
|
||||
x = bytes_to_int(pubkey[1:])
|
||||
|
||||
# p is the finite field order
|
||||
a, b, p = curve.a(), curve.b(), curve.p()
|
||||
y2 = pow(x, 3, p) + b
|
||||
if a:
|
||||
y2 += a * pow(x, 2, p)
|
||||
y = NT.square_root_mod_prime(y2 % p, p)
|
||||
if bool(y & 1) != is_odd:
|
||||
y = p - y
|
||||
point = EC.Point(curve, x, y)
|
||||
|
||||
return ecdsa.VerifyingKey.from_public_point(point, curve=cls.CURVE)
|
||||
|
||||
@cachedproperty
|
||||
def pubkey_bytes(self):
|
||||
'''Return the compressed public key as 33 bytes.'''
|
||||
point = self.verifying_key.pubkey.point
|
||||
prefix = bytes([2 + (point.y() & 1)])
|
||||
padded_bytes = _exponent_to_bytes(point.x())
|
||||
return prefix + padded_bytes
|
||||
|
||||
def address(self, coin):
|
||||
"The public key as a P2PKH address"
|
||||
address = self.addresses.get(coin)
|
||||
if not address:
|
||||
address = coin.P2PKH_address_from_pubkey(self.pubkey_bytes)
|
||||
self.addresses[coin] = address
|
||||
return address
|
||||
|
||||
def ec_point(self):
|
||||
return self.verifying_key.pubkey.point
|
||||
|
||||
def child(self, n):
|
||||
'''Return the derived child extended pubkey at index N.'''
|
||||
if not 0 <= n < (1 << 31):
|
||||
raise ValueError('invalid BIP32 public key child number')
|
||||
|
||||
msg = self.pubkey_bytes + struct.pack('>I', n)
|
||||
L, R = self._hmac_sha512(msg)
|
||||
|
||||
curve = self.CURVE
|
||||
L = bytes_to_int(L)
|
||||
if L >= curve.order:
|
||||
raise DerivationError
|
||||
|
||||
point = curve.generator * L + self.ec_point()
|
||||
if point == EC.INFINITY:
|
||||
raise DerivationError
|
||||
|
||||
verkey = ecdsa.VerifyingKey.from_public_point(point, curve=curve)
|
||||
|
||||
return PubKey(verkey, R, n, self.depth + 1, self)
|
||||
|
||||
def identifier(self):
|
||||
'''Return the key's identifier as 20 bytes.'''
|
||||
return hash160(self.pubkey_bytes)
|
||||
|
||||
def extended_key(self, coin):
|
||||
'''Return a raw extended public key.'''
|
||||
return self._extended_key(coin.XPUB_VERBYTES, self.pubkey_bytes)
|
||||
|
||||
|
||||
class PrivKey(_KeyBase):
|
||||
'''A BIP32 private key.'''
|
||||
|
||||
HARDENED = 1 << 31
|
||||
|
||||
def __init__(self, privkey, chain_code, n, depth, parent=None):
|
||||
super().__init__(chain_code, n, depth, parent)
|
||||
if isinstance(privkey, ecdsa.SigningKey):
|
||||
self.signing_key = privkey
|
||||
else:
|
||||
self.signing_key = self._signing_key_from_privkey(privkey)
|
||||
|
||||
@classmethod
|
||||
def _signing_key_from_privkey(cls, privkey):
|
||||
'''Converts a 32-byte privkey into an ecdsa.SigningKey object.'''
|
||||
exponent = cls._privkey_secret_exponent(privkey)
|
||||
return ecdsa.SigningKey.from_secret_exponent(exponent, curve=cls.CURVE)
|
||||
|
||||
@classmethod
|
||||
def _privkey_secret_exponent(cls, privkey):
|
||||
'''Return the private key as a secret exponent if it is a valid private
|
||||
key.'''
|
||||
if not isinstance(privkey, (bytes, bytearray)):
|
||||
raise TypeError('privkey must be raw bytes')
|
||||
if len(privkey) != 32:
|
||||
raise ValueError('privkey must be 32 bytes')
|
||||
exponent = bytes_to_int(privkey)
|
||||
if not 1 <= exponent < cls.CURVE.order:
|
||||
raise ValueError('privkey represents an invalid exponent')
|
||||
|
||||
return exponent
|
||||
|
||||
@classmethod
|
||||
def from_seed(cls, seed):
|
||||
# This hard-coded message string seems to be coin-independent...
|
||||
hmac = hmac_sha512(b'Bitcoin seed', seed)
|
||||
privkey, chain_code = hmac[:32], hmac[32:]
|
||||
return cls(privkey, chain_code, 0, 0)
|
||||
|
||||
@cachedproperty
|
||||
def privkey_bytes(self):
|
||||
'''Return the serialized private key (no leading zero byte).'''
|
||||
return _exponent_to_bytes(self.secret_exponent())
|
||||
|
||||
@cachedproperty
|
||||
def public_key(self):
|
||||
'''Return the corresponding extended public key.'''
|
||||
verifying_key = self.signing_key.get_verifying_key()
|
||||
parent_pubkey = self.parent.public_key if self.parent else None
|
||||
return PubKey(verifying_key, self.chain_code, self.n, self.depth,
|
||||
parent_pubkey)
|
||||
|
||||
def ec_point(self):
|
||||
return self.public_key.ec_point()
|
||||
|
||||
def secret_exponent(self):
|
||||
'''Return the private key as a secret exponent.'''
|
||||
return self.signing_key.privkey.secret_multiplier
|
||||
|
||||
def WIF(self, coin):
|
||||
'''Return the private key encoded in Wallet Import Format.'''
|
||||
return coin.privkey_WIF(self.privkey_bytes, compressed=True)
|
||||
|
||||
def address(self, coin):
|
||||
"The public key as a P2PKH address"
|
||||
return self.public_key.address(coin)
|
||||
|
||||
def child(self, n):
|
||||
'''Return the derived child extended privkey at index N.'''
|
||||
if not 0 <= n < (1 << 32):
|
||||
raise ValueError('invalid BIP32 private key child number')
|
||||
|
||||
if n >= self.HARDENED:
|
||||
serkey = b'\0' + self.privkey_bytes
|
||||
else:
|
||||
serkey = self.public_key.pubkey_bytes
|
||||
|
||||
msg = serkey + struct.pack('>I', n)
|
||||
L, R = self._hmac_sha512(msg)
|
||||
|
||||
curve = self.CURVE
|
||||
L = bytes_to_int(L)
|
||||
exponent = (L + bytes_to_int(self.privkey_bytes)) % curve.order
|
||||
if exponent == 0 or L >= curve.order:
|
||||
raise DerivationError
|
||||
|
||||
privkey = _exponent_to_bytes(exponent)
|
||||
|
||||
return PrivKey(privkey, R, n, self.depth + 1, self)
|
||||
|
||||
def identifier(self):
|
||||
'''Return the key's identifier as 20 bytes.'''
|
||||
return self.public_key.identifier()
|
||||
|
||||
def extended_key(self, coin):
|
||||
'''Return a raw extended private key.'''
|
||||
return self._extended_key(coin.XPRV_VERBYTES,
|
||||
b'\0' + self.privkey_bytes)
|
||||
|
||||
|
||||
def _exponent_to_bytes(exponent):
|
||||
'''Convert an exponent to 32 big-endian bytes'''
|
||||
return (bytes(32) + int_to_bytes(exponent))[-32:]
|
||||
|
||||
def _from_extended_key(ekey):
|
||||
'''Return a PubKey or PrivKey from an extended key raw bytes.'''
|
||||
if not isinstance(ekey, (bytes, bytearray)):
|
||||
raise TypeError('extended key must be raw bytes')
|
||||
if len(ekey) != 78:
|
||||
raise ValueError('extended key must have length 78')
|
||||
|
||||
is_public, coin = Coin.lookup_xverbytes(ekey[:4])
|
||||
depth = ekey[4]
|
||||
fingerprint = ekey[5:9] # Not used
|
||||
n, = struct.unpack('>I', ekey[9:13])
|
||||
chain_code = ekey[13:45]
|
||||
|
||||
if is_public:
|
||||
pubkey = ekey[45:]
|
||||
key = PubKey(pubkey, chain_code, n, depth)
|
||||
else:
|
||||
if ekey[45] is not 0:
|
||||
raise ValueError('invalid extended private key prefix byte')
|
||||
privkey = ekey[46:]
|
||||
key = PrivKey(privkey, chain_code, n, depth)
|
||||
|
||||
return key, coin
|
||||
|
||||
def from_extended_key_string(ekey_str):
|
||||
'''Given an extended key string, such as
|
||||
|
||||
xpub6BsnM1W2Y7qLMiuhi7f7dbAwQZ5Cz5gYJCRzTNainXzQXYjFwtuQXHd
|
||||
3qfi3t3KJtHxshXezfjft93w4UE7BGMtKwhqEHae3ZA7d823DVrL
|
||||
|
||||
return a (key, coin) pair. key is either a PubKey or PrivKey.
|
||||
'''
|
||||
return _from_extended_key(Base58.decode_check(ekey_str))
|
||||
Loading…
Reference in New Issue
Block a user