Private Safe ownership with zero-knowledge proofs
VeilSafe is a local-first reference implementation for private multisig ownership on top of Safe. Instead of storing signer EOAs and thresholds directly inside a Safe, VeilSafe installs a single ERC-1271 contract owner that validates zero-knowledge proofs through an ERC-8039-style verifier interface.
The result is a different on-chain surface:
- A plain Safe exposes owner addresses and threshold publicly.
- A VeilSafe-backed Safe exposes only a proxy owner, a verifier address, and a state commitment.
- The signer set, private threshold, and which signers approved a transaction stay off-chain.
This repository now supports a full localhost flow on Anvil for the Safe path:
- Deploy local Safe core contracts
- Deploy the ERC-8039-compatible verifier adapters
- Create a private signer proxy
- Create a Safe owned by that proxy
- Produce off-chain signatures
- Generate a Noir proof
- Execute the Safe transaction through ERC-1271 -> ERC-8039 verification
This repository also includes a plain Safe comparison flow so you can inspect the privacy difference directly on the same local chain.
Traditional smart-account ownership is programmable, but public. A Safe can express rich policies, yet the signer set and threshold are visible forever. VeilSafe shifts the sensitive ownership logic into a ZK circuit:
- the Safe sees one contract owner
- the contract owner sees a proof and public inputs
- the proof establishes that the hidden signer policy was satisfied
That gives you private multisig semantics without publishing the signer set as on-chain state.
flowchart LR
A["Hidden signers sign tx hash off-chain"] --> B["Noir circuit builds proof"]
B --> C["ERC-8039-style verifier adapter"]
C --> D["ERC-1271 proxy owner"]
D --> E["Safe executes transaction"]
F["State root commitment"] --> D
contracts/safe/ZKMultiSigEcdsaProxy.solStores the public commitment and verifier address.contracts/safe/ZKMultiSigEcdsaSingleton.solImplements ERC-1271 validation logic for Safe.contracts/ERC8039/IERC8039.solCommon verifier interface for proof verification.contracts/ERC8039/ZKMultiSigEcdsaProofVerifier.solTransaction-proof adapter.contracts/ERC8039/PrivateStateValidationProofVerifier.solDeployment-time state validation adapter.noir/zk_multi_sig_ecdsaExecution circuit.noir/zk_multi_sig_ecdsa_private_state_validationInitial configuration circuit.
- Safe address
- proxy owner address
- verifier address
- state root commitment
- transaction target, calldata, and value
- proof blob
- execution metadata such as gas usage and timing
- signer EOA addresses
- private signer threshold
- which signers approved a given transaction
- raw witness data used to generate the proof
Two runnable flows are included:
localhostDemoRuns the VeilSafe ZK flow end to end on a local Anvil chain.scripts/public-safe-localhost-demo.tsDeploys and executes a plain Safe with publicly visible owners.
After both have been run, pnpm compare:privacy writes a side-by-side report showing:
- the plain Safe publicly exposes its owner EOAs and threshold
- the VeilSafe-backed Safe exposes only the proxy owner in
getOwners() - the proxy exposes only a
stateRootcommitment and verifier address
Generated comparison artifacts:
local/localhost-demo.latest.jsonlocal/public-safe-demo.latest.jsonlocal/safe-privacy-comparison.latest.jsonlocal/safe-privacy-comparison.latest.md
This project is currently pinned around:
@noir-lang/noir_js:1.0.0-beta.18@aztec/bb.js:3.0.0-nightly.20260102
If nargo is not already installed:
curl -L https://raw.githubusercontent.com/noir-lang/noirup/main/install | bash
source ~/.zshrc
noirup --version 1.0.0-beta.18pnpm installanvil --chain-id 31337pnpm compilepnpm compile does three things:
- compiles the Noir circuits
- generates Solidity verifiers from the compiled Noir artifacts
- compiles the Solidity contracts with Hardhat
npx hardhat --network localhost localhostDemoThis task:
- deploys local Safe infrastructure
- deploys the verifier adapters and signer factory
- creates a private signer proxy
- creates a Safe owned by that proxy
- signs a Safe transaction with hidden EOAs
- generates the ZK proof
- executes the Safe transaction
- writes a summary file to
local/localhost-demo.latest.json
It also refreshes local deployment metadata in:
local/safe-contracts.31337.jsonlocal/proofs/
npx ts-node scripts/public-safe-localhost-demo.tsThis script deploys a standard 2-of-2 Safe with public EOA owners and executes a transaction without the ZK layer. Its summary is written to local/public-safe-demo.latest.json.
pnpm compare:privacyThis reads the latest ZK and plain Safe runs, queries the live localhost chain, and writes:
local/safe-privacy-comparison.latest.jsonlocal/safe-privacy-comparison.latest.md
Use the markdown report when you want a compact human-readable summary of what each Safe reveals on-chain.
If you want to inspect the pieces individually instead of using localhostDemo, these Hardhat tasks are available:
npx hardhat --network localhost deployLocalSafeInfrastructurenpx hardhat --network localhost deployVeilSafeFactorynpx hardhat --network localhost createPrivateSignerProxy \
--privatesigners 0xSigner1,0xSigner2 \
--privatethreshold 2 \
--factoryaddress 0xFactory \
--txvalidationverifieraddress 0xVerifiernpx hardhat --network localhost createVeilSafe \
--owners 0xProxyOwner \
--threshold 1npx hardhat --network localhost sign \
--safe 0xSafe \
--to 0xRecipient \
--value 0 \
--data 0x \
--privatekey 0xSignerPrivateKeynpx hardhat --network localhost prove \
--safe 0xSafe \
--txhash 0xSafeTxHash \
--signatures 0xSig1,0xSig2 \
--privatesigners 0xSigner1,0xSigner2 \
--privatethreshold 2 \
--signersaddressesformat 0The proof artifact is written under local/proofs/.
npx hardhat --network localhost send \
--safe 0xSafe \
--to 0xRecipient \
--value 0 \
--data 0x \
--proof local/proofs/<proof-file>.jsoncontracts/
ERC8039/ ERC-8039-style verifier interface and adapters
safe/ Safe-facing proxy, singleton, and factory contracts
noir/
zk_multi_sig_ecdsa/ Transaction authorization circuit
zk_multi_sig_ecdsa_private_state_validation/
Initial state validation circuit
scripts/
generate-noir-verifiers.ts Generates Solidity verifiers from Noir artifacts
public-safe-localhost-demo.ts Plain Safe comparison flow
compare-safe-privacy.ts Side-by-side privacy report
zksafe/
zksafe.ts Localhost orchestration and Hardhat task logic
local/
*.json / *.md Generated summaries, reports, and deployment artifacts
- The documented path in this README is the Safe localhost flow.
localhostDemois the fastest way to verify the project end to end.- Proof generation is not instant; expect some delay while Noir and UltraHonk produce the proof.
- Restarting Anvil invalidates local deployment addresses, so rerun the demo after each fresh chain start.
- The verifier boundary is proof-system agnostic at the interface level, but the working local flow in this repository currently uses the Noir + UltraHonk stack.
VeilSafe improves privacy at the account-ownership layer, but it does not make every part of a transaction private.
- Transaction targets and calldata are still public.
- The proof blob is still public.
- Correct replay protection depends on what is included in the public inputs.
- This repository should be treated as a reference implementation and demo environment, not an audited production release.
Apache-2.0