def _sign_(c: int, q: int, nonce: int, lower_s: bool, ec: Curve) -> Sig: # Private function for testing purposes: it allows to explore all # possible value of the challenge c (for low-cardinality curves). # It assume that c is in [0, n-1], while q and nonce are in [1, n-1] # Steps numbering follows SEC 1 v.2 section 4.1.3 KJ = _mult(nonce, ec.GJ, ec) # 1 # affine x_K-coordinate of K (field element) x_K = (KJ[0] * mod_inv(KJ[2] * KJ[2], ec.p)) % ec.p # mod n makes it a scalar r = x_K % ec.n # 2, 3 if r == 0: # r≠0 required as it multiplies the public key raise BTClibRuntimeError("failed to sign: r = 0") s = mod_inv(nonce, ec.n) * (c + r * q) % ec.n # 6 if s == 0: # s≠0 required as verify will need the inverse of s raise BTClibRuntimeError("failed to sign: s = 0") # bitcoin canonical 'low-s' encoding for ECDSA signatures # it removes signature malleability as cause of transaction malleability # see https://github.com/bitcoin/bitcoin/pull/6769 if lower_s and s > ec.n / 2: s = ec.n - s # s = - s % ec.n return Sig(r, s, ec)
def test_der_deserialize() -> None: err_msg = "non-hexadecimal number found " with pytest.raises(ValueError, match=err_msg): Sig.parse("not a sig") sig = Sig(2**255 - 4, 2**247 - 1) sig_bin = sig.serialize() r_size = sig_bin[3] bad_sig_bin = b"\x31" + sig_bin[1:] err_msg = "invalid compound header: " with pytest.raises(BTClibValueError, match=err_msg): Sig.parse(bad_sig_bin) bad_sig_bin = sig_bin[:1] + b"\x41" + sig_bin[2:] err_msg = "not enough binary data" with pytest.raises(BTClibRuntimeError, match=err_msg): Sig.parse(bad_sig_bin) # r and s scalars for offset in (4, 6 + r_size): bad_sig_bin = sig_bin[:offset - 2] + b"\x00" + sig_bin[offset - 1:] err_msg = "invalid value header: " with pytest.raises(BTClibValueError, match=err_msg): Sig.parse(bad_sig_bin) bad_sig_bin = sig_bin[:offset - 1] + b"\x00" + sig_bin[offset:] err_msg = "zero size" with pytest.raises(BTClibRuntimeError, match=err_msg): Sig.parse(bad_sig_bin) bad_sig_bin = sig_bin[:offset - 1] + b"\x80" + sig_bin[offset:] err_msg = "not enough binary data" with pytest.raises(BTClibRuntimeError, match=err_msg): Sig.parse(bad_sig_bin) bad_sig_bin = sig_bin[:offset] + b"\x80" + sig_bin[offset + 1:] err_msg = "invalid negative scalar" with pytest.raises(BTClibValueError, match=err_msg): Sig.parse(bad_sig_bin) bad_sig_bin = sig_bin[:offset] + b"\x00\x7f" + sig_bin[offset + 2:] err_msg = "invalid 'highest bit set' padding" with pytest.raises(BTClibValueError, match=err_msg): Sig.parse(bad_sig_bin) data_size = sig_bin[1] malleated_size = (data_size + 1).to_bytes(1, byteorder="big", signed=False) bad_sig_bin = sig_bin[:1] + malleated_size + sig_bin[2:] + b"\x01" err_msg = "invalid DER sequence length" with pytest.raises(BTClibValueError, match=err_msg): Sig.parse(bad_sig_bin)
def test_der_size() -> None: sig8 = 1, 1 sig72 = ec.n - 2, ec.n - 1 sig71 = 2**255 - 4, ec.n - 1 sig70 = 2**255 - 4, 2**255 - 1 sig70b = 2**255 - 4, 2**248 - 1 sig69 = 2**255 - 4, 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] for length, (r, s) in zip(lenghts, sigs): sig = Sig(r, s) assert r == sig.r assert s == sig.s assert ec == sig.ec sig_bin = sig.serialize() assert len(sig_bin) == length assert sig == Sig.parse(sig_bin)
def test_der_serialize() -> None: r = 2**247 - 1 s = 2**247 - 1 Sig(r, s) err_msg = "scalar r not in 1..n-1: " for bad_r in (0, ec.n): _ = Sig(bad_r, s, check_validity=False) with pytest.raises(BTClibValueError, match=err_msg): Sig(bad_r, s) err_msg = "scalar s not in 1..n-1: " for bad_s in (0, ec.n): _ = Sig(r, bad_s, check_validity=False) with pytest.raises(BTClibValueError, match=err_msg): Sig(r, bad_s) err_msg = r"r is not \(congruent to\) a valid x-coordinate: " with pytest.raises(BTClibValueError, match=err_msg): Sig(5, s)
print("3. Verify signature") print(verify(msg1, Q, sig1)) print("4. Recover keys") keys = recover_pub_keys(msg1, sig1) for i, key in enumerate(keys): print( f" key#{i}: {'02' if key[1] % 2 == 0 else '03'} {hex(key[0]).upper()}") print("\n** Malleated signature") sm = ec.n - sig1.s print(f" r1: {hex(sig1.r).upper()}") print(f" sm: {hex(sm).upper()}") print("** Verify malleated signature") print(verify(msg1, Q, Sig(sig1.r, sm))) print(verify(msg1, Q, Sig(sig1.r, sm), False)) print("\n1. Another message to sign") msg2 = "and Paolo is right to be afraid".encode() print(msg2.decode()) print("2. Sign message") sig2 = sign(msg2, q) print(f" r2: {hex(sig2.r).upper()}") print(f" s2: {hex(sig2.s).upper()}") print("3. Verify signature") print(verify(msg2, Q, sig2)) print("4. Recover keys")