ZK Account Authentication
Table of Contents
We authenticate transactions using zero-knowledge proofs instead of the usual ECDSA signature scheme. This allows the keystore to support arbitrary smart account types without additional protocol changes.
ZK Proof Format
We specify the authentication of a message hash msgHash against data committed to by dataHash using a verification key vkey for a halo2 circuit. A proof validating against vkey will establish the following statement:
Assuming the authentication is against data committed to by
dataHash, there is valid authentication data to approve the message inmsgHash.
To implement this, we require the public inputs PI(pf) to be a vector with 2 <= len(PI(pf)) <= 4 forming a byte subarray of pf, with each element being 32 bytes and representing a value in the 254-bit scalar field of the BN254 curve. This vector should encode the following values:
PI(pf)[0:-2] – optional extra public values (a max of two extra public values are supported)
PI(pf)[-2:] – hi-lo representation of bytes32 keccak256(dataHash, parentDataHash, msgHash)
The last two elements are required and will be where the STF expects a hi-lo hash of the authentication-related data to reside. The protocol also offers some flexibility for a circuit to expose at most two additional public values whose constraints will be transparent to the STF.
We say that bytes32 values (a, b) form a hi-lo representation of a bytes32 value c if a and b are 16 bytes and c = (a << 128) || b, i.e. a contains the top 16 bytes of c, and b contains the lower 16 bytes of c.
Account Authentication
To verify a ZK account authentication for msgHash, we perform the following check:
function authenticateMsg(bytes32 dataHash, bytes32 msgHash, bytes proof, bytes verificationData) {
bytes32 extraPvsHash = verificationData[0:32];
bytes vkey = verificationData[32:];
require(keccak256(publicInputs[0:-2]) == extraPvsHash);
require(publicInputs[-2:] == keccak256(abi.encodePacked(dataHash, parentDataHash, msgHash)));
require(proof is valid against vkey);
}
Example: 1-of-1 ECDSA Multisig
As an example, each L1 EOA can be associated with a 1-of-1 ECDSA multisig on the keystore as follows.
1-of-1 ECDSA multisig
=====================
Key:
* keccak256(salt, keccak256(eoa_addr), keccak256(keccak256(0x) || vk))
Public IO:
* PI[0:2] -- keccak256(abi.encodePacked(dataHash, parentDataHash, msgHash))
Circuit statement:
* `keccak256(eoa_addr) == dataHash`
* there is an ECDSA signature for `msgHash` which verifies against `pub_key`
* `eoa_addr` corresponds to `pub_key`
STF enforces:
* `keccak256(PI[0:0]) == keccak256(0x)`