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

@@ -337,7 +337,20 @@ int secp256k1_surjectionproof_generate(const secp256k1_context* ctx, secp256k1_s
rsizes[0] = (int) n_used_pubkeys;
indices[0] = (int) ring_input_index;
secp256k1_surjection_genmessage(hash_ctx, msg32, ephemeral_input_tags, n_total_pubkeys, ephemeral_output_tag);
if (secp256k1_surjection_genrand(hash_ctx, borromean_s, n_used_pubkeys, &blinding_key) == 0) {
/* Derive every s-value, including the one used as the signing nonce, from
* every proof-relevant input to
* secp256k1_surjectionproof_generate. Except with negligible hash-collision
* probability, this prevents distinct proof inputs from reusing any
* s-value.
*
* The proof-relevant arguments to secp256k1_surjectionproof_generate
* correspond as follows: proof supplies n_total_pubkeys (proof->n_inputs),
* proof->used_inputs, and n_used_pubkeys, while proof->data is output and
* proof->initialized is VERIFY-only validation state; ephemeral_input_tags
* is committed by msg32; n_ephemeral_input_tags equals n_total_pubkeys as
* checked above; ephemeral_output_tag is committed by msg32; input_index
* and both blinding keys are passed directly. */
if (secp256k1_surjection_genrand(hash_ctx, borromean_s, n_used_pubkeys, n_total_pubkeys, proof->used_inputs, msg32, input_index, input_blinding_key, output_blinding_key) == 0) {
return 0;
}
/* Borromean sign will overwrite one of the s values we just generated, so use