ECRecover


Ecrecover PI

Input

GitHubarrow-up-right

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

GitHubarrow-up-right

pub struct PrecompileFunctionOutputData<F: SmallField> {
    pub final_memory_state: QueueState<F, FULL_SPONGE_QUEUE_STATE_WIDTH>,
}

FSM Input and FSM Output

GitHubarrow-up-right

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.pdfarrow-up-right

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.

  1. 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.

  1. Next, the circuit checks whether or not the given x input (which is the x-coordinate of the signature) falls within the scalar field of the curve. Since, in ecrecover, x = r + kn, almost any r will encode a unique x-coordinate, except for when r > scalar_field_modulus. If this is the case, x = r + n, otherwise, x = r. x is recovered here from r.

  1. 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 UInt256 numbers. 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.

  1. Now we are going to compute t and check whether or not it is quadratic residue in the base field. To start, we take x which we calculated before, and calculate t by doing x^3 + b, where b is the B parameter of the secp256k1 curve. We check to make sure that t is not zero.

  1. The Legendre symbol for t is computed to do a quadratic residue check. We need to compute t^b which corresponds to t^{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 of t is created (up to t^255). Then, we multiply together all the elements in the denominator of the equation, which are t^{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 with t^b.

  1. 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.

  1. Then, proceed with the quadratic residue check. In case t is nonresidue, we swap out our inputs for the hardcoded ‘valid’ inputs.

  1. The next step is computing the public key. We compute the public key Q by calculating Q = (s * X - hash * G) / r. We can simplify this in-circuit by calculating s / r and hash / r separately, 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:

  1. 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.

  1. 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