def test_crack() -> None: parent_xpub = "xpub6BabMgRo8rKHfpAb8waRM5vj2AneD4kDMsJhm7jpBDHSJvrFAjHJHU5hM43YgsuJVUVHWacAcTsgnyRptfMdMP8b28LYfqGocGdKCFjhQMV" child_xprv = "xprv9xkG88dGyiurKbVbPH1kjdYrA8poBBBXa53RKuRGJXyruuoJUDd8e4m6poiz7rV8Z4NoM5AJNcPHN6aj8wRFt5CWvF8VPfQCrDUcLU5tcTm" parent_xprv = crack_prv_key(parent_xpub, child_xprv) assert xpub_from_xprv(parent_xprv) == parent_xpub # same check with XKeyDict parent_xprv = crack_prv_key(BIP32KeyData.b58decode(parent_xpub), BIP32KeyData.b58decode(child_xprv)) assert xpub_from_xprv(parent_xprv) == parent_xpub err_msg = "extended parent key is not a public key: " with pytest.raises(BTClibValueError, match=err_msg): crack_prv_key(parent_xprv, child_xprv) err_msg = "extended child key is not a private key: " with pytest.raises(BTClibValueError, match=err_msg): crack_prv_key(parent_xpub, parent_xpub) child_xpub = xpub_from_xprv(child_xprv) with pytest.raises(BTClibValueError, match="not a parent's child: wrong depths"): crack_prv_key(child_xpub, child_xprv) child0_xprv = derive(parent_xprv, 0) grandchild_xprv = derive(child0_xprv, 0) err_msg = "not a parent's child: wrong parent fingerprint" with pytest.raises(BTClibValueError, match=err_msg): crack_prv_key(child_xpub, grandchild_xprv) hardened_child_xprv = derive(parent_xprv, 0x80000000) with pytest.raises(BTClibValueError, match="hardened child derivation"): crack_prv_key(parent_xpub, hardened_child_xprv)
def test_invalid_bip32_xkeys() -> None: filename = path.join(data_folder, "bip32_invalid_keys.json") with open(filename, "r") as file_: test_vectors = json.load(file_) for xkey, err_msg in test_vectors: with pytest.raises(BTClibValueError, match=re.escape(err_msg)): BIP32KeyData.b58decode(xkey)
def test_invalid_bip32_xkeys() -> None: """BIP32 test vectors #5 https://github.com/bitcoin/bips/pull/921 """ filename = path.join(data_folder, "bip32_invalid_keys.json") with open(filename, "r", encoding="ascii") as file_: test_vectors = json.load(file_) for xkey, err_msg in test_vectors: with pytest.raises(BTClibValueError, match=re.escape(err_msg)): BIP32KeyData.b58decode(xkey)
def _pub_keyinfo_from_xpub(xpub: BIP32Key, network: Optional[str] = None, compressed: Optional[bool] = None) -> PubkeyInfo: """Return the pub_key tuple (SEC-bytes, network) from a BIP32 xpub. BIP32Key is always compressed and includes network information: here the 'network, compressed' input parameters are passed only to allow consistency checks. """ compressed = True if compressed is None else compressed if not compressed: raise BTClibValueError("Uncompressed SEC / compressed BIP32 mismatch") if isinstance(xpub, BIP32KeyData): xpub.assert_valid() else: xpub = BIP32KeyData.b58decode(xpub) if xpub.key[0] not in (2, 3): err_msg = f"not a public key: {xpub.b58encode()}" raise BTClibValueError(err_msg) if network is None: return xpub.key, network_from_xkeyversion(xpub.version) allowed_versions = xpubversions_from_network(network) if xpub.version not in allowed_versions: err_msg = f"Not a {network} key: " err_msg += f"{xpub.b58encode()}" raise BTClibValueError(err_msg) return xpub.key, network
def _prv_keyinfo_from_xprv(xprv: BIP32Key, network: Optional[str] = None, compressed: Optional[bool] = None) -> PrvkeyInfo: """Return prv_key tuple (int, compressed, network) from BIP32 xprv. BIP32Key is always compressed and includes network information: here the 'network, compressed' input parameters are passed only to allow consistency checks. """ compressed = True if compressed is None else compressed if not compressed: raise BTClibValueError("uncompressed SEC / compressed BIP32 mismatch") if isinstance(xprv, BIP32KeyData): xprv.assert_valid() else: xprv = BIP32KeyData.b58decode(xprv) if xprv.key[0] != 0: err_msg = f"not a private key: {xprv.b58encode()}" raise BTClibValueError(err_msg) if network is None: network = network_from_xkeyversion(xprv.version) allowed_versions = xprvversions_from_network(network) if xprv.version not in allowed_versions: err_msg = f"not a {network} key: " err_msg += f"{xprv.b58encode()}" raise BTClibValueError(err_msg) q = int.from_bytes(xprv.key[1:], byteorder="big") return q, network, True
def test_point_from_bip340pub_key() -> None: q, x_Q = ssa.gen_keys() Q = mult(q) # Integer (int) assert ssa.point_from_bip340pub_key(x_Q) == Q # Integer (bytes) x_Q_bytes = x_Q.to_bytes(32, "big", signed=False) assert ssa.point_from_bip340pub_key(x_Q_bytes) == Q # Integer (hex-str) assert ssa.point_from_bip340pub_key(x_Q_bytes.hex()) == Q # tuple Point assert ssa.point_from_bip340pub_key(Q) == Q # 33 bytes assert ssa.point_from_bip340pub_key(bytes_from_point(Q)) == Q # 33 bytes hex-string assert ssa.point_from_bip340pub_key(bytes_from_point(Q).hex()) == Q # 65 bytes assert ssa.point_from_bip340pub_key(bytes_from_point( Q, compressed=False)) == Q # 65 bytes hex-string assert (ssa.point_from_bip340pub_key( bytes_from_point(Q, compressed=False).hex()) == Q) xpub_data = BIP32KeyData.b58decode( "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" ) xpub_data.key = bytes_from_point(Q) # BIP32KeyData assert ssa.point_from_bip340pub_key(xpub_data) == Q # BIP32Key encoded str xpub = xpub_data.b58encode() assert ssa.point_from_bip340pub_key(xpub) == Q # BIP32Key str assert ssa.point_from_bip340pub_key(xpub.encode("ascii")) == Q
def test_fingerprint() -> None: seed = "bfc4cbaad0ff131aa97fa30a48d09ae7df914bcc083af1e07793cd0a7c61a03f65d622848209ad3366a419f4718a80ec9037df107d8d12c19b83202de00a40ad" xprv = rootxprv_from_seed(seed) pf = fingerprint(xprv) # xprv is automatically converted to xpub child_key = derive(xprv, 0x80000000) pf2 = BIP32KeyData.b58decode(child_key).parent_fingerprint assert pf == pf2
def address_from_xpub(xpub: BIP32Key) -> str: """Return the SLIP132 base58/bech32 address. The address is always derived from the compressed public key, as this is the default public key representation in BIP32. """ if not isinstance(xpub, BIP32KeyData): xpub = BIP32KeyData.b58decode(xpub) if xpub.key[0] not in (2, 3): err_msg = f"not a public key: {xpub.b58encode()}" raise BTClibValueError(err_msg) function_list: List[Callable[[Any, str], str]] = [ b58.p2pkh, b32.p2wpkh, b58.p2wpkh_p2sh, ] version_list: List[str] = [ "bip32_pub", "slip132_p2wpkh_pub", "slip132_p2wpkh_p2sh_pub", ] for version, function in zip(version_list, function_list): # with pytohn>=3.8 use walrus operator # if network := network_from_key_value(version, xpub.version): network = network_from_key_value(version, xpub.version) if network: return function(xpub, network) err_msg = f"unknown xpub version: {xpub.version.hex()}" # pragma: no cover raise BTClibValueError(err_msg) # pragma: no cover
def _helper_checks(xkey: BIP32Key, check_root_xkey: bool) -> Tuple[BIP32KeyData, Network]: if not isinstance(xkey, BIP32KeyData): xkey = BIP32KeyData.b58decode(xkey) if check_root_xkey and not xkey.is_root: raise BTClibValueError(f"not a root key: {xkey.b58encode()}") network = NETWORKS[network_from_xkeyversion(xkey.version)] return xkey, network
def test_derive_exceptions() -> None: # root key, zero depth rootmxprv = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" xprv = BIP32KeyData.b58decode(rootmxprv) # FIXME # assert xprv == _derive(xprv, "m") assert rootmxprv == derive(xprv, "m") assert rootmxprv == derive(xprv, "") fingerprint = hashes.hash160(pub_keyinfo_from_key(xprv)[0])[:4] assert fingerprint == _derive(xprv, bytes.fromhex("80000000")).parent_fingerprint for der_path in ("/1", "800000", "80000000"): xkey = _derive(xprv, der_path) assert fingerprint == xkey.parent_fingerprint err_msg = "invalid literal for int" for der_path in (";/0", "invalid index"): with pytest.raises(ValueError, match=err_msg): derive(xprv, der_path) with pytest.raises(BTClibValueError, match="depth greater than 255: "): derive(xprv, "m" + 256 * "/0") with pytest.raises(BTClibValueError, match="index are not a multiple of 4-bytes: "): derive(xprv, b"\x00" * 5) for index in (2**32, 0x8000000000): with pytest.raises(OverflowError, match="int too big to convert"): derive(xprv, index) xprv = _derive(xprv, "1") err_msg = "final depth greater than 255: " with pytest.raises(BTClibValueError, match=err_msg): derive(xprv, "m" + 255 * "/0") rootxprv = "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS" temp = base58.b58decode(rootxprv) bad_xprv = base58.b58encode(temp[:45] + b"\x02" + temp[46:], 78) err_msg = "invalid private key prefix: " with pytest.raises(BTClibValueError, match=err_msg): derive(bad_xprv, 0x80000000) xpub = xpub_from_xprv(rootxprv) temp = base58.b58decode(xpub) bad_xpub = base58.b58encode(temp[:45] + b"\x00" + temp[46:], 78) err_msg = r"invalid public key prefix not in \(0x02, 0x03\): " with pytest.raises(BTClibValueError, match=err_msg): derive(bad_xpub, 0x80000000) err_msg = "hardened derivation from public key" with pytest.raises(BTClibValueError, match=err_msg): derive(xpub, 0x80000000)
def _point_from_xpub(xpub: BIP32Key, ec: Curve) -> Point: "Return an elliptic curve point tuple from a xpub key." if isinstance(xpub, BIP32KeyData): xpub.assert_valid() else: xpub = BIP32KeyData.b58decode(xpub) if xpub.key[0] in (2, 3): ec2 = curve_from_xkeyversion(xpub.version) if ec != ec2: raise BTClibValueError(f"ec/xpub version ({xpub.version.hex()}) mismatch") return point_from_octets(xpub.key, ec) raise BTClibValueError(f"not a public key: {xpub.key.hex()}")
def test_serialization() -> None: xkey = "xprv9s21ZrQH143K3QTDL4LXw2F7HEK3wJUD2nW2nRk4stbPy6cq3jPPqjiChkVvvNKmPGJxWUtg6LnF5kejMRNNU3TGtRBeJgk33yuGBxrMPHi" xkey_data = BIP32KeyData.b58decode(xkey) decoded_key = base58.b58decode(xkey, 78) assert xkey_data.version == decoded_key[:4] assert xkey_data.depth == decoded_key[4] assert xkey_data.parent_fingerprint == decoded_key[5:9] assert xkey_data.index == int.from_bytes(decoded_key[9:13], "big", signed=False) assert xkey_data.chain_code == decoded_key[13:45] assert xkey_data.key == decoded_key[45:] assert xkey_data.b58encode() == xkey xpub = xpub_from_xprv(xkey) xpub2 = xpub_from_xprv(xkey_data) assert xpub == xpub2
def test_assert_valid2() -> None: xkey = "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS" xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.version = (xkey_data.version)[:-1] with pytest.raises(BTClibValueError, match="invalid version length: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.version = "1234" # type: ignore with pytest.raises(TypeError): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.depth = -1 with pytest.raises(BTClibValueError, match="invalid depth: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.depth = 256 with pytest.raises(BTClibValueError, match="invalid depth: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.depth = tuple() # type: ignore with pytest.raises(TypeError): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.parent_fingerprint = (xkey_data.parent_fingerprint)[:-1] with pytest.raises(BTClibValueError, match="invalid parent_fingerprint length: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.parent_fingerprint = "1234" # type: ignore with pytest.raises(TypeError): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.index = -1 with pytest.raises(BTClibValueError, match="invalid index: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.index = 0xFFFFFFFF + 1 with pytest.raises(BTClibValueError, match="invalid index: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.index = tuple() # type: ignore with pytest.raises(TypeError): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.chain_code = (xkey_data.chain_code)[:-1] with pytest.raises(BTClibValueError, match="invalid chain_code length: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.chain_code = "length is 32 but not a chaincode" # type: ignore assert len(xkey_data.chain_code) == 32 with pytest.raises(TypeError): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.key = (xkey_data.key)[:-1] with pytest.raises(BTClibValueError, match="invalid key length: "): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.key = "length is 33, but not a key " # type: ignore assert len(xkey_data.key) == 33 with pytest.raises(TypeError): xkey_data.assert_valid() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.parent_fingerprint = bytes.fromhex("deadbeef") err_msg = "zero depth with non-zero parent fingerprint: " with pytest.raises(BTClibValueError, match=err_msg): xkey_data.b58encode() xkey_data = BIP32KeyData.b58decode(xkey) xkey_data.index = 1 with pytest.raises(BTClibValueError, match="zero depth with non-zero index: "): xkey_data.b58encode()