Skip to main content

ERC-7579 validator module

PQValidatorModule is an ERC-7579 validator module written in Solidity that stores each account's ML-DSA-65 public key and delegates all signature verification to the Stylus verifier.

Compatible with any ERC-7579 smart account (Kernel, Safe, Rhinestone). Tested end-to-end with Kernel v3.

Architecture

flowchart TD
EP[EntryPoint v0.7] -->|validateUserOp| K[Kernel v3<br/>Smart Account]
K -->|IValidator.validateUserOp| PQ[PQValidatorModule]
PQ -->|IMLDSAVerifier.verify<br/>pubKey + msgHash + sig| S[Stylus Verifier<br/>Rust/WASM]
S -->|bool| PQ
PQ -->|0 = success<br/>1 = failure| K

subgraph Storage
PQ ---|mapping: address → bytes| DB[(Public Keys<br/>1,952 bytes each)]
end

Contracts

FilePurpose
src/PQValidatorModule.solValidator module: onInstall / onUninstall, validateUserOp, isValidSignatureWithSender (ERC-1271)
src/interfaces/IMLDSAVerifier.solInterface to the Stylus verifier: verify(bytes, bytes32, bytes) → bool

Build and test

# Build
forge build --root evm/

# Test (15 test cases)
forge test --root evm/ -vvv

# Format check
forge fmt --root evm/ --check

Deployment

Automated

./scripts/dev-stack.sh # deploys verifier + module

Manual

# 1. Deploy (requires Stylus verifier address)
BYTECODE=$(jq -r '.bytecode.object' evm/out/PQValidatorModule.sol/PQValidatorModule.json)
ARGS=$(cast abi-encode "constructor(address)" $STYLUS_VERIFIER)
cast send --private-key $PK --rpc-url $RPC --create "${BYTECODE}${ARGS:2}"

# 2. Install on Kernel account (1,952-byte ML-DSA public key as init data)
# moduleType = 1 (validator), initData = raw public key bytes

# 3. Grant selector access for non-root usage
# Kernel.grantAccess(selector, pqModule) for execute(bytes32,bytes)

Kernel integration notes

  • Non-root validator nonce key: 0x0001{20-byte validator address}0000.
  • verificationGasLimit must be at least 2,000,000 (Stylus verify ~374K + Kernel overhead).
  • Kernel must be compiled with FOUNDRY_PROFILE=deploy (via-ir) to stay under the 24 KB contract size limit.

Production limitations

LimitationDetail
Mock-based testsUnit tests use vm.mockCall to stub the Stylus verifier. Real integration requires a running devnode.
No aggregationSingle-validator, non-aggregated path only.
Verifier upgrade = redeployImmutable verifier address means upgrading the ML-DSA implementation requires deploying a new module.
No key rotationOnce installed, the public key can only be changed by uninstalling and reinstalling the module.
ERC-1271 sender allowlistisValidSignatureWithSender currently accepts any sender. Per-account allowlisting is on the roadmap.