ECRecover
Ecrecover PI
Input
pub struct PrecompileFunctionInputData<F: SmallField> {
pub initial_log_queue_state: QueueState<F, QUEUE_STATE_WIDTH>,
pub initial_memory_queue_state: QueueState<F, FULL_SPONGE_QUEUE_STATE_WIDTH>,
}Output
pub struct PrecompileFunctionOutputData<F: SmallField> {
pub final_memory_state: QueueState<F, FULL_SPONGE_QUEUE_STATE_WIDTH>,
}FSM Input and FSM Output
pub struct EcrecoverCircuitFSMInputOutput<F: SmallField> {
pub log_queue_state: QueueState<F, QUEUE_STATE_WIDTH>,
pub memory_queue_state: QueueState<F, FULL_SPONGE_QUEUE_STATE_WIDTH>,
}Main circuit logic
This circuit implements the ecrecover precompile described in the Ethereum yellow paper: https://ethereum.github.io/yellowpaper/paper.pdf
The purpose of ecrecover is to recover the signer’s public key from digital signature.
A special note about this circuit is that there are hardcoded ‘valid’ field element values provided to the circuit. This is to prevent the circuit from not satisfying in case the user-provided inputs are incorrect and, when the circuit detects this, the bad values are swapped out for the hardcoded ones. In this event, exceptions are logged and pushed into a vector which are returned to the caller, informing them that the provided inputs were incorrect and the result should be discarded.
Most of the relevant circuit logic resides in the ecrecover_precompile_inner_routine function. Let’s take the circuit step by step.
The circuit starts off by declaring a set of constants which are useful to have throughout the circuit. These include the B parameter of the secp256k1 curve, the constant -1 in the curve’s base field, and the base field and scalar field modulus. We also create the vector that should capture any exceptions.
Next, the circuit checks whether or not the given
xinput (which is the x-coordinate of the signature) falls within the scalar field of the curve. Since, in ecrecover,x = r + kn, almost anyrwill encode a unique x-coordinate, except for whenr > scalar_field_modulus. If this is the case,x = r + n, otherwise,x = r.xis recovered here fromr.
Then, all field elements are interpreted as such within the circuit. As they are passed in, they are simply byte arrays which are interpreted initially as
UInt256numbers. These get converted to field elements by using the conversion functions defined near the top of the file. Additionally, checks are done to make sure none of the passed in field elements are zero.
Now we are going to compute
tand check whether or not it is quadratic residue in the base field. To start, we takexwhich we calculated before, and calculatetby doingx^3 + b, wherebis the B parameter of the secp256k1 curve. We check to make sure thattis not zero.
The Legendre symbol for
tis computed to do a quadratic residue check. We need to computet^bwhich corresponds tot^{2^255} / ( t^{2^31} * t^{2^8} * t^{2^7} * t^{2^6} * t^{2^5} * t^{2^3} * t). First, an array of powers oftis created (up tot^255). Then, we multiply together all the elements in the denominator of the equation, which aret^{2^31} * t^{2^8} * t^{2^7} * t^{2^6} * t^{2^5} * t^{2^3} * t. Lastly, the division is performed and we end up witht^b.
Before we proceed to the quadratic residue check, we take advantage of the powers we just calculated to compute the square root of
t, in order to determine whether the y-coordinate of the signature we’ve passed is positive or negative.
Then, proceed with the quadratic residue check. In case
tis nonresidue, we swap out our inputs for the hardcoded ‘valid’ inputs.
The next step is computing the public key. We compute the public key
Qby calculatingQ = (s * X - hash * G) / r. We can simplify this in-circuit by calculatings / randhash / rseparately, and then doing an MSM to get the combined output. First, we pre-compute these divided field elements, and then compute the point like so:
Now that we have our public key recovered, the last thing we will need to do is take the keccak hash of the public key and then take the first 20 bytes to recover the address.
At this point, we are basically done! What’s left now is to ensure we send a masked value in case of any exception, and then we can output the resulting address and any exceptions which occurred for the caller to handle. This wraps up the ecrecover circuit!
Last updated