Skip to content

duongja/VeilSafe

Repository files navigation

VeilSafe

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.

What Is Working

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.

Why VeilSafe Exists

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.

Architecture

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
Loading

Main pieces

  • contracts/safe/ZKMultiSigEcdsaProxy.sol Stores the public commitment and verifier address.
  • contracts/safe/ZKMultiSigEcdsaSingleton.sol Implements ERC-1271 validation logic for Safe.
  • contracts/ERC8039/IERC8039.sol Common verifier interface for proof verification.
  • contracts/ERC8039/ZKMultiSigEcdsaProofVerifier.sol Transaction-proof adapter.
  • contracts/ERC8039/PrivateStateValidationProofVerifier.sol Deployment-time state validation adapter.
  • noir/zk_multi_sig_ecdsa Execution circuit.
  • noir/zk_multi_sig_ecdsa_private_state_validation Initial configuration circuit.

Privacy Model

Public on-chain

  • 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

Kept private by the flow

  • signer EOA addresses
  • private signer threshold
  • which signers approved a given transaction
  • raw witness data used to generate the proof

Verified Local Comparison

Two runnable flows are included:

  1. localhostDemo Runs the VeilSafe ZK flow end to end on a local Anvil chain.
  2. scripts/public-safe-localhost-demo.ts Deploys 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 stateRoot commitment and verifier address

Generated comparison artifacts:

  • local/localhost-demo.latest.json
  • local/public-safe-demo.latest.json
  • local/safe-privacy-comparison.latest.json
  • local/safe-privacy-comparison.latest.md

Quickstart

Prerequisites

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.18

Install Dependencies

pnpm install

Start a Local Chain

anvil --chain-id 31337

Compile Everything

pnpm compile

pnpm compile does three things:

  • compiles the Noir circuits
  • generates Solidity verifiers from the compiled Noir artifacts
  • compiles the Solidity contracts with Hardhat

Run The Private Safe Demo

npx hardhat --network localhost localhostDemo

This 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.json
  • local/proofs/

Run The Plain Safe Demo

npx ts-node scripts/public-safe-localhost-demo.ts

This 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.

Generate The Privacy Report

pnpm compare:privacy

This reads the latest ZK and plain Safe runs, queries the live localhost chain, and writes:

  • local/safe-privacy-comparison.latest.json
  • local/safe-privacy-comparison.latest.md

Use the markdown report when you want a compact human-readable summary of what each Safe reveals on-chain.

Manual Flow

If you want to inspect the pieces individually instead of using localhostDemo, these Hardhat tasks are available:

1. Deploy local Safe core contracts

npx hardhat --network localhost deployLocalSafeInfrastructure

2. Deploy the verifier stack and signer factory

npx hardhat --network localhost deployVeilSafeFactory

3. Create the private signer proxy

npx hardhat --network localhost createPrivateSignerProxy \
  --privatesigners 0xSigner1,0xSigner2 \
  --privatethreshold 2 \
  --factoryaddress 0xFactory \
  --txvalidationverifieraddress 0xVerifier

4. Create the Safe

npx hardhat --network localhost createVeilSafe \
  --owners 0xProxyOwner \
  --threshold 1

5. Sign the Safe transaction hash off-chain

npx hardhat --network localhost sign \
  --safe 0xSafe \
  --to 0xRecipient \
  --value 0 \
  --data 0x \
  --privatekey 0xSignerPrivateKey

6. Build the proof

npx hardhat --network localhost prove \
  --safe 0xSafe \
  --txhash 0xSafeTxHash \
  --signatures 0xSig1,0xSig2 \
  --privatesigners 0xSigner1,0xSigner2 \
  --privatethreshold 2 \
  --signersaddressesformat 0

The proof artifact is written under local/proofs/.

7. Execute the Safe transaction

npx hardhat --network localhost send \
  --safe 0xSafe \
  --to 0xRecipient \
  --value 0 \
  --data 0x \
  --proof local/proofs/<proof-file>.json

Repository Layout

contracts/
  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

Developer Notes

  • The documented path in this README is the Safe localhost flow.
  • localhostDemo is 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.

Security Notes

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.

License

Apache-2.0

About

private multisig ownership using ZK

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors