mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-09-03 20:35:13 +00:00
bip32: refactor whole module. clean-up.
This commit is contained in:
parent
b39c51adf7
commit
85a7aa291e
18 changed files with 356 additions and 279 deletions
|
@ -3,7 +3,7 @@
|
||||||
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
|
||||||
|
|
||||||
import hashlib
|
import hashlib
|
||||||
from typing import List
|
from typing import List, Tuple, NamedTuple, Union, Iterable
|
||||||
|
|
||||||
from .util import bfh, bh2u, BitcoinException, print_error
|
from .util import bfh, bh2u, BitcoinException, print_error
|
||||||
from . import constants
|
from . import constants
|
||||||
|
@ -13,257 +13,299 @@ from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check
|
||||||
|
|
||||||
|
|
||||||
BIP32_PRIME = 0x80000000
|
BIP32_PRIME = 0x80000000
|
||||||
|
UINT32_MAX = (1 << 32) - 1
|
||||||
|
|
||||||
|
|
||||||
def protect_against_invalid_ecpoint(func):
|
def protect_against_invalid_ecpoint(func):
|
||||||
def func_wrapper(*args):
|
def func_wrapper(*args):
|
||||||
n = args[-1]
|
child_index = args[-1]
|
||||||
while True:
|
while True:
|
||||||
is_prime = n & BIP32_PRIME
|
is_prime = child_index & BIP32_PRIME
|
||||||
try:
|
try:
|
||||||
return func(*args[:-1], n=n)
|
return func(*args[:-1], child_index=child_index)
|
||||||
except ecc.InvalidECPointException:
|
except ecc.InvalidECPointException:
|
||||||
print_error('bip32 protect_against_invalid_ecpoint: skipping index')
|
print_error('bip32 protect_against_invalid_ecpoint: skipping index')
|
||||||
n += 1
|
child_index += 1
|
||||||
is_prime2 = n & BIP32_PRIME
|
is_prime2 = child_index & BIP32_PRIME
|
||||||
if is_prime != is_prime2: raise OverflowError()
|
if is_prime != is_prime2: raise OverflowError()
|
||||||
return func_wrapper
|
return func_wrapper
|
||||||
|
|
||||||
|
|
||||||
# Child private key derivation function (from master private key)
|
|
||||||
# k = master private key (32 bytes)
|
|
||||||
# c = master chain code (extra entropy for key derivation) (32 bytes)
|
|
||||||
# n = the index of the key we want to derive. (only 32 bits will be used)
|
|
||||||
# If n is hardened (i.e. the 32nd bit is set), the resulting private key's
|
|
||||||
# corresponding public key can NOT be determined without the master private key.
|
|
||||||
# However, if n is not hardened, the resulting private key's corresponding
|
|
||||||
# public key can be determined without the master private key.
|
|
||||||
@protect_against_invalid_ecpoint
|
@protect_against_invalid_ecpoint
|
||||||
def CKD_priv(k, c, n):
|
def CKD_priv(parent_privkey: bytes, parent_chaincode: bytes, child_index: int) -> Tuple[bytes, bytes]:
|
||||||
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
|
"""Child private key derivation function (from master private key)
|
||||||
is_prime = n & BIP32_PRIME
|
If n is hardened (i.e. the 32nd bit is set), the resulting private key's
|
||||||
return _CKD_priv(k, c, bfh(rev_hex(int_to_hex(n,4))), is_prime)
|
corresponding public key can NOT be determined without the master private key.
|
||||||
|
However, if n is not hardened, the resulting private key's corresponding
|
||||||
|
public key can be determined without the master private key.
|
||||||
|
"""
|
||||||
|
if child_index < 0: raise ValueError('the bip32 index needs to be non-negative')
|
||||||
|
is_hardened_child = bool(child_index & BIP32_PRIME)
|
||||||
|
return _CKD_priv(parent_privkey=parent_privkey,
|
||||||
|
parent_chaincode=parent_chaincode,
|
||||||
|
child_index=bfh(rev_hex(int_to_hex(child_index, 4))),
|
||||||
|
is_hardened_child=is_hardened_child)
|
||||||
|
|
||||||
|
|
||||||
def _CKD_priv(k, c, s, is_prime):
|
def _CKD_priv(parent_privkey: bytes, parent_chaincode: bytes,
|
||||||
|
child_index: bytes, is_hardened_child: bool) -> Tuple[bytes, bytes]:
|
||||||
try:
|
try:
|
||||||
keypair = ecc.ECPrivkey(k)
|
keypair = ecc.ECPrivkey(parent_privkey)
|
||||||
except ecc.InvalidECPointException as e:
|
except ecc.InvalidECPointException as e:
|
||||||
raise BitcoinException('Impossible xprv (not within curve order)') from e
|
raise BitcoinException('Impossible xprv (not within curve order)') from e
|
||||||
cK = keypair.get_public_key_bytes(compressed=True)
|
parent_pubkey = keypair.get_public_key_bytes(compressed=True)
|
||||||
data = bytes([0]) + k + s if is_prime else cK + s
|
if is_hardened_child:
|
||||||
I = hmac_oneshot(c, data, hashlib.sha512)
|
data = bytes([0]) + parent_privkey + child_index
|
||||||
|
else:
|
||||||
|
data = parent_pubkey + child_index
|
||||||
|
I = hmac_oneshot(parent_chaincode, data, hashlib.sha512)
|
||||||
I_left = ecc.string_to_number(I[0:32])
|
I_left = ecc.string_to_number(I[0:32])
|
||||||
k_n = (I_left + ecc.string_to_number(k)) % ecc.CURVE_ORDER
|
child_privkey = (I_left + ecc.string_to_number(parent_privkey)) % ecc.CURVE_ORDER
|
||||||
if I_left >= ecc.CURVE_ORDER or k_n == 0:
|
if I_left >= ecc.CURVE_ORDER or child_privkey == 0:
|
||||||
raise ecc.InvalidECPointException()
|
raise ecc.InvalidECPointException()
|
||||||
k_n = ecc.number_to_string(k_n, ecc.CURVE_ORDER)
|
child_privkey = ecc.number_to_string(child_privkey, ecc.CURVE_ORDER)
|
||||||
c_n = I[32:]
|
child_chaincode = I[32:]
|
||||||
return k_n, c_n
|
return child_privkey, child_chaincode
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# Child public key derivation function (from public key only)
|
|
||||||
# K = master public key
|
|
||||||
# c = master chain code
|
|
||||||
# n = index of key we want to derive
|
|
||||||
# This function allows us to find the nth public key, as long as n is
|
|
||||||
# not hardened. If n is hardened, we need the master private key to find it.
|
|
||||||
@protect_against_invalid_ecpoint
|
@protect_against_invalid_ecpoint
|
||||||
def CKD_pub(cK, c, n):
|
def CKD_pub(parent_pubkey: bytes, parent_chaincode: bytes, child_index: int) -> Tuple[bytes, bytes]:
|
||||||
if n < 0: raise ValueError('the bip32 index needs to be non-negative')
|
"""Child public key derivation function (from public key only)
|
||||||
if n & BIP32_PRIME: raise Exception()
|
This function allows us to find the nth public key, as long as n is
|
||||||
return _CKD_pub(cK, c, bfh(rev_hex(int_to_hex(n,4))))
|
not hardened. If n is hardened, we need the master private key to find it.
|
||||||
|
"""
|
||||||
|
if child_index < 0: raise ValueError('the bip32 index needs to be non-negative')
|
||||||
|
if child_index & BIP32_PRIME: raise Exception('not possible to derive hardened child from parent pubkey')
|
||||||
|
return _CKD_pub(parent_pubkey=parent_pubkey,
|
||||||
|
parent_chaincode=parent_chaincode,
|
||||||
|
child_index=bfh(rev_hex(int_to_hex(child_index, 4))))
|
||||||
|
|
||||||
# helper function, callable with arbitrary string.
|
|
||||||
# note: 's' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
|
# helper function, callable with arbitrary 'child_index' byte-string.
|
||||||
def _CKD_pub(cK, c, s):
|
# i.e.: 'child_index' does not need to fit into 32 bits here! (c.f. trustedcoin billing)
|
||||||
I = hmac_oneshot(c, cK + s, hashlib.sha512)
|
def _CKD_pub(parent_pubkey: bytes, parent_chaincode: bytes, child_index: bytes) -> Tuple[bytes, bytes]:
|
||||||
pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(cK)
|
I = hmac_oneshot(parent_chaincode, parent_pubkey + child_index, hashlib.sha512)
|
||||||
|
pubkey = ecc.ECPrivkey(I[0:32]) + ecc.ECPubkey(parent_pubkey)
|
||||||
if pubkey.is_at_infinity():
|
if pubkey.is_at_infinity():
|
||||||
raise ecc.InvalidECPointException()
|
raise ecc.InvalidECPointException()
|
||||||
cK_n = pubkey.get_public_key_bytes(compressed=True)
|
child_pubkey = pubkey.get_public_key_bytes(compressed=True)
|
||||||
c_n = I[32:]
|
child_chaincode = I[32:]
|
||||||
return cK_n, c_n
|
return child_pubkey, child_chaincode
|
||||||
|
|
||||||
|
|
||||||
def xprv_header(xtype, *, net=None):
|
def xprv_header(xtype: str, *, net=None) -> bytes:
|
||||||
if net is None:
|
if net is None:
|
||||||
net = constants.net
|
net = constants.net
|
||||||
return bfh("%08x" % net.XPRV_HEADERS[xtype])
|
return net.XPRV_HEADERS[xtype].to_bytes(length=4, byteorder="big")
|
||||||
|
|
||||||
|
|
||||||
def xpub_header(xtype, *, net=None):
|
def xpub_header(xtype: str, *, net=None) -> bytes:
|
||||||
if net is None:
|
if net is None:
|
||||||
net = constants.net
|
net = constants.net
|
||||||
return bfh("%08x" % net.XPUB_HEADERS[xtype])
|
return net.XPUB_HEADERS[xtype].to_bytes(length=4, byteorder="big")
|
||||||
|
|
||||||
|
|
||||||
def serialize_xprv(xtype, c, k, depth=0, fingerprint=b'\x00'*4,
|
|
||||||
child_number=b'\x00'*4, *, net=None):
|
|
||||||
if not ecc.is_secret_within_curve_range(k):
|
|
||||||
raise BitcoinException('Impossible xprv (not within curve order)')
|
|
||||||
xprv = xprv_header(xtype, net=net) \
|
|
||||||
+ bytes([depth]) + fingerprint + child_number + c + bytes([0]) + k
|
|
||||||
return EncodeBase58Check(xprv)
|
|
||||||
|
|
||||||
|
|
||||||
def serialize_xpub(xtype, c, cK, depth=0, fingerprint=b'\x00'*4,
|
|
||||||
child_number=b'\x00'*4, *, net=None):
|
|
||||||
xpub = xpub_header(xtype, net=net) \
|
|
||||||
+ bytes([depth]) + fingerprint + child_number + c + cK
|
|
||||||
return EncodeBase58Check(xpub)
|
|
||||||
|
|
||||||
|
|
||||||
class InvalidMasterKeyVersionBytes(BitcoinException): pass
|
class InvalidMasterKeyVersionBytes(BitcoinException): pass
|
||||||
|
|
||||||
|
|
||||||
def deserialize_xkey(xkey, prv, *, net=None):
|
class BIP32Node(NamedTuple):
|
||||||
if net is None:
|
xtype: str
|
||||||
net = constants.net
|
eckey: Union[ecc.ECPubkey, ecc.ECPrivkey]
|
||||||
xkey = DecodeBase58Check(xkey)
|
chaincode: bytes
|
||||||
if len(xkey) != 78:
|
depth: int = 0
|
||||||
raise BitcoinException('Invalid length for extended key: {}'
|
fingerprint: bytes = b'\x00'*4
|
||||||
.format(len(xkey)))
|
child_number: bytes = b'\x00'*4
|
||||||
depth = xkey[4]
|
|
||||||
fingerprint = xkey[5:9]
|
|
||||||
child_number = xkey[9:13]
|
|
||||||
c = xkey[13:13+32]
|
|
||||||
header = int.from_bytes(xkey[0:4], byteorder='big')
|
|
||||||
headers = net.XPRV_HEADERS if prv else net.XPUB_HEADERS
|
|
||||||
if header not in headers.values():
|
|
||||||
raise InvalidMasterKeyVersionBytes('Invalid extended key format: {}'
|
|
||||||
.format(hex(header)))
|
|
||||||
xtype = list(headers.keys())[list(headers.values()).index(header)]
|
|
||||||
n = 33 if prv else 32
|
|
||||||
K_or_k = xkey[13+n:]
|
|
||||||
if prv and not ecc.is_secret_within_curve_range(K_or_k):
|
|
||||||
raise BitcoinException('Impossible xprv (not within curve order)')
|
|
||||||
return xtype, depth, fingerprint, child_number, c, K_or_k
|
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def from_xkey(cls, xkey: str, *, net=None) -> 'BIP32Node':
|
||||||
|
if net is None:
|
||||||
|
net = constants.net
|
||||||
|
xkey = DecodeBase58Check(xkey)
|
||||||
|
if len(xkey) != 78:
|
||||||
|
raise BitcoinException('Invalid length for extended key: {}'
|
||||||
|
.format(len(xkey)))
|
||||||
|
depth = xkey[4]
|
||||||
|
fingerprint = xkey[5:9]
|
||||||
|
child_number = xkey[9:13]
|
||||||
|
chaincode = xkey[13:13 + 32]
|
||||||
|
header = int.from_bytes(xkey[0:4], byteorder='big')
|
||||||
|
if header in net.XPRV_HEADERS_INV:
|
||||||
|
headers_inv = net.XPRV_HEADERS_INV
|
||||||
|
is_private = True
|
||||||
|
elif header in net.XPUB_HEADERS_INV:
|
||||||
|
headers_inv = net.XPUB_HEADERS_INV
|
||||||
|
is_private = False
|
||||||
|
else:
|
||||||
|
raise InvalidMasterKeyVersionBytes(f'Invalid extended key format: {hex(header)}')
|
||||||
|
xtype = headers_inv[header]
|
||||||
|
if is_private:
|
||||||
|
eckey = ecc.ECPrivkey(xkey[13 + 33:])
|
||||||
|
else:
|
||||||
|
eckey = ecc.ECPubkey(xkey[13 + 32:])
|
||||||
|
return BIP32Node(xtype=xtype,
|
||||||
|
eckey=eckey,
|
||||||
|
chaincode=chaincode,
|
||||||
|
depth=depth,
|
||||||
|
fingerprint=fingerprint,
|
||||||
|
child_number=child_number)
|
||||||
|
|
||||||
def deserialize_xpub(xkey, *, net=None):
|
@classmethod
|
||||||
return deserialize_xkey(xkey, False, net=net)
|
def from_rootseed(cls, seed: bytes, *, xtype: str) -> 'BIP32Node':
|
||||||
|
I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
|
||||||
|
master_k = I[0:32]
|
||||||
|
master_c = I[32:]
|
||||||
|
return BIP32Node(xtype=xtype,
|
||||||
|
eckey=ecc.ECPrivkey(master_k),
|
||||||
|
chaincode=master_c)
|
||||||
|
|
||||||
|
def to_xprv(self, *, net=None) -> str:
|
||||||
|
if not self.is_private():
|
||||||
|
raise Exception("cannot serialize as xprv; private key missing")
|
||||||
|
payload = (xprv_header(self.xtype, net=net) +
|
||||||
|
bytes([self.depth]) +
|
||||||
|
self.fingerprint +
|
||||||
|
self.child_number +
|
||||||
|
self.chaincode +
|
||||||
|
bytes([0]) +
|
||||||
|
self.eckey.get_secret_bytes())
|
||||||
|
assert len(payload) == 78, f"unexpected xprv payload len {len(payload)}"
|
||||||
|
return EncodeBase58Check(payload)
|
||||||
|
|
||||||
|
def to_xpub(self, *, net=None) -> str:
|
||||||
|
payload = (xpub_header(self.xtype, net=net) +
|
||||||
|
bytes([self.depth]) +
|
||||||
|
self.fingerprint +
|
||||||
|
self.child_number +
|
||||||
|
self.chaincode +
|
||||||
|
self.eckey.get_public_key_bytes(compressed=True))
|
||||||
|
assert len(payload) == 78, f"unexpected xpub payload len {len(payload)}"
|
||||||
|
return EncodeBase58Check(payload)
|
||||||
|
|
||||||
|
def to_xkey(self, *, net=None) -> str:
|
||||||
|
if self.is_private():
|
||||||
|
return self.to_xprv(net=net)
|
||||||
|
else:
|
||||||
|
return self.to_xpub(net=net)
|
||||||
|
|
||||||
|
def convert_to_public(self) -> 'BIP32Node':
|
||||||
|
if not self.is_private():
|
||||||
|
return self
|
||||||
|
pubkey = ecc.ECPubkey(self.eckey.get_public_key_bytes())
|
||||||
|
return self._replace(eckey=pubkey)
|
||||||
|
|
||||||
|
def is_private(self) -> bool:
|
||||||
|
return isinstance(self.eckey, ecc.ECPrivkey)
|
||||||
|
|
||||||
|
def subkey_at_private_derivation(self, path: Union[str, Iterable[int]]) -> 'BIP32Node':
|
||||||
|
if isinstance(path, str):
|
||||||
|
path = convert_bip32_path_to_list_of_uint32(path)
|
||||||
|
if not self.is_private():
|
||||||
|
raise Exception("cannot do bip32 private derivation; private key missing")
|
||||||
|
if not path:
|
||||||
|
return self
|
||||||
|
depth = self.depth
|
||||||
|
chaincode = self.chaincode
|
||||||
|
privkey = self.eckey.get_secret_bytes()
|
||||||
|
for child_index in path:
|
||||||
|
parent_privkey = privkey
|
||||||
|
privkey, chaincode = CKD_priv(privkey, chaincode, child_index)
|
||||||
|
depth += 1
|
||||||
|
parent_pubkey = ecc.ECPrivkey(parent_privkey).get_public_key_bytes(compressed=True)
|
||||||
|
fingerprint = hash_160(parent_pubkey)[0:4]
|
||||||
|
child_number = child_index.to_bytes(length=4, byteorder="big")
|
||||||
|
return BIP32Node(xtype=self.xtype,
|
||||||
|
eckey=ecc.ECPrivkey(privkey),
|
||||||
|
chaincode=chaincode,
|
||||||
|
depth=depth,
|
||||||
|
fingerprint=fingerprint,
|
||||||
|
child_number=child_number)
|
||||||
|
|
||||||
|
def subkey_at_public_derivation(self, path: Union[str, Iterable[int]]) -> 'BIP32Node':
|
||||||
|
if isinstance(path, str):
|
||||||
|
path = convert_bip32_path_to_list_of_uint32(path)
|
||||||
|
if not path:
|
||||||
|
return self.convert_to_public()
|
||||||
|
depth = self.depth
|
||||||
|
chaincode = self.chaincode
|
||||||
|
pubkey = self.eckey.get_public_key_bytes(compressed=True)
|
||||||
|
for child_index in path:
|
||||||
|
parent_pubkey = pubkey
|
||||||
|
pubkey, chaincode = CKD_pub(pubkey, chaincode, child_index)
|
||||||
|
depth += 1
|
||||||
|
fingerprint = hash_160(parent_pubkey)[0:4]
|
||||||
|
child_number = child_index.to_bytes(length=4, byteorder="big")
|
||||||
|
return BIP32Node(xtype=self.xtype,
|
||||||
|
eckey=ecc.ECPubkey(pubkey),
|
||||||
|
chaincode=chaincode,
|
||||||
|
depth=depth,
|
||||||
|
fingerprint=fingerprint,
|
||||||
|
child_number=child_number)
|
||||||
|
|
||||||
def deserialize_xprv(xkey, *, net=None):
|
|
||||||
return deserialize_xkey(xkey, True, net=net)
|
|
||||||
|
|
||||||
def xpub_type(x):
|
def xpub_type(x):
|
||||||
return deserialize_xpub(x)[0]
|
return BIP32Node.from_xkey(x).xtype
|
||||||
|
|
||||||
|
|
||||||
def is_xpub(text):
|
def is_xpub(text):
|
||||||
try:
|
try:
|
||||||
deserialize_xpub(text)
|
node = BIP32Node.from_xkey(text)
|
||||||
return True
|
return not node.is_private()
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def is_xprv(text):
|
def is_xprv(text):
|
||||||
try:
|
try:
|
||||||
deserialize_xprv(text)
|
node = BIP32Node.from_xkey(text)
|
||||||
return True
|
return node.is_private()
|
||||||
except:
|
except:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def xpub_from_xprv(xprv):
|
def xpub_from_xprv(xprv):
|
||||||
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
|
return BIP32Node.from_xkey(xprv).to_xpub()
|
||||||
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
|
|
||||||
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
|
||||||
|
|
||||||
|
|
||||||
def bip32_root(seed, xtype):
|
|
||||||
I = hmac_oneshot(b"Bitcoin seed", seed, hashlib.sha512)
|
|
||||||
master_k = I[0:32]
|
|
||||||
master_c = I[32:]
|
|
||||||
# create xprv first, as that will check if master_k is within curve order
|
|
||||||
xprv = serialize_xprv(xtype, master_c, master_k)
|
|
||||||
cK = ecc.ECPrivkey(master_k).get_public_key_bytes(compressed=True)
|
|
||||||
xpub = serialize_xpub(xtype, master_c, cK)
|
|
||||||
return xprv, xpub
|
|
||||||
|
|
||||||
|
|
||||||
def xpub_from_pubkey(xtype, cK):
|
|
||||||
if cK[0] not in (0x02, 0x03):
|
|
||||||
raise ValueError('Unexpected first byte: {}'.format(cK[0]))
|
|
||||||
return serialize_xpub(xtype, b'\x00'*32, cK)
|
|
||||||
|
|
||||||
|
|
||||||
def bip32_derivation(s: str) -> int:
|
|
||||||
if not s.startswith('m/'):
|
|
||||||
raise ValueError('invalid bip32 derivation path: {}'.format(s))
|
|
||||||
s = s[2:]
|
|
||||||
for n in s.split('/'):
|
|
||||||
if n == '': continue
|
|
||||||
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
|
|
||||||
yield i
|
|
||||||
|
|
||||||
def convert_bip32_path_to_list_of_uint32(n: str) -> List[int]:
|
def convert_bip32_path_to_list_of_uint32(n: str) -> List[int]:
|
||||||
"""Convert bip32 path to list of uint32 integers with prime flags
|
"""Convert bip32 path to list of uint32 integers with prime flags
|
||||||
m/0/-1/1' -> [0, 0x80000001, 0x80000001]
|
m/0/-1/1' -> [0, 0x80000001, 0x80000001]
|
||||||
|
|
||||||
based on code in trezorlib
|
based on code in trezorlib
|
||||||
"""
|
"""
|
||||||
|
if not n:
|
||||||
|
return []
|
||||||
|
if n.endswith("/"):
|
||||||
|
n = n[:-1]
|
||||||
|
n = n.split('/')
|
||||||
|
# cut leading "m" if present, but do not require it
|
||||||
|
if n[0] == "m":
|
||||||
|
n = n[1:]
|
||||||
path = []
|
path = []
|
||||||
for x in n.split('/')[1:]:
|
for x in n:
|
||||||
if x == '': continue
|
if x == '':
|
||||||
|
# gracefully allow repeating "/" chars in path.
|
||||||
|
# makes concatenating paths easier
|
||||||
|
continue
|
||||||
prime = 0
|
prime = 0
|
||||||
if x.endswith("'"):
|
if x.endswith("'") or x.endswith("h"):
|
||||||
x = x.replace('\'', '')
|
x = x[:-1]
|
||||||
prime = BIP32_PRIME
|
prime = BIP32_PRIME
|
||||||
if x.startswith('-'):
|
if x.startswith('-'):
|
||||||
prime = BIP32_PRIME
|
prime = BIP32_PRIME
|
||||||
path.append(abs(int(x)) | prime)
|
child_index = abs(int(x)) | prime
|
||||||
|
if child_index > UINT32_MAX:
|
||||||
|
raise ValueError(f"bip32 path child index too large: {child_index} > {UINT32_MAX}")
|
||||||
|
path.append(child_index)
|
||||||
return path
|
return path
|
||||||
|
|
||||||
def is_bip32_derivation(x: str) -> bool:
|
|
||||||
|
def is_bip32_derivation(s: str) -> bool:
|
||||||
try:
|
try:
|
||||||
[ i for i in bip32_derivation(x)]
|
if not s.startswith('m/'):
|
||||||
return True
|
return False
|
||||||
except :
|
convert_bip32_path_to_list_of_uint32(s)
|
||||||
|
except:
|
||||||
return False
|
return False
|
||||||
|
else:
|
||||||
def bip32_private_derivation(xprv, branch, sequence):
|
return True
|
||||||
if not sequence.startswith(branch):
|
|
||||||
raise ValueError('incompatible branch ({}) and sequence ({})'
|
|
||||||
.format(branch, sequence))
|
|
||||||
if branch == sequence:
|
|
||||||
return xprv, xpub_from_xprv(xprv)
|
|
||||||
xtype, depth, fingerprint, child_number, c, k = deserialize_xprv(xprv)
|
|
||||||
sequence = sequence[len(branch):]
|
|
||||||
for n in sequence.split('/'):
|
|
||||||
if n == '': continue
|
|
||||||
i = int(n[:-1]) + BIP32_PRIME if n[-1] == "'" else int(n)
|
|
||||||
parent_k = k
|
|
||||||
k, c = CKD_priv(k, c, i)
|
|
||||||
depth += 1
|
|
||||||
parent_cK = ecc.ECPrivkey(parent_k).get_public_key_bytes(compressed=True)
|
|
||||||
fingerprint = hash_160(parent_cK)[0:4]
|
|
||||||
child_number = bfh("%08X"%i)
|
|
||||||
cK = ecc.ECPrivkey(k).get_public_key_bytes(compressed=True)
|
|
||||||
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
|
||||||
xprv = serialize_xprv(xtype, c, k, depth, fingerprint, child_number)
|
|
||||||
return xprv, xpub
|
|
||||||
|
|
||||||
|
|
||||||
def bip32_public_derivation(xpub, branch, sequence):
|
|
||||||
xtype, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
|
|
||||||
if not sequence.startswith(branch):
|
|
||||||
raise ValueError('incompatible branch ({}) and sequence ({})'
|
|
||||||
.format(branch, sequence))
|
|
||||||
sequence = sequence[len(branch):]
|
|
||||||
for n in sequence.split('/'):
|
|
||||||
if n == '': continue
|
|
||||||
i = int(n)
|
|
||||||
parent_cK = cK
|
|
||||||
cK, c = CKD_pub(cK, c, i)
|
|
||||||
depth += 1
|
|
||||||
fingerprint = hash_160(parent_cK)[0:4]
|
|
||||||
child_number = bfh("%08X"%i)
|
|
||||||
return serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
|
||||||
|
|
||||||
|
|
||||||
def bip32_private_key(sequence, k, chain):
|
|
||||||
for i in sequence:
|
|
||||||
k, chain = CKD_priv(k, chain, i)
|
|
||||||
return k
|
|
||||||
|
|
|
@ -39,6 +39,7 @@ from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_enc
|
||||||
from . import bitcoin
|
from . import bitcoin
|
||||||
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
|
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
|
||||||
from . import bip32
|
from . import bip32
|
||||||
|
from .bip32 import BIP32Node
|
||||||
from .i18n import _
|
from .i18n import _
|
||||||
from .transaction import Transaction, multisig_script, TxOutput
|
from .transaction import Transaction, multisig_script, TxOutput
|
||||||
from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
from .paymentrequest import PR_PAID, PR_UNPAID, PR_UNKNOWN, PR_EXPIRED
|
||||||
|
@ -439,12 +440,11 @@ class Commands:
|
||||||
@command('')
|
@command('')
|
||||||
def convert_xkey(self, xkey, xtype):
|
def convert_xkey(self, xkey, xtype):
|
||||||
"""Convert xtype of a master key. e.g. xpub -> ypub"""
|
"""Convert xtype of a master key. e.g. xpub -> ypub"""
|
||||||
is_xprv = bip32.is_xprv(xkey)
|
try:
|
||||||
if not bip32.is_xpub(xkey) and not is_xprv:
|
node = BIP32Node.from_xkey(xkey)
|
||||||
|
except:
|
||||||
raise Exception('xkey should be a master public/private key')
|
raise Exception('xkey should be a master public/private key')
|
||||||
_, depth, fingerprint, child_number, c, cK = bip32.deserialize_xkey(xkey, is_xprv)
|
return node._replace(xtype=xtype).to_xkey()
|
||||||
serialize = bip32.serialize_xprv if is_xprv else bip32.serialize_xpub
|
|
||||||
return serialize(xtype, c, cK, depth, fingerprint, child_number)
|
|
||||||
|
|
||||||
@command('wp')
|
@command('wp')
|
||||||
def getseed(self, password=None):
|
def getseed(self, password=None):
|
||||||
|
|
|
@ -26,6 +26,8 @@
|
||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from .util import inv_dict
|
||||||
|
|
||||||
|
|
||||||
def read_json(filename, default):
|
def read_json(filename, default):
|
||||||
path = os.path.join(os.path.dirname(__file__), filename)
|
path = os.path.join(os.path.dirname(__file__), filename)
|
||||||
|
@ -63,6 +65,7 @@ class BitcoinMainnet(AbstractNet):
|
||||||
'p2wpkh': 0x04b2430c, # zprv
|
'p2wpkh': 0x04b2430c, # zprv
|
||||||
'p2wsh': 0x02aa7a99, # Zprv
|
'p2wsh': 0x02aa7a99, # Zprv
|
||||||
}
|
}
|
||||||
|
XPRV_HEADERS_INV = inv_dict(XPRV_HEADERS)
|
||||||
XPUB_HEADERS = {
|
XPUB_HEADERS = {
|
||||||
'standard': 0x0488b21e, # xpub
|
'standard': 0x0488b21e, # xpub
|
||||||
'p2wpkh-p2sh': 0x049d7cb2, # ypub
|
'p2wpkh-p2sh': 0x049d7cb2, # ypub
|
||||||
|
@ -70,6 +73,7 @@ class BitcoinMainnet(AbstractNet):
|
||||||
'p2wpkh': 0x04b24746, # zpub
|
'p2wpkh': 0x04b24746, # zpub
|
||||||
'p2wsh': 0x02aa7ed3, # Zpub
|
'p2wsh': 0x02aa7ed3, # Zpub
|
||||||
}
|
}
|
||||||
|
XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)
|
||||||
BIP44_COIN_TYPE = 0
|
BIP44_COIN_TYPE = 0
|
||||||
|
|
||||||
|
|
||||||
|
@ -92,6 +96,7 @@ class BitcoinTestnet(AbstractNet):
|
||||||
'p2wpkh': 0x045f18bc, # vprv
|
'p2wpkh': 0x045f18bc, # vprv
|
||||||
'p2wsh': 0x02575048, # Vprv
|
'p2wsh': 0x02575048, # Vprv
|
||||||
}
|
}
|
||||||
|
XPRV_HEADERS_INV = inv_dict(XPRV_HEADERS)
|
||||||
XPUB_HEADERS = {
|
XPUB_HEADERS = {
|
||||||
'standard': 0x043587cf, # tpub
|
'standard': 0x043587cf, # tpub
|
||||||
'p2wpkh-p2sh': 0x044a5262, # upub
|
'p2wpkh-p2sh': 0x044a5262, # upub
|
||||||
|
@ -99,6 +104,7 @@ class BitcoinTestnet(AbstractNet):
|
||||||
'p2wpkh': 0x045f1cf6, # vpub
|
'p2wpkh': 0x045f1cf6, # vpub
|
||||||
'p2wsh': 0x02575483, # Vpub
|
'p2wsh': 0x02575483, # Vpub
|
||||||
}
|
}
|
||||||
|
XPUB_HEADERS_INV = inv_dict(XPUB_HEADERS)
|
||||||
BIP44_COIN_TYPE = 1
|
BIP44_COIN_TYPE = 1
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -229,6 +229,9 @@ class ECPubkey(object):
|
||||||
def point(self) -> Tuple[int, int]:
|
def point(self) -> Tuple[int, int]:
|
||||||
return self._pubkey.point.x(), self._pubkey.point.y()
|
return self._pubkey.point.x(), self._pubkey.point.y()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ECPubkey {self.get_public_key_hex()}>"
|
||||||
|
|
||||||
def __mul__(self, other: int):
|
def __mul__(self, other: int):
|
||||||
if not isinstance(other, int):
|
if not isinstance(other, int):
|
||||||
raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other)))
|
raise TypeError('multiplication not defined for ECPubkey and {}'.format(type(other)))
|
||||||
|
@ -375,6 +378,12 @@ class ECPrivkey(ECPubkey):
|
||||||
privkey_32bytes = number_to_string(scalar, CURVE_ORDER)
|
privkey_32bytes = number_to_string(scalar, CURVE_ORDER)
|
||||||
return privkey_32bytes
|
return privkey_32bytes
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<ECPrivkey {self.get_public_key_hex()}>"
|
||||||
|
|
||||||
|
def get_secret_bytes(self) -> bytes:
|
||||||
|
return number_to_string(self.secret_scalar, CURVE_ORDER)
|
||||||
|
|
||||||
def sign(self, data: bytes, sigencode=None, sigdecode=None) -> bytes:
|
def sign(self, data: bytes, sigencode=None, sigdecode=None) -> bytes:
|
||||||
if sigencode is None:
|
if sigencode is None:
|
||||||
sigencode = sig_string_from_r_and_s
|
sigencode = sig_string_from_r_and_s
|
||||||
|
|
|
@ -31,10 +31,8 @@ from typing import Tuple
|
||||||
from . import bitcoin, ecc, constants, bip32
|
from . import bitcoin, ecc, constants, bip32
|
||||||
from .bitcoin import (deserialize_privkey, serialize_privkey,
|
from .bitcoin import (deserialize_privkey, serialize_privkey,
|
||||||
public_key_to_p2pkh)
|
public_key_to_p2pkh)
|
||||||
from .bip32 import (bip32_public_derivation, deserialize_xpub, CKD_pub,
|
from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
|
||||||
bip32_root, deserialize_xprv, bip32_private_derivation,
|
is_xpub, is_xprv, BIP32Node)
|
||||||
bip32_private_key, bip32_derivation, BIP32_PRIME,
|
|
||||||
is_xpub, is_xprv)
|
|
||||||
from .ecc import string_to_number, number_to_string
|
from .ecc import string_to_number, number_to_string
|
||||||
from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
|
from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
|
||||||
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
|
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
|
||||||
|
@ -133,6 +131,9 @@ class Software_KeyStore(KeyStore):
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
raise NotImplementedError() # implemented by subclasses
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
|
def get_private_key(self, *args, **kwargs) -> Tuple[bytes, bool]:
|
||||||
|
raise NotImplementedError() # implemented by subclasses
|
||||||
|
|
||||||
|
|
||||||
class Imported_KeyStore(Software_KeyStore):
|
class Imported_KeyStore(Software_KeyStore):
|
||||||
# keystore for imported private keys
|
# keystore for imported private keys
|
||||||
|
@ -263,7 +264,8 @@ class Xpub:
|
||||||
def derive_pubkey(self, for_change, n):
|
def derive_pubkey(self, for_change, n):
|
||||||
xpub = self.xpub_change if for_change else self.xpub_receive
|
xpub = self.xpub_change if for_change else self.xpub_receive
|
||||||
if xpub is None:
|
if xpub is None:
|
||||||
xpub = bip32_public_derivation(self.xpub, "", "/%d"%for_change)
|
rootnode = BIP32Node.from_xkey(self.xpub)
|
||||||
|
xpub = rootnode.subkey_at_public_derivation((for_change,)).to_xpub()
|
||||||
if for_change:
|
if for_change:
|
||||||
self.xpub_change = xpub
|
self.xpub_change = xpub
|
||||||
else:
|
else:
|
||||||
|
@ -272,10 +274,8 @@ class Xpub:
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_pubkey_from_xpub(self, xpub, sequence):
|
def get_pubkey_from_xpub(self, xpub, sequence):
|
||||||
_, _, _, _, c, cK = deserialize_xpub(xpub)
|
node = BIP32Node.from_xkey(xpub).subkey_at_public_derivation(sequence)
|
||||||
for i in sequence:
|
return node.eckey.get_public_key_hex(compressed=True)
|
||||||
cK, c = CKD_pub(cK, c, i)
|
|
||||||
return bh2u(cK)
|
|
||||||
|
|
||||||
def get_xpubkey(self, c, i):
|
def get_xpubkey(self, c, i):
|
||||||
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))
|
s = ''.join(map(lambda x: bitcoin.int_to_hex(x,2), (c, i)))
|
||||||
|
@ -334,7 +334,7 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
|
|
||||||
def check_password(self, password):
|
def check_password(self, password):
|
||||||
xprv = pw_decode(self.xprv, password, version=self.pw_hash_version)
|
xprv = pw_decode(self.xprv, password, version=self.pw_hash_version)
|
||||||
if deserialize_xprv(xprv)[4] != deserialize_xpub(self.xpub)[4]:
|
if BIP32Node.from_xkey(xprv).chaincode != BIP32Node.from_xkey(self.xpub).chaincode:
|
||||||
raise InvalidPassword()
|
raise InvalidPassword()
|
||||||
|
|
||||||
def update_password(self, old_password, new_password):
|
def update_password(self, old_password, new_password):
|
||||||
|
@ -360,14 +360,14 @@ class BIP32_KeyStore(Deterministic_KeyStore, Xpub):
|
||||||
self.xpub = bip32.xpub_from_xprv(xprv)
|
self.xpub = bip32.xpub_from_xprv(xprv)
|
||||||
|
|
||||||
def add_xprv_from_seed(self, bip32_seed, xtype, derivation):
|
def add_xprv_from_seed(self, bip32_seed, xtype, derivation):
|
||||||
xprv, xpub = bip32_root(bip32_seed, xtype)
|
rootnode = BIP32Node.from_rootseed(bip32_seed, xtype=xtype)
|
||||||
xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
|
node = rootnode.subkey_at_private_derivation(derivation)
|
||||||
self.add_xprv(xprv)
|
self.add_xprv(node.to_xprv())
|
||||||
|
|
||||||
def get_private_key(self, sequence, password):
|
def get_private_key(self, sequence, password):
|
||||||
xprv = self.get_master_private_key(password)
|
xprv = self.get_master_private_key(password)
|
||||||
_, _, _, _, c, k = deserialize_xprv(xprv)
|
node = BIP32Node.from_xkey(xprv).subkey_at_private_derivation(sequence)
|
||||||
pk = bip32_private_key(sequence, k, c)
|
pk = node.eckey.get_secret_bytes()
|
||||||
return pk, True
|
return pk, True
|
||||||
|
|
||||||
|
|
||||||
|
@ -658,7 +658,7 @@ def xtype_from_derivation(derivation: str) -> str:
|
||||||
elif derivation.startswith("m/45'"):
|
elif derivation.startswith("m/45'"):
|
||||||
return 'standard'
|
return 'standard'
|
||||||
|
|
||||||
bip32_indices = list(bip32_derivation(derivation))
|
bip32_indices = convert_bip32_path_to_list_of_uint32(derivation)
|
||||||
if len(bip32_indices) >= 4:
|
if len(bip32_indices) >= 4:
|
||||||
if bip32_indices[0] == 48 + BIP32_PRIME:
|
if bip32_indices[0] == 48 + BIP32_PRIME:
|
||||||
# m / purpose' / coin_type' / account' / script_type' / change / address_index
|
# m / purpose' / coin_type' / account' / script_type' / change / address_index
|
||||||
|
|
|
@ -126,7 +126,7 @@ class Mnemonic(object):
|
||||||
print_error("wordlist has %d words"%len(self.wordlist))
|
print_error("wordlist has %d words"%len(self.wordlist))
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mnemonic_to_seed(self, mnemonic, passphrase):
|
def mnemonic_to_seed(self, mnemonic, passphrase) -> bytes:
|
||||||
PBKDF2_ROUNDS = 2048
|
PBKDF2_ROUNDS = 2048
|
||||||
mnemonic = normalize_text(mnemonic)
|
mnemonic = normalize_text(mnemonic)
|
||||||
passphrase = passphrase or ''
|
passphrase = passphrase or ''
|
||||||
|
|
|
@ -6,7 +6,7 @@ from struct import pack, unpack
|
||||||
import os, sys, time, io
|
import os, sys, time, io
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from electrum.bip32 import serialize_xpub, deserialize_xpub, InvalidMasterKeyVersionBytes
|
from electrum.bip32 import BIP32Node, InvalidMasterKeyVersionBytes
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import Device
|
from electrum.plugin import Device
|
||||||
from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey, Xpub
|
from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey, Xpub
|
||||||
|
@ -40,12 +40,7 @@ try:
|
||||||
def mitm_verify(self, sig, expect_xpub):
|
def mitm_verify(self, sig, expect_xpub):
|
||||||
# verify a signature (65 bytes) over the session key, using the master bip32 node
|
# verify a signature (65 bytes) over the session key, using the master bip32 node
|
||||||
# - customized to use specific EC library of Electrum.
|
# - customized to use specific EC library of Electrum.
|
||||||
from electrum.ecc import ECPubkey
|
pubkey = BIP32Node.from_xkey(expect_xpub).eckey
|
||||||
|
|
||||||
xtype, depth, parent_fingerprint, child_number, chain_code, K_or_k \
|
|
||||||
= deserialize_xpub(expect_xpub)
|
|
||||||
|
|
||||||
pubkey = ECPubkey(K_or_k)
|
|
||||||
try:
|
try:
|
||||||
pubkey.verify_message_hash(sig[1:65], self.session_key)
|
pubkey.verify_message_hash(sig[1:65], self.session_key)
|
||||||
return True
|
return True
|
||||||
|
@ -191,12 +186,12 @@ class CKCCClient:
|
||||||
# TODO handle timeout?
|
# TODO handle timeout?
|
||||||
# change type of xpub to the requested type
|
# change type of xpub to the requested type
|
||||||
try:
|
try:
|
||||||
__, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub)
|
node = BIP32Node.from_xkey(xpub)
|
||||||
except InvalidMasterKeyVersionBytes:
|
except InvalidMasterKeyVersionBytes:
|
||||||
raise UserFacingException(_('Invalid xpub magic. Make sure your {} device is set to the correct chain.')
|
raise UserFacingException(_('Invalid xpub magic. Make sure your {} device is set to the correct chain.')
|
||||||
.format(self.device)) from None
|
.format(self.device)) from None
|
||||||
if xtype != 'standard':
|
if xtype != 'standard':
|
||||||
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
xpub = node._replace(xtype=xtype).to_xpub()
|
||||||
return xpub
|
return xpub
|
||||||
|
|
||||||
def ping_check(self):
|
def ping_check(self):
|
||||||
|
|
|
@ -29,8 +29,9 @@ from xmlrpc.client import ServerProxy
|
||||||
from PyQt5.QtCore import QObject, pyqtSignal
|
from PyQt5.QtCore import QObject, pyqtSignal
|
||||||
from PyQt5.QtWidgets import QPushButton
|
from PyQt5.QtWidgets import QPushButton
|
||||||
|
|
||||||
from electrum import util, keystore, ecc, bip32, crypto
|
from electrum import util, keystore, ecc, crypto
|
||||||
from electrum import transaction
|
from electrum import transaction
|
||||||
|
from electrum.bip32 import BIP32Node
|
||||||
from electrum.plugin import BasePlugin, hook
|
from electrum.plugin import BasePlugin, hook
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.wallet import Multisig_Wallet
|
from electrum.wallet import Multisig_Wallet
|
||||||
|
@ -131,12 +132,12 @@ class Plugin(BasePlugin):
|
||||||
self.cosigner_list = []
|
self.cosigner_list = []
|
||||||
for key, keystore in wallet.keystores.items():
|
for key, keystore in wallet.keystores.items():
|
||||||
xpub = keystore.get_master_public_key()
|
xpub = keystore.get_master_public_key()
|
||||||
K = bip32.deserialize_xpub(xpub)[-1]
|
pubkey = BIP32Node.from_xkey(xpub).eckey.get_public_key_bytes(compressed=True)
|
||||||
_hash = bh2u(crypto.sha256d(K))
|
_hash = bh2u(crypto.sha256d(pubkey))
|
||||||
if not keystore.is_watching_only():
|
if not keystore.is_watching_only():
|
||||||
self.keys.append((key, _hash, window))
|
self.keys.append((key, _hash, window))
|
||||||
else:
|
else:
|
||||||
self.cosigner_list.append((window, xpub, K, _hash))
|
self.cosigner_list.append((window, xpub, pubkey, _hash))
|
||||||
if self.listener:
|
if self.listener:
|
||||||
self.listener.set_keyhashes([t[1] for t in self.keys])
|
self.listener.set_keyhashes([t[1] for t in self.keys])
|
||||||
|
|
||||||
|
@ -221,9 +222,8 @@ class Plugin(BasePlugin):
|
||||||
if not xprv:
|
if not xprv:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
k = bip32.deserialize_xprv(xprv)[-1]
|
privkey = BIP32Node.from_xkey(xprv).eckey
|
||||||
EC = ecc.ECPrivkey(k)
|
message = bh2u(privkey.decrypt_message(message))
|
||||||
message = bh2u(EC.decrypt_message(message))
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
traceback.print_exc(file=sys.stdout)
|
traceback.print_exc(file=sys.stdout)
|
||||||
window.show_error(_('Error decrypting message') + ':\n' + str(e))
|
window.show_error(_('Error decrypting message') + ':\n' + str(e))
|
||||||
|
|
|
@ -18,7 +18,7 @@ import time
|
||||||
from electrum.crypto import sha256d, EncodeAES_base64, EncodeAES_bytes, DecodeAES_bytes, hmac_oneshot
|
from electrum.crypto import sha256d, EncodeAES_base64, EncodeAES_bytes, DecodeAES_bytes, hmac_oneshot
|
||||||
from electrum.bitcoin import (TYPE_ADDRESS, push_script, var_int, public_key_to_p2pkh,
|
from electrum.bitcoin import (TYPE_ADDRESS, push_script, var_int, public_key_to_p2pkh,
|
||||||
is_address)
|
is_address)
|
||||||
from electrum.bip32 import serialize_xpub, deserialize_xpub
|
from electrum.bip32 import BIP32Node
|
||||||
from electrum import ecc
|
from electrum import ecc
|
||||||
from electrum.ecc import msg_magic
|
from electrum.ecc import msg_magic
|
||||||
from electrum.wallet import Standard_Wallet
|
from electrum.wallet import Standard_Wallet
|
||||||
|
@ -118,8 +118,8 @@ class DigitalBitbox_Client():
|
||||||
# only ever returns the mainnet standard type, but it is agnostic
|
# only ever returns the mainnet standard type, but it is agnostic
|
||||||
# to the type when signing.
|
# to the type when signing.
|
||||||
if xtype != 'standard' or constants.net.TESTNET:
|
if xtype != 'standard' or constants.net.TESTNET:
|
||||||
_, depth, fingerprint, child_number, c, cK = deserialize_xpub(xpub, net=constants.BitcoinMainnet)
|
node = BIP32Node.from_xkey(xpub, net=constants.BitcoinMainnet)
|
||||||
xpub = serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
xpub = node._replace(xtype=xtype).to_xpub()
|
||||||
return xpub
|
return xpub
|
||||||
else:
|
else:
|
||||||
raise Exception('no reply')
|
raise Exception('no reply')
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import time
|
import time
|
||||||
from struct import pack
|
from struct import pack
|
||||||
|
|
||||||
|
from electrum import ecc
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import PrintError, UserCancelled
|
from electrum.util import PrintError, UserCancelled
|
||||||
from electrum.keystore import bip39_normalize_passphrase
|
from electrum.keystore import bip39_normalize_passphrase
|
||||||
from electrum.bip32 import serialize_xpub, convert_bip32_path_to_list_of_uint32
|
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32
|
||||||
|
|
||||||
|
|
||||||
class GuiMixin(object):
|
class GuiMixin(object):
|
||||||
|
@ -154,7 +155,12 @@ class KeepKeyClientBase(GuiMixin, PrintError):
|
||||||
address_n = self.expand_path(bip32_path)
|
address_n = self.expand_path(bip32_path)
|
||||||
creating = False
|
creating = False
|
||||||
node = self.get_public_node(address_n, creating).node
|
node = self.get_public_node(address_n, creating).node
|
||||||
return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
|
return BIP32Node(xtype=xtype,
|
||||||
|
eckey=ecc.ECPubkey(node.public_key),
|
||||||
|
chaincode=node.chain_code,
|
||||||
|
depth=node.depth,
|
||||||
|
fingerprint=self.i4b(node.fingerprint),
|
||||||
|
child_number=self.i4b(node.child_num)).to_xpub()
|
||||||
|
|
||||||
def toggle_passphrase(self):
|
def toggle_passphrase(self):
|
||||||
if self.features.passphrase_protection:
|
if self.features.passphrase_protection:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import sys
|
||||||
|
|
||||||
from electrum.util import bfh, bh2u, UserCancelled, UserFacingException
|
from electrum.util import bfh, bh2u, UserCancelled, UserFacingException
|
||||||
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
||||||
from electrum.bip32 import deserialize_xpub
|
from electrum.bip32 import BIP32Node
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.transaction import deserialize, Transaction
|
from electrum.transaction import deserialize, Transaction
|
||||||
|
@ -227,13 +227,13 @@ class KeepKeyPlugin(HW_PluginBase):
|
||||||
label, language)
|
label, language)
|
||||||
|
|
||||||
def _make_node_path(self, xpub, address_n):
|
def _make_node_path(self, xpub, address_n):
|
||||||
_, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
|
bip32node = BIP32Node.from_xkey(xpub)
|
||||||
node = self.types.HDNodeType(
|
node = self.types.HDNodeType(
|
||||||
depth=depth,
|
depth=bip32node.depth,
|
||||||
fingerprint=int.from_bytes(fingerprint, 'big'),
|
fingerprint=int.from_bytes(bip32node.fingerprint, 'big'),
|
||||||
child_num=int.from_bytes(child_num, 'big'),
|
child_num=int.from_bytes(bip32node.child_number, 'big'),
|
||||||
chain_code=chain_code,
|
chain_code=bip32node.chaincode,
|
||||||
public_key=key,
|
public_key=bip32node.eckey.get_public_key_bytes(compressed=True),
|
||||||
)
|
)
|
||||||
return self.types.HDNodePathType(node=node, address_n=address_n)
|
return self.types.HDNodePathType(node=node, address_n=address_n)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,9 @@ import hashlib
|
||||||
import sys
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
|
from electrum import ecc
|
||||||
from electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int
|
from electrum.bitcoin import TYPE_ADDRESS, int_to_hex, var_int
|
||||||
from electrum.bip32 import serialize_xpub
|
from electrum.bip32 import BIP32Node
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.keystore import Hardware_KeyStore
|
from electrum.keystore import Hardware_KeyStore
|
||||||
from electrum.transaction import Transaction
|
from electrum.transaction import Transaction
|
||||||
|
@ -112,8 +113,12 @@ class Ledger_Client():
|
||||||
depth = len(splitPath)
|
depth = len(splitPath)
|
||||||
lastChild = splitPath[len(splitPath) - 1].split('\'')
|
lastChild = splitPath[len(splitPath) - 1].split('\'')
|
||||||
childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0])
|
childnum = int(lastChild[0]) if len(lastChild) == 1 else 0x80000000 | int(lastChild[0])
|
||||||
xpub = serialize_xpub(xtype, nodeData['chainCode'], publicKey, depth, self.i4b(fingerprint), self.i4b(childnum))
|
return BIP32Node(xtype=xtype,
|
||||||
return xpub
|
eckey=ecc.ECPubkey(publicKey),
|
||||||
|
chaincode=nodeData['chainCode'],
|
||||||
|
depth=depth,
|
||||||
|
fingerprint=self.i4b(fingerprint),
|
||||||
|
child_number=self.i4b(childnum)).to_xpub()
|
||||||
|
|
||||||
def has_detached_pin_support(self, client):
|
def has_detached_pin_support(self, client):
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import time
|
import time
|
||||||
from struct import pack
|
from struct import pack
|
||||||
|
|
||||||
|
from electrum import ecc
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import PrintError, UserCancelled
|
from electrum.util import PrintError, UserCancelled
|
||||||
from electrum.keystore import bip39_normalize_passphrase
|
from electrum.keystore import bip39_normalize_passphrase
|
||||||
from electrum.bip32 import serialize_xpub, convert_bip32_path_to_list_of_uint32
|
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32
|
||||||
|
|
||||||
|
|
||||||
class GuiMixin(object):
|
class GuiMixin(object):
|
||||||
|
@ -156,7 +157,12 @@ class SafeTClientBase(GuiMixin, PrintError):
|
||||||
address_n = self.expand_path(bip32_path)
|
address_n = self.expand_path(bip32_path)
|
||||||
creating = False
|
creating = False
|
||||||
node = self.get_public_node(address_n, creating).node
|
node = self.get_public_node(address_n, creating).node
|
||||||
return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
|
return BIP32Node(xtype=xtype,
|
||||||
|
eckey=ecc.ECPubkey(node.public_key),
|
||||||
|
chaincode=node.chain_code,
|
||||||
|
depth=node.depth,
|
||||||
|
fingerprint=self.i4b(node.fingerprint),
|
||||||
|
child_number=self.i4b(node.child_num)).to_xpub()
|
||||||
|
|
||||||
def toggle_passphrase(self):
|
def toggle_passphrase(self):
|
||||||
if self.features.passphrase_protection:
|
if self.features.passphrase_protection:
|
||||||
|
|
|
@ -4,7 +4,7 @@ import sys
|
||||||
|
|
||||||
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
|
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
|
||||||
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
||||||
from electrum.bip32 import deserialize_xpub
|
from electrum.bip32 import BIP32Node
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import Device
|
from electrum.plugin import Device
|
||||||
|
@ -244,13 +244,13 @@ class SafeTPlugin(HW_PluginBase):
|
||||||
label, language)
|
label, language)
|
||||||
|
|
||||||
def _make_node_path(self, xpub, address_n):
|
def _make_node_path(self, xpub, address_n):
|
||||||
_, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
|
bip32node = BIP32Node.from_xkey(xpub)
|
||||||
node = self.types.HDNodeType(
|
node = self.types.HDNodeType(
|
||||||
depth=depth,
|
depth=bip32node.depth,
|
||||||
fingerprint=int.from_bytes(fingerprint, 'big'),
|
fingerprint=int.from_bytes(bip32node.fingerprint, 'big'),
|
||||||
child_num=int.from_bytes(child_num, 'big'),
|
child_num=int.from_bytes(bip32node.child_number, 'big'),
|
||||||
chain_code=chain_code,
|
chain_code=bip32node.chaincode,
|
||||||
public_key=key,
|
public_key=bip32node.eckey.get_public_key_bytes(compressed=True),
|
||||||
)
|
)
|
||||||
return self.types.HDNodePathType(node=node, address_n=address_n)
|
return self.types.HDNodePathType(node=node, address_n=address_n)
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import time
|
import time
|
||||||
from struct import pack
|
from struct import pack
|
||||||
|
|
||||||
|
from electrum import ecc
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.util import PrintError, UserCancelled, UserFacingException
|
from electrum.util import PrintError, UserCancelled, UserFacingException
|
||||||
from electrum.keystore import bip39_normalize_passphrase
|
from electrum.keystore import bip39_normalize_passphrase
|
||||||
from electrum.bip32 import serialize_xpub, convert_bip32_path_to_list_of_uint32 as parse_path
|
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
|
||||||
|
|
||||||
from trezorlib.client import TrezorClient
|
from trezorlib.client import TrezorClient
|
||||||
from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError
|
from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError
|
||||||
|
@ -120,7 +121,12 @@ class TrezorClientBase(PrintError):
|
||||||
address_n = parse_path(bip32_path)
|
address_n = parse_path(bip32_path)
|
||||||
with self.run_flow(creating_wallet=creating):
|
with self.run_flow(creating_wallet=creating):
|
||||||
node = trezorlib.btc.get_public_node(self.client, address_n).node
|
node = trezorlib.btc.get_public_node(self.client, address_n).node
|
||||||
return serialize_xpub(xtype, node.chain_code, node.public_key, node.depth, self.i4b(node.fingerprint), self.i4b(node.child_num))
|
return BIP32Node(xtype=xtype,
|
||||||
|
eckey=ecc.ECPubkey(node.public_key),
|
||||||
|
chaincode=node.chain_code,
|
||||||
|
depth=node.depth,
|
||||||
|
fingerprint=self.i4b(node.fingerprint),
|
||||||
|
child_number=self.i4b(node.child_num)).to_xpub()
|
||||||
|
|
||||||
def toggle_passphrase(self):
|
def toggle_passphrase(self):
|
||||||
if self.features.passphrase_protection:
|
if self.features.passphrase_protection:
|
||||||
|
|
|
@ -3,7 +3,7 @@ import sys
|
||||||
|
|
||||||
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
|
from electrum.util import bfh, bh2u, versiontuple, UserCancelled, UserFacingException
|
||||||
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
from electrum.bitcoin import TYPE_ADDRESS, TYPE_SCRIPT
|
||||||
from electrum.bip32 import deserialize_xpub, convert_bip32_path_to_list_of_uint32 as parse_path
|
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
|
||||||
from electrum import constants
|
from electrum import constants
|
||||||
from electrum.i18n import _
|
from electrum.i18n import _
|
||||||
from electrum.plugin import Device
|
from electrum.plugin import Device
|
||||||
|
@ -241,13 +241,13 @@ class TrezorPlugin(HW_PluginBase):
|
||||||
raise RuntimeError("Unsupported recovery method")
|
raise RuntimeError("Unsupported recovery method")
|
||||||
|
|
||||||
def _make_node_path(self, xpub, address_n):
|
def _make_node_path(self, xpub, address_n):
|
||||||
_, depth, fingerprint, child_num, chain_code, key = deserialize_xpub(xpub)
|
bip32node = BIP32Node.from_xkey(xpub)
|
||||||
node = HDNodeType(
|
node = HDNodeType(
|
||||||
depth=depth,
|
depth=bip32node.depth,
|
||||||
fingerprint=int.from_bytes(fingerprint, 'big'),
|
fingerprint=int.from_bytes(bip32node.fingerprint, 'big'),
|
||||||
child_num=int.from_bytes(child_num, 'big'),
|
child_num=int.from_bytes(bip32node.child_number, 'big'),
|
||||||
chain_code=chain_code,
|
chain_code=bip32node.chaincode,
|
||||||
public_key=key,
|
public_key=bip32node.eckey.get_public_key_bytes(compressed=True),
|
||||||
)
|
)
|
||||||
return HDNodePathType(node=node, address_n=address_n)
|
return HDNodePathType(node=node, address_n=address_n)
|
||||||
|
|
||||||
|
|
|
@ -37,8 +37,7 @@ from aiohttp import ClientResponse
|
||||||
|
|
||||||
from electrum import ecc, constants, keystore, version, bip32, bitcoin
|
from electrum import ecc, constants, keystore, version, bip32, bitcoin
|
||||||
from electrum.bitcoin import TYPE_ADDRESS
|
from electrum.bitcoin import TYPE_ADDRESS
|
||||||
from electrum.bip32 import (deserialize_xpub, deserialize_xprv, bip32_private_key, CKD_pub,
|
from electrum.bip32 import CKD_pub, BIP32Node, xpub_type
|
||||||
serialize_xpub, bip32_root, bip32_private_derivation, xpub_type)
|
|
||||||
from electrum.crypto import sha256
|
from electrum.crypto import sha256
|
||||||
from electrum.transaction import TxOutput
|
from electrum.transaction import TxOutput
|
||||||
from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type
|
from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type
|
||||||
|
@ -59,9 +58,8 @@ def get_signing_xpub(xtype):
|
||||||
raise NotImplementedError('xtype: {}'.format(xtype))
|
raise NotImplementedError('xtype: {}'.format(xtype))
|
||||||
if xtype == 'standard':
|
if xtype == 'standard':
|
||||||
return xpub
|
return xpub
|
||||||
_, depth, fingerprint, child_number, c, cK = bip32.deserialize_xpub(xpub)
|
node = BIP32Node.from_xkey(xpub)
|
||||||
xpub = bip32.serialize_xpub(xtype, c, cK, depth, fingerprint, child_number)
|
return node._replace(xtype=xtype).to_xpub()
|
||||||
return xpub
|
|
||||||
|
|
||||||
def get_billing_xpub():
|
def get_billing_xpub():
|
||||||
if constants.net.TESTNET:
|
if constants.net.TESTNET:
|
||||||
|
@ -388,20 +386,26 @@ def get_user_id(storage):
|
||||||
short_id = hashlib.sha256(long_id).hexdigest()
|
short_id = hashlib.sha256(long_id).hexdigest()
|
||||||
return long_id, short_id
|
return long_id, short_id
|
||||||
|
|
||||||
def make_xpub(xpub, s):
|
def make_xpub(xpub, s) -> str:
|
||||||
version, _, _, _, c, cK = deserialize_xpub(xpub)
|
rootnode = BIP32Node.from_xkey(xpub)
|
||||||
cK2, c2 = bip32._CKD_pub(cK, c, s)
|
child_pubkey, child_chaincode = bip32._CKD_pub(parent_pubkey=rootnode.eckey.get_public_key_bytes(compressed=True),
|
||||||
return serialize_xpub(version, c2, cK2)
|
parent_chaincode=rootnode.chaincode,
|
||||||
|
child_index=s)
|
||||||
|
child_node = BIP32Node(xtype=rootnode.xtype,
|
||||||
|
eckey=ecc.ECPubkey(child_pubkey),
|
||||||
|
chaincode=child_chaincode)
|
||||||
|
return child_node.to_xpub()
|
||||||
|
|
||||||
def make_billing_address(wallet, num, addr_type):
|
def make_billing_address(wallet, num, addr_type):
|
||||||
long_id, short_id = wallet.get_user_id()
|
long_id, short_id = wallet.get_user_id()
|
||||||
xpub = make_xpub(get_billing_xpub(), long_id)
|
xpub = make_xpub(get_billing_xpub(), long_id)
|
||||||
version, _, _, _, c, cK = deserialize_xpub(xpub)
|
usernode = BIP32Node.from_xkey(xpub)
|
||||||
cK, c = CKD_pub(cK, c, num)
|
child_node = usernode.subkey_at_public_derivation([num])
|
||||||
|
pubkey = child_node.eckey.get_public_key_bytes(compressed=True)
|
||||||
if addr_type == 'legacy':
|
if addr_type == 'legacy':
|
||||||
return bitcoin.public_key_to_p2pkh(cK)
|
return bitcoin.public_key_to_p2pkh(pubkey)
|
||||||
elif addr_type == 'segwit':
|
elif addr_type == 'segwit':
|
||||||
return bitcoin.public_key_to_p2wpkh(cK)
|
return bitcoin.public_key_to_p2wpkh(pubkey)
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'unexpected billing type: {addr_type}')
|
raise ValueError(f'unexpected billing type: {addr_type}')
|
||||||
|
|
||||||
|
@ -538,9 +542,9 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
assert is_any_2fa_seed_type(t)
|
assert is_any_2fa_seed_type(t)
|
||||||
xtype = 'standard' if t == '2fa' else 'p2wsh'
|
xtype = 'standard' if t == '2fa' else 'p2wsh'
|
||||||
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
|
bip32_seed = Mnemonic.mnemonic_to_seed(seed, passphrase)
|
||||||
xprv, xpub = bip32_root(bip32_seed, xtype)
|
rootnode = BIP32Node.from_rootseed(bip32_seed, xtype=xtype)
|
||||||
xprv, xpub = bip32_private_derivation(xprv, "m/", derivation)
|
child_node = rootnode.subkey_at_private_derivation(derivation)
|
||||||
return xprv, xpub
|
return child_node.to_xprv(), child_node.to_xpub()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def xkeys_from_seed(self, seed, passphrase):
|
def xkeys_from_seed(self, seed, passphrase):
|
||||||
|
@ -721,9 +725,8 @@ class TrustedCoinPlugin(BasePlugin):
|
||||||
challenge = r.get('challenge')
|
challenge = r.get('challenge')
|
||||||
message = 'TRUSTEDCOIN CHALLENGE: ' + challenge
|
message = 'TRUSTEDCOIN CHALLENGE: ' + challenge
|
||||||
def f(xprv):
|
def f(xprv):
|
||||||
_, _, _, _, c, k = deserialize_xprv(xprv)
|
rootnode = BIP32Node.from_xkey(xprv)
|
||||||
pk = bip32_private_key([0, 0], k, c)
|
key = rootnode.subkey_at_private_derivation((0, 0)).eckey
|
||||||
key = ecc.ECPrivkey(pk)
|
|
||||||
sig = key.sign_message(message, True)
|
sig = key.sign_message(message, True)
|
||||||
return base64.b64encode(sig).decode()
|
return base64.b64encode(sig).decode()
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@ from electrum.bitcoin import (public_key_to_p2pkh, address_from_private_key,
|
||||||
is_compressed_privkey, EncodeBase58Check,
|
is_compressed_privkey, EncodeBase58Check,
|
||||||
script_num_to_hex, push_script, add_number_to_script, int_to_hex,
|
script_num_to_hex, push_script, add_number_to_script, int_to_hex,
|
||||||
opcodes)
|
opcodes)
|
||||||
from electrum.bip32 import (bip32_root, bip32_public_derivation, bip32_private_derivation,
|
from electrum.bip32 import (BIP32Node,
|
||||||
xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation,
|
xpub_from_xprv, xpub_type, is_xprv, is_bip32_derivation,
|
||||||
is_xpub, convert_bip32_path_to_list_of_uint32)
|
is_xpub, convert_bip32_path_to_list_of_uint32)
|
||||||
from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS
|
from electrum.crypto import sha256d, SUPPORTED_PW_HASH_VERSIONS
|
||||||
|
@ -405,19 +405,18 @@ class Test_xprv_xpub(SequentialTestCase):
|
||||||
'xtype': 'p2wpkh'},
|
'xtype': 'p2wpkh'},
|
||||||
)
|
)
|
||||||
|
|
||||||
def _do_test_bip32(self, seed, sequence):
|
def _do_test_bip32(self, seed: str, sequence):
|
||||||
xprv, xpub = bip32_root(bfh(seed), 'standard')
|
node = BIP32Node.from_rootseed(bfh(seed), xtype='standard')
|
||||||
|
xprv, xpub = node.to_xprv(), node.to_xpub()
|
||||||
self.assertEqual("m/", sequence[0:2])
|
self.assertEqual("m/", sequence[0:2])
|
||||||
path = 'm'
|
|
||||||
sequence = sequence[2:]
|
sequence = sequence[2:]
|
||||||
for n in sequence.split('/'):
|
for n in sequence.split('/'):
|
||||||
child_path = path + '/' + n
|
|
||||||
if n[-1] != "'":
|
if n[-1] != "'":
|
||||||
xpub2 = bip32_public_derivation(xpub, path, child_path)
|
xpub2 = BIP32Node.from_xkey(xpub).subkey_at_public_derivation(n).to_xpub()
|
||||||
xprv, xpub = bip32_private_derivation(xprv, path, child_path)
|
node = BIP32Node.from_xkey(xprv).subkey_at_private_derivation(n)
|
||||||
|
xprv, xpub = node.to_xprv(), node.to_xpub()
|
||||||
if n[-1] != "'":
|
if n[-1] != "'":
|
||||||
self.assertEqual(xpub, xpub2)
|
self.assertEqual(xpub, xpub2)
|
||||||
path = child_path
|
|
||||||
|
|
||||||
return xpub, xprv
|
return xpub, xprv
|
||||||
|
|
||||||
|
@ -474,7 +473,7 @@ class Test_xprv_xpub(SequentialTestCase):
|
||||||
def test_convert_bip32_path_to_list_of_uint32(self):
|
def test_convert_bip32_path_to_list_of_uint32(self):
|
||||||
self.assertEqual([0, 0x80000001, 0x80000001], convert_bip32_path_to_list_of_uint32("m/0/-1/1'"))
|
self.assertEqual([0, 0x80000001, 0x80000001], convert_bip32_path_to_list_of_uint32("m/0/-1/1'"))
|
||||||
self.assertEqual([], convert_bip32_path_to_list_of_uint32("m/"))
|
self.assertEqual([], convert_bip32_path_to_list_of_uint32("m/"))
|
||||||
self.assertEqual([2147483692, 2147488889, 221], convert_bip32_path_to_list_of_uint32("m/44'/5241'/221"))
|
self.assertEqual([2147483692, 2147488889, 221], convert_bip32_path_to_list_of_uint32("m/44'/5241h/221"))
|
||||||
|
|
||||||
def test_xtype_from_derivation(self):
|
def test_xtype_from_derivation(self):
|
||||||
self.assertEqual('standard', xtype_from_derivation("m/44'"))
|
self.assertEqual('standard', xtype_from_derivation("m/44'"))
|
||||||
|
|
Loading…
Add table
Reference in a new issue