add surjection proof module

Includes fix and tests by Jonas Nick.
This commit is contained in:
Andrew Poelstra 2016-07-01 15:51:07 +00:00
parent a66ea35227
commit 94e81a250e
11 changed files with 1142 additions and 2 deletions

View File

@ -193,3 +193,7 @@ endif
if ENABLE_MODULE_WHITELIST
include src/modules/whitelist/Makefile.am.include
endif
if ENABLE_MODULE_SURJECTIONPROOF
include src/modules/surjection/Makefile.am.include
endif

View File

@ -159,6 +159,11 @@ AC_ARG_ENABLE(jni,
[use_jni=$enableval],
[use_jni=no])
AC_ARG_ENABLE(module_surjectionproof,
AS_HELP_STRING([--enable-module-surjectionproof],[enable surjection proof module (default is no)]),
[enable_module_surjectionproof=$enableval],
[enable_module_surjectionproof=no])
AC_ARG_WITH([field], [AS_HELP_STRING([--with-field=64bit|32bit|auto],
[finite field implementation to use [default=auto]])],[req_field=$withval], [req_field=auto])
@ -190,6 +195,12 @@ else
CFLAGS="$CFLAGS -O3"
fi
AC_MSG_CHECKING([for __builtin_popcount])
AC_COMPILE_IFELSE([AC_LANG_SOURCE([[void myfunc() {__builtin_popcount(0);}]])],
[ AC_MSG_RESULT([yes]);AC_DEFINE(HAVE_BUILTIN_POPCOUNT,1,[Define this symbol if __builtin_popcount is available]) ],
[ AC_MSG_RESULT([no])
])
if test x"$use_ecmult_static_precomputation" != x"no"; then
# Temporarily switch to an environment for the native compiler
save_cross_compiling=$cross_compiling
@ -525,6 +536,10 @@ if test x"$enable_module_whitelist" = x"yes"; then
AC_DEFINE(ENABLE_MODULE_WHITELIST, 1, [Define this symbol to enable the key whitelisting module])
fi
if test x"$enable_module_surjectionproof" = x"yes"; then
AC_DEFINE(ENABLE_MODULE_SURJECTIONPROOF, 1, [Define this symbol to enable the surjection proof module])
fi
AC_C_BIGENDIAN()
if test x"$use_external_asm" = x"yes"; then
@ -543,6 +558,7 @@ if test x"$enable_experimental" = x"yes"; then
AC_MSG_NOTICE([Building NUMS generator module: $enable_module_generator])
AC_MSG_NOTICE([Building range proof module: $enable_module_rangeproof])
AC_MSG_NOTICE([Building key whitelisting module: $enable_module_whitelist])
AC_MSG_NOTICE([Building surjection proof module: $enable_module_surjectionproof])
AC_MSG_NOTICE([******])
if test x"$enable_module_generator" != x"yes"; then
@ -551,10 +567,13 @@ if test x"$enable_experimental" = x"yes"; then
fi
fi
if test x"$enable_module_whitelist" = x"yes"; then
if test x"$enable_module_rangeproof" != x"yes"; then
if test x"$enable_module_rangeproof" != x"yes"; then
if test x"$enable_module_whitelist" = x"yes"; then
AC_MSG_ERROR([Whitelist module requires the rangeproof module. Use --enable-module-rangeproof to allow.])
fi
if test x"$enable_module_surjectionproof" = x"yes"; then
AC_MSG_ERROR([Surjection proof module requires the rangeproof module. Use --enable-module-rangeproof to allow.])
fi
fi
else
if test x"$enable_module_ecdh" = x"yes"; then
@ -572,6 +591,9 @@ else
if test x"$enable_module_whitelist" = x"yes"; then
AC_MSG_ERROR([Key whitelisting module is experimental. Use --enable-experimental to allow.])
fi
if test x"$enable_module_surjectionproof" = x"yes"; then
AC_MSG_ERROR([Surjection proof module is experimental. Use --enable-experimental to allow.])
fi
fi
AC_CONFIG_HEADERS([src/libsecp256k1-config.h])
@ -594,6 +616,7 @@ AM_CONDITIONAL([ENABLE_MODULE_WHITELIST], [test x"$enable_module_whitelist" = x"
AM_CONDITIONAL([USE_JNI], [test x"$use_jni" = x"yes"])
AM_CONDITIONAL([USE_EXTERNAL_ASM], [test x"$use_external_asm" = x"yes"])
AM_CONDITIONAL([USE_ASM_ARM], [test x"$set_asm" = x"arm"])
AM_CONDITIONAL([ENABLE_MODULE_SURJECTIONPROOF], [test x"$enable_module_surjectionproof" = x"yes"])
dnl make sure nothing new is exported so that we don't break the cache
PKGCONFIG_PATH_TEMP="$PKG_CONFIG_PATH"

View File

@ -0,0 +1,212 @@
#ifndef _SECP256K1_SURJECTIONPROOF_
#define _SECP256K1_SURJECTIONPROOF_
#include "secp256k1.h"
#include "secp256k1_rangeproof.h"
#ifdef __cplusplus
extern "C" {
#endif
/** Maximum number of inputs that may be given in a surjection proof */
#define SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS 256
/** Number of bytes a serialized surjection proof requires given the
* number of inputs and the number of used inputs.
*/
#define SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(n_inputs, n_used_inputs) \
(2 + (n_inputs + 7)/8 + 32 * (1 + (n_used_inputs)))
/** Maximum number of bytes a serialized surjection proof requires. */
#define SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES_MAX \
SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS)
/** Opaque data structure that holds a parsed surjection proof
*
* The exact representation of data inside is implementation defined and not
* guaranteed to be portable between different platforms or versions. Nor is
* it guaranteed to have any particular size, nor that identical proofs
* will have identical representation. (That is, memcmp may return nonzero
* even for identical proofs.)
*
* To obtain these properties, instead use secp256k1_surjectionproof_parse
* and secp256k1_surjectionproof_serialize to encode/decode proofs into a
* well-defined format.
*
* The representation is exposed to allow creation of these objects on the
* stack; please *do not* use these internals directly.
*/
typedef struct {
#ifdef VERIFY
/** Mark whether this proof has gone through `secp256k1_surjectionproof_initialize` */
int initialized;
#endif
/** Total number of input asset tags */
size_t n_inputs;
/** Bitmap of which input tags are used in the surjection proof */
unsigned char used_inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS / 8];
/** Borromean signature: e0, scalars */
unsigned char data[32 * (1 + SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS)];
} secp256k1_surjectionproof;
/** Parse a surjection proof
*
* Returns: 1 when the proof could be parsed, 0 otherwise.
* Args: ctx: a secp256k1 context object
* Out: proof: a pointer to a proof object
* In: input: a pointer to the array to parse
* inputlen: length of the array pointed to by input
*
* The proof must consist of:
* - A 2-byte little-endian total input count `n`
* - A ceil(n/8)-byte bitmap indicating which inputs are used.
* - A big-endian 32-byte borromean signature e0 value
* - `m` big-endian 32-byte borromean signature s values, where `m`
* is the number of set bits in the bitmap
*/
SECP256K1_API int secp256k1_surjectionproof_parse(
const secp256k1_context* ctx,
secp256k1_surjectionproof *proof,
const unsigned char *input,
size_t inputlen
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
/** Serialize a surjection proof
*
* Returns: 1 if enough space was available to serialize, 0 otherwise
* Args: ctx: a secp256k1 context object
* Out: output: a pointer to an array to store the serialization
* In/Out: outputlen: a pointer to an integer which is initially set to the
* size of output, and is overwritten with the written
* size.
* In: proof: a pointer to an initialized proof object
*
* See secp256k1_surjectionproof_parse for details about the encoding.
*/
SECP256K1_API int secp256k1_surjectionproof_serialize(
const secp256k1_context* ctx,
unsigned char *output,
size_t *outputlen,
const secp256k1_surjectionproof *proof
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4);
/** Data structure that holds a fixed asset tag.
*
* This data type is *not* opaque. It will always be 32 bytes of whatever
* data the API user wants to use as an asset tag. Its contents have no
* semantic meaning to libsecp whatsoever.
*/
typedef struct {
unsigned char data[32];
} secp256k1_fixed_asset_tag;
/** Returns the total number of inputs a proof expects to be over.
*
* Returns: the number of inputs for the given proof
* In: ctx: pointer to a context object
* proof: a pointer to a proof object
*/
SECP256K1_API size_t secp256k1_surjectionproof_n_total_inputs(
const secp256k1_context* ctx,
const secp256k1_surjectionproof* proof
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
/** Returns the actual number of inputs that a proof uses
*
* Returns: the number of inputs for the given proof
* In: ctx: pointer to a context object
* proof: a pointer to a proof object
*/
SECP256K1_API size_t secp256k1_surjectionproof_n_used_inputs(
const secp256k1_context* ctx,
const secp256k1_surjectionproof* proof
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
/** Returns the total size this proof would take, in bytes, when serialized
*
* Returns: the total size
* In: ctx: pointer to a context object
* proof: a pointer to a proof object
*/
SECP256K1_API size_t secp256k1_surjectionproof_serialized_size(
const secp256k1_context* ctx,
const secp256k1_surjectionproof* proof
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2);
/** Surjection proof initialization function; decides on inputs to use
* Returns 0: inputs could not be selected
* n: inputs were selected after n iterations of random selection
*
* In: ctx: pointer to a context object
* fixed_input_tags: fixed input tags `A_i` for all inputs. (If the fixed tag is not known,
* e.g. in a coinjoin with others' inputs, an ephemeral tag can be given;
* this won't match the output tag but might be used in the anonymity set.)
* n_input_tags: the number of entries in the fixed_input_tags array
* n_input_tags_to_use: the number of inputs to select randomly to put in the anonymity set
* fixed_output_tag: fixed output tag
* max_n_iterations: the maximum number of iterations to do before giving up
* random_seed32: a random seed to be used for input selection
* Out: proof: The proof whose bitvector will be initialized. In case of failure,
* the state of the proof is undefined.
* input_index: The index of the actual input that is secretly mapped to the output
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_initialize(
const secp256k1_context* ctx,
secp256k1_surjectionproof* proof,
size_t *input_index,
const secp256k1_fixed_asset_tag* fixed_input_tags,
const size_t n_input_tags,
const size_t n_input_tags_to_use,
const secp256k1_fixed_asset_tag* fixed_output_tag,
const size_t n_max_iterations,
const unsigned char *random_seed32
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(7);
/** Surjection proof generation function
* Returns 0: proof could not be created
* 1: proof was successfully created
*
* In: ctx: pointer to a context object, initialized for signing and verification
* ephemeral_input_tags: the ephemeral asset tag of all inputs
* n_ephemeral_input_tags: the number of entries in the ephemeral_input_tags array
* ephemeral_output_tag: the ephemeral asset tag of the output
* input_index: the index of the input that actually maps to the output
* input_blinding_key: the blinding key of the input
* output_blinding_key: the blinding key of the output
* In/Out: proof: The produced surjection proof. Must have already gone through `secp256k1_surjectionproof_initialize`
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_surjectionproof_generate(
const secp256k1_context* ctx,
secp256k1_surjectionproof* proof,
const secp256k1_generator* ephemeral_input_tags,
size_t n_ephemeral_input_tags,
const secp256k1_generator* ephemeral_output_tag,
size_t input_index,
const unsigned char *input_blinding_key,
const unsigned char *output_blinding_key
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
/** Surjection proof verification function
* Returns 0: proof was invalid
* 1: proof was valid
*
* In: ctx: pointer to a context object, initialized for signing and verification
* proof: proof to be verified
* ephemeral_input_tags: the ephemeral asset tag of all inputs
* n_ephemeral_input_tags: the number of entries in the ephemeral_input_tags array
* ephemeral_output_tag: the ephemeral asset tag of the output
*/
SECP256K1_API int secp256k1_surjectionproof_verify(
const secp256k1_context* ctx,
const secp256k1_surjectionproof* proof,
const secp256k1_generator* ephemeral_input_tags,
size_t n_ephemeral_input_tags,
const secp256k1_generator* ephemeral_output_tag
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(5);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,6 @@
include_HEADERS += include/secp256k1_surjectionproof.h
noinst_HEADERS += src/modules/surjection/main_impl.h
noinst_HEADERS += src/modules/surjection/surjection.h
noinst_HEADERS += src/modules/surjection/surjection_impl.h
noinst_HEADERS += src/modules/surjection/tests_impl.h

View File

@ -0,0 +1,334 @@
/**********************************************************************
* Copyright (c) 2016 Andrew Poelstra *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_SURJECTION_MAIN
#define SECP256K1_MODULE_SURJECTION_MAIN
#include <assert.h>
#include <string.h>
#include "modules/rangeproof/borromean.h"
#include "modules/surjection/surjection_impl.h"
#include "hash.h"
#include "include/secp256k1_rangeproof.h"
#include "include/secp256k1_surjectionproof.h"
static size_t secp256k1_count_bits_set(const unsigned char* data, size_t count) {
size_t ret = 0;
size_t i;
for (i = 0; i < count; i++) {
#ifdef HAVE_BUILTIN_POPCOUNT
ret += __builtin_popcount(data[i]);
#else
ret += !!(data[i] & 0x1);
ret += !!(data[i] & 0x2);
ret += !!(data[i] & 0x4);
ret += !!(data[i] & 0x8);
ret += !!(data[i] & 0x10);
ret += !!(data[i] & 0x20);
ret += !!(data[i] & 0x40);
ret += !!(data[i] & 0x80);
#endif
}
return ret;
}
int secp256k1_surjectionproof_parse(const secp256k1_context* ctx, secp256k1_surjectionproof *proof, const unsigned char *input, size_t inputlen) {
size_t n_inputs;
size_t signature_len;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
ARG_CHECK(input != NULL);
(void) ctx;
if (inputlen < 2) {
return 0;
}
n_inputs = ((size_t) (input[1] << 8)) + input[0];
if (n_inputs > SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS) {
return 0;
}
if (inputlen < 2 + (n_inputs + 7) / 8) {
return 0;
}
signature_len = 32 * (1 + secp256k1_count_bits_set(&input[2], (n_inputs + 7) / 8));
if (inputlen < 2 + (n_inputs + 7) / 8 + signature_len) {
return 0;
}
proof->n_inputs = n_inputs;
memcpy(proof->used_inputs, &input[2], (n_inputs + 7) / 8);
memcpy(proof->data, &input[2 + (n_inputs + 7) / 8], signature_len);
return 1;
}
int secp256k1_surjectionproof_serialize(const secp256k1_context* ctx, unsigned char *output, size_t *outputlen, const secp256k1_surjectionproof *proof) {
size_t signature_len;
size_t serialized_len;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(output != NULL);
ARG_CHECK(outputlen != NULL);
ARG_CHECK(proof != NULL);
(void) ctx;
signature_len = 32 * (1 + secp256k1_count_bits_set(proof->used_inputs, (proof->n_inputs + 7) / 8));
serialized_len = 2 + (proof->n_inputs + 7) / 8 + signature_len;
if (*outputlen < serialized_len) {
return 0;
}
output[0] = proof->n_inputs % 0x100;
output[1] = proof->n_inputs / 0x100;
memcpy(&output[2], proof->used_inputs, (proof->n_inputs + 7) / 8);
memcpy(&output[2 + (proof->n_inputs + 7) / 8], proof->data, signature_len);
*outputlen = serialized_len;
return 1;
}
size_t secp256k1_surjectionproof_n_total_inputs(const secp256k1_context* ctx, const secp256k1_surjectionproof* proof) {
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
(void) ctx;
return proof->n_inputs;
}
size_t secp256k1_surjectionproof_n_used_inputs(const secp256k1_context* ctx, const secp256k1_surjectionproof* proof) {
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
(void) ctx;
return secp256k1_count_bits_set(proof->used_inputs, (proof->n_inputs + 7) / 8);
}
size_t secp256k1_surjectionproof_serialized_size(const secp256k1_context* ctx, const secp256k1_surjectionproof* proof) {
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
return 2 + (proof->n_inputs + 7) / 8 + 32 * (1 + secp256k1_surjectionproof_n_used_inputs(ctx, proof));
}
typedef struct {
unsigned char state[32];
size_t state_i;
} secp256k1_surjectionproof_csprng;
static void secp256k1_surjectionproof_csprng_init(secp256k1_surjectionproof_csprng *csprng, const unsigned char* state) {
memcpy(csprng->state, state, 32);
csprng->state_i = 0;
}
static size_t secp256k1_surjectionproof_csprng_next(secp256k1_surjectionproof_csprng *csprng, size_t rand_max) {
/* The number of random bytes to read for each random sample */
const size_t increment = rand_max > 256 ? 2 : 1;
/* The maximum value expressable by the number of random bytes we read */
const size_t selection_range = rand_max > 256 ? 0xffff : 0xff;
/* The largest multiple of rand_max that fits within selection_range */
const size_t limit = ((selection_range + 1) / rand_max) * rand_max;
while (1) {
size_t val;
if (csprng->state_i + increment >= 32) {
secp256k1_sha256 sha;
secp256k1_sha256_initialize(&sha);
secp256k1_sha256_write(&sha, csprng->state, 32);
secp256k1_sha256_finalize(&sha, csprng->state);
csprng->state_i = 0;
}
val = csprng->state[csprng->state_i];
if (increment > 1) {
val = (val << 8) + csprng->state[csprng->state_i + 1];
}
csprng->state_i += increment;
/* Accept only values below our limit. Values equal to or above the limit are
* biased because they comprise only a subset of the range (0, rand_max - 1) */
if (val < limit) {
return val % rand_max;
}
}
}
int secp256k1_surjectionproof_initialize(const secp256k1_context* ctx, secp256k1_surjectionproof* proof, size_t *input_index, const secp256k1_fixed_asset_tag* fixed_input_tags, const size_t n_input_tags, const size_t n_input_tags_to_use, const secp256k1_fixed_asset_tag* fixed_output_tag, const size_t n_max_iterations, const unsigned char *random_seed32) {
secp256k1_surjectionproof_csprng csprng;
size_t n_iterations = 0;
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
ARG_CHECK(input_index != NULL);
ARG_CHECK(fixed_input_tags != NULL);
ARG_CHECK(fixed_output_tag != NULL);
ARG_CHECK(random_seed32 != NULL);
ARG_CHECK(n_input_tags <= SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS);
ARG_CHECK(n_input_tags_to_use <= n_input_tags);
secp256k1_surjectionproof_csprng_init(&csprng, random_seed32);
memset(proof->data, 0, sizeof(proof->data));
proof->n_inputs = n_input_tags;
while (1) {
int has_output_tag = 0;
size_t i;
/* obtain a random set of indices */
memset(proof->used_inputs, 0, sizeof(proof->used_inputs));
for (i = 0; i < n_input_tags_to_use; i++) {
while (1) {
size_t next_input_index;
next_input_index = secp256k1_surjectionproof_csprng_next(&csprng, n_input_tags);
if (memcmp(&fixed_input_tags[next_input_index], fixed_output_tag, sizeof(*fixed_output_tag)) == 0) {
*input_index = next_input_index;
has_output_tag = 1;
}
if (!(proof->used_inputs[next_input_index / 8] & (1 << (next_input_index % 8)))) {
proof->used_inputs[next_input_index / 8] |= (1 << (next_input_index % 8));
break;
}
}
}
/* Check if we succeeded */
n_iterations++;
if (has_output_tag) {
#ifdef VERIFY
proof->initialized = 1;
#endif
return n_iterations;
}
if (n_iterations >= n_max_iterations) {
#ifdef VERIFY
proof->initialized = 0;
#endif
return 0;
}
}
}
int secp256k1_surjectionproof_generate(const secp256k1_context* ctx, secp256k1_surjectionproof* proof, const secp256k1_generator* ephemeral_input_tags, size_t n_ephemeral_input_tags, const secp256k1_generator* ephemeral_output_tag, size_t input_index, const unsigned char *input_blinding_key, const unsigned char *output_blinding_key) {
secp256k1_scalar blinding_key;
secp256k1_scalar tmps;
secp256k1_scalar nonce;
int overflow = 0;
size_t rsizes[1]; /* array needed for borromean sig API */
size_t indices[1]; /* array needed for borromean sig API */
size_t i;
size_t n_total_pubkeys;
size_t n_used_pubkeys;
size_t ring_input_index = 0;
secp256k1_gej ring_pubkeys[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
secp256k1_scalar borromean_s[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
secp256k1_ge inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
secp256k1_ge output;
unsigned char msg32[32];
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
ARG_CHECK(ephemeral_input_tags != NULL);
ARG_CHECK(ephemeral_output_tag != NULL);
ARG_CHECK(input_blinding_key != NULL);
ARG_CHECK(output_blinding_key != NULL);
#ifdef VERIFY
CHECK(proof->initialized == 1);
#endif
/* Compute secret key */
secp256k1_scalar_set_b32(&tmps, input_blinding_key, &overflow);
if (overflow) {
return 0;
}
secp256k1_scalar_set_b32(&blinding_key, output_blinding_key, &overflow);
if (overflow) {
return 0;
}
/* The only time the input may equal the output is if neither one was blinded in the first place,
* i.e. both blinding keys are zero. Otherwise this is a privacy leak. */
if (secp256k1_scalar_eq(&tmps, &blinding_key) && !secp256k1_scalar_is_zero(&blinding_key)) {
return 0;
}
secp256k1_scalar_negate(&tmps, &tmps);
secp256k1_scalar_add(&blinding_key, &blinding_key, &tmps);
/* Compute public keys */
n_total_pubkeys = secp256k1_surjectionproof_n_total_inputs(ctx, proof);
n_used_pubkeys = secp256k1_surjectionproof_n_used_inputs(ctx, proof);
if (n_used_pubkeys > n_total_pubkeys || n_total_pubkeys != n_ephemeral_input_tags) {
return 0;
}
secp256k1_generator_load(&output, ephemeral_output_tag);
for (i = 0; i < n_total_pubkeys; i++) {
secp256k1_generator_load(&inputs[i], &ephemeral_input_tags[i]);
}
secp256k1_surjection_compute_public_keys(ring_pubkeys, n_used_pubkeys, inputs, n_total_pubkeys, proof->used_inputs, &output, input_index, &ring_input_index);
/* Produce signature */
rsizes[0] = (int) n_used_pubkeys;
indices[0] = (int) ring_input_index;
secp256k1_surjection_genmessage(msg32, inputs, n_total_pubkeys, &output);
if (secp256k1_surjection_genrand(borromean_s, n_used_pubkeys, &blinding_key) == 0) {
return 0;
}
/* Borromean sign will overwrite one of the s values we just generated, so use
* it as a nonce instead. This avoids extra random generation and also is an
* homage to the rangeproof code which does this very cleverly to encode messages. */
nonce = borromean_s[ring_input_index];
secp256k1_scalar_clear(&borromean_s[ring_input_index]);
if (secp256k1_borromean_sign(&ctx->ecmult_ctx, &ctx->ecmult_gen_ctx, &proof->data[0], borromean_s, ring_pubkeys, &nonce, &blinding_key, rsizes, indices, 1, msg32, 32) == 0) {
return 0;
}
for (i = 0; i < n_used_pubkeys; i++) {
secp256k1_scalar_get_b32(&proof->data[32 + 32 * i], &borromean_s[i]);
}
return 1;
}
int secp256k1_surjectionproof_verify(const secp256k1_context* ctx, const secp256k1_surjectionproof* proof, const secp256k1_generator* ephemeral_input_tags, size_t n_ephemeral_input_tags, const secp256k1_generator* ephemeral_output_tag) {
size_t rsizes[1]; /* array needed for borromean sig API */
size_t i;
size_t n_total_pubkeys;
size_t n_used_pubkeys;
secp256k1_gej ring_pubkeys[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
secp256k1_scalar borromean_s[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
secp256k1_ge inputs[SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS];
secp256k1_ge output;
unsigned char msg32[32];
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(proof != NULL);
ARG_CHECK(ephemeral_input_tags != NULL);
ARG_CHECK(ephemeral_output_tag != NULL);
/* Compute public keys */
n_total_pubkeys = secp256k1_surjectionproof_n_total_inputs(ctx, proof);
n_used_pubkeys = secp256k1_surjectionproof_n_used_inputs(ctx, proof);
if (n_used_pubkeys == 0 || n_used_pubkeys > n_total_pubkeys || n_total_pubkeys != n_ephemeral_input_tags) {
return 0;
}
secp256k1_generator_load(&output, ephemeral_output_tag);
for (i = 0; i < n_total_pubkeys; i++) {
secp256k1_generator_load(&inputs[i], &ephemeral_input_tags[i]);
}
if (secp256k1_surjection_compute_public_keys(ring_pubkeys, n_used_pubkeys, inputs, n_total_pubkeys, proof->used_inputs, &output, 0, NULL) == 0) {
return 0;
}
/* Verify signature */
rsizes[0] = (int) n_used_pubkeys;
for (i = 0; i < n_used_pubkeys; i++) {
int overflow = 0;
secp256k1_scalar_set_b32(&borromean_s[i], &proof->data[32 + 32 * i], &overflow);
if (overflow == 1) {
return 0;
}
}
secp256k1_surjection_genmessage(msg32, inputs, n_total_pubkeys, &output);
return secp256k1_borromean_verify(&ctx->ecmult_ctx, NULL, &proof->data[0], borromean_s, ring_pubkeys, rsizes, 1, msg32, 32);
}
#endif

View File

@ -0,0 +1,19 @@
/**********************************************************************
* Copyright (c) 2016 Andrew Poelstra *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef _SECP256K1_SURJECTION_H_
#define _SECP256K1_SURJECTION_H_
#include "group.h"
#include "scalar.h"
SECP256K1_INLINE static int secp256k1_surjection_genmessage(unsigned char *msg32, secp256k1_ge *ephemeral_input_tags, size_t n_input_tags, secp256k1_ge *ephemeral_output_tag);
SECP256K1_INLINE static int secp256k1_surjection_genrand(secp256k1_scalar *s, size_t ns, const secp256k1_scalar *blinding_key);
SECP256K1_INLINE static int secp256k1_surjection_compute_public_keys(secp256k1_gej *pubkeys, size_t n_pubkeys, const secp256k1_ge *input_tags, size_t n_input_tags, const unsigned char *used_tags, const secp256k1_ge *output_tag, size_t input_index, size_t *ring_input_index);
#endif

View File

@ -0,0 +1,108 @@
Surjection Proof Module
===========================
This module implements a scheme by which a given point can be proven to be
equal to one of a set of points, plus a known difference. This is used in
Confidential Assets when reblinding "asset commitments", which are NUMS
points, to prove that the underlying NUMS point does not change during
reblinding.
Assets are represented, in general, by a 32-byte seed (a hash of some
transaction data) which is hashed to form a NUMS generator, which appears
on the blockchain only in blinded form. We refer to the seed as an
"asset ID" and the blinded generator as an "(ephemeral) asset commitment".
These asset commitments are unique per-output, and their NUMS components
are in general known only to the holder of the output.
The result is that within a transaction, all outputs are able to have
a new uniformly-random asset commitment which cannot be associated with
any individual input asset id, but verifiers are nonetheless assured that
all assets coming out of a transaction are ones that went in.
### Terminology
Assets are identified by a 32-byte "asset ID". In this library these IDs
are used as input to a point-valued hash function `H`. We usually refer
to the hash output as `A`, since this output is the only thing that appears
in the algebra.
Then transaction outputs have "asset commitments", which are curvepoints
of the form `A + rG`, where `A` is the hash of the asset ID and `r` is
some random "blinding factor".
### Design Rationale
Confidential Assets essentially works by replacing the second NUMS generator
`H` in Confidental Transactions with a per-asset unique NUMS generator. This
allows the same verification equation (the sum of all blinded inputs must
equal the sum of all blinded outputs) to imply that quantity of *every* asset
type is preserved in each transaction.
It turns out that even if outputs are reblinded by the addition of `rG` for
some known `r`, this verification equation has the same meaning, with one
caveat: verifiers must be assured that the reblinding preserves the original
generators (and does not, for example, negate them).
This assurance is what surjection proofs provide.
### Limitations
The naive scheme works as follows: every output asset is shown to have come
from some input asset. However, the proofs scale with the number of input
assets, so for all outputs the total size of all surjection proofs is `O(mn)`
for `m`, `n` the number of inputs and outputs.
We therefore restrict the number of inputs that each output may have come
from to 3 (well, some fixed number, which is passed into the API), which
provides a weaker form of blinding, but gives `O(n)` scaling. Over many
transactions, the privacy afforded by this increases exponentially.
### Our Scheme
Our scheme works as follows. Proofs are generated in two steps, "initialization"
which selects a subset of inputs and "generation" which does the mathematical
part of proof generation.
Every input has an asset commitment for which we know the blinding key and
underlying asset ID.
#### Initialization
The initialization function takes a list of input asset IDs and one output
asset ID. It chooses an input subset of some fixed size repeatedly until it
the output ID appears at least once in its subset.
It stores a bitmap representing this subset in the proof object and returns
the number of iterations it needed to choose the subset. The reciprocal of
this represents the probability that a uniformly random input-output
mapping would correspond to the actual input-output mapping, and therefore
gives a measure of privacy. (Lower iteration counts are better.)
It also informs the caller the index of the input whose ID matches the output.
As the API works on only a single output at a time, the total probability
should be computed by multiplying together the counts for each output.
#### Generation
The generation function takes a list of input asset commitments, an output
asset commitment, the input index returned by the initialization step, and
blinding keys for (a) the output commitment, (b) the input commitment. Here
"the input commitment" refers specifically to the input whose index was
chosen during initialization.
Next, it computes a ring signature over the differences between the output
commitment and every input commitment chosen during initialization. Since
the discrete log of one of these is the difference between the output and
input blinding keys, it is possible to create a ring signature over every
differences will be the blinding factor of the output. We create such a
signature, which completes the proof.
#### Verification
Verification takes a surjection proof object, a list of input commitments,
and an output commitment. The proof object contains a ring signature and
a bitmap describing which input commitments to use, and verification
succeeds iff the signature verifies.

View File

@ -0,0 +1,86 @@
/**********************************************************************
* Copyright (c) 2016 Andrew Poelstra *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef _SECP256K1_SURJECTION_IMPL_H_
#define _SECP256K1_SURJECTION_IMPL_H_
#include <assert.h>
#include <string.h>
#include "eckey.h"
#include "group.h"
#include "scalar.h"
#include "hash.h"
SECP256K1_INLINE static void secp256k1_surjection_genmessage(unsigned char *msg32, secp256k1_ge *ephemeral_input_tags, size_t n_input_tags, secp256k1_ge *ephemeral_output_tag) {
/* compute message */
size_t i;
unsigned char pk_ser[33];
size_t pk_len = sizeof(pk_ser);
secp256k1_sha256 sha256_en;
secp256k1_sha256_initialize(&sha256_en);
for (i = 0; i < n_input_tags; i++) {
secp256k1_eckey_pubkey_serialize(&ephemeral_input_tags[i], pk_ser, &pk_len, 1);
assert(pk_len == sizeof(pk_ser));
secp256k1_sha256_write(&sha256_en, pk_ser, pk_len);
}
secp256k1_eckey_pubkey_serialize(ephemeral_output_tag, pk_ser, &pk_len, 1);
assert(pk_len == sizeof(pk_ser));
secp256k1_sha256_write(&sha256_en, pk_ser, pk_len);
secp256k1_sha256_finalize(&sha256_en, msg32);
}
SECP256K1_INLINE static int secp256k1_surjection_genrand(secp256k1_scalar *s, size_t ns, const secp256k1_scalar *blinding_key) {
size_t i;
unsigned char sec_input[36];
secp256k1_sha256 sha256_en;
/* compute s values */
secp256k1_scalar_get_b32(&sec_input[4], blinding_key);
for (i = 0; i < ns; i++) {
int overflow = 0;
sec_input[0] = i;
sec_input[1] = i >> 8;
sec_input[2] = i >> 16;
sec_input[3] = i >> 24;
secp256k1_sha256_initialize(&sha256_en);
secp256k1_sha256_write(&sha256_en, sec_input, 36);
secp256k1_sha256_finalize(&sha256_en, sec_input);
secp256k1_scalar_set_b32(&s[i], sec_input, &overflow);
if (overflow == 1) {
memset(sec_input, 0, 32);
return 0;
}
}
memset(sec_input, 0, 32);
return 1;
}
SECP256K1_INLINE static int secp256k1_surjection_compute_public_keys(secp256k1_gej *pubkeys, size_t n_pubkeys, const secp256k1_ge *input_tags, size_t n_input_tags, const unsigned char *used_tags, const secp256k1_ge *output_tag, size_t input_index, size_t *ring_input_index) {
size_t i;
size_t j = 0;
for (i = 0; i < n_input_tags; i++) {
if (used_tags[i / 8] & (1 << (i % 8))) {
secp256k1_ge tmpge;
secp256k1_ge_neg(&tmpge, &input_tags[i]);
secp256k1_gej_set_ge(&pubkeys[j], &tmpge);
secp256k1_gej_add_ge_var(&pubkeys[j], &pubkeys[j], output_tag, NULL);
if (ring_input_index != NULL && input_index == i) {
*ring_input_index = j;
}
j++;
if (j > n_pubkeys) {
return 0;
}
}
}
return 1;
}
#endif

View File

@ -0,0 +1,336 @@
/**********************************************************************
* Copyright (c) 2016 Andrew Poelstra *
* Distributed under the MIT software license, see the accompanying *
* file COPYING or http://www.opensource.org/licenses/mit-license.php.*
**********************************************************************/
#ifndef SECP256K1_MODULE_SURJECTIONPROOF_TESTS
#define SECP256K1_MODULE_SURJECTIONPROOF_TESTS
#include <assert.h>
#include "testrand.h"
#include "group.h"
#include "include/secp256k1_generator.h"
#include "include/secp256k1_rangeproof.h"
#include "include/secp256k1_surjectionproof.h"
static void run_input_selection_tests(size_t n_inputs) {
unsigned char seed[32];
size_t i;
size_t result;
size_t input_index;
size_t try_count = n_inputs * 100;
secp256k1_surjectionproof proof;
secp256k1_fixed_asset_tag fixed_input_tags[1000];
const size_t max_n_inputs = sizeof(fixed_input_tags) / sizeof(fixed_input_tags[0]) - 1;
assert(n_inputs < max_n_inputs);
secp256k1_rand256(seed);
for (i = 0; i < n_inputs + 1; i++) {
secp256k1_rand256(fixed_input_tags[i].data);
}
/* cannot match output when told to use zero keys */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, 0, &fixed_input_tags[0], try_count, seed);
assert(result == 0);
assert(secp256k1_surjectionproof_n_used_inputs(ctx, &proof) == 0);
assert(secp256k1_surjectionproof_n_total_inputs(ctx, &proof) == n_inputs);
assert(secp256k1_surjectionproof_serialized_size(ctx, &proof) == 34 + (n_inputs + 7) / 8);
if (n_inputs > 0) {
/* succeed in 100*n_inputs tries (probability of failure e^-100) */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, 1, &fixed_input_tags[0], try_count, seed);
assert(result > 0);
assert(result < n_inputs * 10);
assert(secp256k1_surjectionproof_n_used_inputs(ctx, &proof) == 1);
assert(secp256k1_surjectionproof_n_total_inputs(ctx, &proof) == n_inputs);
assert(secp256k1_surjectionproof_serialized_size(ctx, &proof) == 66 + (n_inputs + 7) / 8);
assert(input_index == 0);
}
if (n_inputs >= 3) {
/* succeed in 10*n_inputs tries (probability of failure e^-10) */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, 3, &fixed_input_tags[1], try_count, seed);
assert(result > 0);
assert(secp256k1_surjectionproof_n_used_inputs(ctx, &proof) == 3);
assert(secp256k1_surjectionproof_n_total_inputs(ctx, &proof) == n_inputs);
assert(secp256k1_surjectionproof_serialized_size(ctx, &proof) == 130 + (n_inputs + 7) / 8);
assert(input_index == 1);
/* fail, key not found */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, 3, &fixed_input_tags[n_inputs], try_count, seed);
assert(result == 0);
/* succeed on first try when told to use all keys */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, n_inputs, &fixed_input_tags[0], try_count, seed);
assert(result == 1);
assert(secp256k1_surjectionproof_n_used_inputs(ctx, &proof) == n_inputs);
assert(secp256k1_surjectionproof_n_total_inputs(ctx, &proof) == n_inputs);
assert(secp256k1_surjectionproof_serialized_size(ctx, &proof) == 2 + 32 * (n_inputs + 1) + (n_inputs + 7) / 8);
assert(input_index == 0);
/* succeed in less than 64 tries when told to use half keys. (probability of failure 2^-64) */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, n_inputs / 2, &fixed_input_tags[0], 64, seed);
assert(result > 0);
assert(result < 64);
assert(secp256k1_surjectionproof_n_used_inputs(ctx, &proof) == n_inputs / 2);
assert(secp256k1_surjectionproof_n_total_inputs(ctx, &proof) == n_inputs);
assert(secp256k1_surjectionproof_serialized_size(ctx, &proof) == 2 + 32 * (n_inputs / 2 + 1) + (n_inputs + 7) / 8);
assert(input_index == 0);
}
}
/** Runs surjectionproof_initilize multiple times and records the number of times each input was used.
*/
static void run_input_selection_distribution_tests_helper(const secp256k1_fixed_asset_tag* fixed_input_tags, const size_t n_input_tags, const size_t n_input_tags_to_use, size_t *used_inputs) {
secp256k1_surjectionproof proof;
size_t input_index;
size_t i;
size_t j;
unsigned char seed[32];
size_t result;
for (i = 0; i < n_input_tags; i++) {
used_inputs[i] = 0;
}
for(j = 0; j < 10000; j++) {
secp256k1_rand256(seed);
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_input_tags, n_input_tags_to_use, &fixed_input_tags[0], 64, seed);
assert(result > 0);
for (i = 0; i < n_input_tags; i++) {
if (proof.used_inputs[i / 8] & (1 << (i % 8))) {
used_inputs[i] += 1;
}
}
}
}
/** Probabilistic test of the distribution of used_inputs after surjectionproof_initialize.
* Each confidence interval assertion fails incorrectly with a probability of 2^-128.
*/
static void run_input_selection_distribution_tests(void) {
size_t i;
size_t n_input_tags_to_use;
const size_t n_inputs = 4;
secp256k1_fixed_asset_tag fixed_input_tags[4];
size_t used_inputs[4];
for (i = 0; i < n_inputs; i++) {
secp256k1_rand256(fixed_input_tags[i].data);
}
/* If there is one input tag to use, initialize must choose the one equal to fixed_output_tag. */
n_input_tags_to_use = 1;
run_input_selection_distribution_tests_helper(fixed_input_tags, n_inputs, n_input_tags_to_use, used_inputs);
assert(used_inputs[0] == 10000);
assert(used_inputs[1] == 0);
assert(used_inputs[2] == 0);
assert(used_inputs[3] == 0);
n_input_tags_to_use = 2;
/* The input equal to the fixed_output_tag must be included in all used_inputs sets.
* For each fixed_input_tag != fixed_output_tag the probability that it's included
* in the used_inputs set is P(used_input|not fixed_output_tag) = 1/3.
*/
run_input_selection_distribution_tests_helper(fixed_input_tags, n_inputs, n_input_tags_to_use, used_inputs);
assert(used_inputs[0] == 10000);
assert(used_inputs[1] > 2725 && used_inputs[1] < 3961);
assert(used_inputs[2] > 2725 && used_inputs[2] < 3961);
assert(used_inputs[3] > 2725 && used_inputs[3] < 3961);
n_input_tags_to_use = 3;
/* P(used_input|not fixed_output_tag) = 2/3 */
run_input_selection_distribution_tests_helper(fixed_input_tags, n_inputs, n_input_tags_to_use, used_inputs);
assert(used_inputs[0] == 10000);
assert(used_inputs[1] > 6039 && used_inputs[1] < 7275);
assert(used_inputs[2] > 6039 && used_inputs[2] < 7275);
assert(used_inputs[3] > 6039 && used_inputs[3] < 7275);
n_input_tags_to_use = 1;
/* Create second input tag that is equal to the output tag. Therefore, when using only
* one input we have P(used_input|fixed_output_tag) = 1/2 and P(used_input|not fixed_output_tag) = 0
*/
memcpy(fixed_input_tags[0].data, fixed_input_tags[1].data, 32);
run_input_selection_distribution_tests_helper(fixed_input_tags, n_inputs, n_input_tags_to_use, used_inputs);
assert(used_inputs[0] > 4345 && used_inputs[0] < 5655);
assert(used_inputs[1] > 4345 && used_inputs[1] < 5655);
assert(used_inputs[2] == 0);
assert(used_inputs[3] == 0);
n_input_tags_to_use = 2;
/* When choosing 2 inputs in initialization there are 5 possible combinations of
* input indexes {(0, 1), (1, 2), (0, 3), (1, 3), (0, 2)}. Therefore we have
* P(used_input|fixed_output_tag) = 3/5 and P(used_input|not fixed_output_tag) = 2/5.
*/
run_input_selection_distribution_tests_helper(fixed_input_tags, n_inputs, n_input_tags_to_use, used_inputs);
assert(used_inputs[0] > 5352 && used_inputs[0] < 6637);
assert(used_inputs[1] > 5352 && used_inputs[1] < 6637);
assert(used_inputs[2] > 3363 && used_inputs[2] < 4648);
assert(used_inputs[3] > 3363 && used_inputs[3] < 4648);
n_input_tags_to_use = 3;
/* There are 4 combinations, each with all inputs except one. Therefore we have
* P(used_input|fixed_output_tag) = 3/4 and P(used_input|not fixed_output_tag) = 3/4.
*/
run_input_selection_distribution_tests_helper(fixed_input_tags, n_inputs, n_input_tags_to_use, used_inputs);
assert(used_inputs[0] > 6918 && used_inputs[0] < 8053);
assert(used_inputs[1] > 6918 && used_inputs[1] < 8053);
assert(used_inputs[2] > 6918 && used_inputs[2] < 8053);
assert(used_inputs[3] > 6918 && used_inputs[3] < 8053);
}
static void run_gen_verify(size_t n_inputs, size_t n_used) {
unsigned char seed[32];
secp256k1_surjectionproof proof;
unsigned char serialized_proof[SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES_MAX];
size_t serialized_len = SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES_MAX;
secp256k1_fixed_asset_tag fixed_input_tags[1000];
secp256k1_generator ephemeral_input_tags[1000];
unsigned char *input_blinding_key[1000];
const size_t max_n_inputs = sizeof(fixed_input_tags) / sizeof(fixed_input_tags[0]) - 1;
size_t try_count = n_inputs * 100;
size_t key_index;
size_t input_index;
size_t i;
int result;
/* setup */
assert(n_used <= n_inputs);
assert(n_inputs < max_n_inputs);
secp256k1_rand256(seed);
key_index = (((size_t) seed[0] << 8) + seed[1]) % n_inputs;
for (i = 0; i < n_inputs + 1; i++) {
input_blinding_key[i] = malloc(32);
secp256k1_rand256(input_blinding_key[i]);
/* choose random fixed tag, except that for the output one copy from the key_index */
if (i < n_inputs) {
secp256k1_rand256(fixed_input_tags[i].data);
} else {
memcpy(&fixed_input_tags[i], &fixed_input_tags[key_index], sizeof(fixed_input_tags[i]));
}
assert(secp256k1_generator_generate_blinded(ctx, &ephemeral_input_tags[i], fixed_input_tags[i].data, input_blinding_key[i]));
}
/* test */
result = secp256k1_surjectionproof_initialize(ctx, &proof, &input_index, fixed_input_tags, n_inputs, n_used, &fixed_input_tags[key_index], try_count, seed);
if (n_used == 0) {
assert(result == 0);
return;
}
assert(result > 0);
assert(input_index == key_index);
result = secp256k1_surjectionproof_generate(ctx, &proof, ephemeral_input_tags, n_inputs, &ephemeral_input_tags[n_inputs], input_index, input_blinding_key[input_index], input_blinding_key[n_inputs]);
assert(result == 1);
assert(secp256k1_surjectionproof_serialize(ctx, serialized_proof, &serialized_len, &proof));
assert(serialized_len == secp256k1_surjectionproof_serialized_size(ctx, &proof));
assert(serialized_len == SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES(n_inputs, n_used));
assert(secp256k1_surjectionproof_parse(ctx, &proof, serialized_proof, serialized_len));
result = secp256k1_surjectionproof_verify(ctx, &proof, ephemeral_input_tags, n_inputs, &ephemeral_input_tags[n_inputs]);
assert(result == 1);
/* various fail cases */
if (n_inputs > 1) {
result = secp256k1_surjectionproof_verify(ctx, &proof, ephemeral_input_tags, n_inputs, &ephemeral_input_tags[n_inputs - 1]);
assert(result == 0);
/* number of entries in ephemeral_input_tags array is less than proof.n_inputs */
n_inputs -= 1;
result = secp256k1_surjectionproof_generate(ctx, &proof, ephemeral_input_tags, n_inputs, &ephemeral_input_tags[n_inputs], input_index, input_blinding_key[input_index], input_blinding_key[n_inputs]);
assert(result == 0);
result = secp256k1_surjectionproof_verify(ctx, &proof, ephemeral_input_tags, n_inputs, &ephemeral_input_tags[n_inputs - 1]);
assert(result == 0);
n_inputs += 1;
}
/* cleanup */
for (i = 0; i < n_inputs + 1; i++) {
free(input_blinding_key[i]);
}
}
/* check that a proof with empty n_used_inputs is invalid */
static void run_no_used_inputs_verify(void) {
secp256k1_surjectionproof proof;
secp256k1_fixed_asset_tag fixed_input_tag;
secp256k1_fixed_asset_tag fixed_output_tag;
secp256k1_generator ephemeral_input_tags[1];
size_t n_ephemeral_input_tags = 1;
secp256k1_generator ephemeral_output_tag;
unsigned char blinding_key[32];
secp256k1_ge inputs[1];
secp256k1_ge output;
secp256k1_sha256 sha256_e0;
int result;
/* Create proof that doesn't use inputs. secp256k1_surjectionproof_initialize
* will not work here since it insists on selecting an input that matches the output. */
proof.n_inputs = 1;
memset(proof.used_inputs, 0, SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS / 8);
/* create different fixed input and output tags */
secp256k1_rand256(fixed_input_tag.data);
secp256k1_rand256(fixed_output_tag.data);
/* blind fixed output tags with random blinding key */
secp256k1_rand256(blinding_key);
assert(secp256k1_generator_generate_blinded(ctx, &ephemeral_input_tags[0], fixed_input_tag.data, blinding_key));
assert(secp256k1_generator_generate_blinded(ctx, &ephemeral_output_tag, fixed_output_tag.data, blinding_key));
/* create "borromean signature" which is just a hash of metadata (pubkeys, etc) in this case */
secp256k1_generator_load(&output, &ephemeral_output_tag);
secp256k1_generator_load(&inputs[0], &ephemeral_input_tags[0]);
secp256k1_surjection_genmessage(proof.data, inputs, 1, &output);
secp256k1_sha256_initialize(&sha256_e0);
secp256k1_sha256_write(&sha256_e0, proof.data, 32);
secp256k1_sha256_finalize(&sha256_e0, proof.data);
result = secp256k1_surjectionproof_verify(ctx, &proof, ephemeral_input_tags, n_ephemeral_input_tags, &ephemeral_output_tag);
assert(result == 0);
}
void run_bad_serialize(void) {
secp256k1_surjectionproof proof;
unsigned char serialized_proof[SECP256K1_SURJECTIONPROOF_SERIALIZATION_BYTES_MAX];
size_t serialized_len;
proof.n_inputs = 0;
serialized_len = 2 + 31;
/* e0 is one byte too short */
assert(secp256k1_surjectionproof_serialize(ctx, serialized_proof, &serialized_len, &proof) == 0);
}
void run_bad_parse(void) {
secp256k1_surjectionproof proof;
unsigned char serialized_proof0[] = { 0x00 };
unsigned char serialized_proof1[] = { 0x01, 0x00 };
unsigned char serialized_proof2[33] = { 0 };
/* Missing total input count */
assert(secp256k1_surjectionproof_parse(ctx, &proof, serialized_proof0, sizeof(serialized_proof0)) == 0);
/* Missing bitmap */
assert(secp256k1_surjectionproof_parse(ctx, &proof, serialized_proof1, sizeof(serialized_proof1)) == 0);
/* Missing e0 value */
assert(secp256k1_surjectionproof_parse(ctx, &proof, serialized_proof2, sizeof(serialized_proof2)) == 0);
}
void run_surjection_tests(void) {
run_input_selection_tests(0);
run_input_selection_tests(1);
run_input_selection_tests(5);
run_input_selection_tests(100);
run_input_selection_tests(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS);
run_input_selection_distribution_tests();
run_gen_verify(10, 3);
run_gen_verify(SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS, SECP256K1_SURJECTIONPROOF_MAX_N_INPUTS);
run_no_used_inputs_verify();
run_bad_serialize();
run_bad_parse();
}
#endif

View File

@ -710,3 +710,7 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey *
#ifdef ENABLE_MODULE_WHITELIST
# include "modules/whitelist/main_impl.h"
#endif
#ifdef ENABLE_MODULE_SURJECTIONPROOF
# include "modules/surjection/main_impl.h"
#endif

View File

@ -5224,6 +5224,10 @@ void run_ecdsa_openssl(void) {
# include "modules/whitelist/tests_impl.h"
#endif
#ifdef ENABLE_MODULE_SURJECTIONPROOF
# include "modules/surjection/tests_impl.h"
#endif
int main(int argc, char **argv) {
unsigned char seed16[16] = {0};
unsigned char run32[32] = {0};
@ -5362,6 +5366,10 @@ int main(int argc, char **argv) {
run_whitelist_tests();
#endif
#ifdef ENABLE_MODULE_SURJECTIONPROOF
run_surjection_tests();
#endif
secp256k1_rand256(run32);
printf("random run = %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", run32[0], run32[1], run32[2], run32[3], run32[4], run32[5], run32[6], run32[7], run32[8], run32[9], run32[10], run32[11], run32[12], run32[13], run32[14], run32[15]);