def test_mult(): for ec in low_card_curves.values(): for q in range(ec.n): Q = _mult_aff(q, ec.G, ec) QJ = _mult_jac(q, ec.GJ, ec) assert Q == ec._aff_from_jac(QJ) assert INF == _mult_aff(q, INF, ec) assert INFJ == _mult_jac(q, INFJ, ec)
def test_mult(self): for ec in low_card_curves.values(): for q in range(ec.n): Q = _mult_aff(q, ec.G, ec) QJ = _mult_jac(q, ec.GJ, ec) Q2 = ec._aff_from_jac(QJ) self.assertEqual(Q, Q2) # with last curve self.assertEqual(INF, _mult_aff(3, INF, ec)) self.assertEqual(INFJ, _mult_jac(3, INFJ, ec))
def test_mult(self): for ec in low_card_curves: for q in range(ec.n): Q = _mult_aff(ec, q, ec.G) Qjac = _mult_jac(ec, q, ec.GJ) Q2 = ec._aff_from_jac(Qjac) self.assertEqual(Q, Q2) # with last curve self.assertEqual(Inf, _mult_aff(ec, 3, Inf)) self.assertEqual(InfJ, _mult_jac(ec, 3, InfJ))
def test_mult_aff_curves(): for ec in all_curves.values(): assert _mult_aff(0, ec.G, ec) == INF assert _mult_aff(0, INF, ec) == INF assert _mult_aff(1, INF, ec) == INF assert _mult_aff(1, ec.G, ec) == ec.G P = ec._add_aff(ec.G, ec.G) assert P == _mult_aff(2, ec.G, ec) P = _mult_aff(ec.n - 1, ec.G, ec) assert ec.negate(ec.G) == P assert _mult_aff(ec.n - 1, INF, ec) == INF assert ec._add_aff(P, ec.G) == INF assert _mult_aff(ec.n, ec.G, ec) == INF with pytest.raises(ValueError, match="negative m: -0x"): _mult_aff(-1, ec.G, ec)
def test_jac(): ec = Curve(13, 0, 2, (1, 9), 19, 1, False) assert ec._jac_equality(ec.GJ, _jac_from_aff(ec.G)) # q in [2, n-1] q = 2 + secrets.randbelow(ec.n - 2) Q = _mult_aff(q, ec.G, ec) QJ = _mult_jac(q, ec.GJ, ec) assert ec._jac_equality(QJ, _jac_from_aff(Q)) assert not ec._jac_equality(QJ, ec.negate(QJ)) assert not ec._jac_equality(QJ, ec.GJ)
def test_ecf(self): ec = CurveGroup(9739, 497, 1768) # challenge = 'Point Negation' P = (8045, 6936) S = ec.negate(P) S_exp = (8045, 2803) self.assertEqual(S, S_exp) # challenge = 'Point Addition' X = (5274, 2841) Y = (8669, 740) assert ec.add(X, Y) == (1024, 4440) assert ec.add(X, X) == (7284, 2107) P = (493, 5564) Q = (1539, 4742) R = (4403, 5202) S = ec.add(ec.add(ec.add(P, P), Q), R) ec.require_on_curve(S) S_exp = (4215, 2162) self.assertEqual(S, S_exp) # challenge = 'Scalar Multiplication' X = (5323, 5438) assert _mult_aff(1337, X, ec) == (1089, 6931) P = (2339, 2213) S = _mult_aff(7863, P, ec) ec.require_on_curve(S) S_exp = (9467, 2742) self.assertEqual(S, S_exp) # challenge = 'Curves and Logs' all_points = find_all_points(ec) self.assertEqual(len(all_points), 9735) G = (1804, 5368) points = find_subgroup_points(ec, G) self.assertEqual(len(points), 9735)
def test_is_on_curve(): for ec in all_curves.values(): P = "not a point" with pytest.raises(ValueError, match="point must be a tuple"): ec.is_on_curve(P) with pytest.raises(ValueError, match="x-coordinate not in 0..p-1: "): ec.y(ec.p) # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = _mult_aff(q, ec.G, ec) with pytest.raises(ValueError, match="y-coordinate not in 1..p-1: "): ec.is_on_curve((Q[0], ec.p))
def test_add(): for ec in all_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = _mult_aff(q, ec.G, ec) QJ = _jac_from_aff(Q) # add Q and G R = ec._add_aff(Q, ec.G) RJ = ec._add_jac(QJ, ec.GJ) assert R == ec._aff_from_jac(RJ) # double Q R = ec._add_aff(Q, Q) RJ = ec._add_jac(QJ, QJ) assert R == ec._aff_from_jac(RJ)
def test_aff_jac_conversions(): for ec in all_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = _mult_aff(q, ec.G, ec) QJ = _jac_from_aff(Q) assert Q == ec._aff_from_jac(QJ) x_Q = ec._x_aff_from_jac(QJ) assert Q[0] == x_Q assert INF == ec._aff_from_jac(_jac_from_aff(INF)) # relevant for BIP340-Schnorr signature verification assert not ec.has_square_y(INF) with pytest.raises(ValueError, match="infinity point has no x-coordinate"): ec._x_aff_from_jac(INFJ) with pytest.raises(TypeError, match="not a point"): ec.has_square_y("notapoint")
def test_negate(): for ec in all_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = _mult_aff(q, ec.G, ec) minus_Q = ec.negate(Q) assert ec.add(Q, minus_Q) == INF # Jacobian coordinates QJ = _jac_from_aff(Q) minus_QJ = ec.negate(QJ) assert ec._add_jac(QJ, minus_QJ) == INFJ # negate of INF is INF minus_INF = ec.negate(INF) assert minus_INF == INF # negate of INFJ is INFJ minus_INFJ = ec.negate(INFJ) assert minus_INFJ == INFJ with pytest.raises(TypeError, match="not a point"): ec.negate("notapoint")
def test_octets2point(): for ec in all_curves.values(): Gbytes = bytes_from_point(ec.G, ec) G2 = point_from_octets(Gbytes, ec) assert ec.G == G2 Gbytes = bytes_from_point(ec.G, ec, False) G2 = point_from_octets(Gbytes, ec) assert ec.G == G2 # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = _mult_aff(q, ec.G, ec) Q_bytes = b"\x03" if Q[1] & 1 else b"\x02" Q_bytes += Q[0].to_bytes(ec.psize, byteorder="big") R = point_from_octets(Q_bytes, ec) assert R == Q assert bytes_from_point(R, ec) == Q_bytes Q_hex_str = Q_bytes.hex() R = point_from_octets(Q_hex_str, ec) assert R == Q Q_bytes = b"\x04" + Q[0].to_bytes(ec.psize, byteorder="big") Q_bytes += Q[1].to_bytes(ec.psize, byteorder="big") R = point_from_octets(Q_bytes, ec) assert R == Q assert bytes_from_point(R, ec, False) == Q_bytes Q_hex_str = Q_bytes.hex() R = point_from_octets(Q_hex_str, ec) assert R == Q t = tuple() err_msg = "'<' not supported between instances of 'tuple' and 'int'" with pytest.raises(TypeError, match=err_msg): _mult_aff(t, ec.G, ec) Q_bytes = b"\x01" + b"\x01" * ec.psize with pytest.raises(ValueError, match="not a point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x01" + b"\x01" * 2 * ec.psize with pytest.raises(ValueError, match="not a point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x04" + b"\x01" * ec.psize with pytest.raises(ValueError, match="invalid size for uncompressed point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x02" + b"\x01" * 2 * ec.psize with pytest.raises(ValueError, match="invalid size for compressed point: "): point_from_octets(Q_bytes, ec) Q_bytes = b"\x03" + b"\x01" * 2 * ec.psize with pytest.raises(ValueError, match="invalid size for compressed point: "): point_from_octets(Q_bytes, ec) # invalid x_Q coordinate ec = CURVES["secp256k1"] x_Q = 0xEEFDEA4CDB677750A420FEE807EACF21EB9898AE79B9768766E4FAA04A2D4A34 xstr = format(x_Q, "32X") with pytest.raises(ValueError, match="invalid x-coordinate: "): point_from_octets("03" + xstr, ec) with pytest.raises(ValueError, match="point not on curve: "): point_from_octets("04" + 2 * xstr, ec) with pytest.raises(ValueError, match="point not on curve"): bytes_from_point((x_Q, x_Q), ec) with pytest.raises(ValueError, match="point not on curve"): bytes_from_point((x_Q, x_Q), ec, False)
def test_symmetry(): """Methods to break simmetry: quadratic residue, odd/even, low/high""" for ec in low_card_curves.values(): # just a random point, not INF q = 1 + secrets.randbelow(ec.n - 1) Q = _mult_aff(q, ec.G, ec) x_Q = Q[0] y_odd = ec.y_odd(x_Q) assert y_odd % 2 == 1 y_even = ec.y_odd(x_Q, False) assert y_even % 2 == 0 assert y_even == ec.p - y_odd y_low = ec.y_low(x_Q) y_high = ec.y_low(x_Q, False) assert y_low < y_high assert y_high == ec.p - y_low # compute quadratic residues hasRoot = set() hasRoot.add(1) for i in range(2, ec.p): hasRoot.add(i * i % ec.p) if ec.p % 4 == 3: quad_res = ec.y_quadratic_residue(x_Q) not_quad_res = ec.y_quadratic_residue(x_Q, False) # in this case only quad_res is a quadratic residue assert quad_res in hasRoot root = mod_sqrt(quad_res, ec.p) assert quad_res == (root * root) % ec.p root = ec.p - root assert quad_res == (root * root) % ec.p assert not_quad_res == ec.p - quad_res assert not_quad_res not in hasRoot with pytest.raises(ValueError, match="no root for "): mod_sqrt(not_quad_res, ec.p) else: assert ec.p % 4 == 1 # cannot use y_quadratic_residue in this case err_msg = "field prime is not equal to 3 mod 4: " with pytest.raises(ValueError, match=err_msg): ec.y_quadratic_residue(x_Q) with pytest.raises(ValueError, match=err_msg): ec.y_quadratic_residue(x_Q, False) # in this case neither or both y_Q are quadratic residues neither = y_odd not in hasRoot and y_even not in hasRoot both = y_odd in hasRoot and y_even in hasRoot assert neither or both if y_odd in hasRoot: # both have roots root = mod_sqrt(y_odd, ec.p) assert y_odd == (root * root) % ec.p root = ec.p - root assert y_odd == (root * root) % ec.p root = mod_sqrt(y_even, ec.p) assert y_even == (root * root) % ec.p root = ec.p - root assert y_even == (root * root) % ec.p else: err_msg = "no root for " with pytest.raises(ValueError, match=err_msg): mod_sqrt(y_odd, ec.p) with pytest.raises(ValueError, match=err_msg): mod_sqrt(y_even, ec.p) # with the last curve with pytest.raises(ValueError, match="low1high0 must be bool or 1/0"): ec.y_low(x_Q, 2) with pytest.raises(ValueError, match="odd1even0 must be bool or 1/0"): ec.y_odd(x_Q, 2) with pytest.raises(ValueError, match="quad_res must be bool or 1/0"): ec.y_quadratic_residue(x_Q, 2)
# or distributed except according to the terms contained in the LICENSE file. import random import time from btclib.curve import _jac_from_aff, _mult_aff, _mult_jac from btclib.curves import secp256k1 random.seed(42) ec = secp256k1 # setup qs = [] for _ in range(50): qs.append(random.getrandbits(ec.nlen) % ec.n) start = time.time() for q in qs: _mult_aff(ec, q, ec.G) elapsed1 = time.time() - start start = time.time() for q in qs: # starts from affine coordinates, ends with affine coordinates GJ = _jac_from_aff(ec.G) ec._aff_from_jac(_mult_jac(ec, q, GJ)) elapsed2 = time.time() - start print(elapsed2 / elapsed1)