-
Notifications
You must be signed in to change notification settings - Fork 36
Introduce first version of ECDSA adaptor signature spec #114
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
Merged
Merged
Changes from 8 commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
e18968d
First version of ECDSA adaptor signature spec
LLFourn b852b78
Fix typos in ecdsa_adaptor.md
LLFourn 3a01367
Fix more typos is ecdsa adaptor
LLFourn df500e7
Rename ecdsa_adaptor.md => ECDSA-adaptor.md
LLFourn 22bf0d9
Add ECDSA adaptor spec tests
LLFourn bc7f160
s/verification_key/public_signing_key/
LLFourn 596a177
Apply suggestions from @jesseposner
LLFourn 03bf709
Update spec to use "DLEQ" tag
LLFourn 1d04308
Fix typo ECDSA-adaptor.md
LLFourn 2de6534
Add serialization test vectors
LLFourn fa11e86
Add warning about leaking DH key
LLFourn e1a1214
Add references section
LLFourn 23cc5bd
Fix references
LLFourn 9b11bcc
Make DLEQ proof section less wordy
LLFourn d01595b
[ecdsa-adaptor] Make key ordering consistent in test vectors
LLFourn 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 hidden or 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,208 @@ | ||
| # ECDSA adaptor signatures | ||
|
|
||
| DLCs work by conditioning a transaction on the revelation of an oracle's signature . | ||
| Concretely the approach is to encrypt a transaction signature such that the oracle's released signature is the decryption key. | ||
| Once the oracle has revealed a signature, either party may use it to decrypt the corresponding transaction signature and broadcast the transaction. | ||
| By this method an oracle's view of a real world event enforces a particular distribution of funds between two parties according to their private bet. | ||
|
|
||
| ## Adaptor Signatures | ||
|
|
||
| *Adaptor signatures* are an efficient form of verifiable signature encryption that can achieve our required structure. | ||
| Given an anticipated signature `Y` we may encrypt a signature (for the purposes of this document, an ECDSA signature) under `Y` such that once a party learns the discrete logarithm of `Y` they can decrypt it and get a valid ECDSA signature. | ||
|
|
||
| An interesting aspect of adaptor signatures is that the decryption key (the discrete logarithm of the encryption key `Y`) can be derived if both the plaintext (the valid ECDSA signature) and the ciphertext (the adaptor signature) are known. | ||
| In our case the leaked decryption key is the scalar *s* value of a [[BIP340]] signature which turns out to be quite useful. | ||
| The ability to recover the *s* value from the on-chain transaction actually improves the security of the protocol compared to the original DLC description in [[Dry]]. | ||
| To see why, imagine an oracle who engages in bets based on their own signatures. | ||
| In order to maintain their credibility they must publish the correct signature scalar `s_valid` publicly, but what is to stop them from privately generating a favorable and wrong `s_cheat` to settle their own bet? | ||
| In the original DLC protocol this was possible and difficult to catch. | ||
| Using adaptor signatures the wronged party can extract the malicious `s_cheat` value from the on-chain signature and use it along with `s_valid` to produce a proof of fraud. | ||
|
|
||
| An appropriate ECDSA adaptor signature scheme that can be used in Bitcoin prior to the Taproot soft-fork was originally posted to the Bitcoin mailing list by [[Mor]] and then further refined by [[Fou],[Aum]]. | ||
| The algorithms presented here resemble the scheme from [[Fou]] but the security proof of the scheme in [[Aum]] can be applied to it which requires a proof of knowledge of the discrete logarithm to be attached to each encryption key. | ||
| Although we do not explicitly attach proofs of knowledge, in the DLC application the encryption key is the anticipated signature of an oracle which by definition the oracle must "know" and therefore we can safely omit the proof of knowledge. | ||
|
|
||
| ## Notation and Conventions | ||
|
|
||
| - secp256k1 operations: We refer to *points* and *scalars* of the secp256k1 group and the operations between them. | ||
| - *n* denotes the curve order of secp256k1 `0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141` | ||
| - Scalars are integers between *0..(n-1)* inclusive. | ||
| - Scalars have three infix operations: multiplication (`*`), addition (`+`) and subtraction (`-`) which are done modulo *n*. | ||
| - Scalars have a postfix operation (`⁻¹`) which denotes multiplicative inversion modulo *n*. | ||
| - Points are members of the secp256k1 group. | ||
| - Points have two binary operations between them: addition (`+`) and subtraction (`-`) which represent the group operation and the inverse group operation respectively. | ||
| - Points and scalars have a infix multiplication (`*`) operation which for `s * P` means adding the point `P` to itself `s` times. | ||
| - *G* is the standard generator point of secp256k1 with coordinates: | ||
| - x: `0x79BE667EF9DCBBAC55A06295CE870B07029BFCDB2DCE28D959F2815B16F81798` | ||
| - y: `0x483ADA7726A3C4655DA4FBFC0E1108A8FD17B448A68554199C47D08FFB10D4B8` | ||
| - When non-zero points are encoded as bytes they are encoded using the 33-byte compact [[SEC1]] encoding (implementations should fail if they try and encode the point at infinity). | ||
| - When scalars are encoded as bytes they are encoded as 32-byte big endian integers. | ||
| - other operations: | ||
| - `||` is an infix operation referring to byte concatenation. Its two arguments are implicitly converted to bytes. | ||
|
|
||
| ### Secure Nonce Generation | ||
|
|
||
| Nonce generation is the most difficult aspect of cryptographic protocols to specify and the most important to get right. | ||
| We abandon some of the responsibility for this in this document for brevity and flexibility. | ||
| In the specification we use a function `sample_nonce` that takes a byte string and reliably returns a uniformly random scalar. | ||
| Implementations may decide precisely how to instantiate this function. | ||
| If `sample_nonce` is instantiated with a secure hash function then the resulting scheme is secure if we model it as a random oracle as shown by [[Bel]]. | ||
| However, we recommend adding system randomness into the process as well. | ||
| This can be done by hashing 32 random bytes along side the input string or applying more sophisticated approach as in [[BIP340]]. | ||
|
|
||
| ## Proof of Discrete Logarithm Equality | ||
|
|
||
| As part of the ECDSA adaptor signature a proof of discrete logarithm equality must be provided. | ||
| In our case this is a proof that the discrete logarithm of some `X` to the standard base `G` is the same as the discrete logarithm of some `Z` to the base `Y`. | ||
| This proof can be constructed by using *equality composition* on two Sigma protocols proving knowledge of the discrete logarithm between both pairs of points. | ||
| In other words the prover proves knowledge of `a` such that `X = a * G` and `b` such that `Z = b * Y` and that `a = b`. | ||
| We make the resulting Sigma protocol non-interactive by applying the Fiat-Shamir transformation with SHA256 as the challenge hash. | ||
| For background on Sigma protocols and making them non-interactive see [[Sch]]. | ||
|
|
||
| To disambiguate the proof we first *tag* a SHA256 as specified in [[BIP340]] with a string that identifies the kind of statement we are proving. | ||
| Specifically, we set `tag` to `SHA256("DLEQ") || SHA256("DLEQ")` and use it to prefix hash operations. | ||
| The challenge hash `H` below is defined as `H(x) = scalar(SHA256(tag || x))`. | ||
|
|
||
| ### Proving | ||
|
|
||
| The `DLEQ_prove(x, (X, Y, Z))` algorithm takes the following inputs: | ||
|
|
||
| - `x`: A **non-zero** scalar representing the witness for the proof. | ||
| - `(X, Y, Z)`: Three **non-zero** secp256k1 points which define the statement to be verified. | ||
|
|
||
| and is defined as: | ||
|
|
||
| - Set `a` to `sample_nonce(tag || X || Y || Z || x)` | ||
| - Set `A_G` to `a * G` | ||
| - Set `A_Y` to `a * Y` | ||
| - Set `b` to `H(X || Y || Z || A_G || A_Y)` | ||
| - Set `c` to `a + b * x` | ||
| - Set `proof` to `b || c` | ||
| - Return `proof` | ||
|
|
||
| ### Verifying | ||
|
|
||
| The `DLEQ_verify((X,Y,Z), proof)` algorithm takes as input: | ||
|
|
||
| - `(X,Y,Z)`: Three **non-zero** secp256k1 points which define the statement we are verifying. | ||
| Specifically, there exists `x` such that `X = x * G` and `Z = x * Y`. | ||
| - `proof`: the proof for the statement. | ||
|
|
||
| and is defined as: | ||
|
|
||
| - Parse `proof` as two scalars `(b,c)` | ||
| - Set `A_G` to `c * G - b * X` | ||
| - Set `A_Y` to `c * Y - b * Z` | ||
| - Set `implied_b` to `H(X || Y || Z || A_G || A_Y)` | ||
| - Check `implied_b == b` | ||
|
|
||
| ## Adaptor Signature | ||
|
|
||
| ### Encrypted Signing | ||
|
|
||
| The encrypted signing algorithm `ecdsa_adaptor_encrypt(x, Y, message_hash)` takes as input: | ||
|
|
||
| - `x`: The signing *secret key*. | ||
| - `Y`: The encryption *public key* (in our case the *anticipated signature* of an oracle associated with a particular outcome). | ||
| - `message_hash`: A 32-byte array which **must** be the hash of the message (in our case a transaction digest). | ||
|
|
||
| and is defined as: | ||
|
|
||
| - Set `k` to `sample_nonce(Y || message_hash || x)` | ||
| - Set `m` to `scalar(message_hash)` | ||
| - Set `R_a` to `k * G` | ||
| - Set `R` to `k * Y` | ||
| - Set `proof` to `DLEQ_prove(k, (R_a, Y, R))` | ||
| - Set `r` to the x-coordinate of `R` modulo `n` (i.e. convert it to a scalar) | ||
| - Set `s_a` to `k⁻¹ (m + r * x)` | ||
| - Set `a` to `R || R_a || s_a || proof` | ||
| - Return `a` | ||
|
|
||
| The return value is an adaptor signature. | ||
|
|
||
| ### Encryption Verification | ||
|
|
||
| The verification algorithm `ecdsa_adaptor_verify(X, Y, message_hash, a)` takes as input: | ||
|
|
||
| - `X`: The signing *public key* the adaptor signature was created under. | ||
| - `Y`: The encryption *public key* the adaptor signature was encrypted under. | ||
| - `message_hash`: The message hash the adaptor signature was created for (in our case a transaction digest). | ||
| - `a`: The adaptor signature | ||
|
|
||
| and is defined as: | ||
|
|
||
| - Parse `a` as `(R, R_a, s_a, proof)` | ||
| - Check `DLEQ_verify((R_a, Y, R), proof)` or fail | ||
| - Set `m` to `scalar(message_hash)` | ||
| - Set `u_1` to `s_a⁻¹ * m` | ||
| - Set `u_2` to `s_a⁻¹ * r` | ||
| - Check `u_1 * G + u2 * X == R_a` | ||
|
|
||
| ### Decryption | ||
|
|
||
| The `ecdsa_adaptor_decrypt(a, y)` algorithm takes as input: | ||
|
|
||
| - `a`: The adaptor signature. | ||
| - `y`: The decryption *secret key*. | ||
|
|
||
| and is defined as: | ||
|
|
||
| - Parse `a` as `(R, R_a, s_a, proof)` | ||
| - Set `s` to `s_a * y⁻¹` | ||
| - Negate `s` if it is high as specified in [[BIP62]] | ||
| - Set `r` to the x-coordinate of `R` modulo `n` | ||
| - Return `r || s` | ||
|
|
||
| The returned value is a ECDSA signature. | ||
| The signature will be valid if `a` passed `ecdsa_adaptor_verify` and the decryption key is `y` is such that the original encryption key `Y` is equal to `y*G`. | ||
|
|
||
| ### Key Recovery | ||
|
|
||
| The decryption key recovery algorithm `ecdsa_adaptor_recover(Y, a, sig)` takes the following input: | ||
|
|
||
| - `Y`: The encryption *public key* the adaptor signature was encrypted under. | ||
| - `a`: The ECDSA adaptor signature | ||
| - `sig`: The ECDSA signature decrypted from `a` | ||
|
|
||
| and is defined as follows: | ||
|
|
||
| - Parse `a` as `(R, R_a, s_a, proof)` | ||
| - Parse `sig` as `(r,s)` | ||
| - Set `r_implied` to the x-coordinate of `R` modulo `n` | ||
| - Check `r == r_implied` or fail | ||
| - Set `y` to `s⁻¹ * s_a` | ||
| - Set `Y_implied` to `y * G` | ||
| - If `Y_implied == Y` then return `y` | ||
| - Otherwise, if `Y_implied == -Y` then return `-y` | ||
| - Otherwise fail | ||
|
|
||
| The returned value is the decryption key corresponding to the original encryption key `Y` used to create `a`. | ||
| In our context this is an oracle signature *s* value. | ||
|
|
||
| Note that if this function returns successfully and `a` has been verified with `ecdsa_adaptor_verify` for some public key and message hash then this implies that `sig` is a valid ECDSA signature on the message hash | ||
| i.e. there is no strict need to verify `sig` after (or before) successfully calling `ecdsa_adaptor_recover` if you have already ensured that `ecdsa_adaptor_verify` passes on the original adaptor signature. | ||
| However, successfully recovering the signature does not ensure that it follows the `low_s` rule described in [[BIP62]] necessary to be able to broadcast the signature on a transaction in Bitcoin. | ||
| This point is not relevant to on-chain DLCs since the ECDSA signatures always come from the blockchain where they have already been verified. | ||
|
|
||
|
|
||
| ## Specification tests | ||
|
|
||
| The test vectors for this specification are in [./test/ecdsa_adaptor.json] and come in two kinds: | ||
|
|
||
| - `verification`: These test the verifier in three stages: | ||
| - Check `adaptor_sig` passes `ecdsa_adaptor_verify`. | ||
| - Check that `ecdsa_adaptor_decrypt` yields `signature`. | ||
| - Check that `ecdsa_adaptor_recover` recovers `decryption_key`. | ||
| - `recovery`: These test only the key recovery function. The recovery function should recovery the correct decryption key. | ||
|
|
||
| These should fail only when `error` is set in the test vector. | ||
|
|
||
| [Dry]: https://adiabat.github.io/dlc.pdf | ||
| [Mor]: https://lists.linuxfoundation.org/pipermail/lightning-dev/attachments/20180426/fe978423/attachment-0001.pdf | ||
| [Fou]: https://github.com/LLFourn/one-time-VES/blob/master/main.pdf | ||
| [Aum]: https://eprint.iacr.org/2020/476.pdf | ||
| [SEC1]: https://www.secg.org/sec1-v2.pdf | ||
| [Bel]: https://eprint.iacr.org/2015/1157.pdf | ||
| [Sch]: https://www.win.tue.nl/~berry/CryptographicProtocols/LectureNotes.pdf | ||
| [BIP62]: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki#low-s-values-in-signatures | ||
| [BIP340]: https://github.com/bitcoin/bips/blob/master/bip-0340.mediawiki | ||
This file contains hidden or 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,48 @@ | ||
| [ | ||
| { | ||
| "kind" : "verification", | ||
| "adaptor_sig": "03424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb6730223f325042fce535d040fee52ec13231bf709ccd84233c6944b90317e62528b2527dff9d659a96db4c99f9750168308633c1867b70f3a18fb0f4539a1aecedcd1fc0148fc22f36b6303083ece3f872b18e35d368b3958efe5fb081f7716736ccb598d269aa3084d57e1855e1ea9a45efc10463bbf32ae378029f5763ceb40173f", | ||
| "message_hash": "8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d", | ||
| "public_signing_key": "035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c", | ||
| "encryption_key": "02c2662c97488b07b6e819124b8989849206334a4c2fbdf691f7b34d2b16e9c293", | ||
| "decryption_key": "0b2aba63b885a0f0e96fa0f303920c7fb7431ddfa94376ad94d969fbf4109dc8", | ||
| "signature": "424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb67329e80e0ee60e57af3e625bbae1672b1ecaa58effe613426b024fa1621d903394", | ||
| "comment": "plain valid adaptor signature" | ||
| }, | ||
| { | ||
| "kind" : "verification", | ||
| "adaptor_sig" : "036035c89860ec62ad153f69b5b3077bcd08fbb0d28dc7f7f6df4a05cca35455be037043b63c56f6317d9928e8f91007335748c49824220db14ad10d80a5d00a9654af0996c1824c64c90b951bb2734aaecf78d4b36131a47238c3fa2ba25e2ced54255b06df696de1483c3767242a3728826e05f79e3981e12553355bba8a0131cd370e63e3da73106f638576a5aab0ea6d45c042574c0c8d0b14b8c7c01cfe9072", | ||
| "public_signing_key" : "035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c", | ||
| "encryption_key" : "024eee18be9a5a5224000f916c80b393447989e7194bc0b0f1ad7a03369702bb51", | ||
| "decryption_key" : "db2debddb002473a001dd70b06f6c97bdcd1c46ba1001237fe0ee1aeffb2b6c4", | ||
| "signature" : "6035c89860ec62ad153f69b5b3077bcd08fbb0d28dc7f7f6df4a05cca35455be4ceacf921546c03dd1be596723ad1e7691bdac73d88cc36c421c5e7f08384305", | ||
| "message_hash": "8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d", | ||
|
LLFourn marked this conversation as resolved.
Outdated
|
||
| "comment": "the decrypted signature is high so it must be negated first AND the extracted decryption key must be negated" | ||
| }, | ||
| { | ||
| "kind" : "verification", | ||
| "adaptor_sig": "03f94dca206d7582c015fb9bffe4e43b14591b30ef7d2b464d103ec5e116595dba03127f8ac3533d249280332474339000922eb6a58e3b9bf4fc7e01e4b4df2b7a4100a1e089f16e5d70bb89f961516f1de0684cc79db978495df2f399b0d01ed7240fa6e3252aedb58bdc6b5877b0c602628a235dd1ccaebdddcbe96198c0c21bead7b05f423b673d14d206fa1507b2dbe2722af792b8c266fc25a2d901d7e2c335", | ||
| "message_hash": "8131e6f4b45754f2c90bd06688ceeabc0c45055460729928b4eecf11026a9e2d", | ||
| "public_signing_key": "035be5e9478209674a96e60f1f037f6176540fd001fa1d64694770c56a7709c42c", | ||
| "encryption_key": "0214ccb756249ad6e733c80285ea7ac2ee12ffebbcee4e556e6810793a60c45ad4", | ||
| "decryption_key": "1dfcfc0880e72509768ab46f2545b33168b8b8df8e4f5feb5059aa3750ee59d0", | ||
| "signature": "424d14a5471c048ab87b3b83f6085d125d5864249ae4297a57c84e74710bb67329e80e0ee60e57af3e625bbae1672b1ecaa58effe613426b024fa1621d903394", | ||
| "error": "proof is wrong" | ||
| }, | ||
| { | ||
| "kind" : "recovery", | ||
| "encryption_key" : "027ee4f899bc9c5f2b626fa1a9b37ce291c0388b5227e90b0fd8f4fa576164ede7", | ||
| "adaptor_sig" : "03f2db6e9ed33092cc0b898fd6b282e99bdaeccb3de85c2d2512d8d507f9abab290210c01b5bed7094a12664aeaab3402d8709a8f362b140328d1b36dd7cb420d02fb66b1230d61c16d0cd0a2a02246d5ac7848dcd6f04fe627053cd3c7015a7d4aa6ac2b04347348bd67da43be8722515d99a7985fbfa66f0365c701de76ff0400dffdc9fa84dddf413a729823b16af60aa6361bc32e7cfd6701e32957c72ace67b", | ||
| "signature" : "f2db6e9ed33092cc0b898fd6b282e99bdaeccb3de85c2d2512d8d507f9abab2921811fe7b53becf3b7affa9442abaa93c0ab8a8e45cd7ee2ea8d258bfc25d464", | ||
| "decryption_key" : "9cf3ea9be594366b78c457162908af3c2ea177058177e9c6bf99047927773a06", | ||
| "comment" : "plain recovery" | ||
| }, | ||
| { | ||
| "kind" : "recovery", | ||
| "adaptor_sig" : "03aa86d78059a91059c29ec1a757c4dc029ff636a1e6c1142fefe1e9d7339617c003a8153e50c0c8574a38d389e61bbb0b5815169e060924e4b5f2e78ff13aa7ad858e0c27c4b9eed9d60521b3f54ff83ca4774be5fb3a680f820a35e8840f4aaf2de88e7c5cff38a37b78725904ef97bb82341328d55987019bd38ae1745e3efe0f8ea8bdfede0d378fc1f96e944a7505249f41e93781509ee0bade77290d39cd12", | ||
| "signature" : "f7f7fe6bd056fc4abd70d335f72d0aa1e8406bba68f3e579e4789475323564a452c46176c7fb40aa37d5651341f55697dab27d84a213b30c93011a7790bace8c", | ||
| "encryption_key": "035176d24129741b0fcaa5fd6750727ce30860447e0a92c9ebebdeb7c3f93995ed", | ||
| "decryption_key" : null, | ||
| "error" : "the R value of the signature does not match" | ||
| } | ||
| ] | ||
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.
Uh oh!
There was an error while loading. Please reload this page.