From 828196889b0b348fe2b1a2c1fe00af4b946458bc Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Aug 2022 20:30:52 +0000 Subject: [PATCH 1/6] rangeproof: add method to verify single-value proofs --- include/secp256k1_rangeproof.h | 23 +++++++ src/modules/rangeproof/main_impl.h | 93 +++++++++++++++++++++++++++++ src/modules/rangeproof/tests_impl.h | 2 + 3 files changed, 118 insertions(+) diff --git a/include/secp256k1_rangeproof.h b/include/secp256k1_rangeproof.h index 9bb014545..678e3674c 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -296,6 +296,29 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info( size_t plen ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); +/** Verify a rangeproof with a single-value range. Useful as a "proof of value" + * of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_sign` + * by passing an `exp` parameter of -1 and the target value as both `value` and `min_value`. + * (In this case `min_bits` is ignored and may take any value, but for clarity it's best + * to pass zero.) + * Returns 1: Proof was valid and proved the given value + * 0: Otherwise + * In: ctx: pointer to a context object + * proof: pointer to character array with the proof. + * plen: length of proof in bytes. + * value: value being claimed for the Pedersen commitment + * commit: the Pedersen commitment whose value is being verified + * gen: additional generator 'h' + */ +SECP256K1_API int secp256k1_rangeproof_verify_value( + const secp256k1_context* ctx, + const unsigned char* proof, + size_t plen, + uint64_t value, + const secp256k1_pedersen_commitment* commit, + const secp256k1_generator* gen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); + /** Returns an upper bound on the size of a rangeproof with the given parameters * * An actual rangeproof may be smaller, for example if the actual value diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index 432f4b959..a8746d9d4 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -304,6 +304,99 @@ int secp256k1_rangeproof_sign(const secp256k1_context* ctx, unsigned char *proof proof, plen, min_value, &commitp, blind, nonce, exp, min_bits, value, message, msg_len, extra_commit, extra_commit_len, &genp); } +int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsigned char* proof, size_t plen, uint64_t value, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) { + secp256k1_ge commitp; + secp256k1_ge genp; + secp256k1_gej tmpj; + secp256k1_gej xj; + secp256k1_ge rp; + secp256k1_scalar es; + secp256k1_scalar ss; + secp256k1_sha256 sha2; + unsigned char tmpch[33]; + unsigned char pp_comm[32]; + size_t offset; + size_t sz; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(proof != NULL); + ARG_CHECK(commit != NULL); + ARG_CHECK(gen != NULL); + + if (plen != 73 && plen != 65) { + return 0; + } + /* 0x80 must be unset for any rangeproof; 0x40 indicates "has nonzero range" + * so must also be unset for single-value proofs */ + if ((proof[0] & 0xC0) != 0x00) { + return 0; + } + + secp256k1_pedersen_commitment_load(&commitp, commit); + secp256k1_generator_load(&genp, gen); + /* Verify that value in the header is what we expect; 0x20 is "has nonzero min-value" */ + if ((proof[0] & 0x20) == 0x00) { + if (value != 0) { + return 0; + } + offset = 1; + } else { + uint64_t claimed = 0; + /* Iterate from 0 to 8, setting `offset` to 9 as a side-effect */ + for (offset = 1; offset < 9; offset++) { + claimed = (claimed << 8) | proof[offset]; + } + if (value != claimed) { + return 0; + } + } + /* Subtract value from commitment; store modified commitment in xj */ + secp256k1_pedersen_ecmult_small(&tmpj, value, &genp); + secp256k1_gej_neg(&tmpj, &tmpj); + secp256k1_gej_add_ge_var(&xj, &tmpj, &commitp, NULL); + + /* Now we just have a Schnorr signature in (e, s) form. The verification + * equation is e == H(sG - eX || proof params) */ + + /* 1. Compute slow/overwrought commitment to proof params */ + secp256k1_sha256_initialize(&sha2); + secp256k1_rangeproof_serialize_point(tmpch, &commitp); + secp256k1_sha256_write(&sha2, tmpch, 33); + secp256k1_rangeproof_serialize_point(tmpch, &genp); + secp256k1_sha256_write(&sha2, tmpch, 33); + secp256k1_sha256_write(&sha2, proof, offset); + secp256k1_sha256_finalize(&sha2, pp_comm); + + /* ... feed this into our hash */ + secp256k1_borromean_hash(tmpch, pp_comm, 32, &proof[offset], 32, 0, 0); + secp256k1_scalar_set_b32(&es, tmpch, &overflow); + if (overflow || secp256k1_scalar_is_zero(&es)) { + return 0; + } + + /* 1. Compute R = sG - eX */ + secp256k1_scalar_set_b32(&ss, &proof[offset + 32], &overflow); + if (overflow || secp256k1_scalar_is_zero(&ss)) { + return 0; + } + secp256k1_ecmult(&tmpj, &xj, &es, &ss); + if (secp256k1_gej_is_infinity(&tmpj)) { + return 0; + } + secp256k1_ge_set_gej(&rp, &tmpj); + secp256k1_eckey_pubkey_serialize(&rp, tmpch, &sz, 1); + + /* 2. Compute e = H(R || proof params) */ + secp256k1_sha256_initialize(&sha2); + secp256k1_sha256_write(&sha2, tmpch, sz); + secp256k1_sha256_write(&sha2, pp_comm, sizeof(pp_comm)); + secp256k1_sha256_finalize(&sha2, tmpch); + + /* 3. Check computed e against original e */ + return !secp256k1_memcmp_var(tmpch, &proof[offset], 32); +} + size_t secp256k1_rangeproof_max_size(const secp256k1_context* ctx, uint64_t max_value, int min_bits) { const int val_mantissa = max_value > 0 ? 64 - secp256k1_clz64_var(max_value) : 1; const int mantissa = min_bits > val_mantissa ? min_bits : val_mantissa; diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 9c9207343..116974ba1 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -983,6 +983,8 @@ void test_rangeproof_fixed_vectors(void) { CHECK(min_value == UINT64_MAX); CHECK(max_value == UINT64_MAX); CHECK(m_len == 0); + + CHECK(secp256k1_rangeproof_verify_value(ctx, vector_3, sizeof(vector_3), UINT64_MAX, &pc, secp256k1_generator_h)); } } From 0b7cf31184f8a1f5502b94b28a476126151ea48c Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 22 Jul 2022 22:44:08 +0000 Subject: [PATCH 2/6] surjectionproof: add method to verify single-input proofs --- include/secp256k1_surjectionproof.h | 18 ++++++++ src/modules/surjection/main_impl.h | 70 +++++++++++++++++++++++++++++ src/modules/surjection/tests_impl.h | 5 +++ 3 files changed, 93 insertions(+) diff --git a/include/secp256k1_surjectionproof.h b/include/secp256k1_surjectionproof.h index ab7a4a9ec..cf08c7fcc 100644 --- a/include/secp256k1_surjectionproof.h +++ b/include/secp256k1_surjectionproof.h @@ -263,6 +263,24 @@ SECP256K1_API int secp256k1_surjectionproof_verify( ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5); #endif +/** Verify a single-input surjectionproof. Such proofs are sometimes useful to + * prove in zero knowledge that a given commitment commits to a specific asset. + * They can be verified with much less memory than general proofs. + * Returns 0: proof was invalid + * 1: proof was valid + * + * In: ctx: pointer to a context object, initialized for signing and verification + * proof: proof to be verified + * input_tag: the ephemeral asset tag of the sole input + * output_tag: the ephemeral asset tag of the output + */ +SECP256K1_API int secp256k1_surjectionproof_verify_single( + const secp256k1_context* ctx, + const secp256k1_surjectionproof* proof, + const secp256k1_generator* input_tag, + const secp256k1_generator* output_tag +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); + #ifdef __cplusplus } #endif diff --git a/src/modules/surjection/main_impl.h b/src/modules/surjection/main_impl.h index a214f90b3..d9966765f 100644 --- a/src/modules/surjection/main_impl.h +++ b/src/modules/surjection/main_impl.h @@ -401,4 +401,74 @@ int secp256k1_surjectionproof_verify(const secp256k1_context* ctx, const secp256 return secp256k1_borromean_verify(NULL, &proof->data[0], borromean_s, ring_pubkeys, rsizes, 1, msg32, 32); } +int secp256k1_surjectionproof_verify_single(const secp256k1_context* ctx, const secp256k1_surjectionproof* proof, const secp256k1_generator* input_tag, const secp256k1_generator* output_tag) { + secp256k1_ge inputp; + secp256k1_ge outputp; + secp256k1_gej tmpj; + secp256k1_gej xj; + secp256k1_ge rp; + secp256k1_scalar es; + secp256k1_scalar ss; + secp256k1_sha256 sha2; + unsigned char tmpch[33]; + unsigned char pp_comm[32]; + size_t sz; + int overflow; + + /* Validate and decode surjectionproof data */ + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(proof != NULL); + ARG_CHECK(input_tag != NULL); + ARG_CHECK(output_tag != NULL); +#ifdef VERIFY + CHECK(proof->initialized == 1); +#endif + + if (proof->n_inputs != 1 || proof->used_inputs[0] != 1) { + return 0; + } + + secp256k1_generator_load(&inputp, input_tag); + secp256k1_generator_load(&outputp, output_tag); + secp256k1_ge_neg(&inputp, &inputp); + secp256k1_gej_set_ge(&xj, &inputp); + secp256k1_gej_add_ge(&xj, &xj, &outputp); + + /* Now we just have a Schnorr signature in (e, s) form. The verification + * equation is e == H(sG - eX || proof params), where X is the difference + * between the output and input. */ + + /* 1. Compute slow/overwrought commitment to proof params */ + secp256k1_surjection_genmessage(pp_comm, input_tag, 1, output_tag); + /* (past this point the code is identical to rangeproof_verify_value) */ + + /* ... feed this into our hash */ + secp256k1_borromean_hash(tmpch, pp_comm, 32, &proof->data[0], 32, 0, 0); + secp256k1_scalar_set_b32(&es, tmpch, &overflow); + if (overflow || secp256k1_scalar_is_zero(&es)) { + return 0; + } + + /* 1. Compute R = sG - eX */ + secp256k1_scalar_set_b32(&ss, &proof->data[32], &overflow); + if (overflow || secp256k1_scalar_is_zero(&ss)) { + return 0; + } + secp256k1_ecmult(&tmpj, &xj, &es, &ss); + if (secp256k1_gej_is_infinity(&tmpj)) { + return 0; + } + secp256k1_ge_set_gej(&rp, &tmpj); + secp256k1_eckey_pubkey_serialize(&rp, tmpch, &sz, 1); + + /* 2. Compute e = H(R || proof params) */ + secp256k1_sha256_initialize(&sha2); + secp256k1_sha256_write(&sha2, tmpch, sz); + secp256k1_sha256_write(&sha2, pp_comm, sizeof(pp_comm)); + secp256k1_sha256_finalize(&sha2, tmpch); + + /* 3. Check computed e against original e */ + return !secp256k1_memcmp_var(tmpch, &proof->data[0], 32); +} + #endif diff --git a/src/modules/surjection/tests_impl.h b/src/modules/surjection/tests_impl.h index a792bb5f6..cb873798b 100644 --- a/src/modules/surjection/tests_impl.h +++ b/src/modules/surjection/tests_impl.h @@ -445,6 +445,10 @@ static void test_gen_verify(size_t n_inputs, size_t n_used) { CHECK(secp256k1_surjectionproof_parse(ctx, &proof, serialized_proof, serialized_len)); result = secp256k1_surjectionproof_verify(ctx, &proof, ephemeral_input_tags, n_inputs, &ephemeral_input_tags[n_inputs]); CHECK(result == 1); + if (n_inputs == 1) { + result = secp256k1_surjectionproof_verify_single(ctx, &proof, ephemeral_input_tags, &ephemeral_input_tags[n_inputs]); + CHECK(result == 1); + } /* various fail cases */ if (n_inputs > 1) { @@ -720,6 +724,7 @@ void run_surjection_tests(void) { test_input_selection(SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS); test_input_selection_distribution(); + test_gen_verify(1, 1); test_gen_verify(10, 3); test_gen_verify(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_USED_INPUTS); test_no_used_inputs_verify(); From b6b33c928d0124da76b2713844fa849c0b5a69ae Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Aug 2022 20:32:03 +0000 Subject: [PATCH 3/6] rangeproof: add verify_value function to create single-value proofs --- include/secp256k1_rangeproof.h | 30 +++++++- src/modules/rangeproof/main_impl.h | 115 +++++++++++++++++++++++++++- src/modules/rangeproof/tests_impl.h | 45 ++++++++++- 3 files changed, 183 insertions(+), 7 deletions(-) diff --git a/include/secp256k1_rangeproof.h b/include/secp256k1_rangeproof.h index 678e3674c..845d6a018 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -297,10 +297,10 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info( ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); /** Verify a rangeproof with a single-value range. Useful as a "proof of value" - * of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_sign` - * by passing an `exp` parameter of -1 and the target value as both `value` and `min_value`. - * (In this case `min_bits` is ignored and may take any value, but for clarity it's best - * to pass zero.) + * of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_create_value`, + * or with `secp256k1_rangeproof_sign` by passing an `exp` parameter of -1 and the + * target value as both `value` and `min_value`. (In this case `min_bits` is ignored + * and may take any value, but for clarity it's best to pass zero.) * Returns 1: Proof was valid and proved the given value * 0: Otherwise * In: ctx: pointer to a context object @@ -319,6 +319,28 @@ SECP256K1_API int secp256k1_rangeproof_verify_value( const secp256k1_generator* gen ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); +/** Create a rangeproof with a single-value range. + * Returns 1: Proof was successfully generated + * 0: Otherwise. The contents of `proof` are unspecified in this case. + * Args: ctx: pointer to a context object + * Out: proof: pointer to character array to populate the proof with. Must be at least 73 + * bytes unless `value` is 0, in which case it must be at least 65 bytes + * In/Out: plen: length of the `proof` buffer; will be overwritten with the actual length + * In: value: value being claimed for the Pedersen commitment + * blind: the blinding factor for the Pedersen commitment `commit` + * commit: the Pedersen commitment whose value is being proven + * gen: additional generator 'h' + */ +SECP256K1_API int secp256k1_rangeproof_create_value( + const secp256k1_context* ctx, + unsigned char* proof, + size_t* plen, + uint64_t value, + const unsigned char* blind, + const secp256k1_pedersen_commitment* commit, + const secp256k1_generator* gen +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7); + /** Returns an upper bound on the size of a rangeproof with the given parameters * * An actual rangeproof may be smaller, for example if the actual value diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index a8746d9d4..e8bc1d950 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -359,7 +359,7 @@ int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsign /* Now we just have a Schnorr signature in (e, s) form. The verification * equation is e == H(sG - eX || proof params) */ - /* 1. Compute slow/overwrought commitment to proof params */ + /* 0. Compute slow/overwrought commitment to proof params */ secp256k1_sha256_initialize(&sha2); secp256k1_rangeproof_serialize_point(tmpch, &commitp); secp256k1_sha256_write(&sha2, tmpch, 33); @@ -375,7 +375,7 @@ int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsign return 0; } - /* 1. Compute R = sG - eX */ + /* 1. Compute R = sG + eX */ secp256k1_scalar_set_b32(&ss, &proof[offset + 32], &overflow); if (overflow || secp256k1_scalar_is_zero(&ss)) { return 0; @@ -397,6 +397,117 @@ int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsign return !secp256k1_memcmp_var(tmpch, &proof[offset], 32); } +int secp256k1_rangeproof_create_value(const secp256k1_context* ctx, unsigned char* proof, size_t* plen, uint64_t value, const unsigned char* blind, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) { + secp256k1_ge commitp; + secp256k1_ge genp; + secp256k1_gej tmpj; + secp256k1_ge tmpp; + secp256k1_scalar es; + secp256k1_scalar ks; + secp256k1_scalar xs; + secp256k1_sha256 sha2; + unsigned char tmpch[33]; + unsigned char pp_comm[32]; + size_t offset; + size_t sz; + int overflow; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx)); + ARG_CHECK(proof != NULL); + ARG_CHECK(plen != NULL); + ARG_CHECK(blind != NULL); + ARG_CHECK(commit != NULL); + ARG_CHECK(gen != NULL); + + if (*plen < (value == 0 ? 65 : 73)) { + return 0; + } + *plen = value == 0 ? 65 : 73; + + secp256k1_pedersen_commitment_load(&commitp, commit); + secp256k1_generator_load(&genp, gen); + + /* Encode header */ + if (value > 0) { + proof[0] = 0x20; + proof[1] = value >> 56; + proof[2] = value >> 48; + proof[3] = value >> 40; + proof[4] = value >> 32; + proof[5] = value >> 24; + proof[6] = value >> 16; + proof[7] = value >> 8; + proof[8] = value; + offset = 9; + } else { + proof[0] = 0x00; + offset = 1; + } + + /* Now we have to make a Schnorr signature in (e, s) form. */ + + /* 1. Compute random k */ + secp256k1_sha256_initialize(&sha2); + secp256k1_sha256_write(&sha2, blind, 32); + secp256k1_sha256_write(&sha2, proof, offset); + secp256k1_rangeproof_serialize_point(tmpch, &genp); + secp256k1_sha256_write(&sha2, tmpch, 33); + secp256k1_sha256_finalize(&sha2, tmpch); + secp256k1_scalar_set_b32(&ks, tmpch, &overflow); + if (overflow || secp256k1_scalar_is_zero(&ks)) { + secp256k1_scalar_clear(&ks); + memset(tmpch, 0, sizeof(tmpch)); + return 0; + } + + /* 2. Compute R = kG */ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &tmpj, &ks); + secp256k1_ge_set_gej(&tmpp, &tmpj); + + /* 3. Compute slow/overwrought commitment to proof params */ + secp256k1_sha256_initialize(&sha2); + secp256k1_rangeproof_serialize_point(tmpch, &commitp); + secp256k1_sha256_write(&sha2, tmpch, 33); + secp256k1_rangeproof_serialize_point(tmpch, &genp); + secp256k1_sha256_write(&sha2, tmpch, 33); + secp256k1_sha256_write(&sha2, proof, offset); + secp256k1_sha256_finalize(&sha2, pp_comm); + + /* 4. Compute e0 = H(R || proof params) and serialize it into the proof */ + secp256k1_sha256_initialize(&sha2); + secp256k1_eckey_pubkey_serialize(&tmpp, tmpch, &sz, 1); + secp256k1_sha256_write(&sha2, tmpch, sz); + secp256k1_sha256_write(&sha2, pp_comm, sizeof(pp_comm)); + secp256k1_sha256_finalize(&sha2, &proof[offset]); + + /* ... feed this into our hash e, along with e0 */ + secp256k1_borromean_hash(tmpch, pp_comm, 32, &proof[offset], 32, 0, 0); + secp256k1_scalar_set_b32(&es, tmpch, &overflow); + if (overflow || secp256k1_scalar_is_zero(&es)) { + secp256k1_scalar_clear(&ks); + secp256k1_scalar_clear(&es); + return 0; + } + + /* 5. Compute k - ex from this, and serialize it */ + secp256k1_scalar_set_b32(&xs, blind, &overflow); + if (overflow || secp256k1_scalar_is_zero(&xs)) { + secp256k1_scalar_clear(&ks); + secp256k1_scalar_clear(&xs); + secp256k1_scalar_clear(&es); + return 0; + } + secp256k1_scalar_mul(&es, &es, &xs); + secp256k1_scalar_negate(&es, &es); + secp256k1_scalar_add(&es, &es, &ks); + secp256k1_scalar_get_b32(&proof[offset + 32], &es); + + secp256k1_scalar_clear(&ks); + secp256k1_scalar_clear(&xs); + return 1; +} + size_t secp256k1_rangeproof_max_size(const secp256k1_context* ctx, uint64_t max_value, int min_bits) { const int val_mantissa = max_value > 0 ? 64 - secp256k1_clz64_var(max_value) : 1; const int mantissa = min_bits > val_mantissa ? min_bits : val_mantissa; diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 116974ba1..2aca09b9b 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -76,7 +76,7 @@ static void test_pedersen_api(const secp256k1_context *none, const secp256k1_con CHECK(*ecount == 14); } -static void test_rangeproof_api(const secp256k1_context *none, const secp256k1_context *sign, const secp256k1_context *vrfy, const secp256k1_context *both, const secp256k1_context *sttc, const int32_t *ecount) { +static void test_rangeproof_api(const secp256k1_context *none, const secp256k1_context *sign, const secp256k1_context *vrfy, const secp256k1_context *both, const secp256k1_context *sttc, int32_t *ecount) { unsigned char proof[5134]; unsigned char blind[32]; secp256k1_pedersen_commitment commit; @@ -226,6 +226,49 @@ static void test_rangeproof_api(const secp256k1_context *none, const secp256k1_c CHECK(*ecount == 29); } + { + *ecount = 0; + len = sizeof(proof); + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_value(none, NULL, &len, val, blind, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 1); + CHECK(secp256k1_rangeproof_create_value(none, proof, NULL, val, blind, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 2); + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, NULL, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 3); + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, NULL, secp256k1_generator_h) == 0); + CHECK(*ecount == 4); + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, &commit, NULL) == 0); + CHECK(*ecount == 5); + len = 0; + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + len = 64; + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + len = 65; + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 1); + len = 65; + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); + len = 72; + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); + len = 73; + CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(*ecount == 5); + + *ecount = 0; + CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val, &commit, secp256k1_generator_h) == 1); + CHECK(*ecount == 0); + CHECK(secp256k1_rangeproof_verify_value(none, NULL, len, val, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 1); + CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val, NULL, secp256k1_generator_h) == 0); + CHECK(*ecount == 2); + CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val, &commit, NULL) == 0); + CHECK(*ecount == 3); + CHECK(secp256k1_rangeproof_verify_value(none, proof, 0, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_value(none, proof, len - 1, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val ^ 1, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 3); + } + /* This constant is hardcoded in these tests and elsewhere, so we * consider it to be part of the API and test it here. */ CHECK(secp256k1_rangeproof_max_size(none, 0, 64) == 5134); From b8fc96766910ad79e90ee652103b3511870ec754 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Aug 2022 20:35:22 +0000 Subject: [PATCH 4/6] rangeproof: add more tests for exact-value proofs --- src/modules/rangeproof/tests_impl.h | 79 ++++++++++++++++++----------- 1 file changed, 48 insertions(+), 31 deletions(-) diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 2aca09b9b..34aa4d2f9 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -667,6 +667,7 @@ static void test_single_value_proof(uint64_t val) { uint64_t val_out = 0; size_t m_len_out = 0; + const int using_exact_value = secp256k1_testrand32() & 1; secp256k1_testrand256(blind); secp256k1_testrand256(nonce); @@ -685,19 +686,27 @@ static void test_single_value_proof(uint64_t val) { secp256k1_generator_h ) == 0); - plen = sizeof(proof); - CHECK(secp256k1_rangeproof_sign( - ctx, - proof, &plen, - val, /* min_val */ - &commit, blind, nonce, - -1, /* exp: -1 is magic value to indicate a single-value proof */ - 0, /* min_bits */ - val, /* val */ - NULL, 0, - NULL, 0, - secp256k1_generator_h - ) == 1); + if (using_exact_value && val == 0) { + plen = 70; /* sanity-check that plen can be <73 for 0-value proofs */ + } else { + plen = sizeof(proof); + } + if (using_exact_value) { + CHECK(secp256k1_rangeproof_create_value(ctx, proof, &plen, val, blind, &commit, secp256k1_generator_h) == 1); + } else { + CHECK(secp256k1_rangeproof_sign( + ctx, + proof, &plen, + val, /* min_val */ + &commit, blind, nonce, + -1, /* exp: -1 is magic value to indicate a single-value proof */ + 0, /* min_bits */ + val, /* val */ + NULL, 0, + NULL, 0, + secp256k1_generator_h + ) == 1); + } CHECK(plen <= secp256k1_rangeproof_max_size(ctx, val, 0)); /* Different proof sizes are unfortunate but is caused by `min_value` of @@ -721,25 +730,29 @@ static void test_single_value_proof(uint64_t val) { memset(message_out, 0, sizeof(message_out)); m_len_out = sizeof(message_out); - CHECK(secp256k1_rangeproof_rewind( - ctx, - blind_out, &val_out, - message_out, &m_len_out, - nonce, - &min_val_out, &max_val_out, - &commit, - proof, plen, - NULL, 0, - secp256k1_generator_h - )); - CHECK(val_out == val); - CHECK(min_val_out == val); - CHECK(max_val_out == val); - CHECK(m_len_out == 0); - CHECK(secp256k1_memcmp_var(blind, blind_out, 32) == 0); - for (m_len_out = 0; m_len_out < sizeof(message_out); m_len_out++) { - CHECK(message_out[m_len_out] == 0); + /* exact-value proofs cannot be rewound */ + if (!using_exact_value) { + CHECK(secp256k1_rangeproof_rewind( + ctx, + blind_out, &val_out, + message_out, &m_len_out, + nonce, + &min_val_out, &max_val_out, + &commit, + proof, plen, + NULL, 0, + secp256k1_generator_h + ) == 1); + CHECK(val_out == val); + CHECK(min_val_out == val); + CHECK(max_val_out == val); + CHECK(m_len_out == 0); + CHECK(secp256k1_memcmp_var(blind, blind_out, 32) == 0); + for (m_len_out = 0; m_len_out < sizeof(message_out); m_len_out++) { + CHECK(message_out[m_len_out] == 0); + } } + CHECK(secp256k1_rangeproof_verify_value(ctx, proof, plen, val, &commit, secp256k1_generator_h)); } #define MAX_N_GENS 30 @@ -1594,6 +1607,10 @@ void run_rangeproof_tests(void) { test_single_value_proof(0); test_single_value_proof(12345678); test_single_value_proof(UINT64_MAX); + for (i = 0; i < count / 2; i++) { + test_single_value_proof(secp256k1_testrand32()); + test_single_value_proof(((uint64_t) secp256k1_testrand32() << 32) + secp256k1_testrand32()); + } test_rangeproof_fixed_vectors(); test_rangeproof_fixed_vectors_reproducible(); From 7cf34e24c921ec277adbde7b9ef72b4003354f9d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Sat, 13 Aug 2022 15:16:47 +0000 Subject: [PATCH 5/6] rangeproof: rename create_value and verify_value to create_exact and verify_exact --- include/secp256k1_rangeproof.h | 6 ++-- src/modules/rangeproof/main_impl.h | 4 +-- src/modules/rangeproof/tests_impl.h | 44 ++++++++++++++--------------- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/include/secp256k1_rangeproof.h b/include/secp256k1_rangeproof.h index 845d6a018..a3d286eb9 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -297,7 +297,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info( ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); /** Verify a rangeproof with a single-value range. Useful as a "proof of value" - * of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_create_value`, + * of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_create_exact`, * or with `secp256k1_rangeproof_sign` by passing an `exp` parameter of -1 and the * target value as both `value` and `min_value`. (In this case `min_bits` is ignored * and may take any value, but for clarity it's best to pass zero.) @@ -310,7 +310,7 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info( * commit: the Pedersen commitment whose value is being verified * gen: additional generator 'h' */ -SECP256K1_API int secp256k1_rangeproof_verify_value( +SECP256K1_API int secp256k1_rangeproof_verify_exact( const secp256k1_context* ctx, const unsigned char* proof, size_t plen, @@ -331,7 +331,7 @@ SECP256K1_API int secp256k1_rangeproof_verify_value( * commit: the Pedersen commitment whose value is being proven * gen: additional generator 'h' */ -SECP256K1_API int secp256k1_rangeproof_create_value( +SECP256K1_API int secp256k1_rangeproof_create_exact( const secp256k1_context* ctx, unsigned char* proof, size_t* plen, diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index e8bc1d950..522c9b069 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -304,7 +304,7 @@ int secp256k1_rangeproof_sign(const secp256k1_context* ctx, unsigned char *proof proof, plen, min_value, &commitp, blind, nonce, exp, min_bits, value, message, msg_len, extra_commit, extra_commit_len, &genp); } -int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsigned char* proof, size_t plen, uint64_t value, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) { +int secp256k1_rangeproof_verify_exact(const secp256k1_context* ctx, const unsigned char* proof, size_t plen, uint64_t value, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) { secp256k1_ge commitp; secp256k1_ge genp; secp256k1_gej tmpj; @@ -397,7 +397,7 @@ int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsign return !secp256k1_memcmp_var(tmpch, &proof[offset], 32); } -int secp256k1_rangeproof_create_value(const secp256k1_context* ctx, unsigned char* proof, size_t* plen, uint64_t value, const unsigned char* blind, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) { +int secp256k1_rangeproof_create_exact(const secp256k1_context* ctx, unsigned char* proof, size_t* plen, uint64_t value, const unsigned char* blind, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) { secp256k1_ge commitp; secp256k1_ge genp; secp256k1_gej tmpj; diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 34aa4d2f9..6dcadb67d 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -229,43 +229,43 @@ static void test_rangeproof_api(const secp256k1_context *none, const secp256k1_c { *ecount = 0; len = sizeof(proof); - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); - CHECK(secp256k1_rangeproof_create_value(none, NULL, &len, val, blind, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_exact(none, NULL, &len, val, blind, &commit, secp256k1_generator_h) == 0); CHECK(*ecount == 1); - CHECK(secp256k1_rangeproof_create_value(none, proof, NULL, val, blind, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, NULL, val, blind, &commit, secp256k1_generator_h) == 0); CHECK(*ecount == 2); - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, NULL, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, val, NULL, &commit, secp256k1_generator_h) == 0); CHECK(*ecount == 3); - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, NULL, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, val, blind, NULL, secp256k1_generator_h) == 0); CHECK(*ecount == 4); - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, &commit, NULL) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, val, blind, &commit, NULL) == 0); CHECK(*ecount == 5); len = 0; - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); len = 64; - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); len = 65; - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 1); len = 65; - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); len = 72; - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); len = 73; - CHECK(secp256k1_rangeproof_create_value(none, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_exact(none, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); CHECK(*ecount == 5); *ecount = 0; - CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_verify_exact(none, proof, len, val, &commit, secp256k1_generator_h) == 1); CHECK(*ecount == 0); - CHECK(secp256k1_rangeproof_verify_value(none, NULL, len, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_exact(none, NULL, len, val, &commit, secp256k1_generator_h) == 0); CHECK(*ecount == 1); - CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val, NULL, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_exact(none, proof, len, val, NULL, secp256k1_generator_h) == 0); CHECK(*ecount == 2); - CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val, &commit, NULL) == 0); + CHECK(secp256k1_rangeproof_verify_exact(none, proof, len, val, &commit, NULL) == 0); CHECK(*ecount == 3); - CHECK(secp256k1_rangeproof_verify_value(none, proof, 0, val, &commit, secp256k1_generator_h) == 0); - CHECK(secp256k1_rangeproof_verify_value(none, proof, len - 1, val, &commit, secp256k1_generator_h) == 0); - CHECK(secp256k1_rangeproof_verify_value(none, proof, len, val ^ 1, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_exact(none, proof, 0, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_exact(none, proof, len - 1, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_exact(none, proof, len, val ^ 1, &commit, secp256k1_generator_h) == 0); CHECK(*ecount == 3); } @@ -692,7 +692,7 @@ static void test_single_value_proof(uint64_t val) { plen = sizeof(proof); } if (using_exact_value) { - CHECK(secp256k1_rangeproof_create_value(ctx, proof, &plen, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_exact(ctx, proof, &plen, val, blind, &commit, secp256k1_generator_h) == 1); } else { CHECK(secp256k1_rangeproof_sign( ctx, @@ -752,7 +752,7 @@ static void test_single_value_proof(uint64_t val) { CHECK(message_out[m_len_out] == 0); } } - CHECK(secp256k1_rangeproof_verify_value(ctx, proof, plen, val, &commit, secp256k1_generator_h)); + CHECK(secp256k1_rangeproof_verify_exact(ctx, proof, plen, val, &commit, secp256k1_generator_h)); } #define MAX_N_GENS 30 @@ -1040,7 +1040,7 @@ void test_rangeproof_fixed_vectors(void) { CHECK(max_value == UINT64_MAX); CHECK(m_len == 0); - CHECK(secp256k1_rangeproof_verify_value(ctx, vector_3, sizeof(vector_3), UINT64_MAX, &pc, secp256k1_generator_h)); + CHECK(secp256k1_rangeproof_verify_exact(ctx, vector_3, sizeof(vector_3), UINT64_MAX, &pc, secp256k1_generator_h)); } } From a20c1e950e258d4e5b478f4f20baee589240ee02 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 25 Aug 2022 20:29:25 +0000 Subject: [PATCH 6/6] rangeproof_sign_exact: compute `k` in a more obviously non-reusing way --- src/modules/rangeproof/main_impl.h | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index 522c9b069..8dca5b176 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -447,12 +447,20 @@ int secp256k1_rangeproof_create_exact(const secp256k1_context* ctx, unsigned cha /* Now we have to make a Schnorr signature in (e, s) form. */ - /* 1. Compute random k */ + /* 1. Compute slow/overwrought commitment to proof params */ secp256k1_sha256_initialize(&sha2); - secp256k1_sha256_write(&sha2, blind, 32); - secp256k1_sha256_write(&sha2, proof, offset); + secp256k1_rangeproof_serialize_point(tmpch, &commitp); + secp256k1_sha256_write(&sha2, tmpch, 33); secp256k1_rangeproof_serialize_point(tmpch, &genp); secp256k1_sha256_write(&sha2, tmpch, 33); + secp256k1_sha256_write(&sha2, proof, offset); + secp256k1_sha256_finalize(&sha2, pp_comm); + + /* 2. Compute random k */ + secp256k1_sha256_initialize(&sha2); + secp256k1_sha256_write(&sha2, blind, 32); + secp256k1_sha256_write(&sha2, proof, offset); + secp256k1_sha256_write(&sha2, pp_comm, 32); secp256k1_sha256_finalize(&sha2, tmpch); secp256k1_scalar_set_b32(&ks, tmpch, &overflow); if (overflow || secp256k1_scalar_is_zero(&ks)) { @@ -461,19 +469,10 @@ int secp256k1_rangeproof_create_exact(const secp256k1_context* ctx, unsigned cha return 0; } - /* 2. Compute R = kG */ + /* 3. Compute R = kG */ secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &tmpj, &ks); secp256k1_ge_set_gej(&tmpp, &tmpj); - /* 3. Compute slow/overwrought commitment to proof params */ - secp256k1_sha256_initialize(&sha2); - secp256k1_rangeproof_serialize_point(tmpch, &commitp); - secp256k1_sha256_write(&sha2, tmpch, 33); - secp256k1_rangeproof_serialize_point(tmpch, &genp); - secp256k1_sha256_write(&sha2, tmpch, 33); - secp256k1_sha256_write(&sha2, proof, offset); - secp256k1_sha256_finalize(&sha2, pp_comm); - /* 4. Compute e0 = H(R || proof params) and serialize it into the proof */ secp256k1_sha256_initialize(&sha2); secp256k1_eckey_pubkey_serialize(&tmpp, tmpch, &sz, 1);