Skip to content
This repository has been archived by the owner on Jul 5, 2024. It is now read-only.

Feat/#498 integrate precompiles into callop #508

Merged
merged 13 commits into from
Feb 21, 2024
Merged
24 changes: 24 additions & 0 deletions src/zkevm_specs/evm_circuit/execution/precompiles/ecrecover.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
from dataclasses import dataclass
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
from zkevm_specs.util.arithmetic import RLC

SECP256K1N = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141


@dataclass(frozen=True)
class PrecompileRlcData:
input_rlc: FQ
output_rlc: FQ


def ecRecover(instruction: Instruction):
is_success = instruction.call_context_lookup(CallContextFieldTag.IsSuccess, RW.Read)
address_word = instruction.call_context_lookup_word(CallContextFieldTag.CalleeAddress)
Expand All @@ -26,9 +34,25 @@ def ecRecover(instruction: Instruction):
sig_r: Word = instruction.curr.aux_data[2]
sig_s: Word = instruction.curr.aux_data[3]
recovered_addr: FQ = instruction.curr.aux_data[4]
rlc_data: PrecompileRlcData = instruction.curr.aux_data[5]
keccak_randomness: FQ = instruction.curr.aux_data[6]

is_recovered = FQ(instruction.is_zero(recovered_addr) != FQ(1))

# Verify input and output
input_bytes = bytearray(b"")
input_bytes.extend(msg_hash.int_value().to_bytes(32, "little"))
input_bytes.extend(sig_v.int_value().to_bytes(32, "little"))
input_bytes.extend(sig_r.int_value().to_bytes(32, "little"))
input_bytes.extend(sig_s.int_value().to_bytes(32, "little"))
input_rlc = RLC(bytes(reversed(input_bytes)), keccak_randomness, n_bytes=128).expr()
instruction.constrain_equal(rlc_data.input_rlc, input_rlc)
Copy link
Contributor

Choose a reason for hiding this comment

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

Aren't these two values actually the same (I mean not equal, but the same)? Because rlc_data.input_rlc is computed when instantiating the auxiliary data and input_rlc is computed accessing the same values in aux_data?

Copy link
Contributor Author

@KimiWu123 KimiWu123 Feb 20, 2024

Choose a reason for hiding this comment

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

For me, data assignment in testing is like what assign_exec_step does at proving time in our circuit. So, yes, it looks the same (but not always the same).
At proving time, a prover assigns

  • msg_hash
  • sig_v, sig_r, sig_s
  • RLC(msg_hash, sig_v, sig_r, sig_s) (a.k.a input_rlc here, this field is to verify data consistency between calls)

In verification logic here, we have to gaurantee msg_hash, sig_v, sig_r and sig_s are the same pairs (sig_v, sig_r and sig_s are coming from the signature of msg_hash). However, a malious signer could sign msg_hash2 and have sig_v2, sig_r2 and sig_s2. It still can pass all the constraints if we don't have calculate input_rlc here. I'm assuming rlc_data.input_rlc is coming from previous step (we can pass data around between previous step and next step in our circuit, but it seems not doable in our spec), so that's why rlc_data.input_rlc and input_rlc look "the same".

Does it make sense to you? Or do you think we need to copy rlc_data.input_rlc from copy_table?

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 I am missing something and would like to fix my understanding :). My understanding was that we need to ensure that instruction.curr.aux_data is the same as used in callop.py and that this should be done by checking the RLC, so to have a lookup call into copy_table (RLC field) here in ecrecover.py. Maybe this is not the case?

I agree on the assignment part, putting a link to the assignment was probably misleading from my side. What it seems to me here is that we have aux_data: PrecompileAuxData = instruction.curr.aux_data[0] and then both values, aux_data.input_rlc and input_rlc, are computed using aux_data. Or is it aux_data.input_rlc checked somewhere else?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My understanding was that we need to ensure that instruction.curr.aux_data is the same as used in callop.py and that this should be done by checking the RLC, so to have a lookup call into copy_table (RLC field) here in ecrecover.py. Maybe this is not the case?

Yes, that's exactly what I want to do.

I agree on the assignment part, putting a link to the assignment was probably misleading from my side. What it seems to me here is that we have aux_data: PrecompileAuxData = instruction.curr.aux_data[0] and then both values, aux_data.input_rlc and input_rlc, are computed using aux_data. Or is it aux_data.input_rlc checked somewhere else?

No, aux_data.input_rlc was not check in other place.

In my implementation, I treated aux_data as witness inputs, a place I can assign my witnesses. Those data (e.g. sig_r, sig_v...etc) can't be retrieved from stack or memory like what other opcode gagdets implemented.

I might not explain it very well, but you can check Scroll's impl. The msg_hash_raw was converted into rlc formate and compared with msg_hash_keccak_rlc. In the assignment, you can see both of them come from the same source, here and here.


output_rlc = RLC(
bytes(reversed(recovered_addr.n.to_bytes(32, "little"))), keccak_randomness, n_bytes=32
).expr()
instruction.constrain_equal(rlc_data.output_rlc, output_rlc)

# is_success is always true
# ref: https://github.com/ethereum/execution-specs/blob/master/src/ethereum/shanghai/vm/precompiled_contracts/ecrecover.py
instruction.constrain_equal(is_success, FQ(1))
Expand Down
17 changes: 16 additions & 1 deletion tests/evm/precompiles/test_ecRecover.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@
Tables,
verify_steps,
)
from zkevm_specs.evm_circuit.execution.precompiles.ecrecover import SECP256K1N
from zkevm_specs.evm_circuit.execution.precompiles.ecrecover import PrecompileRlcData, SECP256K1N
from zkevm_specs.util import (
Word,
FQ,
)
from zkevm_specs.evm_circuit.table import SigTableRow
from zkevm_specs.util.arithmetic import RLC


def gen_testing_data():
Expand Down Expand Up @@ -49,6 +50,8 @@ def gen_testing_data():

TESTING_DATA = gen_testing_data()

randomness_keccak = rand_fq()


@pytest.mark.parametrize(
"caller_ctx, msg_hash, v, r, s, address",
Expand All @@ -72,12 +75,24 @@ def test_ecRecover(
return_data_offset = 0
return_data_length = 0x20 if recovered else 0

input_bytes = bytearray(b"")
input_bytes.extend(msg_hash)
input_bytes.extend((v + 27).to_bytes(32, "little"))
input_bytes.extend(r.to_bytes(32, "little"))
input_bytes.extend(s.to_bytes(32, "little"))
input_rlc = RLC(bytes(reversed(input_bytes)), randomness_keccak, n_bytes=128).expr()
output_bytes = int.from_bytes(address, "big").to_bytes(32, "little")
output_rlc = RLC(bytes(reversed(output_bytes)), randomness_keccak, n_bytes=32).expr()
rlc_data = PrecompileRlcData(input_rlc, output_rlc)

aux_data = [
Word(msg_hash),
Word(v + 27),
Word(r),
Word(s),
FQ(int.from_bytes(address, "big")),
rlc_data,
randomness_keccak,
]

# assign sig_table
Expand Down
Loading