Skip to content

Commit

Permalink
rangeproof: add method to verify single-value proofs
Browse files Browse the repository at this point in the history
  • Loading branch information
apoelstra committed Jul 26, 2022
1 parent 9eea081 commit 5e90403
Show file tree
Hide file tree
Showing 3 changed files with 118 additions and 0 deletions.
23 changes: 23 additions & 0 deletions include/secp256k1_rangeproof.h
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,29 @@ 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);

/** Verify a rangeproof with a single-value range. Useful as a "proof of value"
* of a Pedersen commitment. Such proofs can be created with `secp256k1_rangeproof_sign`
* by passing an `exp` parameter of -1 and the target value as both `value` and `min_value`.
* (In this case `min_bits` is ignored and may take any value, but for clarity it's best
* to pass zero.)
* Returns 1: Proof was valid and proved the given value
* 0: Otherwise
* In: ctx: pointer to a context object
* proof: pointer to character array with the proof.
* plen: length of proof in bytes.
* value: value being claimed for the Pedersen commitment
* commit: the Pedersen commitment whose value is being proven
* gen: additional generator 'h'
*/
SECP256K1_API 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
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);

# ifdef __cplusplus
}
# endif
Expand Down
93 changes: 93 additions & 0 deletions src/modules/rangeproof/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -304,4 +304,97 @@ 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);
}

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) {
secp256k1_ge commitp;
secp256k1_ge genp;
secp256k1_gej tmpj;
secp256k1_gej xj;
secp256k1_ge rp;
secp256k1_scalar es;
secp256k1_scalar ss;
secp256k1_sha256 sha2;
unsigned char tmpch[33];
unsigned char pp_comm[32];
size_t offset;
size_t sz;
int overflow;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
ARG_CHECK(commit != NULL);
ARG_CHECK(gen != NULL);

if (plen != 73 && plen != 65) {
return 0;
}
/* 0x80 must be unset for any rangeproof; 0x40 indicates "has nonzero range"
* so must also be unset for single-value proofs */
if ((proof[0] & 0xC0) != 0x00) {
return 0;
}

secp256k1_pedersen_commitment_load(&commitp, commit);
secp256k1_generator_load(&genp, gen);
/* Verify that value in the header is what we expect; 0x20 is "has nonzero min-value" */
if ((proof[0] & 0x20) == 0x00) {
if (value != 0) {
return 0;
}
offset = 1;
} else {
uint64_t claimed = 0;
/* Iterate from 0 to 8, setting `offset` to 9 as a side-effect */
for (offset = 1; offset < 9; offset++) {
claimed = (claimed << 8) | proof[offset];
}
if (value != claimed) {
return 0;
}
}
/* Subtract value from commitment; store modified commitment in xj */
secp256k1_pedersen_ecmult_small(&tmpj, value, &genp);
secp256k1_gej_neg(&tmpj, &tmpj);
secp256k1_gej_add_ge_var(&xj, &tmpj, &commitp, NULL);

/* Now we just have a Schnorr signature in (e, s) form. The verification
* equation is e == H(sG - eX || proof params) */

/* 1. Compute slow/overwrought commitment to proof params */
secp256k1_sha256_initialize(&sha2);
secp256k1_rangeproof_serialize_point(tmpch, &commitp);
secp256k1_sha256_write(&sha2, tmpch, 33);
secp256k1_rangeproof_serialize_point(tmpch, &genp);
secp256k1_sha256_write(&sha2, tmpch, 33);
secp256k1_sha256_write(&sha2, proof, offset); /* lol we commit to one extra byte here */
secp256k1_sha256_finalize(&sha2, pp_comm);

/* ... feed this into our hash */
secp256k1_borromean_hash(tmpch, pp_comm, 32, &proof[offset], 32, 0, 0);
secp256k1_scalar_set_b32(&es, tmpch, &overflow);
if (overflow || secp256k1_scalar_is_zero(&es)) {
return 0;
}

/* 1. Compute R = sG - eX */
secp256k1_scalar_set_b32(&ss, &proof[offset + 32], &overflow);
if (overflow || secp256k1_scalar_is_zero(&ss)) {
return 0;
}
secp256k1_ecmult(&tmpj, &xj, &es, &ss);
if (secp256k1_gej_is_infinity(&tmpj)) {
return 0;
}
secp256k1_ge_set_gej(&rp, &tmpj);
secp256k1_eckey_pubkey_serialize(&rp, tmpch, &sz, 1);

/* 2. Compute e = H(R || proof params) */
secp256k1_sha256_initialize(&sha2);
secp256k1_sha256_write(&sha2, tmpch, sz);
secp256k1_sha256_write(&sha2, pp_comm, sizeof(pp_comm));
secp256k1_sha256_finalize(&sha2, tmpch);

/* 3. Check computed e against original e */
return !memcmp(tmpch, &proof[offset], 32);
}

#endif
2 changes: 2 additions & 0 deletions src/modules/rangeproof/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -919,6 +919,8 @@ void test_rangeproof_fixed_vectors(void) {
CHECK(min_value == UINT64_MAX);
CHECK(max_value == UINT64_MAX);
CHECK(m_len == 0);

CHECK(secp256k1_rangeproof_verify_value(ctx, vector_3, sizeof(vector_3), UINT64_MAX, &pc, secp256k1_generator_h));
}
}

Expand Down

0 comments on commit 5e90403

Please sign in to comment.