Privacy & Governance
Privacy & Governance Contracts
Three contracts implementing privacy-preserving payments and sybil-resistant governance on Starknet.
StealthRegistry
File: contracts/src/stealth/StealthRegistry.cairo (240 LOC)
ERC-5564-inspired stealth address system for privacy-preserving transfers on Starknet.
How It Works
1. Recipient registers meta-address (spending_pubkey, viewing_pubkey)
2. Sender generates ephemeral keypair, computes shared secret
3. Sender sends funds to derived stealth address
4. Sender calls announce() with ephemeral_pubkey + view_tag
5. Recipient scans announcements via view_tag to detect fundsRecipients register once; senders can transfer privately to any registered user without revealing the link between sender and recipient on-chain.
Interface
| Function | Access | Description |
|---|---|---|
register(spending_pubkey, viewing_pubkey) | Anyone | Register meta-address for stealth receiving |
update(spending_pubkey, viewing_pubkey) | Registered user | Update meta-address keys |
deregister() | Registered user | Remove registration |
get_meta_address(user) | View | Get user's public keys + registration status |
announce(ephemeral_pubkey, view_tag, stealth_address, token) | Anyone | Publish stealth transfer announcement |
get_announcement(index) | View | Read announcement by index |
get_announcement_count() | View | Total announcements (for pagination) |
is_registered(user) | View | Check registration status |
Events
| Event | Indexed Keys | Data |
|---|---|---|
MetaAddressRegistered | user | spending_pubkey, viewing_pubkey |
MetaAddressUpdated | user | spending_pubkey, viewing_pubkey |
MetaAddressRemoved | user | — |
StealthAnnouncement | stealth_address, view_tag | ephemeral_pubkey, token, timestamp, index |
Design Decisions
- Open announce(): Any address can publish announcements (per ERC-5564 spec). This prevents sender deanonymization through access control.
- View tags: 4-byte prefix of shared secret hash enables fast scanning without full decryption of every announcement.
- Append-only log: Announcements are stored sequentially for efficient indexing and pagination.
ConfidentialPool
File: contracts/src/privacy/ConfidentialPool.cairo (324 LOC)
Commitment-based deposit/withdraw system inspired by Vitalik's Privacy Pools paper. Uses Poseidon hash commitments and nullifiers.
How It Works
Deposit:
commitment = poseidon(secret, nullifier_preimage, amount, token)
user deposits fixed amount + commitment into pool
Withdraw:
nullifier = poseidon(nullifier_preimage)
user reveals preimage → contract reconstructs commitment
if commitment exists + nullifier unspent → transfer tokensInterface
| Function | Access | Description |
|---|---|---|
deposit(commitment) | Anyone | Deposit fixed amount with Poseidon commitment |
withdraw(nullifier, secret, nullifier_preimage, recipient) | Anyone | Withdraw by proving commitment knowledge |
has_commitment(commitment) | View | Check if commitment exists |
is_nullifier_spent(nullifier) | View | Check double-spend status |
get_pool_config() | View | Returns (token, deposit_amount, deposit_count) |
get_commitment_at(index) | View | Read commitment by index |
pause() / unpause() | Owner | Emergency pause |
add_to_blocked_set(commitment) | Owner | Block commitment (compliance) |
remove_from_blocked_set(commitment) | Owner | Unblock commitment |
Security Properties
- Double-spend prevention: Nullifier registry ensures each commitment can only be withdrawn once.
- Commitment uniqueness: Each commitment can only be deposited once.
- ReentrancyGuard: Manual reentrancy lock on deposit and withdraw.
- CEI pattern: Nullifier marked as spent before external token transfer.
- felt252 overflow protection: Deposit amount validated against field prime.
- Association sets: Owner can block specific commitments for regulatory compliance.
- Pausable: Emergency halt for deposit/withdraw operations.
v1 Limitation
v1 uses plaintext preimage revelation — withdrawal calldata reveals the secret and nullifier_preimage on-chain, allowing deposit-withdrawal linking by observers. This provides pseudonymous transfers, not full privacy. v2 will integrate STARK proofs for true zero-knowledge withdrawal.
DaoGovernance (Glacis Protocol)
File: glacis-protocol/contracts/src/dao_governance.cairo (435 LOC)
Sybil-resistant governance where each verified human gets exactly one vote. Voting power is gated by HumanAttestation SBT — no token-weighted plutocracy.
How It Works
1. Verified human calls propose() with target contract call
2. After voting_delay, voting opens for voting_period
3. Each verified human votes once (for / against / abstain)
4. If quorum met and for > against → proposal succeeds
5. queue() schedules execution via TimelockController
6. execute() after timelock delayProposal States
PENDING (0) → ACTIVE (1) → DEFEATED (2) or SUCCEEDED (3)
↓
QUEUED (4) → EXECUTED (5)
↓
CANCELLED (6)Interface
| Function | Access | Description |
|---|---|---|
propose(target, selector, calldata_hash, description_hash) | Verified human | Create proposal |
cast_vote(proposal_id, support) | Verified human | Vote: 0=against, 1=for, 2=abstain |
queue(proposal_id) | Anyone | Queue succeeded proposal to timelock |
execute(proposal_id) | Anyone | Execute after timelock delay |
cancel(proposal_id) | Proposer or admin | Cancel pending/active/queued proposal |
get_proposal_state(proposal_id) | View | Current state machine position |
get_proposal_votes(proposal_id) | View | Vote counts (for, against, abstain) |
set_voting_delay(delay) | Admin | Update default voting delay |
set_voting_period(period) | Admin | Update default voting period |
set_quorum(quorum) | Admin | Update default quorum threshold |
Security (Audit Fixes)
- Parameter snapshotting: Governance params (quorum, voting_period) are snapshotted per proposal at creation time. This prevents an admin from changing quorum mid-vote to manipulate outcomes.
- ParameterUpdated events: All governance parameter changes emit events for transparency.
- Cancel supports QUEUED state: Proposers and admins can cancel proposals even after they've been queued to the timelock.
Integration with Glacis
DaoGovernance requires IHumanAttestation to verify voters. Only wallets holding a valid (non-expired, non-revoked) HumanAttestation SBT can propose or vote. This creates one-person-one-vote governance without token accumulation attacks.
Test Coverage
| Contract | Tests | Suite |
|---|---|---|
| StealthRegistry | 20/20 | contracts/tests/test_stealth_registry.cairo |
| ConfidentialPool | 29/29 | contracts/tests/test_confidential_pool.cairo |
| DaoGovernance | 21/21 | glacis-protocol/contracts/tests/test_dao_governance.cairo |
Deployment Status
| Contract | Sepolia | Mainnet |
|---|---|---|
| StealthRegistry | Pending | — |
| ConfidentialPool | Pending | — |
| DaoGovernance | Pending | — |