From bcf2fcfd3a6d701b36d5d1dd4a76c1336a2de3cd Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 23 Jun 2015 12:35:20 -0700 Subject: [PATCH 1/4] gej_add_ge: rearrange algebra There is zero functionality or opcount changes here; I need to do this to make sure both R and M are computed before they are used, since a future patch will replace either none or both of them. Also compute r->y directly in terms of r->x, which again will be used in a future patch. --- src/group_impl.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/group_impl.h b/src/group_impl.h index 535874f125e..547d6ddd929 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -463,7 +463,7 @@ static void secp256k1_gej_add_zinv_var(secp256k1_gej_t *r, const secp256k1_gej_t static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, const secp256k1_ge_t *b) { /* Operations: 7 mul, 5 sqr, 5 normalize, 17 mul_int/add/negate/cmov */ static const secp256k1_fe_t fe_1 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); - secp256k1_fe_t zz, u1, u2, s1, s2, z, t, m, n, q, rr; + secp256k1_fe_t zz, u1, u2, s1, s2, z, t, tt, m, n, q, rr; int infinity; VERIFY_CHECK(!b->infinity); VERIFY_CHECK(a->infinity == 0 || a->infinity == 1); @@ -499,12 +499,12 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c z = a->z; /* z = Z = Z1*Z2 (8) */ t = u1; secp256k1_fe_add(&t, &u2); /* t = T = U1+U2 (2) */ m = s1; secp256k1_fe_add(&m, &s2); /* m = M = S1+S2 (2) */ + secp256k1_fe_sqr(&rr, &t); /* rr = T^2 (1) */ + secp256k1_fe_mul(&tt, &u1, &u2); secp256k1_fe_negate(&tt, &tt, 1); /* t = -U1*U2 (2) */ + secp256k1_fe_add(&rr, &tt); /* rr = R = T^2-U1*U2 (3) */ secp256k1_fe_sqr(&n, &m); /* n = M^2 (1) */ secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*M^2 (1) */ secp256k1_fe_sqr(&n, &n); /* n = M^4 (1) */ - secp256k1_fe_sqr(&rr, &t); /* rr = T^2 (1) */ - secp256k1_fe_mul(&t, &u1, &u2); secp256k1_fe_negate(&t, &t, 1); /* t = -U1*U2 (2) */ - secp256k1_fe_add(&rr, &t); /* rr = R = T^2-U1*U2 (3) */ secp256k1_fe_sqr(&t, &rr); /* t = R^2 (1) */ secp256k1_fe_mul(&r->z, &m, &z); /* r->z = M*Z (1) */ infinity = secp256k1_fe_normalizes_to_zero(&r->z) * (1 - a->infinity); @@ -513,10 +513,10 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c secp256k1_fe_negate(&q, &q, 1); /* q = -Q (2) */ secp256k1_fe_add(&r->x, &q); /* r->x = R^2-Q (3) */ secp256k1_fe_normalize(&r->x); - secp256k1_fe_mul_int(&q, 3); /* q = -3*Q (6) */ - secp256k1_fe_mul_int(&t, 2); /* t = 2*R^2 (2) */ - secp256k1_fe_add(&t, &q); /* t = 2*R^2-3*Q (8) */ - secp256k1_fe_mul(&t, &t, &rr); /* t = R*(2*R^2-3*Q) (1) */ + t = r->x; + secp256k1_fe_mul_int(&t, 2); /* t = 2*x3 (2) */ + secp256k1_fe_add(&t, &q); /* t = 2*x3 - Q: (8) */ + secp256k1_fe_mul(&t, &t, &rr); /* t = R*(2*x3 - Q) (1) */ secp256k1_fe_add(&t, &n); /* t = R*(2*R^2-3*Q)+M^4 (2) */ secp256k1_fe_negate(&r->y, &t, 2); /* r->y = R*(3*Q-2*R^2)-M^4 (3) */ secp256k1_fe_normalize_weak(&r->y); From 5de4c5dffd22aa4510a5c97d0ad4a9c2eed71d85 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Tue, 23 Jun 2015 12:41:02 -0700 Subject: [PATCH 2/4] gej_add_ge: fix degenerate case when computing P + (-lambda)P If two points (x1, y1) and (x2, y2) are given to gej_add_ge with x1 != x2 but y1 = -y2, the function gives a wrong answer since this causes it to compute "lambda = 0/0" during an intermediate step. (Here lambda refers to an auxiallary variable in the point addition formula, not the cube-root of 1 used by the endomorphism optimization.) This commit catches the 0/0 and replaces it with an alternate expression for lambda, cmov'ing it in place if necessary. --- src/group_impl.h | 85 +++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 70 insertions(+), 15 deletions(-) diff --git a/src/group_impl.h b/src/group_impl.h index 547d6ddd929..2da89097930 100644 --- a/src/group_impl.h +++ b/src/group_impl.h @@ -464,7 +464,8 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c /* Operations: 7 mul, 5 sqr, 5 normalize, 17 mul_int/add/negate/cmov */ static const secp256k1_fe_t fe_1 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1); secp256k1_fe_t zz, u1, u2, s1, s2, z, t, tt, m, n, q, rr; - int infinity; + secp256k1_fe_t m_alt, rr_alt; + int infinity, degenerate; VERIFY_CHECK(!b->infinity); VERIFY_CHECK(a->infinity == 0 || a->infinity == 1); @@ -488,6 +489,34 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c * Y3 = 4*(R*(3*Q-2*R^2)-M^4) * Z3 = 2*M*Z * (Note that the paper uses xi = Xi / Zi and yi = Yi / Zi instead.) + * + * This formula has the benefit of being the same for both addition + * of distinct points and doubling. However, it breaks down in the + * case that either point is infinity, or that y1 = -y2. We handle + * these cases in the following ways: + * + * - If b is infinity we simply bail by means of a VERIFY_CHECK. + * + * - If a is infinity, we detect this, and at the end of the + * computation replace the result (which will be meaningless, + * but we compute to be constant-time) with b.x : b.y : 1. + * + * - If a = -b, we have y1 = -y2, which is a degenerate case. + * But here the answer is infinity, so we simply set the + * infinity flag of the result, overriding the computed values + * without even needing to cmov. + * + * - If y1 = -y2 but x1 != x2, which does occur thanks to certain + * properties of our curve (specifically, 1 has nontrivial cube + * roots in our field, and the curve equation has no x coefficient) + * then the answer is not infinity but also not given by the above + * equation. In this case, we cmov in place an alternate expression + * for lambda. Specifically (y1 - y2)/(x1 - x2). Where both these + * expressions for lambda are defined, they are equal, and can be + * obtained from each other by multiplication by (y1 + y2)/(y1 + y2) + * then substitution of x^3 + 7 for y^2 (using the curve equation). + * For all pairs of nonzero points (a, b) at least one is defined, + * so this covers everything. */ secp256k1_fe_sqr(&zz, &a->z); /* z = Z1^2 */ @@ -500,28 +529,54 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c t = u1; secp256k1_fe_add(&t, &u2); /* t = T = U1+U2 (2) */ m = s1; secp256k1_fe_add(&m, &s2); /* m = M = S1+S2 (2) */ secp256k1_fe_sqr(&rr, &t); /* rr = T^2 (1) */ - secp256k1_fe_mul(&tt, &u1, &u2); secp256k1_fe_negate(&tt, &tt, 1); /* t = -U1*U2 (2) */ + secp256k1_fe_mul(&tt, &u1, &u2); secp256k1_fe_negate(&tt, &tt, 1); /* tt = -U1*U2 (2) */ secp256k1_fe_add(&rr, &tt); /* rr = R = T^2-U1*U2 (3) */ - secp256k1_fe_sqr(&n, &m); /* n = M^2 (1) */ - secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*M^2 (1) */ - secp256k1_fe_sqr(&n, &n); /* n = M^4 (1) */ - secp256k1_fe_sqr(&t, &rr); /* t = R^2 (1) */ - secp256k1_fe_mul(&r->z, &m, &z); /* r->z = M*Z (1) */ + /** If lambda = R/M = 0/0 we have a problem (except in the "trivial" + * case that Z = z1z2 = 0, and this is special-cased later on). */ + degenerate = secp256k1_fe_normalizes_to_zero(&m) & + secp256k1_fe_normalizes_to_zero(&rr); + /* This only occurs when y1 == -y2 and x1^3 == x2^3, but x1 != x2. + * This means either x1 == beta*x2 or beta*x1 == x2, where beta is + * a nontrivial cube root of one. In either case, an alternate + * non-indeterminate expression for lambda is (y1 - y2)/(x1 - x2), + * so we set R/M equal to this. */ + secp256k1_fe_negate(&rr_alt, &s2, 1); /* rr = -Y2*Z1^3 */ + secp256k1_fe_add(&rr_alt, &s1); /* rr = Y1*Z2^3 - Y2*Z1^3 */ + secp256k1_fe_negate(&m_alt, &u2, 1); /* m = -X2*Z1^2 */ + secp256k1_fe_add(&m_alt, &u1); /* m = X1*Z2^2 - X2*Z1^2 */ + + secp256k1_fe_cmov(&rr_alt, &rr, !degenerate); + secp256k1_fe_cmov(&m_alt, &m, !degenerate); + /* Now Ralt / Malt = lambda and is guaranteed not to be 0/0. + * From here on out Ralt and Malt represent the numerator + * and denominator of lambda; R and M represent the explicit + * expressions x1^2 + x2^2 + x1x2 and y1 + y2. */ + secp256k1_fe_sqr(&n, &m_alt); /* n = Malt^2 (1) */ + secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*Malt^2 (1) */ + /* These two lines use the observation that either M == Malt or M == 0, + * so M^3 * Malt is either Malt^4 (which is computed by squaring), or + * zero (which is "computed" by cmov). So the cost is one squaring + * versus two multiplications. */ + secp256k1_fe_sqr(&n, &n); /* n = M^3 * Malt (1) */ + secp256k1_fe_cmov(&n, &m, degenerate); + secp256k1_fe_normalize_weak(&n); + secp256k1_fe_sqr(&t, &rr_alt); /* t = Ralt^2 (1) */ + secp256k1_fe_mul(&r->z, &m_alt, &z); /* r->z = Malt*Z (1) */ infinity = secp256k1_fe_normalizes_to_zero(&r->z) * (1 - a->infinity); - secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*M*Z (2) */ - r->x = t; /* r->x = R^2 (1) */ + secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*Malt*Z (2) */ + r->x = t; /* r->x = Ralt^2 (1) */ secp256k1_fe_negate(&q, &q, 1); /* q = -Q (2) */ - secp256k1_fe_add(&r->x, &q); /* r->x = R^2-Q (3) */ + secp256k1_fe_add(&r->x, &q); /* r->x = Ralt^2-Q (3) */ secp256k1_fe_normalize(&r->x); t = r->x; secp256k1_fe_mul_int(&t, 2); /* t = 2*x3 (2) */ secp256k1_fe_add(&t, &q); /* t = 2*x3 - Q: (8) */ - secp256k1_fe_mul(&t, &t, &rr); /* t = R*(2*x3 - Q) (1) */ - secp256k1_fe_add(&t, &n); /* t = R*(2*R^2-3*Q)+M^4 (2) */ - secp256k1_fe_negate(&r->y, &t, 2); /* r->y = R*(3*Q-2*R^2)-M^4 (3) */ + secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*x3 - Q) (1) */ + secp256k1_fe_add(&t, &n); /* t = Ralt*(2*x3 - Q) + M^3*Malt (2) */ + secp256k1_fe_negate(&r->y, &t, 2); /* r->y = Ralt*(Q - 2x3) - M^3*Malt (3) */ secp256k1_fe_normalize_weak(&r->y); - secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(R^2-Q) */ - secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*R*(3*Q-2*R^2)-4*M^4 (4) */ + secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(Ralt^2-Q) */ + secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*Ralt*(Q - 2x3) - 4*M^3*Malt (4) */ /** In case a->infinity == 1, replace r with (b->x, b->y, 1). */ secp256k1_fe_cmov(&r->x, &b->x, a->infinity); From 8c5d5f7b5beaad60e0f4c61b98235fa2a39107d6 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sun, 21 Jun 2015 10:11:50 -0700 Subject: [PATCH 3/4] tests: Add failing unit test for #257 (bad addition formula) --- src/tests.c | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/src/tests.c b/src/tests.c index 7ac02efd91b..4c374424b62 100644 --- a/src/tests.c +++ b/src/tests.c @@ -1146,11 +1146,79 @@ void test_ge(void) { free(zinv); } +void test_add_neg_y_diff_x(void) { + /* The point of this test is to check that we can add two points + * whose y-coordinates are negatives of each other but whose x + * coordinates differ. If the x-coordinates were the same, these + * points would be negatives of each other and their sum is + * infinity. This is cool because it "covers up" any degeneracy + * in the addition algorithm that would cause the xy coordinates + * of the sum to be wrong (since infinity has no xy coordinates). + * HOWEVER, if the x-coordinates are different, infinity is the + * wrong answer, and such degeneracies are exposed. This is the + * root of https://github.com/bitcoin/secp256k1/issues/257 which + * this test is a regression test for. + * + * These points were generated in sage as + * # secp256k1 params + * F = FiniteField (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F) + * C = EllipticCurve ([F (0), F (7)]) + * G = C.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798) + * N = FiniteField(G.order()) + * + * # endomorphism values (lambda is 1^{1/3} in N, beta is 1^{1/3} in F) + * x = polygen(N) + * lam = (1 - x^3).roots()[1][0] + * + * # random "bad pair" + * P = C.random_element() + * Q = -int(lam) * P + * print " P: %x %x" % P.xy() + * print " Q: %x %x" % Q.xy() + * print "P + Q: %x %x" % (P + Q).xy() + */ + secp256k1_gej_t aj = SECP256K1_GEJ_CONST( + 0x8d24cd95, 0x0a355af1, 0x3c543505, 0x44238d30, + 0x0643d79f, 0x05a59614, 0x2f8ec030, 0xd58977cb, + 0x001e337a, 0x38093dcd, 0x6c0f386d, 0x0b1293a8, + 0x4d72c879, 0xd7681924, 0x44e6d2f3, 0x9190117d + ); + secp256k1_gej_t bj = SECP256K1_GEJ_CONST( + 0xc7b74206, 0x1f788cd9, 0xabd0937d, 0x164a0d86, + 0x95f6ff75, 0xf19a4ce9, 0xd013bd7b, 0xbf92d2a7, + 0xffe1cc85, 0xc7f6c232, 0x93f0c792, 0xf4ed6c57, + 0xb28d3786, 0x2897e6db, 0xbb192d0b, 0x6e6feab2 + ); + secp256k1_gej_t sumj = SECP256K1_GEJ_CONST( + 0x671a63c0, 0x3efdad4c, 0x389a7798, 0x24356027, + 0xb3d69010, 0x278625c3, 0x5c86d390, 0x184a8f7a, + 0x5f6409c2, 0x2ce01f2b, 0x511fd375, 0x25071d08, + 0xda651801, 0x70e95caf, 0x8f0d893c, 0xbed8fbbe + ); + secp256k1_ge_t b; + secp256k1_gej_t resj; + secp256k1_ge_t res; + secp256k1_ge_set_gej(&b, &bj); + + secp256k1_gej_add_var(&resj, &aj, &bj, NULL); + secp256k1_ge_set_gej(&res, &resj); + ge_equals_gej(&res, &sumj); + + secp256k1_gej_add_ge(&resj, &aj, &b); + secp256k1_ge_set_gej(&res, &resj); + ge_equals_gej(&res, &sumj); + + secp256k1_gej_add_ge_var(&resj, &aj, &b, NULL); + secp256k1_ge_set_gej(&res, &resj); + ge_equals_gej(&res, &sumj); +} + void run_ge(void) { int i; for (i = 0; i < count * 32; i++) { test_ge(); } + test_add_neg_y_diff_x(); } /***** ECMULT TESTS *****/ From 765742021af54d27a7d6ca018922219bff997074 Mon Sep 17 00:00:00 2001 From: Pieter Wuille Date: Tue, 23 Jun 2015 12:33:49 +0200 Subject: [PATCH 4/4] Add tests for adding P+Q with P.x!=Q.x and P.y=-Q.y --- src/tests.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/tests.c b/src/tests.c index 4c374424b62..bcd2d62a4af 100644 --- a/src/tests.c +++ b/src/tests.c @@ -958,11 +958,17 @@ void ge_equals_gej(const secp256k1_ge_t *a, const secp256k1_gej_t *b) { void test_ge(void) { int i, i1; +#ifdef USE_ENDOMORPHISM + int runs = 6; +#else int runs = 4; +#endif /* Points: (infinity, p1, p1, -p1, -p1, p2, p2, -p2, -p2, p3, p3, -p3, -p3, p4, p4, -p4, -p4). * The second in each pair of identical points uses a random Z coordinate in the Jacobian form. * All magnitudes are randomized. * All 17*17 combinations of points are added to eachother, using all applicable methods. + * + * When the endomorphism code is compiled in, p5 = lambda*p1 and p6 = lambda^2*p1 are added as well. */ secp256k1_ge_t *ge = (secp256k1_ge_t *)malloc(sizeof(secp256k1_ge_t) * (1 + 4 * runs)); secp256k1_gej_t *gej = (secp256k1_gej_t *)malloc(sizeof(secp256k1_gej_t) * (1 + 4 * runs)); @@ -977,6 +983,14 @@ void test_ge(void) { int j; secp256k1_ge_t g; random_group_element_test(&g); +#ifdef USE_ENDOMORPHISM + if (i >= runs - 2) { + secp256k1_ge_mul_lambda(&g, &ge[1]); + } + if (i >= runs - 1) { + secp256k1_ge_mul_lambda(&g, &g); + } +#endif ge[1 + 4 * i] = g; ge[2 + 4 * i] = g; secp256k1_ge_neg(&ge[3 + 4 * i], &g);