Implement ring-signature based whitelist delegation scheme

This commit is contained in:
Andrew Poelstra 2016-04-21 22:22:39 +00:00 committed by Pieter Wuille
parent 6eebf82d8a
commit da035050f8
10 changed files with 684 additions and 0 deletions

View File

@ -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

View File

@ -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"])

View 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

View 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

View 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

View 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

View 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.

View 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

View File

@ -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

View File

@ -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]);