Skip to content

Commit eb4047f

Browse files
committed
bulletproofs: add rangeproof rewinding capability
1 parent 50c8ca1 commit eb4047f

File tree

4 files changed

+238
-3
lines changed

4 files changed

+238
-3
lines changed

include/secp256k1_bulletproofs.h

+30
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,36 @@ SECP256K1_API int secp256k1_bulletproof_rangeproof_verify_multi(
112112
size_t *extra_commit_len
113113
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8);
114114

115+
/** Extracts the value and blinding factor from a single-commit rangeproof given a secret nonce
116+
* Returns: 1: value and blinding factor were extracted and matched the input commit
117+
* 0: one of the above was not true, extraction failed
118+
* Args: ctx: pointer to a context object (cannot be NULL)
119+
* gens: generator set used to make original proof (cannot be NULL)
120+
* Out: value: pointer to value that will be extracted
121+
* blind: pointer to 32-byte array for blinding factor to be extracted
122+
* In: proof: byte-serialized rangeproof (cannot be NULL)
123+
* plen: length of every individual proof
124+
* min_value: minimum value that the proof ranges over
125+
* commit: pedersen commitment that the rangeproof is over (cannot be NULL)
126+
* value_gen: generator multiplied by value in pedersen commitments (cannot be NULL)
127+
* nonce: random 32-byte seed used to derive blinding factors (cannot be NULL)
128+
* extra_commit: additonal data committed to by the rangeproof
129+
* extra_commit_len: length of additional data
130+
*/
131+
SECP256K1_API int secp256k1_bulletproof_rangeproof_rewind(
132+
const secp256k1_context* ctx,
133+
const secp256k1_bulletproof_generators* gens,
134+
uint64_t* value,
135+
unsigned char* blind,
136+
const unsigned char* proof,
137+
size_t plen,
138+
size_t min_value,
139+
const secp256k1_pedersen_commitment* commit,
140+
const secp256k1_generator* value_gen,
141+
const unsigned char* nonce,
142+
const unsigned char* extra_commit,
143+
size_t extra_commit_len
144+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9);
115145

116146
/** Produces an aggregate Bulletproof rangeproof for a set of Pedersen commitments
117147
* Returns: 1: rangeproof was successfully created

src/modules/bulletproofs/main_impl.h

+21
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,27 @@ int secp256k1_bulletproof_rangeproof_verify_multi(const secp256k1_context* ctx,
173173
return ret;
174174
}
175175

176+
int secp256k1_bulletproof_rangeproof_rewind(const secp256k1_context* ctx, const secp256k1_bulletproof_generators *gens, uint64_t *value, unsigned char *blind, const unsigned char *proof, size_t plen, uint64_t min_value, const secp256k1_pedersen_commitment* commit, const secp256k1_generator *value_gen, const unsigned char *nonce, const unsigned char *extra_commit, size_t extra_commit_len) {
177+
secp256k1_scalar blinds;
178+
int ret;
179+
180+
VERIFY_CHECK(ctx != NULL);
181+
ARG_CHECK(value != NULL);
182+
ARG_CHECK(blind != NULL);
183+
ARG_CHECK(gens != NULL);
184+
ARG_CHECK(proof != NULL);
185+
ARG_CHECK(commit != NULL);
186+
ARG_CHECK(value_gen != NULL);
187+
ARG_CHECK(nonce != NULL);
188+
ARG_CHECK(extra_commit != NULL || extra_commit_len == 0);
189+
190+
ret = secp256k1_bulletproof_rangeproof_rewind_impl(value, &blinds, proof, plen, min_value, commit, value_gen, gens->blinding_gen, nonce, extra_commit, extra_commit_len);
191+
if (ret == 1) {
192+
secp256k1_scalar_get_b32(blind, &blinds);
193+
}
194+
return ret;
195+
}
196+
176197
int secp256k1_bulletproof_rangeproof_prove(const secp256k1_context* ctx, secp256k1_scratch_space *scratch, const secp256k1_bulletproof_generators *gens, unsigned char *proof, size_t *plen, const uint64_t *value, const uint64_t *min_value, const unsigned char* const* blind, size_t n_commits, const secp256k1_generator *value_gen, size_t nbits, const unsigned char *nonce, const unsigned char *extra_commit, size_t extra_commit_len) {
177198
int ret;
178199
secp256k1_ge *commitp;

src/modules/bulletproofs/rangeproof_impl.h

+144
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,13 @@ static int secp256k1_bulletproof_rangeproof_prove_impl(const secp256k1_ecmult_co
488488

489489
secp256k1_scalar_chacha20(&alpha, &rho, nonce, 0);
490490
secp256k1_scalar_chacha20(&tau1, &tau2, nonce, 1);
491+
/* Encrypt value into alpha, so it will be recoverable from -mu by someone who knows `nonce` */
492+
if (n_commits == 1) {
493+
secp256k1_scalar vals;
494+
secp256k1_scalar_set_u64(&vals, value[0]);
495+
secp256k1_scalar_negate(&vals, &vals); /* Negate so it'll be positive in -mu */
496+
secp256k1_scalar_add(&alpha, &alpha, &vals);
497+
}
491498

492499
/* Compute A and S */
493500
secp256k1_ecmult_const(&aj, &gens->blinding_gen[0], &alpha, 256);
@@ -645,4 +652,141 @@ static int secp256k1_bulletproof_rangeproof_prove_impl(const secp256k1_ecmult_co
645652

646653
return 1;
647654
}
655+
656+
static int secp256k1_bulletproof_rangeproof_rewind_impl(uint64_t *value, secp256k1_scalar *blind, const unsigned char *proof, const size_t plen, uint64_t min_value, const secp256k1_pedersen_commitment *pcommit, const secp256k1_generator *value_gen, const secp256k1_ge *blind_gen, const unsigned char *nonce, const unsigned char *extra_commit, size_t extra_commit_len) {
657+
secp256k1_sha256 sha256;
658+
static const unsigned char zero24[24] = { 0 };
659+
unsigned char commit[32] = { 0 };
660+
unsigned char lrparity;
661+
secp256k1_scalar taux, mu;
662+
secp256k1_scalar alpha, rho, tau1, tau2;
663+
secp256k1_scalar x, z;
664+
secp256k1_ge commitp, value_genp;
665+
secp256k1_gej rewind_commitj;
666+
int overflow;
667+
668+
if (plen < 64 + 128 + 1 || plen > SECP256K1_BULLETPROOF_MAX_PROOF) {
669+
return 0;
670+
}
671+
672+
/* Extract data from beginning of proof */
673+
secp256k1_scalar_set_b32(&taux, &proof[0], &overflow);
674+
if (overflow || secp256k1_scalar_is_zero(&taux)) {
675+
return 0;
676+
}
677+
secp256k1_scalar_set_b32(&mu, &proof[32], &overflow);
678+
if (overflow || secp256k1_scalar_is_zero(&mu)) {
679+
return 0;
680+
}
681+
682+
secp256k1_scalar_chacha20(&alpha, &rho, nonce, 0);
683+
secp256k1_scalar_chacha20(&tau1, &tau2, nonce, 1);
684+
685+
if (min_value > 0) {
686+
unsigned char vbuf[8];
687+
vbuf[0] = min_value;
688+
vbuf[1] = min_value >> 8;
689+
vbuf[2] = min_value >> 16;
690+
vbuf[3] = min_value >> 24;
691+
vbuf[4] = min_value >> 32;
692+
vbuf[5] = min_value >> 40;
693+
vbuf[6] = min_value >> 48;
694+
vbuf[7] = min_value >> 56;
695+
secp256k1_sha256_initialize(&sha256);
696+
secp256k1_sha256_write(&sha256, commit, 32);
697+
secp256k1_sha256_write(&sha256, vbuf, 8);
698+
secp256k1_sha256_finalize(&sha256, commit);
699+
}
700+
701+
/* This breaks the abstraction of both the Pedersen commitment and the generator
702+
* type by directly reading the parity bit and x-coordinate from the data. But
703+
* the alternative using the _load functions is to do two full point decompression,
704+
* and in my benchmarks we save ~80% of the rewinding time by avoiding this. -asp */
705+
lrparity = 2 * !!(pcommit->data[0] & 1) + !!(value_gen->data[0] & 1);
706+
secp256k1_sha256_initialize(&sha256);
707+
secp256k1_sha256_write(&sha256, commit, 32);
708+
secp256k1_sha256_write(&sha256, &lrparity, 1);
709+
secp256k1_sha256_write(&sha256, &pcommit->data[1], 32);
710+
secp256k1_sha256_write(&sha256, &value_gen->data[1], 32);
711+
secp256k1_sha256_finalize(&sha256, commit);
712+
713+
if (extra_commit != NULL) {
714+
secp256k1_sha256_initialize(&sha256);
715+
secp256k1_sha256_write(&sha256, commit, 32);
716+
secp256k1_sha256_write(&sha256, extra_commit, extra_commit_len);
717+
secp256k1_sha256_finalize(&sha256, commit);
718+
}
719+
720+
/* Extract A and S to compute y and z */
721+
lrparity = 2 * !!(proof[64] & 1) + !!(proof[64] & 2);
722+
/* y */
723+
secp256k1_sha256_initialize(&sha256);
724+
secp256k1_sha256_write(&sha256, commit, 32);
725+
secp256k1_sha256_write(&sha256, &lrparity, 1);
726+
secp256k1_sha256_write(&sha256, &proof[65], 64);
727+
secp256k1_sha256_finalize(&sha256, commit);
728+
729+
/* z */
730+
secp256k1_sha256_initialize(&sha256);
731+
secp256k1_sha256_write(&sha256, commit, 32);
732+
secp256k1_sha256_write(&sha256, &lrparity, 1);
733+
secp256k1_sha256_write(&sha256, &proof[65], 64);
734+
secp256k1_sha256_finalize(&sha256, commit);
735+
736+
secp256k1_scalar_set_b32(&z, commit, &overflow);
737+
if (overflow || secp256k1_scalar_is_zero(&z)) {
738+
return 0;
739+
}
740+
741+
/* x */
742+
lrparity = 2 * !!(proof[64] & 4) + !!(proof[64] & 8);
743+
secp256k1_sha256_initialize(&sha256);
744+
secp256k1_sha256_write(&sha256, commit, 32);
745+
secp256k1_sha256_write(&sha256, &lrparity, 1);
746+
secp256k1_sha256_write(&sha256, &proof[129], 64);
747+
secp256k1_sha256_finalize(&sha256, commit);
748+
749+
secp256k1_scalar_set_b32(&x, commit, &overflow);
750+
if (overflow || secp256k1_scalar_is_zero(&x)) {
751+
return 0;
752+
}
753+
754+
/* Compute candidate mu and add to (negated) mu from proof to get value */
755+
secp256k1_scalar_mul(&rho, &rho, &x);
756+
secp256k1_scalar_add(&mu, &mu, &rho);
757+
secp256k1_scalar_add(&mu, &mu, &alpha);
758+
759+
secp256k1_scalar_get_b32(commit, &mu);
760+
if (memcmp(commit, zero24, 24) != 0) {
761+
return 0;
762+
}
763+
*value = commit[31] + ((uint64_t) commit[30] << 8) +
764+
((uint64_t) commit[29] << 16) + ((uint64_t) commit[28] << 24) +
765+
((uint64_t) commit[27] << 32) + ((uint64_t) commit[26] << 40) +
766+
((uint64_t) commit[25] << 48) + ((uint64_t) commit[24] << 56);
767+
768+
/* Derive blinding factor */
769+
secp256k1_scalar_mul(&tau1, &tau1, &x);
770+
secp256k1_scalar_mul(&tau2, &tau2, &x);
771+
secp256k1_scalar_mul(&tau2, &tau2, &x);
772+
773+
secp256k1_scalar_add(&taux, &taux, &tau1);
774+
secp256k1_scalar_add(&taux, &taux, &tau2);
775+
776+
secp256k1_scalar_sqr(&z, &z);
777+
secp256k1_scalar_inverse_var(&z, &z);
778+
secp256k1_scalar_mul(blind, &taux, &z);
779+
secp256k1_scalar_negate(blind, blind);
780+
781+
/* Check blinding factor */
782+
secp256k1_pedersen_commitment_load(&commitp, pcommit);
783+
secp256k1_generator_load(&value_genp, value_gen);
784+
785+
secp256k1_pedersen_ecmult(&rewind_commitj, blind, *value, &value_genp, blind_gen);
786+
secp256k1_gej_neg(&rewind_commitj, &rewind_commitj);
787+
secp256k1_gej_add_ge_var(&rewind_commitj, &rewind_commitj, &commitp, NULL);
788+
789+
return secp256k1_gej_is_infinity(&rewind_commitj);
790+
}
791+
648792
#endif

src/modules/bulletproofs/tests_impl.h

+43-3
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,8 @@ static void test_bulletproof_api(void) {
3535
uint64_t value[4] = { 1234, 4567, 8910, 1112 } ;
3636
uint64_t min_value[4] = { 1000, 4567, 0, 5000 } ;
3737
const uint64_t *mv_ptr = min_value;
38-
39-
const char circ_desc_good[] = "2,0,0,4; L0 = 17; 2*L1 - L0 = 21; O0 = 1; O1 = 1;";
40-
const char circ_desc_bad[] = "2,0,0,4; L0 = 17; 2*L1 - L0 = 21; O0 = 1; O1 x 1;";
38+
unsigned char rewind_blind[32];
39+
size_t rewind_v;
4140

4241
int32_t ecount = 0;
4342

@@ -223,6 +222,35 @@ static void test_bulletproof_api(void) {
223222
CHECK(secp256k1_bulletproof_rangeproof_verify_multi(both, scratch, gens, &proof_ptr, 1, plen, &mv_ptr, pcommit_arr, 4, 64, &value_gen, blind_ptr, &blindlen) == 0);
224223
CHECK(ecount == 14);
225224

225+
/* Rewind */
226+
ecount = 0;
227+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 1);
228+
CHECK(ecount == 0);
229+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, NULL, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
230+
CHECK(ecount == 1);
231+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, NULL, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
232+
CHECK(ecount == 2);
233+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, NULL, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
234+
CHECK(ecount == 3);
235+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, NULL, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
236+
CHECK(ecount == 4);
237+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, 0, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
238+
CHECK(ecount == 4);
239+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, 0, pcommit, &value_gen, blind, blind, 32) == 0);
240+
CHECK(ecount == 4);
241+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], NULL, &value_gen, blind, blind, 32) == 0);
242+
CHECK(ecount == 5);
243+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, NULL, blind, blind, 32) == 0);
244+
CHECK(ecount == 6);
245+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, NULL, blind, 32) == 0);
246+
CHECK(ecount == 7);
247+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, NULL, 32) == 0);
248+
CHECK(ecount == 8);
249+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 0) == 0);
250+
CHECK(ecount == 8);
251+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, NULL, 0) == 0);
252+
CHECK(ecount == 8);
253+
226254
secp256k1_bulletproof_generators_destroy(none, gens);
227255
secp256k1_bulletproof_generators_destroy(none, NULL);
228256
secp256k1_scratch_destroy(scratch);
@@ -438,15 +466,18 @@ void test_bulletproof_inner_product(size_t n, const secp256k1_bulletproof_genera
438466

439467
void test_bulletproof_rangeproof(size_t nbits, size_t expected_size, const secp256k1_bulletproof_generators *gens) {
440468
secp256k1_scalar blind;
469+
secp256k1_scalar blind_recovered;
441470
unsigned char proof[1024];
442471
unsigned char proof2[1024];
443472
unsigned char proof3[1024];
444473
const unsigned char *proof_ptr[3];
445474
size_t plen = sizeof(proof);
446475
uint64_t v = 123456;
476+
uint64_t v_recovered;
447477
secp256k1_gej commitj;
448478
secp256k1_ge commitp;
449479
secp256k1_ge commitp2;
480+
secp256k1_pedersen_commitment pcommit;
450481
const secp256k1_ge *commitp_ptr[3];
451482
secp256k1_ge value_gen[3];
452483
unsigned char nonce[32] = "my kingdom for some randomness!!";
@@ -472,6 +503,7 @@ void test_bulletproof_rangeproof(size_t nbits, size_t expected_size, const secp2
472503
secp256k1_ge_set_gej(&commitp2, &commitj);
473504
commitp_ptr[0] = commitp_ptr[1] = &commitp;
474505
commitp_ptr[2] = &commitp2;
506+
secp256k1_pedersen_commitment_save(&pcommit, &commitp);
475507

476508
CHECK(secp256k1_bulletproof_rangeproof_prove_impl(&ctx->ecmult_ctx, scratch, proof, &plen, nbits, &v, NULL, &blind, &commitp, 1, &value_gen[0], gens, nonce, NULL, 0) == 1);
477509
CHECK(plen == expected_size);
@@ -489,6 +521,14 @@ void test_bulletproof_rangeproof(size_t nbits, size_t expected_size, const secp2
489521
/* Verify thrice at once where one has a different asset type */
490522
CHECK(secp256k1_bulletproof_rangeproof_verify_impl(&ctx->ecmult_ctx, scratch, proof_ptr, 3, plen, nbits, NULL, commitp_ptr, 1, value_gen, gens, NULL, 0) == 1);
491523

524+
/* Rewind */
525+
CHECK(secp256k1_bulletproof_rangeproof_rewind_impl(&v_recovered, &blind_recovered, proof, plen, 0, &pcommit, &secp256k1_generator_const_g, gens->blinding_gen, nonce, NULL, 0) == 1);
526+
CHECK(v_recovered == v);
527+
CHECK(secp256k1_scalar_eq(&blind_recovered, &blind) == 1);
528+
529+
nonce[0] ^= 111;
530+
CHECK(secp256k1_bulletproof_rangeproof_rewind_impl(&v_recovered, &blind_recovered, proof, plen, 0, &pcommit, &secp256k1_generator_const_g, gens->blinding_gen, nonce, NULL, 0) == 0);
531+
492532
secp256k1_scratch_destroy(scratch);
493533
}
494534

0 commit comments

Comments
 (0)