def test_libsecp256k1() -> None: msg = "Satoshi Nakamoto".encode() q, _ = dsa.gen_keys(0x1) sig = dsa.sign(msg, q) msg_hash = reduce_to_hlen(msg) secret = q.to_bytes(32, "big") c_sig = ffi.new("secp256k1_ecdsa_signature *") if not lib.secp256k1_ecdsa_sign(GLOBAL_CTX, c_sig, msg_hash, secret, ffi.NULL, ffi.NULL): raise RuntimeError("libsecp256k1 signature failed") output = ffi.new("unsigned char[%d]" % CDATA_SIG_LENGTH) if not lib.secp256k1_ecdsa_signature_serialize_compact( GLOBAL_CTX, output, c_sig): raise RuntimeError("libsecp256k1 signature serialization failed") c_sig_bytes = bytes(ffi.buffer(output, CDATA_SIG_LENGTH)) r = c_sig_bytes[:32] s = c_sig_bytes[32:] assert r.hex() == sig.r.to_bytes(32, "big").hex() assert s.hex() == sig.s.to_bytes(32, "big").hex()
def test_pub_key_recovery() -> None: ec = CURVES["secp112r2"] q = 0x10 Q = mult(q, ec.G, ec) msg = "Satoshi Nakamoto".encode() sig = dsa.sign(msg, q, ec=ec) dsa.assert_as_valid(msg, Q, sig) assert dsa.verify(msg, Q, sig) keys = dsa.recover_pub_keys(msg, sig) assert len(keys) == 4 assert Q in keys for Q in keys: assert dsa.verify(msg, Q, sig)
def sign(msg: Octets, prv_key: PrvKey, addr: Optional[String] = None) -> Sig: "Generate address-based compact signature for the provided message." # first sign the message magic_msg = magic_message(msg) q, network, compressed = prv_keyinfo_from_prv_key(prv_key) dsa_sig = dsa.sign(magic_msg, q) # now calculate the key_id # TODO do the match in Jacobian coordinates avoiding mod_inv pub_keys = dsa.recover_pub_keys(magic_msg, dsa_sig) Q = mult(q) # key_id is in [0, 3] # first two bits in rf are reserved for it key_id = pub_keys.index(Q) pub_key = bytes_from_point(Q, compressed=compressed) if isinstance(addr, str): addr = addr.strip() elif isinstance(addr, bytes): addr = addr.decode("ascii") # finally, calculate the recovery flag if addr is None or addr == p2pkh(pub_key, network, compressed): rf = key_id + 27 # third bit in rf is reserved for the 'compressed' boolean rf += 4 if compressed else 0 # BIP137 elif addr == p2wpkh_p2sh(pub_key, network): rf = key_id + 35 elif addr == p2wpkh(pub_key, network): rf = key_id + 39 else: raise BTClibValueError("mismatch between private key and address") return Sig(rf, dsa_sig)
print("\n*** EC:") print(ec) print("0. Key generation") q = 0x18E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 q %= ec.n Q = mult(q, ec.G) print(f"prvkey: {hex(q).upper()}") print(f"PubKey: {'02' if Q[1] % 2 == 0 else '03'} {hex(Q[0]).upper()}") print("\n1. Message to be signed") msg1 = "Paolo is afraid of ephemeral random numbers".encode() print(msg1.decode()) print("2. Sign message") sig1 = sign(msg1, q) print(f" r1: {hex(sig1.r).upper()}") print(f" s1: {hex(sig1.s).upper()}") 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()}")
def test_signature() -> None: msg = "Satoshi Nakamoto".encode() q, Q = dsa.gen_keys(0x1) sig = dsa.sign(msg, q) dsa.assert_as_valid(msg, Q, sig) assert dsa.verify(msg, Q, sig) assert sig == dsa.Sig.parse(sig.serialize()) assert sig == dsa.Sig.parse(sig.serialize().hex()) # https://bitcointalk.org/index.php?topic=285142.40 # Deterministic Usage of DSA and ECDSA (RFC 6979) r = 0x934B1EA10A4B3C1757E2B0C017D0B6143CE3C9A7E6A4A49860D7A6AB210EE3D8 s = 0x2442CE9D2B916064108014783E923EC36B49743E2FFA1C4496F01A512AAFD9E5 assert sig.r == r assert sig.s in (s, sig.ec.n - s) # malleability malleated_sig = dsa.Sig(sig.r, sig.ec.n - sig.s) assert dsa.verify(msg, Q, malleated_sig, lower_s=False) keys = dsa.recover_pub_keys(msg, sig) assert len(keys) == 2 assert Q in keys keys = dsa.recover_pub_keys(msg, sig.serialize()) assert len(keys) == 2 assert Q in keys msg_fake = "Craig Wright".encode() assert not dsa.verify(msg_fake, Q, sig) err_msg = "signature verification failed" with pytest.raises(BTClibRuntimeError, match=err_msg): dsa.assert_as_valid(msg_fake, Q, sig) _, Q_fake = dsa.gen_keys() assert not dsa.verify(msg, Q_fake, sig) err_msg = "signature verification failed" with pytest.raises(BTClibRuntimeError, match=err_msg): dsa.assert_as_valid(msg, Q_fake, sig) err_msg = "not a valid public key: " with pytest.raises(BTClibValueError, match=err_msg): dsa.assert_as_valid(msg, INF, sig) sig_invalid = dsa.Sig(sig.ec.p, sig.s, check_validity=False) assert not dsa.verify(msg, Q, sig_invalid) err_msg = "scalar r not in 1..n-1: " with pytest.raises(BTClibValueError, match=err_msg): dsa.assert_as_valid(msg, Q, sig_invalid) sig_invalid = dsa.Sig(sig.r, sig.ec.p, check_validity=False) assert not dsa.verify(msg, Q, sig_invalid) err_msg = "scalar s not in 1..n-1: " with pytest.raises(BTClibValueError, match=err_msg): dsa.assert_as_valid(msg, Q, sig_invalid) err_msg = "private key not in 1..n-1: " with pytest.raises(BTClibValueError, match=err_msg): dsa.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): dsa.sign_(reduce_to_hlen(msg), q, 0) with pytest.raises(BTClibValueError, match=err_msg): dsa.sign_(reduce_to_hlen(msg), q, sig.ec.n)
def test_sign_input_type() -> None: msg = "Satoshi Nakamoto".encode() q, Q = dsa.gen_keys(0x1) sig = dsa.sign(msg, q) dsa.assert_as_valid(msg, Q, sig) dsa.assert_as_valid(msg, Q, sig.serialize())
from btclib.ecc import bms, dsa, ssa msg = "Hello, I'm Alice!".encode() print("\n", msg.decode()) # ECDSA print("\n ECDSA") dsa_prv, dsa_pub = dsa.gen_keys() print("prv", hex(dsa_prv)) print("pub", hex(dsa_pub[0]), hex(dsa_pub[1])) dsa_sig = dsa.sign(msg, dsa_prv) print("r:", hex(dsa_sig.r)) print("s:", hex(dsa_sig.s)) 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.r)) print("s:", hex(ssa_sig.s)) ssa_valid = ssa.verify(msg, ssa_pub, ssa_sig)