Implement ring-signature based whitelist delegation scheme
This commit is contained in:
parent
6eebf82d8a
commit
da035050f8
@ -183,3 +183,7 @@ endif
|
||||
if ENABLE_MODULE_RANGEPROOF
|
||||
include src/modules/rangeproof/Makefile.am.include
|
||||
endif
|
||||
|
||||
if ENABLE_MODULE_WHITELIST
|
||||
include src/modules/whitelist/Makefile.am.include
|
||||
endif
|
||||
|
21
configure.ac
21
configure.ac
@ -144,6 +144,11 @@ AC_ARG_ENABLE(module_rangeproof,
|
||||
[enable_module_rangeproof=$enableval],
|
||||
[enable_module_rangeproof=no])
|
||||
|
||||
AC_ARG_ENABLE(module_whitelist,
|
||||
AS_HELP_STRING([--enable-module-whitelist],[enable key whitelisting module (default is no)]),
|
||||
[enable_module_whitelist=$enableval],
|
||||
[enable_module_whitelist=no])
|
||||
|
||||
AC_ARG_ENABLE(jni,
|
||||
AS_HELP_STRING([--enable-jni],[enable libsecp256k1_jni (default is auto)]),
|
||||
[use_jni=$enableval],
|
||||
@ -459,6 +464,10 @@ if test x"$enable_module_rangeproof" = x"yes"; then
|
||||
AC_DEFINE(ENABLE_MODULE_RANGEPROOF, 1, [Define this symbol to enable the Pedersen / zero knowledge range proof module])
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
AC_C_BIGENDIAN()
|
||||
|
||||
if test x"$use_external_asm" = x"yes"; then
|
||||
@ -483,12 +492,20 @@ if test x"$enable_experimental" = x"yes"; then
|
||||
AC_MSG_NOTICE([Building ECDH module: $enable_module_ecdh])
|
||||
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([******])
|
||||
|
||||
if test x"$enable_module_generator" != x"yes"; then
|
||||
if test x"$enable_module_rangeproof" = x"yes"; then
|
||||
AC_MSG_ERROR([Rangeproof module requires the generator module. Use --enable-module-generator to allow.])
|
||||
fi
|
||||
fi
|
||||
|
||||
if test x"$enable_module_whitelist" = x"yes"; then
|
||||
if test x"$enable_module_rangeproof" != x"yes"; then
|
||||
AC_MSG_ERROR([Whitelist module requires the rangeproof module. Use --enable-module-rangeproof to allow.])
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if test x"$enable_module_ecdh" = x"yes"; then
|
||||
AC_MSG_ERROR([ECDH module is experimental. Use --enable-experimental to allow.])
|
||||
@ -502,6 +519,9 @@ else
|
||||
if test x"$enable_module_rangeproof" = x"yes"; then
|
||||
AC_MSG_ERROR([Range proof module is experimental. Use --enable-experimental to allow.])
|
||||
fi
|
||||
if test x"$enable_module_whitelist" = x"yes"; then
|
||||
AC_MSG_ERROR([Key whitelisting module is experimental. Use --enable-experimental to allow.])
|
||||
fi
|
||||
fi
|
||||
|
||||
AC_CONFIG_HEADERS([src/libsecp256k1-config.h])
|
||||
@ -520,6 +540,7 @@ AM_CONDITIONAL([ENABLE_MODULE_ECDH], [test x"$enable_module_ecdh" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_RECOVERY], [test x"$enable_module_recovery" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_GENERATOR], [test x"$enable_module_generator" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_RANGEPROOF], [test x"$enable_module_rangeproof" = x"yes"])
|
||||
AM_CONDITIONAL([ENABLE_MODULE_WHITELIST], [test x"$enable_module_whitelist" = x"yes"])
|
||||
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"])
|
||||
|
146
include/secp256k1_whitelist.h
Normal file
146
include/secp256k1_whitelist.h
Normal file
@ -0,0 +1,146 @@
|
||||
/**********************************************************************
|
||||
* 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_WHITELIST_
|
||||
#define _SECP256K1_WHITELIST_
|
||||
|
||||
#include "secp256k1.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define SECP256K1_WHITELIST_MAX_N_KEYS 256
|
||||
|
||||
/** Opaque data structure that holds a parsed whitelist 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 signatures
|
||||
* will have identical representation. (That is, memcmp may return nonzero
|
||||
* even for identical signatures.)
|
||||
*
|
||||
* To obtain these properties, instead use secp256k1_whitelist_signature_parse
|
||||
* and secp256k1_whitelist_signature_serialize to encode/decode signatures
|
||||
* 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. To learn the number
|
||||
* of keys for a signature, use `secp256k1_whitelist_signature_n_keys`.
|
||||
*/
|
||||
typedef struct {
|
||||
size_t n_keys;
|
||||
/* e0, scalars */
|
||||
unsigned char data[32 * (1 + SECP256K1_WHITELIST_MAX_N_KEYS)];
|
||||
} secp256k1_whitelist_signature;
|
||||
|
||||
/** Parse a whitelist signature
|
||||
*
|
||||
* Returns: 1 when the signature could be parsed, 0 otherwise.
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: sig: a pointer to a signature object
|
||||
* In: input: a pointer to the array to parse
|
||||
*
|
||||
* The signature must consist of a 1-byte n_keys value, followed by a 32-byte
|
||||
* big endian e0 value, followed by n_keys many 32-byte big endian s values.
|
||||
* If n_keys falls outside of [0..SECP256K1_WHITELIST_MAX_N_KEYS] the encoding
|
||||
* is invalid.
|
||||
*
|
||||
* The total length of the input array must therefore be 33 + 32 * n_keys.
|
||||
*
|
||||
* After the call, sig will always be initialized. If parsing failed or any
|
||||
* scalar values overflow or are zero, the resulting sig value is guaranteed
|
||||
* to fail validation for any set of keys.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_signature_parse(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_whitelist_signature *sig,
|
||||
const unsigned char *input
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Returns the number of keys a signature expects to have.
|
||||
*
|
||||
* Returns: the number of keys for the given signature
|
||||
* In: sig: a pointer to a signature object
|
||||
*/
|
||||
SECP256K1_API size_t secp256k1_whitelist_signature_n_keys(
|
||||
const secp256k1_whitelist_signature *sig
|
||||
) SECP256K1_ARG_NONNULL(1);
|
||||
|
||||
/** Serialize a whitelist signature
|
||||
*
|
||||
* Returns: 1
|
||||
* Args: ctx: a secp256k1 context object
|
||||
* Out: output64: a pointer to an array to store the serialization
|
||||
* In: sig: a pointer to an initialized signature object
|
||||
*
|
||||
* See secp256k1_whitelist_signature_parse for details about the encoding.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_signature_serialize(
|
||||
const secp256k1_context* ctx,
|
||||
unsigned char *output,
|
||||
const secp256k1_whitelist_signature *sig
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
|
||||
|
||||
/** Compute a whitelist signature
|
||||
* Returns 1: signature was successfully created
|
||||
* 0: signature was not successfully created
|
||||
* In: ctx: pointer to a context object, initialized for signing and verification
|
||||
* online_pubkeys: list of all online pubkeys
|
||||
* offline_pubkeys: list of all offline pubkeys
|
||||
* n_keys: the number of entries in each of the above two arrays
|
||||
* sub_pubkey: the key to be whitelisted
|
||||
* online_seckey: the secret key to the signer's online pubkey
|
||||
* summed_seckey: the secret key to the sum of (whitelisted key, signer's offline pubkey)
|
||||
* index: the signer's index in the lists of keys
|
||||
* noncefp:pointer to a nonce generation function. If NULL, secp256k1_nonce_function_default is used
|
||||
* ndata: pointer to arbitrary data used by the nonce generation function (can be NULL)
|
||||
* Out: sig: The produced signature.
|
||||
*
|
||||
* The signatures are of the list of all passed pubkeys in the order
|
||||
* ( whitelist, online_1, offline_1, online_2, offline_2, ... )
|
||||
* The verification key list consists of
|
||||
* online_i + H(offline_i + whitelist)(offline_i + whitelist)
|
||||
* for each public key pair (offline_i, offline_i). Here H means sha256 of the
|
||||
* compressed serialization of the key.
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_sign(
|
||||
const secp256k1_context* ctx,
|
||||
secp256k1_whitelist_signature *sig,
|
||||
const secp256k1_pubkey *online_pubkeys,
|
||||
const secp256k1_pubkey *offline_pubkeys,
|
||||
const size_t n_keys,
|
||||
const secp256k1_pubkey *sub_pubkey,
|
||||
const unsigned char *online_seckey,
|
||||
const unsigned char *summed_seckey,
|
||||
const size_t index,
|
||||
secp256k1_nonce_function noncefp,
|
||||
const void *noncedata
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(6) SECP256K1_ARG_NONNULL(7) SECP256K1_ARG_NONNULL(8);
|
||||
|
||||
/** Verify a whitelist signature
|
||||
* Returns 1: signature is valid
|
||||
* 0: signature is not valid
|
||||
* In: ctx: pointer to a context object, initialized for signing and verification
|
||||
* sig: the signature to be verified
|
||||
* online_pubkeys: list of all online pubkeys
|
||||
* offline_pubkeys: list of all offline pubkeys
|
||||
* n_keys: the number of entries in each of the above two arrays
|
||||
* sub_pubkey: the key to be whitelisted
|
||||
*/
|
||||
SECP256K1_API int secp256k1_whitelist_verify(
|
||||
const secp256k1_context* ctx,
|
||||
const secp256k1_whitelist_signature *sig,
|
||||
const secp256k1_pubkey *online_pubkeys,
|
||||
const secp256k1_pubkey *offline_pubkeys,
|
||||
const secp256k1_pubkey *sub_pubkey
|
||||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
3
src/modules/whitelist/Makefile.am.include
Normal file
3
src/modules/whitelist/Makefile.am.include
Normal file
@ -0,0 +1,3 @@
|
||||
include_HEADERS += include/secp256k1_whitelist.h
|
||||
noinst_HEADERS += src/modules/whitelist/main_impl.h
|
||||
noinst_HEADERS += src/modules/whitelist/tests_impl.h
|
164
src/modules/whitelist/main_impl.h
Normal file
164
src/modules/whitelist/main_impl.h
Normal file
@ -0,0 +1,164 @@
|
||||
/**********************************************************************
|
||||
* 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_WHITELIST_MAIN
|
||||
#define SECP256K1_MODULE_WHITELIST_MAIN
|
||||
|
||||
#include "include/secp256k1_whitelist.h"
|
||||
#include "modules/whitelist/whitelist_impl.h"
|
||||
|
||||
#define MAX_KEYS SECP256K1_WHITELIST_MAX_N_KEYS /* shorter alias */
|
||||
|
||||
int secp256k1_whitelist_sign(const secp256k1_context* ctx, secp256k1_whitelist_signature *sig, const secp256k1_pubkey *online_pubkeys, const secp256k1_pubkey *offline_pubkeys, const size_t n_keys, const secp256k1_pubkey *sub_pubkey, const unsigned char *online_seckey, const unsigned char *summed_seckey, const size_t index, secp256k1_nonce_function noncefp, const void *noncedata) {
|
||||
secp256k1_gej pubs[MAX_KEYS];
|
||||
secp256k1_scalar s[MAX_KEYS];
|
||||
secp256k1_scalar sec, non;
|
||||
unsigned char msg32[32];
|
||||
int ret;
|
||||
|
||||
if (noncefp == NULL) {
|
||||
noncefp = secp256k1_nonce_function_default;
|
||||
}
|
||||
|
||||
/* Sanity checks */
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
|
||||
ARG_CHECK(secp256k1_ecmult_gen_context_is_built(&ctx->ecmult_gen_ctx));
|
||||
ARG_CHECK(sig != NULL);
|
||||
ARG_CHECK(online_pubkeys != NULL);
|
||||
ARG_CHECK(offline_pubkeys != NULL);
|
||||
ARG_CHECK(n_keys <= MAX_KEYS);
|
||||
ARG_CHECK(sub_pubkey != NULL);
|
||||
ARG_CHECK(online_seckey != NULL);
|
||||
ARG_CHECK(summed_seckey != NULL);
|
||||
ARG_CHECK(index < n_keys);
|
||||
|
||||
/* Compute pubkeys: online_pubkey + tweaked(offline_pubkey + address), and message */
|
||||
ret = secp256k1_whitelist_compute_keys_and_message(ctx, msg32, pubs, online_pubkeys, offline_pubkeys, n_keys, sub_pubkey);
|
||||
|
||||
/* Compute signing key: online_seckey + tweaked(summed_seckey) */
|
||||
if (ret) {
|
||||
ret = secp256k1_whitelist_compute_tweaked_privkey(ctx, &sec, online_seckey, summed_seckey);
|
||||
}
|
||||
/* Compute nonce and random s-values */
|
||||
if (ret) {
|
||||
unsigned char seckey32[32];
|
||||
unsigned int count = 0;
|
||||
int overflow = 0;
|
||||
|
||||
secp256k1_scalar_get_b32(seckey32, &sec);
|
||||
while (1) {
|
||||
size_t i;
|
||||
unsigned char nonce32[32];
|
||||
int done;
|
||||
ret = noncefp(nonce32, msg32, seckey32, NULL, (void*)noncedata, count);
|
||||
if (!ret) {
|
||||
break;
|
||||
}
|
||||
secp256k1_scalar_set_b32(&non, nonce32, &overflow);
|
||||
memset(nonce32, 0, 32);
|
||||
if (overflow || secp256k1_scalar_is_zero(&non)) {
|
||||
count++;
|
||||
continue;
|
||||
}
|
||||
done = 1;
|
||||
for (i = 0; i < n_keys; i++) {
|
||||
msg32[0] ^= i + 1;
|
||||
msg32[1] ^= (i + 1) / 0x100;
|
||||
ret = noncefp(&sig->data[32 * (i + 1)], msg32, seckey32, NULL, (void*)noncedata, count);
|
||||
if (!ret) {
|
||||
break;
|
||||
}
|
||||
secp256k1_scalar_set_b32(&s[i], &sig->data[32 * (i + 1)], &overflow);
|
||||
msg32[0] ^= i + 1;
|
||||
msg32[1] ^= (i + 1) / 0x100;
|
||||
if (overflow || secp256k1_scalar_is_zero(&s[i])) {
|
||||
count++;
|
||||
done = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (done) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
memset(seckey32, 0, 32);
|
||||
}
|
||||
/* Actually sign */
|
||||
if (ret) {
|
||||
sig->n_keys = n_keys;
|
||||
ret = secp256k1_borromean_sign(&ctx->ecmult_ctx, &ctx->ecmult_gen_ctx, &sig->data[0], s, pubs, &non, &sec, &n_keys, &index, 1, msg32, 32);
|
||||
/* Signing will change s[index], so update in the sig structure */
|
||||
secp256k1_scalar_get_b32(&sig->data[32 * (index + 1)], &s[index]);
|
||||
}
|
||||
|
||||
secp256k1_scalar_clear(&non);
|
||||
secp256k1_scalar_clear(&sec);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int secp256k1_whitelist_verify(const secp256k1_context* ctx, const secp256k1_whitelist_signature *sig, const secp256k1_pubkey *online_pubkeys, const secp256k1_pubkey *offline_pubkeys, const secp256k1_pubkey *sub_pubkey) {
|
||||
secp256k1_scalar s[MAX_KEYS];
|
||||
secp256k1_gej pubs[MAX_KEYS];
|
||||
unsigned char msg32[32];
|
||||
size_t i;
|
||||
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(secp256k1_ecmult_context_is_built(&ctx->ecmult_ctx));
|
||||
ARG_CHECK(sig != NULL);
|
||||
ARG_CHECK(online_pubkeys != NULL);
|
||||
ARG_CHECK(offline_pubkeys != NULL);
|
||||
ARG_CHECK(sub_pubkey != NULL);
|
||||
|
||||
if (sig->n_keys > MAX_KEYS) {
|
||||
return 0;
|
||||
}
|
||||
for (i = 0; i < sig->n_keys; i++) {
|
||||
int overflow = 0;
|
||||
secp256k1_scalar_set_b32(&s[i], &sig->data[32 * (i + 1)], &overflow);
|
||||
if (overflow || secp256k1_scalar_is_zero(&s[i])) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Compute pubkeys: online_pubkey + tweaked(offline_pubkey + address), and message */
|
||||
if (!secp256k1_whitelist_compute_keys_and_message(ctx, msg32, pubs, online_pubkeys, offline_pubkeys, sig->n_keys, sub_pubkey)) {
|
||||
return 0;
|
||||
}
|
||||
/* Do verification */
|
||||
return secp256k1_borromean_verify(&ctx->ecmult_ctx, NULL, &sig->data[0], s, pubs, &sig->n_keys, 1, msg32, 32);
|
||||
}
|
||||
|
||||
size_t secp256k1_whitelist_signature_n_keys(const secp256k1_whitelist_signature *sig) {
|
||||
return sig->n_keys;
|
||||
}
|
||||
|
||||
int secp256k1_whitelist_signature_parse(const secp256k1_context* ctx, secp256k1_whitelist_signature *sig, const unsigned char *input) {
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(sig != NULL);
|
||||
ARG_CHECK(input != NULL);
|
||||
|
||||
sig->n_keys = input[0];
|
||||
if (sig->n_keys >= MAX_KEYS) {
|
||||
return 0;
|
||||
}
|
||||
memcpy(&sig->data[0], &input[1], 32 * (sig->n_keys + 1));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int secp256k1_whitelist_signature_serialize(const secp256k1_context* ctx, unsigned char *output, const secp256k1_whitelist_signature *sig) {
|
||||
VERIFY_CHECK(ctx != NULL);
|
||||
ARG_CHECK(output != NULL);
|
||||
ARG_CHECK(sig != NULL);
|
||||
|
||||
output[0] = sig->n_keys;
|
||||
memcpy(&output[1], &sig->data[0], 32 * (sig->n_keys + 1));
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif
|
108
src/modules/whitelist/tests_impl.h
Normal file
108
src/modules/whitelist/tests_impl.h
Normal file
@ -0,0 +1,108 @@
|
||||
/**********************************************************************
|
||||
* Copyright (c) 2014-2016 Pieter Wuille, 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_WHITELIST_TESTS
|
||||
#define SECP256K1_MODULE_WHITELIST_TESTS
|
||||
|
||||
#include "include/secp256k1_whitelist.h"
|
||||
|
||||
void test_whitelist_end_to_end(const size_t n_keys) {
|
||||
unsigned char **online_seckey = (unsigned char **) malloc(n_keys * sizeof(*online_seckey));
|
||||
unsigned char **summed_seckey = (unsigned char **) malloc(n_keys * sizeof(*summed_seckey));
|
||||
secp256k1_pubkey *online_pubkeys = (secp256k1_pubkey *) malloc(n_keys * sizeof(*online_pubkeys));
|
||||
secp256k1_pubkey *offline_pubkeys = (secp256k1_pubkey *) malloc(n_keys * sizeof(*offline_pubkeys));
|
||||
|
||||
secp256k1_scalar ssub;
|
||||
unsigned char csub[32];
|
||||
secp256k1_pubkey sub_pubkey;
|
||||
|
||||
/* Generate random keys */
|
||||
size_t i;
|
||||
/* Start with subkey */
|
||||
random_scalar_order_test(&ssub);
|
||||
secp256k1_scalar_get_b32(csub, &ssub);
|
||||
CHECK(secp256k1_ec_seckey_verify(ctx, csub) == 1);
|
||||
CHECK(secp256k1_ec_pubkey_create(ctx, &sub_pubkey, csub) == 1);
|
||||
/* Then offline and online whitelist keys */
|
||||
for (i = 0; i < n_keys; i++) {
|
||||
secp256k1_scalar son, soff;
|
||||
|
||||
online_seckey[i] = (unsigned char *) malloc(32);
|
||||
summed_seckey[i] = (unsigned char *) malloc(32);
|
||||
|
||||
/* Create two keys */
|
||||
random_scalar_order_test(&son);
|
||||
secp256k1_scalar_get_b32(online_seckey[i], &son);
|
||||
CHECK(secp256k1_ec_seckey_verify(ctx, online_seckey[i]) == 1);
|
||||
CHECK(secp256k1_ec_pubkey_create(ctx, &online_pubkeys[i], online_seckey[i]) == 1);
|
||||
|
||||
random_scalar_order_test(&soff);
|
||||
secp256k1_scalar_get_b32(summed_seckey[i], &soff);
|
||||
CHECK(secp256k1_ec_seckey_verify(ctx, summed_seckey[i]) == 1);
|
||||
CHECK(secp256k1_ec_pubkey_create(ctx, &offline_pubkeys[i], summed_seckey[i]) == 1);
|
||||
|
||||
/* Make summed_seckey correspond to the sum of offline_pubkey and sub_pubkey */
|
||||
secp256k1_scalar_add(&soff, &soff, &ssub);
|
||||
secp256k1_scalar_get_b32(summed_seckey[i], &soff);
|
||||
CHECK(secp256k1_ec_seckey_verify(ctx, summed_seckey[i]) == 1);
|
||||
}
|
||||
|
||||
/* Sign/verify with each one */
|
||||
for (i = 0; i < n_keys; i++) {
|
||||
unsigned char serialized[32 + 4 + 32 * SECP256K1_WHITELIST_MAX_N_KEYS] = {0};
|
||||
secp256k1_whitelist_signature sig;
|
||||
secp256k1_whitelist_signature sig1;
|
||||
|
||||
CHECK(secp256k1_whitelist_sign(ctx, &sig, online_pubkeys, offline_pubkeys, n_keys, &sub_pubkey, online_seckey[i], summed_seckey[i], i, NULL, NULL));
|
||||
CHECK(secp256k1_whitelist_verify(ctx, &sig, online_pubkeys, offline_pubkeys, &sub_pubkey) == 1);
|
||||
/* Check that exchanging keys causes a failure */
|
||||
CHECK(secp256k1_whitelist_verify(ctx, &sig, offline_pubkeys, online_pubkeys, &sub_pubkey) != 1);
|
||||
/* Serialization round trip */
|
||||
CHECK(secp256k1_whitelist_signature_serialize(ctx, serialized, &sig) == 1);
|
||||
CHECK(secp256k1_whitelist_signature_parse(ctx, &sig1, serialized) == 1);
|
||||
CHECK(secp256k1_whitelist_verify(ctx, &sig1, online_pubkeys, offline_pubkeys, &sub_pubkey) == 1);
|
||||
CHECK(secp256k1_whitelist_verify(ctx, &sig1, offline_pubkeys, online_pubkeys, &sub_pubkey) != 1);
|
||||
/* Test n_keys */
|
||||
CHECK(secp256k1_whitelist_signature_n_keys(&sig) == n_keys);
|
||||
CHECK(secp256k1_whitelist_signature_n_keys(&sig1) == n_keys);
|
||||
}
|
||||
|
||||
for (i = 0; i < n_keys; i++) {
|
||||
free(online_seckey[i]);
|
||||
free(summed_seckey[i]);
|
||||
}
|
||||
free(online_seckey);
|
||||
free(summed_seckey);
|
||||
free(online_pubkeys);
|
||||
free(offline_pubkeys);
|
||||
}
|
||||
|
||||
void test_whitelist_bad_parse(void) {
|
||||
const unsigned char serialized[] = {
|
||||
/* Hash */
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08,
|
||||
/* Length in excess of maximum */
|
||||
0x00, 0x00, 0x01, 0x00
|
||||
/* No room for s-values; parse should be rejected before reading past length */
|
||||
};
|
||||
secp256k1_whitelist_signature sig;
|
||||
|
||||
CHECK(secp256k1_whitelist_signature_parse(ctx, &sig, serialized) == 0);
|
||||
}
|
||||
|
||||
void run_whitelist_tests(void) {
|
||||
int i;
|
||||
for (i = 0; i < count; i++) {
|
||||
test_whitelist_end_to_end(1);
|
||||
test_whitelist_end_to_end(10);
|
||||
test_whitelist_end_to_end(50);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
96
src/modules/whitelist/whitelist.md
Normal file
96
src/modules/whitelist/whitelist.md
Normal file
@ -0,0 +1,96 @@
|
||||
Address Whitelisting Module
|
||||
===========================
|
||||
|
||||
This module implements a scheme by which members of some group, having fixed
|
||||
signing keys, can prove control of an arbitrary other key without associating
|
||||
their own identity (only that they belong to the group) to the new key. The
|
||||
application is to patch ring-signature-like behaviour onto systems such as
|
||||
Bitcoin or PGP which do not directly support this.
|
||||
|
||||
We refer to such delegation as "whitelisting" because we expect it to be used
|
||||
to build a dynamic whitelist of authorized keys.
|
||||
|
||||
For example, imagine a private sidechain with a fixed membership set but
|
||||
stronger privacy properties than Bitcoin. When moving coins from this system
|
||||
to Bitcoin, it is desirable that the destination Bitcoin addresses be provably
|
||||
in control of some user of the sidechain. This prevents malicious or erroneous
|
||||
behaviour on the sidechain, which can likely be resolved by its participants,
|
||||
from translating to theft on the wider Bitcoin network, which is irreversible.
|
||||
|
||||
### Unused Schemes and Design Rationale
|
||||
|
||||
#### Direct Signing
|
||||
|
||||
An obvious scheme for such delegation is to simply have participants sign the
|
||||
key they want to whitelist. To avoid revealing their specific identity, they
|
||||
could use a ring signature. The problem with this is that it really only proves
|
||||
that a participant *signed off* on a key, not that they control it. Thus any
|
||||
security failure that allows text substitution could be used to subvert this
|
||||
and redirect coins to an attacker-controlled address.
|
||||
|
||||
#### Signing with Difference-of-Keys
|
||||
|
||||
A less obvious scheme is to have a participant sign an arbitrary message with
|
||||
the sum of her key `P` and the whitelisted key `W`. Such a signature with the key
|
||||
`P + W` proves knowledge of either (a) discrete logarithms of both `P` and `W`;
|
||||
or (b) neither. This makes directly attacking participants' signing schemes much
|
||||
harder, but allows an attacker to whitelist arbitrary "garbage" keys by computing
|
||||
`W` as the difference between an attacker-controlled key and `P`. For Bitcoin,
|
||||
the effect of garbage keys is to "burn" stolen coins, destroying them.
|
||||
|
||||
In an important sense, this "burning coins" attack is a good thing: it enables
|
||||
*offline delegation*. That is, the key `P` does not need to be available at the
|
||||
time of delegation. Instead, participants could choose `S = P + W`, sign with
|
||||
this to delegate, and only later compute the discrete logarithm of `W = P - S`.
|
||||
This allows `P` to be in cold storage or be otherwise inaccessible, improving
|
||||
the overall system security.
|
||||
|
||||
#### Signing with Tweaked-Difference-of-Keys
|
||||
|
||||
A modification of this scheme, which prevents this "garbage key" attack, is to
|
||||
instead have participants sign some message with the key `P + H(W)W`, for `H`
|
||||
some random-oracle hash that maps group elements to scalars. This key, and its
|
||||
discrete logarithm, cannot be known until after `W` is chosen, so `W` cannot
|
||||
be selected as the difference between it and `P`. (Note that `P` could still
|
||||
be some chosen difference; however `P` is a fixed key and must be verified
|
||||
out-of-band to have come from a legitimate participant anyway.)
|
||||
|
||||
This scheme is almost what we want, but it no longer supports offline
|
||||
delegation. However, we can get this back by introducing a new key, `P'`,
|
||||
and signing with the key `P + H(W + P')(W + P')`. This gives us the best
|
||||
of both worlds: `P'` does not need to be online to delegate, allowing it
|
||||
to be securely stored and preventing real-time attacks; `P` does need to
|
||||
be online, but its compromise only allows an attacker to whitelist "garbage
|
||||
keys", not attacker-controlled ones.
|
||||
|
||||
### Our Scheme
|
||||
|
||||
Our scheme works as follows: each participant `i` chooses two keys, `P_i` and `Q_i`.
|
||||
We refer to `P_i` as the "online key" and `Q_i` as the "offline key". To whitelist
|
||||
a key `W`, the participant computes the key `L_j = P_j + H(W + Q_j)(W + Q_j)` for
|
||||
every participant `j`. Then she will know the discrete logarithm of `L_i` for her
|
||||
own `i`.
|
||||
|
||||
Next, she signs a message containing every `P_i` and `Q_i` as well as `W` with
|
||||
a ring signature over all the keys `L_j`. This proves that she knows the discrete
|
||||
logarithm of some `L_i` (though it is zero-knowledge which one), and therefore
|
||||
knows:
|
||||
1. The discrete logarithms of all of `W`, `P_i` and `Q_i`; or
|
||||
2. The discrete logarithm of `P_i` but of *neither* `W` nor `Q_i`.
|
||||
In other words, compromise of the online key `P_i` allows an attacker to whitelist
|
||||
"garbage keys" for which nobody knows the discrete logarithm; to whitelist an
|
||||
attacker-controlled key, he must compromise both `P_i` and `Q_i`. This is difficult
|
||||
because by design, only the sum `S = W + Q_i` is used when signing; then by choosing
|
||||
`S` freely, a participant can delegate without the secret key to `Q_i` ever being online.
|
||||
(Later, when she wants to actually use `W`, she will need to compute its key as the
|
||||
difference between `S` and `Q_i`; but this can be done offline and much later
|
||||
and with more expensive security requirements.)
|
||||
|
||||
The message to be signed contains all public keys to prevent a class of attacks
|
||||
centered around choosing keys to match pre-computed signatures. In our proposed
|
||||
use case, whitelisted keys already must be computed before they are signed, and
|
||||
the remaining public keys are verified out-of-band when setting up the system,
|
||||
so there is no direct benefit to this. We do it only to reduce fragility and
|
||||
increase safety of unforeseen uses.
|
||||
|
||||
|
129
src/modules/whitelist/whitelist_impl.h
Normal file
129
src/modules/whitelist/whitelist_impl.h
Normal file
@ -0,0 +1,129 @@
|
||||
/**********************************************************************
|
||||
* 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_WHITELIST_IMPL_H_
|
||||
#define _SECP256K1_WHITELIST_IMPL_H_
|
||||
|
||||
static int secp256k1_whitelist_hash_pubkey(secp256k1_scalar* output, secp256k1_gej* pubkey) {
|
||||
unsigned char h[32];
|
||||
unsigned char c[33];
|
||||
secp256k1_sha256_t sha;
|
||||
int overflow = 0;
|
||||
size_t size = 33;
|
||||
secp256k1_ge ge;
|
||||
|
||||
secp256k1_ge_set_gej(&ge, pubkey);
|
||||
|
||||
secp256k1_sha256_initialize(&sha);
|
||||
if (!secp256k1_eckey_pubkey_serialize(&ge, c, &size, SECP256K1_EC_COMPRESSED)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_sha256_write(&sha, c, size);
|
||||
secp256k1_sha256_finalize(&sha, h);
|
||||
|
||||
secp256k1_scalar_set_b32(output, h, &overflow);
|
||||
if (overflow || secp256k1_scalar_is_zero(output)) {
|
||||
/* This return path is mathematically impossible to hit */
|
||||
secp256k1_scalar_clear(output);
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int secp256k1_whitelist_tweak_pubkey(const secp256k1_context* ctx, secp256k1_gej* pub_tweaked) {
|
||||
secp256k1_scalar tweak;
|
||||
secp256k1_scalar zero;
|
||||
int ret;
|
||||
|
||||
secp256k1_scalar_set_int(&zero, 0);
|
||||
|
||||
ret = secp256k1_whitelist_hash_pubkey(&tweak, pub_tweaked);
|
||||
if (ret) {
|
||||
secp256k1_ecmult(&ctx->ecmult_ctx, pub_tweaked, pub_tweaked, &tweak, &zero);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int secp256k1_whitelist_compute_tweaked_privkey(const secp256k1_context* ctx, secp256k1_scalar* skey, const unsigned char *online_key, const unsigned char *summed_key) {
|
||||
secp256k1_scalar tweak;
|
||||
int ret = 1;
|
||||
int overflow = 0;
|
||||
|
||||
secp256k1_scalar_set_b32(skey, summed_key, &overflow);
|
||||
if (overflow || secp256k1_scalar_is_zero(skey)) {
|
||||
ret = 0;
|
||||
}
|
||||
if (ret) {
|
||||
secp256k1_gej pkeyj;
|
||||
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pkeyj, skey);
|
||||
ret = secp256k1_whitelist_hash_pubkey(&tweak, &pkeyj);
|
||||
}
|
||||
if (ret) {
|
||||
secp256k1_scalar sonline;
|
||||
secp256k1_scalar_mul(skey, skey, &tweak);
|
||||
|
||||
secp256k1_scalar_set_b32(&sonline, online_key, &overflow);
|
||||
if (overflow || secp256k1_scalar_is_zero(&sonline)) {
|
||||
ret = 0;
|
||||
}
|
||||
secp256k1_scalar_add(skey, skey, &sonline);
|
||||
secp256k1_scalar_clear(&sonline);
|
||||
secp256k1_scalar_clear(&tweak);
|
||||
}
|
||||
|
||||
if (!ret) {
|
||||
secp256k1_scalar_clear(skey);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Takes a list of pubkeys and combines them to form the public keys needed
|
||||
* for the ring signature; also produce a commitment to every one that will
|
||||
* be our "message". */
|
||||
static int secp256k1_whitelist_compute_keys_and_message(const secp256k1_context* ctx, unsigned char *msg32, secp256k1_gej *keys, const secp256k1_pubkey *online_pubkeys, const secp256k1_pubkey *offline_pubkeys, const int n_keys, const secp256k1_pubkey *sub_pubkey) {
|
||||
unsigned char c[33];
|
||||
size_t size = 33;
|
||||
secp256k1_sha256_t sha;
|
||||
int i;
|
||||
secp256k1_ge subkey_ge;
|
||||
|
||||
secp256k1_sha256_initialize(&sha);
|
||||
secp256k1_pubkey_load(ctx, &subkey_ge, sub_pubkey);
|
||||
|
||||
/* commit to sub-key */
|
||||
if (!secp256k1_eckey_pubkey_serialize(&subkey_ge, c, &size, SECP256K1_EC_COMPRESSED)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_sha256_write(&sha, c, size);
|
||||
for (i = 0; i < n_keys; i++) {
|
||||
secp256k1_ge offline_ge;
|
||||
secp256k1_ge online_ge;
|
||||
secp256k1_gej tweaked_gej;
|
||||
|
||||
/* commit to fixed keys */
|
||||
secp256k1_pubkey_load(ctx, &offline_ge, &offline_pubkeys[i]);
|
||||
if (!secp256k1_eckey_pubkey_serialize(&offline_ge, c, &size, SECP256K1_EC_COMPRESSED)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_sha256_write(&sha, c, size);
|
||||
secp256k1_pubkey_load(ctx, &online_ge, &online_pubkeys[i]);
|
||||
if (!secp256k1_eckey_pubkey_serialize(&online_ge, c, &size, SECP256K1_EC_COMPRESSED)) {
|
||||
return 0;
|
||||
}
|
||||
secp256k1_sha256_write(&sha, c, size);
|
||||
|
||||
/* compute tweaked keys */
|
||||
secp256k1_gej_set_ge(&tweaked_gej, &offline_ge);
|
||||
secp256k1_gej_add_ge_var(&tweaked_gej, &tweaked_gej, &subkey_ge, NULL);
|
||||
secp256k1_whitelist_tweak_pubkey(ctx, &tweaked_gej);
|
||||
secp256k1_gej_add_ge_var(&keys[i], &tweaked_gej, &online_ge, NULL);
|
||||
}
|
||||
secp256k1_sha256_finalize(&sha, msg32);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
#endif
|
@ -604,3 +604,7 @@ int secp256k1_ec_pubkey_combine(const secp256k1_context* ctx, secp256k1_pubkey *
|
||||
#ifdef ENABLE_MODULE_RANGEPROOF
|
||||
# include "modules/rangeproof/main_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_WHITELIST
|
||||
# include "modules/whitelist/main_impl.h"
|
||||
#endif
|
||||
|
@ -4467,6 +4467,10 @@ void run_ecdsa_openssl(void) {
|
||||
# include "modules/rangeproof/tests_impl.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_WHITELIST
|
||||
# include "modules/whitelist/tests_impl.h"
|
||||
#endif
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
unsigned char seed16[16] = {0};
|
||||
unsigned char run32[32] = {0};
|
||||
@ -4599,6 +4603,11 @@ int main(int argc, char **argv) {
|
||||
run_rangeproof_tests();
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_MODULE_WHITELIST
|
||||
/* Key whitelisting tests */
|
||||
run_whitelist_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