Skip to content

Commit 14b003f

Browse files
committed
bulletproofs: add rangeproof rewinding capability
1 parent 72ffc02 commit 14b003f

File tree

4 files changed

+227
-0
lines changed

4 files changed

+227
-0
lines changed

include/secp256k1_bulletproofs.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,36 @@ SECP256K1_API int secp256k1_bulletproof_rangeproof_verify_multi(
103103
size_t *extra_commit_len
104104
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(8);
105105

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

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

src/modules/bulletproofs/main_impl.h

Lines changed: 21 additions & 0 deletions
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

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -490,6 +490,13 @@ static int secp256k1_bulletproof_rangeproof_prove_impl(const secp256k1_ecmult_co
490490

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

494501
/* Compute A and S */
495502
secp256k1_ecmult_const(&aj, &gens->blinding_gen[0], &alpha, 256);
@@ -647,4 +654,130 @@ static int secp256k1_bulletproof_rangeproof_prove_impl(const secp256k1_ecmult_co
647654

648655
return 1;
649656
}
657+
658+
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) {
659+
secp256k1_sha256 sha256;
660+
static const unsigned char zero24[24] = { 0 };
661+
unsigned char commit[32] = { 0 };
662+
unsigned char lrparity;
663+
secp256k1_scalar taux, mu;
664+
secp256k1_scalar alpha, rho, tau1, tau2;
665+
secp256k1_scalar x, z;
666+
secp256k1_ge commitp, value_genp;
667+
secp256k1_gej rewind_commitj;
668+
int overflow;
669+
670+
if (plen < 64 + 128 + 1 || plen > SECP256K1_BULLETPROOF_MAX_PROOF) {
671+
return 0;
672+
}
673+
674+
/* Extract data from beginning of proof */
675+
secp256k1_scalar_set_b32(&taux, &proof[0], &overflow);
676+
if (overflow || secp256k1_scalar_is_zero(&taux)) {
677+
return 0;
678+
}
679+
secp256k1_scalar_set_b32(&mu, &proof[32], &overflow);
680+
if (overflow || secp256k1_scalar_is_zero(&mu)) {
681+
return 0;
682+
}
683+
684+
secp256k1_scalar_chacha20(&alpha, &rho, nonce, 0);
685+
secp256k1_scalar_chacha20(&tau1, &tau2, nonce, 1);
686+
687+
if (min_value > 0) {
688+
unsigned char vbuf[8];
689+
vbuf[0] = min_value;
690+
vbuf[1] = min_value >> 8;
691+
vbuf[2] = min_value >> 16;
692+
vbuf[3] = min_value >> 24;
693+
vbuf[4] = min_value >> 32;
694+
vbuf[5] = min_value >> 40;
695+
vbuf[6] = min_value >> 48;
696+
vbuf[7] = min_value >> 56;
697+
secp256k1_sha256_initialize(&sha256);
698+
secp256k1_sha256_write(&sha256, commit, 32);
699+
secp256k1_sha256_write(&sha256, vbuf, 8);
700+
secp256k1_sha256_finalize(&sha256, commit);
701+
}
702+
703+
secp256k1_pedersen_commitment_load(&commitp, pcommit);
704+
secp256k1_generator_load(&value_genp, value_gen);
705+
secp256k1_bulletproof_update_commit(commit, &commitp, &value_genp);
706+
707+
if (extra_commit != NULL) {
708+
secp256k1_sha256_initialize(&sha256);
709+
secp256k1_sha256_write(&sha256, commit, 32);
710+
secp256k1_sha256_write(&sha256, extra_commit, extra_commit_len);
711+
secp256k1_sha256_finalize(&sha256, commit);
712+
}
713+
714+
/* Extract A and S to compute y and z */
715+
lrparity = 2 * !!(proof[64] & 1) + !!(proof[64] & 2);
716+
/* y */
717+
secp256k1_sha256_initialize(&sha256);
718+
secp256k1_sha256_write(&sha256, commit, 32);
719+
secp256k1_sha256_write(&sha256, &lrparity, 1);
720+
secp256k1_sha256_write(&sha256, &proof[65], 64);
721+
secp256k1_sha256_finalize(&sha256, commit);
722+
723+
/* z */
724+
secp256k1_sha256_initialize(&sha256);
725+
secp256k1_sha256_write(&sha256, commit, 32);
726+
secp256k1_sha256_write(&sha256, &lrparity, 1);
727+
secp256k1_sha256_write(&sha256, &proof[65], 64);
728+
secp256k1_sha256_finalize(&sha256, commit);
729+
730+
secp256k1_scalar_set_b32(&z, commit, &overflow);
731+
if (overflow || secp256k1_scalar_is_zero(&z)) {
732+
return 0;
733+
}
734+
735+
/* x */
736+
lrparity = 2 * !!(proof[64] & 4) + !!(proof[64] & 8);
737+
secp256k1_sha256_initialize(&sha256);
738+
secp256k1_sha256_write(&sha256, commit, 32);
739+
secp256k1_sha256_write(&sha256, &lrparity, 1);
740+
secp256k1_sha256_write(&sha256, &proof[129], 64);
741+
secp256k1_sha256_finalize(&sha256, commit);
742+
743+
secp256k1_scalar_set_b32(&x, commit, &overflow);
744+
if (overflow || secp256k1_scalar_is_zero(&x)) {
745+
return 0;
746+
}
747+
748+
/* Compute candidate mu and add to (negated) mu from proof to get value */
749+
secp256k1_scalar_mul(&rho, &rho, &x);
750+
secp256k1_scalar_add(&mu, &mu, &rho);
751+
secp256k1_scalar_add(&mu, &mu, &alpha);
752+
753+
secp256k1_scalar_get_b32(commit, &mu);
754+
if (memcmp(commit, zero24, 24) != 0) {
755+
return 0;
756+
}
757+
*value = commit[31] + ((uint64_t) commit[30] << 8) +
758+
((uint64_t) commit[29] << 16) + ((uint64_t) commit[28] << 24) +
759+
((uint64_t) commit[27] << 32) + ((uint64_t) commit[26] << 40) +
760+
((uint64_t) commit[25] << 48) + ((uint64_t) commit[24] << 56);
761+
762+
/* Derive blinding factor */
763+
secp256k1_scalar_mul(&tau1, &tau1, &x);
764+
secp256k1_scalar_mul(&tau2, &tau2, &x);
765+
secp256k1_scalar_mul(&tau2, &tau2, &x);
766+
767+
secp256k1_scalar_add(&taux, &taux, &tau1);
768+
secp256k1_scalar_add(&taux, &taux, &tau2);
769+
770+
secp256k1_scalar_sqr(&z, &z);
771+
secp256k1_scalar_inverse_var(&z, &z);
772+
secp256k1_scalar_mul(blind, &taux, &z);
773+
secp256k1_scalar_negate(blind, blind);
774+
775+
/* Check blinding factor */
776+
secp256k1_pedersen_ecmult(&rewind_commitj, blind, *value, &value_genp, blind_gen);
777+
secp256k1_gej_neg(&rewind_commitj, &rewind_commitj);
778+
secp256k1_gej_add_ge_var(&rewind_commitj, &rewind_commitj, &commitp, NULL);
779+
780+
return secp256k1_gej_is_infinity(&rewind_commitj);
781+
}
782+
650783
#endif

src/modules/bulletproofs/tests_impl.h

Lines changed: 43 additions & 0 deletions
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)