From 5e90403c4f8f406e920fed68677fc45609463b32 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 22 Jul 2022 17:53:49 +0000 Subject: [PATCH] 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 d4f35de7a..8f7558c3e 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -286,6 +286,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 proven + * 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); + # ifdef __cplusplus } # endif diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index 9129e4c30..71b9f4513 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -304,4 +304,97 @@ 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); /* lol we commit to one extra byte here */ + 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 !memcmp(tmpch, &proof[offset], 32); +} + #endif diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 9c3906ca8..8d38ebc67 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -919,6 +919,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)); } }