Skip to content

Conversation

@algorandskiy
Copy link
Contributor

@algorandskiy algorandskiy commented Sep 7, 2021

Summary

  • ecdsa_verify, ecdsa_pk_decompress, ecdsa_pk_recover opcodes.
  • crypto/secp256k1 sources from go-ethereum 1.10.7 committed into the repo.
  • costs calculated (see below).
  • so far only secp256k1 curve is supported.
  • ecdsa_verify and ecdsa_pk_recover work 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 ed25519verify cost

    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

Reviewer Notes

Most of the code is crypto/secp256k1 import from go-ethereum, please ignore.

@algorandskiy algorandskiy force-pushed the pavel/ecdsa branch 2 times, most recently from 28019e7 to 1da24d5 Compare September 8, 2021 14:08
@tsachiherman
Copy link
Contributor

Could you fork the eth secp256k1 to algorand/secp256k1 and use it from there ? ( and have the eth as upstream ).
( so that we won't be depending on the eth repo directly )

Copy link
Contributor

@jasonpaulos jasonpaulos left a comment

Choose a reason for hiding this comment

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

This is a great start. I have some comments on opcode details, but things are looking good in general.

@codecov-commenter
Copy link

codecov-commenter commented Sep 9, 2021

Codecov Report

Merging #2852 (a727dd7) into master (6afa86b) will decrease coverage by 0.04%.
The diff coverage is 41.80%.

Impacted file tree graph

@@            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     
Impacted Files Coverage Δ
crypto/secp256k1/panic_cb.go 0.00% <0.00%> (ø)
data/transactions/logic/doc.go 82.75% <ø> (ø)
crypto/secp256k1/curve.go 14.61% <14.61%> (ø)
data/transactions/logic/fields_string.go 5.18% <16.66%> (+0.53%) ⬆️
crypto/secp256k1/secp256.go 42.25% <42.25%> (ø)
data/transactions/logic/assembler.go 80.32% <45.00%> (-0.35%) ⬇️
data/transactions/logic/eval.go 88.38% <66.35%> (-1.18%) ⬇️
data/transactions/logic/fields.go 81.57% <66.66%> (-2.01%) ⬇️
crypto/secp256k1/scalar_mult_cgo.go 77.77% <77.77%> (ø)
data/transactions/logic/opcodes.go 100.00% <100.00%> (ø)
... and 12 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 6afa86b...a727dd7. Read the comment docs.

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
@algorandskiy
Copy link
Contributor Author

algorandskiy commented Sep 9, 2021

Could you fork the eth secp256k1 to algorand/secp256k1 and use it from there ?

Done.
Edit: Well, vendored instead. @jasonpaulos Do we plan to expose ECDSA in SDKs? If so then makes sense forking instead of vendoring (to re-import to go-sdk at least)

@algorandskiy algorandskiy marked this pull request as ready for review September 9, 2021 01:59
Copy link
Contributor

@jannotti jannotti left a comment

Choose a reason for hiding this comment

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

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} |
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 143 to 144
| `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] |
Copy link
Contributor

Choose a reason for hiding this comment

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

Earlier comment applies to these too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 223 to 226
"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.",
Copy link
Contributor

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree but in this extended section we do not refer A-E at all, only in short description.

Copy link
Contributor

@jasonpaulos jasonpaulos left a comment

Choose a reason for hiding this comment

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

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]",
Copy link
Contributor

Choose a reason for hiding this comment

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

This should not say compressed key

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"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.",
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you mention that Y, X, S, and R are big-endian encoded?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"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.",
Copy link
Contributor

Choose a reason for hiding this comment

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

compressed should not be mentioned here either

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

pass bool
}{
{r, true},
{rTampered, false},
Copy link
Contributor

Choose a reason for hiding this comment

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

I would also recommend altering the message to make sure verification fails

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@tolikzinovyev
Copy link
Contributor

Could we maybe have crypto/secp256k1 in a separate repo?

Copy link
Contributor Author

@algorandskiy algorandskiy left a comment

Choose a reason for hiding this comment

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

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} |
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 143 to 144
| `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] |
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"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]",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"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.",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

"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.",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Comment on lines 223 to 226
"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.",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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},
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@algorandskiy
Copy link
Contributor Author

Could we maybe have crypto/secp256k1 in a separate repo?

This is quite small and follows the approach of keeping crypto stuff under `crypto/' in go-algorand.

@jannotti jannotti merged commit 8f743a9 into algorand:master Sep 11, 2021
algojohnlee pushed a commit that referenced this pull request Sep 14, 2021
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.
@algorandskiy algorandskiy deleted the pavel/ecdsa branch September 21, 2021 16:55
tmc pushed a commit to tmc/go-algorand that referenced this pull request Mar 7, 2025
* 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
tmc pushed a commit to tmc/go-algorand that referenced this pull request Mar 7, 2025
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants