Merge BlockstreamResearch/secp256k1-zkp#329: ecdsa_adaptor: Batch affine conversions in encrypt and dleq_verify

3f7a9429ad ecdsa_adaptor: batch affine conversion in dleq_pair and dleq_prove (mllwchrry)
d380549e38 ecdsa_adaptor: optimize encrypt with batch affine conversion (mllwchrry)

Pull request description:

  Replace pairs of `secp256k1_ge_set_gej` calls with single `secp256k1_ge_set_all_gej[_var]` calls, saving one expensive field inversion per operation.

  `secp256k1_ecdsa_adaptor_encrypt`:
  - Batch convert nonce points `R'` and `R` using `secp256k1_ge_set_all_gej`.
  - Add early `enckey` validation: `secp256k1_ge_set_all_gej` requires non-infinity inputs, but invalid `enckey` could cause `secp256k1_ecmult_const` to produce infinity.
  - Add early return when `secp256k1_dleq_prove` fails.

  `secp256k1_dleq_pair`:
  - Batch convert points `p[0]` and `p[1]` using `secp256k1_ge_set_all_gej`.

  `secp256k1_dleq_prove`:
  - Add early return with `secp256k1_declassify` when nonce generation fails, ensuring the nonce `k` passed to `secp256k1_dleq_pair` is always non-zero (required by `secp256k1_ge_set_all_gej`).

  `secp256k1_dleq_verify`:
  - Batch convert using variable-time `secp256k1_ge_set_all_gej_var` (already uses variable-time operations, processes public data).

ACKs for top commit:
  real-or-random:
    ACK 3f7a9429ad

Tree-SHA512: 706ab9df439f0803d1ec0181caf94a6f480f8e2c9337eae966c6b7a94b5c3ad14e71fc1a223a7d0f2b8d6db0c46a059b83712adff1d74e2d63ac3fc915234698
This commit is contained in:
merge-script
2026-03-02 13:56:51 +01:00
3 changed files with 64 additions and 47 deletions

View File

@@ -77,29 +77,27 @@ static void secp256k1_dleq_challenge(secp256k1_scalar *e, secp256k1_ge *gen2, se
secp256k1_scalar_set_b32(e, buf, NULL);
}
/* P1 = x*G, P2 = x*Y */
static void secp256k1_dleq_pair(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_ge *p1, secp256k1_ge *p2, const secp256k1_scalar *sk, const secp256k1_ge *gen2) {
secp256k1_gej p1j, p2j;
/* p[0] = x*G, p[1] = x*Y */
static void secp256k1_dleq_pair(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_ge *p, const secp256k1_scalar *sk, const secp256k1_ge *gen2) {
secp256k1_gej pj[2];
secp256k1_ecmult_gen(ecmult_gen_ctx, &p1j, sk);
secp256k1_ge_set_gej(p1, &p1j);
secp256k1_ecmult_const(&p2j, gen2, sk);
secp256k1_ge_set_gej(p2, &p2j);
secp256k1_ecmult_gen(ecmult_gen_ctx, &pj[0], sk);
secp256k1_ecmult_const(&pj[1], gen2, sk);
secp256k1_ge_set_all_gej(p, pj, 2);
}
/* Generates a proof that the discrete logarithm of P1 to the secp256k1 base G is the
* same as the discrete logarithm of P2 to the base Y */
static int secp256k1_dleq_prove(const secp256k1_context* ctx, secp256k1_scalar *s, secp256k1_scalar *e, const secp256k1_scalar *sk, secp256k1_ge *gen2, secp256k1_ge *p1, secp256k1_ge *p2, secp256k1_nonce_function_hardened_ecdsa_adaptor noncefp, void *ndata) {
secp256k1_ge r1, r2;
secp256k1_ge r[2];
secp256k1_scalar k = { 0 };
unsigned char sk32[32];
unsigned char gen2_33[33];
unsigned char p1_33[33];
unsigned char p2_33[33];
int ret = 1;
size_t pubkey_size = 33;
int ret;
secp256k1_scalar_get_b32(sk32, sk);
if (!secp256k1_eckey_pubkey_serialize(gen2, gen2_33, &pubkey_size, 1)) {
return 0;
}
@@ -110,22 +108,30 @@ static int secp256k1_dleq_prove(const secp256k1_context* ctx, secp256k1_scalar *
return 0;
}
ret &= secp256k1_dleq_nonce(&k, sk32, gen2_33, p1_33, p2_33, noncefp, ndata);
/* R1 = k*G, R2 = k*Y */
secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, &r1, &r2, &k, gen2);
/* We declassify the non-secret values r1 and r2 to allow using them as
* branch points. */
secp256k1_declassify(ctx, &r1, sizeof(r1));
secp256k1_declassify(ctx, &r2, sizeof(r2));
secp256k1_scalar_get_b32(sk32, sk);
/* e = tagged hash(p1, gen2, p2, r1, r2) */
ret = secp256k1_dleq_nonce(&k, sk32, gen2_33, p1_33, p2_33, noncefp, ndata);
secp256k1_declassify(ctx, &ret, sizeof(ret));
if (!ret) {
secp256k1_memclear_explicit(sk32, sizeof(sk32));
return 0;
}
/* R1 = k*G, R2 = k*Y */
secp256k1_dleq_pair(&ctx->ecmult_gen_ctx, r, &k, gen2);
/* We declassify the non-secret values r[0] and r[1] to allow using them as
* branch points. */
secp256k1_declassify(ctx, &r[0], sizeof(r[0]));
secp256k1_declassify(ctx, &r[1], sizeof(r[1]));
/* e = tagged hash(p1, gen2, p2, r[0], r[1]) */
/* s = k + e * sk */
secp256k1_dleq_challenge(e, gen2, &r1, &r2, p1, p2);
secp256k1_dleq_challenge(e, gen2, &r[0], &r[1], p1, p2);
secp256k1_scalar_mul(s, e, sk);
secp256k1_scalar_add(s, s, &k);
secp256k1_scalar_clear(&k);
return ret;
secp256k1_memclear_explicit(sk32, sizeof(sk32));
return 1;
}
static int secp256k1_dleq_verify(const secp256k1_scalar *s, const secp256k1_scalar *e, secp256k1_ge *p1, secp256k1_ge *gen2, secp256k1_ge *p2) {
@@ -133,8 +139,8 @@ static int secp256k1_dleq_verify(const secp256k1_scalar *s, const secp256k1_scal
secp256k1_scalar e_expected;
secp256k1_gej gen2j;
secp256k1_gej p1j, p2j;
secp256k1_gej r1j, r2j;
secp256k1_ge r1, r2;
secp256k1_gej rj[2];
secp256k1_ge r[2];
secp256k1_gej tmpj;
secp256k1_gej_set_ge(&p1j, p1);
@@ -142,16 +148,16 @@ static int secp256k1_dleq_verify(const secp256k1_scalar *s, const secp256k1_scal
secp256k1_scalar_negate(&e_neg, e);
/* R1 = s*G - e*P1 */
secp256k1_ecmult(&r1j, &p1j, &e_neg, s);
secp256k1_ecmult(&rj[0], &p1j, &e_neg, s);
/* R2 = s*gen2 - e*P2 */
secp256k1_ecmult(&tmpj, &p2j, &e_neg, &secp256k1_scalar_zero);
secp256k1_gej_set_ge(&gen2j, gen2);
secp256k1_ecmult(&r2j, &gen2j, s, &secp256k1_scalar_zero);
secp256k1_gej_add_var(&r2j, &r2j, &tmpj, NULL);
secp256k1_ecmult(&rj[1], &gen2j, s, &secp256k1_scalar_zero);
secp256k1_gej_add_var(&rj[1], &rj[1], &tmpj, NULL);
secp256k1_ge_set_gej(&r1, &r1j);
secp256k1_ge_set_gej(&r2, &r2j);
secp256k1_dleq_challenge(&e_expected, gen2, &r1, &r2, p1, p2);
secp256k1_ge_set_all_gej_var(r, rj, 2);
secp256k1_dleq_challenge(&e_expected, gen2, &r[0], &r[1], p1, p2);
secp256k1_scalar_add(&e_expected, &e_expected, &e_neg);
return secp256k1_scalar_is_zero(&e_expected);

View File

@@ -150,8 +150,8 @@ const secp256k1_nonce_function_hardened_ecdsa_adaptor secp256k1_nonce_function_e
int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char *adaptor_sig162, unsigned char *seckey32, const secp256k1_pubkey *enckey, const unsigned char *msg32, secp256k1_nonce_function_hardened_ecdsa_adaptor noncefp, void *ndata) {
secp256k1_scalar k;
secp256k1_gej rj, rpj;
secp256k1_ge r, rp;
secp256k1_ge nonce_pts[2];
secp256k1_gej nonce_ptj[2];
secp256k1_ge enckey_ge;
secp256k1_scalar dleq_proof_s;
secp256k1_scalar dleq_proof_e;
@@ -179,32 +179,39 @@ int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char
noncefp = secp256k1_nonce_function_ecdsa_adaptor;
}
ret &= secp256k1_pubkey_load(ctx, &enckey_ge, enckey);
ret &= secp256k1_eckey_pubkey_serialize(&enckey_ge, buf33, &size, 1);
if (!secp256k1_pubkey_load(ctx, &enckey_ge, enckey)) {
return 0;
}
secp256k1_eckey_pubkey_serialize(&enckey_ge, buf33, &size, 1);
ret &= !!noncefp(nonce32, msg32, seckey32, buf33, ecdsa_adaptor_algo, sizeof(ecdsa_adaptor_algo), ndata);
secp256k1_scalar_set_b32(&k, nonce32, NULL);
ret &= !secp256k1_scalar_is_zero(&k);
secp256k1_scalar_cmov(&k, &secp256k1_scalar_one, !ret);
/* R' := k*G */
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &rpj, &k);
secp256k1_ge_set_gej(&rp, &rpj);
/* R = k*Y; */
secp256k1_ecmult_const(&rj, &enckey_ge, &k);
secp256k1_ge_set_gej(&r, &rj);
/* We declassify the non-secret values rp and r to allow using them
* as branch points. */
secp256k1_declassify(ctx, &rp, sizeof(rp));
secp256k1_declassify(ctx, &r, sizeof(r));
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &nonce_ptj[0], &k);
/* R := k*Y */
secp256k1_ecmult_const(&nonce_ptj[1], &enckey_ge, &k);
secp256k1_ge_set_all_gej(nonce_pts, nonce_ptj, 2);
/* We declassify the non-secret nonce values to allow using them as branch points. */
secp256k1_declassify(ctx, &nonce_pts[0], sizeof(nonce_pts[0]));
secp256k1_declassify(ctx, &nonce_pts[1], sizeof(nonce_pts[1]));
/* dleq_proof = DLEQ_prove(k, (R', Y, R)) */
ret &= secp256k1_dleq_prove(ctx, &dleq_proof_s, &dleq_proof_e, &k, &enckey_ge, &rp, &r, noncefp, ndata);
if (!secp256k1_dleq_prove(ctx, &dleq_proof_s, &dleq_proof_e, &k, &enckey_ge, &nonce_pts[0], &nonce_pts[1], noncefp, ndata)) {
memset(adaptor_sig162, 0, 162);
secp256k1_memclear_explicit(nonce32, sizeof(nonce32));
secp256k1_scalar_clear(&k);
return 0;
}
ret &= secp256k1_scalar_set_b32_seckey(&sk, seckey32);
secp256k1_scalar_cmov(&sk, &secp256k1_scalar_one, !ret);
secp256k1_scalar_set_b32(&msg, msg32, NULL);
secp256k1_fe_normalize(&r.x);
secp256k1_fe_get_b32(buf33, &r.x);
secp256k1_fe_normalize(&nonce_pts[1].x);
secp256k1_fe_get_b32(buf33, &nonce_pts[1].x);
secp256k1_scalar_set_b32(&sigr, buf33, NULL);
ret &= !secp256k1_scalar_is_zero(&sigr);
/* s' = k⁻¹(m + R.x * x) */
@@ -215,9 +222,10 @@ int secp256k1_ecdsa_adaptor_encrypt(const secp256k1_context* ctx, unsigned char
ret &= !secp256k1_scalar_is_zero(&sp);
/* return (R, R', s', dleq_proof) */
ret &= secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig162, &r, &rp, &sp, &dleq_proof_e, &dleq_proof_s);
ret &= secp256k1_ecdsa_adaptor_sig_serialize(adaptor_sig162, &nonce_pts[1], &nonce_pts[0], &sp, &dleq_proof_e, &dleq_proof_s);
secp256k1_memczero(adaptor_sig162, 162, !ret);
secp256k1_memclear_explicit(nonce32, sizeof(nonce32));
secp256k1_scalar_clear(&n);
secp256k1_scalar_clear(&k);
secp256k1_scalar_clear(&sk);

View File

@@ -31,6 +31,7 @@ static void dleq_nonce_bitflip(unsigned char **args, size_t n_flip, size_t n_byt
static void dleq_tests_internal(void) {
secp256k1_scalar s, e, sk, k;
secp256k1_ge gen2, p1, p2;
secp256k1_ge p[2];
unsigned char *args[5];
unsigned char sk32[32];
unsigned char gen2_33[33];
@@ -42,7 +43,9 @@ static void dleq_tests_internal(void) {
rand_point(&gen2);
rand_scalar(&sk);
secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, &p1, &p2, &sk, &gen2);
secp256k1_dleq_pair(&CTX->ecmult_gen_ctx, p, &sk, &gen2);
p1 = p[0];
p2 = p[1];
CHECK(secp256k1_dleq_prove(CTX, &s, &e, &sk, &gen2, &p1, &p2, NULL, NULL) == 1);
CHECK(secp256k1_dleq_verify(&s, &e, &p1, &gen2, &p2) == 1);