From 2f4b275ae09946d676945a1521df157d49ddab7d Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Fri, 22 Jul 2022 22:44:08 +0000 Subject: [PATCH] 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..7878a8bc7 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 generalr 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 b8f145e5f..4a869e9a5 100644 --- a/src/modules/surjection/main_impl.h +++ b/src/modules/surjection/main_impl.h @@ -393,4 +393,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 !memcmp(tmpch, &proof->data[0], 32); +} + #endif diff --git a/src/modules/surjection/tests_impl.h b/src/modules/surjection/tests_impl.h index 89ba4e1a6..bc36d689b 100644 --- a/src/modules/surjection/tests_impl.h +++ b/src/modules/surjection/tests_impl.h @@ -431,6 +431,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) { @@ -680,6 +684,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();