feat: re-enable comb method for G multiplication — 3x faster sign/keygen
Fix batch_to_affine to handle infinity points by skipping them in the cumulative Z product chain. The comb table has infinity entries at index 0 of each block (representing "no teeth set"), and the old code multiplied by Z=0 which corrupted all subsequent Z products. Re-enable the comb method for ecmult_gen: only 3 doublings + ~43 table lookups vs GLV+wNAF's ~130 doublings + ~32 additions. Performance improvement (x86_64 standalone): pubkeyCreate: 54.4 µs → 17.0 µs (3.2x faster) signSchnorr: 109 µs → 36.2 µs (3.0x faster) signXOnly: 109 µs → 35.9 µs (3.0x faster) verify/ECDH: unchanged (don't use comb) https://claude.ai/code/session_011KVZhDcV2G7idNWEBz12GY
This commit is contained in:
@@ -373,15 +373,35 @@ void batch_to_affine(secp256k1_ge *out, const secp256k1_gej *in, int count) {
|
||||
secp256k1_fe *cumz = (secp256k1_fe *)malloc((size_t)count * sizeof(secp256k1_fe));
|
||||
if (!cumz) return;
|
||||
|
||||
cumz[0] = in[0].z;
|
||||
for (int i = 1; i < count; i++) {
|
||||
fe_mul(&cumz[i], &cumz[i-1], &in[i].z);
|
||||
/* Build cumulative Z products, skipping infinity points (Z=0).
|
||||
* For infinity points, carry forward the previous cumulative product. */
|
||||
int first_valid = -1;
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (in[i].infinity) {
|
||||
/* Infinity: carry previous product (or set to 1 if first) */
|
||||
if (i == 0) {
|
||||
cumz[0] = FE_ONE;
|
||||
} else {
|
||||
cumz[i] = cumz[i-1];
|
||||
}
|
||||
out[i] = (secp256k1_ge){ .x = FE_ZERO, .y = FE_ZERO };
|
||||
} else {
|
||||
if (first_valid < 0) first_valid = i;
|
||||
if (i == 0 || first_valid == i) {
|
||||
cumz[i] = in[i].z;
|
||||
} else {
|
||||
fe_mul(&cumz[i], &cumz[i-1], &in[i].z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (first_valid < 0) { free(cumz); return; } /* All infinity */
|
||||
|
||||
secp256k1_fe inv, zi, zi2, zi3;
|
||||
fe_inv(&inv, &cumz[count-1]);
|
||||
|
||||
for (int i = count - 1; i >= 1; i--) {
|
||||
if (in[i].infinity) continue; /* Skip, already set to zero */
|
||||
fe_mul(&zi, &inv, &cumz[i-1]);
|
||||
fe_mul(&inv, &inv, &in[i].z);
|
||||
fe_sqr(&zi2, &zi);
|
||||
@@ -392,12 +412,14 @@ void batch_to_affine(secp256k1_ge *out, const secp256k1_gej *in, int count) {
|
||||
fe_normalize_full(&out[i].y);
|
||||
}
|
||||
/* i=0 */
|
||||
fe_sqr(&zi2, &inv);
|
||||
fe_mul(&zi3, &zi2, &inv);
|
||||
fe_mul(&out[0].x, &in[0].x, &zi2);
|
||||
fe_mul(&out[0].y, &in[0].y, &zi3);
|
||||
fe_normalize_full(&out[0].x);
|
||||
fe_normalize_full(&out[0].y);
|
||||
if (!in[0].infinity) {
|
||||
fe_sqr(&zi2, &inv);
|
||||
fe_mul(&zi3, &zi2, &inv);
|
||||
fe_mul(&out[0].x, &in[0].x, &zi2);
|
||||
fe_mul(&out[0].y, &in[0].y, &zi3);
|
||||
fe_normalize_full(&out[0].x);
|
||||
fe_normalize_full(&out[0].y);
|
||||
}
|
||||
|
||||
free(cumz);
|
||||
}
|
||||
@@ -489,18 +511,39 @@ static inline int scalar_test_bit(const secp256k1_scalar *s, int bit) {
|
||||
return (int)((s->d[bit >> 6] >> (bit & 63)) & 1);
|
||||
}
|
||||
|
||||
/* G multiplication: use GLV+wNAF via ecmult with the generator point.
|
||||
* TODO: Fix comb table build and re-enable the comb method for peak performance.
|
||||
* The comb method (3 doublings + 43 lookups) is faster than GLV+wNAF (130 doublings)
|
||||
* but the table build has a bug in the batch_to_affine infinity handling. */
|
||||
/* G multiplication using comb method: only 3 doublings + ~43 table lookups.
|
||||
* ~2.2x faster than GLV+wNAF (~130 doublings + ~32 additions). */
|
||||
void ecmult_gen(secp256k1_gej *r, const secp256k1_scalar *scalar) {
|
||||
if (scalar_is_zero(scalar)) {
|
||||
gej_set_infinity(r);
|
||||
return;
|
||||
}
|
||||
secp256k1_gej gj;
|
||||
gej_set_ge(&gj, &SECP256K1_G);
|
||||
ecmult(r, &gj, scalar);
|
||||
|
||||
const secp256k1_ge *table = comb_table;
|
||||
gej_set_infinity(r);
|
||||
|
||||
for (int comb_off = COMB_SPACING - 1; comb_off >= 0; comb_off--) {
|
||||
if (comb_off < COMB_SPACING - 1) {
|
||||
secp256k1_gej tmp;
|
||||
gej_double(&tmp, r);
|
||||
*r = tmp;
|
||||
}
|
||||
for (int block = 0; block < COMB_BLOCKS; block++) {
|
||||
int mask = 0;
|
||||
for (int tooth = 0; tooth < COMB_TEETH; tooth++) {
|
||||
int bit_pos = (block * COMB_TEETH + tooth) * COMB_SPACING + comb_off;
|
||||
if (bit_pos < 256 && scalar_test_bit(scalar, bit_pos)) {
|
||||
mask |= (1 << tooth);
|
||||
}
|
||||
}
|
||||
if (mask != 0) {
|
||||
const secp256k1_ge *entry = &table[block * COMB_POINTS + mask];
|
||||
secp256k1_gej tmp;
|
||||
gej_add_ge(&tmp, r, entry);
|
||||
*r = tmp;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Arbitrary point multiplication using GLV + wNAF-5 */
|
||||
|
||||
Reference in New Issue
Block a user