Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose Borromean ring signature and de-anonymization functionality #110

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 93 additions & 0 deletions include/secp256k1_rangeproof.h
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,99 @@ 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);

/** Returns the serialized size of a ring signature with the given number of keys
* Args: ctx: pointer to a context object (cannot be NULL)
* In: n_keys: number of keys
*/
SECP256K1_API size_t secp256k1_ring_signature_length(
const secp256k1_context* ctx,
size_t n_keys
) SECP256K1_ARG_NONNULL(1);

/** Returns the minimum scratch space size to sign or verify a ring signature with the given number of keys
* Args: ctx: pointer to a context object (cannot be NULL)
* In: n_keys: number of keys
*/
SECP256K1_API size_t secp256k1_ring_signature_scratch_space_size(
const secp256k1_context* ctx,
size_t n_keys
) SECP256K1_ARG_NONNULL(1);

/** Produces a linear-sized ring signature with a given set of keys, returning a "proof" which can be used to reveal the signer
* Returns 1: Proof successfully created.
* 0: if sig_len was less than the `secp256k1_ring_signature_length` for this number of keys
* Args: ctx: pointer to a context object, initialized for both signing and verification (cannot be NULL)
* scratch: scratch space with enough space available (to learn a minimum size
* use `secp256k1_ring_signature_scratch_space_size`) (cannot be NULL)
* Out: sig: pointer to array to receive the signature (cannot be NULL)
* author_proof: if non-NULL, pointer to a 32-byte byte array to receive a proof of authorship
* In: pubkeys: pointer to array of public keys (cannot be NULL)
* n_pubkeys: length of the above array
* sec_key: secret key to sign with (cannot be NULL)
* sec_idx: index of the signer in the list of pubkeys
* nonce: if non-NULL, 32-byte secret nonce used to randomize the proof; to make a proof it is
* impossible to revoke anonymity on, leave `author_proof` NULL and throw this value away
* message: 32-byte message hash to sign (cannot be NULL)
* In/out: sig_len: pointer to the size of `sig`, overwritten with the actual proof size (cannot be NULL)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ring_sign(
const secp256k1_context* ctx,
secp256k1_scratch_space* scratch,
unsigned char* sig,
size_t* sig_len,
unsigned char* author_proof,
const secp256k1_pubkey* pubkeys,
size_t n_pubkeys,
const unsigned char *sec_key,
size_t sec_idx,
const unsigned char *nonce,
const unsigned char *message
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(11);

/** Verify a ring signature
* Returns 1: Signature was valid
* 0: Signature was not valid
* Args: ctx: pointer to a context object, initialized for verification (cannot be NULL)
* scratch: scratch space with enough space available (to learn a minimum size
* use `secp256k1_ring_signature_scratch_space_size`) (cannot be NULL)
* In: sig: pointer to the signature to verify (cannot be NULL)
* sig_len: length of the signature in bytes.
* pubkeys: pointer to array of public keys (cannot be NULL)
* n_pubkeys: length of the above array
* message: 32-byte message hash that was signed (cannot be NULL)
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ring_verify(
const secp256k1_context* ctx,
secp256k1_scratch_space* scratch,
const unsigned char* sig,
size_t sig_len,
const secp256k1_pubkey* pubkeys,
size_t n_pubkeys,
const unsigned char *message
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7);

/** De-anonymizes a ring signature, given a 32-byte author proof
* Returns 1: Signature was successfully deanonymized
* 0: Author proof was a lie
* Args: ctx: pointer to a context object (cannot be NULL)
* Out: author_idx: populated with the index of the author of the ring signature on success;
* if the function fails, this value is unspecified (cannot be NULL)
* In: sig: pointer to the signature to verify (cannot be NULL)
* sig_len: length of the signature in bytes.
* author_proof: an author proof as output from `secp256k1_ring_sign` (cannot be NULL)
* pubkeys: pointer to array of public keys (cannot be NULL)
* n_pubkeys: length of the above array
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ring_deanonymize(
const secp256k1_context* ctx,
size_t *author_idx,
const unsigned char* sig,
size_t sig_len,
const unsigned char* author_proof,
const secp256k1_pubkey* pubkeys,
size_t n_pubkeys
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6);

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

size_t secp256k1_ring_signature_length(const secp256k1_context* ctx, size_t n_keys) {
(void) ctx;
if (SIZE_MAX / 32 < n_keys + 1)
return 0;
return 32 * (n_keys + 1);
}

size_t secp256k1_ring_signature_scratch_space_size(const secp256k1_context* ctx, size_t n_keys) {
(void) ctx;
if (SIZE_MAX / (sizeof(secp256k1_gej) + sizeof(secp256k1_scalar)) < n_keys) {
return 0;
} else {
size_t gej_len = ROUND_TO_ALIGN(sizeof(secp256k1_gej) * n_keys);
size_t sca_len = ROUND_TO_ALIGN(sizeof(secp256k1_scalar) * n_keys);
if (gej_len + sca_len < gej_len) {
return 0;
}
return gej_len + sca_len;
}
}

int secp256k1_ring_sign(const secp256k1_context* ctx, secp256k1_scratch_space* scratch, unsigned char* sig, size_t* sig_len, unsigned char* author_proof, const secp256k1_pubkey* pubkeys, size_t n_pubkeys, const unsigned char *sec_key, size_t sec_idx, const unsigned char *nonce, const unsigned char *message) {
size_t scratch_checkpoint;
secp256k1_rfc6979_hmac_sha256 rng;
int overflow;
secp256k1_scalar x;
secp256k1_scalar k;
secp256k1_scalar *s;
secp256k1_gej *pubs;
secp256k1_sha256 sha2eng;
unsigned char buf[32];
size_t i;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
ARG_CHECK(scratch != NULL);
ARG_CHECK(sig != NULL);
ARG_CHECK(sig_len != NULL);
ARG_CHECK(pubkeys != NULL);
ARG_CHECK(sec_key != NULL);
ARG_CHECK(message != NULL);

/* Check for overflow, or 0 pubkeys (which is technically ok but probably
* more confusing than meaningful) */
if (secp256k1_ring_signature_length(ctx, n_pubkeys) == 0 || secp256k1_ring_signature_scratch_space_size(ctx, n_pubkeys) == 0) {
return 0;
}
if (*sig_len >= secp256k1_ring_signature_length(ctx, n_pubkeys)) {
*sig_len = secp256k1_ring_signature_length(ctx, n_pubkeys);
} else {
return 0;
}

secp256k1_scalar_set_b32(&x, sec_key, &overflow);
if (overflow || secp256k1_scalar_is_zero(&x)) {
return 0;
}

/* Directly derive `k` from the nonce */
secp256k1_sha256_initialize(&sha2eng);
secp256k1_sha256_write(&sha2eng, (unsigned char*)"ringsig/k", 9);
secp256k1_sha256_finalize(&sha2eng, buf);

secp256k1_sha256_initialize(&sha2eng);
secp256k1_sha256_write(&sha2eng, buf, 32);
secp256k1_sha256_write(&sha2eng, buf, 32);
secp256k1_sha256_write(&sha2eng, sec_key, 32);
secp256k1_sha256_write(&sha2eng, message, 32);
if (nonce != NULL) {
secp256k1_sha256_write(&sha2eng, nonce, 32);
}
secp256k1_sha256_finalize(&sha2eng, buf);

secp256k1_scalar_set_b32(&k, buf, &overflow);
if (overflow || secp256k1_scalar_is_zero(&k)) {
return 0;
}

/* Use a different tagged hash to get a seed for the `s` values */
secp256k1_sha256_initialize(&sha2eng);
secp256k1_sha256_write(&sha2eng, (unsigned char*)"ringsig/s", 9);
secp256k1_sha256_finalize(&sha2eng, buf);

secp256k1_sha256_initialize(&sha2eng);
secp256k1_sha256_write(&sha2eng, buf, 32);
secp256k1_sha256_write(&sha2eng, buf, 32);
secp256k1_sha256_write(&sha2eng, sec_key, 32);
secp256k1_sha256_write(&sha2eng, message, 32);
if (nonce != NULL) {
secp256k1_sha256_write(&sha2eng, nonce, 32);
}
secp256k1_sha256_finalize(&sha2eng, buf);

if (author_proof != NULL) {
memcpy(author_proof, buf, 32);
}

/* Generate s values from the author proof (and unpack pubkeys while we're at it) */
scratch_checkpoint = secp256k1_scratch_checkpoint(&ctx->error_callback, scratch);
s = (secp256k1_scalar*)secp256k1_scratch_alloc(&ctx->error_callback, scratch, n_pubkeys * sizeof(*s));
pubs = (secp256k1_gej*)secp256k1_scratch_alloc(&ctx->error_callback, scratch, n_pubkeys * sizeof(*pubs));
if (s == NULL || pubs == NULL) {
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);
return 0;
}

secp256k1_rfc6979_hmac_sha256_initialize(&rng, buf, 32);
for (i = 0; i < n_pubkeys; i++) {
secp256k1_ge pubp;
CHECK (secp256k1_pubkey_load(ctx, &pubp, &pubkeys[i]));
secp256k1_gej_set_ge(&pubs[i], &pubp);

do {
secp256k1_rfc6979_hmac_sha256_generate(&rng, buf, 32);
secp256k1_scalar_set_b32(&s[i], buf, &overflow);
} while (overflow || secp256k1_scalar_is_zero(&s[i]));
}

/* Produce the proof -- will update the `s` array and output `e0` in `buf` */
if (!secp256k1_borromean_sign(&ctx->ecmult_ctx, &ctx->ecmult_gen_ctx, buf, s, pubs, &k, &x, &n_pubkeys, &sec_idx, 1, message, 32)) {
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);
return 0;
}
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);

/* Serialize it */
memcpy(sig, buf, 32);
for (i = 0; i < n_pubkeys; i++) {
secp256k1_scalar_get_b32(&sig[32 * (1 + i)], &s[i]);
}

return 1;
}

int secp256k1_ring_verify(const secp256k1_context* ctx, secp256k1_scratch_space* scratch, const unsigned char* sig, size_t sig_len, const secp256k1_pubkey* pubkeys, size_t n_pubkeys, const unsigned char *message) {
size_t scratch_checkpoint;
secp256k1_scalar *s;
secp256k1_gej *pubs;
size_t i;
int overflow;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
ARG_CHECK(scratch != NULL);
ARG_CHECK(sig != NULL);
ARG_CHECK(pubkeys != NULL);
ARG_CHECK(message != NULL);

/* Check for overflow, or 0 pubkeys (which is technically ok but probably
* more confusing than meaningful) */
if (secp256k1_ring_signature_length(ctx, n_pubkeys) == 0 || secp256k1_ring_signature_scratch_space_size(ctx, n_pubkeys) == 0) {
return 0;
}
if (sig_len != secp256k1_ring_signature_length(ctx, n_pubkeys)) {
return 0;
}

scratch_checkpoint = secp256k1_scratch_checkpoint(&ctx->error_callback, scratch);
s = (secp256k1_scalar*)secp256k1_scratch_alloc(&ctx->error_callback, scratch, n_pubkeys * sizeof(*s));
pubs = (secp256k1_gej*)secp256k1_scratch_alloc(&ctx->error_callback, scratch, n_pubkeys * sizeof(*pubs));
if (s == NULL || pubs == NULL) {
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);
return 0;
}

for (i = 0; i < n_pubkeys; i++) {
secp256k1_ge pubp;
CHECK (secp256k1_pubkey_load(ctx, &pubp, &pubkeys[i]));
secp256k1_gej_set_ge(&pubs[i], &pubp);

secp256k1_scalar_set_b32(&s[i], &sig[32 * (1 + i)], &overflow);
if (overflow || secp256k1_scalar_is_zero(&s[i])) {
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);
return 0;
}
}

if (!secp256k1_borromean_verify(&ctx->ecmult_ctx, NULL, sig, s, pubs, &n_pubkeys, 1, message, 32)) {
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);
return 0;
}
secp256k1_scratch_apply_checkpoint(&ctx->error_callback, scratch, scratch_checkpoint);
return 1;
}

int secp256k1_ring_deanonymize(const secp256k1_context* ctx, size_t *author_idx, const unsigned char* sig, size_t sig_len, const unsigned char* author_proof, const secp256k1_pubkey* pubkeys, size_t n_pubkeys) {
secp256k1_rfc6979_hmac_sha256 rng;
int found = 0;
size_t i;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(author_idx != NULL);
ARG_CHECK(sig != NULL);
ARG_CHECK(author_proof != NULL);
ARG_CHECK(pubkeys != NULL);

if (sig_len == 0 || sig_len != secp256k1_ring_signature_length(ctx, n_pubkeys)) {
return 0;
}

secp256k1_rfc6979_hmac_sha256_initialize(&rng, author_proof, 32);
for (i = 0; i < n_pubkeys; i++) {
unsigned char buf[32];
secp256k1_scalar ref;
int overflow;

do {
secp256k1_rfc6979_hmac_sha256_generate(&rng, buf, 32);
secp256k1_scalar_set_b32(&ref, buf, &overflow);
} while (overflow || secp256k1_scalar_is_zero(&ref));

/* FIXME this should not be var-time since author_proof may be secret */
if (secp256k1_memcmp_var(&sig[32 * (i + 1)], buf, 32) != 0) {
if (found == 1) {
return 0;
}
found = 1;
*author_idx = i;
}
}

return 1;
}

#endif
Loading