From a66ea35227b689cf3a512fbe93403b5cf4b4790f Mon Sep 17 00:00:00 2001 From: Andrew Poelstra Date: Thu, 21 Apr 2016 22:22:39 +0000 Subject: [PATCH] Implement ring-signature based whitelist delegation scheme --- Makefile.am | 4 + configure.ac | 22 +++ include/secp256k1_whitelist.h | 146 +++++++++++++++++++ src/modules/whitelist/Makefile.am.include | 3 + src/modules/whitelist/main_impl.h | 164 ++++++++++++++++++++++ src/modules/whitelist/tests_impl.h | 108 ++++++++++++++ src/modules/whitelist/whitelist.md | 96 +++++++++++++ src/modules/whitelist/whitelist_impl.h | 129 +++++++++++++++++ src/secp256k1.c | 4 + src/tests.c | 9 ++ 10 files changed, 685 insertions(+) create mode 100644 include/secp256k1_whitelist.h create mode 100644 src/modules/whitelist/Makefile.am.include create mode 100644 src/modules/whitelist/main_impl.h create mode 100644 src/modules/whitelist/tests_impl.h create mode 100644 src/modules/whitelist/whitelist.md create mode 100644 src/modules/whitelist/whitelist_impl.h diff --git a/Makefile.am b/Makefile.am index 6076e08d..7dfe4e39 100644 --- a/Makefile.am +++ b/Makefile.am @@ -189,3 +189,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 diff --git a/configure.ac b/configure.ac index 7c8f864d..5c8b44ac 100644 --- a/configure.ac +++ b/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(external_default_callbacks, AS_HELP_STRING([--enable-external-default-callbacks],[enable external default callback functions (default is no)]), [use_external_default_callbacks=$enableval], @@ -516,6 +521,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 @@ -533,12 +542,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.]) @@ -552,6 +569,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]) @@ -570,6 +590,8 @@ 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"]) diff --git a/include/secp256k1_whitelist.h b/include/secp256k1_whitelist.h new file mode 100644 index 00000000..c3175ce0 --- /dev/null +++ b/include/secp256k1_whitelist.h @@ -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 diff --git a/src/modules/whitelist/Makefile.am.include b/src/modules/whitelist/Makefile.am.include new file mode 100644 index 00000000..e926ffce --- /dev/null +++ b/src/modules/whitelist/Makefile.am.include @@ -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 diff --git a/src/modules/whitelist/main_impl.h b/src/modules/whitelist/main_impl.h new file mode 100644 index 00000000..0de178fc --- /dev/null +++ b/src/modules/whitelist/main_impl.h @@ -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 diff --git a/src/modules/whitelist/tests_impl.h b/src/modules/whitelist/tests_impl.h new file mode 100644 index 00000000..e307de16 --- /dev/null +++ b/src/modules/whitelist/tests_impl.h @@ -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 diff --git a/src/modules/whitelist/whitelist.md b/src/modules/whitelist/whitelist.md new file mode 100644 index 00000000..15ab998c --- /dev/null +++ b/src/modules/whitelist/whitelist.md @@ -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. + + diff --git a/src/modules/whitelist/whitelist_impl.h b/src/modules/whitelist/whitelist_impl.h new file mode 100644 index 00000000..ff8d87f4 --- /dev/null +++ b/src/modules/whitelist/whitelist_impl.h @@ -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 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 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 diff --git a/src/secp256k1.c b/src/secp256k1.c index 1c655c59..e2459a5d 100644 --- a/src/secp256k1.c +++ b/src/secp256k1.c @@ -706,3 +706,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 diff --git a/src/tests.c b/src/tests.c index 4a842776..1ab6430b 100644 --- a/src/tests.c +++ b/src/tests.c @@ -5220,6 +5220,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}; @@ -5353,6 +5357,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]);