Privacy-preserving social recovery for Safe wallets using secret guardians on Aztec Network.
Demo Ready: This project is deployed on Aztec Devnet + Sepolia and ready for testing.
Try it now:
Either:
- Deploy a new safe with our app: Safe App Link
- Use existing safe and add app in the "Apps" section as "Custom App"
Open Safe → Apps → Custom Apps → Add frontend-coral-gamma-16.vercel.app → Open it → Press "Get Started" → Connect Aztec wallet → Enable module → Press "Manage Guardians" → Add new guardian
1-setup-with-existing-safe.mp4
Guardian opens Guardian Portal → connects Aztec wallet → Enters Safe address → Votes for new owner
2-guardian-portal.mp4
Guardians vote for recovery candidate
Once threshold reached, Wormhole relays to Sepolia and Safe ownership transfers
4-recovery-complete.mp4
Requirements: Chrome 85+, Azguard Wallet
# 1. Clone and install
git clone https://github.com/alik-eth/aztec-safe-recovery.git
cd aztec-safe-recovery
npm install
# 2. Start the frontend
cd packages/frontend
npm run dev
# Open http://localhost:3000Traditional social recovery systems expose guardian identities on-chain. This creates privacy and security risks:
- Attackers know who to target for social engineering
- Guardians can be coerced or bribed
- The social graph of wallet owners is publicly visible
Secret Guardians on Aztec: Store guardian identities privately on Aztec Network. When recovery is needed, guardians vote anonymously, and only the recovery action (not guardian identities) is revealed.
┌─────────────────────────────────────────────────────────────────────────────┐
│ SETUP PHASE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Safe Owner Aztec Network │
│ ────────── ───────────── │
│ │
│ 1. Register Safe ──────────► Recovery Registry │
│ (links EVM Safe to │ │
│ Aztec wallet) │ │
│ │ │
│ 2. Add Secret Guardians ────► │ [Guardian Notes - PRIVATE] │
│ (Aztec addresses) │ - Guardian A: 0x123... │
│ │ - Guardian B: 0x456... │
│ │ - Guardian C: 0x789... │
│ │ │
│ 3. Set Threshold ───────────► │ Threshold: 2 of 3 │
│ │ │
│ 4. Install Module ──────────► Safe Wallet (EVM) │
│ on Safe │ │
│ │ [SafeRecoveryModule enabled] │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────────┐
│ RECOVERY PHASE │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ Guardian A (Aztec) Aztec Network Safe (EVM) │
│ ────────────────── ───────────── ────────── │
│ │
│ 1. Start Vote ─────────────► Recovery Registry │
│ candidate: 0xNEW_OWNER │ │
│ (EVM address) │ Vote started for │
│ │ Safe: 0xSAFE │
│ │ Candidate: 0xNEW_OWNER │
│ │ Votes: 1/2 │
│ │
│ Guardian B (Aztec) │ │
│ ────────────────── │ │
│ │ │
│ 2. Vote ───────────────────► │ Votes: 2/2 ✓ │
│ (anonymous) │ Threshold reached! │
│ │ │
│ │ │
│ 3. Send Wormhole Message ───────┼──────────────────► Wormhole │
│ [Safe, NewOwner] │ │ │
│ │ │ │
│ │ ▼ │
│ │ Relayer picks up │
│ │ VAA message │
│ │ │ │
│ │ ▼ │
│ ◄────┼─────────────── SafeRecoveryModule │
│ │ executes recovery │
│ │ │ │
│ │ ▼ │
│ │ Safe owner changed │
│ │ to 0xNEW_OWNER │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
| What's Private | What's Public |
|---|---|
| Guardian identities (Aztec addresses) | That a Safe has recovery enabled |
| Number of guardians | Recovery threshold |
| Who voted | That threshold was reached |
| Guardian-owner relationships | The new owner address (after recovery) |
packages/
├── aztec-contracts/
│ └── recovery/ # Noir contract - Secret guardian registry
│ ├── src/main.nr # Main contract logic
│ ├── src/notes.nr # GuardianNote, VoteNote definitions
│ └── src/config.nr # Configuration structs
│
├── evm-contracts/
│ └── recovery-sol/ # Solidity contracts
│ ├── SafeRecoveryModule.sol # Module enabled on Safe
│ └── AztecRecoveryValidator.sol # Validates Wormhole messages
│
├── relayer/ # Go service
│ └── relayer.go # Bridges Wormhole VAAs to EVM
│
└── frontend/ # Next.js 15 + React 19
├── app/
│ ├── page.tsx # Landing page
│ ├── setup/ # Setup wizard for Safe owners
│ └── guardian/ # Guardian portal for voting
└── components/
├── safe/ # Safe wallet integration
└── providers/ # Wagmi + RainbowKit
A shared registry where multiple Safe wallets can register their secret guardians.
// Storage structure
struct Storage {
safe_owners: Map<EthAddress, AztecAddress>, // Safe → Owner's Aztec wallet
thresholds: Map<EthAddress, u32>, // Safe → Required votes
guardians: PrivateSet<GuardianNote>, // Private guardian storage
votes: Map<EthAddress, Map<EthAddress, VoteNote>>, // Safe → Candidate → Votes
}
// Key functions
fn register_safe(safe_address: EthAddress, threshold: u32)
fn add_guardian(safe_address: EthAddress, guardian: AztecAddress) // Private
fn remove_guardian(safe_address: EthAddress, guardian: AztecAddress) // Private
fn start_vote(safe_address: EthAddress, candidate: EthAddress) // Private
fn vote(safe_address: EthAddress, candidate: EthAddress) // Private
fn send_wormhole_message(safe_address: EthAddress, candidate: EthAddress, msg: [[u8; 31]; 7])A singleton module that any Safe can enable. Receives recovery instructions via Wormhole.
contract SafeRecoveryModule {
IValidator7579 public immutable validator;
function applyRecovery(
address targetSafe,
address[] calldata newOwners,
uint256 newThreshold,
bytes32 nonce,
bytes calldata vaa // Wormhole Verified Action Approval
) external;
}Validates that recovery messages originated from the Aztec contract via Wormhole.
contract AztecRecoveryValidator is IValidator7579 {
IWormhole public wormhole;
bytes32 public aztecEmitterAddress;
uint16 public aztecChainId;
function validate(bytes calldata callData, bytes calldata vaa)
external view returns (bytes4);
}| Contract | Address | Etherscan |
|---|---|---|
| SafeRecoveryModule | 0x641a72f4B0BabE087A955aFeC6Da9E58bdB18643 |
View |
| AztecRecoveryValidator | 0x6b27676c01108FaB773e9731Fe3453d3E35a12E3 |
View |
| MockWormholeCore | 0xcA17193413115D712eE57ed74c9968f819Ae4b7E |
View |
| Contract | Address |
|---|---|
| Recovery Registry | Deploy your own via /setup or use existing |
| Wormhole Core | Built into Aztec devnet |
Note: Each Safe owner deploys their own Recovery contract on Aztec. The contract address is registered with the SafeRecoveryModule on Sepolia.
- Node.js >= 20.9.0
- Go >= 1.21 (for relayer)
- Foundry (for Solidity contracts)
- Aztec CLI (for Noir contracts)
# Install dependencies
npm install
# Build Aztec contracts
cd packages/aztec-contracts/recovery
aztec-nargo compile
# Build EVM contracts
cd packages/evm-contracts/recovery-sol
forge build
# Build relayer
cd packages/relayer
go build -o relayer .
# Install frontend dependencies
cd packages/frontend
npm installCopy environment files:
cp packages/relayer/.env.example packages/relayer/.env
cp packages/frontend/.env.example packages/frontend/.envConfigure the relayer (.env):
SPY_RPC_HOST=localhost:7073
SOURCE_CHAIN_ID=56 # Aztec application messages
DEST_CHAIN_ID=10002 # Sepolia chain ID in Wormhole
EVM_RPC_URL=https://0xrpc.io/sep
PRIVATE_KEY=0x...your_private_key
EVM_TARGET_CONTRACT=0x641a72f4B0BabE087A955aFeC6Da9E58bdB18643
ACCEPT_ANY_EMITTER=true # For testingImportant: Use
SOURCE_CHAIN_ID=56for recovery VAAs. Chain 26 is for Aztec internal messages only.
Configure the frontend (.env):
NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID=your_project_id
NEXT_PUBLIC_AZTEC_PXE_URL=https://pxe.devnet.aztec.networkNote: The Aztec recovery contract is deployed per-Safe during setup. Use Azguard wallet for Aztec devnet.
# Build and start the relayer
cd packages/relayer
go build -o relayer .
./relayer
# Start the frontend (in another terminal)
cd packages/frontend
npm run devRun the full end-to-end test (Aztec → Wormhole → Sepolia):
# Terminal 1: Start the relayer
cd packages/relayer
./relayer
# Terminal 2: Run the e2e test
cd packages/aztec-contracts/recovery-ts
PRIVATE_KEY=0x... npx tsx scripts/e2e_test.tsThe e2e test creates a new Safe, enables the module, deploys an Aztec recovery contract, adds a guardian, votes, and verifies the recovery completes via Wormhole.
- Connect Safe: Enter your Safe wallet address
- Connect Aztec: Connect your Aztec wallet (runs PXE locally)
- Add Guardians: Add Aztec addresses of your trusted guardians
- Set Threshold: Choose how many guardians needed (e.g., 2 of 3)
- Install Module: Enable SafeRecoveryModule on your Safe
- Connect Aztec Wallet: Connect to see Safes you're guarding
- Initiate Recovery: Start a vote with proposed new owner (EVM address)
- Vote: Other guardians vote to approve
- Execute: Once threshold reached, recovery is sent via Wormhole
- Guardian Privacy: Guardian addresses are stored in Aztec private notes, never revealed on-chain
- Double-Vote Prevention: Nullifiers prevent guardians from voting twice
- Threshold Delay: Threshold changes have a time delay to prevent last-minute attacks
- Wormhole Verification: All recovery messages are verified through Wormhole's guardian network
# Run Aztec contract tests
cd packages/aztec-contracts/recovery
aztec-nargo test
# Run EVM contract tests
cd packages/evm-contracts/recovery-sol
forge testMIT License - see LICENSE
