Skip to content

Commit 3d1c301

Browse files
jonasnickbenma
andcommitted
add schnorr sign-to-contract opening with parse/ serialize functions
Adapted from #589. Co-authored-by: Marko Bencun <[email protected]>
1 parent 6b64825 commit 3d1c301

File tree

3 files changed

+172
-0
lines changed

3 files changed

+172
-0
lines changed

include/secp256k1_schnorrsig.h

+56
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#ifndef SECP256K1_SCHNORRSIG_H
22
#define SECP256K1_SCHNORRSIG_H
33

4+
#include <stdint.h>
5+
46
#include "secp256k1.h"
57
#include "secp256k1_extrakeys.h"
68

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

18+
/** Data structure that holds a sign-to-contract ("s2c") opening information.
19+
* Sign-to-contract allows a signer to commit to some data as part of a signature. It
20+
* can be used as an Out-argument in certain signing functions.
21+
*
22+
* This structure is not opaque, but it is strongly discouraged to read or write to
23+
* it directly.
24+
*
25+
* The exact representation of data inside is implementation defined and not
26+
* guaranteed to be portable between different platforms or versions. It can
27+
* be safely copied/moved.
28+
*/
29+
typedef struct {
30+
/* magic is set during initialization */
31+
uint64_t magic;
32+
/* Public nonce before applying the sign-to-contract commitment */
33+
secp256k1_pubkey original_pubnonce;
34+
/* Byte indicating if signing algorithm negated the nonce. Alternatively when
35+
* verifying we could compute the EC commitment of original_pubnonce and the
36+
* data and negate if this would not be a valid nonce. But this would prevent
37+
* batch verification of sign-to-contract commitments. */
38+
int nonce_is_negated;
39+
} secp256k1_schnorrsig_s2c_opening;
40+
41+
/** Parse a sign-to-contract opening.
42+
*
43+
* Returns: 1 if the opening was fully valid.
44+
* 0 if the opening could not be parsed or is invalid.
45+
* Args: ctx: a secp256k1 context object.
46+
* Out: opening: pointer to an opening object. If 1 is returned, it is set to a
47+
* parsed version of input. If not, its value is undefined.
48+
* In: input33: pointer to 33-byte array with a serialized opening
49+
*
50+
*/
51+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_s2c_opening_parse(
52+
const secp256k1_context* ctx,
53+
secp256k1_schnorrsig_s2c_opening* opening,
54+
const unsigned char *input33
55+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
56+
57+
/** Serialize a sign-to-contract opening into a byte sequence.
58+
*
59+
* Returns: 1 if the opening was successfully serialized.
60+
* 0 if the opening was not initializaed.
61+
* Args: ctx: a secp256k1 context object.
62+
* Out: output33: pointer to a 33-byte array to place the serialized opening
63+
* in.
64+
* In: opening: a pointer to an initialized `secp256k1_schnorrsig_s2c_opening`.
65+
*/
66+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_schnorrsig_s2c_opening_serialize(
67+
const secp256k1_context* ctx,
68+
unsigned char *output33,
69+
const secp256k1_schnorrsig_s2c_opening* opening
70+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
71+
1672
/** A pointer to a function to deterministically generate a nonce.
1773
*
1874
* Same as secp256k1_nonce function with the exception of accepting an

src/modules/schnorrsig/main_impl.h

+54
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,60 @@
1111
#include "../../../include/secp256k1_schnorrsig.h"
1212
#include "../../hash.h"
1313

14+
static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL;
15+
16+
static void secp256k1_schnorrsig_s2c_opening_init(secp256k1_schnorrsig_s2c_opening *opening) {
17+
opening->magic = s2c_opening_magic;
18+
opening->nonce_is_negated = 0;
19+
}
20+
21+
static int secp256k1_schnorrsig_s2c_commit_is_init(const secp256k1_schnorrsig_s2c_opening *opening) {
22+
return opening->magic == s2c_opening_magic;
23+
}
24+
25+
/* s2c_opening is serialized as 33 bytes containing the compressed original pubnonce. In addition to
26+
* holding the EVEN or ODD tag, the first byte has the third bit set to 1 if the nonce was negated.
27+
* The remaining bits in the first byte are 0. */
28+
int secp256k1_schnorrsig_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_schnorrsig_s2c_opening* opening, const unsigned char *input33) {
29+
unsigned char pk_ser[33];
30+
VERIFY_CHECK(ctx != NULL);
31+
ARG_CHECK(opening != NULL);
32+
ARG_CHECK(input33 != NULL);
33+
34+
secp256k1_schnorrsig_s2c_opening_init(opening);
35+
/* Return 0 if unknown bits are set */
36+
if ((input33[0] & ~0x06) != 0) {
37+
return 0;
38+
}
39+
/* Read nonce_is_negated bit */
40+
opening->nonce_is_negated = input33[0] & (1 << 2);
41+
memcpy(pk_ser, input33, sizeof(pk_ser));
42+
/* Unset nonce_is_negated bit to allow parsing the public key */
43+
pk_ser[0] &= ~(1 << 2);
44+
return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &pk_ser[0], 33);
45+
}
46+
47+
int secp256k1_schnorrsig_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output33, const secp256k1_schnorrsig_s2c_opening* opening) {
48+
size_t outputlen = 33;
49+
50+
VERIFY_CHECK(ctx != NULL);
51+
ARG_CHECK(output33 != NULL);
52+
ARG_CHECK(opening != NULL);
53+
ARG_CHECK(secp256k1_schnorrsig_s2c_commit_is_init(opening));
54+
55+
if (!secp256k1_ec_pubkey_serialize(ctx, &output33[0], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED)) {
56+
return 0;
57+
}
58+
/* Verify that ec_pubkey_serialize only sets the first two bits of the
59+
* first byte, otherwise this function doesn't make any sense */
60+
VERIFY_CHECK(output33[0] == 0x02 || output33[0] == 0x03);
61+
if (opening->nonce_is_negated) {
62+
/* Set nonce_is_negated bit */
63+
output33[0] |= (1 << 2);
64+
}
65+
return 1;
66+
}
67+
1468
/* Initializes SHA256 with fixed midstate. This midstate was computed by applying
1569
* SHA256 to SHA256("BIP0340/nonce")||SHA256("BIP0340/nonce"). */
1670
static void secp256k1_nonce_function_bip340_sha256_tagged(secp256k1_sha256 *sha) {

src/modules/schnorrsig/tests_impl.h

+62
Original file line numberDiff line numberDiff line change
@@ -888,6 +888,67 @@ void test_schnorrsig_taproot(void) {
888888
CHECK(secp256k1_xonly_pubkey_tweak_add_check(ctx, output_pk_bytes, pk_parity, &internal_pk, tweak) == 1);
889889
}
890890

891+
void test_s2c_opening(void) {
892+
int i = 0;
893+
unsigned char output[33];
894+
/* First byte 0x06 means that nonce_is_negated and EVEN tag for the
895+
* following compressed pubkey (which is valid). */
896+
unsigned char input[33] = {
897+
0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
898+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
899+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
900+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
901+
0x02
902+
};
903+
secp256k1_schnorrsig_s2c_opening opening;
904+
size_t ecount = 0;
905+
906+
secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);
907+
908+
/* Uninitialized opening can't be serialized. Actually testing that would be
909+
* undefined behavior. Therefore we simulate it by setting the opening to 0. */
910+
memset(&opening, 0, sizeof(opening));
911+
CHECK(ecount == 0);
912+
CHECK(secp256k1_schnorrsig_s2c_opening_serialize(ctx, output, &opening) == 0);
913+
CHECK(ecount == 1);
914+
915+
/* First parsing, then serializing works */
916+
CHECK(secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input) == 1);
917+
CHECK(secp256k1_schnorrsig_s2c_opening_serialize(ctx, output, &opening) == 1);
918+
CHECK(secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input) == 1);
919+
920+
{
921+
/* Invalid pubkey makes parsing fail */
922+
unsigned char input_tmp[33];
923+
memcpy(input_tmp, input, sizeof(input_tmp));
924+
/* Pubkey oddness tag is invalid */
925+
input_tmp[0] = 0;
926+
CHECK(secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
927+
/* nonce_is_negated bit is set but pubkey oddness tag is invalid */
928+
input_tmp[0] = 5;
929+
CHECK(secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
930+
/* Unknown bit is set */
931+
input_tmp[0] = 8;
932+
CHECK(secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
933+
}
934+
935+
/* Try parsing and serializing a bunch of openings */
936+
do {
937+
/* This is expected to fail in about 50% of iterations because the
938+
* points' x-coordinates are uniformly random */
939+
if (secp256k1_schnorrsig_s2c_opening_parse(ctx, &opening, input) == 1) {
940+
CHECK(secp256k1_schnorrsig_s2c_opening_serialize(ctx, output, &opening) == 1);
941+
CHECK(memcmp(output, input, sizeof(output)) == 0);
942+
}
943+
secp256k1_testrand256(&input[1]);
944+
/* Set pubkey oddness tag to first bit of input[1] */
945+
input[0] = (input[1] & 1) + 2;
946+
/* Set nonce_is_negated bit to input[1]'s 3rd bit */
947+
input[0] |= (input[1] & (1 << 2));
948+
i++;
949+
} while(i < count);
950+
}
951+
891952
void run_schnorrsig_tests(void) {
892953
int i;
893954
run_nonce_function_bip340_tests();
@@ -900,6 +961,7 @@ void run_schnorrsig_tests(void) {
900961
test_schnorrsig_sign_verify();
901962
}
902963
test_schnorrsig_taproot();
964+
test_s2c_opening();
903965
}
904966

905967
#endif

0 commit comments

Comments
 (0)