diff --git a/include/secp256k1_rangeproof.h b/include/secp256k1_rangeproof.h index d179b36f..23e511ed 100644 --- a/include/secp256k1_rangeproof.h +++ b/include/secp256k1_rangeproof.h @@ -287,6 +287,33 @@ 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); +/** Returns an upper bound on the size of a rangeproof with the given parameters + * + * An actual rangeproof may be smaller, for example if the actual value + * is less than both the provided `max_value` and 2^`min_bits`, or if + * the `exp` parameter to `secp256k1_rangeproof_sign` is set such that + * the proven range is compressed. In particular this function will always + * overestimate the size of single-value proofs. Also, if `min_value` + * is set to 0 in the proof, the result will usually, but not always, + * be 8 bytes smaller than if a nonzero value had been passed. + * + * The goal of this function is to provide a useful upper bound for + * memory allocation or fee estimation purposes, without requiring + * too many parameters be fixed in advance. + * + * To obtain the size of largest possible proof, set `max_value` to + * `UINT64_MAX` (and `min_bits` to any valid value such as 0). + * + * In: ctx: pointer to a context object + * max_value: the maximum value that might be passed for `value` for the proof. + * min_bits: the value that will be passed as `min_bits` for the proof. + */ +SECP256K1_API SECP256K1_WARN_UNUSED_RESULT size_t secp256k1_rangeproof_max_size( + const secp256k1_context* ctx, + uint64_t max_value, + int min_bits +) SECP256K1_ARG_NONNULL(1); + # ifdef __cplusplus } # endif diff --git a/src/modules/rangeproof/main_impl.h b/src/modules/rangeproof/main_impl.h index b1c36cdd..432f4b95 100644 --- a/src/modules/rangeproof/main_impl.h +++ b/src/modules/rangeproof/main_impl.h @@ -304,4 +304,16 @@ 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); } +size_t secp256k1_rangeproof_max_size(const secp256k1_context* ctx, uint64_t max_value, int min_bits) { + const int val_mantissa = max_value > 0 ? 64 - secp256k1_clz64_var(max_value) : 1; + const int mantissa = min_bits > val_mantissa ? min_bits : val_mantissa; + const size_t rings = (mantissa + 1) / 2; + const size_t npubs = rings * 4 - 2 * (mantissa % 2); + + VERIFY_CHECK(ctx != NULL); + (void) ctx; + + return 10 + 32 * (npubs + rings - 1) + 32 + ((rings - 1 + 7) / 8); +} + #endif diff --git a/src/modules/rangeproof/tests_impl.h b/src/modules/rangeproof/tests_impl.h index 03d80314..27703800 100644 --- a/src/modules/rangeproof/tests_impl.h +++ b/src/modules/rangeproof/tests_impl.h @@ -225,6 +225,11 @@ 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); } + + /* This constant is hardcoded in these tests and elsewhere, so we + * consider it to be part of the API and test it here. */ + CHECK(secp256k1_rangeproof_max_size(none, 0, 64) == 5134); + CHECK(secp256k1_rangeproof_max_size(none, UINT64_MAX, 0) == 5134); } static void test_api(void) { @@ -431,6 +436,7 @@ static void test_rangeproof(void) { len = 5134; CHECK(secp256k1_rangeproof_sign(ctx, proof, &len, vmin, &commit, blind, commit.data, 0, 0, v, input_message, input_message_len, NULL, 0, secp256k1_generator_h)); CHECK(len <= 5134); + CHECK(len <= secp256k1_rangeproof_max_size(ctx, v, 0)); mlen = 4096; CHECK(secp256k1_rangeproof_rewind(ctx, blindout, &vout, message, &mlen, commit.data, &minv, &maxv, &commit, proof, len, NULL, 0, secp256k1_generator_h)); if (input_message != NULL) { @@ -447,6 +453,7 @@ static void test_rangeproof(void) { len = 5134; CHECK(secp256k1_rangeproof_sign(ctx, proof, &len, v, &commit, blind, commit.data, -1, 64, v, NULL, 0, NULL, 0, secp256k1_generator_h)); CHECK(len <= 73); + CHECK(len <= secp256k1_rangeproof_max_size(ctx, v, 0)); CHECK(secp256k1_rangeproof_rewind(ctx, blindout, &vout, NULL, NULL, commit.data, &minv, &maxv, &commit, proof, len, NULL, 0, secp256k1_generator_h)); CHECK(secp256k1_memcmp_var(blindout, blind, 32) == 0); CHECK(vout == v); @@ -457,6 +464,7 @@ static void test_rangeproof(void) { len = 5134; CHECK(secp256k1_rangeproof_sign(ctx, proof, &len, v, &commit, blind, commit.data, -1, 64, v, NULL, 0, message_short, sizeof(message_short), secp256k1_generator_h)); CHECK(len <= 73); + CHECK(len <= secp256k1_rangeproof_max_size(ctx, v, 0)); CHECK(!secp256k1_rangeproof_rewind(ctx, blindout, &vout, NULL, NULL, commit.data, &minv, &maxv, &commit, proof, len, NULL, 0, secp256k1_generator_h)); CHECK(!secp256k1_rangeproof_rewind(ctx, blindout, &vout, NULL, NULL, commit.data, &minv, &maxv, &commit, proof, len, message_long, sizeof(message_long), secp256k1_generator_h)); CHECK(secp256k1_rangeproof_rewind(ctx, blindout, &vout, NULL, NULL, commit.data, &minv, &maxv, &commit, proof, len, message_short, sizeof(message_short), secp256k1_generator_h)); @@ -472,6 +480,7 @@ static void test_rangeproof(void) { for (i = 0; i < 19; i++) { len = 5134; CHECK(secp256k1_rangeproof_sign(ctx, proof, &len, 0, &commit, blind, commit.data, i, 0, v, NULL, 0, NULL, 0, secp256k1_generator_h)); + CHECK(len <= secp256k1_rangeproof_max_size(ctx, v, 0)); CHECK(secp256k1_rangeproof_verify(ctx, &minv, &maxv, &commit, proof, len, NULL, 0, secp256k1_generator_h)); CHECK(len <= 5134); CHECK(minv <= v); @@ -487,6 +496,7 @@ static void test_rangeproof(void) { len = 5134; CHECK(secp256k1_rangeproof_sign(ctx, proof, &len, 0, &commit, blind, commit.data, 0, 3, v, NULL, 0, NULL, 0, secp256k1_generator_h)); CHECK(len <= 5134); + CHECK(len <= secp256k1_rangeproof_max_size(ctx, v, 3)); /* Test if trailing bytes are rejected. */ proof[len] = v; CHECK(!secp256k1_rangeproof_verify(ctx, &minv, &maxv, &commit, proof, len + 1, NULL, 0, secp256k1_generator_h)); @@ -521,6 +531,7 @@ static void test_rangeproof(void) { } CHECK(secp256k1_rangeproof_sign(ctx, proof, &len, vmin, &commit, blind, commit.data, exp, min_bits, v, NULL, 0, NULL, 0, secp256k1_generator_h)); CHECK(len <= 5134); + CHECK(len <= secp256k1_rangeproof_max_size(ctx, v, min_bits)); mlen = 4096; CHECK(secp256k1_rangeproof_rewind(ctx, blindout, &vout, message, &mlen, commit.data, &minv, &maxv, &commit, proof, len, NULL, 0, secp256k1_generator_h)); for (j = 0; j < mlen; j++) {