This project demonstrates verifying an Ethereum ECDSA signature inside a Noir circuit using the ecrecover
crate.
We generate a wallet, sign a message with our private key, split the public key into X and Y coordinates, feed them to the circuit, and prove the verification in zero-knowledge.
We start by hashing our message (hello
) using cast keccak
from Foundry:
cast keccak "hello"
# Output:
# 0x1c8aff950685c2ed4bc3174f3472287b56d9517b9c948127319a09a7a36deac8
cast wallet new
# Address: 0xcd7Cf0418480B063A86fCD93E792F0A908b7AaD6
# Private key: 0xPRIV_KEY
cast wallet pubkey --private-key
cast wallet sign --no-hash --private-key 0xPRIV_KEY 0xMESSAGE_HASH
--no-hash
because we do not want to hash the message again as this will lead to hash mismatch
chmod +x generate_inputs.sh
./generate_inputs.sh
# Wrote Prover.toml
nargo execute
# [zk_ecdsa] Circuit witness successfully solved
# [zk_ecdsa] Witness saved to target/zk_ecdsa.gz
bb prove -b ./target/zk_ecdsa.json -w ./target/zk_ecdsa.gz -o ./target
# Proof saved to "./target/proof"
bb write_vk -b ./target/zk_ecdsa.json -o ./target
# VK saved to "./target/vk"
bb verify -k ./target/vk -p ./target/proof
# Proof verified successfully
This project uses Noir’s ecrecover
crate to verify an Ethereum ECDSA signature inside a zero-knowledge proof.
- A plaintext message (
hello
) is hashed using Keccak256. - The resulting 32-byte hash is the value that will be signed.
- A new Ethereum private key is generated with Foundry's
cast wallet new
. - From this private key, the public key is derived and split into
X
andY
coordinates (each 32 bytes) representing a point on the secp256k1 elliptic curve. - The corresponding Ethereum address is computed from the public key.
- The hashed message is signed with the private key using ECDSA via
cast wallet sign
. - This produces the signature
(r, s, v)
which together form 65 bytes.
In this circuit, only(r, s)
are passed, sincev
is not needed forecrecover
inside the proof.
- The Noir circuit imports
ecrecover
and takes as public inputs:pub_key_x
(X coordinate of public key)pub_key_y
(Y coordinate of public key)signature
(64-byte concatenation ofr
ands
)hashed_message
(Keccak256 of the message)expected_address
(Ethereum address derived from public key)
- Inside the circuit,
ecrecover
is used to recover the Ethereum address from(pub_key_x, pub_key_y, signature, hashed_message)
. - An assertion checks that the recovered address equals
expected_address
.
nargo execute
runs the circuit and generates a witness file.bb prove
uses the compiled circuit and witness to generate a zero-knowledge proof.bb write_vk
creates the verification key.bb verify
verifies the proof, confirming that:- The given
(X, Y)
public key indeed corresponds to the provided Ethereum address. - The signature is valid for the given hashed message.
- All this is proven without revealing the private key.
- The given