Skip to content

Commit ed20dbd

Browse files
committed
Add and expose sign-to-contract opening with parse and serialize functions
1 parent 4debe30 commit ed20dbd

File tree

3 files changed

+136
-0
lines changed

3 files changed

+136
-0
lines changed

include/secp256k1.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,26 @@ typedef struct {
8080
unsigned char data[64];
8181
} secp256k1_ecdsa_signature;
8282

83+
/** Data structure that holds a sign-to-contract ("s2c") opening. Sign-to-contract
84+
* allows a signer to commit to some data as part of a signature. It can be used as
85+
* an Out-argument in certain signing functions.
86+
*
87+
* This structure is not opaque, but it is strongly discouraged to read or write to
88+
* it directly.
89+
*
90+
* The exact representation of data inside is implementation defined and not
91+
* guaranteed to be portable between different platforms or versions. It is however
92+
* guaranteed to be 73 bytes in size, and can be safely copied/moved.
93+
*/
94+
typedef struct {
95+
/* magic is set during initialization */
96+
unsigned char magic[8];
97+
/* Public nonce before applying the sign-to-contract commitment */
98+
secp256k1_pubkey original_pubnonce;
99+
/* Integer indicating if signing algorithm negated the nonce */
100+
unsigned char nonce_is_negated;
101+
} secp256k1_s2c_opening;
102+
83103
/** A pointer to a function to deterministically generate a nonce.
84104
*
85105
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail.
@@ -396,6 +416,37 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact(
396416
const secp256k1_ecdsa_signature* sig
397417
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
398418

419+
/** Parse a sign-to-contract opening.
420+
*
421+
* Returns: 1 if the opening was fully valid.
422+
* 0 if the opening could not be parsed or is invalid.
423+
* Args: ctx: a secp256k1 context object.
424+
* Out: opening: pointer to an opening object. If 1 is returned, it is set to a
425+
* parsed version of input. If not, its value is undefined.
426+
* In: input34: pointer to 34-byte array with a serialized opening
427+
*
428+
*/
429+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_parse(
430+
const secp256k1_context* ctx,
431+
secp256k1_s2c_opening* opening,
432+
const unsigned char *input34
433+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
434+
435+
/** Serialize a sign-to-contract opening into a byte sequence.
436+
*
437+
* Returns: 1 if the opening was successfully serialized.
438+
* 0 if the opening was not initializaed.
439+
* Args: ctx: a secp256k1 context object.
440+
* Out: output34: pointer to a 34-byte array to place the serialized opening
441+
* in.
442+
* In: opening: a pointer to an initialized `secp256k1_s2c_opening`.
443+
*/
444+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_serialize(
445+
const secp256k1_context* ctx,
446+
unsigned char *output34,
447+
const secp256k1_s2c_opening* opening
448+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
449+
399450
/** Verify an ECDSA signature.
400451
*
401452
* Returns: 1: correct signature

src/secp256k1.c

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -706,6 +706,38 @@ static int secp256k1_ec_commit_verify(const secp256k1_context* ctx, const secp25
706706
return secp256k1_gej_is_infinity(&pj);
707707
}
708708

709+
static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168;
710+
static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening) {
711+
memcpy(opening->magic, &s2c_opening_magic, sizeof(opening->magic));
712+
opening->nonce_is_negated = 0;
713+
}
714+
715+
static int secp256k1_s2c_commit_is_init(const secp256k1_s2c_opening *opening) {
716+
return memcmp(opening->magic, &s2c_opening_magic, sizeof(opening->magic)) == 0;
717+
}
718+
719+
int secp256k1_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_s2c_opening* opening, const unsigned char *input34) {
720+
VERIFY_CHECK(ctx != NULL);
721+
ARG_CHECK(opening != NULL);
722+
ARG_CHECK(input34 != NULL);
723+
724+
secp256k1_s2c_opening_init(opening);
725+
opening->nonce_is_negated = input34[0];
726+
return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &input34[1], 33);
727+
}
728+
729+
int secp256k1_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output34, const secp256k1_s2c_opening* opening) {
730+
size_t outputlen = 33;
731+
732+
VERIFY_CHECK(ctx != NULL);
733+
ARG_CHECK(output34 != NULL);
734+
ARG_CHECK(opening != NULL);
735+
ARG_CHECK(secp256k1_s2c_commit_is_init(opening));
736+
737+
output34[0] = opening->nonce_is_negated;
738+
return secp256k1_ec_pubkey_serialize(ctx, &output34[1], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED);
739+
}
740+
709741
#ifdef ENABLE_MODULE_ECDH
710742
# include "modules/ecdh/main_impl.h"
711743
#endif

src/tests.c

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4101,6 +4101,57 @@ void run_eckey_edge_case_test(void) {
41014101
secp256k1_context_set_illegal_callback(ctx, NULL, NULL);
41024102
}
41034103

4104+
4105+
void run_s2c_opening_test(void) {
4106+
int i = 0;
4107+
unsigned char output[34];
4108+
unsigned char input[34] = {
4109+
0x01,
4110+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4111+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4112+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4113+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4114+
0x02
4115+
};
4116+
secp256k1_s2c_opening opening;
4117+
size_t ecount = 0;
4118+
4119+
secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);
4120+
4121+
/* Uninitialized opening can't be serialized */
4122+
CHECK(ecount == 0);
4123+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 0);
4124+
CHECK(ecount == 1);
4125+
4126+
/* First parsing, then serializing works */
4127+
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1);
4128+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
4129+
4130+
{
4131+
/* Invalid pubkey makes parsing fail */
4132+
unsigned char input_tmp[34];
4133+
memcpy(input_tmp, input, sizeof(input_tmp));
4134+
input_tmp[33] = 0;
4135+
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
4136+
}
4137+
4138+
/* Try parsing and serializing a bunch of openings */
4139+
do {
4140+
/* This is expected to fail in about 50% of iterations because the
4141+
* points' x-coordinates are uniformly random */
4142+
if (secp256k1_s2c_opening_parse(ctx, &opening, input) == 1) {
4143+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
4144+
CHECK(memcmp(output, input, 34) == 0);
4145+
}
4146+
secp256k1_rand256(input);
4147+
/* nonce_is_negated */
4148+
input[0] = input[0] & 1;
4149+
/* oddness */
4150+
input[1] = (input[1] % 2) + 2;
4151+
i++;
4152+
} while(i < count);
4153+
}
4154+
41044155
void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) {
41054156
secp256k1_scalar nonce;
41064157
do {
@@ -5270,6 +5321,8 @@ int main(int argc, char **argv) {
52705321
/* EC key edge cases */
52715322
run_eckey_edge_case_test();
52725323

5324+
run_s2c_opening_test();
5325+
52735326
#ifdef ENABLE_MODULE_ECDH
52745327
/* ecdh tests */
52755328
run_ecdh_tests();

0 commit comments

Comments
 (0)