-
Notifications
You must be signed in to change notification settings - Fork 272
Feat/#318 precompile ecrecover #495
Changes from 14 commits
0ff6fd4
52a8dd1
9c6b839
b2f3d2c
e53546d
9690c3d
1bf4565
8e29537
e53104a
c0e0a22
80e97bd
3ae72d0
b517a95
a1b878d
86866f9
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,33 @@ | ||
# ecRecover precompile | ||
|
||
## Procedure | ||
|
||
To recover the signer from a signature. It returns signer's address if input signature is valid, otherwise returns 0. | ||
|
||
## EVM behavior | ||
|
||
### Inputs | ||
|
||
The length of inputs is 128 bytes. The first 32 bytes is keccak hash of the message, and following 96 bytes are v, r, s values. v is either 27 or 28. | ||
|
||
### Output | ||
|
||
The recovered 20-byte address right aligned to 32 byte. If an address can't be recovered or not enough gas was given, then return 0. | ||
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. Nitpick: "then return 0" -> "then the output is 0". 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. fixed in 86866f9 |
||
|
||
### Gas cost | ||
|
||
A constant gas cost: 3000 | ||
|
||
## Constraints | ||
|
||
1. If gas_left < gas_required, then is_success == false and return data is zero. | ||
1. v, r and s are valid | ||
- v is 27 or 28 | ||
- both of r and s are less than `secp256k1N (0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141)` | ||
- both of r and s are greater than `1` | ||
2. `sig_table` lookups | ||
3. recovered address is zero if the signature can't be recovered. | ||
|
||
## Code | ||
|
||
Please refer to `src/zkevm_specs/evm_circuit/execution/precompiles/ecrecover.py`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
# Signature Proof | ||
|
||
[Elliptic Curve Digital Signature Algorithm]: https://en.wikipedia.org/wiki/Elliptic_Curve_Digital_Signature_Algorithm | ||
|
||
According to the [Elliptic Curve Digital Signature Algorithm] (ECDSA), the signatures `(r,s)` are calculated via ECDSA from `msg_hash` and a `public_key` using the formula | ||
|
||
`(r,s)=ecdsa(msg_hash, public_key)` | ||
|
||
The `public_key` is obtained from `private_key` by mapping the latter to an elliptic curve (EC) point. The `r` is the x-component of an EC point, and the same EC point's y-component will be used to determine the recovery id `v = y%2` (the parity of y). Given the signature `(v, r, s)`, the `public_key` can be recovered from `(v, r, s)` and `msg_hash` using `ecrecover`. | ||
|
||
|
||
## Circuit behavior | ||
|
||
SigTable built inside zkevm-circuits is used to verify signatures. It has the following columns: | ||
- `msg_hash`: Advice Column, the Keccak256 hash of the message that's signed; | ||
- `sig_v`: Advice Column, the recovery id, either 0 or 1, it should be the parity of y; | ||
- `sig_r`: Advice Column, the signature's `r` component; | ||
- `sig_s`: Advice Column, the signature's `s` component; | ||
- `recovered_addr`: Advice Column, the recovered address, i.e. the 20-bytes address that must have signed the message; | ||
- `is_valid`: Advice Column, indicates whether or not the signature is valid or not upon signature verification. | ||
|
||
Constraints on the shape of the table is like: | ||
|
||
| 0 msg_hash | 1 sig_v | 2 sig_r | 3 sig_s | 4 recovered_addr | 5 is_valid | | ||
| ------------- | ------ | ------------- | ------------- | ---------------- | ---------- | | ||
| $value{Lo,Hi} | 0/1 | $value{Lo,Hi} | $value{Lo,Hi} | $value{Lo,Hi} | bool | | ||
|
||
|
||
The Sig Circuit aims at proving the correctness of SigTable. This mainly includes the following type of constraints: | ||
- checking that the signature is obtained correctly. This is done by the ECDSA chip, and the correctness of `v` is checked separately; | ||
- checking that `msg_hash` is obtained correctly from Keccak hash function. This is done by lookup to Keccak table; | ||
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. Nitpick: Perhaps make "checking" upper case as there are two sentences in the bullet. 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. fixed in 86866f9 |
||
|
||
|
||
## Constraints | ||
|
||
`assign_ecdsa` method takes the signature data and uses ECDSA chip to verify its correctness. The verification result `sig_is_valid` will be returned. The recovery id `v` value will be computed and verified. | ||
|
||
`sign_data_decomposition` method takes the signature data and the return values of `assign_ecdsa`, and returns the cells for byte decomposition of the keys and messages in the form of `SignDataDecomposed`. The latter consists of the following contents: | ||
- `SignDataDecomposed` | ||
- `pk_hash_cells`: byte cells for keccak256 hash of public key; | ||
- `msg_hash_cells`: byte cells for `msg_hash`; | ||
- `pk_cells`: byte cells for the EC coordinates of public key; | ||
- `address`: RLC of `pk_hash` last 20 bytes; | ||
- `is_address_zero`: check if address is zero; | ||
- `r_cells`, `s_cells`: byte cells for signatures `r` and `s`. | ||
|
||
The decomposed sign data are sent to `assign_sign_verify` method to compute and verify their RLC values and perform Keccak lookup checks. | ||
|
||
## Code | ||
|
||
Please refer to `src/zkevm-specs/sig_circuit.py` |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,3 +7,4 @@ | |
from .table import * | ||
from .typing import * | ||
from .util import * | ||
from .precompile import * |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
from zkevm_specs.evm_circuit.instruction import Instruction | ||
from zkevm_specs.evm_circuit.table import ( | ||
CallContextFieldTag, | ||
FixedTableTag, | ||
RW, | ||
) | ||
from zkevm_specs.util import FQ, Word, EcrecoverGas | ||
|
||
SECP256K1N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 | ||
|
||
|
||
def ecRecover(instruction: Instruction): | ||
is_success = instruction.call_context_lookup(CallContextFieldTag.IsSuccess, RW.Read) | ||
address_word = instruction.call_context_lookup_word(CallContextFieldTag.CalleeAddress) | ||
address = instruction.word_to_address(address_word) | ||
instruction.fixed_lookup( | ||
FixedTableTag.PrecompileInfo, | ||
FQ(instruction.curr.execution_state), | ||
address, | ||
FQ(EcrecoverGas), | ||
) | ||
|
||
# Get msg_hash, signature and recovered address from aux_data | ||
msg_hash: Word = instruction.curr.aux_data[0] | ||
sig_v: Word = instruction.curr.aux_data[1] | ||
sig_r: Word = instruction.curr.aux_data[2] | ||
sig_s: Word = instruction.curr.aux_data[3] | ||
recovered_addr: FQ = instruction.curr.aux_data[4] | ||
|
||
is_recovered = FQ(instruction.is_zero(recovered_addr) != FQ(1)) | ||
|
||
# is_success is always true | ||
# ref: ref: https://github.com/ethereum/execution-specs/blob/master/src/ethereum/shanghai/vm/precompiled_contracts/ecrecover.py | ||
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. Nitpick: "ref:" two times 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. fixed in 86866f9 |
||
instruction.constrain_equal(is_success, FQ(1)) | ||
|
||
# verify r and s | ||
sig_r_upper_bound, _ = instruction.compare_word(sig_r, Word(SECP256K1N)) | ||
sig_s_upper_bound, _ = instruction.compare_word(sig_s, Word(SECP256K1N)) | ||
sig_r_is_non_zero = FQ(instruction.is_zero_word(sig_r) != FQ(1)) | ||
sig_s_is_non_zero = FQ(instruction.is_zero_word(sig_s) != FQ(1)) | ||
valid_r_s = instruction.is_equal( | ||
sig_r_upper_bound + sig_s_upper_bound + sig_r_is_non_zero + sig_s_is_non_zero, FQ(4) | ||
) | ||
|
||
# verify v | ||
is_equal_27 = instruction.is_equal_word(sig_v, Word(27)) | ||
is_equal_28 = instruction.is_equal_word(sig_v, Word(28)) | ||
valid_v = instruction.is_equal(is_equal_27 + is_equal_28, FQ(1)) | ||
|
||
if valid_r_s + valid_v == FQ(2): | ||
# sig table lookups | ||
instruction.sig_lookup( | ||
msg_hash, sig_v.lo.expr() - FQ(27), sig_r, sig_s, recovered_addr, is_recovered | ||
) | ||
else: | ||
instruction.constrain_zero(is_recovered) | ||
instruction.constrain_zero(recovered_addr) | ||
|
||
# Restore caller state to next StepState | ||
instruction.step_state_transition_to_restored_context( | ||
rw_counter_delta=instruction.rw_counter_offset, | ||
return_data_offset=FQ.zero(), | ||
return_data_length=FQ(32) if is_recovered == FQ(1) else FQ.zero(), | ||
gas_left=instruction.curr.gas_left - EcrecoverGas, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: "and following" -> "and the following", "v is either" -> "The value v is either"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
fixed in 86866f9