Skip to content

Commit 5b88fe2

Browse files
committed
bulletproofs: add rangeproof rewinding capability
1 parent a23af85 commit 5b88fe2

File tree

4 files changed

+238
-0
lines changed

4 files changed

+238
-0
lines changed

include/secp256k1_bulletproofs.h

+30
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,36 @@ SECP256K1_API int secp256k1_bulletproof_rangeproof_verify_multi(
109109
size_t *extra_commit_len
110110
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8);
111111

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

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

src/modules/bulletproofs/main_impl.h

+21
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,27 @@ int secp256k1_bulletproof_rangeproof_verify_multi(const secp256k1_context* ctx,
156156
return ret;
157157
}
158158

159+
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) {
160+
secp256k1_scalar blinds;
161+
int ret;
162+
163+
VERIFY_CHECK(ctx != NULL);
164+
ARG_CHECK(value != NULL);
165+
ARG_CHECK(blind != NULL);
166+
ARG_CHECK(gens != NULL);
167+
ARG_CHECK(proof != NULL);
168+
ARG_CHECK(commit != NULL);
169+
ARG_CHECK(value_gen != NULL);
170+
ARG_CHECK(nonce != NULL);
171+
ARG_CHECK(extra_commit != NULL || extra_commit_len == 0);
172+
173+
ret = secp256k1_bulletproof_rangeproof_rewind_impl(value, &blinds, proof, plen, min_value, commit, value_gen, gens->blinding_gen, nonce, extra_commit, extra_commit_len);
174+
if (ret == 1) {
175+
secp256k1_scalar_get_b32(blind, &blinds);
176+
}
177+
return ret;
178+
}
179+
159180
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) {
160181
int ret;
161182
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
Original file line numberDiff line numberDiff line change
@@ -35,6 +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+
unsigned char rewind_blind[32];
39+
size_t rewind_v;
3840

3941
int32_t ecount = 0;
4042

@@ -212,6 +214,35 @@ static void test_bulletproof_api(void) {
212214
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);
213215
CHECK(ecount == 14);
214216

217+
/* Rewind */
218+
ecount = 0;
219+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 1);
220+
CHECK(ecount == 0);
221+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, NULL, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
222+
CHECK(ecount == 1);
223+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, NULL, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
224+
CHECK(ecount == 2);
225+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, NULL, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
226+
CHECK(ecount == 3);
227+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, NULL, plen, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
228+
CHECK(ecount == 4);
229+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, 0, min_value[0], pcommit, &value_gen, blind, blind, 32) == 0);
230+
CHECK(ecount == 4);
231+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, 0, pcommit, &value_gen, blind, blind, 32) == 0);
232+
CHECK(ecount == 4);
233+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], NULL, &value_gen, blind, blind, 32) == 0);
234+
CHECK(ecount == 5);
235+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, NULL, blind, blind, 32) == 0);
236+
CHECK(ecount == 6);
237+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, NULL, blind, 32) == 0);
238+
CHECK(ecount == 7);
239+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, NULL, 32) == 0);
240+
CHECK(ecount == 8);
241+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, blind, 0) == 0);
242+
CHECK(ecount == 8);
243+
CHECK(secp256k1_bulletproof_rangeproof_rewind(none, gens, &rewind_v, rewind_blind, proof, plen, min_value[0], pcommit, &value_gen, blind, NULL, 0) == 0);
244+
CHECK(ecount == 8);
245+
215246
secp256k1_bulletproof_generators_destroy(none, gens);
216247
secp256k1_bulletproof_generators_destroy(none, NULL);
217248
secp256k1_scratch_destroy(scratch);
@@ -427,15 +458,18 @@ void test_bulletproof_inner_product(size_t n, const secp256k1_bulletproof_genera
427458

428459
void test_bulletproof_rangeproof(size_t nbits, size_t expected_size, const secp256k1_bulletproof_generators *gens) {
429460
secp256k1_scalar blind;
461+
secp256k1_scalar blind_recovered;
430462
unsigned char proof[1024];
431463
unsigned char proof2[1024];
432464
unsigned char proof3[1024];
433465
const unsigned char *proof_ptr[3];
434466
size_t plen = sizeof(proof);
435467
uint64_t v = 123456;
468+
uint64_t v_recovered;
436469
secp256k1_gej commitj;
437470
secp256k1_ge commitp;
438471
secp256k1_ge commitp2;
472+
secp256k1_pedersen_commitment pcommit;
439473
const secp256k1_ge *commitp_ptr[3];
440474
secp256k1_ge value_gen[3];
441475
unsigned char nonce[32] = "my kingdom for some randomness!!";
@@ -461,6 +495,7 @@ void test_bulletproof_rangeproof(size_t nbits, size_t expected_size, const secp2
461495
secp256k1_ge_set_gej(&commitp2, &commitj);
462496
commitp_ptr[0] = commitp_ptr[1] = &commitp;
463497
commitp_ptr[2] = &commitp2;
498+
secp256k1_pedersen_commitment_save(&pcommit, &commitp);
464499

465500
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);
466501
CHECK(plen == expected_size);
@@ -478,6 +513,14 @@ void test_bulletproof_rangeproof(size_t nbits, size_t expected_size, const secp2
478513
/* Verify thrice at once where one has a different asset type */
479514
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);
480515

516+
/* Rewind */
517+
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);
518+
CHECK(v_recovered == v);
519+
CHECK(secp256k1_scalar_eq(&blind_recovered, &blind) == 1);
520+
521+
nonce[0] ^= 111;
522+
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);
523+
481524
secp256k1_scratch_destroy(scratch);
482525
}
483526

0 commit comments

Comments
 (0)