Skip to content

Commit

Permalink
add schnorr sign-to-contract opening with parse/ serialize functions
Browse files Browse the repository at this point in the history
Adapted from bitcoin-core#589.

Co-authored-by: Marko Bencun <mbencun+pgp@gmail.com>
  • Loading branch information
jonasnick and benma committed Sep 12, 2022
1 parent 6b64825 commit a903875
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 0 deletions.
56 changes: 56 additions & 0 deletions include/secp256k1_schnorrsig.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#ifndef SECP256K1_SCHNORRSIG_H
#define SECP256K1_SCHNORRSIG_H

#include <stdint.h>

#include "secp256k1.h"
#include "secp256k1_extrakeys.h"

Expand All @@ -13,6 +15,60 @@ extern "C" {
* (https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki).
*/

/** 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 */
unsigned char magic[8];
/* 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_schnorrsig_s2c_opening;

/** 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_schnorrsig_s2c_opening_parse(
const secp256k1_context* ctx,
secp256k1_schnorrsig_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_schnorrsig_s2c_opening`.
*/
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_s2c_opening_serialize(
const secp256k1_context* ctx,
unsigned char *output33,
const secp256k1_schnorrsig_s2c_opening* opening
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);

/** A pointer to a function to deterministically generate a nonce.
*
* Same as secp256k1_nonce function with the exception of accepting an
Expand Down
54 changes: 54 additions & 0 deletions src/modules/schnorrsig/main_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,60 @@
#include "../../../include/secp256k1_schnorrsig.h"
#include "../../hash.h"

static unsigned char s2c_opening_magic[8] = "\x5d\x05\x20\xb8\xb7\xf2\xb1\x68";

static void secp256k1_schnorrsig_s2c_opening_init(secp256k1_schnorrsig_s2c_opening *opening) {
memcpy(opening->magic, s2c_opening_magic, 8);
opening->nonce_is_negated = 0;
}

static int secp256k1_schnorrsig_s2c_commit_is_init(const secp256k1_schnorrsig_s2c_opening *opening) {
return secp256k1_memcmp_var(opening->magic, s2c_opening_magic, 8) == 0;
}

/* 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_schnorrsig_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_schnorrsig_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_schnorrsig_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_schnorrsig_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_schnorrsig_s2c_opening* opening) {
size_t outputlen = 33;

VERIFY_CHECK(ctx != NULL);
ARG_CHECK(output33 != NULL);
ARG_CHECK(opening != NULL);
ARG_CHECK(secp256k1_schnorrsig_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;
}

/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
* SHA256 to SHA256("BIP0340/nonce")||SHA256("BIP0340/nonce"). */
static void secp256k1_nonce_function_bip340_sha256_tagged(secp256k1_sha256 *sha) {
Expand Down
62 changes: 62 additions & 0 deletions src/modules/schnorrsig/tests_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,67 @@ void test_schnorrsig_taproot(void) {
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1);
}

void test_s2c_opening(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_schnorrsig_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_schnorrsig_s2c_opening_serialize(ctx, output, &opening) == 0);
CHECK(ecount == 1);

/* First parsing, then serializing works */
CHECK(secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input) == 1);
CHECK(secp256k1_schnorrsig_s2c_opening_serialize(ctx, output, &opening) == 1);
CHECK(secp256k1_schnorrsig_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_schnorrsig_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_schnorrsig_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
/* Unknown bit is set */
input_tmp[0] = 8;
CHECK(secp256k1_schnorrsig_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_schnorrsig_s2c_opening_parse(ctx, &opening, input) == 1) {
CHECK(secp256k1_schnorrsig_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 run_schnorrsig_tests(void) {
int i;
run_nonce_function_bip340_tests();
Expand All @@ -900,6 +961,7 @@ void run_schnorrsig_tests(void) {
test_schnorrsig_sign_verify();
}
test_schnorrsig_taproot();
test_s2c_opening();
}

#endif

0 comments on commit a903875

Please sign in to comment.