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_low_cardinality(self): """test low-cardinality curves for all msg/key pairs.""" # ec.n has to be prime to sign prime = [11, 13, 17, 19] for ec in low_card_curves: # only low card or it would take forever if ec._p in prime: # only few curves or it would take too long for q in range(1, ec.n): # all possible private keys PJ = _mult_jac(q, ec.GJ, ec) # public key for e in range(ec.n): # all possible int from hash for k in range(1, ec.n): # all possible ephemeral keys RJ = _mult_jac(k, ec.GJ, ec) Rx = (RJ[0] * mod_inv(RJ[2] * RJ[2], ec._p)) % ec._p r = Rx % ec.n s = mod_inv(k, ec.n) * (e + q * r) % ec.n # bitcoin canonical 'low-s' encoding for ECDSA if s > ec.n / 2: s = ec.n - s if r == 0 or s == 0: self.assertRaises(ValueError, dsa._sign, e, q, k, ec) continue sig = dsa._sign(e, q, k, ec) self.assertEqual((r, s), sig) # valid signature must pass verification self.assertIsNone(dsa._verhlp(e, PJ, r, s, ec)) JacobianKeys = dsa._recover_pubkeys(e, r, s, ec) Qs = [ ec._aff_from_jac(key) for key in JacobianKeys ] self.assertIn(ec._aff_from_jac(PJ), Qs)
def test_low_cardinality(): """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: for q in range(1, ec.n): # all possible private keys QJ = _mult_jac(q, ec.GJ, ec) # public key for k in range(1, ec.n): # all possible ephemeral keys RJ = _mult_jac(k, ec.GJ, ec) r = ec._x_aff_from_jac(RJ) % ec.n k_inv = mod_inv(k, ec.n) for e in range(ec.n): # all possible challenges s = k_inv * (e + q * r) % ec.n # bitcoin canonical 'low-s' encoding for ECDSA if s > ec.n / 2: s = ec.n - s if r == 0 or s == 0: err_msg = "failed to sign: " with pytest.raises(RuntimeError, match=err_msg): dsa._sign(e, q, k, ec) continue sig = dsa._sign(e, q, k, ec) assert (r, s) == sig # valid signature must pass verification dsa._assert_as_valid(e, QJ, r, s, ec) JacobianKeys = dsa._recover_pubkeys(e, r, s, ec) # FIXME speed this up Qs = [ec._aff_from_jac(key) for key in JacobianKeys] assert ec._aff_from_jac(QJ) in Qs assert len(JacobianKeys) in (2, 4)
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_low_cardinality(self): """test all msg/key pairs of low cardinality elliptic curves""" # ec.n has to be prime to sign prime = [11, 13, 17, 19] for ec in low_card_curves: # only low card or it would take forever if ec._p in prime: # only few curves or it would take too long for d in range(ec.n): # all possible private keys if d == 0: # invalid prvkey = 0 self.assertRaises(ValueError, dsa._sign, ec, 1, d, 1) continue P = mult(ec, d, ec.G) # public key for e in range(ec.n): # all possible int from hash for k in range(ec.n): # all possible ephemeral keys if k == 0: self.assertRaises(ValueError, dsa._sign, ec, e, d, k) continue R = mult(ec, k, ec.G) r = R[0] % ec.n if r == 0: self.assertRaises(ValueError, dsa._sign, ec, e, d, k) continue s = mod_inv(k, ec.n) * (e + d * r) % ec.n if s == 0: self.assertRaises(ValueError, dsa._sign, ec, e, d, k) continue # bitcoin canonical 'low-s' encoding for ECDSA if s > ec.n / 2: s = ec.n - s # valid signature sig = dsa._sign(ec, e, d, k) self.assertEqual((r, s), sig) # valid signature must validate self.assertTrue(dsa._verhlp(ec, e, P, sig)) keys = dsa._pubkey_recovery(ec, e, sig) self.assertIn(P, keys) for Q in keys: self.assertTrue(dsa._verhlp(ec, e, Q, sig))
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 assert format(dU, str(ec.nsize) + "x") == "aa374ffc3ce144e6b073307972cb6d57b2a4e982" QU = mult(dU, ec.G, ec) assert QU == ( 466448783855397898016055842232266600516272889280, 1110706324081757720403272427311003102474457754220, ) assert (bytes_from_point( QU, ec).hex() == "0251b4496fecc406ed0e75a24a3c03206251419dc0") # 2.1.3 Signing Operation for U msg = b"abc" k = 702232148019446860144825009548118511996283736794 exp_sig = ( 0xCE2873E5BE449563391FEB47DDCBA2DC16379191, 0x3480EC1371A091A464B31CE47DF0CB8AA2D98B54, ) low_s = False sig = dsa._sign(reduce_to_hlen(msg, hf), dU, k, low_s, ec, hf) r, s = sig assert r == exp_sig[0] assert s == exp_sig[1] # 2.1.4 Verifying Operation for V assert dsa.verify(msg, QU, sig, ec, hf)
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)
return r == v # 8 random.seed(42) # setup qs = [] es = [] Qs = [] sigs = [] for _ in range(5): q = random.getrandbits(ec.nlen) % ec.n qs.append(q) Qs.append(ec.mult(q, ec.G)) e = random.getrandbits(ec.nlen) % ec.n es.append(e) k = _rfc6979(e, q) sigs.append(_sign(e, q, k)) start = time.time() for i in range(len(qs)): assert _verhlp(ec, es[i], Qs[i], sigs[i], True) elapsed1 = time.time() - start start = time.time() for i in range(len(qs)): assert _verhlp(ec, es[i], Qs[i], sigs[i], False) elapsed2 = time.time() - start print(elapsed2 / elapsed1)