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..fe827144b 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] = secp256k1_testrand64(); + /* nb these theoretically could go out of range but not in this universe's lifetime */ + secp256k1_testrand256(abf[i]); + secp256k1_testrand256(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);