def test_msgsign_p2pkh_2(self): msg = 'test message' # sigs are taken from (Electrum and) Bitcoin Core # second private key wif = 'Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ' # compressed address = '1DAag8qiPLHh6hMFVu9qJQm9ro1HtwuyK5' exp_sig = b'IFqUo4/sxBEFkfK8mZeeN56V13BqOc0D90oPBChF3gTqMXtNSCTN79UxC33kZ8Mi0cHy4zYCnQfCxTyLpMVXKeA=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif, address) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) sig = btcmsg.sign(msg.encode(), wif) self.assertTrue(btcmsg.verify(msg.encode(), address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) wif = '5JDopdKaxz5bXVYXcAnfno6oeSL8dpipxtU1AhfKe3Z58X48srn' # uncompressed address = '19f7adDYqhHSJm2v7igFWZAqxXHj1vUa3T' exp_sig = b'HFqUo4/sxBEFkfK8mZeeN56V13BqOc0D90oPBChF3gTqMXtNSCTN79UxC33kZ8Mi0cHy4zYCnQfCxTyLpMVXKeA=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif, address) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) sig = btcmsg.sign(msg.encode(), wif) self.assertTrue(btcmsg.verify(msg.encode(), address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig)
def test_sign_strippable_message(self): wif = 'Ky1XfDK2v6wHPazA6ECaD8UctEoShXdchgABjpU9GWGZDxVRDBMJ' address = '1DAag8qiPLHh6hMFVu9qJQm9ro1HtwuyK5' msg = '' exp_sig = b'IFh0InGTy8lLCs03yoUIpJU6MUbi0La/4abhVxyKcCsoUiF3RM7lg51rCqyoOZ8Yt43h8LZrmj7nwwO3HIfesiw=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # Bitcoin Core exp_sig (Electrum does strip leading/trailing spaces) msg = ' ' exp_sig = b'IEveV6CMmOk5lFP+oDbw8cir/OkhJn4S767wt+YwhzHnEYcFOb/uC6rrVmTtG3M43mzfObA0Nn1n9CRcv5IGyak=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # Bitcoin Core exp_sig (Electrum does strip leading/trailing spaces) msg = ' ' exp_sig = b'H/QjF1V4fVI8IHX8ko0SIypmb0yxfaZLF0o56Cif9z8CX24n4petTxolH59pYVMvbTKQkGKpznSiPiQVn83eJF0=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) msg = 'test' exp_sig = b'IJUtN/2LZjh1Vx8Ekj9opnIKA6ohKhWB95PLT/3EFgLnOu9hTuYX4+tJJ60ZyddFMd6dgAYx15oP+jLw2NzgNUo=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # Bitcoin Core exp_sig (Electrum does strip leading/trailing spaces) msg = ' test ' exp_sig = b'IA59z13/HBhvMMJtNwT6K7vJByE40lQUdqEMYhX2tnZSD+IGQIoBGE+1IYGCHCyqHvTvyGeqJTUx5ywb4StuX0s=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # Bitcoin Core exp_sig (Electrum does strip leading/trailing spaces) msg = 'test ' exp_sig = b'IPp9l2w0LVYB4FYKBahs+k1/Oa08j+NTuzriDpPWnWQmfU0+UsJNLIPI8Q/gekrWPv6sDeYsFSG9VybUKDPGMuo=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # Bitcoin Core exp_sig (Electrum does strip leading/trailing spaces) msg = ' test' exp_sig = b'H1nGwD/kcMSmsYU6qihV2l2+Pa+7SPP9zyViZ59VER+QL9cJsIAtu1CuxfYDAVt3kgr4t3a/Es3PV82M6z0eQAo=' self.assertTrue(btcmsg.verify(msg, address, exp_sig)) sig = btcmsg.sign(msg, wif) self.assertTrue(btcmsg.verify(msg, address, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig)
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, False) self.assertEqual( wif1u, b'5KMWWy2d3Mjc8LojNoj8Lcz9B1aWu8bRofUgGwQk959Dw5h2iyw') add1u = p2pkh_from_wif(wif1u) self.assertEqual(add1u, b'1HUBHMij46Hae75JPdWjeZ5Q7KaL7EFRSD') sig1u = btcmsg.sign(msg, wif1u) self.assertTrue(btcmsg.verify(msg, add1u, sig1u)) self.assertEqual(sig1u[0], 27) exp_sig1u = b'G/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=' self.assertEqual(btcmsg.serialize(*sig1u), exp_sig1u) # compressed wif1c = wif_from_prvkey(q1, True) self.assertEqual( wif1c, b'L41XHGJA5QX43QRG3FEwPbqD5BYvy6WxUxqAMM9oQdHJ5FcRHcGk') add1c = p2pkh_from_wif(wif1c) self.assertEqual(add1c, b'14dD6ygPi5WXdwwBTt1FBZK3aD8uDem1FY') sig1c = btcmsg.sign(msg, wif1c) self.assertTrue(btcmsg.verify(msg, add1c, sig1c)) self.assertEqual(sig1c[0], 31) exp_sig1c = b'H/iew/NhHV9V9MdUEn/LFOftaTy1ivGPKPKyMlr8OSokNC755fAxpSThNRivwTNsyY9vPUDTRYBPc2cmGd5d4y4=' self.assertEqual(btcmsg.serialize(*sig1c), exp_sig1c) self.assertFalse(btcmsg.verify(msg, add1c, sig1u)) self.assertFalse(btcmsg.verify(msg, add1u, sig1c)) rf, r, s, = sig1c sig1c_malleated_rf = btcmsg.serialize(rf + 1, r, s) self.assertFalse(btcmsg.verify(msg, add1c, sig1c_malleated_rf)) sig1c_malleated_s = btcmsg.serialize(rf, r, ec.n - s) self.assertFalse(btcmsg.verify(msg, add1c, sig1c_malleated_s)) sig1c_malleated_rf_s = btcmsg.serialize(rf + 1, r, ec.n - s) self.assertTrue(btcmsg.verify(msg, add1c, sig1c_malleated_rf_s))
def test_segwit(self): msg = 'test' wif = 'L4xAvhKR35zFcamyHME2ZHfhw5DEyeJvEMovQHQ7DttPTM8NLWCK' p2pkh = p2pkh_from_wif(wif) p2wpkh = p2wpkh_from_wif(wif) p2wpkh_p2sh = p2wpkh_p2sh_from_wif(wif) # p2pkh base58 address (Core, Electrum, BIP137) exp_sig = b'IBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=' self.assertTrue(btcmsg.verify(msg, p2pkh, exp_sig)) sig = btcmsg.sign(msg, wif) # no address: p2pkh assumed self.assertTrue(btcmsg.verify(msg, p2pkh, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # p2wpkh-p2sh base58 address (Electrum) self.assertTrue(btcmsg.verify(msg, p2wpkh_p2sh, sig)) # p2wpkh bech32 address (Electrum) self.assertTrue(btcmsg.verify(msg, p2wpkh, sig)) # p2wpkh-p2sh base58 address (BIP137) # different first letter in sig because of different rf exp_sig = b'JBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=' self.assertTrue(btcmsg.verify(msg, p2wpkh_p2sh, exp_sig)) sig = btcmsg.sign(msg, wif, p2wpkh_p2sh) self.assertTrue(btcmsg.verify(msg, p2wpkh_p2sh, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig) # p2wpkh bech32 address (BIP137) # different first letter in sig because of different rf exp_sig = b'KBFyn+h9m3pWYbB4fBFKlRzBD4eJKojgCIZSNdhLKKHPSV2/WkeV7R7IOI0dpo3uGAEpCz9eepXLrA5kF35MXuU=' self.assertTrue(btcmsg.verify(msg, p2wpkh, exp_sig)) sig = btcmsg.sign(msg, wif, p2wpkh) self.assertTrue(btcmsg.verify(msg, p2wpkh, sig)) self.assertEqual(btcmsg.serialize(*sig), exp_sig)
def test_vector_python_bitcoinlib(self): """Test python-bitcoinlib test vectors https://github.com/petertodd/python-bitcoinlib/blob/master/bitcoin/tests/data/btcmsg.json """ file = "btcmsg.json" filename = path.join(path.dirname(__file__), "data", file) with open(filename, 'r') as f: test_vectors = json.load(f) for vector in test_vectors[:5]: msg = vector['address'] tuplesig = btcmsg.sign(msg, vector['wif']) self.assertTrue(btcmsg.verify(msg, vector['address'], tuplesig)) b64sig = btcmsg.serialize(*tuplesig) self.assertTrue(btcmsg.verify(msg, vector['address'], b64sig)) self.assertTrue( btcmsg.verify(msg, vector['address'], vector['signature'])) # python-bitcoinlib has a signature different from the # one generated by Core/Electrum/btclib (which are identical) self.assertNotEqual(b64sig.decode(), vector['signature']) # python-bitcoinlib does not use RFC6979 deterministic nonce # as proved by different r compared to Core/Electrum/btclib rf, r, s = tuplesig rf0, r0, s0 = btcmsg.deserialize(vector['signature']) self.assertNotEqual(r, r0) # while Core/Electrum/btclib use 'low-s' canonical signature self.assertLess(s, ec.n - s) # this is not true for python-bitcoinlib #self.assertLess(s0, ec.n - s0) #self.assertGreater(s0, ec.n - s0) # just in case you wonder, here's the malleated signature rf += (1 if rf == 31 else -1) tuplesig_malleated = rf, r, ec.n - s self.assertTrue( btcmsg.verify(msg, vector['address'], tuplesig_malleated)) b64sig_malleated = btcmsg.serialize(*tuplesig_malleated) self.assertTrue( btcmsg.verify(msg, vector['address'], b64sig_malleated)) # of course, it is not equal to the python-bitcoinlib one (different r) self.assertNotEqual(b64sig_malleated.decode(), vector['signature'])
) == "04ae3d0d5c669ed364636e79e72abc012a33be63e537babddf56bfd393256acf6dba0fac21da6386513674573a2d7baff4375c9b6d2498383853c52f0565f97f1a" app_wif = base58wif.wif_from_xprv(app_xprv) app_address = slip32.address_from_xpub(app_xpub) assert app_address == b'1J6674MtZBpfHytdNofLUX6sLHAUaG33uK' msg = "hello world" h = sha256(msg.encode()) h256 = h.digest() assert h256.hex().upper( ) == "B94D27B9934D3E08A52E52D7DA7DABFAC484EFE37A5380EE9088F7ACE2EFCDE9", h256.hex( ).upper() # hex-string ledger_sig = bytes.fromhex( "3044022044487c80833b7025739f450751c1d6624118e32e5f922b5a40a407efb48382e202200f2b6e53448f8e219ee1c2f109fa5b0a2b8bae482a4a81cf8c54f8c168260886" ) # from ledger signature style to (base64-encoded) compact signature standard r, s, _ = der.deserialize(ledger_sig) rec_flag = 27 + ledger_sig[0] - 44 print(rec_flag) rec_flag = 27 + 4 + (ledger_sig[0] & 0x01) print(rec_flag) b64sig = btcmsg.serialize(rec_flag + 1, r, s) btcmsg._verify(msg, app_address, b64sig) # from (tuple) compact signature standard to ledger signature style rf, r, s = btcmsg.sign(msg, app_wif, app_address) btcmsg._verify(msg, app_address, (rf + 1, r, s)) ledger_sig_equivalent = der.serialize(r, s, None)
def test_ledger(self): """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 = '3144022012ec0c174936c2a46dc657252340b2e6e6dd8c31dd059b6f9f33a90c21af2fba022030e6305b3ccf88009d419bf7651afcfcc0a30898b93ae9de9aa6ac03cf8ec56b' # pubkey derivation rprv = bip32.rootxprv_from_bip39mnemonic(mnemonic) xprv = bip32.derive(rprv, derivation_path) xpub = bip32.xpub_from_xprv(xprv) # the actual message being signed magic_msg = btcmsg._magic_hash(msg) # save key_id and patch dersig dersig = bytes.fromhex(dersig) key_id = dersig[0] dersig = b'\x30' + dersig[1:] r, s, sighash = der.deserialize(dersig) self.assertIsNone(sighash) # ECDSA signature verification of the patched dersig dsa._verify(magic_msg, xpub, dersig, ec, hf) self.assertTrue(dsa.verify(magic_msg, xpub, dersig)) # compressed address addr = p2pkh(xpub) # equivalent Bitcoin Message Signature (non-serialized) rec_flag = 27 + 4 + (key_id & 0x01) btcmsgsig = (rec_flag, r, s) # Bitcoin Message Signature verification btcmsg._verify(msg, addr, btcmsgsig) self.assertTrue(btcmsg.verify(msg, addr, btcmsgsig)) self.assertFalse(btcmsg.verify(magic_msg, addr, btcmsgsig)) btcmsg.sign(msg, xprv) #### standard leading 30 in DER serialization derivation_path = 'm/0/0' msg = "hello world" dersig = "3045022100967dac3262b4686e89638c8219c5761017f05cd87a855edf034f4a3ec6b59d3d0220108a4ef9682b71a45979d8c75c393382d9ccb8eb561d73b8c5fc0b87a47e7d27" # pubkey derivation rprv = bip32.rootxprv_from_bip39mnemonic(mnemonic) xprv = bip32.derive(rprv, derivation_path) xpub = bip32.xpub_from_xprv(xprv) # the actual message being signed magic_msg = btcmsg._magic_hash(msg) # save key_id and patch dersig dersig = bytes.fromhex(dersig) key_id = dersig[0] dersig = b'\x30' + dersig[1:] r, s, sighash = der.deserialize(dersig) self.assertIsNone(sighash) # ECDSA signature verification of the patched dersig dsa._verify(magic_msg, xpub, dersig, ec, hf) self.assertTrue(dsa.verify(magic_msg, xpub, dersig)) # compressed address addr = p2pkh(xpub) # equivalent Bitcoin Message Signature (non-serialized) rec_flag = 27 + 4 + (key_id & 0x01) btcmsgsig = (rec_flag, r, s) # Bitcoin Message Signature verification btcmsg._verify(msg, addr, btcmsgsig) self.assertTrue(btcmsg.verify(msg, addr, btcmsgsig)) self.assertFalse(btcmsg.verify(magic_msg, addr, btcmsgsig))