Skip to content

jimmychu0807/semaphore-msa-validator

Repository files navigation

Semaphore Modular Smart Account Validator Module

Overview

This module is a validator module adheres to ERC-7579 standard that uses Semaphore to create a multi-signature owner validation. This means the smart account that incorporates this validator gains the following benefits:

  • The smart account become a M-N multi-sig wallet controlled by members added to the Semaphore group of the smart account.
  • Semaphore feature such as preserving the privacy of which members are signing a transaction, while guaranteeing that members cannot double-sign for a particular transaction.

Development of this project is part of the PSE Acceleration Program (FY24-1847).

Using the Module

# Install dependencies
pnpm install

# Build
pnpm run build

# Test
pnpm run test

Developer Documentation

Smart Contract Storage

SemaphoreMSAValidator contract stores the following information on-chain.

  • groupMapping: This object maps from the smart account address to a Semaphore group.
  • thresholds: The threshold number of proofs a particular smart account needs to collect for a transaction to be executed.
  • memberCount: The member count of a Semaphore group. The actual member commitments are stored in the smaphore contract Lean Incremental Merkle Tree structure.
  • acctTxCount: This object stores the transaction call data and value that are waiting to be proved (signed), and the proofs it has collected so far. This information is stored in the ExtCallCount data structure.
  • acctSeqNum: The sequence number corresponding to a smart account. This value is used when generating a transaction signature to uniquely identify a particular transaction.

Development Approach

After installing this validator, the smart account can only call three functions in this validator contract, initiateTx(), signTx(), and executeTx(). Calling other functions, either to other non-validator contract addresses or other funtions beyond the mentioned three, would be rejected in the validateUserOp() check.

  1. initiateTx(): for the Semaphore member to initate a new transaction of the Smart account. This function checks the validity of the semaphore proof and corresponding parameters. It takes three paramters.

    • targetAddr: The target address of the transaction. It can be an EOA for value transfer, or other smart contract address.
    • txcallData: The call data to the target address. The first four bytes are the target function selector, and the rest function payload. For EOA value transfer, this value must be null (zero-length byte).
    • proof: The zero-knowledge proof genereated off-chain to prove a member signed the transaction and value.
    • execute: A boolean value to indicate if the transaction collects enough proof (namely 1 for initiateTx), it will also execute the transaction.

    msg.value is used as the value to be used for the transaction call. An ExtCallCount object is created to store the user transaction call data.

    A 32-byte hash txHash is returned, generated from keccak256(abi.encodePacked(seq, targetAddr, msg.value, txCallData)).

  2. signTx(): for other Semaphore member to sign a previously initiated transaction. Again, it checks the Semaphore proof, if the hash and the proof are valid, the proof count is incremented.

    • txHash: The hash value returned from initiatedTx(), to specify the transaction for signing
    • proof: The zero-knowledge proof for the transaction txHash corresponding to.
    • execute: Same as initiateTx().
  3. executeTx(): call to execute the transaction specified by txHash. If the transaction hasn't collected enough proofs, it would revert.

    • txHash: Same as initiateTx().

Signature and Calldata

Transactions from ERC-4337 will go through validateUserOp() for validation, based on userOp, and userOpHash. In validation, the key logic is to check the userOp hash (userOpHash), the signature (signature), and the target call data (targetCallData).

A proper userOp signature is a 160 bytes value signed by EdDSA signature scheme. The signature itself is 32 * 3 = 96 bytes, but we also prepend the identity public key in it to be used for validation.

UserOp Signature

The userOpHash is 32-byte long, it is a keccak256() of sequence number, target address, value, and the target parameters.

For the UserOp calldata passing to getExecOps() in testing, it is:

UserOp Signature

Now, when decoding the calldata from PackedUserOperation object in validateUserOp(), the above calldata is combined with other information and what we are interested started from the 100th byte, as shown below.

calldata-packedUserOp

Verifying EdDSA Signature

A Semaphore identity consists of an EdDSA public/private key pair and a commitment. Semaphore uses an EdDSA implementation based on Baby Jubjub and Poseidon. The actual implementation is in zk-kit repository.

We implement the identity verification logic Identity.verifySignature() on-chain. We also have a Identity.verifySignatureFFI() function for testing to compare the result with calling Semaphore typescript-based implementation. It relies on the Baby JubJub curve Solidity implementataion by yondonfu with a minor fix.

ERC-1271 and ERC-7780

The module is also compatible with:

  • ERC-1271: Accepting signature from other smart contract by implementing isValidSignatureWithSender().
  • ERC-7780, Being a Stateless Validator by implementing validateSignatureWithData().

Testing

The testing code relies heavily on Foundry FFI to call Semaphore typescript API to generate zero-knowledge proof and EdDSA signature.

Relevant Information

ERC-4337 Lifecycle on Validation

ERC-4337 Lifecycle

Source: ERC-4337 website

Contributions

Thanks to the following folks on discussing about this project and helps along:

  • Saleel on initiating this idea with Semaphore Wallet, showing me that the idea is feasible.
  • Cedoor & Vivian on Semaphore development and their opinions.
  • John Guilding on the discussion, support, and review of the project.
  • Konrad Kopp on the support of using ModuleKit framework which this module is built upon, and answering my question on some details of ERC-4337 standard.

About

Semaphore Modular Smart Account Validator

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published