surjection: prevent s-value reuse for different proof inputs

The s-values produced by secp256k1_surjection_genrand previously
depended only on their indices and the difference between
input_blinding_key and output_blinding_key. Calls with the same
difference therefore reused s-values even when their proof inputs
differed.

For proofs with the same used-input selection and honest input index,
the same generated s-value was used as the signing nonce. Reusing this
nonce across different proof messages allowed recovery of the
blinding-key difference. The remaining repeated s-values also revealed
the honest input index.

This commit affects proof generation only; verification is unchanged.
This commit is contained in:
mllwchrry
2026-08-10 11:29:49 +03:00
parent 2af926dc30
commit 65093e1444
4 changed files with 192 additions and 15 deletions

View File

@@ -35,31 +35,52 @@ SECP256K1_INLINE static void secp256k1_surjection_genmessage(const secp256k1_has
secp256k1_sha256_clear(&sha256_en);
}
SECP256K1_INLINE static int secp256k1_surjection_genrand(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *s, size_t ns, const secp256k1_scalar *blinding_key) {
/* Derive the ring's s-values, one of which is used as the signing nonce, from a
* seed that hashes the passed-in arguments. See the call site for how these
* correspond to the proof inputs. */
SECP256K1_INLINE static int secp256k1_surjection_genrand(const secp256k1_hash_ctx *hash_ctx, secp256k1_scalar *s, size_t ns, size_t n_inputs, const unsigned char *used_inputs, const unsigned char *msg32, size_t input_index, const unsigned char *input_blinding_key, const unsigned char *output_blinding_key) {
size_t i;
unsigned char sec_input[36];
size_t used_inputs_len;
unsigned char n_inputs_ser[4];
unsigned char index_ser[4];
unsigned char counter[4];
unsigned char seed[32];
unsigned char out[32];
secp256k1_sha256 sha256_en;
used_inputs_len = (n_inputs + 7) / 8;
secp256k1_write_be32(n_inputs_ser, (uint32_t)n_inputs);
secp256k1_write_be32(index_ser, (uint32_t)input_index);
/* Hash the arguments into the seed. */
secp256k1_sha256_initialize(&sha256_en);
secp256k1_sha256_write(hash_ctx, &sha256_en, n_inputs_ser, 4);
secp256k1_sha256_write(hash_ctx, &sha256_en, used_inputs, used_inputs_len);
secp256k1_sha256_write(hash_ctx, &sha256_en, msg32, 32);
secp256k1_sha256_write(hash_ctx, &sha256_en, index_ser, 4);
secp256k1_sha256_write(hash_ctx, &sha256_en, input_blinding_key, 32);
secp256k1_sha256_write(hash_ctx, &sha256_en, output_blinding_key, 32);
secp256k1_sha256_finalize(hash_ctx, &sha256_en, seed);
secp256k1_sha256_clear(&sha256_en);
/* compute s values */
secp256k1_scalar_get_b32(&sec_input[4], blinding_key);
for (i = 0; i < ns; i++) {
int overflow = 0;
sec_input[0] = i;
sec_input[1] = i >> 8;
sec_input[2] = i >> 16;
sec_input[3] = i >> 24;
secp256k1_write_be32(counter, (uint32_t)i);
secp256k1_sha256_initialize(&sha256_en);
secp256k1_sha256_write(hash_ctx, &sha256_en, sec_input, 36);
secp256k1_sha256_finalize(hash_ctx, &sha256_en, sec_input);
secp256k1_sha256_write(hash_ctx, &sha256_en, counter, 4);
secp256k1_sha256_write(hash_ctx, &sha256_en, seed, 32);
secp256k1_sha256_finalize(hash_ctx, &sha256_en, out);
secp256k1_sha256_clear(&sha256_en);
secp256k1_scalar_set_b32(&s[i], sec_input, &overflow);
secp256k1_scalar_set_b32(&s[i], out, &overflow);
if (overflow == 1) {
secp256k1_memclear_explicit(sec_input, 32);
secp256k1_memclear_explicit(out, sizeof(out));
secp256k1_memclear_explicit(seed, sizeof(seed));
return 0;
}
}
secp256k1_memclear_explicit(sec_input, 32);
secp256k1_memclear_explicit(out, sizeof(out));
secp256k1_memclear_explicit(seed, sizeof(seed));
return 1;
}