-
Notifications
You must be signed in to change notification settings - Fork 226
Add ECDSA sign-to-contract module #111
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
Changes from all commits
826bd04
8e46cac
290dee5
396b558
47efb5e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,234 @@ | ||
| #ifndef SECP256K1_ECDSA_S2C_H | ||
| #define SECP256K1_ECDSA_S2C_H | ||
|
|
||
| #include "secp256k1.h" | ||
|
|
||
| /** This module implements the sign-to-contract scheme for ECDSA signatures, as | ||
| * well as the "ECDSA Anti-Klepto Protocol" that is based on sign-to-contract | ||
| * and is specified further down. The sign-to-contract scheme allows creating a | ||
| * signature that also commits to some data. This works by offsetting the public | ||
| * nonce point of the signature R by hash(R, data)*G where G is the secp256k1 | ||
| * group generator. | ||
| */ | ||
|
|
||
| #ifdef __cplusplus | ||
| extern "C" { | ||
| #endif | ||
|
|
||
| /** Data structure that holds a sign-to-contract ("s2c") opening information. | ||
| * Sign-to-contract allows a signer to commit to some data as part of a signature. It | ||
| * can be used as an Out-argument in certain signing functions. | ||
| * | ||
| * The exact representation of data inside is implementation defined and not | ||
| * guaranteed to be portable between different platforms or versions. It is | ||
| * however guaranteed to be 64 bytes in size, and can be safely copied/moved. | ||
| * If you need to convert to a format suitable for storage, transmission, or | ||
| * comparison, use secp256k1_ecdsa_s2c_opening_serialize and secp256k1_ecdsa_s2c_opening_parse. | ||
| */ | ||
| typedef struct { | ||
| unsigned char data[64]; | ||
| } secp256k1_ecdsa_s2c_opening; | ||
|
|
||
| /** Parse a sign-to-contract opening. | ||
| * | ||
| * Returns: 1 if the opening could be parsed | ||
| * 0 if the opening could not be parsed | ||
| * Args: ctx: a secp256k1 context object. | ||
| * Out: opening: pointer to an opening object. If 1 is returned, it is set to a | ||
| * parsed version of input. If not, its value is unspecified. | ||
| * In: input33: pointer to 33-byte array with a serialized opening | ||
| * | ||
| */ | ||
| SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_parse( | ||
| const secp256k1_context* ctx, | ||
| secp256k1_ecdsa_s2c_opening* opening, | ||
| const unsigned char* input33 | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); | ||
|
|
||
| /** Serialize a sign-to-contract opening into a byte sequence. | ||
| * | ||
| * Returns: 1 if the opening was successfully serialized. | ||
| * 0 if the opening could not be serialized | ||
| * Args: ctx: a secp256k1 context object | ||
| * Out: output33: pointer to a 33-byte array to place the serialized opening in | ||
| * In: opening: a pointer to an initialized `secp256k1_ecdsa_s2c_opening` | ||
| */ | ||
| SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_opening_serialize( | ||
| const secp256k1_context* ctx, | ||
| unsigned char* output33, | ||
| const secp256k1_ecdsa_s2c_opening* opening | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); | ||
|
|
||
| /** Same as secp256k1_ecdsa_sign, but s2c_data32 is committed to inside the nonce | ||
| * | ||
| * Returns: 1: signature created | ||
| * 0: the nonce generation function failed, or the private key was invalid. | ||
| * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) | ||
| * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) | ||
| * s2c_opening: if non-NULL, pointer to an secp256k1_ecdsa_s2c_opening structure to populate | ||
| * In: msg32: the 32-byte message hash being signed (cannot be NULL) | ||
| * seckey: pointer to a 32-byte secret key (cannot be NULL) | ||
| * s2c_data32: pointer to a 32-byte data to commit to in the nonce (cannot be NULL) | ||
| */ | ||
| SECP256K1_API int secp256k1_ecdsa_s2c_sign( | ||
| const secp256k1_context* ctx, | ||
| secp256k1_ecdsa_signature* sig, | ||
| secp256k1_ecdsa_s2c_opening* s2c_opening, | ||
| const unsigned char* msg32, | ||
| const unsigned char* seckey, | ||
| const unsigned char* s2c_data32 | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); | ||
|
|
||
| /** Verify a sign-to-contract commitment. | ||
| * | ||
| * Returns: 1: the signature contains a commitment to data32 (though it does | ||
| * not necessarily need to be a valid siganture!) | ||
| * 0: incorrect opening | ||
| * Args: ctx: a secp256k1 context object, initialized for verification. | ||
| * In: sig: the signature containing the sign-to-contract commitment (cannot be NULL) | ||
| * data32: the 32-byte data that was committed to (cannot be NULL) | ||
| * opening: pointer to the opening created during signing (cannot be NULL) | ||
| */ | ||
| SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_ecdsa_s2c_verify_commit( | ||
| const secp256k1_context* ctx, | ||
| const secp256k1_ecdsa_signature *sig, | ||
| const unsigned char *data32, | ||
| const secp256k1_ecdsa_s2c_opening *opening | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4); | ||
|
|
||
|
|
||
| /** ECDSA Anti-Klepto Protocol | ||
| * | ||
| * The ecdsa_anti_klepto_* functions can be used to prevent a signing device from | ||
| * exfiltrating the secret signing keys through biased signature nonces. The general | ||
| * idea is that a host provides additional randomness to the signing device client | ||
| * and the client commits to the randomness in the nonce using sign-to-contract. | ||
|
jonasnick marked this conversation as resolved.
|
||
| * | ||
| * The following scheme is described by Stepan Snigirev here: | ||
| * https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-February/017655.html | ||
| * and by Pieter Wuille (as "Scheme 6") here: | ||
| * https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2020-March/017667.html | ||
| * | ||
| * In order to ensure the host cannot trick the signing device into revealing its | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It worries me that this is a mix of protocol description and usage instructions. I think it will be easier for the user to have clear instructions with just what functions must be called in which order and on what device and what messages are transferred. And we can still have a protocol description. Host:
Singing device:
Now that I wrote that, this will ideally be example code. But we could do this in another PR instead if that's easier.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Let's do it in another PR. I think it's reasonable to have a protocol description which includes which API functions correspond to what steps. |
||
| * keys, or the signing device to bias the nonce despite the host's contributions, | ||
| * the host and client must engage in a commit-reveal protocol as follows: | ||
| * 1. The host draws randomness `rho` and computes a sha256 commitment to it using | ||
| * `secp256k1_ecdsa_anti_klepto_host_commit`. It sends this to the signing device. | ||
| * 2. The signing device computes a public nonce `R` using the host's commitment | ||
| * as auxiliary randomness, using `secp256k1_ecdsa_anti_klepto_signer_commit`. | ||
| * The signing device sends the resulting `R` to the host as a s2c_opening. | ||
| * | ||
| * If, at any point from this step onward, the hardware device fails, it is | ||
| * okay to restart the protocol using **exactly the same `rho`** and checking | ||
| * that the hardware device proposes **exactly the same** `R`. Otherwise, the | ||
| * hardware device may be selectively aborting and thereby biasing the set of | ||
| * nonces that are used in actual signatures. | ||
| * | ||
| * It takes many (>100) such aborts before there is a plausible attack, given | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Micro-nit: I'd say it takes about a 100 such aborts not more. To communicate 200 bits you need no more than 100 aborts on average and the remaining 56 bits are somewhat possible to brute-force. |
||
| * current knowledge in 2020. However such aborts accumulate even across a total | ||
| * replacement of all relevant devices (but not across replacement of the actual | ||
| * signing keys with new independently random ones). | ||
| * | ||
| * In case the hardware device cannot be made to sign with the given `rho`, `R` | ||
| * pair, wallet authors should alert the user and present a very scary message | ||
| * implying that if this happens more than even a few times, say 20 or more times | ||
| * EVER, they should change hardware vendors and perhaps sweep their coins. | ||
| * | ||
| * 3. The host replies with `rho` generated in step 1. | ||
| * 4. The device signs with `secp256k1_anti_klepto_sign`, using `rho` as `host_data32`, | ||
| * and sends the signature to the host. | ||
| * 5. The host verifies that the signature's public nonce matches the opening from | ||
| * step 2 and its original randomness `rho`, using `secp256k1_anti_klepto_host_verify`. | ||
| * | ||
| * Rationale: | ||
| * - The reason for having a host commitment is to allow the signing device to | ||
| * deterministically derive a unique nonce even if the host restarts the protocol | ||
| * using the same message and keys. Otherwise the signer might reuse the original | ||
| * nonce in two iterations of the protocol with different `rho`, which leaks the | ||
| * the secret key. | ||
| * - The signer does not need to check that the host commitment matches the host's | ||
| * claimed `rho`. Instead it re-derives the commitment (and its original `R`) from | ||
| * the provided `rho`. If this differs from the original commitment, the result | ||
| * will be an invalid `s2c_opening`, but since `R` was unique there is no risk to | ||
| * the signer's secret keys. Because of this, the signing device does not need to | ||
| * maintain any state about the progress of the protocol. | ||
| */ | ||
|
|
||
| /** Create the initial host commitment to `rho`. Part of the ECDSA Anti-Klepto Protocol. | ||
| * | ||
| * Returns 1 on success, 0 on failure. | ||
| * Args: ctx: pointer to a context object (cannot be NULL) | ||
| * Out: rand_commitment32: pointer to 32-byte array to store the returned commitment (cannot be NULL) | ||
| * In: rand32: the 32-byte randomness to commit to (cannot be NULL). It must come from | ||
| * a cryptographically secure RNG. As per the protocol, this value must not | ||
| * be revealed to the client until after the host has received the client | ||
| * commitment. | ||
| */ | ||
| SECP256K1_API int secp256k1_ecdsa_anti_klepto_host_commit( | ||
| const secp256k1_context* ctx, | ||
| unsigned char* rand_commitment32, | ||
| const unsigned char* rand32 | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3); | ||
|
|
||
| /** Compute signer's original nonce. Part of the ECDSA Anti-Klepto Protocol. | ||
| * | ||
| * Returns 1 on success, 0 on failure. | ||
| * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) | ||
| * Out: s2c_opening: pointer to an s2c_opening where the signer's public nonce will be | ||
| * placed. (cannot be NULL) | ||
| * In: msg32: the 32-byte message hash to be signed (cannot be NULL) | ||
| * seckey32: the 32-byte secret key used for signing (cannot be NULL) | ||
| * rand_commitment32: the 32-byte randomness commitment from the host (cannot be NULL) | ||
| */ | ||
| SECP256K1_API int secp256k1_ecdsa_anti_klepto_signer_commit( | ||
| const secp256k1_context* ctx, | ||
| secp256k1_ecdsa_s2c_opening* s2c_opening, | ||
| const unsigned char* msg32, | ||
| const unsigned char* seckey32, | ||
| const unsigned char* rand_commitment32 | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); | ||
|
|
||
| /** Same as secp256k1_ecdsa_sign, but commits to host randomness in the nonce. Part of the | ||
| * ECDSA Anti-Klepto Protocol. | ||
| * | ||
| * Returns: 1: signature created | ||
| * 0: the nonce generation function failed, or the private key was invalid. | ||
| * Args: ctx: pointer to a context object, initialized for signing (cannot be NULL) | ||
| * Out: sig: pointer to an array where the signature will be placed (cannot be NULL) | ||
| * In: msg32: the 32-byte message hash being signed (cannot be NULL) | ||
| * seckey: pointer to a 32-byte secret key (cannot be NULL) | ||
| * host_data32: pointer to 32-byte host-provided randomness (cannot be NULL) | ||
| */ | ||
| SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_anti_klepto_sign( | ||
| const secp256k1_context* ctx, | ||
| secp256k1_ecdsa_signature* sig, | ||
| const unsigned char* msg32, | ||
| const unsigned char* seckey, | ||
| const unsigned char* host_data32 | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we add
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really don't want to support recoverable signatures any more than we absolutely have to.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the BitBox02 we make use of it. I plan on rolling out the anti-klepto protocol there, so it would definitely make my life a bit easier. Otherwise I will likely maintain a small commit on top which adds this here.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think I'm gonna make you maintain a patch, sorry. It's only a couple lines for you to expose this variable but for us to do it properly we'd need to add another function gated on the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, but for my information: why gate it behind
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because |
||
|
|
||
| /** Verify a signature was correctly constructed using the ECDSA Anti-Klepto Protocol. | ||
| * | ||
| * Returns: 1: the signature is valid and contains a commitment to host_data32 | ||
| * 0: incorrect opening | ||
| * Args: ctx: a secp256k1 context object, initialized for verification. | ||
| * In: sig: the signature produced by the signer (cannot be NULL) | ||
| * msghash32: the 32-byte message hash being verified (cannot be NULL) | ||
| * pubkey: pointer to the signer's public key (cannot be NULL) | ||
| * host_data32: the 32-byte data provided by the host (cannot be NULL) | ||
| * opening: the s2c opening provided by the signer (cannot be NULL) | ||
| */ | ||
| SECP256K1_API SECP256K1_WARN_UNUSED_RESULT int secp256k1_anti_klepto_host_verify( | ||
| const secp256k1_context* ctx, | ||
| const secp256k1_ecdsa_signature *sig, | ||
| const unsigned char *msg32, | ||
| const secp256k1_pubkey *pubkey, | ||
| const unsigned char *host_data32, | ||
| const secp256k1_ecdsa_s2c_opening *opening | ||
| ) SECP256K1_ARG_NONNULL(1) SECP256K1_ARG_NONNULL(2) SECP256K1_ARG_NONNULL(3) SECP256K1_ARG_NONNULL(4) SECP256K1_ARG_NONNULL(5) SECP256K1_ARG_NONNULL(6); | ||
|
|
||
| #ifdef __cplusplus | ||
| } | ||
| #endif | ||
|
|
||
| #endif /* SECP256K1_ECDSA_S2C_H */ | ||
Uh oh!
There was an error while loading. Please reload this page.