Skip to content

Commit

Permalink
Pedersen commitments, borromean ring signatures, and ZK range proofs.
Browse files Browse the repository at this point in the history
This commit adds three new cryptosystems to libsecp256k1:

Pedersen commitments are a system for making blinded commitments
 to a value.  Functionally they work like:
  commit_b,v = H(blind_b || value_v),
 except they are additively homorphic, e.g.
  C(b1, v1) - C(b2, v2) = C(b1 - b2, v1 - v2) and
  C(b1, v1) - C(b1, v1) = 0, etc.
 The commitments themselves are EC points, serialized as 33 bytes.
 In addition to the commit function this implementation includes
 utility functions for verifying that a set of commitments sums
 to zero, and for picking blinding factors that sum to zero.
 If the blinding factors are uniformly random, pedersen commitments
 have information theoretic privacy.

Borromean ring signatures are a novel efficient ring signature
 construction for AND/OR admissions policies (the code here implements
 an AND of ORs, each of any size).  This construction requires
 32 bytes of signature per pubkey used plus 32 bytes of constant
 overhead. With these you can construct signatures like "Given pubkeys
 A B C D E F G, the signer knows the discrete logs
 satisifying (A || B) & (C || D || E) & (F || G)".

ZK range proofs allow someone to prove a pedersen commitment is in
 a particular range (e.g. [0..2^64)) without revealing the specific
 value.  The construction here is based on the above borromean
 ring signature and uses a radix-4 encoding and other optimizations
 to maximize efficiency.  It also supports encoding proofs with a
 non-private base-10 exponent and minimum-value to allow trading
 off secrecy for size and speed (or just avoiding wasting space
 keeping data private that was already public due to external
 constraints).

A proof for a 32-bit mantissa takes 2564 bytes, but 2048 bytes of
 this can be used to communicate a private message to a receiver
 who shares a secret random seed with the prover.
  • Loading branch information
gmaxwell committed May 29, 2015
1 parent 5161227 commit bd06794
Show file tree
Hide file tree
Showing 15 changed files with 1,924 additions and 6 deletions.
9 changes: 8 additions & 1 deletion Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ noinst_HEADERS += src/hash_impl.h
noinst_HEADERS += src/field.h
noinst_HEADERS += src/field_impl.h
noinst_HEADERS += src/bench.h
noinst_HEADERS += src/borromean.h
noinst_HEADERS += src/borromean_impl.h
noinst_HEADERS += src/rangeproof.h
noinst_HEADERS += src/rangeproof_impl.h

pkgconfigdir = $(libdir)/pkgconfig
pkgconfig_DATA = libsecp256k1.pc
Expand All @@ -51,7 +55,7 @@ libsecp256k1_la_LIBADD = $(SECP_LIBS)

noinst_PROGRAMS =
if USE_BENCHMARK
noinst_PROGRAMS += bench_verify bench_recover bench_sign bench_internal bench_ecdh
noinst_PROGRAMS += bench_verify bench_recover bench_sign bench_rangeproof bench_internal bench_ecdh
bench_verify_SOURCES = src/bench_verify.c
bench_verify_LDADD = libsecp256k1.la $(SECP_LIBS)
bench_verify_LDFLAGS = -static
Expand All @@ -61,6 +65,9 @@ bench_recover_LDFLAGS = -static
bench_sign_SOURCES = src/bench_sign.c
bench_sign_LDADD = libsecp256k1.la $(SECP_LIBS)
bench_sign_LDFLAGS = -static
bench_rangeproof_SOURCES = src/bench_rangeproof.c
bench_rangeproof_LDADD = libsecp256k1.la $(SECP_LIBS)
bench_rangeproof_LDFLAGS = -static
bench_internal_SOURCES = src/bench_internal.c
bench_internal_LDADD = $(SECP_LIBS)
bench_internal_LDFLAGS = -static
Expand Down
6 changes: 6 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,12 @@ AC_COMPILE_IFELSE([AC_LANG_SOURCE([[void myfunc() {__builtin_expect(0,0);}]])],
[ AC_MSG_RESULT([no])
])

AC_MSG_CHECKING([for __builtin_clzll])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[void myfunc() { __builtin_clzll(1);}]])],
[ AC_MSG_RESULT([yes]);AC_DEFINE(HAVE_BUILTIN_CLZLL,1,[Define this symbol if __builtin_clzll is available]) ],
[ AC_MSG_RESULT([no])
])

if test x"$req_asm" = x"auto"; then
SECP_64BIT_ASM_CHECK
if test x"$has_64bit_asm" = x"yes"; then
Expand Down
168 changes: 168 additions & 0 deletions include/secp256k1.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
extern "C" {
# endif

#include <stdint.h>

# if !defined(SECP256K1_GNUC_PREREQ)
# if defined(__GNUC__)&&defined(__GNUC_MINOR__)
# define SECP256K1_GNUC_PREREQ(_maj,_min) \
Expand Down Expand Up @@ -50,6 +52,8 @@ typedef struct secp256k1_context_struct secp256k1_context_t;
/** Flags to pass to secp256k1_context_create. */
# define SECP256K1_CONTEXT_VERIFY (1 << 0)
# define SECP256K1_CONTEXT_SIGN (1 << 1)
# define SECP256K1_CONTEXT_COMMIT (1 << 7)
# define SECP256K1_CONTEXT_RANGEPROOF (1 << 8)

/** Create a secp256k1 context object.
* Returns: a newly created context object.
Expand Down Expand Up @@ -358,6 +362,170 @@ SECP256K1_WARN_UNUSED_RESULT int secp256k1_context_randomize(
) SECP256K1_ARG_NONNULL(1);


/** Generate a pedersen commitment.
* Returns 1: commitment successfully created.
* 0: error
* In: ctx: pointer to a context object, initialized for signing and commitment (cannot be NULL)
* blind: pointer to a 32-byte blinding factor (cannot be NULL)
* value: unsigned 64-bit integer value to commit to.
* Out: commit: pointer to a 33-byte array for the commitment (cannot be NULL)
*
* Blinding factors can be generated and verified in the same way as secp256k1 private keys for ECDSA.
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_commit(
const secp256k1_context_t* ctx,
unsigned char *commit,
unsigned char *blind,
uint64_t value
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Computes the sum of multiple positive and negative blinding factors.
* Returns 1: sum successfully computed.
* 0: error
* In: ctx: pointer to a context object (cannot be NULL)
* blinds: pointer to pointers to 32-byte character arrays for blinding factors. (cannot be NULL)
* n: number of factors pointed to by blinds.
* nneg: how many of the initial factors should be treated with a positive sign.
* Out: blind_out: pointer to a 32-byte array for the sum (cannot be NULL)
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_blind_sum(
const secp256k1_context_t* ctx,
unsigned char *blind_out,
const unsigned char * const *blinds,
int n,
int npositive
)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Verify a tally of pedersen commitments
* Returns 1: commitments successfully sum to zero.
* 0: Commitments do not sum to zero or other error.
* In: ctx: pointer to a context object, initialized for commitment (cannot be NULL)
* commits: pointer to pointers to 33-byte character arrays for the commitments. (cannot be NULL if pcnt is non-zero)
* pcnt: number of commitments pointed to by commits.
* ncommits: pointer to pointers to 33-byte character arrays for negative commitments. (cannot be NULL if ncnt is non-zero)
* ncnt: number of commitments pointed to by ncommits.
* excess: signed 64bit amount to add to the total to bring it to zero, can be negative.
*
* This computes sum(commit[0..pcnt)) - sum(ncommit[0..ncnt)) - excess*H == 0.
*
* A pedersen commitment is xG + vH where G and H are generators for the secp256k1 group and x is a blinding factor,
* while v is the committed value. For a collection of commitments to sum to zero both their blinding factors and
* values must sum to zero.
*
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_pedersen_verify_tally(
const secp256k1_context_t* ctx,
const unsigned char * const *commits,
int pcnt,
const unsigned char * const *ncommits,
int ncnt,
int64_t excess
)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4);

/** Verify a proof that a committed value is within a range.
* Returns 1: Value is within the range [0..2^64), the specifically proven range is in the min/max value outputs.
* 0: Proof failed or other error.
* In: ctx: pointer to a context object, initialized for range-proof and commitment (cannot be NULL)
* commit: the 33-byte commitment being proved. (cannot be NULL)
* proof: pointer to character array with the proof. (cannot be NULL)
* plen: length of proof in bytes.
* Out: min_value: pointer to a unsigned int64 which will be updated with the minimum value that commit could have. (cannot be NULL)
* max_value: pointer to a unsigned int64 which will be updated with the maximum value that commit could have. (cannot be NULL)
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_verify(
const secp256k1_context_t* ctx,
uint64_t *min_value,
uint64_t *max_value,
const unsigned char *commit,
const unsigned char *proof,
int plen
)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);

/** Verify a range proof proof and rewind the proof to recover information sent by its author.
* Returns 1: Value is within the range [0..2^64), the specifically proven range is in the min/max value outputs, and the value and blinding were recovered.
* 0: Proof failed, rewind failed, or other error.
* In: ctx: pointer to a context object, initialized for range-proof and commitment (cannot be NULL)
* commit: the 33-byte commitment being proved. (cannot be NULL)
* proof: pointer to character array with the proof. (cannot be NULL)
* plen: length of proof in bytes.
* nonce: 32-byte secret nonce used by the prover (cannot be NULL)
* In/Out: blind_out: storage for the 32-byte blinding factor used for the commitment
* value_out: pointer to an unsigned int64 which has the exact value of the commitment.
* message_out: pointer to a 4096 byte character array to receive message data from the proof author.
* outlen: length of message data written to message_out.
* min_value: pointer to an unsigned int64 which will be updated with the minimum value that commit could have. (cannot be NULL)
* max_value: pointer to an unsigned int64 which will be updated with the maximum value that commit could have. (cannot be NULL)
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_rewind(
const secp256k1_context_t* ctx,
unsigned char *blind_out,
uint64_t *value_out,
unsigned char *message_out,
int *outlen,
const unsigned char *nonce,
uint64_t *min_value,
uint64_t *max_value,
const unsigned char *commit,
const unsigned char *proof,
int plen
)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8) SECP256K1_ARG_NONNULL(9) SECP256K1_ARG_NONNULL(10);

/** Author a proof that a committed value is within a range.
* Returns 1: Proof successfully created.
* 0: Error
* In: ctx: pointer to a context object, initialized for range-proof, signing, and commitment (cannot be NULL)
* proof: pointer to array to receive the proof, can be up to 5134 bytes. (cannot be NULL)
* min_value: constructs a proof where the verifer can tell the minimum value is at least the specified amount.
* commit: 33-byte array with the commitment being proved.
* blind: 32-byte blinding factor used by commit.
* nonce: 32-byte secret nonce used to initialize the proof (value can be reverse-engineered out of the proof if this secret is known.)
* exp: Base-10 exponent. Digits below above will be made public, but the proof will be made smaller. Allowed range is -1 to 18.
* (-1 is a special case that makes the value public. 0 is the most private.)
* min_bits: Number of bits of the value to keep private. (0 = auto/minimal, - 64).
* value: Actual value of the commitment.
* In/out: plen: point to an integer with the size of the proof buffer and the size of the constructed proof.
*
* If min_value or exp is non-zero then the value must be on the range [0, 2^63) to prevent the proof range from spanning past 2^64.
*
* If exp is -1 the value is revealed by the proof (e.g. it proves that the proof is a blinding of a specific value, without revealing the blinding key.)
*
* This can randomly fail with probability around one in 2^100. If this happens, buy a lottery ticket and retry with a different nonce or blinding.
*
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_sign(
const secp256k1_context_t* ctx,
unsigned char *proof,
int *plen,
uint64_t min_value,
const unsigned char *commit,
const unsigned char *blind,
const unsigned char *nonce,
int exp,
int min_bits,
uint64_t value
)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7);

/** Extract some basic information from a range-proof.
* Returns 1: Information successfully extracted.
* 0: Decode failed.
* In: ctx: pointer to a context object
* proof: pointer to character array with the proof.
* plen: length of proof in bytes.
* Out: exp: Exponent used in the proof (-1 means the value isn't private).
* mantissa: Number of bits covered by the proof.
* min_value: pointer to an unsigned int64 which will be updated with the minimum value that commit could have. (cannot be NULL)
* max_value: pointer to an unsigned int64 which will be updated with the maximum value that commit could have. (cannot be NULL)
*/
SECP256K1_WARN_UNUSED_RESULT int secp256k1_rangeproof_info(
const secp256k1_context_t* ctx,
int *exp,
int *mantissa,
uint64_t *min_value,
uint64_t *max_value,
const unsigned char *proof,
int plen
)SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);

# ifdef __cplusplus
}
# endif
Expand Down
63 changes: 63 additions & 0 deletions src/bench_rangeproof.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**********************************************************************
* Copyright (c) 2014, 2015 Pieter Wuille, Gregory Maxwell *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/

#include <stdint.h>

#include "include/secp256k1.h"
#include "util.h"
#include "bench.h"

typedef struct {
secp256k1_context_t* ctx;
unsigned char commit[33];
unsigned char proof[5134];
unsigned char blind[32];
int len;
int min_bits;
uint64_t v;
} bench_rangeproof_t;

static void bench_rangeproof_setup(void* arg) {
int i;
uint64_t minv;
uint64_t maxv;
bench_rangeproof_t *data = (bench_rangeproof_t*)arg;

data->v = 0;
for (i = 0; i < 32; i++) data->blind[i] = i + 1;
CHECK(secp256k1_pedersen_commit(data->ctx, data->commit, data->blind, data->v));
data->len = 5134;
CHECK(secp256k1_rangeproof_sign(data->ctx, data->proof, &data->len, 0, data->commit, data->blind, data->commit, 0, data->min_bits, data->v));
CHECK(secp256k1_rangeproof_verify(data->ctx, &minv, &maxv, data->commit, data->proof, data->len));
}

static void bench_rangeproof(void* arg) {
int i;
bench_rangeproof_t *data = (bench_rangeproof_t*)arg;

for (i = 0; i < 1000; i++) {
int j;
uint64_t minv;
uint64_t maxv;
j = secp256k1_rangeproof_verify(data->ctx, &minv, &maxv, data->commit, data->proof, data->len);
for (j = 0; j < 4; j++) {
data->proof[j + 2 + 32 *((data->min_bits + 1) >> 1) - 4] = (i >> 8)&255;
}
}
}

int main(void) {
bench_rangeproof_t data;

data.ctx = secp256k1_context_create(SECP256K1_CONTEXT_SIGN | SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_COMMIT | SECP256K1_CONTEXT_RANGEPROOF);

data.min_bits = 32;

run_benchmark("rangeproof_verif_bit", bench_rangeproof, bench_rangeproof_setup, NULL, &data, 10, 1000 * data.min_bits);

secp256k1_context_destroy(data.ctx);
return 0;
}
24 changes: 24 additions & 0 deletions src/borromean.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**********************************************************************
* Copyright (c) 2014, 2015 Gregory Maxwell *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/


#ifndef _SECP256K1_BORROMEAN_H_
#define _SECP256K1_BORROMEAN_H_

#include "scalar.h"
#include "field.h"
#include "group.h"
#include "ecmult.h"
#include "ecmult_gen.h"

int secp256k1_borromean_verify(const secp256k1_ecmult_context_t* ecmult_ctx, secp256k1_scalar_t *evalues, const unsigned char *e0, const secp256k1_scalar_t *s,
const secp256k1_gej_t *pubs, const int *rsizes, int nrings, const unsigned char *m, int mlen);

int secp256k1_borromean_sign(const secp256k1_ecmult_context_t* ecmult_ctx, const secp256k1_ecmult_gen_context_t *ecmult_gen_ctx,
unsigned char *e0, secp256k1_scalar_t *s, const secp256k1_gej_t *pubs, const secp256k1_scalar_t *k, const secp256k1_scalar_t *sec,
const int *rsizes, const int *secidx, int nrings, const unsigned char *m, int mlen);

#endif

0 comments on commit bd06794

Please sign in to comment.