def test_crack_prvkey() -> None: ec = CURVES["secp256k1"] # FIXME: make it random q = 0x17E14A7B6A307F426A94F8114701E7C8E774E7F9A47E2C2035DB29A206321725 msg1 = "Paolo is afraid of ephemeral random numbers" m_1 = reduce_to_hlen(msg1) k = _rfc6979(m_1, q) sig1 = dsa._sign(m_1, q, k) msg2 = "and Paolo is right to be afraid" m_2 = reduce_to_hlen(msg2) # reuse same k sig2 = dsa._sign(m_2, q, k) qc, kc = dsa.crack_prvkey(msg1, sig1, msg2, sig2) assert q in (qc, ec.n - qc) assert q == qc assert k in (kc, ec.n - kc) # assert k == kc with pytest.raises(BTClibValueError, match="not the same r in signatures"): dsa.crack_prvkey(msg1, sig1, msg2, (16, sig1[1])) with pytest.raises(BTClibValueError, match="identical signatures"): dsa.crack_prvkey(msg1, sig1, msg1, sig1)
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_prv_key() -> None: q, x_Q = ssa.gen_keys(19) # remove any randomness msg1 = "Paolo is afraid of ephemeral random numbers".encode() m_1 = reduce_to_hlen(msg1) k = ssa.det_nonce_(m_1, q, aux=32 * b"\x01") # remove any randomness sig1 = ssa.sign_(m_1, q, k) msg2 = "and Paolo is right to be afraid".encode() m_2 = reduce_to_hlen(msg2) # reuse same k sig2 = ssa.sign_(m_2, q, k) qc, kc = ssa.crack_prv_key(msg1, sig1, msg2, sig2, x_Q) assert q == qc assert k in (kc, sig1.ec.n - kc) qc, kc = ssa.crack_prv_key(msg1, sig1.serialize(), msg2, sig2.serialize(), x_Q) assert q == qc assert k in (kc, sig1.ec.n - kc) sig = ssa.Sig(16, sig2.s, sig2.ec) with pytest.raises(BTClibValueError, match="not the same r in signatures"): ssa.crack_prv_key_(m_1, sig1, m_2, sig, x_Q) with pytest.raises(BTClibValueError, match="identical signatures"): ssa.crack_prv_key_(m_1, sig1, m_1, sig1, x_Q) sig = ssa.Sig(sig1.r, sig1.s, CURVES["secp256r1"]) with pytest.raises(BTClibValueError, match="not the same curve in signatures"): ssa.crack_prv_key_(m_1, sig, m_2, sig2, x_Q)
def ssa_verify_commit( commit: Octets, receipt: Point, msg: Octets, pub_key: ssa.BIP340PubKey, sig: ssa.Sig, hf: HashF = sha256, ) -> bool: "Open the commitment associated to an EC SSA signature." commit_hash = reduce_to_hlen(commit, hf) msg_hash = reduce_to_hlen(msg, hf) return ssa_verify_commit_(commit_hash, receipt, msg_hash, pub_key, sig, hf)
def crack_prv_key( msg1: Octets, sig1: Union[Sig, Octets], msg2: Octets, sig2: Union[Sig, Octets], hf: HashF = sha256, ) -> Tuple[int, int]: msg_hash1 = reduce_to_hlen(msg1, hf) msg_hash2 = reduce_to_hlen(msg2, hf) return crack_prv_key_(msg_hash1, sig1, msg_hash2, sig2, hf)
def ssa_commit_sign( commit: Octets, msg: Octets, prv_key: PrvKey, nonce: Optional[PrvKey] = None, ec: Curve = secp256k1, hf: HashF = sha256, ) -> Tuple[ssa.Sig, Point]: "Include a commitment inside an EC SSA signature." commit_hash = reduce_to_hlen(commit, hf) msg_hash = reduce_to_hlen(msg, hf) return ssa_commit_sign_(commit_hash, msg_hash, prv_key, nonce, ec, hf)
def dsa_verify_commit( commit: Octets, receipt: Point, msg: Octets, key: dsa.Key, sig: dsa.Sig, lower_s: bool = True, hf: HashF = sha256, ) -> bool: "Open the commitment associated to an EC DSA signature." commit_hash = reduce_to_hlen(commit, hf) msg_hash = reduce_to_hlen(msg, hf) return dsa_verify_commit_(commit_hash, receipt, msg_hash, key, sig, lower_s, hf)
def sign( msg: Octets, prv_key: PrvKey, nonce: Optional[PrvKey] = None, ec: Curve = secp256k1, hf: HashF = sha256, ) -> Sig: """Sign message according to BIP340 signature algorithm. The message msg is first processed by hf, yielding the value msg_hash = hf(msg), a sequence of bits of length *hf_len*. Normally, hf is chosen such that its output length *hf_len* is roughly equal to *nlen*, the bit-length of the group order *n*, since the overall security of the signature scheme will depend on the smallest of *hf_len* and *nlen*; however, ECSSA supports all combinations of *hf_len* and *nlen*. The BIP340 deterministic nonce (not RFC6979) is used. """ msg_hash = reduce_to_hlen(msg, hf) return sign_(msg_hash, prv_key, nonce, ec, 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 assert_as_valid(msg: Octets, Q: BIP340PubKey, sig: Union[Sig, Octets], hf: HashF = sha256) -> None: msg_hash = reduce_to_hlen(msg, hf) assert_as_valid_(msg_hash, Q, sig, hf)
def verify( msg: Octets, Q: BIP340PubKey, sig: Union[Sig, Octets], hf: HashF = sha256 ) -> bool: "Verify the BIP340 signature of the provided message." msg_hash = reduce_to_hlen(msg, hf) return verify_(msg_hash, Q, sig, hf)
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 sign( msg: Octets, prv_key: PrvKey, nonce: Optional[PrvKey] = None, lower_s: bool = True, ec: Curve = secp256k1, hf: HashF = sha256, ) -> Sig: """ECDSA signature with canonical low-s preference. Implemented according to SEC 1 v.2 The message msg is first processed by hf, yielding the value msg_hash = hf(msg), a sequence of bits of length *hf_len*. Normally, hf is chosen such that its output length *hf_len* is roughly equal to *nlen*, the bit-length of the group order *n*, since the overall security of the signature scheme will depend on the smallest of *hf_len* and *nlen*; however, the ECDSA standard supports all combinations of *hf_len* and *nlen*. RFC6979 is used for deterministic nonce. See https://tools.ietf.org/html/rfc6979#section-3.2 """ msg_hash = reduce_to_hlen(msg, hf) return sign_(msg_hash, prv_key, nonce, lower_s, ec, hf)
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 assert_batch_as_valid( ms: Sequence[Octets], Qs: Sequence[BIP340PubKey], sigs: Sequence[Sig], hf: HashF = sha256, ) -> None: m_hashes = [reduce_to_hlen(msg, hf) for msg in ms] return assert_batch_as_valid_(m_hashes, Qs, sigs, hf)
def batch_verify( ms: Sequence[Octets], Qs: Sequence[BIP340PubKey], sigs: Sequence[Sig], hf: HashF = sha256, ) -> bool: "Batch verification of BIP340 signatures." m_hashes = [reduce_to_hlen(msg, hf) for msg in ms] return batch_verify_(m_hashes, Qs, sigs, hf)
def verify( msg: Octets, key: Key, sig: Union[Sig, Octets], lower_s: bool = True, hf: HashF = sha256, ) -> bool: """ECDSA signature verification (SEC 1 v.2 section 4.1.4).""" msg_hash = reduce_to_hlen(msg, hf) return verify_(msg_hash, key, sig, lower_s, hf)
def recover_pub_keys(msg: Octets, sig: Union[Sig, Octets], lower_s: bool = True, hf: HashF = sha256) -> List[Point]: """ECDSA public key recovery (SEC 1 v.2 section 4.1.6). See also: https://crypto.stackexchange.com/questions/18105/how-does-recovering-the-public-key-from-an-ecdsa-signature-work/18106#18106 """ msg_hash = reduce_to_hlen(msg, hf) return recover_pub_keys_(msg_hash, sig, lower_s, hf)
def assert_as_valid( msg: Octets, key: Key, sig: Union[Sig, Octets], lower_s: bool = True, hf: HashF = sha256, ) -> None: # Private function for test/dev purposes # It raises Errors, while verify should always return True or False msg_hash = reduce_to_hlen(msg, hf) assert_as_valid_(msg_hash, key, sig, lower_s, hf)
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_libsecp256k1() -> None: try: import btclib_libsecp256k1.ssa # pylint: disable=import-outside-toplevel except ImportError: # pragma: no cover pytest.skip() prvkey, X_Q = ssa.gen_keys(0x1) pubkey_bytes = X_Q.to_bytes(32, "big") msg = "Satoshi Nakamoto".encode() msg_hash = reduce_to_hlen(msg) libsecp256k1_sig = btclib_libsecp256k1.ssa.sign(msg_hash, prvkey) btclib_sig = ssa.sign_(msg_hash, prvkey) assert btclib_libsecp256k1.ssa.verify(msg_hash, pubkey_bytes, btclib_sig.serialize()) assert ssa.verify(msg, X_Q, libsecp256k1_sig)
def test_rfc6979_tv() -> None: fname = "rfc6979.json" filename = path.join(path.dirname(__file__), "test_data", fname) with open(filename, "r") as file_: test_dict = json.load(file_) for ec_name in test_dict: ec = CURVES[ec_name] test_vectors = test_dict[ec_name] for x, x_U, y_U, hf, msg, k, r, s in test_vectors: x = int(x, 16) m = reduce_to_hlen(msg, hf=getattr(hashlib, hf)) # test RFC6979 implementation k2 = _rfc6979(m, x, ec, getattr(hashlib, hf)) assert k == hex(k2) # test RFC6979 usage in DSA sig = dsa._sign(m, x, k2, low_s=False, ec=ec, hf=getattr(hashlib, hf)) assert r == hex(sig[0]) assert s == hex(sig[1]) # test that RFC6979 is the default nonce for DSA sig = dsa._sign(m, x, None, low_s=False, ec=ec, hf=getattr(hashlib, hf)) assert r == hex(sig[0]) assert s == hex(sig[1]) # test key-pair coherence U = mult(x, ec.G, ec) assert int(x_U, 16), int(y_U, 16) == U # test signature validity dsa.assert_as_valid(msg, U, sig, ec, getattr(hashlib, hf))
def test_rfc6979_tv() -> None: fname = "rfc6979.json" filename = path.join(path.dirname(__file__), "_data", fname) with open(filename, "r") as file_: test_dict = json.load(file_) lower_s = False for ec_name in test_dict: ec = CURVES[ec_name] test_vectors = test_dict[ec_name] for x, x_U, y_U, hf, msg, k, r, s in test_vectors: x = int(x, 16) msg = msg.encode() m = reduce_to_hlen(msg, hf=getattr(hashlib, hf)) # test RFC6979 implementation k2 = rfc6979_(m, x, ec, getattr(hashlib, hf)) assert int(k, 16) == k2 # test RFC6979 usage in DSA sig = dsa.sign_(m, x, k2, lower_s, ec=ec, hf=getattr(hashlib, hf)) assert int(r, 16) == sig.r assert int(s, 16) == sig.s # test that RFC6979 is the default nonce for DSA sig = dsa.sign_(m, x, None, lower_s, ec=ec, hf=getattr(hashlib, hf)) assert int(r, 16) == sig.r assert int(s, 16) == sig.s # test key-pair coherence U = mult(x, ec.G, ec) assert int(x_U, 16), int(y_U, 16) == U # test signature validity dsa.assert_as_valid(msg, U, sig, lower_s, getattr(hashlib, hf))
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_signature() -> None: msg = "Satoshi Nakamoto".encode() 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.Sig.parse(sig.serialize()) assert sig == ssa.Sig.parse(sig.serialize().hex()) msg_fake = "Craig Wright".encode() 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(BTClibTypeError, match=err_msg): ssa.assert_as_valid(msg, INF, sig) # type: ignore with pytest.raises(BTClibTypeError, match=err_msg): ssa.point_from_bip340pub_key(INF) # type: ignore sig_invalid = ssa.Sig(sig.ec.p, sig.s, check_validity=False) 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 = ssa.Sig(sig.r, sig.ec.p, check_validity=False) 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_bytes = reduce_to_hlen(msg, hf) err_msg = "invalid size: 31 bytes instead of 32" with pytest.raises(BTClibValueError, match=err_msg): ssa.assert_as_valid_(m_bytes[:31], x_Q, sig) with pytest.raises(BTClibValueError, match=err_msg): ssa.sign_(m_bytes[:31], 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_(m_bytes, q, 0) with pytest.raises(BTClibValueError, match=err_msg): ssa.sign_(m_bytes, q, sig.ec.n)
def test_signature() -> None: ec = CURVES["secp256k1"] msg = "Satoshi Nakamoto" q, Q = dsa.gen_keys(0x1) sig = dsa.sign(msg, q) assert dsa.verify(msg, Q, sig) assert sig == dsa.deserialize(sig) # https://bitcointalk.org/index.php?topic=285142.40 # Deterministic Usage of DSA and ECDSA (RFC 6979) exp_sig = ( 0x934B1EA10A4B3C1757E2B0C017D0B6143CE3C9A7E6A4A49860D7A6AB210EE3D8, 0x2442CE9D2B916064108014783E923EC36B49743E2FFA1C4496F01A512AAFD9E5, ) r, s = sig assert sig[0] == exp_sig[0] assert sig[1] in (exp_sig[1], ec.n - exp_sig[1]) dsa.assert_as_valid(msg, Q, sig) dsa.assert_as_valid(msg, Q, dsa.serialize(*sig)) dsa.assert_as_valid(msg, Q, dsa.serialize(*sig).hex()) # malleability malleated_sig = (r, ec.n - s) assert dsa.verify(msg, Q, malleated_sig) keys = dsa.recover_pubkeys(msg, sig) assert len(keys) == 2 assert Q in keys msg_fake = "Craig Wright" 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_fake = (sig[0], sig[1], sig[1]) assert not dsa.verify(msg, Q, sig_fake) # type: ignore err_msg = "too many values to unpack " with pytest.raises(ValueError, match=err_msg): dsa.assert_as_valid(msg, Q, sig_fake) # type: ignore sig_invalid = ec.p, sig[1] 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 = sig[0], ec.p 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, ec.n)
def test_threshold() -> None: "testing 2-of-3 threshold signature (Pedersen secret sharing)" ec = CURVES["secp256k1"] # parameters m = 2 H = second_generator(ec) # FIRST PHASE: key pair generation ################################### # 1.1 signer one acting as the dealer commits1: List[Point] = [] q1, _ = ssa.gen_keys() q1_prime, _ = ssa.gen_keys() commits1.append(double_mult(q1_prime, H, q1, ec.G)) # sharing polynomials f1 = [q1] f1_prime = [q1_prime] for i in range(1, m): f1.append(ssa.gen_keys()[0]) f1_prime.append(ssa.gen_keys()[0]) commits1.append(double_mult(f1_prime[i], H, f1[i], ec.G)) # shares of the secret alpha12 = 0 # share of q1 belonging to signer two alpha12_prime = 0 alpha13 = 0 # share of q1 belonging to signer three alpha13_prime = 0 for i in range(m): alpha12 += (f1[i] * pow(2, i)) % ec.n alpha12_prime += (f1_prime[i] * pow(2, i)) % ec.n alpha13 += (f1[i] * pow(3, i)) % ec.n alpha13_prime += (f1_prime[i] * pow(3, i)) % ec.n # signer two verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(2, i), commits1[i])) t = double_mult(alpha12_prime, H, alpha12, ec.G) assert t == RHS, "signer one is cheating" # signer three verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(3, i), commits1[i])) t = double_mult(alpha13_prime, H, alpha13, ec.G) assert t == RHS, "signer one is cheating" # 1.2 signer two acting as the dealer commits2: List[Point] = [] q2, _ = ssa.gen_keys() q2_prime, _ = ssa.gen_keys() commits2.append(double_mult(q2_prime, H, q2, ec.G)) # sharing polynomials f2 = [q2] f2_prime = [q2_prime] for i in range(1, m): f2.append(ssa.gen_keys()[0]) f2_prime.append(ssa.gen_keys()[0]) commits2.append(double_mult(f2_prime[i], H, f2[i], ec.G)) # shares of the secret alpha21 = 0 # share of q2 belonging to signer one alpha21_prime = 0 alpha23 = 0 # share of q2 belonging to signer three alpha23_prime = 0 for i in range(m): alpha21 += (f2[i] * pow(1, i)) % ec.n alpha21_prime += (f2_prime[i] * pow(1, i)) % ec.n alpha23 += (f2[i] * pow(3, i)) % ec.n alpha23_prime += (f2_prime[i] * pow(3, i)) % ec.n # signer one verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(1, i), commits2[i])) t = double_mult(alpha21_prime, H, alpha21, ec.G) assert t == RHS, "signer two is cheating" # signer three verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(3, i), commits2[i])) t = double_mult(alpha23_prime, H, alpha23, ec.G) assert t == RHS, "signer two is cheating" # 1.3 signer three acting as the dealer commits3: List[Point] = [] q3, _ = ssa.gen_keys() q3_prime, _ = ssa.gen_keys() commits3.append(double_mult(q3_prime, H, q3, ec.G)) # sharing polynomials f3 = [q3] f3_prime = [q3_prime] for i in range(1, m): f3.append(ssa.gen_keys()[0]) f3_prime.append(ssa.gen_keys()[0]) commits3.append(double_mult(f3_prime[i], H, f3[i], ec.G)) # shares of the secret alpha31 = 0 # share of q3 belonging to signer one alpha31_prime = 0 alpha32 = 0 # share of q3 belonging to signer two alpha32_prime = 0 for i in range(m): alpha31 += (f3[i] * pow(1, i)) % ec.n alpha31_prime += (f3_prime[i] * pow(1, i)) % ec.n alpha32 += (f3[i] * pow(2, i)) % ec.n alpha32_prime += (f3_prime[i] * pow(2, i)) % ec.n # signer one verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(1, i), commits3[i])) t = double_mult(alpha31_prime, H, alpha31, ec.G) assert t == RHS, "signer three is cheating" # signer two verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(2, i), commits3[i])) t = double_mult(alpha32_prime, H, alpha32, ec.G) assert t == RHS, "signer three is cheating" # shares of the secret key q = q1 + q2 + q3 alpha1 = (alpha21 + alpha31) % ec.n alpha2 = (alpha12 + alpha32) % ec.n alpha3 = (alpha13 + alpha23) % ec.n for i in range(m): alpha1 += (f1[i] * pow(1, i)) % ec.n alpha2 += (f2[i] * pow(2, i)) % ec.n alpha3 += (f3[i] * pow(3, i)) % ec.n # 1.4 it's time to recover the public key # each participant i = 1, 2, 3 shares Qi as follows # Q = Q1 + Q2 + Q3 = (q1 + q2 + q3) G A1: List[Point] = [] A2: List[Point] = [] A3: List[Point] = [] for i in range(m): A1.append(mult(f1[i])) A2.append(mult(f2[i])) A3.append(mult(f3[i])) # signer one checks others' values RHS2 = INF RHS3 = INF for i in range(m): RHS2 = ec.add(RHS2, mult(pow(1, i), A2[i])) RHS3 = ec.add(RHS3, mult(pow(1, i), A3[i])) assert mult(alpha21) == RHS2, "signer two is cheating" assert mult(alpha31) == RHS3, "signer three is cheating" # signer two checks others' values RHS1 = INF RHS3 = INF for i in range(m): RHS1 = ec.add(RHS1, mult(pow(2, i), A1[i])) RHS3 = ec.add(RHS3, mult(pow(2, i), A3[i])) assert mult(alpha12) == RHS1, "signer one is cheating" assert mult(alpha32) == RHS3, "signer three is cheating" # signer three checks others' values RHS1 = INF RHS2 = INF for i in range(m): RHS1 = ec.add(RHS1, mult(pow(3, i), A1[i])) RHS2 = ec.add(RHS2, mult(pow(3, i), A2[i])) assert mult(alpha13) == RHS1, "signer one is cheating" assert mult(alpha23) == RHS2, "signer two is cheating" # commitment at the global sharing polynomial A: List[Point] = [] for i in range(m): A.append(ec.add(A1[i], ec.add(A2[i], A3[i]))) # aggregated public key Q = A[0] if Q[1] % 2: # print('Q has been negated') A[1] = ec.negate(A[1]) # pragma: no cover alpha1 = ec.n - alpha1 # pragma: no cover alpha2 = ec.n - alpha2 # pragma: no cover alpha3 = ec.n - alpha3 # pragma: no cover Q = ec.negate(Q) # pragma: no cover # SECOND PHASE: generation of the nonces' pair ###################### # Assume signer one and three want to sign msg = "message to sign".encode() msg_hash = reduce_to_hlen(msg, hf) # 2.1 signer one acting as the dealer commits1 = [] k1 = ssa.det_nonce_(msg_hash, q1, None, ec, hf) k1_prime = ssa.det_nonce_(msg_hash, q1_prime, None, ec, hf) commits1.append(double_mult(k1_prime, H, k1, ec.G)) # sharing polynomials f1 = [k1] f1_prime = [k1_prime] for i in range(1, m): f1.append(ssa.gen_keys()[0]) f1_prime.append(ssa.gen_keys()[0]) commits1.append(double_mult(f1_prime[i], H, f1[i], ec.G)) # shares of the secret beta13 = 0 # share of k1 belonging to signer three beta13_prime = 0 for i in range(m): beta13 += (f1[i] * pow(3, i)) % ec.n beta13_prime += (f1_prime[i] * pow(3, i)) % ec.n # signer three verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(3, i), commits1[i])) t = double_mult(beta13_prime, H, beta13, ec.G) assert t == RHS, "signer one is cheating" # 2.2 signer three acting as the dealer commits3 = [] k3 = ssa.det_nonce_(msg_hash, q3, None, ec, hf) k3_prime = ssa.det_nonce_(msg_hash, q3_prime, None, ec, hf) commits3.append(double_mult(k3_prime, H, k3, ec.G)) # sharing polynomials f3 = [k3] f3_prime = [k3_prime] for i in range(1, m): f3.append(ssa.gen_keys()[0]) f3_prime.append(ssa.gen_keys()[0]) commits3.append(double_mult(f3_prime[i], H, f3[i], ec.G)) # shares of the secret beta31 = 0 # share of k3 belonging to signer one beta31_prime = 0 for i in range(m): beta31 += (f3[i] * pow(1, i)) % ec.n beta31_prime += (f3_prime[i] * pow(1, i)) % ec.n # signer one verifies consistency of his share RHS = INF for i in range(m): RHS = ec.add(RHS, mult(pow(1, i), commits3[i])) t = double_mult(beta31_prime, H, beta31, ec.G) assert t == RHS, "signer three is cheating" # 2.3 shares of the secret nonce beta1 = beta31 % ec.n beta3 = beta13 % ec.n for i in range(m): beta1 += (f1[i] * pow(1, i)) % ec.n beta3 += (f3[i] * pow(3, i)) % ec.n # 2.4 it's time to recover the public nonce # each participant i = 1, 3 shares Qi as follows B1: List[Point] = [] B3: List[Point] = [] for i in range(m): B1.append(mult(f1[i])) B3.append(mult(f3[i])) # signer one checks values from signer three RHS3 = INF for i in range(m): RHS3 = ec.add(RHS3, mult(pow(1, i), B3[i])) assert mult(beta31) == RHS3, "signer three is cheating" # signer three checks values from signer one RHS1 = INF for i in range(m): RHS1 = ec.add(RHS1, mult(pow(3, i), B1[i])) assert mult(beta13) == RHS1, "signer one is cheating" # commitment at the global sharing polynomial B: List[Point] = [] for i in range(m): B.append(ec.add(B1[i], B3[i])) # aggregated public nonce K = B[0] if K[1] % 2: # print('K has been negated') B[1] = ec.negate(B[1]) # pragma: no cover beta1 = ec.n - beta1 # pragma: no cover beta3 = ec.n - beta3 # pragma: no cover K = ec.negate(K) # pragma: no cover # PHASE THREE: signature generation ### # partial signatures e = ssa.challenge_(msg_hash, Q[0], K[0], ec, hf) gamma1 = (beta1 + e * alpha1) % ec.n gamma3 = (beta3 + e * alpha3) % ec.n # each participant verifies the other partial signatures # signer one RHS3 = ec.add(K, mult(e, Q)) for i in range(1, m): temp = double_mult(pow(3, i), B[i], e * pow(3, i), A[i]) RHS3 = ec.add(RHS3, temp) assert mult(gamma3) == RHS3, "signer three is cheating" # signer three RHS1 = ec.add(K, mult(e, Q)) for i in range(1, m): temp = double_mult(pow(1, i), B[i], e * pow(1, i), A[i]) RHS1 = ec.add(RHS1, temp) assert mult(gamma1) == RHS1, "signer one is cheating" # PHASE FOUR: aggregating the signature ### omega1 = 3 * mod_inv(3 - 1, ec.n) % ec.n omega3 = 1 * mod_inv(1 - 3, ec.n) % ec.n sigma = (gamma1 * omega1 + gamma3 * omega3) % ec.n sig = ssa.Sig(K[0], sigma, ec) assert ssa.verify_(msg_hash, Q[0], sig) # ADDITIONAL PHASE: reconstruction of the private key ### secret = (omega1 * alpha1 + omega3 * alpha3) % ec.n assert (q1 + q2 + q3) % ec.n in (secret, ec.n - secret)
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)