Skip to content

Commit 17f7148

Browse files
committed
Merge pull request #261
7657420 Add tests for adding P+Q with P.x!=Q.x and P.y=-Q.y (Pieter Wuille) 8c5d5f7 tests: Add failing unit test for #257 (bad addition formula) (Andrew Poelstra) 5de4c5d gej_add_ge: fix degenerate case when computing P + (-lambda)P (Andrew Poelstra) bcf2fcf gej_add_ge: rearrange algebra (Andrew Poelstra)
2 parents 873a453 + 7657420 commit 17f7148

File tree

2 files changed

+157
-20
lines changed

2 files changed

+157
-20
lines changed

src/group_impl.h

Lines changed: 75 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -463,8 +463,9 @@ static void secp256k1_gej_add_zinv_var(secp256k1_gej_t *r, const secp256k1_gej_t
463463
static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, const secp256k1_ge_t *b) {
464464
/* Operations: 7 mul, 5 sqr, 5 normalize, 17 mul_int/add/negate/cmov */
465465
static const secp256k1_fe_t fe_1 = SECP256K1_FE_CONST(0, 0, 0, 0, 0, 0, 0, 1);
466-
secp256k1_fe_t zz, u1, u2, s1, s2, z, t, m, n, q, rr;
467-
int infinity;
466+
secp256k1_fe_t zz, u1, u2, s1, s2, z, t, tt, m, n, q, rr;
467+
secp256k1_fe_t m_alt, rr_alt;
468+
int infinity, degenerate;
468469
VERIFY_CHECK(!b->infinity);
469470
VERIFY_CHECK(a->infinity == 0 || a->infinity == 1);
470471

@@ -488,6 +489,34 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c
488489
* Y3 = 4*(R*(3*Q-2*R^2)-M^4)
489490
* Z3 = 2*M*Z
490491
* (Note that the paper uses xi = Xi / Zi and yi = Yi / Zi instead.)
492+
*
493+
* This formula has the benefit of being the same for both addition
494+
* of distinct points and doubling. However, it breaks down in the
495+
* case that either point is infinity, or that y1 = -y2. We handle
496+
* these cases in the following ways:
497+
*
498+
* - If b is infinity we simply bail by means of a VERIFY_CHECK.
499+
*
500+
* - If a is infinity, we detect this, and at the end of the
501+
* computation replace the result (which will be meaningless,
502+
* but we compute to be constant-time) with b.x : b.y : 1.
503+
*
504+
* - If a = -b, we have y1 = -y2, which is a degenerate case.
505+
* But here the answer is infinity, so we simply set the
506+
* infinity flag of the result, overriding the computed values
507+
* without even needing to cmov.
508+
*
509+
* - If y1 = -y2 but x1 != x2, which does occur thanks to certain
510+
* properties of our curve (specifically, 1 has nontrivial cube
511+
* roots in our field, and the curve equation has no x coefficient)
512+
* then the answer is not infinity but also not given by the above
513+
* equation. In this case, we cmov in place an alternate expression
514+
* for lambda. Specifically (y1 - y2)/(x1 - x2). Where both these
515+
* expressions for lambda are defined, they are equal, and can be
516+
* obtained from each other by multiplication by (y1 + y2)/(y1 + y2)
517+
* then substitution of x^3 + 7 for y^2 (using the curve equation).
518+
* For all pairs of nonzero points (a, b) at least one is defined,
519+
* so this covers everything.
491520
*/
492521

493522
secp256k1_fe_sqr(&zz, &a->z); /* z = Z1^2 */
@@ -499,29 +528,55 @@ static void secp256k1_gej_add_ge(secp256k1_gej_t *r, const secp256k1_gej_t *a, c
499528
z = a->z; /* z = Z = Z1*Z2 (8) */
500529
t = u1; secp256k1_fe_add(&t, &u2); /* t = T = U1+U2 (2) */
501530
m = s1; secp256k1_fe_add(&m, &s2); /* m = M = S1+S2 (2) */
502-
secp256k1_fe_sqr(&n, &m); /* n = M^2 (1) */
503-
secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*M^2 (1) */
504-
secp256k1_fe_sqr(&n, &n); /* n = M^4 (1) */
505531
secp256k1_fe_sqr(&rr, &t); /* rr = T^2 (1) */
506-
secp256k1_fe_mul(&t, &u1, &u2); secp256k1_fe_negate(&t, &t, 1); /* t = -U1*U2 (2) */
507-
secp256k1_fe_add(&rr, &t); /* rr = R = T^2-U1*U2 (3) */
508-
secp256k1_fe_sqr(&t, &rr); /* t = R^2 (1) */
509-
secp256k1_fe_mul(&r->z, &m, &z); /* r->z = M*Z (1) */
532+
secp256k1_fe_mul(&tt, &u1, &u2); secp256k1_fe_negate(&tt, &tt, 1); /* tt = -U1*U2 (2) */
533+
secp256k1_fe_add(&rr, &tt); /* rr = R = T^2-U1*U2 (3) */
534+
/** If lambda = R/M = 0/0 we have a problem (except in the "trivial"
535+
* case that Z = z1z2 = 0, and this is special-cased later on). */
536+
degenerate = secp256k1_fe_normalizes_to_zero(&m) &
537+
secp256k1_fe_normalizes_to_zero(&rr);
538+
/* This only occurs when y1 == -y2 and x1^3 == x2^3, but x1 != x2.
539+
* This means either x1 == beta*x2 or beta*x1 == x2, where beta is
540+
* a nontrivial cube root of one. In either case, an alternate
541+
* non-indeterminate expression for lambda is (y1 - y2)/(x1 - x2),
542+
* so we set R/M equal to this. */
543+
secp256k1_fe_negate(&rr_alt, &s2, 1); /* rr = -Y2*Z1^3 */
544+
secp256k1_fe_add(&rr_alt, &s1); /* rr = Y1*Z2^3 - Y2*Z1^3 */
545+
secp256k1_fe_negate(&m_alt, &u2, 1); /* m = -X2*Z1^2 */
546+
secp256k1_fe_add(&m_alt, &u1); /* m = X1*Z2^2 - X2*Z1^2 */
547+
548+
secp256k1_fe_cmov(&rr_alt, &rr, !degenerate);
549+
secp256k1_fe_cmov(&m_alt, &m, !degenerate);
550+
/* Now Ralt / Malt = lambda and is guaranteed not to be 0/0.
551+
* From here on out Ralt and Malt represent the numerator
552+
* and denominator of lambda; R and M represent the explicit
553+
* expressions x1^2 + x2^2 + x1x2 and y1 + y2. */
554+
secp256k1_fe_sqr(&n, &m_alt); /* n = Malt^2 (1) */
555+
secp256k1_fe_mul(&q, &n, &t); /* q = Q = T*Malt^2 (1) */
556+
/* These two lines use the observation that either M == Malt or M == 0,
557+
* so M^3 * Malt is either Malt^4 (which is computed by squaring), or
558+
* zero (which is "computed" by cmov). So the cost is one squaring
559+
* versus two multiplications. */
560+
secp256k1_fe_sqr(&n, &n); /* n = M^3 * Malt (1) */
561+
secp256k1_fe_cmov(&n, &m, degenerate);
562+
secp256k1_fe_normalize_weak(&n);
563+
secp256k1_fe_sqr(&t, &rr_alt); /* t = Ralt^2 (1) */
564+
secp256k1_fe_mul(&r->z, &m_alt, &z); /* r->z = Malt*Z (1) */
510565
infinity = secp256k1_fe_normalizes_to_zero(&r->z) * (1 - a->infinity);
511-
secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*M*Z (2) */
512-
r->x = t; /* r->x = R^2 (1) */
566+
secp256k1_fe_mul_int(&r->z, 2); /* r->z = Z3 = 2*Malt*Z (2) */
567+
r->x = t; /* r->x = Ralt^2 (1) */
513568
secp256k1_fe_negate(&q, &q, 1); /* q = -Q (2) */
514-
secp256k1_fe_add(&r->x, &q); /* r->x = R^2-Q (3) */
569+
secp256k1_fe_add(&r->x, &q); /* r->x = Ralt^2-Q (3) */
515570
secp256k1_fe_normalize(&r->x);
516-
secp256k1_fe_mul_int(&q, 3); /* q = -3*Q (6) */
517-
secp256k1_fe_mul_int(&t, 2); /* t = 2*R^2 (2) */
518-
secp256k1_fe_add(&t, &q); /* t = 2*R^2-3*Q (8) */
519-
secp256k1_fe_mul(&t, &t, &rr); /* t = R*(2*R^2-3*Q) (1) */
520-
secp256k1_fe_add(&t, &n); /* t = R*(2*R^2-3*Q)+M^4 (2) */
521-
secp256k1_fe_negate(&r->y, &t, 2); /* r->y = R*(3*Q-2*R^2)-M^4 (3) */
571+
t = r->x;
572+
secp256k1_fe_mul_int(&t, 2); /* t = 2*x3 (2) */
573+
secp256k1_fe_add(&t, &q); /* t = 2*x3 - Q: (8) */
574+
secp256k1_fe_mul(&t, &t, &rr_alt); /* t = Ralt*(2*x3 - Q) (1) */
575+
secp256k1_fe_add(&t, &n); /* t = Ralt*(2*x3 - Q) + M^3*Malt (2) */
576+
secp256k1_fe_negate(&r->y, &t, 2); /* r->y = Ralt*(Q - 2x3) - M^3*Malt (3) */
522577
secp256k1_fe_normalize_weak(&r->y);
523-
secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(R^2-Q) */
524-
secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*R*(3*Q-2*R^2)-4*M^4 (4) */
578+
secp256k1_fe_mul_int(&r->x, 4); /* r->x = X3 = 4*(Ralt^2-Q) */
579+
secp256k1_fe_mul_int(&r->y, 4); /* r->y = Y3 = 4*Ralt*(Q - 2x3) - 4*M^3*Malt (4) */
525580

526581
/** In case a->infinity == 1, replace r with (b->x, b->y, 1). */
527582
secp256k1_fe_cmov(&r->x, &b->x, a->infinity);

src/tests.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,11 +958,17 @@ void ge_equals_gej(const secp256k1_ge_t *a, const secp256k1_gej_t *b) {
958958

959959
void test_ge(void) {
960960
int i, i1;
961+
#ifdef USE_ENDOMORPHISM
962+
int runs = 6;
963+
#else
961964
int runs = 4;
965+
#endif
962966
/* Points: (infinity, p1, p1, -p1, -p1, p2, p2, -p2, -p2, p3, p3, -p3, -p3, p4, p4, -p4, -p4).
963967
* The second in each pair of identical points uses a random Z coordinate in the Jacobian form.
964968
* All magnitudes are randomized.
965969
* All 17*17 combinations of points are added to eachother, using all applicable methods.
970+
*
971+
* When the endomorphism code is compiled in, p5 = lambda*p1 and p6 = lambda^2*p1 are added as well.
966972
*/
967973
secp256k1_ge_t *ge = (secp256k1_ge_t *)malloc(sizeof(secp256k1_ge_t) * (1 + 4 * runs));
968974
secp256k1_gej_t *gej = (secp256k1_gej_t *)malloc(sizeof(secp256k1_gej_t) * (1 + 4 * runs));
@@ -977,6 +983,14 @@ void test_ge(void) {
977983
int j;
978984
secp256k1_ge_t g;
979985
random_group_element_test(&g);
986+
#ifdef USE_ENDOMORPHISM
987+
if (i >= runs - 2) {
988+
secp256k1_ge_mul_lambda(&g, &ge[1]);
989+
}
990+
if (i >= runs - 1) {
991+
secp256k1_ge_mul_lambda(&g, &g);
992+
}
993+
#endif
980994
ge[1 + 4 * i] = g;
981995
ge[2 + 4 * i] = g;
982996
secp256k1_ge_neg(&ge[3 + 4 * i], &g);
@@ -1146,11 +1160,79 @@ void test_ge(void) {
11461160
free(zinv);
11471161
}
11481162

1163+
void test_add_neg_y_diff_x(void) {
1164+
/* The point of this test is to check that we can add two points
1165+
* whose y-coordinates are negatives of each other but whose x
1166+
* coordinates differ. If the x-coordinates were the same, these
1167+
* points would be negatives of each other and their sum is
1168+
* infinity. This is cool because it "covers up" any degeneracy
1169+
* in the addition algorithm that would cause the xy coordinates
1170+
* of the sum to be wrong (since infinity has no xy coordinates).
1171+
* HOWEVER, if the x-coordinates are different, infinity is the
1172+
* wrong answer, and such degeneracies are exposed. This is the
1173+
* root of https://github.com/bitcoin/secp256k1/issues/257 which
1174+
* this test is a regression test for.
1175+
*
1176+
* These points were generated in sage as
1177+
* # secp256k1 params
1178+
* F = FiniteField (0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEFFFFFC2F)
1179+
* C = EllipticCurve ([F (0), F (7)])
1180+
* G = C.lift_x(0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798)
1181+
* N = FiniteField(G.order())
1182+
*
1183+
* # endomorphism values (lambda is 1^{1/3} in N, beta is 1^{1/3} in F)
1184+
* x = polygen(N)
1185+
* lam = (1 - x^3).roots()[1][0]
1186+
*
1187+
* # random "bad pair"
1188+
* P = C.random_element()
1189+
* Q = -int(lam) * P
1190+
* print " P: %x %x" % P.xy()
1191+
* print " Q: %x %x" % Q.xy()
1192+
* print "P + Q: %x %x" % (P + Q).xy()
1193+
*/
1194+
secp256k1_gej_t aj = SECP256K1_GEJ_CONST(
1195+
0x8d24cd95, 0x0a355af1, 0x3c543505, 0x44238d30,
1196+
0x0643d79f, 0x05a59614, 0x2f8ec030, 0xd58977cb,
1197+
0x001e337a, 0x38093dcd, 0x6c0f386d, 0x0b1293a8,
1198+
0x4d72c879, 0xd7681924, 0x44e6d2f3, 0x9190117d
1199+
);
1200+
secp256k1_gej_t bj = SECP256K1_GEJ_CONST(
1201+
0xc7b74206, 0x1f788cd9, 0xabd0937d, 0x164a0d86,
1202+
0x95f6ff75, 0xf19a4ce9, 0xd013bd7b, 0xbf92d2a7,
1203+
0xffe1cc85, 0xc7f6c232, 0x93f0c792, 0xf4ed6c57,
1204+
0xb28d3786, 0x2897e6db, 0xbb192d0b, 0x6e6feab2
1205+
);
1206+
secp256k1_gej_t sumj = SECP256K1_GEJ_CONST(
1207+
0x671a63c0, 0x3efdad4c, 0x389a7798, 0x24356027,
1208+
0xb3d69010, 0x278625c3, 0x5c86d390, 0x184a8f7a,
1209+
0x5f6409c2, 0x2ce01f2b, 0x511fd375, 0x25071d08,
1210+
0xda651801, 0x70e95caf, 0x8f0d893c, 0xbed8fbbe
1211+
);
1212+
secp256k1_ge_t b;
1213+
secp256k1_gej_t resj;
1214+
secp256k1_ge_t res;
1215+
secp256k1_ge_set_gej(&b, &bj);
1216+
1217+
secp256k1_gej_add_var(&resj, &aj, &bj, NULL);
1218+
secp256k1_ge_set_gej(&res, &resj);
1219+
ge_equals_gej(&res, &sumj);
1220+
1221+
secp256k1_gej_add_ge(&resj, &aj, &b);
1222+
secp256k1_ge_set_gej(&res, &resj);
1223+
ge_equals_gej(&res, &sumj);
1224+
1225+
secp256k1_gej_add_ge_var(&resj, &aj, &b, NULL);
1226+
secp256k1_ge_set_gej(&res, &resj);
1227+
ge_equals_gej(&res, &sumj);
1228+
}
1229+
11491230
void run_ge(void) {
11501231
int i;
11511232
for (i = 0; i < count * 32; i++) {
11521233
test_ge();
11531234
}
1235+
test_add_neg_y_diff_x();
11541236
}
11551237

11561238
/***** ECMULT TESTS *****/

0 commit comments

Comments
 (0)