Skip to content

Commit 5e90403

Browse files
committed
rangeproof: add method to verify single-value proofs
1 parent 9eea081 commit 5e90403

File tree

3 files changed

+118
-0
lines changed

3 files changed

+118
-0
lines changed

include/secp256k1_rangeproof.h

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,29 @@ SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info(
286286
size_t plen
287287
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
288288

289+
/** Verify a rangeproof with a single-value range. Useful as a "proof of value"
290+
* of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_sign`
291+
* by passing an `exp` parameter of -1 and the target value as both `value` and `min_value`.
292+
* (In this case `min_bits` is ignored and may take any value, but for clarity it's best
293+
* to pass zero.)
294+
* Returns 1: Proof was valid and proved the given value
295+
* 0: Otherwise
296+
* In: ctx: pointer to a context object
297+
* proof: pointer to character array with the proof.
298+
* plen: length of proof in bytes.
299+
* value: value being claimed for the Pedersen commitment
300+
* commit: the Pedersen commitment whose value is being proven
301+
* gen: additional generator 'h'
302+
*/
303+
SECP256K1_API int secp256k1_rangeproof_verify_value(
304+
const secp256k1_context* ctx,
305+
const unsigned char* proof,
306+
size_t plen,
307+
uint64_t value,
308+
const secp256k1_pedersen_commitment* commit,
309+
const secp256k1_generator* gen
310+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);
311+
289312
# ifdef __cplusplus
290313
}
291314
# endif

src/modules/rangeproof/main_impl.h

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,4 +304,97 @@ int secp256k1_rangeproof_sign(const secp256k1_context* ctx, unsigned char *proof
304304
proof, plen, min_value, &commitp, blind, nonce, exp, min_bits, value, message, msg_len, extra_commit, extra_commit_len, &genp);
305305
}
306306

307+
int secp256k1_rangeproof_verify_value(const secp256k1_context* ctx, const unsigned char* proof, size_t plen, uint64_t value, const secp256k1_pedersen_commitment* commit, const secp256k1_generator* gen) {
308+
secp256k1_ge commitp;
309+
secp256k1_ge genp;
310+
secp256k1_gej tmpj;
311+
secp256k1_gej xj;
312+
secp256k1_ge rp;
313+
secp256k1_scalar es;
314+
secp256k1_scalar ss;
315+
secp256k1_sha256 sha2;
316+
unsigned char tmpch[33];
317+
unsigned char pp_comm[32];
318+
size_t offset;
319+
size_t sz;
320+
int overflow;
321+
322+
VERIFY_CHECK(ctx != NULL);
323+
ARG_CHECK(proof != NULL);
324+
ARG_CHECK(commit != NULL);
325+
ARG_CHECK(gen != NULL);
326+
327+
if (plen != 73 && plen != 65) {
328+
return 0;
329+
}
330+
/* 0x80 must be unset for any rangeproof; 0x40 indicates "has nonzero range"
331+
* so must also be unset for single-value proofs */
332+
if ((proof[0] & 0xC0) != 0x00) {
333+
return 0;
334+
}
335+
336+
secp256k1_pedersen_commitment_load(&commitp, commit);
337+
secp256k1_generator_load(&genp, gen);
338+
/* Verify that value in the header is what we expect; 0x20 is "has nonzero min-value" */
339+
if ((proof[0] & 0x20) == 0x00) {
340+
if (value != 0) {
341+
return 0;
342+
}
343+
offset = 1;
344+
} else {
345+
uint64_t claimed = 0;
346+
/* Iterate from 0 to 8, setting `offset` to 9 as a side-effect */
347+
for (offset = 1; offset < 9; offset++) {
348+
claimed = (claimed << 8) | proof[offset];
349+
}
350+
if (value != claimed) {
351+
return 0;
352+
}
353+
}
354+
/* Subtract value from commitment; store modified commitment in xj */
355+
secp256k1_pedersen_ecmult_small(&tmpj, value, &genp);
356+
secp256k1_gej_neg(&tmpj, &tmpj);
357+
secp256k1_gej_add_ge_var(&xj, &tmpj, &commitp, NULL);
358+
359+
/* Now we just have a Schnorr signature in (e, s) form. The verification
360+
* equation is e == H(sG - eX || proof params) */
361+
362+
/* 1. Compute slow/overwrought commitment to proof params */
363+
secp256k1_sha256_initialize(&sha2);
364+
secp256k1_rangeproof_serialize_point(tmpch, &commitp);
365+
secp256k1_sha256_write(&sha2, tmpch, 33);
366+
secp256k1_rangeproof_serialize_point(tmpch, &genp);
367+
secp256k1_sha256_write(&sha2, tmpch, 33);
368+
secp256k1_sha256_write(&sha2, proof, offset); /* lol we commit to one extra byte here */
369+
secp256k1_sha256_finalize(&sha2, pp_comm);
370+
371+
/* ... feed this into our hash */
372+
secp256k1_borromean_hash(tmpch, pp_comm, 32, &proof[offset], 32, 0, 0);
373+
secp256k1_scalar_set_b32(&es, tmpch, &overflow);
374+
if (overflow || secp256k1_scalar_is_zero(&es)) {
375+
return 0;
376+
}
377+
378+
/* 1. Compute R = sG - eX */
379+
secp256k1_scalar_set_b32(&ss, &proof[offset + 32], &overflow);
380+
if (overflow || secp256k1_scalar_is_zero(&ss)) {
381+
return 0;
382+
}
383+
secp256k1_ecmult(&tmpj, &xj, &es, &ss);
384+
if (secp256k1_gej_is_infinity(&tmpj)) {
385+
return 0;
386+
}
387+
secp256k1_ge_set_gej(&rp, &tmpj);
388+
secp256k1_eckey_pubkey_serialize(&rp, tmpch, &sz, 1);
389+
390+
/* 2. Compute e = H(R || proof params) */
391+
secp256k1_sha256_initialize(&sha2);
392+
secp256k1_sha256_write(&sha2, tmpch, sz);
393+
secp256k1_sha256_write(&sha2, pp_comm, sizeof(pp_comm));
394+
secp256k1_sha256_finalize(&sha2, tmpch);
395+
396+
/* 3. Check computed e against original e */
397+
return !memcmp(tmpch, &proof[offset], 32);
398+
}
399+
307400
#endif

src/modules/rangeproof/tests_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -919,6 +919,8 @@ void test_rangeproof_fixed_vectors(void) {
919919
CHECK(min_value == UINT64_MAX);
920920
CHECK(max_value == UINT64_MAX);
921921
CHECK(m_len == 0);
922+
923+
CHECK(secp256k1_rangeproof_verify_value(ctx, vector_3, sizeof(vector_3), UINT64_MAX, &pc, secp256k1_generator_h));
922924
}
923925
}
924926

0 commit comments

Comments
 (0)