def test_slip132_test_vector() -> None: """SLIP132 test vector https://github.com/satoshilabs/slips/blob/master/slip-0132.md """ mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" kpath = "./0/0" test_vectors: List[Tuple[bytes, str, str, str, str]] = [ ( NETWORKS["mainnet"]["bip32_prv"], "m / 44h / 0h / 0h", "xprv9xpXFhFpqdQK3TmytPBqXtGSwS3DLjojFhTGht8gwAAii8py5X6pxeBnQ6ehJiyJ6nDjWGJfZ95WxByFXVkDxHXrqu53WCRGypk2ttuqncb", "xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj", "1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA", ), ( NETWORKS["mainnet"]["slip132_p2wpkh_p2sh_prv"], "m / 49h / 0h / 0h", "yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF", "ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP", "37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf", ), ( NETWORKS["mainnet"]["slip132_p2wpkh_prv"], "m / 84h / 0h / 0h", "zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE", "zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs", "bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu", ), ] for version, der_path, prv, pub, addr_str in test_vectors: addr = addr_str.encode() rxprv = bip32.mxprv_from_bip39_mnemonic(mnemonic, "") mxprv = bip32.derive(rxprv, der_path, version) assert prv.encode() == mxprv mxpub = bip32.xpub_from_xprv(mxprv) assert pub.encode() == mxpub xpub = bip32.derive(mxpub, kpath) address = slip132.address_from_xpub(xpub) assert addr == address address = slip132.address_from_xkey(xpub) assert addr == address xprv = bip32.derive(mxprv, kpath) address = slip132.address_from_xkey(xprv) assert addr == address if version == NETWORKS["mainnet"]["bip32_prv"]: address = base58address.p2pkh(xpub) assert addr == address address = base58address.p2pkh(xprv) assert addr == address elif version == NETWORKS["mainnet"]["slip132_p2wpkh_p2sh_prv"]: address = base58address.p2wpkh_p2sh(xpub) assert addr == address address = base58address.p2wpkh_p2sh(xprv) assert addr == address elif version == NETWORKS["mainnet"]["slip132_p2wpkh_prv"]: address = bech32address.p2wpkh(xpub) assert addr == address address = bech32address.p2wpkh(xprv) assert addr == address
def test_exceptions() -> None: with pytest.raises(ValueError, match="not a private or public key: "): # invalid checksum xprv = "xppp9s21ZrQH143K2oxHiQ5f7D7WYgXD9h6HAXDBuMoozDGGiYHWsq7TLBj2yvGuHTLSPCaFmUyN1v3fJRiY2A4YuNSrqQMPVLZKt76goL6LP7L" p2pkh(xprv) xpub = "xpub6H1LXWLaKsWFhvm6RVpEL9P4KfRZSW7abD2ttkWP3SSQvnyA8FSVqNTEcYFgJS2UaFcxupHiYkro49S8yGasTvXEYBVPamhGW6cFJodrTHy" with pytest.raises(ValueError, match="not a private key: "): bip32.xpub_from_xprv(xpub) xpub_dict = bip32.deserialize(xpub) xpub_dict = bip32.deserialize(xpub_dict) xpub_dict["chain_code"] = (xpub_dict["chain_code"])[:-1] with pytest.raises(ValueError, match="invalid chain code length: "): xpub_dict = bip32.deserialize(xpub_dict) xpub_dict = bip32.deserialize(xpub) xpub_dict[ "chain_code"] = "length is 32 but not a chaincode" # type: ignore with pytest.raises(ValueError, match="invalid chain code"): xpub_dict = bip32.deserialize(xpub_dict) seed = "5b56c417303faa3fcba7e57400e120a0" with pytest.raises(ValueError, match="unknown private key version: "): version = b"\x04\x88\xAD\xE5" bip32.rootxprv_from_seed(seed, version) with pytest.raises(ValueError, match="too many bits for seed: "): bip32.rootxprv_from_seed(seed * 5) with pytest.raises(ValueError, match="too few bits for seed: "): bip32.rootxprv_from_seed(seed[:-2])
def test_p2pkh_from_pubkey(self): # https://en.bitcoin.it/wiki/Technical_background_of_version_1_Bitcoin_addresses pub = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" addr = p2pkh(pub) self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs') _, h160, _, _ = h160_from_b58address(addr) self.assertEqual(h160, hash160(pub)) uncompr_pub = bytes_from_point( point_from_octets(pub), compressed=False) addr = p2pkh(uncompr_pub, compressed=False) self.assertEqual(addr, b'16UwLL9Risc3QfPqBUvKofHmBQ7wMtjvM') _, h160, _, _ = h160_from_b58address(addr) self.assertEqual(h160, hash160(uncompr_pub)) # trailing/leading spaces in string pub = ' 02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352' addr = p2pkh(pub) self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs') _, h160, _, _ = h160_from_b58address(addr) self.assertEqual(h160, hash160(pub)) pub = '02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352 ' addr = p2pkh(pub) self.assertEqual(addr, b'1PMycacnJaSqwwJqjawXBErnLsZ7RkXUAs')
def test_one_prvkey_multiple_addresses(self): msg = "Paolo is afraid of ephemeral random numbers" # Compressed WIF wif = b'Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C' pubkey, network = pubkey_info_from_prvkey(wif) address1 = p2pkh(pubkey, network) address2 = p2wpkh_p2sh(pubkey, network) address3 = p2wpkh(pubkey, network) # sign with no address (or compressed P2PKH) sig1 = bms.sign(msg, wif) # True for Bitcoin Core self.assertTrue(bms.verify(msg, address1, sig1)) # True for Electrum p2wpkh_p2sh self.assertTrue(bms.verify(msg, address2, sig1)) # True for Electrum p2wpkh self.assertTrue(bms.verify(msg, address3, sig1)) # sign with p2wpkh_p2sh address (BIP137) sig2 = bms.sign(msg, wif, address2) # False for Bitcoin Core self.assertFalse(bms.verify(msg, address1, sig2)) # True for BIP137 p2wpkh_p2sh self.assertTrue(bms.verify(msg, address2, sig2)) # False for BIP137 p2wpkh self.assertFalse(bms.verify(msg, address3, sig2)) # sign with p2wpkh address (BIP137) sig3 = bms.sign(msg, wif, address3) # False for Bitcoin Core self.assertFalse(bms.verify(msg, address1, sig3)) # False for BIP137 p2wpkh_p2sh self.assertFalse(bms.verify(msg, address2, sig3)) # True for BIP137 p2wpkh self.assertTrue(bms.verify(msg, address3, sig3)) # uncompressed WIF / P2PKH address q, network, _ = prvkey_info_from_prvkey(wif) wif2 = wif_from_prvkey(q, network, False) pubkey, network = pubkey_info_from_prvkey(wif2) address4 = p2pkh(pubkey, network) # sign with uncompressed P2PKH sig4 = bms.sign(msg, wif2, address4) # False for Bitcoin Core compressed p2pkh self.assertFalse(bms.verify(msg, address1, sig4)) # False for BIP137 p2wpkh_p2sh self.assertFalse(bms.verify(msg, address2, sig4)) # False for BIP137 p2wpkh self.assertFalse(bms.verify(msg, address3, sig4)) # True for Bitcoin Core uncompressed p2pkh self.assertTrue(bms.verify(msg, address4, sig4)) self.assertRaises(ValueError, bms.sign, msg, wif2, address1) self.assertRaises(ValueError, bms.sign, msg, wif, address4)
def test_slip32_test_vector(self): """SLIP32 test vector https://github.com/satoshilabs/slips/blob/master/slip-0132.md """ mnemonic = "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about" kpath = "./0/0" test_vectors = [ [ MAIN_xprv, "m / 44h / 0h / 0h", b'xprv9xpXFhFpqdQK3TmytPBqXtGSwS3DLjojFhTGht8gwAAii8py5X6pxeBnQ6ehJiyJ6nDjWGJfZ95WxByFXVkDxHXrqu53WCRGypk2ttuqncb', b'xpub6BosfCnifzxcFwrSzQiqu2DBVTshkCXacvNsWGYJVVhhawA7d4R5WSWGFNbi8Aw6ZRc1brxMyWMzG3DSSSSoekkudhUd9yLb6qx39T9nMdj', b'1LqBGSKuX5yYUonjxT5qGfpUsXKYYWeabA' ], [ MAIN_yprv, "m / 49h / 0h / 0h", b'yprvAHwhK6RbpuS3dgCYHM5jc2ZvEKd7Bi61u9FVhYMpgMSuZS613T1xxQeKTffhrHY79hZ5PsskBjcc6C2V7DrnsMsNaGDaWev3GLRQRgV7hxF', b'ypub6Ww3ibxVfGzLrAH1PNcjyAWenMTbbAosGNB6VvmSEgytSER9azLDWCxoJwW7Ke7icmizBMXrzBx9979FfaHxHcrArf3zbeJJJUZPf663zsP', b'37VucYSaXLCAsxYyAPfbSi9eh4iEcbShgf' ], [ MAIN_zprv, "m / 84h / 0h / 0h", b'zprvAdG4iTXWBoARxkkzNpNh8r6Qag3irQB8PzEMkAFeTRXxHpbF9z4QgEvBRmfvqWvGp42t42nvgGpNgYSJA9iefm1yYNZKEm7z6qUWCroSQnE', b'zpub6rFR7y4Q2AijBEqTUquhVz398htDFrtymD9xYYfG1m4wAcvPhXNfE3EfH1r1ADqtfSdVCToUG868RvUUkgDKf31mGDtKsAYz2oz2AGutZYs', b'bc1qcr8te4kr609gcawutmrza0j4xv80jy8z306fyu' ], ] for v in test_vectors: rxprv = bip32.rootxprv_from_bip39mnemonic(mnemonic, '', v[0]) mxprv = bip32.derive(rxprv, v[1]) self.assertEqual(v[2], mxprv) mxpub = bip32.xpub_from_xprv(mxprv) self.assertEqual(v[3], mxpub) xpub = bip32.derive(mxpub, kpath) address = slip32.address_from_xpub(xpub) self.assertEqual(v[4], address) address = slip32.address_from_xkey(xpub) self.assertEqual(v[4], address) xprv = bip32.derive(mxprv, kpath) address = slip32.address_from_xkey(xprv) self.assertEqual(v[4], address) if v[0] == MAIN_xprv: address = base58address.p2pkh(xpub) self.assertEqual(v[4], address) address = base58address.p2pkh(xprv) self.assertEqual(v[4], address) elif v[0] == MAIN_yprv: address = base58address.p2wpkh_p2sh(xpub) self.assertEqual(v[4], address) address = base58address.p2wpkh_p2sh(xprv) self.assertEqual(v[4], address) elif v[0] == MAIN_zprv: address = bech32address.p2wpkh(xpub) self.assertEqual(v[4], address) address = bech32address.p2wpkh(xprv) self.assertEqual(v[4], address)
def test_exceptions() -> None: pubkey = "02 50863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352" payload = b"\xf5" + hash160(pubkey) invalid_address = b58encode(payload) with pytest.raises(ValueError, match="invalid base58 address prefix: "): h160_from_b58address(invalid_address) with pytest.raises(ValueError, match="not a private or public key: "): p2pkh(pubkey + "00")
def test_p2pkh(self): script_type = 'p2pkh' # self-consistency pubkey = "04cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaff7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4" payload = hash160(pubkey) script = encode(['OP_DUP', 'OP_HASH160', payload, 'OP_EQUALVERIFY', 'OP_CHECKSIG']) # straight to the scriptPubKey scriptPubKey = p2pkh(pubkey) self.assertEqual(scriptPubKey.hex(), script.hex()) # to the scriptPubKey in two steps (through payload) scriptPubKey = scriptPubKey_from_payload(script_type, payload) self.assertEqual(scriptPubKey.hex(), script.hex()) # back from the scriptPubKey to the payload script_type2, payload2, m2 = payload_from_scriptPubKey(scriptPubKey) self.assertEqual(script_type, script_type2) self.assertEqual(0, m2) self.assertEqual(payload.hex(), payload2.hex()) script_type2, payload2, m2 = payload_from_scriptPubKey(script) self.assertEqual(script_type, script_type2) self.assertEqual(0, m2) self.assertEqual(payload.hex(), payload2.hex()) # data -> payload is not invertible (hash functions) # address network = 'mainnet' address = base58address.p2pkh(pubkey, network) address2 = address_from_scriptPubKey(scriptPubKey, network) self.assertEqual(address, address2) prefix = p2pkh_prefix_from_network(network) address2 = b58address_from_h160(prefix, payload) self.assertEqual(address, address2) scriptPubKey2, network2 = scriptPubKey_from_address(address) self.assertEqual(scriptPubKey2, scriptPubKey) self.assertEqual(network2, network) # documented test case: https://learnmeabitcoin.com/guide/p2pkh payload = "12ab8dc588ca9d5787dde7eb29569da63c3a238c" script = "76a91412ab8dc588ca9d5787dde7eb29569da63c3a238c88ac" scriptPubKey = scriptPubKey_from_payload(script_type, payload) self.assertEqual(scriptPubKey.hex(), script) network = 'mainnet' address = b"12higDjoCCNXSA95xZMWUdPvXNmkAduhWv" address2 = address_from_scriptPubKey(scriptPubKey, network) self.assertEqual(address, address2) scriptPubKey2, network2 = scriptPubKey_from_address(address) self.assertEqual(scriptPubKey2, scriptPubKey) self.assertEqual(network2, network) # Invalid size: 11 bytes instead of 20 self.assertRaises( ValueError, scriptPubKey_from_payload, "00" * 11, 'p2pkh')
def test_msgsign_p2pkh(self): msg = "test message" # sigs are taken from (Electrum and) Bitcoin Core q = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" # uncompressed wif1u = wif_from_prvkey(q, "mainnet", False) self.assertEqual( wif1u, b"5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw") pubkey1u, _ = pubkeyinfo_from_prvkey(wif1u) add1u = base58address.p2pkh(pubkey1u) self.assertEqual(add1u, b"1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD") sig1u = bms.sign(msg, wif1u) self.assertTrue(bms.verify(msg, add1u, sig1u)) self.assertEqual(sig1u[0], 27) exp_sig1u = ("G/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpST" "hNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=").encode() self.assertEqual(bms.serialize(*sig1u), exp_sig1u) # compressed wif1c = wif_from_prvkey(q, "mainnet", True) self.assertEqual( wif1c, b"L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk") pubkey1c, _ = pubkeyinfo_from_prvkey(wif1c) add1c = base58address.p2pkh(pubkey1c) self.assertEqual(add1c, b"14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY") sig1c = bms.sign(msg, wif1c) self.assertTrue(bms.verify(msg, add1c, sig1c)) self.assertEqual(sig1c[0], 31) exp_sig1c = ("H/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpST" "hNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=").encode() self.assertEqual(bms.serialize(*sig1c), exp_sig1c) self.assertFalse(bms.verify(msg, add1c, sig1u)) self.assertFalse(bms.verify(msg, add1u, sig1c)) rf, r, s, = sig1c sig1c_malleated_rf = bms.serialize(rf + 1, r, s) self.assertFalse(bms.verify(msg, add1c, sig1c_malleated_rf)) sig1c_malleated_s = bms.serialize(rf, r, ec.n - s) self.assertFalse(bms.verify(msg, add1c, sig1c_malleated_s)) sig1c_malleated_rf_s = bms.serialize(rf + 1, r, ec.n - s) self.assertTrue(bms.verify(msg, add1c, sig1c_malleated_rf_s))
def test_msgsign_p2pkh(self): msg = 'test message' # sigs are taken from (Electrum and) Bitcoin Core # first private key q1 = 91634880152443617534842621287039938041581081254914058002978601050179556493499 # uncompressed wif1u = wif_from_prvkey(q1, 'mainnet', False) self.assertEqual( wif1u, b'5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw') pubkey1u, _ = pubkey_info_from_prvkey(wif1u) add1u = base58address.p2pkh(pubkey1u) self.assertEqual(add1u, b'1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD') sig1u = bms.sign(msg, wif1u) self.assertTrue(bms.verify(msg, add1u, sig1u)) self.assertEqual(sig1u[0], 27) exp_sig1u = b'G/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=' self.assertEqual(bms.serialize(*sig1u), exp_sig1u) # compressed wif1c = wif_from_prvkey(q1, 'mainnet', True) self.assertEqual( wif1c, b'L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk') pubkey1c, _ = pubkey_info_from_prvkey(wif1c) add1c = base58address.p2pkh(pubkey1c) self.assertEqual(add1c, b'14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY') sig1c = bms.sign(msg, wif1c) self.assertTrue(bms.verify(msg, add1c, sig1c)) self.assertEqual(sig1c[0], 31) exp_sig1c = b'H/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=' self.assertEqual(bms.serialize(*sig1c), exp_sig1c) self.assertFalse(bms.verify(msg, add1c, sig1u)) self.assertFalse(bms.verify(msg, add1u, sig1c)) rf, r, s, = sig1c sig1c_malleated_rf = bms.serialize(rf + 1, r, s) self.assertFalse(bms.verify(msg, add1c, sig1c_malleated_rf)) sig1c_malleated_s = bms.serialize(rf, r, ec.n - s) self.assertFalse(bms.verify(msg, add1c, sig1c_malleated_s)) sig1c_malleated_rf_s = bms.serialize(rf + 1, r, ec.n - s) self.assertTrue(bms.verify(msg, add1c, sig1c_malleated_rf_s))
def test_testnet(self): # bitcoin core derivation style rootxprv = "tprv8ZgxMBicQKsPe3g3HwF9xxTLiyc5tNyEtjhBBAk29YA3MTQUqULrmg7aj9qTKNfieuu2HryQ6tGVHse9x7ANFGs3f4HgypMc5nSSoxwf7TK" # m / 0h / 0h / 51h addr1 = b"mfXYCCsvWPgeCv8ZYGqcubpNLYy5nYHbbj" indexes = [0x80000000, 0x80000000, 0x80000000 + 51] addr = p2pkh(xpub_from_xprv(derive(rootxprv, indexes))) self.assertEqual(addr, addr1) path = "m/0h/0h/51h" addr = p2pkh(xpub_from_xprv(derive(rootxprv, path))) self.assertEqual(addr, addr1) # m / 0h / 1h / 150h addr2 = b"mfaUnRFxVvf55uD1P3zWXpprN1EJcKcGrb" indexes = [0x80000000, 0x80000000 + 1, 0x80000000 + 150] addr = p2pkh(xpub_from_xprv(derive(rootxprv, indexes))) self.assertEqual(addr, addr2) path = "m/0h/1h/150h" addr = p2pkh(xpub_from_xprv(derive(rootxprv, path))) self.assertEqual(addr, addr2)
def test_derive_from_account() -> None: seed = "bfc4cbaad0ff131aa97fa30a48d09ae7df914bcc083af1e07793cd0a7c61a03f65d622848209ad3366a419f4718a80ec9037df107d8d12c19b83202de00a40ad" rmxprv = bip32.rootxprv_from_seed(seed) der_path = "m / 44 h / 0 h" mxpub = bip32.xpub_from_xprv(bip32.derive(rmxprv, der_path)) test_vectors = [ [0, 0], [0, 1], [0, 2], [1, 0], [1, 1], [1, 2], ] for branch, index in test_vectors: full_path = der_path + f"/{branch}/{index}" addr = p2pkh(bip32.derive(rmxprv, full_path)).decode() assert addr == p2pkh(bip32.derive_from_account(mxpub, branch, index)).decode() errmsg = "invalid private derivation at branch level" with pytest.raises(ValueError, match=errmsg): bip32.derive_from_account(mxpub, 0x80000000, 0, True) errmsg = "invalid branch: " with pytest.raises(ValueError, match=errmsg): bip32.derive_from_account(mxpub, 2, 0) errmsg = "invalid private derivation at address index level" with pytest.raises(ValueError, match=errmsg): bip32.derive_from_account(mxpub, 0, 0x80000000) der_path = "m / 44 h / 0" mxpub = bip32.xpub_from_xprv(bip32.derive(rmxprv, der_path)) errmsg = "public derivation at account level" with pytest.raises(UserWarning, match=errmsg): bip32.derive_from_account(mxpub, 0, 0)
def test_address_from_wif() -> None: q = 0x19E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 test_cases: List[Tuple[bool, str, str, str]] = [ ( False, "mainnet", "5J1geo9kcAUSM6GJJmhYRX1eZEjvos9nFyWwPstVziTVueRJYvW", "1LPM8SZ4RQDMZymUmVSiSSvrDfj1UZY9ig", ), ( True, "mainnet", "Kx621phdUCp6sgEXPSHwhDTrmHeUVrMkm6T95ycJyjyxbDXkr162", "1HJC7kFvXHepkSzdc8RX6khQKkAyntdfkB", ), ( False, "testnet", "91nKEXyJCPYaK9maw7bTJ7ZcCu6dy2gybvNtUWF1LTCYggzhZgy", "mzuJRVe3ERecM6F6V4R6GN9B5fKiPC9HxF", ), ( True, "testnet", "cNT1UjhUuGWN37hnmr754XxvPWwtAJTSq8bcCQ4pUrdxqxbA1iU1", "mwp9QoLuLK65XZUFKhPtvfujBjmgkZnmPx", ), ] for compressed, network, wif, address in test_cases: assert wif.encode() == wif_from_prvkey(q, network, compressed) assert prvkeyinfo_from_prvkey(wif) == (q, network, compressed) b58 = p2pkh(wif) assert b58 == address.encode() _, payload, net, is_script = h160_from_b58address(b58) assert net == network assert not is_script if compressed: b32 = p2wpkh(wif) assert (payload, network, is_script) == witness_from_b32address(b32)[1:] b = p2wpkh_p2sh(wif) _, payload2, net, is_script = h160_from_b58address(b) assert is_script assert (hash160(b"\x00\x14" + payload), network) == (payload2, net) else: err_msg = "not a private or compressed public key: " with pytest.raises(ValueError, match=err_msg): p2wpkh(wif) # type: ignore with pytest.raises(ValueError, match=err_msg): p2wpkh_p2sh(wif) # type: ignore
def test_derive() -> None: test_vectors = { "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS": [ ["m / 0 h / 0 h / 463 h", "1DyfBWxhVLmrJ7keyiHeMbt7N3UdeGU4G5"], ["M / 0H / 0h // 267' / ", "11x2mn59Qy43DjisZWQGRResjyQmgthki"], ], "tprv8ZgxMBicQKsPe3g3HwF9xxTLiyc5tNyEtjhBBAk29YA3MTQUqULrmg7aj9qTKNfieuu2HryQ6tGVHse9x7ANFGs3f4HgypMc5nSSoxwf7TK": [ ["m / 0 h / 0 h / 51 h", "mfXYCCsvWPgeCv8ZYGqcubpNLYy5nYHbbj"], ["m / 0 h / 1 h / 150 h", "mfaUnRFxVvf55uD1P3zWXpprN1EJcKcGrb"], ], } for rootxprv, value in test_vectors.items(): for der_path, address in value: assert address == p2pkh(bip32.derive(rootxprv, der_path)).decode() b_indexes, _ = bip32._indexes_from_path(der_path) indexes = [int.from_bytes(b_index, "big") for b_index in b_indexes] assert address == p2pkh(bip32.derive(rootxprv, indexes)).decode()
def test_msgsign_p2pkh() -> None: msg = "test message" # sigs are taken from (Electrum and) Bitcoin Core q = "ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb" # uncompressed wif1u = wif_from_prvkey(q, "mainnet", False) assert wif1u == b"5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw" add1u = base58address.p2pkh(wif1u) assert add1u == b"1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD" sig1u = bms.sign(msg, wif1u) assert bms.verify(msg, add1u, sig1u) assert sig1u[0] == 27 exp_sig1u = "G/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=" assert bms.encode(*sig1u) == exp_sig1u.encode() # compressed wif1c = wif_from_prvkey(q, "mainnet", True) assert wif1c == b"L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk" add1c = base58address.p2pkh(wif1c) assert add1c == b"14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY" sig1c = bms.sign(msg, wif1c) assert bms.verify(msg, add1c, sig1c) assert sig1c[0] == 31 exp_sig1c = "H/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=" assert bms.encode(*sig1c) == exp_sig1c.encode() assert not bms.verify(msg, add1c, sig1u) assert not bms.verify(msg, add1u, sig1c) rf, r, s = sig1c sig1c_malleated_rf = bms.encode(rf + 1, r, s) assert not bms.verify(msg, add1c, sig1c_malleated_rf) sig1c_malleated_s = bms.encode(rf, r, ec.n - s) assert not bms.verify(msg, add1c, sig1c_malleated_s) sig1c_malleated_rf_s = bms.encode(rf + 1, r, ec.n - s) assert bms.verify(msg, add1c, sig1c_malleated_rf_s)
def test_p2pkh_from_wif(self): seed = b"00" * 32 # better be random rxprv = bip32.rootxprv_from_seed(seed) path = "m/0h/0h/12" xprv = bip32.derive(rxprv, path) wif = wif_from_prvkey(xprv) self.assertEqual( wif, b'KyLk7s6Z1FtgYEVp3bPckPVnXvLUWNCcVL6wNt3gaT96EmzTKZwP') pubkey, _ = pubkey_info_from_prvkey(wif) address = p2pkh(pubkey) xpub = bip32.xpub_from_xprv(xprv) address2 = slip32.address_from_xpub(xpub) self.assertEqual(address, address2) self.assertRaises(ValueError, wif_from_prvkey, xpub)
def test_p2pkh_from_wif() -> None: seed = b"\x00" * 32 # better be a documented test case rxprv = bip32.rootxprv_from_seed(seed) path = "m/0h/0h/12" xprv = bip32.derive(rxprv, path) wif = wif_from_prvkey(xprv) assert wif == b"L2L1dqRmkmVtwStNf5wg8nnGaRn3buoQr721XShM4VwDbTcn9bpm" pubkey, _ = pubkeyinfo_from_prvkey(wif) address = p2pkh(pubkey) xpub = bip32.xpub_from_xprv(xprv) address2 = slip132.address_from_xpub(xpub) assert address == address2 err_msg = "not a private key: " with pytest.raises(ValueError, match=err_msg): wif_from_prvkey(xpub)
def test_p2pkh_from_wif() -> None: seed = b"00" * 32 # better be a documented test case rxprv = bip32.rootxprv_from_seed(seed) path = "m/0h/0h/12" xprv = bip32.derive(rxprv, path) wif = wif_from_prvkey(xprv) assert wif == b"KyLk7s6Z1FtgYEVp3bPckPVnXvLUWNCcVL6wNt3gaT96EmzTKZwP" pubkey, _ = pubkeyinfo_from_prvkey(wif) address = p2pkh(pubkey) xpub = bip32.xpub_from_xprv(xprv) address2 = slip132.address_from_xpub(xpub) assert address == address2 err_msg = "not a private key: " with pytest.raises(ValueError, match=err_msg): wif_from_prvkey(xpub)
def test_p2pkh() -> None: # self-consistency pubkey = ( "04 " "cc71eb30d653c0c3163990c47b976f3fb3f37cccdcbedb169a1dfef58bbfbfaf" "f7d8a473e7e2e6d317b87bafe8bde97e3cf8f065dec022b51d11fcdd0d348ac4") payload = hash160(pubkey) script_pubkey = script.serialize( ["OP_DUP", "OP_HASH160", payload, "OP_EQUALVERIFY", "OP_CHECKSIG"]) assert script_pubkey == p2pkh(pubkey) # to the script_pubkey in two steps (through payload) script_type = "p2pkh" assert script_pubkey == script_pubkey_from_payload(script_type, payload) # back from the script_pubkey to the payload assert (script_type, payload, 0) == payload_from_script_pubkey(script_pubkey) # base58 address network = "mainnet" address = base58address.p2pkh(pubkey, network) assert address == address_from_script_pubkey(script_pubkey, network) prefix = NETWORKS[network].p2pkh assert address == b58address_from_h160(prefix, payload, network) # back from the address to the script_pubkey assert (script_pubkey, network) == script_pubkey_from_address(address) # documented test case: https://learnmeabitcoin.com/guide/p2pkh payload = "12ab8dc588ca9d5787dde7eb29569da63c3a238c" script_pubkey = "76a914" + payload + "88ac" assert script_pubkey == script_pubkey_from_payload(script_type, payload).hex() address = b"12higDjoCCNXSA95xZMWUdPvXNmkAduhWv" assert address == address_from_script_pubkey(script_pubkey, network) assert (bytes.fromhex(script_pubkey), network) == script_pubkey_from_address(address) # invalid size: 11 bytes instead of 20 err_msg = "invalid size: " with pytest.raises(BTClibValueError, match=err_msg): script_pubkey_from_payload(script_type, "00" * 11)
def test_segwit(self): msg = "test" wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK" pubkey, _ = pubkeyinfo_from_prvkey(wif) p2pkh = base58address.p2pkh(pubkey) p2wpkh = bech32address.p2wpkh(pubkey) p2wpkh_p2sh = base58address.p2wpkh_p2sh(pubkey) # p2pkh base58 address (Core, Electrum, BIP137) exp_sig = ("IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IO" "I0dpo3uGAEpCz9eepXLrA5kF35MXuU=").encode() self.assertTrue(bms.verify(msg, p2pkh, exp_sig)) sig = bms.sign(msg, wif) # no address: p2pkh assumed self.assertTrue(bms.verify(msg, p2pkh, sig)) self.assertEqual(bms.serialize(*sig), exp_sig) # p2wpkh-p2sh base58 address (Electrum) self.assertTrue(bms.verify(msg, p2wpkh_p2sh, sig)) # p2wpkh bech32 address (Electrum) self.assertTrue(bms.verify(msg, p2wpkh, sig)) # p2wpkh-p2sh base58 address (BIP137) # different first letter in sig because of different rf exp_sig = ("JBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IO" "I0dpo3uGAEpCz9eepXLrA5kF35MXuU=").encode() self.assertTrue(bms.verify(msg, p2wpkh_p2sh, exp_sig)) sig = bms.sign(msg, wif, p2wpkh_p2sh) self.assertTrue(bms.verify(msg, p2wpkh_p2sh, sig)) self.assertEqual(bms.serialize(*sig), exp_sig) # p2wpkh bech32 address (BIP137) # different first letter in sig because of different rf exp_sig = ("KBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IO" "I0dpo3uGAEpCz9eepXLrA5kF35MXuU=").encode() self.assertTrue(bms.verify(msg, p2wpkh, exp_sig)) sig = bms.sign(msg, wif, p2wpkh) self.assertTrue(bms.verify(msg, p2wpkh, sig)) self.assertEqual(bms.serialize(*sig), exp_sig)
def test_segwit() -> None: msg = "test" wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK" b58_p2pkh = base58address.p2pkh(wif) b58_p2wpkh = bech32address.p2wpkh(wif) b58_p2wpkh_p2sh = base58address.p2wpkh_p2sh(wif) # p2pkh base58 address (Core, Electrum, BIP137) exp_sig = "IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" assert bms.verify(msg, b58_p2pkh, exp_sig) sig = bms.sign(msg, wif) # no address: p2pkh assumed assert bms.verify(msg, b58_p2pkh, sig) assert bms.encode(*sig) == exp_sig.encode() # p2wpkh-p2sh base58 address (Electrum) assert bms.verify(msg, b58_p2wpkh_p2sh, sig) # p2wpkh bech32 address (Electrum) assert bms.verify(msg, b58_p2wpkh, sig) # p2wpkh-p2sh base58 address (BIP137) # different first letter in sig because of different rf exp_sig = "JBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" assert bms.verify(msg, b58_p2wpkh_p2sh, exp_sig) sig = bms.sign(msg, wif, b58_p2wpkh_p2sh) assert bms.verify(msg, b58_p2wpkh_p2sh, sig) assert bms.encode(*sig) == exp_sig.encode() # p2wpkh bech32 address (BIP137) # different first letter in sig because of different rf exp_sig = "KBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" assert bms.verify(msg, b58_p2wpkh, exp_sig) sig = bms.sign(msg, wif, b58_p2wpkh) assert bms.verify(msg, b58_p2wpkh, sig) assert bms.encode(*sig) == exp_sig.encode()
def test_mainnet(self): # bitcoin core derivation style rootxprv = "xprv9s21ZrQH143K2ZP8tyNiUtgoezZosUkw9hhir2JFzDhcUWKz8qFYk3cxdgSFoCMzt8E2Ubi1nXw71TLhwgCfzqFHfM5Snv4zboSebePRmLS" # m / 0h / 0h / 463h addr1 = b"1DyfBWxhVLmrJ7keyiHeMbt7N3UdeGU4G5" indexes = [0x80000000, 0x80000000, 0x800001CF] addr = p2pkh(xpub_from_xprv(derive(rootxprv, indexes))) self.assertEqual(addr, addr1) path = "m / 0h / 0h / 463h" addr = p2pkh(xpub_from_xprv(derive(rootxprv, path))) self.assertEqual(addr, addr1) # m / 0h / 0h / 267h addr2 = b"11x2mn59Qy43DjisZWQGRResjyQmgthki" indexes = [0x80000000, 0x80000000, 0x8000010B] addr = p2pkh(xpub_from_xprv(derive(rootxprv, indexes))) self.assertEqual(addr, addr2) path = "M / 0H / 0h // 267' / " addr = p2pkh(xpub_from_xprv(derive(rootxprv, path))) self.assertEqual(addr, addr2) seed = "bfc4cbaad0ff131aa97fa30a48d09ae7df914bcc083af1e07793cd0a7c61a03f65d622848209ad3366a419f4718a80ec9037df107d8d12c19b83202de00a40ad" xprv = rootxprv_from_seed(seed) xpub = "xpub661MyMwAqRbcFMYjmw8C6dJV97a4oLss6hb3v9wTQn2X48msQB61RCaLGtNhzgPCWPaJu7SvuB9EBSFCL43kTaFJC3owdaMka85uS154cEh" self.assertEqual(xpub_from_xprv(xprv).decode(), xpub) ind = "./0/0" addr = p2pkh(xpub_from_xprv(derive(xprv, ind))) self.assertEqual(addr, b"1FcfDbWwGs1PmyhMVpCAhoTfMnmSuptH6g") ind = "./0/1" addr = p2pkh(xpub_from_xprv(derive(xprv, ind))) self.assertEqual(addr, b"1K5GjYkZnPFvMDTGaQHTrVnd8wjmrtfR5x") ind = "./0/2" addr = p2pkh(xpub_from_xprv(derive(xprv, ind))) self.assertEqual(addr, b"1PQYX2uN7NYFd7Hq22ECMzfDcKhtrHmkfi") ind = "./1/0" addr = p2pkh(xpub_from_xprv(derive(xprv, ind))) self.assertEqual(addr, b"1BvSYpojWoWUeaMLnzbkK55v42DbizCoyq") ind = "./1/1" addr = p2pkh(xpub_from_xprv(derive(xprv, ind))) self.assertEqual(addr, b"1NXB59hF4QzYpFrB7o6usLBjbk2D3ZqxAL") ind = "./1/2" addr = p2pkh(xpub_from_xprv(derive(xprv, ind))) self.assertEqual(addr, b"16NLYkKtvYhW1Jp86tbocku3gxWcvitY1w") # version/key mismatch in extended parent key temp = b58decode(rootxprv) bad_xprv = b58encode(temp[0:45] + b"\x01" + temp[46:], 78) self.assertRaises(ValueError, derive, bad_xprv, 1) # derive(bad_xprv, 1) # version/key mismatch in extended parent key xpub = xpub_from_xprv(rootxprv) temp = b58decode(xpub) bad_xpub = b58encode(temp[0:45] + b"\x00" + temp[46:], 78) self.assertRaises(ValueError, derive, bad_xpub, 1) # derive(bad_xpub, 1) # no private/hardened derivation from pubkey self.assertRaises(ValueError, derive, xpub, 0x80000000)
def test_mainnet_versions(self): # data cross-checked with Electrum and # https://jlopp.github.io/xpub-converter/ # 128 bits raw_entr = bytes.fromhex("6" * 32) # 12 words mnemonic = bip39.mnemonic_from_entropy(raw_entr, "en") seed = bip39.seed_from_mnemonic(mnemonic, "") # p2pkh BIP44 # m / 44h / coin_typeh / accounth / change / address_index path = "m/44h/0h/0h" version = NETWORKS["mainnet"]["bip32_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "xpub6C3uWu5Go5q62JzJpbjyCLYRGLYvexFeiepZTsYZ6SRexARkNfjG7GKtQVuGR3KHsyKsAwv7Hz3iNucPp6pfHiLvBczyK1j5CtBtpHB3NKx" self.assertEqual(xpub.decode(), exp) # first addresses xpub_ext = derive(xpub, "./0/0") # external address = p2pkh(xpub_ext) exp_address = b"1DDKKVHoFWGfctyEEJvrusqq6ipEaieGCq" self.assertEqual(address, exp_address) xpub_int = derive(xpub, "./1/0") # internal address = p2pkh(xpub_int) exp_address = b"1FhKoffreKHzhtBMVW9NSsg3ZF148JPGoR" self.assertEqual(address, exp_address) # legacy segwit (p2wsh-p2sh) # m / 49h / coin_typeh / accounth / change / address_index path = "m/49h/0h/0h" version = NETWORKS["mainnet"]["slip32_p2wsh_p2sh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "ypub6YBGdYufCVeoPVmNXfdrWhaBCXsQoLKNetNmD9bPTrKmnKVmiyU8f1uJqwGdmBb8kbAZpHoYfXQTLbWpkXc4skQDAreeCUXdbX9k8vtiHsN" self.assertEqual(xpub.decode(), exp) # first addresses xpub_ext = derive(xpub, "./0/0") # external address = p2wpkh_p2sh(xpub_ext) exp_address = b"3FmNAiTCWe5kPMgc4dtSgEdY8VuaCiJEH8" self.assertEqual(address, exp_address) xpub_int = derive(xpub, "./1/0") # internal address = p2wpkh_p2sh(xpub_int) exp_address = b"34FLgkoRYX5Q5fqiZCZDwsK5GpXxmFuLJN" self.assertEqual(address, exp_address) # legacy segwit (p2wpkh-p2sh) # m / 49h / coin_typeh / accounth / change / address_index path = "m/49h/0h/0h" version = NETWORKS["mainnet"]["slip32_p2wpkh_p2sh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "Ypub6j5Mkne6mTDAp4vkUL6qLmuyvKug1gzxyA2S8QrvqdABQW4gVNrQk8mEeeE7Kcp2z4EYgsofYjnxTm8b3km22EWt1Km3bszdVFRcipc6rXu" self.assertEqual(xpub.decode(), exp) # native segwit (p2wpkh) # m / 84h / coin_typeh / accounth / change / address_index path = "m/84h/0h/0h" version = NETWORKS["mainnet"]["slip32_p2wpkh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "zpub6qg3Uc1BAQkQvcBUYMmZHSzbsshSon3FvJ8yvH3ZZMjFNvJkwSji8UUwghiF3wvpvSvcNWVP8kfUhc2V2RwGp6pTC3ouj6njj956f26TniN" self.assertEqual(xpub.decode(), exp) # first addresses xpub_ext = derive(xpub, "./0/0") # external address = p2wpkh(xpub_ext) exp_address = b"bc1q0hy024867ednvuhy9en4dggflt5w9unw4ztl5a" self.assertEqual(address, exp_address) xpub_int = derive(xpub, "./1/0") # internal address = p2wpkh(xpub_int) exp_address = b"bc1qy4x03jyl88h2zeg7l287xhv2xrwk4c3ztfpjd2" self.assertEqual(address, exp_address) # native segwit (p2wsh) # m / 84h / coin_typeh / accounth / change / address_index path = "m/84h/0h/0h" version = NETWORKS["mainnet"]["slip32_p2wsh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "Zpub72a8bqjcjNJnMBLrV2EY7XLQbfji28irEZneqYK6w8Zf16sfhr7zDbLsVQficP9j9uzbF6VW1y3ypmeFKf6Dxaw82WvK8WFjcsLyEvMNZjF" self.assertEqual(xpub.decode(), exp)
def test_testnet_versions(self): # data cross-checked with Electrum and # https://jlopp.github.io/xpub-converter/ # 128 bits raw_entr = bytes.fromhex("6" * 32) # 12 words mnemonic = bip39.mnemonic_from_entropy(raw_entr, "en") seed = bip39.seed_from_mnemonic(mnemonic, "") # p2pkh BIP44 # m / 44h / coin_typeh / accounth / change / address_index path = "m/44h/1h/0h" version = NETWORKS["testnet"]["bip32_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "tpubDChqWo2Xi2wNsxyJBE8ipcTJHLKWcqeeNUKBVTpUCNPZkHzHTm3qKAeHqgCou1t8PAY5ZnJ9QDa6zXSZxmjDnhiBpgZ7f6Yv88wEm5HXVbm" self.assertEqual(xpub.decode(), exp) # first addresses xpub_ext = derive(xpub, "./0/0") # external address = p2pkh(xpub_ext) exp_address = b"moutHSzeFWViMNEcvBxKzNCMj2kca8MvE1" self.assertEqual(address, exp_address) xpub_int = derive(xpub, "./1/0") # internal address = p2pkh(xpub_int) exp_address = b"myWcXdNais9ExumnGKnNoJwoihQKfNPG9i" self.assertEqual(address, exp_address) # legacy segwit (p2wsh-p2sh) # m / 49h / coin_typeh / accounth / change / address_index path = "m/49h/1h/0h" version = NETWORKS["testnet"]["slip32_p2wsh_p2sh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "upub5Dj8j7YrwodV68mt58QmNpSzjqjso2WMXEpLGLSvskKccGuXhCh3dTedkzVLAePA617UyXAg2vdswJXTYjU4qjMJaHU79GJVVJCAiy9ezZ2" self.assertEqual(xpub.decode(), exp) # first addresses xpub_ext = derive(xpub, "./0/0") # external address = p2wpkh_p2sh(xpub_ext) exp_address = b"2Mw8tQ6uT6mHhybarVhjgomUhHQJTeV9A2c" self.assertEqual(address, exp_address) xpub_int = derive(xpub, "./1/0") # internal address = p2wpkh_p2sh(xpub_int) exp_address = b"2N872CRJ3E1CzWjfixXr3aeC3hkF5Cz4kWb" self.assertEqual(address, exp_address) # legacy segwit (p2wsh-p2sh) # m / 49h / coin_typeh / accounth / change / address_index path = "m/49h/1h/0h" version = NETWORKS["testnet"]["slip32_p2wpkh_p2sh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "Upub5QdDrMHJWmBrWhwG1nskCtnoTdn91PBwqWU1BbiUFXA2ETUSTc5KiaWZZhSoj5c4KUBTr7Anv92P4U9Dqxd1zDTyQkaWYfmVP2U3Js1W5cG" self.assertEqual(xpub.decode(), exp) # native segwit (p2wpkh) # m / 84h / coin_typeh / accounth / change / address_index path = "m/84h/1h/0h" version = NETWORKS["testnet"]["slip32_p2wpkh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "vpub5ZhJmduYY7M5J2qCJgSW7hunX6zJrr5WuNg2kKt321HseZEYxqJc6Zso47aNXQw3Wf3sA8kppbfsxnLheUNXcL3xhzeBHLNp8fTVBN6DnJF" self.assertEqual(xpub.decode(), exp) # first addresses xpub_ext = derive(xpub, "./0/0") # external # explicit network is required to discriminate from testnet address = p2wpkh(xpub_ext, "regtest") exp_address = b"bcrt1qv8lcnmj09rpdqwgl025h2deygur64z4hqf7me5" self.assertEqual(address, exp_address) xpub_int = derive(xpub, "./1/0") # internal # explicit network is required to discriminate from testnet address = p2wpkh(xpub_int, "regtest") exp_address = b"bcrt1qqhxvky4y6qkwpvdzqjkdafmj20vs5trmt6y8w5" self.assertEqual(address, exp_address) # native segwit (p2wsh) # m / 84h / coin_typeh / accounth / change / address_index path = "m/84h/1h/0h" version = NETWORKS["testnet"]["slip32_p2wsh_prv"] rootprv = rootxprv_from_seed(seed, version) xprv = derive(rootprv, path) xpub = xpub_from_xprv(xprv) exp = "Vpub5kbPtsdz74uSibzaFLuUwnFbEu2a5Cm7DeKhfb9aPn8HGjoTjEgtBgjirpXr5r9wk87r2ikwhp4P5wxTwhXUkpAdYTkagjqp2PjMmGPBESU" self.assertEqual(xpub.decode(), exp)
def test_address_from_wif(self): # uncompressed mainnet wif1 = "5J1geo9kcAUSM6GJJmhYRX1eZEjvos9nFyWwPstVziTVueRJYvW" pubkey, network = pubkey_info_from_prvkey(wif1) b58 = p2pkh(pubkey) self.assertEqual(b58, b'1LPM8SZ4RQDMZymUmVSiSSvrDfj1UZY9ig') self.assertRaises(ValueError, p2wpkh, pubkey) self.assertRaises(ValueError, p2wpkh_p2sh, pubkey) # compressed mainnet wif2 = "Kx621phdUCp6sgEXPSHwhDTrmHeUVrMkm6T95ycJyjyxbDXkr162" pubkey, network = pubkey_info_from_prvkey(wif2) b58 = p2pkh(pubkey) self.assertEqual(b58, b'1HJC7kFvXHepkSzdc8RX6khQKkAyntdfkB') b32 = p2wpkh(pubkey) self.assertEqual(h160_from_b58address( b58)[1:], witness_from_b32address(b32)[1:]) h160 = h160_from_b58address(b58)[1] b = p2wpkh_p2sh(pubkey) self.assertEqual(hash160(b'\x00\x14' + h160), h160_from_b58address(b)[1]) self.assertEqual(prvkey_info_from_prvkey( wif1)[0], prvkey_info_from_prvkey(wif2)[0]) # uncompressed testnet wif1 = "91gGn1HgSap6CbU12F6z3pJri26xzp7Ay1VW6NHCoEayNXwRpu2" pubkey, network = pubkey_info_from_prvkey(wif1) b58 = p2pkh(pubkey, network, None) self.assertEqual(b58, b'mvgbzkCSgKbYgaeG38auUzR7otscEGi8U7') self.assertRaises(ValueError, p2wpkh, pubkey) self.assertRaises(ValueError, p2wpkh_p2sh, pubkey) # compressed testnet wif2 = "cMzLdeGd5vEqxB8B6VFQoRopQ3sLAAvEzDAoQgvX54xwofSWj1fx" pubkey, network = pubkey_info_from_prvkey(wif2) b58 = p2pkh(pubkey, network, None) self.assertEqual(b58, b'n1KSZGmQgB8iSZqv6UVhGkCGUbEdw8Lm3Q') b32 = p2wpkh(pubkey, network) self.assertEqual(h160_from_b58address( b58)[1:], witness_from_b32address(b32)[1:]) h160 = h160_from_b58address(b58)[1] b = p2wpkh_p2sh(pubkey, network) self.assertEqual(hash160(b'\x00\x14' + h160), h160_from_b58address(b)[1]) self.assertEqual(prvkey_info_from_prvkey( wif1)[0], prvkey_info_from_prvkey(wif2)[0]) # uncompressed mainnet, trailing/leading spaces in string wif1 = " 5J1geo9kcAUSM6GJJmhYRX1eZEjvos9nFyWwPstVziTVueRJYvW" pubkey, network = pubkey_info_from_prvkey(wif1) b58 = p2pkh(pubkey) self.assertEqual(b58, b'1LPM8SZ4RQDMZymUmVSiSSvrDfj1UZY9ig') self.assertRaises(ValueError, p2wpkh, pubkey) self.assertRaises(ValueError, p2wpkh_p2sh, pubkey) # compressed mainnet, trailing/leading spaces in string wif2 = "Kx621phdUCp6sgEXPSHwhDTrmHeUVrMkm6T95ycJyjyxbDXkr162 " pubkey, network = pubkey_info_from_prvkey(wif2) b58 = p2pkh(pubkey) self.assertEqual(b58, b'1HJC7kFvXHepkSzdc8RX6khQKkAyntdfkB') b32 = p2wpkh(pubkey) self.assertEqual(h160_from_b58address( b58)[1:], witness_from_b32address(b32)[1:]) h160 = h160_from_b58address(b58)[1] b = p2wpkh_p2sh(pubkey) self.assertEqual(hash160(b'\x00\x14' + h160), h160_from_b58address(b)[1])
def test_exceptions(self): msg = 'test' wif = 'KwELaABegYxcKApCb3kJR9ymecfZZskL9BzVUkQhsqFiUKftb4tu' pubkey, _ = pubkey_info_from_prvkey(wif) address = base58address.p2pkh(pubkey) exp_sig = b'IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc=' self.assertTrue(bms.verify(msg, address, exp_sig)) # Invalid recovery flag: 26 _, r, s = bms.deserialize(exp_sig) self.assertRaises(ValueError, bms.serialize, 26, r, s) #bms.serialize(26, r, s) # short exp_sig exp_sig = b'IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLoVc=' self.assertRaises(ValueError, bms._verify, msg, address, exp_sig) self.assertFalse(bms.verify(msg, address, exp_sig)) # Invalid recovery flag: 26 exp_sig = b'GpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU=' self.assertRaises(ValueError, bms._verify, msg, address, exp_sig) self.assertFalse(bms.verify(msg, address, exp_sig)) #bms._verify(msg, address, exp_sig) # Invalid recovery flag: 66 exp_sig = b'QpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU=' self.assertRaises(ValueError, bms._verify, msg, address, exp_sig) self.assertFalse(bms.verify(msg, address, exp_sig)) #bms._verify(msg, address, exp_sig) # Pubkey mismatch: compressed wif, uncompressed address wif = 'Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ' address = '19f7adDYqhHSJm2v7igFWZAqxXHj1vUa3T' self.assertRaises(ValueError, bms.sign, msg, wif, address) #bms.sign(msg, wif, address) # Pubkey mismatch: uncompressed wif, compressed address wif = '5JDopdKaxz5bXVYXcAnfno6oeSL8dpipxtU1AhfKe3Z58X48srn' address = '1DAag8qiPLHh6hMFVu9qJQm9ro1HtwuyK5' self.assertRaises(ValueError, bms.sign, msg, wif, address) #bms.sign(msg, wif, address) msg = 'test' wif = 'L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK' pubkey, _ = pubkey_info_from_prvkey(wif) p2pkh = base58address.p2pkh(pubkey) p2wpkh = bech32address.p2wpkh(pubkey) p2wpkh_p2sh = base58address.p2wpkh_p2sh(pubkey) wif = 'Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ' # Mismatch between p2pkh address and key pair self.assertRaises(ValueError, bms.sign, msg, wif, p2pkh) # bms.sign(msg, wif, p2pkh) # Mismatch between p2wpkh address and key pair self.assertRaises(ValueError, bms.sign, msg, wif, p2wpkh) # bms.sign(msg, wif, p2wpkh) # Mismatch between p2wpkh_p2sh address and key pair self.assertRaises(ValueError, bms.sign, msg, wif, p2wpkh_p2sh) # bms.sign(msg, wif, p2wpkh_p2sh) # Invalid recovery flag (39) for base58 address exp_sig = b'IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc=' _, r, s = bms.deserialize(exp_sig) sig = bms.serialize(39, r, s) self.assertRaises(ValueError, bms._verify, msg, p2pkh, sig) #bms._verify(msg, p2pkh, sig) # Invalid recovery flag (35) for bech32 address exp_sig = b'IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=' _, r, s = bms.deserialize(exp_sig) sig = bms.serialize(35, r, s) self.assertRaises(ValueError, bms._verify, msg, p2wpkh, sig)
def test_one_prvkey_multiple_addresses() -> None: msg = "Paolo is afraid of ephemeral random numbers" # Compressed WIF wif = b"Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C" addr_p2pkh_compressed = p2pkh(wif) addr_p2wpkh_p2sh = p2wpkh_p2sh(wif) addr_p2wpkh = p2wpkh(wif) # sign with no address sig1 = bms.sign(msg, wif) # True for Bitcoin Core bms.assert_as_valid(msg, addr_p2pkh_compressed, sig1) assert bms.verify(msg, addr_p2pkh_compressed, sig1) # True for Electrum p2wpkh_p2sh bms.assert_as_valid(msg, addr_p2wpkh_p2sh, sig1) assert bms.verify(msg, addr_p2wpkh_p2sh, sig1) # True for Electrum p2wpkh bms.assert_as_valid(msg, addr_p2wpkh, sig1) assert bms.verify(msg, addr_p2wpkh, sig1) # sign with no p2pkh address sig1 = bms.sign(msg, wif, addr_p2pkh_compressed) # True for Bitcoin Core bms.assert_as_valid(msg, addr_p2pkh_compressed, sig1) assert bms.verify(msg, addr_p2pkh_compressed, sig1) # True for Electrum p2wpkh_p2sh bms.assert_as_valid(msg, addr_p2wpkh_p2sh, sig1) assert bms.verify(msg, addr_p2wpkh_p2sh, sig1) # True for Electrum p2wpkh bms.assert_as_valid(msg, addr_p2wpkh, sig1) assert bms.verify(msg, addr_p2wpkh, sig1) err_msg = "invalid recovery flag: " # sign with p2wpkh_p2sh address (BIP137) sig2 = bms.sign(msg, wif, addr_p2wpkh_p2sh) # False for Bitcoin Core with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, addr_p2pkh_compressed, sig2) assert not bms.verify(msg, addr_p2pkh_compressed, sig2) # True for BIP137 p2wpkh_p2sh bms.assert_as_valid(msg, addr_p2wpkh_p2sh, sig2) assert bms.verify(msg, addr_p2wpkh_p2sh, sig2) # False for BIP137 p2wpkh with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, addr_p2wpkh, sig2) assert not bms.verify(msg, addr_p2wpkh, sig2) # sign with p2wpkh address (BIP137) sig3 = bms.sign(msg, wif, addr_p2wpkh) # False for Bitcoin Core with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, addr_p2pkh_compressed, sig3) assert not bms.verify(msg, addr_p2pkh_compressed, sig3) # False for BIP137 p2wpkh_p2sh with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, addr_p2wpkh_p2sh, sig3) assert not bms.verify(msg, addr_p2wpkh_p2sh, sig3) # True for BIP137 p2wpkh bms.assert_as_valid(msg, addr_p2wpkh, sig3) assert bms.verify(msg, addr_p2wpkh, sig3) # uncompressed WIF / p2pkh address q, network, _ = prvkeyinfo_from_prvkey(wif) wif2 = wif_from_prvkey(q, network, False) addr_p2pkh_uncompressed = p2pkh(wif2) # sign with uncompressed p2pkh sig4 = bms.sign(msg, wif2, addr_p2pkh_uncompressed) # False for Bitcoin Core compressed p2pkh with pytest.raises(BTClibValueError, match="wrong p2pkh address: "): bms.assert_as_valid(msg, addr_p2pkh_compressed, sig4) assert not bms.verify(msg, addr_p2pkh_compressed, sig4) # False for BIP137 p2wpkh_p2sh # FIXME: puzzling error message # it should have been "wrong p2wpkh-p2sh address: " with pytest.raises(BTClibValueError, match="wrong p2pkh address: "): bms.assert_as_valid(msg, addr_p2wpkh_p2sh, sig4) assert not bms.verify(msg, addr_p2wpkh_p2sh, sig4) # False for BIP137 p2wpkh with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, addr_p2wpkh, sig4) assert not bms.verify(msg, addr_p2wpkh, sig4) # True for Bitcoin Core uncompressed p2pkh bms.assert_as_valid(msg, addr_p2pkh_uncompressed, sig4) assert bms.verify(msg, addr_p2pkh_uncompressed, sig4) # unrelated different wif wif3 = b"KwdMAjGmerYanjeui5SHS7JkmpZvVipYvB2LJGU1ZxJwYvP98617" addr_p2pkh_compressed = p2pkh(wif3) addr_p2wpkh_p2sh = p2wpkh_p2sh(wif3) addr_p2wpkh = p2wpkh(wif3) # False for Bitcoin Core compressed p2pkh with pytest.raises(BTClibValueError, match="wrong p2pkh address: "): bms.assert_as_valid(msg, addr_p2pkh_compressed, sig1) assert not bms.verify(msg, addr_p2pkh_compressed, sig1) # False for BIP137 p2wpkh_p2sh with pytest.raises(BTClibValueError, match="wrong p2wpkh-p2sh address: "): bms.assert_as_valid(msg, addr_p2wpkh_p2sh, sig1) assert not bms.verify(msg, addr_p2wpkh_p2sh, sig1) # False for BIP137 p2wpkh with pytest.raises(BTClibValueError, match="wrong p2wpkh address: "): bms.assert_as_valid(msg, addr_p2wpkh, sig1) assert not bms.verify(msg, addr_p2wpkh, sig1) # FIXME: puzzling error message err_msg = "not a private or compressed public key for mainnet: " with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif2, addr_p2pkh_compressed) err_msg = "mismatch between private key and address" with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, addr_p2pkh_uncompressed)
from btclib.base58address import p2pkh, p2wpkh_p2sh from btclib.base58wif import wif_from_prvkey from btclib.bech32address import p2wpkh from btclib.bms import encode, sign, verify from btclib.to_prvkey import prvkeyinfo_from_prvkey from btclib.to_pubkey import pubkeyinfo_from_prvkey msg = "Paolo is afraid of ephemeral random numbers" print("\n0. Message:", msg) wif = b"Kx45GeUBSMPReYQwgXiKhG9FzNXrnCeutJp4yjTd5kKxCitadm3C" print("1. Compressed WIF:", wif.decode()) pubkey, network = pubkeyinfo_from_prvkey(wif) print("2. Addresses") address1 = p2pkh(pubkey) print(" p2pkh:", address1.decode()) address2 = p2wpkh_p2sh(pubkey) print("p2wpkh_p2sh:", address2.decode()) address3 = p2wpkh(pubkey) print(" p2wpkh:", address3.decode()) print("\n3. Sign message with no address (or with compressed p2pkh address):") sig1 = sign(msg, wif) print(f"rf1: {sig1[0]}") print(f" r1: {hex(sig1[1]).upper()}") print(f" s1: {hex(sig1[2]).upper()}") bsmsig1 = encode(*sig1) print("4. Serialized signature:") print(bsmsig1.decode())
def test_exceptions() -> None: msg = "test" wif = "KwELaABegYxcKApCb3kJR9ymecfZZskL9BzVUkQhsqFiUKftb4tu" address = base58address.p2pkh(wif) exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc=" assert bms.verify(msg, address, exp_sig) _, r, s = bms.decode(exp_sig) err_msg = "invalid recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.encode(26, r, s) exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLoVc=" err_msg = "wrong signature length: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, address, exp_sig) assert not bms.verify(msg, address, exp_sig) exp_sig = "GpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU=" err_msg = "invalid recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, address, exp_sig) assert not bms.verify(msg, address, exp_sig) exp_sig = "QpNLHqEKSzwXV+KwwBfQthQ848mn5qSkmGDXpqshDuPYJELOnSuRYGQQgBR4PpI+w2tJdD4v+hxElvAaUSqv2eU=" with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, address, exp_sig) assert not bms.verify(msg, address, exp_sig) # compressed wif, uncompressed address wif = "Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ" address = b"19f7adDYqhHSJm2v7igFWZAqxXHj1vUa3T" err_msg = "mismatch between private key and address" with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, address) # uncompressed wif, compressed address wif = "5JDopdKaxz5bXVYXcAnfno6oeSL8dpipxtU1AhfKe3Z58X48srn" address = b"1DAag8qiPLHh6hMFVu9qJQm9ro1HtwuyK5" err_msg = "not a private or compressed public key for mainnet: " # FIXME puzzling error message with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, address) msg = "test" wif = "L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK" b58_p2pkh = base58address.p2pkh(wif) b58_p2wpkh = bech32address.p2wpkh(wif) b58_p2wpkh_p2sh = base58address.p2wpkh_p2sh(wif) wif = "Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ" err_msg = "mismatch between private key and address" with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b58_p2pkh) with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b58_p2wpkh) with pytest.raises(BTClibValueError, match=err_msg): bms.sign(msg, wif, b58_p2wpkh_p2sh) # Invalid recovery flag (39) for base58 address exp_sig = "IHdKsFF1bUrapA8GMoQUbgI+Ad0ZXyX1c/yAZHmJn5hSNBi7J+TrI1615FG3g9JEOPGVvcfDWIFWrg2exLNtoVc=" _, r, s = bms.decode(exp_sig) sig = bms.encode(39, r, s) err_msg = "invalid recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2pkh, sig) # Invalid recovery flag (35) for bech32 address exp_sig = "IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=" _, r, s = bms.decode(exp_sig) sig = bms.encode(35, r, s) err_msg = "invalid recovery flag: " with pytest.raises(BTClibValueError, match=err_msg): bms.assert_as_valid(msg, b58_p2wpkh, sig)
def test_ledger() -> None: """Hybrid ECDSA Bitcoin message signature generated by Ledger""" mnemonic = ( "barely sun snack this snack relief pipe attack disease boss enlist lawsuit" ) # non-standard leading 31 in DER serialization derivation_path = "m/1" msg = b"\xfb\xa3\x1f\x8cd\x85\xe29#K\xb3{\xfd\xa7<?\x95oL\xee\x19\xb2'oh\xa7]\xd9A\xfeU\xd8" dersig_hex_str = "3144022012ec0c174936c2a46dc657252340b2e6e6dd8c31dd059b6f9f33a90c21af2fba022030e6305b3ccf88009d419bf7651afcfcc0a30898b93ae9de9aa6ac03cf8ec56b" # pubkey derivation rprv = bip32.mxprv_from_bip39_mnemonic(mnemonic) xprv = bip32.derive(rprv, derivation_path) # the actual message being signed magic_msg = bms._magic_message(msg) # save key_id and patch dersig dersig = bytes.fromhex(dersig_hex_str) key_id = dersig[0] dersig = b"\x30" + dersig[1:] r, s = dsa.deserialize(dersig) # ECDSA signature verification of the patched dersig dsa.assert_as_valid(magic_msg, xprv, dersig, ec, hf) assert dsa.verify(magic_msg, xprv, dersig) # compressed address addr = base58address.p2pkh(xprv) # equivalent Bitcoin Message Signature (non-serialized) rec_flag = 27 + 4 + (key_id & 0x01) btcmsgsig = (rec_flag, r, s) # Bitcoin Message Signature verification bms.assert_as_valid(msg, addr, btcmsgsig) assert bms.verify(msg, addr, btcmsgsig) assert not bms.verify(magic_msg, addr, btcmsgsig) bms.sign(msg, xprv) # standard leading 30 in DER serialization derivation_path = "m/0/0" msg_str = "hello world" dersig_hex_str = "3045022100967dac3262b4686e89638c8219c5761017f05cd87a855edf034f4a3ec6b59d3d0220108a4ef9682b71a45979d8c75c393382d9ccb8eb561d73b8c5fc0b87a47e7d27" # pubkey derivation rprv = bip32.mxprv_from_bip39_mnemonic(mnemonic) xprv = bip32.derive(rprv, derivation_path) # the actual message being signed magic_msg = bms._magic_message(msg_str) # save key_id and patch dersig dersig = bytes.fromhex(dersig_hex_str) key_id = dersig[0] dersig = b"\x30" + dersig[1:] r, s = dsa.deserialize(dersig) # ECDSA signature verification of the patched dersig dsa.assert_as_valid(magic_msg, xprv, dersig, ec, hf) assert dsa.verify(magic_msg, xprv, dersig) # compressed address addr = base58address.p2pkh(xprv) # equivalent Bitcoin Message Signature (non-serialized) rec_flag = 27 + 4 + (key_id & 0x01) btcmsgsig = (rec_flag, r, s) # Bitcoin Message Signature verification bms.assert_as_valid(msg_str, addr, btcmsgsig) assert bms.verify(msg_str, addr, btcmsgsig) assert not bms.verify(magic_msg, addr, btcmsgsig)