forked from bitcoin-core/secp256k1
-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ecdsa_adaptor module #14
Open
jonasnick
wants to merge
20
commits into
master
Choose a base branch
from
ecdsa-adaptor-sigs
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 18 commits
Commits
Show all changes
20 commits
Select commit
Hold shift + click to select a range
d6c271b
add ecdsa_adaptor module
jonasnick b0c4404
Move R from adaptor_proof to adaptor_sig and fix args of _adapt
jonasnick 5988b51
Add dleq proof and dleq verify
jonasnick cf0ae97
Move dleq things in own file and add adaptor sign
jonasnick d4f796b
Add ecdsa_adaptor_sig_verify
jonasnick 9d55dfb
Add ecdsa_adaptor_adapt
jonasnick 0f11217
add ecdsa_adaptor_extract_secret
jonasnick 9fcbf6e
cleanup: stricter input validation
jonasnick 55ec42c
Cleanup a few more things
jonasnick 246eefa
Fix adaptor_sig_verify: should compare scalars and not fe's
jonasnick a9651ee
Clean up docs and reenable tests
jonasnick aac6ccb
Make ecdsa_adaptor_sig_verify compare full points instead of only x c…
jonasnick 7d97f7b
Add gen2 to dleq challenge and nonce function
jonasnick 2fa6bb8
Update TODOs
jonasnick dc18378
Update docs
jonasnick 1267354
Fix dleq tag by removing trailing NUL bytes
jonasnick 966d389
Fix not checking return value of dleq_proof in ecdsa_adaptor_sign
jonasnick 4c3884d
Fix dleq tag for challenge hash too
jonasnick 22da84c
Fix not checking the return value of secp256k1_ge_set_xo_var
jonasnick 4b8b638
Return 0 in ecdsa_adaptor_extract_secret if the sigs are unrelated
jonasnick File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
#ifndef SECP256K1_ECDSA_ADAPTOR_H | ||
#define SECP256K1_ECDSA_ADAPTOR_H | ||
|
||
#ifdef __cplusplus | ||
extern "C" { | ||
#endif | ||
|
||
/** This module implements single signer ECDSA adaptor signatures following | ||
* "One-Time Verifiably Encrypted Signatures A.K.A. Adaptor Signatures" by | ||
* Lloyd Fournier | ||
* (https://lists.linuxfoundation.org/pipermail/lightning-dev/2019-November/002316.html | ||
* and https://github.com/LLFourn/one-time-VES/blob/master/main.pdf). | ||
* | ||
* Note that at this module is currently a work in progress. It's not secure | ||
* nor stable. Let me repeat: IT IS EXTREMELY DANGEROUS AND RECKLESS TO USE | ||
* THIS MODULE IN PRODUCTION. DON'T! | ||
* | ||
* This module passes a rudimentary test suite. But there are some things left | ||
* TODO: | ||
* - add API tests | ||
* - add tests for the various overflow conditions | ||
* - refactor adaptor verify to reuse code from secp256k1_ecdsa_verify() | ||
* - test ecdsa_adaptor_sig_verify() more systematically. This is the most | ||
* crucial function in this module. If it passes, we need to be sure that | ||
* it is possible to compute the adaptor secret from the final ecdsa | ||
* signature. | ||
* - add ecdsa_adaptor_sign(), ecdsa_adaptor_adapt() and | ||
* ecdsa_adaptor_extract_secret() to valgrind_ctime_test.c | ||
* - allow using your own nonce function (noncefp, noncedata, synthetic | ||
* nonces) | ||
* - test module in travis | ||
* - add comments to ease review | ||
*/ | ||
|
||
/** Adaptor sign ("EncSign") | ||
* | ||
* Creates an adaptor signature along with a proof to verify the adaptor | ||
* signature. | ||
* | ||
* Returns: 1 on success, 0 on failure | ||
* Args: ctx: a secp256k1 context object, initialized for signing | ||
* (cannot be NULL) | ||
* Out: adaptor_sig65: pointer to 65 byte to store the returned signature | ||
* (cannot be NULL) | ||
* adaptor_proof97: pointer to 97 byte to store the adaptor proof (cannot be | ||
* NULL) | ||
* In: seckey32: pointer to 32 byte secret key corresponding to the | ||
* pubkey (cannot be NULL) | ||
* adaptor: pointer to the adaptor point (cannot be NULL) | ||
* msg32: pointer to the 32-byte message to sign (cannot be NULL) | ||
*/ | ||
SECP256K1_API int secp256k1_ecdsa_adaptor_sign( | ||
const secp256k1_context* ctx, | ||
unsigned char *adaptor_sig65, | ||
unsigned char *adaptor_proof97, | ||
unsigned char *seckey32, | ||
const secp256k1_pubkey *adaptor, | ||
const unsigned char *msg32 | ||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); | ||
|
||
/** Adaptor verify ("EncVrfy") | ||
* | ||
* Verifies that the adaptor secret can be extracted from the adaptor signature | ||
* and the completed ECDSA signature. | ||
* | ||
* Returns: 1 on success, 0 on failure | ||
* Args: ctx: a secp256k1 context object, initialized for verification | ||
* (cannot be NULL) | ||
* In: adaptor_sig65: pointer to 65-byte signature to verify (cannot be NULL) | ||
* pubkey: pointer to the public key (cannot be NULL) | ||
* msg32: pointer to the 32-byte message (cannot be NULL) | ||
* adaptor: pointer to the adaptor point (cannot be NULL) | ||
* adaptor_proof97: pointer to 97-byte adaptor proof (cannot be NULL) | ||
*/ | ||
SECP256K1_API int secp256k1_ecdsa_adaptor_sig_verify( | ||
const secp256k1_context* ctx, | ||
const unsigned char *adaptor_sig65, | ||
const secp256k1_pubkey *pubkey, | ||
const unsigned char *msg32, | ||
const secp256k1_pubkey *adaptor, | ||
const unsigned char *adaptor_proof97 | ||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); | ||
|
||
/** Adapt signature ("DecSig") | ||
* | ||
* Creates an ECDSA signature from an adaptor signature and an adaptor secret. | ||
* | ||
* Returns: 1 on success, 0 on failure | ||
* Args: ctx: a secp256k1 context object (cannot be NULL) | ||
* Out: sig: pointer to the ecdsa signature to create (cannot | ||
* be NULL) | ||
* In: adaptor_secret32: pointer to 32-byte byte adaptor secret of the adaptor | ||
* point (cannot be NULL) | ||
* adaptor_sig65: pointer to 65-byte byte adaptor sig (cannot be NULL) | ||
*/ | ||
SECP256K1_API int secp256k1_ecdsa_adaptor_adapt( | ||
const secp256k1_context* ctx, | ||
secp256k1_ecdsa_signature *sig, | ||
const unsigned char *adaptor_secret32, | ||
const unsigned char *adaptor_sig65 | ||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); | ||
|
||
/** Adaptor extract ("Rec") | ||
* | ||
* Extracts the adaptor secret from the complete signature and the adaptor | ||
* signature. | ||
* | ||
* Returns: 1 on success, 0 on failure | ||
* Args: ctx: a secp256k1 context object, initialized for signing | ||
* (cannot be NULL) | ||
* Out: adaptor_secret32: pointer to 32-byte adaptor secret of the adaptor point | ||
* (cannot be NULL) | ||
* In: sig: pointer to ecdsa signature to extract the adaptor_secret | ||
* from (cannot be NULL) | ||
* adaptor_sig: pointer to adaptor sig to extract the adaptor_secret | ||
* from (cannot be NULL) | ||
* adaptor: pointer to the adaptor point (cannot be NULL) | ||
*/ | ||
SECP256K1_API int secp256k1_ecdsa_adaptor_extract_secret( | ||
const secp256k1_context* ctx, | ||
unsigned char *adaptor_secret32, | ||
const secp256k1_ecdsa_signature *sig, | ||
const unsigned char *adaptor_sig65, | ||
const secp256k1_pubkey *adaptor | ||
) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); | ||
|
||
#ifdef __cplusplus | ||
} | ||
#endif | ||
|
||
#endif /* SECP256K1_ADAPTOR_H */ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
include_HEADERS += include/secp256k1_ecdsa_adaptor.h | ||
noinst_HEADERS += src/modules/ecdsa_adaptor/main_impl.h | ||
noinst_HEADERS += src/modules/ecdsa_adaptor/dleq_impl.h | ||
noinst_HEADERS += src/modules/ecdsa_adaptor/tests_impl.h |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
#ifndef _SECP256K1_DLEQ_IMPL_H_ | ||
#define _SECP256K1_DLEQ_IMPL_H_ | ||
|
||
/* Remove terminating NUL bytes */ | ||
static int algo16_len(const unsigned char *algo16) { | ||
int algo16_len = 16; | ||
|
||
/* Remove terminating null bytes */ | ||
while (algo16_len > 0 && !algo16[algo16_len - 1]) { | ||
algo16_len--; | ||
} | ||
return algo16_len; | ||
} | ||
|
||
/* Modified bip340 nonce function */ | ||
static int nonce_function_dleq(unsigned char *nonce32, const unsigned char *msg32, const unsigned char *key32, const unsigned char *algo16) { | ||
secp256k1_sha256 sha; | ||
|
||
if (algo16 == NULL) { | ||
return 0; | ||
} | ||
secp256k1_sha256_initialize_tagged(&sha, algo16, algo16_len(algo16)); | ||
secp256k1_sha256_write(&sha, key32, 32); | ||
secp256k1_sha256_write(&sha, msg32, 32); | ||
secp256k1_sha256_finalize(&sha, nonce32); | ||
return 1; | ||
} | ||
|
||
static void secp256k1_dleq_serialize_point(unsigned char *buf33, const secp256k1_ge *p) { | ||
secp256k1_fe x = p->x; | ||
secp256k1_fe y = p->y; | ||
|
||
secp256k1_fe_normalize(&y); | ||
buf33[0] = secp256k1_fe_is_odd(&y); | ||
secp256k1_fe_normalize(&x); | ||
secp256k1_fe_get_b32(&buf33[1], &x); | ||
} | ||
|
||
static int secp256k1_dleq_deserialize_point(secp256k1_ge *p, const unsigned char *buf33) { | ||
secp256k1_fe x; | ||
|
||
if (!secp256k1_fe_set_b32(&x, &buf33[1])) { | ||
return 0; | ||
} | ||
if (buf33[0] > 1) { | ||
return 0; | ||
} | ||
secp256k1_ge_set_xo_var(p, &x, buf33[0]); | ||
return 1; | ||
} | ||
|
||
/* TODO: Remove these debuggin functions */ | ||
static void print_buf(const unsigned char *buf, size_t n) { | ||
size_t i; | ||
for (i = 0; i < n; i++) { | ||
printf("%02X", buf[i]); | ||
} | ||
printf("\n"); | ||
} | ||
static void print_scalar(const secp256k1_scalar *x) { | ||
unsigned char buf32[32]; | ||
secp256k1_scalar_get_b32(buf32, x); | ||
print_buf(buf32, 32); | ||
} | ||
|
||
static void print_ge(const secp256k1_ge *p) { | ||
unsigned char buf33[33]; | ||
secp256k1_dleq_serialize_point(buf33, p); | ||
print_buf(buf33, 33); | ||
} | ||
|
||
static void secp256k1_dleq_hash_point(secp256k1_sha256 *sha, const secp256k1_ge *p) { | ||
unsigned char buf33[33]; | ||
secp256k1_dleq_serialize_point(buf33, p); | ||
secp256k1_sha256_write(sha, buf33, 33); | ||
} | ||
|
||
static void secp256k1_dleq_challenge_hash(secp256k1_scalar *e, const unsigned char *algo16, const secp256k1_ge *gen2, const secp256k1_ge *r1, const secp256k1_ge *r2, const secp256k1_ge *p1, const secp256k1_ge *p2) { | ||
secp256k1_sha256 sha; | ||
unsigned char buf32[32]; | ||
|
||
secp256k1_sha256_initialize_tagged(&sha, algo16, algo16_len(algo16)); | ||
secp256k1_dleq_hash_point(&sha, gen2); | ||
secp256k1_dleq_hash_point(&sha, r1); | ||
secp256k1_dleq_hash_point(&sha, r2); | ||
secp256k1_dleq_hash_point(&sha, p1); | ||
secp256k1_dleq_hash_point(&sha, p2); | ||
secp256k1_sha256_finalize(&sha, buf32); | ||
|
||
secp256k1_scalar_set_b32(e, buf32, NULL); | ||
} | ||
|
||
/* p1 = x*G, p2 = x*gen2, constant time */ | ||
static void secp256k1_dleq_pair(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_ge *p1, secp256k1_ge *p2, const secp256k1_scalar *sk, const secp256k1_ge *gen2) { | ||
secp256k1_gej p1j, p2j; | ||
secp256k1_ecmult_gen(ecmult_gen_ctx, &p1j, sk); | ||
secp256k1_ge_set_gej(p1, &p1j); | ||
secp256k1_ecmult_const(&p2j, gen2, sk, 256); | ||
secp256k1_ge_set_gej(p2, &p2j); | ||
} | ||
|
||
/* TODO: allow signing a message by including it in the challenge hash */ | ||
static int secp256k1_dleq_proof(const secp256k1_ecmult_gen_context *ecmult_gen_ctx, secp256k1_scalar *s, secp256k1_scalar *e, const unsigned char *algo16, const secp256k1_scalar *sk, const secp256k1_ge *gen2) { | ||
unsigned char nonce32[32]; | ||
unsigned char key32[32]; | ||
secp256k1_ge p1, p2; | ||
secp256k1_sha256 sha; | ||
secp256k1_gej r1j, r2j; | ||
secp256k1_ge r1, r2; | ||
unsigned char buf32[32]; | ||
secp256k1_scalar k; | ||
|
||
secp256k1_dleq_pair(ecmult_gen_ctx, &p1, &p2, sk, gen2); | ||
|
||
/* Everything that goes into the challenge hash must go into the nonce as well... */ | ||
secp256k1_sha256_initialize(&sha); | ||
secp256k1_dleq_hash_point(&sha, gen2); | ||
secp256k1_dleq_hash_point(&sha, &p1); | ||
secp256k1_dleq_hash_point(&sha, &p2); | ||
secp256k1_sha256_finalize(&sha, buf32); | ||
secp256k1_scalar_get_b32(key32, sk); | ||
if (!nonce_function_dleq(nonce32, buf32, key32, algo16)) { | ||
return 0; | ||
} | ||
secp256k1_scalar_set_b32(&k, nonce32, NULL); | ||
if (secp256k1_scalar_is_zero(&k)) { | ||
return 0; | ||
} | ||
|
||
secp256k1_ecmult_gen(ecmult_gen_ctx, &r1j, &k); | ||
secp256k1_ge_set_gej(&r1, &r1j); | ||
secp256k1_ecmult_const(&r2j, gen2, &k, 256); | ||
secp256k1_ge_set_gej(&r2, &r2j); | ||
|
||
secp256k1_dleq_challenge_hash(e, algo16, gen2, &r1, &r2, &p1, &p2); | ||
secp256k1_scalar_mul(s, e, sk); | ||
secp256k1_scalar_add(s, s, &k); | ||
|
||
secp256k1_scalar_clear(&k); | ||
return 1; | ||
} | ||
|
||
static int secp256k1_dleq_verify(const secp256k1_ecmult_context *ecmult_ctx, const unsigned char *algo16, const secp256k1_scalar *s, const secp256k1_scalar *e, const secp256k1_ge *p1, const secp256k1_ge *gen2, const secp256k1_ge *p2) { | ||
secp256k1_scalar e_neg; | ||
secp256k1_scalar e_expected; | ||
secp256k1_gej gen2j; | ||
secp256k1_gej p1j, p2j; | ||
secp256k1_gej r1j, r2j; | ||
secp256k1_ge r1, r2; | ||
secp256k1_gej tmpj; | ||
|
||
secp256k1_gej_set_ge(&p1j, p1); | ||
secp256k1_gej_set_ge(&p2j, p2); | ||
|
||
secp256k1_scalar_negate(&e_neg, e); | ||
/* R1 = s*G - e*P1 */ | ||
secp256k1_ecmult(ecmult_ctx, &r1j, &p1j, &e_neg, s); | ||
/* R2 = s*gen2 - e*P2 */ | ||
secp256k1_ecmult(ecmult_ctx, &tmpj, &p2j, &e_neg, &secp256k1_scalar_zero); | ||
secp256k1_gej_set_ge(&gen2j, gen2); | ||
secp256k1_ecmult(ecmult_ctx, &r2j, &gen2j, s, &secp256k1_scalar_zero); | ||
secp256k1_gej_add_var(&r2j, &r2j, &tmpj, NULL); | ||
|
||
secp256k1_ge_set_gej(&r1, &r1j); | ||
secp256k1_ge_set_gej(&r2, &r2j); | ||
secp256k1_dleq_challenge_hash(&e_expected, algo16, gen2, &r1, &r2, p1, p2); | ||
|
||
secp256k1_scalar_add(&e_expected, &e_expected, &e_neg); | ||
return secp256k1_scalar_is_zero(&e_expected); | ||
} | ||
|
||
#endif /* _SECP256K1_DLEQ_IMPL_H_ */ |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
secp256k1_ge_set_xo_var
can fail https://github.com/bitcoin-core/secp256k1/blob/6034a04fb1afe7d78dd367ec719d3ced9db2b05e/src/group_impl.h#L234 I think you should check the return.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Absolutely, great catch. Pushed fix. There could be more issues like that because this PR doesn't include a complete test suite yet (as mentioned in the TODO list of
secp256k1_ecdsa_adaptor.h
.