Privacy Architecture
RP1 provides optional privacy through zkSNARK proofs, enabling shielded transactions while maintaining auditability.
Privacy Model
┌─────────────────────────────────────────────────────────────┐
│ Public Layer │
│ • Transparent balances │
│ • Auditable transactions │
│ • Standard Cosmos SDK accounts │
└─────────────────────────────────────────────────────────────┘
│
┌─────┴─────┐
│ Shield │
│ Unshield │
└─────┬─────┘
│
┌─────────────────────────────────────────────────────────────┐
│ Shielded Layer │
│ • Hidden balances (Pedersen commitments) │
│ • Anonymous transfers (ring signatures) │
│ • Viewing key disclosure (selective transparency) │
└─────────────────────────────────────────────────────────────┘
Core Components
1. Shielded Pool
All private balances are stored as Pedersen commitments:
Commitment = g^amount · h^blinding_factor
- Hides the actual balance amount
- Cryptographically binding (can't change amount later)
- Additively homomorphic (can verify sums without revealing values)
2. ZK Proof Engine
Uses gnark with Groth16 proving system on BN254 curve:
// Circuit constraints
type ShieldCircuit struct {
Amount frontend.Variable `gnark:"amount,public"`
Blinding frontend.Variable `gnark:",secret"`
OldRoot frontend.Variable `gnark:"oldRoot,public"`
NewRoot frontend.Variable `gnark:"newRoot,public"`
Nullifier frontend.Variable `gnark:"nullifier,public"`
}
func (c *ShieldCircuit) Define(api frontend.API) error {
// Verify commitment
commitment := api.Add(
api.Mul(c.Amount, G),
api.Mul(c.Blinding, H),
)
// Verify Merkle proof
// ... additional constraints
return nil
}
3. Ring Signatures (Decoy Mixing)
Provides sender anonymity by mixing with decoy outputs:
Real Input: A → output
Decoy 1: X → (unused)
Decoy 2: Y → (unused)
Decoy 3: Z → (unused)
Ring signature proves: "One of [A,X,Y,Z] authorized this"
(but not which one!)
Configuration:
- Default ring size: 11 (1 real + 10 decoys)
- Decoys selected by age-weighted random sampling
- Prevents timing analysis
4. Viewing Keys
Enables selective disclosure for compliance:
// Viewing key types
type ViewingKeyType =
| "full" // See all transactions and balances
| "incoming" // See only incoming transactions
| "balance" // See current balance only
| "outgoing"; // See only outgoing transactions
// Grant access to auditor
await privacy.grantViewingKey({
grantee: "rp1auditor...",
type: "full",
expiry: "2025-12-31",
});
Transaction Types
Shield (Public → Private)
rp1d tx privacy shield 1000000urp1 --from wallet
- Burns public tokens
- Creates commitment in shielded pool
- Generates ZK proof of correctness
- Updates Merkle tree root
Private Transfer
rp1d tx privacy transfer --to rp1recipient... --amount 500000
- Proves ownership via nullifier (prevents double-spend)
- Creates new commitment for recipient
- Ring signature hides sender
- ZK proof verifies amount conservation
Unshield (Private → Public)
rp1d tx privacy unshield 500000urp1
- Reveals nullifier (marks note as spent)
- ZK proof shows valid note ownership
- Mints public tokens
- Updates Merkle tree
Cryptographic Primitives
| Primitive | Implementation | Purpose |
|---|---|---|
| Hash | MiMC | ZK-friendly hashing |
| Commitment | Pedersen | Hide amounts |
| Proving System | Groth16 | Succinct proofs |
| Curve | BN254 | Efficient pairings |
| Signatures | Ring/MLSAG | Sender anonymity |
Proof Verification
Proof verification is O(1) regardless of circuit complexity:
// Verification cost is constant
func VerifyProof(vk VerifyingKey, proof Proof, publicInputs []fr.Element) bool {
// ~2ms verification time
return groth16.Verify(proof, vk, publicInputs)
}
| Operation | Proof Size | Verify Time |
|---|---|---|
| Shield | 256 bytes | ~2ms |
| Transfer | 256 bytes | ~2ms |
| Unshield | 256 bytes | ~2ms |
Privacy Guarantees
| Property | Guaranteed | Notes |
|---|---|---|
| Amount hiding | ✅ | Commitments hide values |
| Sender anonymity | ✅ | Ring signatures |
| Recipient privacy | ✅ | Stealth addresses |
| Transaction unlinkability | ✅ | One-time keys |
| Timing analysis resistance | ⚠️ | Depends on network activity |
Compliance Features
Viewing Key Audit Trail
type ViewingKeyGrant struct {
Grantee string // Who received access
GrantType string // What they can see
GrantedAt time.Time // When granted
ExpiresAt time.Time // When access expires
RevokedAt time.Time // If revoked early
}
Threshold Disclosure
Require M-of-N authorities to decrypt:
// Configured per account
ThresholdConfig{
Threshold: 3, // 3 of 5 required
Authorities: []string{
"rp1authority1...",
"rp1authority2...",
"rp1authority3...",
"rp1authority4...",
"rp1authority5...",
},
}
Best Practices
- Wait for decoys: Let your transaction age before spending for better anonymity
- Use standard amounts: Round numbers provide better mixing
- Regular shielding: Don't shield immediately before spending
- Viewing key rotation: Periodically rotate keys for auditors