def test_gec() -> None: """GEC 2: Test Vectors for SEC 1, section 2 http://read.pudn.com/downloads168/doc/772358/TestVectorsforSEC%201-gec2.pdf """ # 2.1.1 Scheme setup ec = CURVES["secp160r1"] hf = sha1 # 2.1.2 Key Deployment for U dU = 971761939728640320549601132085879836204587084162 dU, QU = dsa.gen_keys(dU, ec) assert (format(dU, str(ec.n_size) + "x") == "aa374ffc3ce144e6b073307972cb6d57b2a4e982") assert QU == ( 466448783855397898016055842232266600516272889280, 1110706324081757720403272427311003102474457754220, ) assert (bytes_from_point( QU, ec).hex() == "0251b4496fecc406ed0e75a24a3c03206251419dc0") # 2.1.3 Signing Operation for U msg = b"abc" k = 702232148019446860144825009548118511996283736794 lower_s = False sig = dsa.sign_(reduce_to_hlen(msg, hf), dU, k, lower_s, ec, hf) assert sig.r == 0xCE2873E5BE449563391FEB47DDCBA2DC16379191 assert sig.s == 0x3480EC1371A091A464B31CE47DF0CB8AA2D98B54 assert sig.ec == ec # 2.1.4 Verifying Operation for V dsa.assert_as_valid(msg, QU, sig, lower_s, hf) assert dsa.verify(msg, QU, sig, lower_s, hf)
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_ecdh() -> None: ec = CURVES["secp256k1"] hf = sha256 a, A = dsa.gen_keys() # Alice b, B = dsa.gen_keys() # Bob # Alice computes the shared secret using Bob's public key shared_secret_a = mult(a, B) # Bob computes the shared secret using Alice's public key shared_secret_b = mult(b, A) assert shared_secret_a == shared_secret_b assert shared_secret_a == mult(a * b, ec.G) # hash the shared secret to remove weak bits shared_secret_field_element = shared_secret_a[0] z = shared_secret_field_element.to_bytes(ec.p_size, byteorder="big", signed=False) shared_info = b"deadbeef" hf_size = hf().digest_size for size in (hf_size - 1, hf_size, hf_size + 1): shared_key = ansi_x9_63_kdf(z, size, hf, None) assert len(shared_key) == size assert shared_key == diffie_hellman(a, B, size, None, ec, hf) assert shared_key == diffie_hellman(b, A, size, None, ec, hf) shared_key = ansi_x9_63_kdf(z, size, hf, shared_info) assert len(shared_key) == size assert shared_key == diffie_hellman(a, B, size, shared_info, ec, hf) assert shared_key == diffie_hellman(b, A, size, shared_info, ec, hf) max_size = hf_size * (2**32 - 1) size = max_size + 1 with pytest.raises(BTClibValueError, match="cannot derive a key larger than "): ansi_x9_63_kdf(z, size, hf, None)
def test_libsecp256k1() -> None: try: import btclib_libsecp256k1.dsa # pylint: disable=import-outside-toplevel except ImportError: # pragma: no cover pytest.skip() prvkey, Q = dsa.gen_keys(0x1) pubkey_bytes = bytes_from_point(Q) msg = "Satoshi Nakamoto".encode() msg_hash = reduce_to_hlen(msg) libsecp256k1_sig = btclib_libsecp256k1.dsa.sign(msg_hash, prvkey) btclib_sig = dsa.sign_(msg_hash, prvkey) assert btclib_libsecp256k1.dsa.verify(msg_hash, pubkey_bytes, btclib_sig.serialize()) assert dsa.verify(msg, prvkey, libsecp256k1_sig)
def test_sign_to_contract_dsa() -> None: commit_msg = "to be committed".encode() msg = "to be signed".encode() lower_s = True for hf in (sha256, sha1): for ec in (secp256k1, CURVES["secp160r1"]): prv_key, pub_key = dsa.gen_keys(ec=ec) nonce = None dsa_sig, receipt = dsa_commit_sign(commit_msg, msg, prv_key, nonce, ec, hf) dsa.assert_as_valid(msg, pub_key, dsa_sig, lower_s, hf) assert dsa_verify_commit(commit_msg, receipt, msg, pub_key, dsa_sig, lower_s, hf) nonce = 1 + secrets.randbelow(ec.n - 1) dsa_sig, R = dsa_commit_sign(commit_msg, msg, prv_key, nonce, ec, hf) dsa.assert_as_valid(msg, pub_key, dsa_sig, lower_s, hf) assert dsa_verify_commit(commit_msg, R, msg, pub_key, dsa_sig, lower_s, hf)
def test_borromean() -> None: nring = 4 # FIXME randomize; minimum number of rings? ring_sizes = [1 + secrets.randbelow(7) for _ in range(nring)] sign_key_idx = [secrets.randbelow(size) for size in ring_sizes] pubk_rings: Dict[int, List[Point]] = defaultdict(list) sign_keys: List[int] = [] for i in range(nring): for j in range(ring_sizes[i]): priv_key, pub_key = dsa.gen_keys() pubk_rings[i].append(pub_key) if j == sign_key_idx[i]: sign_keys.append(priv_key) msg = "Borromean ring signature".encode() sig = borromean.sign(msg, list(range(1, 5)), sign_key_idx, sign_keys, pubk_rings) borromean.assert_as_valid(msg, sig[0], sig[1], pubk_rings) assert borromean.verify(msg, sig[0], sig[1], pubk_rings) assert not borromean.verify("another message", sig[0], sig[1], pubk_rings) assert not borromean.verify(0, sig[0], sig[1], pubk_rings) # type: ignore
def test_crack_prv_key() -> None: ec = CURVES["secp256k1"] q, _ = dsa.gen_keys(1) k = 1 + secrets.randbelow(ec.n - 1) msg1 = "Paolo is afraid of ephemeral random numbers".encode() m_1 = reduce_to_hlen(msg1) sig1 = dsa.sign_(m_1, q, k) msg2 = "and Paolo is right to be afraid".encode() m_2 = reduce_to_hlen(msg2) sig2 = dsa.sign_(m_2, q, k) q_cracked, k_cracked = dsa.crack_prv_key(msg1, sig1.serialize(), msg2, sig2) # if the lower_s convention has changed only one of s1 and s2 sig2 = dsa.Sig(sig2.r, ec.n - sig2.s) qc2, kc2 = dsa.crack_prv_key(msg1, sig1, msg2, sig2.serialize()) assert (q == q_cracked and k in (k_cracked, ec.n - k_cracked)) or ( q == qc2 and k in (kc2, ec.n - kc2)) with pytest.raises(BTClibValueError, match="not the same r in signatures"): dsa.crack_prv_key(msg1, sig1, msg2, dsa.Sig(16, sig1.s)) with pytest.raises(BTClibValueError, match="identical signatures"): dsa.crack_prv_key(msg1, sig1, msg1, sig1) a = ec._a # pylint: disable=protected-access b = ec._b # pylint: disable=protected-access alt_ec = Curve(ec.p, a, b, ec.double_aff(ec.G), ec.n, ec.cofactor) sig = dsa.Sig(sig1.r, sig1.s, alt_ec) with pytest.raises(BTClibValueError, match="not the same curve in signatures"): dsa.crack_prv_key(msg1, sig, msg2, sig2)
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)
def test_exceptions() -> None: # from creator example psbt_str = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAAAAAA=" psbt = Psbt.b64decode(psbt_str) psbt.outputs[0].redeem_script = "bad script" # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.inputs[0].witness_script = "bad script" # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.outputs[0].unknown = {"bad key": b""} # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.outputs[0].unknown = {b"deadbeef": "bad value"} # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.inputs[0].sig_hash_type = 101 with pytest.raises(BTClibValueError, match="invalid sign_hash type: "): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.inputs[0].final_script_sig = "bad script" # type: ignore with pytest.raises(TypeError): psbt.serialize() psbt = Psbt.b64decode(psbt_str) _, Q = dsa.gen_keys() pub_key = sec_point.bytes_from_point(Q) r = s = int.from_bytes(bytes.fromhex("FF" * 32), byteorder="big", signed=False) sig_bytes = der.Sig(r, s, check_validity=False).serialize(check_validity=False) psbt.inputs[0].partial_sigs = {pub_key: sig_bytes} with pytest.raises(BTClibValueError, match="invalid partial signature: "): psbt.serialize() pub_key = bytes.fromhex("02" + 31 * "00" + "07") psbt.inputs[0].partial_sigs = {pub_key: sig_bytes} with pytest.raises(BTClibValueError, match="invalid partial signature pub_key: "): psbt.serialize() psbt = Psbt.b64decode(psbt_str) err_msg = "invalid version: " psbt.version = -1 with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt.version = 0xFFFFFFFF + 1 with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt.version = 1 # TODO: add to test vectors with pytest.raises(BTClibValueError, match="invalid non-zero version: "): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt_bin = psbt.serialize() psbt_bin = psbt_bin.replace(PSBT_SEPARATOR, PSBT_DELIMITER) # TODO: add to test vectors with pytest.raises(BTClibValueError, match="malformed psbt: missing separator"): Psbt.parse(psbt_bin) psbt = Psbt.b64decode(psbt_str) psbt.inputs.pop() err_msg = "mismatched number of psb.tx.vin and psb.inputs: " # TODO: add to test vectors with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.tx.vin[0].script_witness = Witness([b""]) err_msg = "non empty script_sig or witness" # TODO: add to test vectors with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt = Psbt.b64decode(psbt_str) psbt.outputs.pop() err_msg = "mismatched number of psb.tx.vout and psbt.outputs: " # TODO: add to test vectors with pytest.raises(BTClibValueError, match=err_msg): psbt.serialize() psbt_str = "cHNidP8BAJoCAAAAAljoeiG1ba8MI76OcHBFbDNvfLqlyHV5JPVFiHuyq911AAAAAAD/////g40EJ9DsZQpoqka7CwmK6kQiwHGyyng1Kgd5WdB86h0BAAAAAP////8CcKrwCAAAAAAWABTYXCtx0AYLCcmIauuBXlCZHdoSTQDh9QUAAAAAFgAUAK6pouXw+HaliN9VRuh0LR2HAI8AAAAAAAEAuwIAAAABqtc5MQGL0l+ErkALaISL4J23BurCrBgpi6vucatlb4sAAAAASEcwRAIgWPb8fGoz4bMVSNSByCbAFb0wE1qtQs1neQ2rZtKtJDsCIEoc7SYExnNbY5PltBaR3XiwDwxZQvufdRhW+qk4FX26Af7///8CgPD6AgAAAAAXqRQPuUY0IWlrgsgzryQceMF9295JNIfQ8gonAQAAABepFCnKdPigj4GZlCgYXJe12FLkBj9hh2UAAAAiAgKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgf0cwRAIgdAGK1BgAl7hzMjwAFXILNoTMgSOJEEjn282bVa1nnJkCIHPTabdA4+tT3O+jOCPIBwUUylWn3ZVE8VfBZ5EyYRGMASICAtq2H/SaFNtqfQKwzR+7ePxLGDErW05U2uTbovv+9TbXSDBFAiEA9hA4swjcHahlo0hSdG8BV3KTQgjG0kRUOTzZm98iF3cCIAVuZ1pnWm0KArhbFOXikHTYolqbV2C+ooFvZhkQoAbqAQEDBAEAAAABBEdSIQKVg785rgpgl0etGZrd1jT6YQhVnWxc05tMIYPxq5bgfyEC2rYf9JoU22p9ArDNH7t4/EsYMStbTlTa5Nui+/71NtdSriIGApWDvzmuCmCXR60Zmt3WNPphCFWdbFzTm0whg/GrluB/ENkMak8AAACAAAAAgAAAAIAiBgLath/0mhTban0CsM0fu3j8SxgxK1tOVNrk26L7/vU21xDZDGpPAAAAgAAAAIABAACAAAEBIADC6wsAAAAAF6kUt/X69A49QKWkWbHbNTXyty+pIeiHIgIDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtxHMEQCIGLrelVhB6fHP0WsSrWh3d9vcHX7EnWWmn84Pv/3hLyyAiAMBdu3Rw2/LwhVfdNWxzJcHtMJE+mWzThAlF2xIijaXwEiAgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8Oc0cwRAIgZfRbpZmLWaJ//hp77QFq8fH5DVSzqo90UKpfVqJRA70CIH9yRwOtHtuWaAsoS1bU/8uI9/t1nqu+CKow8puFE4PSAQEDBAEAAAABBCIAIIwjUxc3Q7WV37Sge3K6jkLjeX2nTof+fZ10l+OyAokDAQVHUiEDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwhAjrdkE89bc9Z3bkGsN7iNSm3/7ntUOXoYVGSaGAiHw5zUq4iBgI63ZBPPW3PWd25BrDe4jUpt/+57VDl6GFRkmhgIh8OcxDZDGpPAAAAgAAAAIADAACAIgYDCJ3BDHrG21T5EymvYXMz2ziM6tDCMfcjN50bmQMLAtwQ2QxqTwAAAIAAAACAAgAAgAAiAgOppMN/WZbTqiXbrGtXCvBlA5RJKUJGCzVHU+2e7KWHcRDZDGpPAAAAgAAAAIAEAACAACICAn9jmXV9Lv9VoTatAsaEsYOLZVbl8bazQoKpS2tQBRCWENkMak8AAACAAAAAgAUAAIAA" psbt = Psbt.b64decode(psbt_str) psbt.tx.vin[0].prev_out.tx_id, psbt.tx.vin[1].prev_out.tx_id = ( psbt.tx.vin[1].prev_out.tx_id, psbt.tx.vin[0].prev_out.tx_id, ) err_msg = "mismatched non-witness utxo / outpoint tx_id" with pytest.raises(BTClibValueError, match=err_msg): psbt.assert_valid()