From 9d28292590fb8489e9b031a836b1754999101400 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Wed, 3 Aug 2022 20:31:09 +0000 Subject: [PATCH] rangeproof: add verify_value function to create single-value proofs --- include/secp256k1_rangeproof.h | 30 ++++++-- src/modules/rangeproof/main_impl.h | 105 +++++++++++++++++++++++++++- src/modules/rangeproof/tests_impl.h | 49 ++++++++++++- 3 files changed, 177 insertions(+), 7 deletions(-) diff --git a/include/secp256k1_rangeproof.h b/include/secp256k1_rangeproof.h index 8f7558c3e..58c49bf6e 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -287,10 +287,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 @@ -309,6 +309,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); + # ifdef __cplusplus } # endif diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index 71b9f4513..f31fe28a0 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,4 +397,105 @@ int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsign return !memcmp(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_scalar es; + secp256k1_scalar tmps; + 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 < 73 || (value == 0 && *plen < 65)) { + return 0; + } + + 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 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); /* lol we commit to one extra byte here */ + secp256k1_sha256_finalize(&sha2, pp_comm); + + /* ... feed this into our hash e */ + 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; + } + + /* ... and compute -ex from this */ + secp256k1_scalar_set_b32(&tmps, blind, &overflow); + if (overflow || secp256k1_scalar_is_zero(&tmps)) { + secp256k1_scalar_clear(&tmps); + secp256k1_scalar_clear(&es); + return 0; + } + secp256k1_scalar_mul(&es, &es, &tmps); + secp256k1_scalar_negate(&es, &es); + + /* 2. Compute random k and set `es` to k - ex */ + secp256k1_sha256_initialize(&sha2); + secp256k1_sha256_write(&sha2, blind, 32); + secp256k1_sha256_write(&sha2, pp_comm, 32); + secp256k1_sha256_finalize(&sha2, tmpch); + secp256k1_scalar_set_b32(&tmps, tmpch, &overflow); + if (overflow || secp256k1_scalar_is_zero(&tmps)) { + secp256k1_scalar_clear(&es); + return 0; + } + secp256k1_scalar_add(&es, &es, &tmps); + secp256k1_scalar_get_b32(&proof[offset + 32], &es); + + /* Compute R = kG and serialize it*/ + secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &tmpj, &tmps); + secp256k1_scalar_clear(&tmps); + secp256k1_ge_set_gej(&genp, &tmpj); /* Reuse genp which is no longer used */ + secp256k1_eckey_pubkey_serialize(&genp, tmpch, &sz, 1); + + /* 3. Compute e0 = H(R || proof params) and serialize it */ + secp256k1_sha256_initialize(&sha2); + secp256k1_sha256_write(&sha2, tmpch, sz); + secp256k1_sha256_write(&sha2, pp_comm, sizeof(pp_comm)); + secp256k1_sha256_finalize(&sha2, &proof[offset]); + + return 1; +} + #endif diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 8d38ebc67..b7da730bb 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; @@ -225,6 +225,53 @@ static void test_rangeproof_api(const secp256k1_context *none, const secp256k1_c CHECK(secp256k1_rangeproof_rewind(both, blind_out, &value_out, NULL, 0, commit.data, &min_value, &max_value, &commit, proof, len, NULL, 0, NULL) == 0); 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(vrfy, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, val, blind, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_create_value(sign, NULL, &len, val, blind, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 1); + CHECK(secp256k1_rangeproof_create_value(sign, proof, NULL, val, blind, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 2); + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, val, NULL, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 3); + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, val, blind, NULL, secp256k1_generator_h) == 0); + CHECK(*ecount == 4); + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, val, blind, &commit, NULL) == 0); + CHECK(*ecount == 5); + len = 0; + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + len = 64; + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + len = 65; + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, 0, blind, &commit, secp256k1_generator_h) == 0); + len = 65; + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); + len = 72; + CHECK(secp256k1_rangeproof_create_value(sign, proof, &len, 1, blind, &commit, secp256k1_generator_h) == 0); + len = 73; + CHECK(secp256k1_rangeproof_create_value(sign, 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_value(sign, proof, len, val, &commit, secp256k1_generator_h) == 1); + CHECK(secp256k1_rangeproof_verify_value(vrfy, proof, len, val, &commit, secp256k1_generator_h) == 1); + CHECK(*ecount == 0); + CHECK(secp256k1_rangeproof_verify_value(vrfy, NULL, len, val, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 1); + CHECK(secp256k1_rangeproof_verify_value(vrfy, proof, len, val, NULL, secp256k1_generator_h) == 0); + CHECK(*ecount == 2); + CHECK(secp256k1_rangeproof_verify_value(vrfy, proof, len, val, &commit, NULL) == 0); + CHECK(*ecount == 3); + CHECK(secp256k1_rangeproof_verify_value(vrfy, proof, 0, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_value(vrfy, proof, len - 1, val, &commit, secp256k1_generator_h) == 0); + CHECK(secp256k1_rangeproof_verify_value(vrfy, proof, len, val ^ 1, &commit, secp256k1_generator_h) == 0); + CHECK(*ecount == 3); + } } static void test_api(void) {