def test_crack_prvkey(self): q = 0x6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725DEADBEEF x_Q = mult(q)[0] k = 1010101010101010101 msg1 = "Paolo is afraid of ephemeral random numbers" msg1 = hf(msg1.encode()).digest() sig1 = ssa.sign(msg1, q, k) # print(f'\nmsg1: {msg1.hex().upper()}') # print(f' r1: {hex(sig1[0]).upper()}') # print(f' s1: {hex(sig1[1]).upper()}') msg2 = "and Paolo is right to be afraid" msg2 = hf(msg2.encode()).digest() sig2 = ssa.sign(msg2, q, k) # print(f'\nmsg2: {msg2.hex().upper()}') # print(f' r2: {hex(sig2[0]).upper()}') # print(f' s2: {hex(sig2[1]).upper()}') qc, kc = ssa.crack_prvkey(msg1, sig1, msg2, sig2, x_Q) self.assertIn(q, (qc, ec.n - qc)) self.assertIn(k, (kc, ec.n - kc)) self.assertRaises(ValueError, ssa.crack_prvkey, msg1, sig1, msg2, (16, sig1[1]), x_Q) self.assertRaises(ValueError, ssa.crack_prvkey, msg1, sig1, msg1, sig1, x_Q)
def test_crack_prvkey(): ec = secp256k1 q = 0x19E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 x_Q = mult(q)[0] msg1 = "Paolo is afraid of ephemeral random numbers" msg1 = hf(msg1.encode()).digest() k = ssa.k(msg1, q) sig1 = ssa.sign(msg1, q, k) msg2 = "and Paolo is right to be afraid" msg2 = hf(msg2.encode()).digest() # reuse same k sig2 = ssa.sign(msg2, q, k) qc, kc = ssa.crack_prvkey(msg1, sig1, msg2, sig2, x_Q) assert q in (qc, ec.n - qc) assert k in (kc, ec.n - kc) with pytest.raises(ValueError, match="not the same r in signatures"): ssa.crack_prvkey(msg1, sig1, msg2, (16, sig1[1]), x_Q) with pytest.raises(ValueError, match="identical signatures"): ssa.crack_prvkey(msg1, sig1, msg1, sig1, x_Q)
def test_batch_validation() -> None: ms: List[String] = [] Qs: List[int] = [] sigs: List[ssa.SSASig] = [] err_msg = "no signatures provided" with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_batch_as_valid(ms, Qs, sigs) assert not ssa.batch_verify(ms, Qs, sigs) # valid size for String input to sign, not for Octets input to _sign msg_size = 16 ms.append(secrets.token_bytes(msg_size)) q, Q = ssa.gen_keys() Qs.append(Q) sigs.append(ssa.sign(ms[0], q)) # test with only 1 sig ssa.assert_batch_as_valid(ms, Qs, sigs) assert ssa.batch_verify(ms, Qs, sigs) for _ in range(3): m = secrets.token_bytes(msg_size) ms.append(m) q, Q = ssa.gen_keys() Qs.append(Q) sigs.append(ssa.sign(m, q)) ssa.assert_batch_as_valid(ms, Qs, sigs) assert ssa.batch_verify(ms, Qs, sigs) ms.append(ms[0]) sigs.append(sigs[1]) Qs.append(Qs[0]) err_msg = "signature verification failed" with pytest.raises(BTClibRuntimeError, match=err_msg): ssa.assert_batch_as_valid(ms, Qs, sigs) assert not ssa.batch_verify(ms, Qs, sigs) sigs[-1] = sigs[0] # valid again ms.append(ms[0]) # add extra message err_msg = "mismatch between number of pubkeys " with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_batch_as_valid(ms, Qs, sigs) assert not ssa.batch_verify(ms, Qs, sigs) ms.pop() # valid again sigs.append(sigs[0]) # add extra sig err_msg = "mismatch between number of pubkeys " with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_batch_as_valid(ms, Qs, sigs) assert not ssa.batch_verify(ms, Qs, sigs) sigs.pop() # valid again ms = [reduce_to_hlen(m, hf) for m in ms] ms[0] = ms[0][:-1] err_msg = "invalid size: 31 bytes instead of 32" with pytest.raises(BTClibValueError, match=err_msg): ssa._assert_batch_as_valid(ms, Qs, sigs) assert not ssa._batch_verify(ms, Qs, sigs)
def test_batch_validation(): ec = secp256k1 hsize = hf().digest_size hlen = hsize * 8 ms = [] Qs = [] sigs = [] ms.append(secrets.randbits(hlen).to_bytes(hsize, "big")) q = 1 + secrets.randbelow(ec.n - 1) # bytes version Qs.append(mult(q, ec.G, ec)[0].to_bytes(ec.psize, "big")) sigs.append(ssa.sign(ms[0], q, None, ec, hf)) # test with only 1 sig ssa._batch_verify(ms, Qs, sigs, ec, hf) for _ in range(3): mhd = secrets.randbits(hlen).to_bytes(hsize, "big") ms.append(mhd) q = 1 + secrets.randbelow(ec.n - 1) # Point version Qs.append(mult(q, ec.G, ec)) sigs.append(ssa.sign(mhd, q, None, ec, hf)) ssa._batch_verify(ms, Qs, sigs, ec, hf) assert ssa.batch_verify(ms, Qs, sigs, ec, hf) ms.append(ms[0]) sigs.append(sigs[1]) Qs.append(Qs[0]) assert not ssa.batch_verify(ms, Qs, sigs, ec, hf) err_msg = "signature verification precondition failed" with pytest.raises(ValueError, match=err_msg): ssa._batch_verify(ms, Qs, sigs, ec, hf) sigs[-1] = sigs[0] # valid again ms[-1] = ms[0][:-1] err_msg = "invalid size: 31 bytes instead of 32" with pytest.raises(ValueError, match=err_msg): ssa._batch_verify(ms, Qs, sigs, ec, hf) ms[-1] = ms[0] # valid again ms.append(ms[0]) # add extra message err_msg = "mismatch between number of pubkeys " with pytest.raises(ValueError, match=err_msg): ssa._batch_verify(ms, Qs, sigs, ec, hf) ms.pop() # valid again sigs.append(sigs[0]) # add extra sig err_msg = "mismatch between number of pubkeys " with pytest.raises(ValueError, match=err_msg): ssa._batch_verify(ms, Qs, sigs, ec, hf) sigs.pop() # valid again err_msg = "field prime is not equal to 3 mod 4: " with pytest.raises(ValueError, match=err_msg): ssa._batch_verify(ms, Qs, sigs, secp224k1, hf)
def test_batch_validation(self): hsize = hf().digest_size hlen = hsize * 8 ms = [] Qs = [] sigs = [] ms.append(secrets.randbits(hlen).to_bytes(hsize, 'big')) q = 1 + secrets.randbelow(ec.n - 1) # bytes version Qs.append(mult(q, ec.G, ec)[0].to_bytes(ec.psize, 'big')) sigs.append(ssa.sign(ms[0], q, None, ec, hf)) # test with only 1 sig ssa._batch_verify(ms, Qs, sigs, ec, hf) for _ in range(3): mhd = secrets.randbits(hlen).to_bytes(hsize, 'big') ms.append(mhd) q = 1 + secrets.randbelow(ec.n - 1) # Point version Qs.append(mult(q, ec.G, ec)) sigs.append(ssa.sign(mhd, q, None, ec, hf)) ssa._batch_verify(ms, Qs, sigs, ec, hf) self.assertTrue(ssa.batch_verify(ms, Qs, sigs, ec, hf)) # invalid sig ms.append(ms[0]) sigs.append(sigs[1]) Qs.append(Qs[0]) self.assertFalse(ssa.batch_verify(ms, Qs, sigs, ec, hf)) self.assertRaises(AssertionError, ssa._batch_verify, ms, Qs, sigs, ec, hf) # ssa._batch_verify(ms, Qs, sigs, ec, hf) sigs[-1] = sigs[0] # valid again # Invalid size: 31 bytes instead of 32 ms[-1] = ms[0][:-1] self.assertRaises(ValueError, ssa._batch_verify, ms, Qs, sigs, ec, hf) # ssa._batch_verify(ms, Qs, sigs, ec, hf) ms[-1] = ms[0] # valid again # mismatch between number of pubkeys (5) and number of messages (6) ms.append(ms[0]) # add extra message self.assertRaises(ValueError, ssa._batch_verify, ms, Qs, sigs, ec, hf) # ssa._batch_verify(ms, Qs, sigs, ec, hf) ms.pop() # valid again # mismatch between number of pubkeys (5) and number of signatures (6) sigs.append(sigs[0]) # add extra sig self.assertRaises(ValueError, ssa._batch_verify, ms, Qs, sigs, ec, hf) # ssa._batch_verify(ms, Qs, sigs, ec, hf) sigs.pop() # valid again # field prime p is not equal to 3 (mod 4) self.assertRaises(ValueError, ssa._batch_verify, ms, Qs, sigs, secp224k1, hf)
def test_batch_validation(self): ec = secp256k1 hf = sha256 m = [] sig = [] Q = [] hsize = hf().digest_size hlen = hsize * 8 m.append(random.getrandbits(hlen).to_bytes(hsize, byteorder='big')) q = (1 + random.getrandbits(ec.nlen)) % ec.n sig.append(ssa.sign(m[0], q, None, ec, hf)) Q.append(mult(q, ec.G, ec)) # test with only 1 sig self.assertTrue(ssa.batch_verify(m, Q, sig, ec, hf)) for i in range(1, 4): m.append(random.getrandbits(hlen).to_bytes(hsize, byteorder='big')) q = (1 + random.getrandbits(ec.nlen)) % ec.n sig.append(ssa.sign(m[i], q, None, ec, hf)) Q.append(mult(q, ec.G, ec)) self.assertTrue(ssa.batch_verify(m, Q, sig, ec, hf)) # invalid sig m.append(m[0]) sig.append(sig[1]) Q.append(Q[0]) self.assertFalse(ssa.batch_verify(m, Q, sig, ec, hf)) #ssa._batch_verify(m, Q, sig, ec, hf) sig[-1] = sig[0] # valid again # invalid 31 bytes message m[-1] = m[0][:-1] self.assertFalse(ssa.batch_verify(m, Q, sig, ec, hf)) #ssa._batch_verify(m, Q, sig, ec, hf) m[-1] = m[0] # valid again # mismatch between number of pubkeys and number of messages m.append(m[0]) # add extra message self.assertRaises(ValueError, ssa._batch_verify, m, Q, sig, ec, hf) #ssa._batch_verify(m, Q, sig, ec, hf) m.pop() # valid again # mismatch between number of pubkeys and number of signatures sig.append(sig[0]) # add extra sig self.assertRaises(ValueError, ssa._batch_verify, m, Q, sig, ec, hf) #ssa._batch_verify(m, Q, sig, ec, hf) sig.pop() # valid again # curve prime p must be equal to 3 (mod 4) ec = secp224k1 self.assertRaises(ValueError, ssa._batch_verify, m, Q, sig, ec, hf)
def test_low_cardinality(): """test low-cardinality curves for all msg/key pairs.""" # ec.n has to be prime to sign test_curves = [ low_card_curves["ec13_11"], low_card_curves["ec13_19"], low_card_curves["ec17_13"], low_card_curves["ec17_23"], low_card_curves["ec19_13"], low_card_curves["ec19_23"], low_card_curves["ec23_19"], low_card_curves["ec23_31"], ] # only low cardinality test curves or it would take forever for ec in test_curves: # BIP340 Schnorr only applies to curve whose prime p = 3 %4 if not ec.pIsThreeModFour: err_msg = "field prime is not equal to 3 mod 4: " with pytest.raises(ValueError, match=err_msg): ssa.sign(32 * b"\x00", 1, None, ec) continue for q in range(1, ec.n // 2): # all possible private keys QJ = _mult_jac(q, ec.GJ, ec) # public key x_Q = ec._x_aff_from_jac(QJ) if not ec.has_square_y(QJ): q = ec.n - q QJ = ec.negate(QJ) for k in range(1, ec.n // 2): # all possible ephemeral keys RJ = _mult_jac(k, ec.GJ, ec) r = ec._x_aff_from_jac(RJ) if not ec.has_square_y(RJ): k = ec.n - k for e in range(ec.n): # all possible challenges s = (k + e * q) % ec.n sig = ssa._sign(r, e, q, k, ec) assert (r, s) == sig # valid signature must validate ssa._assert_as_valid(e, QJ, r, s, ec) # if e == 0 then the sig is valid for all {q, Q} # no public key can be recovered if e == 0: err_msg = "invalid zero challenge" with pytest.raises(ValueError, match=err_msg): ssa._recover_pubkey(e, r, s, ec) else: assert x_Q == ssa._recover_pubkey(e, r, s, ec)
def test_bip340_vectors(self): """BIP340 (Schnorr) test vectors https://github.com/bitcoin/bips/blob/master/bip-0340/test-vectors.csv """ fname = "bip340_test_vectors.csv" filename = path.join(path.dirname(__file__), "test_data", fname) with open(filename, newline='') as csvfile: reader = csv.reader(csvfile) # skip column headers while checking that there are 7 columns _, _, _, _, _, _, _ = reader.__next__() for row in reader: (index, seckey, pubkey, mhd, sig, result, comment) = row errmsg = f"Test vector #{int(index)}" if seckey != '': seckey = bytes.fromhex(seckey) _, pubkey_actual = ssa.gen_keys(seckey) self.assertEqual(pubkey, hex(pubkey_actual).upper()[2:], errmsg) sig_actual = ssa.serialize(*ssa.sign(mhd, seckey)) self.assertEqual(sig, sig_actual.hex().upper(), errmsg) result = result == 'TRUE' if comment: errmsg += ": " + comment result_actual = ssa.verify(mhd, pubkey, sig) self.assertEqual(result, result_actual, errmsg)
def test_ecssa(self): """Basic tests""" ec = secp256k1 q = 0x1 Q = mult(ec, q, ec.G) msg = hf('Satoshi Nakamoto'.encode()).digest() sig = ssa.sign(ec, hf, msg, q, None) # no source for the following... but # https://bitcointalk.org/index.php?topic=285142.40 # same r because of rfc6979 exp_sig = ( 0x934B1EA10A4B3C1757E2B0C017D0B6143CE3C9A7E6A4A49860D7A6AB210EE3D8, 0x2DF2423F70563E3C4BD0E00BDEF658081613858F110ECF937A2ED9190BF4A01A) self.assertEqual(sig[0], exp_sig[0]) self.assertEqual(sig[1], exp_sig[1]) ssa._verify(ec, hf, msg, Q, sig) self.assertTrue(ssa.verify(ec, hf, msg, Q, sig)) self.assertTrue(ssa._verify(ec, hf, msg, Q, sig)) fmsg = hf('Craig Wright'.encode()).digest() self.assertFalse(ssa.verify(ec, hf, fmsg, Q, sig)) self.assertFalse(ssa._verify(ec, hf, fmsg, Q, sig)) fssasig = (sig[0], sig[1], sig[1]) self.assertFalse(ssa.verify(ec, hf, msg, Q, fssasig)) self.assertRaises(TypeError, ssa._verify, ec, hf, msg, Q, fssasig) # y(sG - eP) is not a quadratic residue fq = 0x2 fQ = mult(ec, fq, ec.G) self.assertFalse(ssa.verify(ec, hf, msg, fQ, sig)) self.assertRaises(ValueError, ssa._verify, ec, hf, msg, fQ, sig) fq = 0x4 fQ = mult(ec, fq, ec.G) self.assertFalse(ssa.verify(ec, hf, msg, fQ, sig)) self.assertFalse(ssa._verify(ec, hf, msg, fQ, sig)) # not ec.pIsThreeModFour self.assertFalse(ssa.verify(secp224k1, hf, msg, Q, sig)) self.assertRaises(ValueError, ssa._verify, secp224k1, hf, msg, Q, sig) # verify: message of wrong size wrongmsg = msg[:-1] self.assertFalse(ssa.verify(ec, hf, wrongmsg, Q, sig)) self.assertRaises(ValueError, ssa._verify, ec, hf, wrongmsg, Q, sig) #ssa._verify(ec, hf, wrongmsg, Q, sig) # sign: message of wrong size self.assertRaises(ValueError, ssa.sign, ec, hf, wrongmsg, q, None) #ssa.sign(ec, hf, wrongmsg, q, None) # invalid (zero) challenge e self.assertRaises(ValueError, ssa._pubkey_recovery, ec, hf, 0, sig)
def ecssa_commit_sign(c: bytes, ec: Curve, hf: Callable[[Any], Any], m: bytes, prvkey: int, k: Optional[int] = None) -> Tuple[ssa.ECSS, Receipt]: if k is None: k = rfc6979(ec, hf, m, prvkey) ch = hf(c).digest() # commit R, new_k = _tweak(ch, ec, hf, k) # sign sig = ssa.sign(ec, hf, m, prvkey, new_k) # commit receipt receipt = sig[0], R return sig, receipt
def test_ecssa(self): """Basic tests""" q = 0x1 Q = mult(q) mhd = hf(b'Satoshi Nakamoto').digest() sig = ssa.sign(mhd, q, None) ssa._verify(mhd, Q, sig, ec, hf) self.assertTrue(ssa.verify(mhd, Q, sig)) fmhd = hf(b'Craig Wright').digest() self.assertRaises(AssertionError, ssa._verify, fmhd, Q, sig, ec, hf) fssasig = (sig[0], sig[1], sig[1]) self.assertRaises(ValueError, ssa._verify, mhd, Q, fssasig, ec, hf) # y(sG - eP) is not a quadratic residue fq = 0x2 fQ = mult(fq) self.assertRaises(ValueError, ssa._verify, mhd, fQ, sig, ec, hf) fq = 0x4 fQ = mult(fq) self.assertRaises(AssertionError, ssa._verify, mhd, fQ, sig, ec, hf) # not ec.pIsThreeModFour self.assertRaises(ValueError, ssa._verify, mhd, Q, sig, secp224k1, hf) # verify: message of wrong size wrongmhd = mhd[:-1] self.assertRaises(ValueError, ssa._verify, wrongmhd, Q, sig, ec, hf) #ssa._verify(wrongmhd, Q, sig) # sign: message of wrong size self.assertRaises(ValueError, ssa.sign, wrongmhd, q, None) #ssa.sign(wrongmhd, q, None) # invalid (zero) challenge e self.assertRaises(ValueError, ssa._recover_pubkeys, 0, sig[0], sig[1], ec) #ssa._recover_pubkeys(0, sig) # not a BIP340 public key self.assertRaises(ValueError, ssa.to_bip340_pubkey_tuple, ["not", "a BIP340", "public key"])
def test_signature(self): """Basic tests""" q, x_Q = ssa.gen_keys() mhd = hf(b'Satoshi Nakamoto').digest() sig = ssa.sign(mhd, q, None) self.assertEqual(sig, ssa.deserialize(sig)) ssa._verify(mhd, x_Q, sig, ec, hf) self.assertTrue(ssa.verify(mhd, x_Q, sig)) fmhd = hf(b'Craig Wright').digest() self.assertRaises(AssertionError, ssa._verify, fmhd, x_Q, sig, ec, hf) fssasig = (sig[0], sig[1], sig[1]) self.assertRaises(ValueError, ssa._verify, mhd, x_Q, fssasig, ec, hf) # y(sG - eP) is not a quadratic residue _, fQ = ssa.gen_keys(0x2) self.assertRaises(AssertionError, ssa._verify, mhd, fQ, sig, ec, hf) _, fQ = ssa.gen_keys(0x4) self.assertRaises(AssertionError, ssa._verify, mhd, fQ, sig, ec, hf) # not ec.pIsThreeModFour self.assertRaises(ValueError, ssa._verify, mhd, x_Q, sig, secp224k1, hf) # verify: message of wrong size wrongmhd = mhd[:-1] self.assertRaises(ValueError, ssa._verify, wrongmhd, x_Q, sig, ec, hf) # ssa._verify(wrongmhd, x_Q, sig) # sign: message of wrong size self.assertRaises(ValueError, ssa.sign, wrongmhd, q, None) # ssa.sign(wrongmhd, q, None) # invalid (zero) challenge e self.assertRaises(ValueError, ssa._recover_pubkeys, 0, sig[0], sig[1], ec) # ssa._recover_pubkeys(0, sig) # not a BIP340 public key self.assertRaises(ValueError, ssa._to_bip340_point, ["not", "a BIP340", "public key"])
def test_low_cardinality(self): """test low-cardinality curves for all msg/key pairs.""" # ec.n has to be prime to sign prime = [11, 13, 17, 19] # for dsa it is possible to directy iterate on all # possible values for e; # for ssa we have to iterate over all possible hash values hsize = hf().digest_size H = [i.to_bytes(hsize, 'big') for i in range(max(prime) * 4)] # only low card curves or it would take forever for ec in low_card_curves: if ec._p in prime: # only few curves or it would take too long # BIP340 Schnorr only applies to curve whose prime p = 3 %4 if not ec.pIsThreeModFour: self.assertRaises(ValueError, ssa.sign, H[0], 1, None, ec) continue for q in range(1, ec.n): # all possible private keys Q = mult(q, ec.G, ec) # public key if not ec.has_square_y(Q): q = ec.n - q for h in H: # all possible hashed messages k = ssa.k(h, q, ec, hf) K = mult(k, ec.G, ec) if not ec.has_square_y(K): k = ec.n - k x_K = K[0] try: c = ssa._challenge(x_K, Q[0], h, ec, hf) except Exception: pass else: s = (k + c * q) % ec.n sig = ssa.sign(h, q, None, ec) self.assertEqual((x_K, s), sig) # valid signature must validate self.assertIsNone(ssa._verify(h, Q, sig, ec, hf)) if c != 0: # FIXME x_Q = ssa._recover_pubkeys(c, x_K, s, ec) self.assertEqual(Q[0], x_Q)
def test_low_cardinality(self): """test all msg/key pairs of low cardinality elliptic curves""" # ec.n has to be prime to sign prime = [11, 13, 17, 19] # all possible hashed messages hsize = 32 H = [i.to_bytes(hsize, 'big') for i in range(max(prime) * 2)] # only low card curves or it would take forever for ec in low_card_curves: if ec._p in prime: # only few curves or it would take too long # Schnorr-bip only applies to curve whose prime p = 3 %4 if not ec.pIsThreeModFour: self.assertRaises(ValueError, ssa.sign, ec, hf, H[0], 1, None) continue for q in range(ec.n): # all possible private keys if q == 0: # invalid prvkey=0 self.assertRaises(ValueError, ssa.sign, ec, hf, H[0], q, None) self.assertRaises(ValueError, rfc6979, ec, hf, H[0], q) continue Q = mult(ec, q, ec.G) # public key for h in H: # all possible hashed messages # k = 0 self.assertRaises(ValueError, ssa.sign, ec, hf, h, q, 0) k = rfc6979(ec, hf, h, q) K = mult(ec, k, ec.G) if legendre_symbol(K[1], ec._p) != 1: k = ec.n - k e = ssa._e(ec, hf, K[0], Q, h) s = (k + e * q) % ec.n # valid signature sig = ssa.sign(ec, hf, h, q, k) self.assertEqual((K[0], s), sig) # valid signature must validate self.assertTrue(ssa._verify(ec, hf, h, Q, sig))
def test_batch_validation(self): ec = secp256k1 m = [] sig = [] Q = [] hsize = hf().digest_size hlen = hsize * 8 for i in range(10): m.append(random.getrandbits(hlen).to_bytes(hsize, 'big')) q = random.getrandbits(ec.nlen) % ec.n sig.append(ssa.sign(ec, hf, m[i], q)) Q.append(mult(ec, q, ec.G)) self.assertTrue(ssa.batch_verify(ec, hf, m, Q, sig)) m.append(m[0]) sig.append(sig[1]) # invalid Q.append(Q[0]) self.assertFalse(ssa.batch_verify(ec, hf, m, Q, sig)) sig[-1] = sig[0] # valid m[-1] = m[0][:-1] # invalid 31 bytes message self.assertFalse(ssa.batch_verify(ec, hf, m, Q, sig))
dsa_sig = dsa.sign(msg, dsa_prv) print("r:", hex(dsa_sig[0])) print("s:", hex(dsa_sig[1])) dsa_valid = dsa.verify(msg, dsa_pub, dsa_sig) print("valid ECDSA sig:", dsa_valid) # ECSSA print("\n ECSSA") ssa_prv, ssa_pub = ssa.gen_keys() print("prv", hex(ssa_prv)) print("pub", hex(ssa_pub)) ssa_sig = ssa.sign(msg, ssa_prv) print("r:", hex(ssa_sig[0])) print("s:", hex(ssa_sig[1])) ssa_valid = ssa.verify(msg, ssa_pub, ssa_sig) print("valid ECSSA sig:", ssa_valid) # ECBMS print("\n ECBMS") bms_prv, bms_pub = bms.gen_keys() print("prv", bms_prv) print("pub", bms_pub) bms_sig = bms.sign(msg, bms_prv) print("rf:", hex(bms_sig[0]))
def test_signature(): """Basic tests""" ec = secp256k1 q, x_Q = ssa.gen_keys(0x1) mhd = hf(b"Satoshi Nakamoto").digest() sig = ssa.sign(mhd, q, None) ssa.assert_as_valid(mhd, x_Q, sig, ec, hf) assert ssa.verify(mhd, x_Q, sig) assert sig == ssa.deserialize(sig) fmhd = hf(b"Craig Wright").digest() assert not ssa.verify(fmhd, x_Q, sig, ec, hf) err_msg = "signature verification failed" with pytest.raises(AssertionError, match=err_msg): ssa.assert_as_valid(fmhd, x_Q, sig, ec, hf) _, x_fQ = ssa.gen_keys(0x2) assert not ssa.verify(mhd, x_fQ, sig, ec, hf) err_msg = "y_K is not a quadratic residue" with pytest.raises(RuntimeError, match=err_msg): ssa.assert_as_valid(mhd, x_fQ, sig, ec, hf) _, x_fQ = ssa.gen_keys(0x4) assert not ssa.verify(mhd, x_fQ, sig, ec, hf) err_msg = "signature verification failed" with pytest.raises(AssertionError, match=err_msg): ssa.assert_as_valid(mhd, x_fQ, sig, ec, hf) err_msg = "not a BIP340 public key" with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(mhd, INF, sig, ec, hf) assert not ssa.verify(mhd, x_Q, sig, secp224k1, hf) err_msg = "field prime is not equal to 3 mod 4: " with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(mhd, x_Q, sig, secp224k1, hf) wrongmhd = mhd[:-1] assert not ssa.verify(wrongmhd, x_Q, sig, ec, hf) err_msg = "invalid size: 31 bytes instead of 32" with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(wrongmhd, x_Q, sig, ec, hf) fssasig = (sig[0], sig[1], sig[1]) assert not ssa.verify(mhd, x_fQ, fssasig, ec, hf) err_msg = "too many values to unpack " with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(mhd, x_Q, fssasig, ec, hf) invalid_sig = ec.p, sig[1] assert not ssa.verify(mhd, x_Q, invalid_sig) err_msg = "x-coordinate not in 0..p-1: " with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(mhd, x_Q, invalid_sig, ec, hf) invalid_sig = sig[0], ec.p assert not ssa.verify(mhd, x_Q, invalid_sig) err_msg = "scalar s not in 0..n-1: " with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(mhd, x_Q, invalid_sig, ec, hf) err_msg = "invalid size: 31 bytes instead of 32" with pytest.raises(ValueError, match=err_msg): ssa.sign(wrongmhd, q, None) err_msg = "private key not in 1..n-1: " with pytest.raises(ValueError, match=err_msg): ssa.sign(mhd, 0) # ephemeral key not in 1..n-1 err_msg = "private key not in 1..n-1: " with pytest.raises(ValueError, match=err_msg): ssa.sign(mhd, 1, 0) err_msg = "invalid zero challenge" with pytest.raises(ValueError, match=err_msg): ssa._recover_pubkey(0, sig[0], sig[1], ec) err_msg = "not a BIP340 public key" with pytest.raises(ValueError, match=err_msg): ssa._to_bip340_point(["not", "a BIP340", "public key"])
def test_signature() -> None: ec = CURVES["secp256k1"] msg = "Satoshi Nakamoto" q, x_Q = ssa.gen_keys(0x01) sig = ssa.sign(msg, q) ssa.assert_as_valid(msg, x_Q, sig) assert ssa.verify(msg, x_Q, sig) assert sig == ssa.deserialize(sig) ssa.assert_as_valid(msg, x_Q, sig) ssa.assert_as_valid(msg, x_Q, ssa.serialize(*sig)) ssa.assert_as_valid(msg, x_Q, ssa.serialize(*sig).hex()) msg_fake = "Craig Wright" assert not ssa.verify(msg_fake, x_Q, sig) err_msg = r"y_K is odd|signature verification failed" with pytest.raises(BTClibRuntimeError, match=err_msg): ssa.assert_as_valid(msg_fake, x_Q, sig) _, x_Q_fake = ssa.gen_keys(0x02) assert not ssa.verify(msg, x_Q_fake, sig) with pytest.raises(BTClibRuntimeError, match=err_msg): ssa.assert_as_valid(msg, x_Q_fake, sig) _, x_Q_fake = ssa.gen_keys(0x4) assert not ssa.verify(msg, x_Q_fake, sig) with pytest.raises(BTClibRuntimeError, match=err_msg): ssa.assert_as_valid(msg, x_Q_fake, sig) err_msg = "not a BIP340 public key" with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_as_valid(msg, INF, sig) # type: ignore with pytest.raises(BTClibValueError, match=err_msg): ssa.point_from_bip340pubkey(INF) # type: ignore sig_fake = (sig[0], sig[1], sig[1]) assert not ssa.verify(msg, x_Q, sig_fake) # type: ignore err_msg = "too many values to unpack " with pytest.raises(ValueError, match=err_msg): ssa.assert_as_valid(msg, x_Q, sig_fake) # type: ignore sig_invalid = ec.p, sig[1] assert not ssa.verify(msg, x_Q, sig_invalid) err_msg = "x-coordinate not in 0..p-1: " with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_as_valid(msg, x_Q, sig_invalid) sig_invalid = sig[0], ec.p assert not ssa.verify(msg, x_Q, sig_invalid) err_msg = "scalar s not in 0..n-1: " with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_as_valid(msg, x_Q, sig_invalid) m_fake = b"\x00" * 31 err_msg = "invalid size: 31 bytes instead of 32" with pytest.raises(BTClibValueError, match=err_msg): ssa._assert_as_valid(m_fake, x_Q, sig) with pytest.raises(BTClibValueError, match=err_msg): ssa._sign(m_fake, q) err_msg = "private key not in 1..n-1: " with pytest.raises(BTClibValueError, match=err_msg): ssa.sign(msg, 0) # ephemeral key not in 1..n-1 err_msg = "private key not in 1..n-1: " with pytest.raises(BTClibValueError, match=err_msg): ssa._sign(reduce_to_hlen(msg, hf), q, 0) with pytest.raises(BTClibValueError, match=err_msg): ssa._sign(reduce_to_hlen(msg, hf), q, ec.n) err_msg = "invalid zero challenge" with pytest.raises(BTClibValueError, match=err_msg): ssa.__recover_pubkey(0, sig[0], sig[1], ec)
def test_schnorr_bip_tv(self): """Bip-Schnorr Test Vectors https://github.com/sipa/bips/blob/bip-schnorr/bip-schnorr.mediawiki """ hf = sha256 # test vector 1 prv = int_from_bits(b'\x00' * 31 + b'\x01') pub = mult(prv) msg = b'\x00' * 32 expected_sig = ( 0x787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF6, 0x7031A98831859DC34DFFEEDDA86831842CCD0079E1F92AF177F7F22CC1DCED05) eph_prv = int.from_bytes(hf(prv.to_bytes(32, byteorder='big') + msg).digest(), byteorder='big') sig = ssa.sign(msg, prv, eph_prv) self.assertTrue(ssa._verify(msg, pub, sig)) self.assertEqual(sig, expected_sig) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 2 prv = 0xB7E151628AED2A6ABF7158809CF4F3C762E7160F38B4DA56A784D9045190CFEF pub = mult(prv) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") expected_sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) eph_prv = int.from_bytes(hf(prv.to_bytes(32, byteorder='big') + msg).digest(), byteorder='big') sig = ssa.sign(msg, prv, eph_prv) self.assertTrue(ssa._verify(msg, pub, sig)) self.assertEqual(sig, expected_sig) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 3 prv = 0xC90FDAA22168C234C4C6628B80DC1CD129024E088A67CC74020BBEA63B14E5C7 pub = mult(prv) msg = bytes.fromhex( "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") expected_sig = ( 0x00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE, 0x00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380) eph_prv = int.from_bytes(hf(prv.to_bytes(32, byteorder='big') + msg).digest(), byteorder='big') sig = ssa.sign(msg, prv, eph_prv) self.assertTrue(ssa._verify(msg, pub, sig)) self.assertEqual(sig, expected_sig) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 4 pub = point_from_octets( "03DEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" ) msg = bytes.fromhex( "4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703") sig = ( 0x00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C63, 0x02A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 5 # test would fail if jacobi symbol of x(R) instead of y(R) is used pub = point_from_octets( "031B84C5567B126440995D3ED5AABA0565D71E1834604819FF9C17F5E9D5DD078F" ) msg = bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000") sig = ( 0x52818579ACA59767E3291D91B76B637BEF062083284992F2D95F564CA6CB4E35, 0x30B1DA849C8E8304ADC0CFE870660334B3CFC18E825EF1DB34CFAE3DFC5D8187) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # test vector 6 # test would fail if msg is reduced pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") sig = ( 0x570DD4CA83D4E6317B8EE6BAE83467A1BF419D0767122DE409394414B05080DC, 0xE9EE5F237CBD108EABAE1E37759AE47F8E4203DA3532EB28DB860F33D62D49BD) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # new proposed test: test would fail if msg is reduced pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "000008D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A5000000") sig = ( 0x3598678C6C661F02557E2F5614440B53156997936FE54A90961CFCC092EF789D, 0x41E4E4386E54C924251679ADD3D837367EECBFF248A3DE7C2DB4CE52A3D6192A) self.assertTrue(ssa._verify(msg, pub, sig)) e = ssa._e(sig[0], pub, msg) self.assertEqual(ssa._pubkey_recovery(e, sig), pub) # new proposed test: genuine failure pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "0000000000000000000000000000000000000000000000000000000000000000") sig = ( 0x3598678C6C661F02557E2F5614440B53156997936FE54A90961CFCC092EF789D, 0x41E4E4386E54C924251679ADD3D837367EECBFF248A3DE7C2DB4CE52A3D6192A) self.assertFalse(ssa._verify(msg, pub, sig)) # new proposed test: P = infinite pub = 1, 0 msg = bytes.fromhex( "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") sig = ( 0x00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE, 0x00880371D01766935B92D2AB4CD5C8A2A5837EC57FED7660773A05F0DE142380) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 7 # public key not on the curve # impossible to verify with btclib analytics as it at Point conversion self.assertRaises( ValueError, point_from_octets, "03EEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34" ) # msg = bytes.fromhex("4DF3C3F68FCC83B27E9D42C90431A72499F17875C81A599B566C9889B9696703") # sig = (0x00000000000000000000003B78CE563F89A0ED9414F5AA28AD0D96D6795F9C63, 0x02A8DC32E64E86A333F20EF56EAC9BA30B7246D6D25E22ADB8C6BE1AEB08D49D) # self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 8 # Incorrect sig: incorrect R residuosity pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0xFA16AEE06609280A19B67A24E1977E4697712B5FD2943914ECD5F730901B4AB7) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 9 # Incorrect sig: negated message hash pub = point_from_octets( "03FAC2114C2FBB091527EB7C64ECB11F8021CB45E8E7809D3C0938E4B8C0E5F84B" ) msg = bytes.fromhex( "5E2D58D8B3BCDF1ABADEC7829054F90DDA9805AAB56C77333024B9D0A508B75C") sig = ( 0x00DA9B08172A9B6F0466A2DEFD817F2D7AB437E0D253CB5395A963866B3574BE, 0xD092F9D860F1776A1F7412AD8A1EB50DACCC222BC8C0E26B2056DF2F273EFDEC) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 10 # Incorrect sig: negated s value pub = point_from_octets( "0279BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798" ) msg = b'\x00' * 32 sig = ( 0x787A848E71043D280C50470E8E1532B2DD5D20EE912A45DBDD2BD1DFBF187EF6, 0x8FCE5677CE7A623CB20011225797CE7A8DE1DC6CCD4F754A47DA6C600E59543C) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 11 # Incorrect sig: negated public key pub = point_from_octets( "03DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 12 # sG - eP is infinite. # Test fails in single verification if jacobi(y(inf)) is defined as 1 and x(inf) as 0 pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x0000000000000000000000000000000000000000000000000000000000000000, 0x9E9D01AF988B5CEDCE47221BFA9B222721F3FA408915444A4B489021DB55775F) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 13 # sG - eP is infinite. # Test fails in single verification if jacobi(y(inf)) is defined as 1 and x(inf) as 1""" pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x0000000000000000000000000000000000000000000000000000000000000001, 0xD37DDF0254351836D84B1BD6A795FD5D523048F298C4214D187FE4892947F728) self.assertRaises(ValueError, ssa._verify, msg, pub, sig) # test vector 14 # sig[0:32] is not an X coordinate on the curve pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x4A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) self.assertFalse(ssa._verify(msg, pub, sig)) # test vector 15 # sig[0:32] is equal to field size pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFC2F, 0x1E51A22CCEC35599B8F266912281F8365FFC2D035A230434A1A64DC59F7013FD) #self.assertRaises(ValueError, ssa._verify, msg, pub, sig) self.assertFalse(ssa._verify(msg, pub, sig)) # test vector 16 # sig[32:64] is equal to curve order pub = point_from_octets( "02DFF1D77F2A671C5F36183726DB2341BE58FEAE1DA2DECED843240F7B502BA659" ) msg = bytes.fromhex( "243F6A8885A308D313198A2E03707344A4093822299F31D0082EFA98EC4E6C89") sig = ( 0x2A298DACAE57395A15D0795DDBFD1DCB564DA82B0F269BC70A74F8220429BA1D, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141) self.assertRaises(ValueError, ssa._verify, msg, pub, sig)
print("\n0. Message to be signed") orig_msg = "Paolo is afraid of ephemeral random numbers" msg = sha256(orig_msg.encode()).digest() print(f" {msg.hex().upper()}") q = 0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 q %= ec.n print("1. Key generation") print(f"prvkey: {hex(q).upper()}") Q = mult(q, ec.G)[0] print(f"PubKey: {hex(Q).upper()}") print("2. Sign message") r, s = sign(msg, q) print(f" r: {hex(r).upper()}") print(f" s: {hex(s).upper()}") print("3. Verify signature") print(verify(msg, Q, (r, s))) print("\n** Malleated signature") sm = ec.n - s print(f" r: {hex(r).upper()}") print(f" sm: {hex(sm).upper()}") print("** Verify malleated signature") print(verify(msg, Q, (r, sm))) print("\n0. Another message to sign")
random.seed(42) ec = secp256k1 hf = sha256 hsize = hf().digest_size hlen = hsize * 8 # n = 1 loops forever and does not really test batch verify n_sig = [2, 4, 8, 16, 32, 64, 128] m = [] sig = [] Q = [] for j in range(max(n_sig)): m.append(random.getrandbits(hlen).to_bytes(hsize, 'big')) q = random.getrandbits(ec.nlen) % ec.n sig.append(sign(ec, hf, m[j], q)) Q.append(mult(ec, q, ec.G)) for n in n_sig: # no batch start = time.time() for j in range(n): assert verify(ec, hf, m[j], Q[j], sig[j]) elapsed1 = time.time() - start # batch start = time.time() assert batch_verify(ec, hf, m[:n], Q[:n], sig[:n]) elapsed2 = time.time() - start
from btclib.ssa import batch_verify, sign, verify random.seed(42) hsize = hf().digest_size hlen = hsize * 8 # n = 1 loops forever and does not really test batch verify n_sig = [2, 4, 8, 16, 32, 64, 128] m = [] sig = [] Q = [] for j in range(max(n_sig)): m.append(random.getrandbits(hlen).to_bytes(hsize, 'big')) q = random.getrandbits(ec.nlen) % ec.n sig.append(sign(m[j], q)) Q.append(mult(q, ec.G)) for n in n_sig: # no batch start = time.time() for j in range(n): assert verify(m[j], Q[j], sig[j]) elapsed1 = time.time() - start # batch start = time.time() assert batch_verify(m[:n], Q[:n], sig[:n]) elapsed2 = time.time() - start