Shielded Notes Model
Shielded Notes Model - Technical Specification
This document describes the shielded notes model, the fundamental data structure for private transactions in the Roru Protocol.
Note Structure
Core Note Format
Note Definition:
pub struct Note {
pub value: u64, // Transaction amount
pub recipient: ShieldedAddress, // Recipient address (encrypted)
pub randomness: Scalar, // Random value for commitment
pub nullifier_key: Scalar, // Key for nullifier generation
pub timestamp: u64, // Creation timestamp
pub asset_id: AssetId, // Asset identifier
}Note Components
Value:
Amount in smallest unit
Range: 0 to 2^64 - 1
Hidden in commitment
Verified in proof
Recipient:
Shielded address format
Encrypted representation
Cannot be linked to public address
Privacy-preserving
Randomness:
Random scalar value
Ensures commitment uniqueness
Prevents linkability
Cryptographically secure
Nullifier Key:
Used to generate nullifier
Unique per note
Required for spending
Stored securely
Commitment Generation
Pedersen Commitment
Commitment Formula:
C = v*G + r*H + a*JWhere:
v= valuer= randomnessa= recipient address (hashed)G, H, J= base points on curve
Implementation:
fn commit_note(note: &Note) -> Commitment {
let value_point = note.value * G;
let randomness_point = note.randomness * H;
let recipient_point = hash_to_point(¬e.recipient) * J;
value_point + randomness_point + recipient_point
}Commitment Properties
Hiding:
Commitment doesn't reveal value
Computationally infeasible to determine value
Randomness ensures hiding
Binding:
Cannot change value without changing commitment
Commitment binds to specific note
Cryptographically secure
Additivity:
Commitments can be added homomorphically
Useful for balance verification
Enables efficient proofs
Note Lifecycle
Creation
Creation Process:
Generate randomness
Create recipient address
Generate nullifier key
Calculate commitment
Store note securely
Add to state tree
Creation Code:
fn create_note(value: u64, recipient: ShieldedAddress) -> Note {
let randomness = generate_random_scalar();
let nullifier_key = generate_random_scalar();
Note {
value,
recipient,
randomness,
nullifier_key,
timestamp: current_timestamp(),
asset_id: default_asset(),
}
}Storage
Storage Format:
Encrypted on device
Commitment in state tree
Full note only on recipient device
Sender doesn't store full note
Storage Security:
Encrypted with device key
Never stored in plaintext
Secure element storage (Roru One)
Backup encryption
Spending
Spending Process:
Select note to spend
Generate nullifier
Create proof
Update state
Mark as spent
Spending Code:
fn spend_note(note: &Note, nullifier_key: &Scalar) -> Nullifier {
let nullifier = generate_nullifier(note, nullifier_key);
// Add to nullifier set
nullifier
}Shielded Address
Address Format
Address Structure:
pub struct ShieldedAddress {
pub public_key: Point, // Public key point
pub encryption_key: Point, // Encryption key
pub checksum: [u8; 4], // Address checksum
}Address Generation
Generation Process:
Generate keypair
Derive encryption key
Calculate checksum
Encode address
Format for display
Generation Code:
fn generate_address() -> ShieldedAddress {
let (sk, pk) = generate_keypair();
let encryption_key = derive_encryption_key(&sk);
let checksum = calculate_checksum(&pk, &encryption_key);
ShieldedAddress {
public_key: pk,
encryption_key,
checksum,
}
}Nullifier Generation
Nullifier Format
Nullifier Structure:
pub struct Nullifier {
pub hash: Hash, // Hash value
}Generation Algorithm
Nullifier Formula:
nullifier = Hash(commitment || nullifier_key || position)Implementation:
fn generate_nullifier(note: &Note, nullifier_key: &Scalar, position: u32) -> Nullifier {
let input = [
note.commitment().as_bytes(),
nullifier_key.as_bytes(),
position.to_le_bytes(),
].concat();
Nullifier {
hash: hash(input),
}
}Nullifier Properties
Uniqueness:
Unique per note
Cannot collide
Deterministic
Verifiable
Unlinkability:
Cannot link to note
Cannot link to address
Privacy-preserving
No information leakage
Note Encryption
Encryption Scheme
Encryption Format:
pub struct EncryptedNote {
pub ciphertext: Vec<u8>, // Encrypted note data
pub ephemeral_key: Point, // Ephemeral public key
pub nonce: [u8; 12], // Nonce for encryption
}Encryption Process
Encryption Steps:
Generate ephemeral keypair
Derive shared secret
Encrypt note data
Package encrypted note
Encryption Code:
fn encrypt_note(note: &Note, recipient: &ShieldedAddress) -> EncryptedNote {
let (ephemeral_sk, ephemeral_pk) = generate_keypair();
let shared_secret = derive_shared_secret(&ephemeral_sk, &recipient.encryption_key);
let ciphertext = encrypt(¬e.to_bytes(), &shared_secret);
EncryptedNote {
ciphertext,
ephemeral_key: ephemeral_pk,
nonce: generate_nonce(),
}
}Decryption Process
Decryption Steps:
Derive shared secret
Decrypt ciphertext
Verify integrity
Reconstruct note
Decryption Code:
fn decrypt_note(encrypted: &EncryptedNote, recipient_sk: &Scalar) -> Result<Note> {
let shared_secret = derive_shared_secret(recipient_sk, &encrypted.ephemeral_key);
let plaintext = decrypt(&encrypted.ciphertext, &shared_secret)?;
Note::from_bytes(&plaintext)
}Multi-Asset Support
Asset Identification
Asset Format:
pub struct AssetId {
pub chain_id: u32, // Chain identifier
pub contract: Address, // Contract address (if applicable)
pub token_id: Option<u256>, // Token ID (for NFTs)
}Asset Handling
Asset Operations:
Different assets in same tree
Asset-specific commitments
Cross-asset transfers
Asset conversion
Note Selection
Selection Algorithms
Random Selection:
Random note selection
Privacy-preserving
Unlinkable
Optimization Selection:
Select notes to minimize change
Reduce number of inputs
Optimize transaction size
Selection Code:
fn select_notes(amount: u64, available_notes: &[Note]) -> Vec<Note> {
// Greedy selection algorithm
let mut selected = Vec::new();
let mut total = 0u64;
for note in available_notes {
if total >= amount {
break;
}
selected.push(note.clone());
total += note.value;
}
selected
}Privacy Properties
Privacy Guarantees
Unlinkability:
Notes cannot be linked
Transactions cannot be linked
Addresses cannot be linked
Complete unlinkability
Confidentiality:
Values hidden
Recipients hidden
Senders hidden
Complete confidentiality
Anonymity:
Sender anonymity
Recipient anonymity
Transaction anonymity
Full anonymity set
Performance
Efficiency
Operations:
Commitment: O(1)
Encryption: O(1)
Decryption: O(1)
Nullifier: O(1)
Storage:
Note size: ~128 bytes
Commitment: 32 bytes
Encrypted note: ~200 bytes
Nullifier: 32 bytes
Conclusion
The shielded notes model provides:
Privacy: Complete transaction privacy
Security: Cryptographic guarantees
Efficiency: Fast operations
Flexibility: Multi-asset support
Scalability: Efficient storage
Understanding the notes model is essential for protocol development.
Last updated
