ERC-4337 Privacy-Protected Wallet
Overview
This skill provides comprehensive support for developing ERC-4337 compliant Account Abstraction wallets with privacy-preserving features, specifically designed for vehicle identity and other sensitive data use cases. It combines smart contract templates, testing frameworks, privacy validation tools, and security best practices to enable the secure implementation of wallets that protect user privacy while maintaining on-chain functionality.
Key Capabilities:
- Privacy-protected smart contract templates (PrivacyProtectedAccount, AccountFactory, VehicleRegistry)
- Commitment scheme implementation for sensitive data (vehicle plates, personal info)
- Deterministic address generation using CREATE2
- Comprehensive test suites with privacy verification
- Automated privacy validation tooling
- Security and compliance checklists
When to Use This Skill
Invoke this skill when you encounter requests involving:
- Vehicle Identity Wallets: "Create a wallet from vehicle license plate information"
- ERC-4337 Implementation: "Implement account abstraction for privacy-protected accounts"
- Sensitive Data Protection: "Build wallet that hides user's personal identification"
- Deterministic Address Generation: "Generate predictable wallet addresses from private data"
- ZK Proof Integration: "Add zero-knowledge proof verification to ERC-4337 wallet"
- Privacy Auditing: "Validate that my smart contract doesn't leak sensitive information"
Quick Start Guide
1. Understand the Requirements
Ask the user these key questions:
- What sensitive data needs protection? (vehicle plate, biometric, etc.)
- Is deterministic address generation required?
- Are ZK proofs needed, or is commitment hashing sufficient?
- What is the target blockchain/testnet?
2. Set Up the Project
bash
1# Copy contract templates
2cp -r assets/contracts/* ./contracts/
3
4# Install dependencies
5cd contracts
6npm install
7
8# Compile contracts
9npm run compile
3. Customize for Your Use Case
For vehicle-based wallets:
solidity
1// Off-chain: Compute vehicle commitment
2const plateNumber = "ABC-1234";
3const userSalt = ethers.id("user-secret-entropy");
4const commitment = ethers.keccak256(
5 ethers.solidityPacked(["string", "bytes32"], [plateNumber, userSalt])
6);
7
8// Deploy account with commitment
9const account = await factory.createAccount(
10 ownerAddress,
11 commitment,
12 12345 // deployment salt
13);
For biometric/other sensitive data:
solidity
1// Replace plateNumber with your sensitive data
2const biometricHash = computeHash(biometricData);
3const commitment = ethers.keccak256(
4 ethers.solidityPacked(["bytes32", "bytes32"], [biometricHash, userSalt])
5);
4. Run Privacy Validation
bash
1# Validate that contracts don't leak sensitive data
2python scripts/validate_privacy.py contracts/PrivacyProtectedAccount.sol
5. Run Tests
bash
1# Run comprehensive test suite
2npm test
3
4# Run specific test file
5npx hardhat test tests/PrivacyProtectedAccount.test.ts
Core Components
1. PrivacyProtectedAccount
The main account contract that implements ERC-4337's IAccount interface with privacy features.
Key Features:
- Stores only commitment (hash) of sensitive data, never raw values
- Deterministic address generation via CREATE2
- ERC-4337 compliant UserOperation validation
- Owner-based access control
- Batch transaction execution
Usage:
solidity
1// Deploy via factory
2const account = await factory.createAccount(owner, vehicleCommitment, salt);
3
4// Verify ownership without revealing data
5const isValid = await account.verifyVehicleOwnership(plateNumber, userSalt);
6
7// Execute transaction
8await account.execute(targetAddress, value, calldata);
Privacy Guarantee:
- Vehicle commitment is stored as
bytes32 hash
- Raw plate number never touches blockchain
- Verification is view-only (off-chain safe)
2. AccountFactory
Factory contract for deploying PrivacyProtectedAccount instances with deterministic addresses.
Key Features:
- CREATE2-based deterministic deployment
- Counterfactual address computation
- Batch account creation
- Cross-chain address consistency
Usage:
solidity
1// Compute address before deployment
2const predictedAddress = await factory.getAddress(owner, commitment, salt);
3
4// Deploy account
5const account = await factory.createAccount(owner, commitment, salt);
6
7// Address matches prediction
8assert(accountAddress === predictedAddress);
Use Cases:
- Receive funds before account creation
- Consistent addresses across multiple chains
- Privacy-preserving wallet generation
3. VehicleRegistry
Optional registry contract for managing vehicle-to-wallet mappings with privacy.
Key Features:
- Maps commitments to wallet addresses
- Authorized verifier system
- Selective disclosure support
- Batch registration
Usage:
solidity
1// Register vehicle with commitment only
2await registry.registerVehicle(commitment, walletAddress, metadataHash);
3
4// Verify registration (authorized only)
5const isRegistered = await registry.verifyVehicleRegistration(commitment);
6
7// Update commitment (ownership transfer)
8await registry.updateVehicleCommitment(oldCommitment, newCommitment);
Implementation Workflow
Step 1: Define Privacy Model
Determine what needs privacy protection:
Example - Vehicle Wallet:
Sensitive: License plate number
Public: Wallet address, transaction history
Commitment: keccak256(plateNumber + salt)
Example - Biometric Wallet:
Sensitive: Fingerprint hash
Public: Wallet address
Commitment: keccak256(fingerprintHash + salt)
Step 2: Generate Commitment Off-Chain
CRITICAL: Never pass raw sensitive data to blockchain functions.
javascript
1// Frontend/Backend - OFF-CHAIN computation
2import { ethers } from 'ethers';
3
4function generateCommitment(sensitiveData, userSecret) {
5 // Generate cryptographically secure salt
6 const salt = ethers.keccak256(
7 ethers.solidityPacked(
8 ['string', 'uint256', 'address'],
9 [userSecret, Date.now(), userAddress]
10 )
11 );
12
13 // Compute commitment
14 const commitment = ethers.keccak256(
15 ethers.solidityPacked(['string', 'bytes32'], [sensitiveData, salt])
16 );
17
18 return { commitment, salt };
19}
20
21// Usage
22const { commitment, salt } = generateCommitment(licensePlate, userSecret);
23// Store salt securely off-chain!
24// Send only commitment to blockchain
Step 3: Deploy Account
javascript
1// Connect to factory
2const factory = await ethers.getContractAt('AccountFactory', factoryAddress);
3
4// Optional: Predict address first
5const predictedAddress = await factory.getAddress(
6 ownerAddress,
7 commitment,
8 deploymentSalt
9);
10console.log('Account will be deployed at:', predictedAddress);
11
12// Deploy account
13const tx = await factory.createAccount(ownerAddress, commitment, deploymentSalt);
14await tx.wait();
15
16console.log('Account deployed at:', predictedAddress);
Step 4: Create and Submit UserOperation
javascript
1import { ethers } from 'ethers';
2
3// Build UserOperation
4const userOp = {
5 sender: accountAddress,
6 nonce: await account.getNonce(),
7 initCode: '0x', // Empty if account exists
8 callData: account.interface.encodeFunctionData('execute', [
9 targetAddress,
10 value,
11 data
12 ]),
13 accountGasLimits: ethers.solidityPacked(
14 ['uint128', 'uint128'],
15 [verificationGasLimit, callGasLimit]
16 ),
17 preVerificationGas,
18 gasFees: ethers.solidityPacked(
19 ['uint128', 'uint128'],
20 [maxPriorityFeePerGas, maxFeePerGas]
21 ),
22 paymasterAndData: '0x', // Or paymaster address + data
23 signature: '0x' // Will be filled after signing
24};
25
26// Sign UserOperation
27const userOpHash = await account.getUserOpHash(userOp);
28const signature = await owner.signMessage(ethers.getBytes(userOpHash));
29userOp.signature = signature;
30
31// Submit to bundler
32await bundler.sendUserOperation(userOp);
Step 5: Verify Privacy Protection
bash
1# Run automated privacy validation
2python scripts/validate_privacy.py contracts/YourContract.sol
3
4# Expected output:
5# ✅ PRIVACY VALIDATION PASSED
6# No privacy violations detected.
Privacy Patterns
Pattern 1: Basic Commitment
solidity
1// Off-chain
2const commitment = keccak256(abi.encodePacked(sensitiveData, salt));
3
4// On-chain
5bytes32 public dataCommitment;
6
7function initialize(bytes32 _commitment) external {
8 dataCommitment = _commitment;
9}
10
11function verify(string memory data, bytes32 salt) external view returns (bool) {
12 return keccak256(abi.encodePacked(data, salt)) == dataCommitment;
13}
Pattern 2: Multi-Attribute Commitment
solidity
1// For vehicles with multiple attributes
2struct VehicleData {
3 string plateNumber;
4 string model;
5 uint256 year;
6}
7
8// Off-chain
9const commitment = keccak256(abi.encode(
10 vehicleData.plateNumber,
11 vehicleData.model,
12 vehicleData.year,
13 salt
14));
Pattern 3: ZK Proof Integration
For advanced privacy (see references/privacy-patterns.md for full examples):
solidity
1// Verify ZK proof instead of revealing data
2function _validateSignature(
3 PackedUserOperation calldata userOp,
4 bytes32 userOpHash
5) internal override returns (uint256) {
6 (bytes memory proof, bytes memory publicInputs) =
7 abi.decode(userOp.signature, (bytes, bytes));
8
9 require(zkVerifier.verify(proof, publicInputs), "Invalid proof");
10 return SIG_VALIDATION_SUCCESS;
11}
Testing Strategy
Privacy Tests
typescript
1describe("Privacy Protection", () => {
2 it("should not store raw sensitive data", async () => {
3 // Verify storage doesn't contain plate number
4 for (let i = 0; i < 20; i++) {
5 const storage = await ethers.provider.getStorage(account.address, i);
6 expect(storage).to.not.include(ethers.hexlify(ethers.toUtf8Bytes(plateNumber)));
7 }
8 });
9
10 it("should verify with correct preimage", async () => {
11 const isValid = await account.verifyVehicleOwnership(plateNumber, salt);
12 expect(isValid).to.be.true;
13 });
14
15 it("should reject wrong preimage", async () => {
16 const isValid = await account.verifyVehicleOwnership("WRONG-PLATE", salt);
17 expect(isValid).to.be.false;
18 });
19});
ERC-4337 Tests
typescript
1describe("UserOperation Validation", () => {
2 it("should validate correct signature", async () => {
3 const userOp = buildUserOp({ sender: account.address });
4 const userOpHash = await entryPoint.getUserOpHash(userOp);
5 userOp.signature = await owner.signMessage(ethers.getBytes(userOpHash));
6
7 await expect(entryPoint.handleOps([userOp], bundler.address))
8 .to.not.be.reverted;
9 });
10});
Security Checklist
Before deploying to mainnet, verify:
See references/security-checklist.md for complete checklist.
Common Pitfalls
❌ Storing Raw Sensitive Data
solidity
1// NEVER DO THIS
2contract BadExample {
3 string public licensePlate; // Publicly visible!
4}
✅ Correct Approach
solidity
1// DO THIS
2contract GoodExample {
3 bytes32 public vehicleCommitment; // Only hash stored
4}
❌ Weak Salt Generation
solidity
1// WEAK - predictable
2bytes32 salt = bytes32(block.timestamp);
✅ Strong Salt
solidity
1// STRONG - unique per user with high entropy
2bytes32 salt = keccak256(abi.encodePacked(
3 userSecret,
4 block.timestamp,
5 block.prevrandao,
6 msg.sender
7));
❌ Exposing Data in Events
solidity
1// NEVER DO THIS
2event VehicleRegistered(string plateNumber);
✅ Events with Commitments
solidity
1// DO THIS
2event VehicleRegistered(bytes32 indexed commitment);
Advanced Topics
ZK-SNARK Integration
For zero-knowledge proof integration, see references/privacy-patterns.md section on ZK proofs.
High-level flow:
- Generate ZK circuit for vehicle ownership
- Prove knowledge of plate number without revealing it
- Verify proof on-chain in
_validateSignature()
Cross-Chain Deployment
bash
1# Deploy to multiple networks with same addresses
2npx hardhat run scripts/deploy.ts --network sepolia
3npx hardhat run scripts/deploy.ts --network polygonAmoy
4
5# Addresses match due to CREATE2 determinism
Gasless Transactions with Paymaster
javascript
1// Add paymaster to UserOperation
2userOp.paymasterAndData = ethers.solidityPacked(
3 ['address', 'bytes'],
4 [paymasterAddress, paymasterData]
5);
6
7// User doesn't pay gas - paymaster sponsors it
Resources
Bundled Resources
-
assets/contracts/: Production-ready contract templates
PrivacyProtectedAccount.sol - Main account contract
AccountFactory.sol - Deterministic factory
VehicleRegistry.sol - Optional registry
package.json, hardhat.config.ts - Project setup
-
assets/tests/: Comprehensive test suites
- Privacy protection tests
- ERC-4337 compliance tests
- Gas optimization tests
-
scripts/validate_privacy.py: Automated privacy validation tool
- Scans for sensitive data leaks
- Validates commitment patterns
- Checks event safety
-
references/: In-depth documentation
erc4337-architecture.md - ERC-4337 deep dive
privacy-patterns.md - Privacy implementation patterns
security-checklist.md - Comprehensive security guide
External Resources
Support and Troubleshooting
Common Issues
Issue: Gas estimation fails
Solution: Ensure account has sufficient deposit in EntryPoint
await account.addDeposit({ value: ethers.parseEther("0.1") });
Issue: Signature validation fails
Solution: Check that userOpHash is computed correctly
const userOpHash = await entryPoint.getUserOpHash(userOp);
Issue: Privacy validation fails
Solution: Review reported violations and fix
- Remove string storage for sensitive data
- Make verification functions view/pure
- Use commitments instead of raw data
Getting Help
- Check
references/ for detailed documentation
- Review test files for usage examples
- Run privacy validation for security issues
- Consult ERC-4337 documentation for protocol questions
参考文献