add surjection proof module
Includes fix and tests by Jonas Nick.
This commit is contained in:
parent
a66ea35227
commit
94e81a250e
@ -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
|
||||
|
27
configure.ac
27
configure.ac
@ -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"
|
||||
|
212
include/secp256k1_surjectionproof.h
Normal file
212
include/secp256k1_surjectionproof.h
Normal 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
|
6
src/modules/surjection/Makefile.am.include
Normal file
6
src/modules/surjection/Makefile.am.include
Normal 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
|
||||
|
334
src/modules/surjection/main_impl.h
Normal file
334
src/modules/surjection/main_impl.h
Normal 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
|
19
src/modules/surjection/surjection.h
Normal file
19
src/modules/surjection/surjection.h
Normal 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
|
108
src/modules/surjection/surjection.md
Normal file
108
src/modules/surjection/surjection.md
Normal 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.
|
||||
|
||||
|
86
src/modules/surjection/surjection_impl.h
Normal file
86
src/modules/surjection/surjection_impl.h
Normal 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
|
336
src/modules/surjection/tests_impl.h
Normal file
336
src/modules/surjection/tests_impl.h
Normal 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
|
@ -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
|
||||
|
@ -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]);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user