Skip to content

Commit 64475a7

Browse files
committed
Add and expose sign-to-contract opening with parse and serialize functions
1 parent da4ed8d commit 64475a7

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

include/secp256k1.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ extern "C" {
66
#endif
77

88
#include <stddef.h>
9+
#include <stdint.h>
910

1011
/* These rules specify the order of arguments in API calls:
1112
*
@@ -81,6 +82,29 @@ typedef struct {
8182
unsigned char data[64];
8283
} secp256k1_ecdsa_signature;
8384

85+
/** Data structure that holds a sign-to-contract ("s2c") opening information.
86+
* Sign-to-contract allows a signer to commit to some data as part of a signature. It
87+
* can be used as an Out-argument in certain signing functions.
88+
*
89+
* This structure is not opaque, but it is strongly discouraged to read or write to
90+
* it directly.
91+
*
92+
* The exact representation of data inside is implementation defined and not
93+
* guaranteed to be portable between different platforms or versions. It can
94+
* be safely copied/moved.
95+
*/
96+
typedef struct {
97+
/* magic is set during initialization */
98+
uint64_t magic;
99+
/* Public nonce before applying the sign-to-contract commitment */
100+
secp256k1_pubkey original_pubnonce;
101+
/* Byte indicating if signing algorithm negated the nonce. Alternatively when
102+
* verifying we could compute the EC commitment of original_pubnonce and the
103+
* data and negate if this would not be a valid nonce. But this would prevent
104+
* batch verification of sign-to-contract commitments. */
105+
int nonce_is_negated;
106+
} secp256k1_s2c_opening;
107+
84108
/** A pointer to a function to deterministically generate a nonce.
85109
*
86110
* Returns: 1 if a nonce was successfully generated. 0 will cause signing to fail.
@@ -444,6 +468,37 @@ SECP256K1_API int secp256k1_ecdsa_signature_serialize_compact(
444468
const secp256k1_ecdsa_signature* sig
445469
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
446470

471+
/** Parse a sign-to-contract opening.
472+
*
473+
* Returns: 1 if the opening was fully valid.
474+
* 0 if the opening could not be parsed or is invalid.
475+
* Args: ctx: a secp256k1 context object.
476+
* Out: opening: pointer to an opening object. If 1 is returned, it is set to a
477+
* parsed version of input. If not, its value is undefined.
478+
* In: input34: pointer to 34-byte array with a serialized opening
479+
*
480+
*/
481+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_parse(
482+
const secp256k1_context* ctx,
483+
secp256k1_s2c_opening* opening,
484+
const unsigned char *input34
485+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
486+
487+
/** Serialize a sign-to-contract opening into a byte sequence.
488+
*
489+
* Returns: 1 if the opening was successfully serialized.
490+
* 0 if the opening was not initializaed.
491+
* Args: ctx: a secp256k1 context object.
492+
* Out: output34: pointer to a 34-byte array to place the serialized opening
493+
* in.
494+
* In: opening: a pointer to an initialized `secp256k1_s2c_opening`.
495+
*/
496+
SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_s2c_opening_serialize(
497+
const secp256k1_context* ctx,
498+
unsigned char *output34,
499+
const secp256k1_s2c_opening* opening
500+
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3);
501+
447502
/** Verify an ECDSA signature.
448503
*
449504
* Returns: 1: correct signature

src/secp256k1.c

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -788,6 +788,39 @@ static int secp256k1_ec_commit_verify(const secp256k1_context* ctx, const secp25
788788
return secp256k1_gej_is_infinity(&pj);
789789
}
790790

791+
static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL;
792+
793+
static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening) {
794+
opening->magic = s2c_opening_magic;
795+
opening->nonce_is_negated = 0;
796+
}
797+
798+
static int secp256k1_s2c_commit_is_init(const secp256k1_s2c_opening *opening) {
799+
return opening->magic == s2c_opening_magic;
800+
}
801+
802+
int secp256k1_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_s2c_opening* opening, const unsigned char *input34) {
803+
VERIFY_CHECK(ctx != NULL);
804+
ARG_CHECK(opening != NULL);
805+
ARG_CHECK(input34 != NULL);
806+
807+
secp256k1_s2c_opening_init(opening);
808+
opening->nonce_is_negated = input34[0];
809+
return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &input34[1], 33);
810+
}
811+
812+
int secp256k1_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output34, const secp256k1_s2c_opening* opening) {
813+
size_t outputlen = 33;
814+
815+
VERIFY_CHECK(ctx != NULL);
816+
ARG_CHECK(output34 != NULL);
817+
ARG_CHECK(opening != NULL);
818+
ARG_CHECK(secp256k1_s2c_commit_is_init(opening));
819+
820+
output34[0] = opening->nonce_is_negated;
821+
return secp256k1_ec_pubkey_serialize(ctx, &output34[1], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED);
822+
}
823+
791824
#ifdef ENABLE_MODULE_ECDH
792825
# include "modules/ecdh/main_impl.h"
793826
#endif

src/tests.c

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4293,6 +4293,59 @@ void run_eckey_edge_case_test(void) {
42934293
secp256k1_context_set_illegal_callback(ctx, NULL, NULL);
42944294
}
42954295

4296+
4297+
void run_s2c_opening_test(void) {
4298+
int i = 0;
4299+
unsigned char output[34];
4300+
unsigned char input[34] = {
4301+
0x01,
4302+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4303+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4304+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4305+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4306+
0x02
4307+
};
4308+
secp256k1_s2c_opening opening;
4309+
size_t ecount = 0;
4310+
4311+
secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);
4312+
4313+
/* Uninitialized opening can't be serialized. Actually testing that would be
4314+
* undefined behavior. Therefore we simulate it by setting the opening to 0. */
4315+
memset(&opening, 0, sizeof(opening));
4316+
CHECK(ecount == 0);
4317+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 0);
4318+
CHECK(ecount == 1);
4319+
4320+
/* First parsing, then serializing works */
4321+
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1);
4322+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
4323+
4324+
{
4325+
/* Invalid pubkey makes parsing fail */
4326+
unsigned char input_tmp[34];
4327+
memcpy(input_tmp, input, sizeof(input_tmp));
4328+
input_tmp[33] = 0;
4329+
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
4330+
}
4331+
4332+
/* Try parsing and serializing a bunch of openings */
4333+
do {
4334+
/* This is expected to fail in about 50% of iterations because the
4335+
* points' x-coordinates are uniformly random */
4336+
if (secp256k1_s2c_opening_parse(ctx, &opening, input) == 1) {
4337+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
4338+
CHECK(memcmp(output, input, 34) == 0);
4339+
}
4340+
secp256k1_rand256(input);
4341+
/* nonce_is_negated */
4342+
input[0] = input[0] & 1;
4343+
/* oddness */
4344+
input[1] = (input[1] % 2) + 2;
4345+
i++;
4346+
} while(i < count);
4347+
}
4348+
42964349
void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) {
42974350
secp256k1_scalar nonce;
42984351
do {
@@ -5464,6 +5517,8 @@ int main(int argc, char **argv) {
54645517
/* EC key edge cases */
54655518
run_eckey_edge_case_test();
54665519

5520+
run_s2c_opening_test();
5521+
54675522
#ifdef ENABLE_MODULE_ECDH
54685523
/* ecdh tests */
54695524
run_ecdh_tests();

0 commit comments

Comments
 (0)