def test_crack_prvkey() -> None: ec = CURVES["secp256k1"] q, x_Q = ssa.gen_keys() msg1 = "Paolo is afraid of ephemeral random numbers" m_1 = reduce_to_hlen(msg1) k = ssa._det_nonce(m_1, q) sig1 = ssa._sign(m_1, q, k) msg2 = "and Paolo is right to be afraid" m_2 = reduce_to_hlen(msg2) # reuse same k sig2 = ssa._sign(m_2, q, k) qc, kc = ssa.crack_prvkey(msg1, sig1, msg2, sig2, x_Q) assert q == qc assert k in (kc, ec.n - kc) with pytest.raises(BTClibValueError, match="not the same r in signatures"): ssa._crack_prvkey(m_1, sig1, m_2, (16, sig1[1]), x_Q) with pytest.raises(BTClibValueError, match="identical signatures"): ssa._crack_prvkey(m_1, sig1, m_1, sig1, x_Q)
def test_crack_prvkey() -> None: ec = CURVES["secp256k1"] q = 0x19E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 x_Q = mult(q)[0] msg1_str = "Paolo is afraid of ephemeral random numbers" msg1 = hf(msg1_str.encode()).digest() k, _ = ssa._det_nonce(msg1, q) sig1 = ssa._sign(msg1, q, k) msg2_str = "and Paolo is right to be afraid" msg2 = hf(msg2_str.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: ec = CURVES["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]) 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): m = secrets.randbits(hlen).to_bytes(hsize, "big") ms.append(m) q = 1 + secrets.randbelow(ec.n - 1) # Point version Qs.append(mult(q, ec.G, ec)[0]) sigs.append(ssa._sign(m, 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, CURVES["secp224k1"], hf)
def test_low_cardinality() -> None: "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(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_jac(QJ) for k in range(1, ec.n // 2): # all possible ephemeral keys RJ = _mult(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(e, q, k, r, 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() -> None: """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, m, sig, result, comment) = row err_msg = f"Test vector #{int(index)}" if seckey != "": _, pubkey_actual = ssa.gen_keys(seckey) assert pubkey == hex(pubkey_actual).upper()[2:], err_msg sig_actual = ssa.serialize(*ssa._sign(m, seckey)) assert sig == sig_actual.hex().upper(), err_msg if comment: err_msg += ": " + comment # TODO what's worng with xor-ing ? # assert (result == "TRUE") ^ ssa._verify(m, pubkey, sig), err_msg if result == "TRUE": assert ssa._verify(m, pubkey, sig), err_msg else: assert not ssa._verify(m, pubkey, sig), err_msg
def test_invalid_schnorr(): sighash = bytes.fromhex("00" * 32) sig = ssa.serialize(*ssa._sign(sighash, 1)) pubkey = bytes_from_point(mult(1)) pubkey_hash = hash256(pubkey) script = Script([ [0x00, 0x00, pubkey], [0x01, 0x00, sig], [0x02, 0x00, pubkey_hash], # push pubkey_hash [0x03, 0x02, b"\x00"], # hash of pub key from unlocking script [0xFF, 0x01, b"\x03\x02"], # check equality [0xFF, 0x04, b"\xff"], # exit if not equal [0xFF, 0x03, b"\x00\x01"], # schnorr verify [0xFF, 0x04, b"\xff"], ] # exit if not equal]) # push signature ) assert script.execute(memory={0x100: sighash})
def test_bip340_vectors() -> None: """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, aux_rand, m, sig, result, comment) = row err_msg = f"Test vector #{int(index)}" try: if seckey != "": _, pubkey_actual = ssa.gen_keys(seckey) assert pubkey == hex(pubkey_actual).upper()[2:], err_msg k = ssa._det_nonce(m, seckey, aux_rand) sig_actual = ssa._sign(m, seckey, k) ssa._assert_as_valid(m, pubkey, sig_actual) assert ssa.deserialize(sig) == sig_actual, err_msg if comment: err_msg += ": " + comment # TODO what's wrong with xor-ing ? # assert (result == "TRUE") ^ ssa._verify(m, pubkey, sig), err_msg if result == "TRUE": ssa._assert_as_valid(m, pubkey, sig) assert ssa._verify(m, pubkey, sig), err_msg else: assert not ssa._verify(m, pubkey, sig), err_msg except Exception as e: # pragma: no cover # pylint: disable=broad-except print(err_msg) # pragma: no cover raise e # pragma: no cover
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 unlock_p2pkh(sighash, prvkey): sig = ssa.serialize(*ssa._sign(sighash, prvkey)) pubkey = bytes_from_point(mult(prvkey)) return Script( [[0x00, OP_PUSHDATA, pubkey], [0x01, OP_PUSHDATA, sig]] ) # push signature
from btclib.curve import mult from btclib.curve import secp256k1 as ec 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 = [4, 8, 16, 32, 64, 128, 256, 512] m = [ random.getrandbits(hlen).to_bytes(hsize, "big") for _ in range(max(n_sig)) ] q = [random.getrandbits(ec.nlen) % ec.n for _ in m] sig = [_sign(msg, qq) for msg, qq in zip(m, q)] Q = [mult(qq, ec.G)[0] for qq in q] 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 ms = m[:n] Qs = Q[:n] sigs = sig[:n] start = time.time()