From 14620d131250b141f4d3ab352fedac0aef45eb30 Mon Sep 17 00:00:00 2001 From: Andrew Poelstra <apoelstra@wpsoftware.net> Date: Sun, 11 Sep 2022 14:40:50 +0000 Subject: [PATCH] rangeproof: add a "net blinding factor" API for Elements Our original API for Confidential Assets transaction balancing was the single function `secp256k1_pedersen_blind_generator_blind_sum` which attempts to take a complete list of vbfs and abfs and modifies a single abf at the end. However this API has a number of shortcomings: * it is really confusing * it assumes that the user has all the abfs and vbfs in convenient arrays, requiring marshalling on the C++ side * it does not support partial computations, as are needed by PSET * there is no easy/sensible way to extend this API to allow more interesting of transaction balancing (e.g. by blinding only an asset, leaving the value explicit) The hope is that by exposing the arithmetic at a more fine-grained level, these issues will be fixed. These methods can be abused to do arithmetic on arbitrary scalars, but this is already possible (in an ugly manner) by using secp256k1_seckey_tweak_add and explicit 0-checks. --- include/secp256k1_rangeproof.h | 43 ++++++++++++++++ src/modules/rangeproof/main_impl.h | 68 +++++++++++++++++++++++++ src/modules/rangeproof/tests_impl.h | 77 +++++++++++++++++++++++++++++ 3 files changed, 188 insertions(+) diff --git a/include/secp256k1_rangeproof.h b/include/secp256k1_rangeproof.h index 9bb014545..2d7042735 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -128,6 +128,49 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_verify_tally( size_t ncnt ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4); +/** Compute the "net blinding factor" for an asset/amount pair of Pedersen commitments + * + * Returns 0 if either input is out of range, otherwise 1 + * Args: ctx: a secp256k1 context object. + * Out: output: 32-byte array into which the result will be written + * In: val: the value of the amount commitment + * vbf: the amount commitment's blinding factor + * abf: the asset commitment's blinding factor + * + * This computse val*abf + vbf + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_netbf_compute( + const secp256k1_context* ctx, + unsigned char* output, + uint64_t val, + const unsigned char* vbf, + const unsigned char* abf +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); + +/** Accumulate a net blinding factor + * + * Returns 0 if the input is out of range, otherwise 1 + * Args: ctx: a secp256k1 context object. + * In/Out: acc: initially set to the current state of the accumulator; updated in place + * In: nbf: the net blinding factor to add to the accumulator + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_netbf_acc( + const secp256k1_context* ctx, + unsigned char* acc, + const unsigned char* nbf +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); + +/** Negate a(n accumulated) net blinding factor + * + * Returns 0 if the input is out of range, otherwise 1 + * Args: ctx: a secp256k1 context object. + * In/Out: acc: initially set to the bf to negate; changed to the negated version + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_netbf_neg( + const secp256k1_context* ctx, + unsigned char* output +) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2); + /** Sets the final Pedersen blinding factor correctly when the generators themselves * have blinding factors. * diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index 432f4b959..83de68d83 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -165,6 +165,74 @@ int secp256k1_pedersen_verify_tally(const secp256k1_context* ctx, const secp256k return secp256k1_gej_is_infinity(&accj); } +int secp256k1_netbf_compute(const secp256k1_context* ctx, unsigned char* output, uint64_t val, const unsigned char* vbf, const unsigned char* abf) { + int overflow = 0; + secp256k1_scalar vbf_s; + secp256k1_scalar abf_s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(output != NULL); + ARG_CHECK(vbf != NULL); + ARG_CHECK(abf != NULL); + (void) ctx; + + secp256k1_scalar_set_b32(&abf_s, abf, &overflow); + if (overflow == 1) { + return 0; + } + secp256k1_scalar_set_u64(&vbf_s, val); + secp256k1_scalar_mul(&abf_s, &abf_s, &vbf_s); + + secp256k1_scalar_set_b32(&vbf_s, vbf, &overflow); + if (overflow == 1) { + return 0; + } + secp256k1_scalar_add(&vbf_s, &vbf_s, &abf_s); + + secp256k1_scalar_get_b32(output, &vbf_s); + return 1; +} + +int secp256k1_netbf_acc(const secp256k1_context* ctx, unsigned char* acc, const unsigned char* nbf) { + int overflow = 0; + secp256k1_scalar ret_s; + secp256k1_scalar nbf_s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(acc != NULL); + ARG_CHECK(nbf != NULL); + + secp256k1_scalar_set_b32(&ret_s, acc, &overflow); + if (overflow == 1) { + return 0; + } + secp256k1_scalar_set_b32(&nbf_s, nbf, &overflow); + if (overflow == 1) { + return 0; + } + + secp256k1_scalar_add(&ret_s, &ret_s, &nbf_s); + secp256k1_scalar_get_b32(acc, &ret_s); + return 1; +} + +int secp256k1_netbf_neg(const secp256k1_context* ctx, unsigned char* acc) { + int overflow = 0; + secp256k1_scalar ret_s; + + VERIFY_CHECK(ctx != NULL); + ARG_CHECK(acc != NULL); + + secp256k1_scalar_set_b32(&ret_s, acc, &overflow); + if (overflow == 1) { + return 0; + } + + secp256k1_scalar_negate(&ret_s, &ret_s); + secp256k1_scalar_get_b32(acc, &ret_s); + return 1; +} + int secp256k1_pedersen_blind_generator_blind_sum(const secp256k1_context* ctx, const uint64_t *value, const unsigned char* const* generator_blind, unsigned char* const* blinding_factor, size_t n_total, size_t n_inputs) { secp256k1_scalar sum; secp256k1_scalar tmp; diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 9c9207343..123b8626f 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -232,6 +232,81 @@ static void test_rangeproof_api(const secp256k1_context *none, const secp256k1_c CHECK(secp256k1_rangeproof_max_size(none, UINT64_MAX, 0) == 5134); } +#define N_COMMITS 8 +static void test_nbf(const secp256k1_context *none, const int32_t *ecount) { + unsigned char abf[N_COMMITS][32]; + unsigned char vbf[N_COMMITS][32]; + unsigned char nbf[N_COMMITS][32]; + uint64_t val[N_COMMITS]; + + unsigned char acc_p[32]; + unsigned char acc_n[32]; + secp256k1_scalar target; + + size_t i; + VERIFY_CHECK(N_COMMITS >= 2); /* better to trigger this than to trigger UB */ + + /* Check that zeros are ok; check NULL checks */ + memset(abf[0], 0x00, sizeof(abf[0])); + memset(vbf[0], 0x00, sizeof(abf[0])); + val[0] = 0; + + CHECK(secp256k1_netbf_compute(none, nbf[0], val[0], vbf[0], abf[0])); + CHECK(secp256k1_memcmp_var(nbf[0], vbf[0], sizeof(nbf[0])) == 0); + CHECK(*ecount == 0); + CHECK(secp256k1_netbf_compute(none, NULL, val[0], vbf[0], abf[0]) == 0); + CHECK(*ecount == 1); + CHECK(secp256k1_netbf_compute(none, nbf[0], val[0], NULL, abf[0]) == 0); + CHECK(*ecount == 2); + CHECK(secp256k1_netbf_compute(none, nbf[0], val[0], vbf[0], NULL) == 0); + CHECK(*ecount == 3); + + memset(vbf[1], 0xff, sizeof(vbf[1])); /* out of range */ + memset(abf[1], 0xff, sizeof(abf[1])); /* out of range */ + CHECK(secp256k1_netbf_compute(none, nbf[0], val[0], vbf[0], abf[1]) == 0); + CHECK(secp256k1_netbf_compute(none, nbf[0], val[0], vbf[1], abf[0]) == 0); + CHECK(secp256k1_netbf_compute(none, nbf[0], val[0], vbf[1], abf[1]) == 0); + CHECK(*ecount == 3); /* not API errors */ + + memset(acc_p, 0x00, sizeof(acc_p)); /* FIXME should we expose a nbf_clear method for this? */ + memset(acc_n, 0x00, sizeof(acc_n)); + secp256k1_scalar_clear(&target); + for (i = 0; i < N_COMMITS; i++) { + val[i] = ((uint64_t) secp256k1_testrand32() << 32) + secp256k1_testrand32(); + /* nb these theoretically could go out of range but not in this universe's lifetime */ + secp256k1_testrand256_test(abf[i]); + secp256k1_testrand256_test(vbf[i]); + CHECK(secp256k1_netbf_compute(none, nbf[i], val[i], vbf[i], abf[i])); + + if (secp256k1_testrand32() & 1) { + int overflow; + secp256k1_scalar tmps; + /* positive */ + CHECK(secp256k1_netbf_acc(none, acc_p, nbf[i])); + + secp256k1_scalar_set_b32(&tmps, nbf[i], &overflow); + CHECK(overflow == 0); /* actually unreachable, not just cryptographically */ + secp256k1_scalar_add(&target, &target, &tmps); + } else { + int overflow; + secp256k1_scalar tmps; + /* negative */ + CHECK(secp256k1_netbf_acc(none, acc_n, nbf[i])); + + secp256k1_scalar_set_b32(&tmps, nbf[i], &overflow); + CHECK(overflow == 0); /* actually unreachable, not just cryptographically */ + secp256k1_scalar_negate(&tmps, &tmps); + secp256k1_scalar_add(&target, &target, &tmps); + } + } + CHECK(secp256k1_netbf_neg(none, acc_n)); + CHECK(secp256k1_netbf_acc(none, acc_p, acc_n)); + + secp256k1_scalar_get_b32(acc_n, &target); + CHECK(secp256k1_memcmp_var(acc_p, acc_n, sizeof(acc_p)) == 0); +} +#undef N_COMMITS + static void test_api(void) { secp256k1_context *none = secp256k1_context_create(SECP256K1_CONTEXT_NONE); secp256k1_context *sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); @@ -257,6 +332,8 @@ static void test_api(void) { test_pedersen_api(none, sign, vrfy, sttc, &ecount); ecount = 0; test_rangeproof_api(none, sign, vrfy, both, sttc, &ecount); + ecount = 0; + test_nbf(none, &ecount); } secp256k1_context_destroy(none);