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*J

Where:

  • v = value

  • r = randomness

  • a = 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(&note.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:

  1. Generate randomness

  2. Create recipient address

  3. Generate nullifier key

  4. Calculate commitment

  5. Store note securely

  6. 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:

  1. Select note to spend

  2. Generate nullifier

  3. Create proof

  4. Update state

  5. 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:

  1. Generate keypair

  2. Derive encryption key

  3. Calculate checksum

  4. Encode address

  5. 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:

  1. Generate ephemeral keypair

  2. Derive shared secret

  3. Encrypt note data

  4. 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(&note.to_bytes(), &shared_secret);
    
    EncryptedNote {
        ciphertext,
        ephemeral_key: ephemeral_pk,
        nonce: generate_nonce(),
    }
}

Decryption Process

Decryption Steps:

  1. Derive shared secret

  2. Decrypt ciphertext

  3. Verify integrity

  4. 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