def test_der_size() -> None: sig8 = 1, 1 sig72 = ec.n - 1, ec.n - 1 sig71 = 2**255 - 1, ec.n - 1 sig70 = 2**255 - 1, 2**255 - 1 sig70b = 2**255 - 1, 2**248 - 1 sig69 = 2**255 - 1, 2**247 - 1 sig68 = 2**247 - 1, 2**247 - 1 sigs = [sig8, sig72, sig71, sig70, sig70b, sig69, sig68] lenghts = [8, 72, 71, 70, 70, 69, 68] # not including SIGHASHES for length, sig in zip(lenghts, sigs): for sighash in SIGHASHES: der_sig = serialize(*sig, sighash) r, s, sighash2 = deserialize(der_sig) assert sig == (r, s) assert sighash == sighash2 assert len(der_sig) == length + 1 # with the last one only... assert (r, s, sighash) == deserialize((r, s, sighash))
def test_pubkey_recovery(self): ec = secp112r2 q = 0x10 Q = mult(q, ec.G, ec) msg = 'Satoshi Nakamoto' k = sighash = None sig = dsa.sign(msg, q, k, ec) self.assertTrue(dsa.verify(msg, Q, sig, ec)) dersig = der.serialize(*sig, sighash, ec) self.assertTrue(dsa.verify(msg, Q, dersig, ec)) r, s, _ = der.deserialize(dersig) self.assertEqual((r, s), sig) keys = dsa.recover_pubkeys(msg, dersig, ec) self.assertEqual(len(keys), 4) self.assertIn(Q, keys) for Q in keys: self.assertTrue(dsa.verify(msg, Q, sig, ec))
def test_der_deserialize() -> None: err_msg = "non-hexadecimal number found " with pytest.raises(ValueError, match=err_msg): deserialize("not a sig") sig = 2**255 - 1, 2**247 - 1 for sighash in SIGHASHES: der_sig = serialize(*sig, sighash) r_size = der_sig[3] bad_der_sig = b"\x00" * 74 err_msg = "invalid DER size: " with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = b"\x31" + der_sig[1:] err_msg = "DER type must be 0x30 " with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig[:1] + b"\x41" + der_sig[2:] err_msg = "Declared size incompatible with actual size: " with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig + b"\x01" err_msg = "Declared size incompatible with actual size: " with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig[:-1] + b"\x00" err_msg = "invalid sighash: 0x" with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) # r and s scalars for offset in (4, 6 + r_size): bad_der_sig = der_sig[:offset - 2] + b"\x00" + der_sig[offset - 1:] err_msg = "scalar must be an integer" with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig[:offset - 1] + b"\x00" + der_sig[offset:] err_msg = "scalar has size zero" with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig[:offset - 1] + b"\x80" + der_sig[offset:] err_msg = "Size of scalar is too large: " with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig[:offset] + b"\x80" + der_sig[offset + 1:] err_msg = "Negative number not allowed for scalar" with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) bad_der_sig = der_sig[:offset] + b"\x00\x7f" + der_sig[offset + 2:] err_msg = "invalid null bytes at the start of scalar" with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig) data_size = der_sig[1] malleated_size = (data_size + 1).to_bytes(1, byteorder="big") bad_der_sig = der_sig[:1] + malleated_size + der_sig[2:] + b"\x01" err_msg = "Too big DER size for " with pytest.raises(ValueError, match=err_msg): deserialize(bad_der_sig)
) == "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))
def test_der(self): sighash_all = b'\x01' sig73 = ec.n - 1, ec.n - 1 sig72 = 2**255 - 1, ec.n - 1 sig71 = 2**255 - 1, 2**255 - 1 sig71b = 2**255 - 1, 2**248 - 1 sig70 = 2**255 - 1, 2**247 - 1 sig69 = 2**247 - 1, 2**247 - 1 sig9 = 1, 1 sigs = [sig73, sig72, sig71, sig71b, sig70, sig69] lenghts = [73, 72, 71, 71, 70, 69, 9] for lenght, sig in zip(lenghts, sigs): dersig = serialize(*sig, sighash_all) r, s, sighash_all2 = deserialize(dersig) self.assertEqual(sig, (r, s)) self.assertEqual(sighash_all, sighash_all2) self.assertEqual(len(dersig), lenght) # without sighash r, s, sighash_all2 = deserialize(dersig[:-1]) self.assertEqual(sig, (r, s)) self.assertIsNone(sighash_all2) # with the last one # DER signature size should be in [9, 73] dersig2 = dersig + b'\x00' * 70 self.assertRaises(ValueError, deserialize, dersig2) # DER signature must be of type 0x30 (compound) dersig2 = b'\x00' + dersig[1:] self.assertRaises(ValueError, deserialize, dersig2) # Declared signature size does not match with size dersig2 = dersig[:1] + b'\x00' + dersig[2:] self.assertRaises(ValueError, deserialize, dersig2) Rsize = dersig[3] # Zero-size integers are not allowed for r dersig2 = dersig[:3] + b'\x00' + dersig[4:] self.assertRaises(ValueError, deserialize, dersig2) # Length of the s scalar must be inside the signature dersig2 = dersig[:3] + b'\x80' + dersig[4:] self.assertRaises(ValueError, deserialize, dersig2) # Zero-size integers are not allowed for s dersig2 = dersig[:Rsize+5] + b'\x00' + dersig[Rsize+6:] self.assertRaises(ValueError, deserialize, dersig2) # Signature size does not match with scalars dersig2 = dersig[:Rsize+5] + b'\x4f' + dersig[Rsize+6:] self.assertRaises(ValueError, deserialize, dersig2) # r scalar must be an integer dersig2 = dersig[:2] + b'\x00' + dersig[3:] self.assertRaises(ValueError, deserialize, dersig2) # Negative numbers are not allowed for r dersig2 = dersig[:4] + b'\x80' + dersig[5:] self.assertRaises(ValueError, deserialize, dersig2) # Invalid null bytes at the start of r dersig2 = dersig[:4] + b'\x00\x00' + dersig[6:] self.assertRaises(ValueError, deserialize, dersig2) # s scalar must be an integer dersig2 = dersig[:Rsize+4] + b'\x00' + dersig[Rsize+5:] self.assertRaises(ValueError, deserialize, dersig2) # Negative numbers are not allowed for s dersig2 = dersig[:Rsize+6] + b'\x80' + dersig[Rsize+7:] self.assertRaises(ValueError, deserialize, dersig2) # Invalid null bytes at the start of s dersig2 = dersig[:Rsize+6] + b'\x00\x00' + dersig[Rsize+8:] self.assertRaises(ValueError, deserialize, dersig2) # sighash size > 1 self.assertRaises(ValueError, serialize, *sig, sighash_all + b'\x01') # negative signature scalar sig2 = -1, sig[1] self.assertRaises(ValueError, serialize, *sig2, sighash_all) # Invalid sighash type b'\x00' self.assertRaises(ValueError, deserialize, dersig[:-1] + b'\x00')