ECDSA secp256k1#2852
Conversation
c46dd16 to
efb72e2
Compare
28019e7 to
1da24d5
Compare
|
Could you fork the eth secp256k1 to algorand/secp256k1 and use it from there ? ( and have the eth as upstream ). |
jasonpaulos
left a comment
There was a problem hiding this comment.
This is a great start. I have some comments on opcode details, but things are looking good in general.
1da24d5 to
688fbb9
Compare
Codecov Report
@@ Coverage Diff @@
## master #2852 +/- ##
==========================================
- Coverage 47.34% 47.29% -0.05%
==========================================
Files 351 355 +4
Lines 56523 56889 +366
==========================================
+ Hits 26758 26906 +148
- Misses 26758 26948 +190
- Partials 3007 3035 +28
Continue to review full report at Codecov.
|
Cost calculated relative to ed25519verify BenchmarkEcDsa/ecdsa_verify-8 17341 67335 ns/op BenchmarkEcDsa/ecdsa_pk_decompress-8 42854 25714 ns/op BenchmarkEcDsa/ecdsa_pk_recover-8 15337 78249 ns/op BenchmarkEd25519Verifyx1-8 15244 75253 ns/op
688fbb9 to
bb53759
Compare
Done. |
jannotti
left a comment
There was a problem hiding this comment.
I don't feel qualified to comment on the crypto itself, but I've commented where I could.
Thanks for adding work to document fields. I'll will need that to finish up tx_field spec.
| | `getbyte` | pop a byte-array A and integer B. Extract the Bth byte of A and push it as an integer | | ||
| | `setbyte` | pop a byte-array A, integer B, and small integer C (between 0..255). Set the Bth byte of A to C, and push the result | | ||
| | `concat` | pop two byte-arrays A and B and join them, push the result | | ||
| | `ecdsa_verify c` | for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} | |
There was a problem hiding this comment.
This is confusing because c is the name of the field (the immediate) and also (I think) the name you're using to describe the stack value. I'd pick a new name for the immediate.
Also need to say what the immediate is/does.
| | `ecdsa_pk_recover c` | for (data A, recovery id B, signature C, D) recover a public compressed key => [*... stack*, X, Y] | | ||
| | `ecdsa_pk_decompress c` | decompress pubkey A into components X, Y => [*... stack*, X, Y] | |
There was a problem hiding this comment.
Earlier comment applies to these too.
| "ed25519verify": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", | ||
| "ecdsa_verify": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", | ||
| "ecdsa_pk_decompress": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components.", | ||
| "ecdsa_pk_recover": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key in compressed format. The signed data must be 32 bytes long.", |
There was a problem hiding this comment.
I think we should extend our convention for naming stack arguments. So for a five argument opcode, you can just refer to A,B,C,D,&E. (A is the deepest, E is top-of-stack). I think that will simplify the text.
There was a problem hiding this comment.
I agree but in this extended section we do not refer A-E at all, only in short description.
jasonpaulos
left a comment
There was a problem hiding this comment.
This is nearly there, I just have some minor suggestions.
Additionally, could you also add this test I made to verify compatibility with ethereum signatures? https://gist.github.com/jasonpaulos/47166d44c13e4efb18c7a1a09f4ac969
| "ed25519verify": "for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey => {0 or 1}", | ||
| "ecdsa_verify": "for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}", | ||
| "ecdsa_pk_decompress": "decompress pubkey A into components X, Y => [*... stack*, X, Y]", | ||
| "ecdsa_pk_recover": "for (data A, recovery id B, signature C, D) recover a public compressed key => [*... stack*, X, Y]", |
There was a problem hiding this comment.
This should not say compressed key
| "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", | ||
| "log": "`log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes.", | ||
| "ed25519verify": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", | ||
| "ecdsa_verify": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", |
There was a problem hiding this comment.
Could you mention that Y, X, S, and R are big-endian encoded?
| "ed25519verify": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", | ||
| "ecdsa_verify": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", | ||
| "ecdsa_pk_decompress": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components.", | ||
| "ecdsa_pk_recover": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key in compressed format. The signed data must be 32 bytes long.", |
There was a problem hiding this comment.
compressed should not be mentioned here either
| pass bool | ||
| }{ | ||
| {r, true}, | ||
| {rTampered, false}, |
There was a problem hiding this comment.
I would also recommend altering the message to make sure verification fails
|
Could we maybe have |
algorandskiy
left a comment
There was a problem hiding this comment.
All reviews addressed except A, B, C,... naming in extended doc.
Additionally, could you also add this test I made to verify compatibility with ethereum signatures? https://gist.github.com/jasonpaulos/47166d44c13e4efb18c7a1a09f4ac969
Added.
| | `getbyte` | pop a byte-array A and integer B. Extract the Bth byte of A and push it as an integer | | ||
| | `setbyte` | pop a byte-array A, integer B, and small integer C (between 0..255). Set the Bth byte of A to C, and push the result | | ||
| | `concat` | pop two byte-arrays A and B and join them, push the result | | ||
| | `ecdsa_verify c` | for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1} | |
| | `ecdsa_pk_recover c` | for (data A, recovery id B, signature C, D) recover a public compressed key => [*... stack*, X, Y] | | ||
| | `ecdsa_pk_decompress c` | decompress pubkey A into components X, Y => [*... stack*, X, Y] | |
| "ed25519verify": "for (data A, signature B, pubkey C) verify the signature of (\"ProgData\" || program_hash || data) against the pubkey => {0 or 1}", | ||
| "ecdsa_verify": "for (data A, signature B, C and pubkey D, E) verify the signature of the data against the pubkey => {0 or 1}", | ||
| "ecdsa_pk_decompress": "decompress pubkey A into components X, Y => [*... stack*, X, Y]", | ||
| "ecdsa_pk_recover": "for (data A, recovery id B, signature C, D) recover a public compressed key => [*... stack*, X, Y]", |
| "app_params_get": "params: Txn.ForeignApps offset or an app id that appears in Txn.ForeignApps. Return: did_exist flag (1 if exist and 0 otherwise), value.", | ||
| "log": "`log` can be called up to MaxLogCalls times in a program, and log up to a total of 1k bytes.", | ||
| "ed25519verify": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", | ||
| "ecdsa_verify": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", |
| "ed25519verify": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", | ||
| "ecdsa_verify": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", | ||
| "ecdsa_pk_decompress": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components.", | ||
| "ecdsa_pk_recover": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key in compressed format. The signed data must be 32 bytes long.", |
| "ed25519verify": "The 32 byte public key is the last element on the stack, preceded by the 64 byte signature at the second-to-last element on the stack, preceded by the data which was signed at the third-to-last element on the stack.", | ||
| "ecdsa_verify": "The 32 byte Y-component of a public key is the last element on the stack, preceded by X-component of a pubkey, preceded by S and R components of a signature, preceded by the data that is fifth element on the stack. The signed data must be 32 bytes long, and signatures in lower-S form are only accepted.", | ||
| "ecdsa_pk_decompress": "The 33 byte public key in a compressed form to be decompressed into X and Y (top) components.", | ||
| "ecdsa_pk_recover": "S (top) and R elements of a signature, recovery id and data (bottom) are expected on the stack and used to deriver a public key in compressed format. The signed data must be 32 bytes long.", |
There was a problem hiding this comment.
I agree but in this extended section we do not refer A-E at all, only in short description.
| pass bool | ||
| }{ | ||
| {r, true}, | ||
| {rTampered, false}, |
This is quite small and follows the approach of keeping crypto stuff under `crypto/' in go-algorand. |
Since the merge of #2852 go mod tidy and go mod vendor see paths in github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1 as a dependency, and go mod tidy adds it to go.mod.
* Add secp256k1 module from go-ethereum 1.10.7 * ECDSA secp256k1 implementation Cost calculated relative to ed25519verify BenchmarkEcDsa/ecdsa_verify-8 17341 67335 ns/op BenchmarkEcDsa/ecdsa_pk_decompress-8 42854 25714 ns/op BenchmarkEcDsa/ecdsa_pk_recover-8 15337 78249 ns/op BenchmarkEd25519Verifyx1-8 15244 75253 ns/op
Since the merge of algorand#2852 go mod tidy and go mod vendor see paths in github.com/ethereum/go-ethereum/crypto/secp256k1/libsecp256k1 as a dependency, and go mod tidy adds it to go.mod.
Summary
ecdsa_verify,ecdsa_pk_decompress,ecdsa_pk_recoveropcodes.ecdsa_verifyandecdsa_pk_recoverwork with two-component keys (two 32 bytes long numbers) and not a single 33-bytes compressed or 65-bytes uncompressed keys.Test Plan
Added new tests.
Cost calculation
Made as a ratio of
ed25519verifycostReviewer Notes
Most of the code is
crypto/secp256k1import from go-ethereum, please ignore.