Skip to content

Commit 4c0c66d

Browse files
jonasnickbenma
authored andcommitted
Add and expose sign-to-contract opening with parse and serialize functions
1 parent d81c7db commit 4c0c66d

File tree

3 files changed

+143
-0
lines changed

3 files changed

+143
-0
lines changed

include/secp256k1.h

+55
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

+33
Original file line numberDiff line numberDiff line change
@@ -764,6 +764,39 @@ static int secp256k1_ec_commit_verify(const secp256k1_context* ctx, const secp25
764764
return secp256k1_gej_is_infinity(&pj);
765765
}
766766

767+
static uint64_t s2c_opening_magic = 0x5d0520b8b7f2b168ULL;
768+
769+
static void secp256k1_s2c_opening_init(secp256k1_s2c_opening *opening) {
770+
opening->magic = s2c_opening_magic;
771+
opening->nonce_is_negated = 0;
772+
}
773+
774+
static int secp256k1_s2c_commit_is_init(const secp256k1_s2c_opening *opening) {
775+
return opening->magic == s2c_opening_magic;
776+
}
777+
778+
int secp256k1_s2c_opening_parse(const secp256k1_context* ctx, secp256k1_s2c_opening* opening, const unsigned char *input34) {
779+
VERIFY_CHECK(ctx != NULL);
780+
ARG_CHECK(opening != NULL);
781+
ARG_CHECK(input34 != NULL);
782+
783+
secp256k1_s2c_opening_init(opening);
784+
opening->nonce_is_negated = input34[0];
785+
return secp256k1_ec_pubkey_parse(ctx, &opening->original_pubnonce, &input34[1], 33);
786+
}
787+
788+
int secp256k1_s2c_opening_serialize(const secp256k1_context* ctx, unsigned char *output34, const secp256k1_s2c_opening* opening) {
789+
size_t outputlen = 33;
790+
791+
VERIFY_CHECK(ctx != NULL);
792+
ARG_CHECK(output34 != NULL);
793+
ARG_CHECK(opening != NULL);
794+
ARG_CHECK(secp256k1_s2c_commit_is_init(opening));
795+
796+
output34[0] = opening->nonce_is_negated;
797+
return secp256k1_ec_pubkey_serialize(ctx, &output34[1], &outputlen, &opening->original_pubnonce, SECP256K1_EC_COMPRESSED);
798+
}
799+
767800
#ifdef ENABLE_MODULE_ECDH
768801
# include "modules/ecdh/main_impl.h"
769802
#endif

src/tests.c

+55
Original file line numberDiff line numberDiff line change
@@ -4183,6 +4183,59 @@ void run_eckey_edge_case_test(void) {
41834183
secp256k1_context_set_illegal_callback(ctx, NULL, NULL);
41844184
}
41854185

4186+
4187+
void run_s2c_opening_test(void) {
4188+
int i = 0;
4189+
unsigned char output[34];
4190+
unsigned char input[34] = {
4191+
0x01,
4192+
0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4193+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4194+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4195+
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
4196+
0x02
4197+
};
4198+
secp256k1_s2c_opening opening;
4199+
size_t ecount = 0;
4200+
4201+
secp256k1_context_set_illegal_callback(ctx, counting_illegal_callback_fn, &ecount);
4202+
4203+
/* Uninitialized opening can't be serialized. Actually testing that would be
4204+
* undefined behavior. Therefore we simulate it by setting the opening to 0. */
4205+
memset(&opening, 0, sizeof(opening));
4206+
CHECK(ecount == 0);
4207+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 0);
4208+
CHECK(ecount == 1);
4209+
4210+
/* First parsing, then serializing works */
4211+
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input) == 1);
4212+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
4213+
4214+
{
4215+
/* Invalid pubkey makes parsing fail */
4216+
unsigned char input_tmp[34];
4217+
memcpy(input_tmp, input, sizeof(input_tmp));
4218+
input_tmp[33] = 0;
4219+
CHECK(secp256k1_s2c_opening_parse(ctx, &opening, input_tmp) == 0);
4220+
}
4221+
4222+
/* Try parsing and serializing a bunch of openings */
4223+
do {
4224+
/* This is expected to fail in about 50% of iterations because the
4225+
* points' x-coordinates are uniformly random */
4226+
if (secp256k1_s2c_opening_parse(ctx, &opening, input) == 1) {
4227+
CHECK(secp256k1_s2c_opening_serialize(ctx, output, &opening) == 1);
4228+
CHECK(memcmp(output, input, 34) == 0);
4229+
}
4230+
secp256k1_rand256(input);
4231+
/* nonce_is_negated */
4232+
input[0] = input[0] & 1;
4233+
/* oddness */
4234+
input[1] = (input[1] % 2) + 2;
4235+
i++;
4236+
} while(i < count);
4237+
}
4238+
41864239
void random_sign(secp256k1_scalar *sigr, secp256k1_scalar *sigs, const secp256k1_scalar *key, const secp256k1_scalar *msg, int *recid) {
41874240
secp256k1_scalar nonce;
41884241
do {
@@ -5350,6 +5403,8 @@ int main(int argc, char **argv) {
53505403
/* EC key edge cases */
53515404
run_eckey_edge_case_test();
53525405

5406+
run_s2c_opening_test();
5407+
53535408
#ifdef ENABLE_MODULE_ECDH
53545409
/* ecdh tests */
53555410
run_ecdh_tests();

0 commit comments

Comments
 (0)