Skip to content
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 an experimental schnorr signature adaptor module #268

Open
wants to merge 8 commits into
base: master
Choose a base branch
from

Conversation

ZhePang
Copy link

@ZhePang ZhePang commented Aug 19, 2023

Overview

This PR adds support for Schnorr Adaptor signatures. It is based on the work of Schnorr Signature by @jonasnick and @apoelstra, and the discussion #191.

Schnorr Adaptor Signatures

This implementation is mainly based on the idea of adaptor signatures in BIP-340 specification. It refers to the design and implementation of ECDSA adaptor signatures and MuSig2 adaptor signature.

Instead of having a verification algorithm for the adaptor signature, this module uses extract_t which extracts the adaptor point from the adaptor signature. (graph for complete atomic swap process)

Python

A Python specification of Schnorr Adaptor signatures has also been implemented, which generates all test vectors for the module.

@ssantos21
Copy link

Concept ACK.

It might be interesting to include a examples/adaptor.c file.

@instagibbs
Copy link
Contributor

It might be interesting to include a examples/adaptor.c file.

this would be great :)

@ZhePang
Copy link
Author

ZhePang commented Aug 24, 2023

@ssantos21 @instagibbs Thanks for the advice! I'll for sure add the example code for Schnorr Adaptor.

@ZhePang
Copy link
Author

ZhePang commented Aug 24, 2023

This pull request is still under review and most likely will have some modifications. Feel free to give me more suggestions and advice!

@siv2r
Copy link
Contributor

siv2r commented Aug 27, 2023

The current const time test is failing because:

  • invalid compressed adaptor point value used (the pt doesn't lie on the secp curve)
  • pre_sig doesn’t declassify R, and T before using them is gej_add_ge_var

I have fixed these issues in this commit: siv2r@ad1cc6e. Feel free to cherry-pick it using git or simply copy the changes :)

The commit also adds const-time tests for the adapt, and extract_adaptor APIs.

@instagibbs
Copy link
Contributor

Pushed an example file, in the pattern of proposed LN PTLC usage: https://github.com/instagibbs/secp256k1-zkp/tree/schnorr-adaptor-signature-example

feel free to cherry-pick as well

include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Show resolved Hide resolved
ci/cirrus.sh Outdated Show resolved Hide resolved
@jonasnick
Copy link
Contributor

We've just merged #270 which means that this PR can be rebased to fix CI. Note that rebasing may require some additional changes that are documented in #271. Let me know if you have any questions.

ZhePang added 3 commits October 24, 2023 14:27
add schnorr adaptor nonce function that follows BIP-340 and also accepts the adaptor point as input

add presign, extract_t, adapt, and extract_adaptor APIs

add tests for schnorr adaptor nonce functions and APIs
added new hash tags specifically for schnorr adaptor
Copy link
Contributor

@jonasnick jonasnick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CI tasks fail because .github/workflows/ci.yml does not contain a definition of SCHNORRADAPTOR. Just search for BPPP and add a definition for SCHNORRADAPTOR right next to it that defines SCHNORRADAPTOR to the same value as BPPP ('yes' or 'no').

ci/ci.sh Show resolved Hide resolved
src/modules/schnorr_adaptor/tests_impl.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
Copy link

@remix7531 remix7531 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Over all, what I found challenging using this new feature is the naming of function arguments and the corresponding comments. It led to confusion whether a function takes a pre signature or the Schnorr signature (sig64 vs sig65). Same goes for adaptor point (t33) vs. adaptor (t32)

sig65: Should always named the same. Either "serialized adaptor signature" or "adaptor signature", never just "serialized signature".

include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
@Tibo-lg
Copy link

Tibo-lg commented Dec 6, 2023

I was playing around with this, and it's great! Though one issue I encountered is that the schnorr_adaptor_extract_adaptor will sometimes lead to a call to the illegal_callback for invalid adaptor signatures when the extracted public key is invalid (which leads to a panic at the rust layer). This makes is not a very good method for verifying adaptor signatures.

@ZhePang
Copy link
Author

ZhePang commented Jan 2, 2024

I was playing around with this, and it's great! Though one issue I encountered is that the schnorr_adaptor_extract_adaptor will sometimes lead to a call to the illegal_callback for invalid adaptor signatures when the extracted public key is invalid (which leads to a panic at the rust layer). This makes is not a very good method for verifying adaptor signatures.

Hi @Tibo-lg , we expect users to call schnorr_adaptor_extract (was extract_t) before schnorr_adaptor_extract_sec (was schnorr_adaptor_extract_adaptor) so in this way the adaptor signature passed to 'extract_sec' is valid and would not raise illegal_callback. We'll put that in the documentation, thanks for mentioning it up!

@jonasnick
Copy link
Contributor

Hi @Tibo-lg , we expect users to call schnorr_adaptor_extract (was extract_t) before schnorr_adaptor_extract_sec (was schnorr_adaptor_extract_adaptor) so in this way the adaptor signature passed to 'extract_sec' is valid and would not raise illegal_callback. We'll put that in the documentation, thanks for mentioning it up!

Actually we don't. Sorry, I was wrong when we talked about this. Typically, the party that created the adaptor signature would call extract_sec and therefore they know that they created the first byte correctly. But since this cannot be guaranteed in a proper type safe language like rust, I'd prefer if we'd remove the ARG_CHECK and just returned 0 instead.

Copy link
Contributor

@jonasnick jonasnick left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've just completed a review of your code and noticed a few areas that could benefit from refinement. I did not find any serious issues. It would be good if we could add another test vector as mentioned in the review comments. As promised, I'll also follow-up with an attempt at a module level documentation.

To further enhance your code's readability, you may want to tweak some of the code comments in the presign, extract, adapt, and extract_sec functions.

include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
include/secp256k1_schnorr_adaptor.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/main_impl.h Outdated Show resolved Hide resolved
@jonasnick
Copy link
Contributor

Here's my suggestion for a module level documentation (to be put at the top of include/secp256k1_schnorr_adaptor.h):

/** This module provides an experimental implementation of a Schnorr adaptor
 *  signature protocol variant.
 *
 *  The test vectors have been generated and cross-verified using a Python
 *  implementation of this adaptor signature variant available at [0].
 *
 *  The protocol involves two parties, Alice and Bob. The general sequence of
 *  their interaction is as follows:
 *  1. Alice calls the `schnorr_adaptor_presign` function for an adaptor point T
 *  and sends the pre-signature to Bob.
 *  2. Bob extracts the adaptor point T from the pre-signature using
 *  `schnorr_adaptor_extract`.
 *  3. Bob provides the pre-signature and the discrete logarithm of T to
 *  `schnorr_adaptor_adapt` which outputs a valid BIP 340 Schnorr signature.
 *  4. Alice extracts the discrete logarithm of T from the pre-signature and the
 *  BIP 340 signature using `schnorr_adaptor_extract_sec`.
 *
 *  In contrast to common descriptions of adaptor signature protocols, this
 *  module does not provide a verification algorithm for pre-signatures.
 *  Instead, `schnorr_adaptor_extract` returns the adaptor point encoded by a
 *  pre-signature, reducing communication cost. If a verification function for
 *  pre-signatures is needed, it can be easily simulated with
 *  `schnorr_adaptor_extract`.
 *
 *  Assuming that BIP 340 Schnorr signatures satisfy strong unforgeability under
 *  chosen message attack, the Schnorr adaptor signature scheme fulfills the
 *  following properties as formalized by [1].
 *
 *  - Witness extractability:
 *    If Alice
 *      1. creates a pre-signature with `schnorr_adaptor_presign` for message m
 *         and adaptor point T and
 *      2. receives a Schnorr signature for message m that she hasn't created
 *         herself,
 *    then Alice is able to obtain the discrete logarithm of T with
 *    `schnorr_adaptor_extract_sec`.
 *
 *  - Pre-signature adaptability:
 *    If Bob
 *      1. receives a pre-signature and extracts an adaptor point T using
 *         `schnorr_adaptor_extract`, and
 *      2. obtains the discrete logarithm of the adaptor point T
 *    Then then Bob is able to adapt the received pre-signature to a valid BIP
 *    340 Schnorr signature using `schnorr_adaptor_adapt`.
 *
 *  - Existential Unforgeability:
 *    Bob is not able to create a BIP 340 signature from a pre-signature for
 *    adaptor T without knowing the discrete logarithm of T.
 *
 *  - Pre-signature existiential unforgeability:
 *    Only Alice can create a pre-signature for her public key.
 *
 *  [0] https://github.com/ZhePang/Python_Specification_for_Schnorr_Adaptor
 *  [1] https://eprint.iacr.org/2020/476.pdf
 */

*/
SECP256K1_API const secp256k1_adaptor_nonce_function_hardened secp256k1_adaptor_nonce_function_bip340;

/** Create a Schnorr adaptor signature.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would it make sense to replace the term "adaptor signature" with "pre-signature" to be consistent with the literature (see #268 (comment))?


const secp256k1_adaptor_nonce_function_hardened secp256k1_adaptor_nonce_function_bip340 = adaptor_nonce_function_bip340;

static int secp256k1_schnorr_adaptor_presign_internal(const secp256k1_context *ctx, unsigned char *presig65, const unsigned char *msg32, const secp256k1_keypair *keypair, secp256k1_adaptor_nonce_function_hardened noncefp, const unsigned char *adaptor, void *ndata) {
Copy link
Contributor

@siv2r siv2r Jan 9, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
static int secp256k1_schnorr_adaptor_presign_internal(const secp256k1_context *ctx, unsigned char *presig65, const unsigned char *msg32, const secp256k1_keypair *keypair, secp256k1_adaptor_nonce_function_hardened noncefp, const unsigned char *adaptor, void *ndata) {
static int secp256k1_schnorr_adaptor_presign_internal(const secp256k1_context *ctx, unsigned char *presig65, const unsigned char *msg32, const secp256k1_keypair *keypair, secp256k1_adaptor_nonce_function_hardened noncefp, const secp256k1_pubkey *adaptor, void *ndata) {

The current design allows the user to input an invalid adaptor point. By making this arg as secp256k1_pubkey, we can catch an invalid 33-byte adaptor point during secp256k1_ec_pubkey_parse.

secp256k1_ecdsa_adaptor_encrypt API does the same.

Copy link
Contributor

@siv2r siv2r Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am unsure if the change is necessary since we parse the adaptor point using secp256k1_ec_pubkey_parse inside the presign function anyway.

Copy link
Contributor

@siv2r siv2r Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The essence of this modification is to make a design choice about serialization/deserialization.

  • Option 1: user handles the ser/de-ser of the adaptor point
    • we receive the adaptor point as secp256k1_pubkey (in presign & extract_adaptor)
  • Option 2: adaptor interface handles the ser/de-ser of the adaptor point
    • we receive the adaptor point as unsigned_char * (in presign & extract_adaptor)

Leaving the ser/de-ser to the user makes more sense to me since we already do this for the pubkey point. What’s the libsecp norm here?

Copy link
Contributor

@siv2r siv2r Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized there is already a comment regarding this: #268 (comment). We are going with Option 1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'd say the libsecp norm is to allow avoiding unnecessary costly recomputations such as pubkey deserialization.

src/modules/schnorr_adaptor/tests_impl.h Outdated Show resolved Hide resolved
/* NULL algo is disallowed */
CHECK(adaptor_nonce_function_bip340(nonce, msg, key, t, pk, NULL, 0, NULL) == 0);
CHECK(adaptor_nonce_function_bip340(nonce, msg, key, t, pk, algo, algolen, NULL) == 1);
/* Other algo is fine */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some additional edge-case tests that could be added here.

/* Empty algo is fine */
memset(algo, 0x00, algolen);
CHECK(adaptor_nonce_function_bip340(nonce, msg, key, t, pk, algo, algolen, NULL) == 1);
/* Max algo is fine */
memset(algo, 0xFF, algolen);
CHECK(adaptor_nonce_function_bip340(nonce, msg, key, t, pk, algo, algolen, NULL) == 1);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this test significantly increases test coverage because the nonce function does relatively little with algo. If we test algo = 0 an algo 0xff...f, why not test the same for the other nonce function arguments?

Copy link
Contributor

@siv2r siv2r Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not test the same for the other nonce function arguments?

I didn't consider this case. I suggested those tests because the ECDSA adaptor had them.

I take back my suggestion. The adaptor_nonce_function_bip340 function passes its arguments to the hash function without checking their values (even their validity), except for algo and aux. Thus, a min-max test for each args seems unnecessary.

Copy link
Contributor

@siv2r siv2r Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add a test for a non-null aux_rand argument? The current tests don't seem to hit the if (data != NULL) branch in adaptor_nonce_function_bip340.

src/modules/schnorr_adaptor/tests_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/tests_impl.h Outdated Show resolved Hide resolved
src/modules/schnorr_adaptor/tests_impl.h Outdated Show resolved Hide resolved
CHECK_ILLEGAL(CTX, secp256k1_schnorr_adaptor_extract(CTX, t2, sig, msg, NULL));
CHECK_ILLEGAL(CTX, secp256k1_schnorr_adaptor_extract(CTX, t2, sig, msg, &zero_pk));

CHECK(secp256k1_schnorr_adaptor_adapt(CTX, sig64, sig, secadaptor) == 1);
Copy link
Contributor

@siv2r siv2r Jan 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: we can check this sig64 result using schnorrsig_verify after this line.

Copy link
Contributor

@siv2r siv2r Jan 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have a use-case test. For example,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, similar to the "correctness" suggestion here: #268 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants