Merge elementsproject/secp256k1-zkp#369: surjection: bind genrand nonce to the full statement
65093e1444surjection: prevent s-value reuse for different proof inputs (mllwchrry) Pull request description: ACKs for top commit: jonasnick: ACK65093e1444Tree-SHA512: 563c91fc40d962064efed2fcfc70bbf6f25fc9be049feff95f88cbf62141056cfbe16ae000e828cdbde023aa271436910ed8f4930e244221fc49fb9e838fe2bd
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
SECP256K1_INLINE static int secp256k1_surjection_genmessage(unsigned char *msg32, secp256k1_ge *ephemeral_input_tags, size_t n_input_tags, secp256k1_ge *ephemeral_output_tag);
|
||||
|
||||
SECP256K1_INLINE static int secp256k1_surjection_genrand(secp256k1_scalar *s, size_t ns, const secp256k1_scalar *blinding_key);
|
||||
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);
|
||||
|
||||
SECP256K1_INLINE static int secp256k1_surjection_compute_public_keys(secp256k1_gej *pubkeys, size_t n_pubkeys, const secp256k1_ge *input_tags, size_t n_input_tags, const unsigned char *used_tags, const secp256k1_ge *output_tag, size_t input_index, size_t *ring_input_index);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -643,6 +643,147 @@ static void test_gen_verify_all(void) {
|
||||
test_gen_verify(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS);
|
||||
}
|
||||
|
||||
static int surjection_genrand_streams_equal(const secp256k1_scalar *a, const secp256k1_scalar *b, size_t n) {
|
||||
size_t i;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (!secp256k1_scalar_eq(&a[i], &b[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int surjection_genrand_stream_all_differ(const secp256k1_scalar *a, const secp256k1_scalar *b, size_t n) {
|
||||
size_t i;
|
||||
for (i = 0; i < n; i++) {
|
||||
if (secp256k1_scalar_eq(&a[i], &b[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/* Test that changing any proof-relevant argument to
|
||||
* secp256k1_surjectionproof_generate changes every s-value produced by
|
||||
* secp256k1_surjection_genrand. */
|
||||
static void test_surjection_genrand(void) {
|
||||
const secp256k1_hash_ctx *hash_ctx = secp256k1_get_hash_context(CTX);
|
||||
const size_t ns = 4;
|
||||
const size_t n_inputs = 5;
|
||||
unsigned char msg_a[32];
|
||||
unsigned char msg_b[32];
|
||||
unsigned char ikey_a[32];
|
||||
unsigned char ikey_b[32];
|
||||
unsigned char okey_a[32];
|
||||
unsigned char okey_b[32];
|
||||
unsigned char used_a[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS / 8] = { 0 };
|
||||
unsigned char used_b[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS / 8] = { 0 };
|
||||
secp256k1_scalar s_base[4];
|
||||
secp256k1_scalar s_cmp[4];
|
||||
|
||||
/* Baseline inputs, plus a one-bit variant of each. */
|
||||
testrand256(msg_a);
|
||||
memcpy(msg_b, msg_a, 32);
|
||||
msg_b[0] ^= 0x01;
|
||||
testrand256(ikey_a);
|
||||
memcpy(ikey_b, ikey_a, 32);
|
||||
ikey_b[0] ^= 0x01;
|
||||
testrand256(okey_a);
|
||||
memcpy(okey_b, okey_a, 32);
|
||||
okey_b[0] ^= 0x01;
|
||||
|
||||
/* Two used-input bitmaps with the same popcount but a different set. */
|
||||
used_a[0] = 0x0f; /* inputs {0,1,2,3} */
|
||||
used_b[0] = 0x17; /* inputs {0,1,2,4} */
|
||||
|
||||
/* Baseline. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_base, ns, n_inputs, used_a, msg_a, 0, ikey_a, okey_a) == 1);
|
||||
|
||||
/* Determinism: identical arguments reproduce the identical stream. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, n_inputs, used_a, msg_a, 0, ikey_a, okey_a) == 1);
|
||||
CHECK(surjection_genrand_streams_equal(s_base, s_cmp, ns));
|
||||
|
||||
/* The message is bound. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, n_inputs, used_a, msg_b, 0, ikey_a, okey_a) == 1);
|
||||
CHECK(surjection_genrand_stream_all_differ(s_base, s_cmp, ns));
|
||||
|
||||
/* The used-input selection is bound. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, n_inputs, used_b, msg_a, 0, ikey_a, okey_a) == 1);
|
||||
CHECK(surjection_genrand_stream_all_differ(s_base, s_cmp, ns));
|
||||
|
||||
/* The total input count is bound even at the same bitmap byte length:
|
||||
* n_inputs = 8 and n_inputs = 5 both use a one-byte bitmap. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, 8, used_a, msg_a, 0, ikey_a, okey_a) == 1);
|
||||
CHECK(surjection_genrand_stream_all_differ(s_base, s_cmp, ns));
|
||||
|
||||
/* The honest input index is bound. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, n_inputs, used_a, msg_a, 1, ikey_a, okey_a) == 1);
|
||||
CHECK(surjection_genrand_stream_all_differ(s_base, s_cmp, ns));
|
||||
|
||||
/* The input blinding key is bound. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, n_inputs, used_a, msg_a, 0, ikey_b, okey_a) == 1);
|
||||
CHECK(surjection_genrand_stream_all_differ(s_base, s_cmp, ns));
|
||||
|
||||
/* The output blinding key is bound. */
|
||||
CHECK(secp256k1_surjection_genrand(hash_ctx, s_cmp, ns, n_inputs, used_a, msg_a, 0, ikey_a, okey_b) == 1);
|
||||
CHECK(surjection_genrand_stream_all_differ(s_base, s_cmp, ns));
|
||||
}
|
||||
|
||||
/* Changing an unused input tag changes the proof message. Check that every
|
||||
* s-value changes and that both proofs verify. */
|
||||
static void test_surjectionproof_generate_changes_s_values(void) {
|
||||
const size_t n_inputs = 3;
|
||||
const size_t n_used = 2;
|
||||
unsigned char seed[32];
|
||||
unsigned char input_blinding_key[3][32] = {{ 0 }};
|
||||
unsigned char output_blinding_key[32] = { 0 };
|
||||
unsigned char reblind[32] = { 0 };
|
||||
secp256k1_fixed_asset_tag fixed_input_tags[3];
|
||||
secp256k1_generator ephemeral_input_tags[3];
|
||||
secp256k1_generator ephemeral_output_tag;
|
||||
secp256k1_surjectionproof proof_a;
|
||||
secp256k1_surjectionproof proof_b;
|
||||
size_t input_index;
|
||||
size_t unused_index = n_inputs;
|
||||
size_t i;
|
||||
|
||||
testrand256(seed);
|
||||
for (i = 0; i < n_inputs; i++) {
|
||||
testrand256(fixed_input_tags[i].data);
|
||||
input_blinding_key[i][31] = (unsigned char)i + 1;
|
||||
CHECK(secp256k1_generator_generate_blinded(CTX, &ephemeral_input_tags[i], fixed_input_tags[i].data, input_blinding_key[i]));
|
||||
}
|
||||
output_blinding_key[31] = 4;
|
||||
reblind[31] = 5;
|
||||
CHECK(secp256k1_generator_generate_blinded(CTX, &ephemeral_output_tag, fixed_input_tags[1].data, output_blinding_key));
|
||||
|
||||
CHECK(secp256k1_surjectionproof_initialize(CTX, &proof_a, &input_index, fixed_input_tags, n_inputs, n_used, &fixed_input_tags[1], 100, seed) > 0);
|
||||
CHECK(input_index == 1);
|
||||
CHECK(secp256k1_surjectionproof_n_used_inputs(CTX, &proof_a) == n_used);
|
||||
proof_b = proof_a;
|
||||
|
||||
for (i = 0; i < n_inputs; i++) {
|
||||
if (!(proof_a.used_inputs[i / 8] & (1 << (i % 8)))) {
|
||||
unused_index = i;
|
||||
}
|
||||
}
|
||||
CHECK(unused_index < n_inputs);
|
||||
|
||||
CHECK(secp256k1_surjectionproof_generate(CTX, &proof_a, ephemeral_input_tags, n_inputs, &ephemeral_output_tag, input_index, input_blinding_key[input_index], output_blinding_key) == 1);
|
||||
CHECK(secp256k1_surjectionproof_verify(CTX, &proof_a, ephemeral_input_tags, n_inputs, &ephemeral_output_tag) == 1);
|
||||
|
||||
/* Regenerate the unused ephemeral input tag with reblind instead of
|
||||
* input_blinding_key[unused_index]. The modified tag is the only proof input
|
||||
* that differs between the two secp256k1_surjectionproof_generate calls. */
|
||||
CHECK(secp256k1_generator_generate_blinded(CTX, &ephemeral_input_tags[unused_index], fixed_input_tags[unused_index].data, reblind));
|
||||
CHECK(secp256k1_surjectionproof_generate(CTX, &proof_b, ephemeral_input_tags, n_inputs, &ephemeral_output_tag, input_index, input_blinding_key[input_index], output_blinding_key) == 1);
|
||||
CHECK(secp256k1_surjectionproof_verify(CTX, &proof_b, ephemeral_input_tags, n_inputs, &ephemeral_output_tag) == 1);
|
||||
|
||||
for (i = 0; i < n_used; i++) {
|
||||
CHECK(secp256k1_memcmp_var(&proof_a.data[32 + 32 * i], &proof_b.data[32 + 32 * i], 32) != 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Test registry --- */
|
||||
static const struct tf_test_entry tests_surjection[] = {
|
||||
CASE1(test_surjectionproof_api),
|
||||
@@ -651,6 +792,8 @@ static const struct tf_test_entry tests_surjection[] = {
|
||||
CASE1(test_input_selection_all),
|
||||
CASE1(test_input_selection_distribution),
|
||||
CASE1(test_gen_verify_all),
|
||||
CASE1(test_surjection_genrand),
|
||||
CASE1(test_surjectionproof_generate_changes_s_values),
|
||||
CASE1(test_no_used_inputs_verify),
|
||||
CASE1(test_bad_serialize),
|
||||
CASE1(test_bad_parse),
|
||||
|
||||
Reference in New Issue
Block a user