Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Schnorr sign-to-contract commitments #1018

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions include/secp256k1.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ extern "C" {
#endif

#include <stddef.h>
#include <stdint.h>

/* Unless explicitly stated all pointer arguments must not be NULL.
*
Expand Down Expand Up @@ -84,6 +85,29 @@ typedef struct {
unsigned char data[64];
} secp256k1_ecdsa_signature;

/** Data structure that holds a sign-to-contract ("s2c") opening information.
* Sign-to-contract allows a signer to commit to some data as part of a signature. It
* can be used as an Out-argument in certain signing functions.
*
* This structure is not opaque, but it is strongly discouraged to read or write to
* it directly.
*
* The exact representation of data inside is implementation defined and not
* guaranteed to be portable between different platforms or versions. It can
* be safely copied/moved.
*/
typedef struct {
/* magic is set during initialization */
uint64_t magic;
/* Public nonce before applying the sign-to-contract commitment */
secp256k1_pubkey original_pubnonce;
/* Byte indicating if signing algorithm negated the nonce. Alternatively when
* verifying we could compute the EC commitment of original_pubnonce and the
* data and negate if this would not be a valid nonce. But this would prevent
* batch verification of sign-to-contract commitments. */
int nonce_is_negated;
} secp256k1_s2c_opening;

/** A pointer to a function to deterministically generate a nonce.
*
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail.
Expand Down Expand Up @@ -475,6 +499,37 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact(
const secp256k1_ecdsa_signature* sig
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Parse a sign-to-contract opening.
*
* Returns: 1 if the opening was fully valid.
* 0 if the opening could not be parsed or is invalid.
* Args: ctx: a secp256k1 context object.
* Out: opening: pointer to an opening object. If 1 is returned, it is set to a
* parsed version of input. If not, its value is undefined.
* In: input33: pointer to 33-byte array with a serialized opening
*
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_parse(
const secp256k1_context* ctx,
secp256k1_s2c_opening* opening,
const unsigned char *input33
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Serialize a sign-to-contract opening into a byte sequence.
*
* Returns: 1 if the opening was successfully serialized.
* 0 if the opening was not initializaed.
* Args: ctx: a secp256k1 context object.
* Out: output33: pointer to a 33-byte array to place the serialized opening
* in.
* In: opening: a pointer to an initialized `secp256k1_s2c_opening`.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_serialize(
const secp256k1_context* ctx,
unsigned char *output33,
const secp256k1_s2c_opening* opening
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** Verify an ECDSA signature.
*
* Returns: 1: correct signature
Expand Down
136 changes: 136 additions & 0 deletions src/secp256k1.c
Original file line number Diff line number Diff line change
Expand Up @@ -788,6 +788,142 @@ int secp256k1_tagged_sha256(const secp256k1_context* ctx, unsigned char *hash32,
return 1;
}

/* Compute an ec commitment tweak as hash(pubkey, data). */
static int secp256k1_ec_commit_tweak(const secp256k1_context *ctx, unsigned char *tweak32, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) {
secp256k1_ge p;
unsigned char rbuf[33];
size_t rbuf_size = sizeof(rbuf);
secp256k1_sha256 sha;

if (data_size == 0) {
/* That's probably not what the caller wanted */
return 0;
}
if(!secp256k1_pubkey_load(ctx, &p, pubkey)) {
return 0;
}
secp256k1_eckey_pubkey_serialize(&p, rbuf, &rbuf_size, 1);

secp256k1_sha256_initialize(&sha);
secp256k1_sha256_write(&sha, rbuf, rbuf_size);
secp256k1_sha256_write(&sha, data, data_size);
secp256k1_sha256_finalize(&sha, tweak32);
return 1;
}

/* Compute an ec commitment as pubkey + hash(pubkey, data)*G. */
static int secp256k1_ec_commit(const secp256k1_context* ctx, secp256k1_pubkey *commitment, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) {
unsigned char tweak[32];

*commitment = *pubkey;
if (!secp256k1_ec_commit_tweak(ctx, tweak, commitment, data, data_size)) {
return 0;
}
return secp256k1_ec_pubkey_tweak_add(ctx, commitment, tweak);
}

/* Compute the seckey of an ec commitment from the original secret key of the pubkey as seckey +
* hash(pubkey, data). */
static int secp256k1_ec_commit_seckey(const secp256k1_context* ctx, unsigned char *seckey, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) {
unsigned char tweak[32];
secp256k1_pubkey pubkey_tmp;

if (pubkey == NULL) {
/* Compute pubkey from seckey if not provided */
int overflow;
secp256k1_scalar x;
secp256k1_gej pj;
secp256k1_ge p;

secp256k1_scalar_set_b32(&x, seckey, &overflow);
if (overflow != 0) {
return 0;
}
secp256k1_ecmult_gen(&ctx->ecmult_gen_ctx, &pj, &x);
secp256k1_ge_set_gej(&p, &pj);
secp256k1_pubkey_save(&pubkey_tmp, &p);
pubkey = &pubkey_tmp;
}

if (!secp256k1_ec_commit_tweak(ctx, tweak, pubkey, data, data_size)) {
return 0;
}
return secp256k1_ec_privkey_tweak_add(ctx, seckey, tweak);
}

/* Verify an ec commitment as pubkey + hash(pubkey, data)*G ?= commitment. */
static int secp256k1_ec_commit_verify(const secp256k1_context* ctx, const secp256k1_pubkey *commitment, const secp256k1_pubkey *pubkey, const unsigned char *data, size_t data_size) {
secp256k1_gej pj;
secp256k1_ge p;
secp256k1_pubkey commitment_tmp;

if (!secp256k1_ec_commit(ctx, &commitment_tmp, pubkey, data, data_size)) {
return 0;
}

/* Return commitment == commitment_tmp */
secp256k1_pubkey_load(ctx, &p, &commitment_tmp);
secp256k1_gej_set_ge(&pj, &p);
secp256k1_pubkey_load(ctx, &p, commitment);
secp256k1_ge_neg(&p, &p);
secp256k1_gej_add_ge_var(&pj, &pj, &p, NULL);
return secp256k1_gej_is_infinity(&pj);
}

static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL;

static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening) {
opening->magic = s2c_opening_magic;
opening->nonce_is_negated = 0;
}

static int secp256k1_s2c_commit_is_init(const secp256k1_s2c_opening *opening) {
return opening->magic == s2c_opening_magic;
}

/* s2c_opening is serialized as 33 bytes containing the compressed original pubnonce. In addition to
* holding the EVEN or ODD tag, the first byte has the third bit set to 1 if the nonce was negated.
* The remaining bits in the first byte are 0. */
int secp256k1_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_s2c_opening* opening, const unsigned char *input33) {
unsigned char pk_ser[33];
VERIFY_CHECK(ctx != NULL);
ARG_CHECK(opening != NULL);
ARG_CHECK(input33 != NULL);

secp256k1_s2c_opening_init(opening);
/* Return 0 if unknown bits are set */
if ((input33[0] & ~0x06) != 0) {
return 0;
}
/* Read nonce_is_negated bit */
opening->nonce_is_negated = input33[0] & (1 << 2);
memcpy(pk_ser, input33, sizeof(pk_ser));
/* Unset nonce_is_negated bit to allow parsing the public key */
pk_ser[0] &= ~(1 << 2);
return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &pk_ser[0], 33);
}

int secp256k1_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_s2c_opening* opening) {
size_t outputlen = 33;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(output33 != NULL);
ARG_CHECK(opening != NULL);
ARG_CHECK(secp256k1_s2c_commit_is_init(opening));

if (!secp256k1_ec_pubkey_serialize(ctx, &output33[0], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED)) {
return 0;
}
/* Verify that ec_pubkey_serialize only sets the first two bits of the
* first byte, otherwise this function doesn't make any sense */
VERIFY_CHECK(output33[0] == 0x02 || output33[0] == 0x03);
if (opening->nonce_is_negated) {
/* Set nonce_is_negated bit */
output33[0] |= (1 << 2);
}
return 1;
}

#ifdef ENABLE_MODULE_ECDH
# include "modules/ecdh/main_impl.h"
#endif
Expand Down
144 changes: 144 additions & 0 deletions src/tests.c
Original file line number Diff line number Diff line change
Expand Up @@ -3360,6 +3360,85 @@ void run_ec_combine(void) {
}
}

int test_ec_commit_seckey(unsigned char *seckey, secp256k1_pubkey *commitment) {
/* Return if seckey is the discrete log of commitment */
secp256k1_pubkey pubkey_tmp;
return secp256k1_ec_pubkey_create(ctx, &pubkey_tmp, seckey) == 1
&& memcmp(&pubkey_tmp, commitment, sizeof(pubkey_tmp)) == 0;
}

void test_ec_commit(void) {
unsigned char seckey[32];
secp256k1_pubkey pubkey;
secp256k1_pubkey commitment;
unsigned char data[32];

/* Create random keypair and data */
secp256k1_testrand256(seckey);
CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey));
secp256k1_testrand256_test(data);

/* Commit to data and verify */
CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 32));
CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 32));
CHECK(secp256k1_ec_commit_seckey(ctx, seckey, &pubkey, data, 32));
CHECK(test_ec_commit_seckey(seckey, &commitment) == 1);

/* Check that verification fails with different data */
CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 31) == 0);
}

void test_ec_commit_api(void) {
unsigned char seckey[32];
secp256k1_pubkey pubkey;
secp256k1_pubkey commitment;
unsigned char data[32];

memset(data, 23, sizeof(data));

/* Create random keypair */
secp256k1_testrand256(seckey);
CHECK(secp256k1_ec_pubkey_create(ctx, &pubkey, seckey));

CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 1) == 1);
/* The same pubkey can be both input and output of the function */
{
secp256k1_pubkey pubkey_tmp = pubkey;
CHECK(secp256k1_ec_commit(ctx, &pubkey_tmp, &pubkey_tmp, data, 1) == 1);
CHECK(memcmp(commitment.data, pubkey_tmp.data, sizeof(commitment.data)) == 0);
}

/* If the pubkey is not provided it will be computed from seckey */
CHECK(secp256k1_ec_commit_seckey(ctx, seckey, NULL, data, 1) == 1);
CHECK(test_ec_commit_seckey(seckey, &commitment) == 1);
/* pubkey is not provided but seckey overflows */
{
unsigned char overflowed_seckey[32];
memset(overflowed_seckey, 0xFF, sizeof(overflowed_seckey));
CHECK(secp256k1_ec_commit_seckey(ctx, overflowed_seckey, NULL, data, 1) == 0);
}

CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 1) == 1);

/* Commitment to 0-len data should fail */
CHECK(secp256k1_ec_commit(ctx, &commitment, &pubkey, data, 0) == 0);
CHECK(secp256k1_ec_commit_verify(ctx, &commitment, &pubkey, data, 0) == 0);
CHECK(memcmp(&pubkey.data, &commitment.data, sizeof(pubkey.data)) == 0);
{
unsigned char seckey_tmp[32];
memcpy(seckey_tmp, seckey, 32);
CHECK(secp256k1_ec_commit_seckey(ctx, seckey_tmp, &pubkey, data, 0) == 0);
}
}

void run_ec_commit(void) {
int i;
for (i = 0; i < count * 8; i++) {
test_ec_commit();
}
test_ec_commit_api();
}

void test_group_decompress(const secp256k1_fe* x) {
/* The input itself, normalized. */
secp256k1_fe fex = *x;
Expand Down Expand Up @@ -5312,6 +5391,68 @@ void run_eckey_negate_test(void) {
CHECK(secp256k1_memcmp_var(seckey, seckey_tmp, 32) == 0);
}


void run_s2c_opening_test(void) {
int i = 0;
unsigned char output[33];
/* First byte 0x06 means that nonce_is_negated and EVEN tag for the
* following compressed pubkey (which is valid). */
unsigned char input[33] = {
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x02
};
secp256k1_s2c_opening opening;
size_t ecount = 0;

secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);

/* Uninitialized opening can't be serialized. Actually testing that would be
* undefined behavior. Therefore we simulate it by setting the opening to 0. */
memset(&opening, 0, sizeof(opening));
CHECK(ecount == 0);
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 0);
CHECK(ecount == 1);

/* First parsing, then serializing works */
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1);
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1);

{
/* Invalid pubkey makes parsing fail */
unsigned char input_tmp[33];
memcpy(input_tmp, input, sizeof(input_tmp));
/* Pubkey oddness tag is invalid */
input_tmp[0] = 0;
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
/* nonce_is_negated bit is set but pubkey oddness tag is invalid */
input_tmp[0] = 5;
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
/* Unknown bit is set */
input_tmp[0] = 8;
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
}

/* Try parsing and serializing a bunch of openings */
do {
/* This is expected to fail in about 50% of iterations because the
* points' x-coordinates are uniformly random */
if (secp256k1_s2c_opening_parse(ctx, &opening, input) == 1) {
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
CHECK(memcmp(output, input, sizeof(output)) == 0);
}
secp256k1_testrand256(&input[1]);
/* Set pubkey oddness tag to first bit of input[1] */
input[0] = (input[1] & 1) + 2;
/* Set nonce_is_negated bit to input[1]'s 3rd bit */
input[0] |= (input[1] & (1 << 2));
i++;
} while(i < count);
}

void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) {
secp256k1_scalar nonce;
do {
Expand Down Expand Up @@ -6584,6 +6725,7 @@ int main(int argc, char **argv) {
run_ecmult_const_tests();
run_ecmult_multi_tests();
run_ec_combine();
run_ec_commit();

/* endomorphism tests */
run_endomorphism_tests();
Expand All @@ -6597,6 +6739,8 @@ int main(int argc, char **argv) {
/* EC key arithmetic test */
run_eckey_negate_test();

run_s2c_opening_test();

#ifdef ENABLE_MODULE_ECDH
/* ecdh tests */
run_ecdh_tests();
Expand Down