SDK Architecture

SDK Architecture - Technical Deep Dive

This document provides a comprehensive technical overview of the Roru SDK architecture, including detailed component descriptions, data flows, and implementation details.

Architecture Overview

SDK Structure

The Roru SDK is organized into four distinct layers, each with specific responsibilities:

┌─────────────────────────────────────────┐
│         Application Layer                 │
│  (Your Application Code)                  │
└──────────────┬────────────────────────────┘

┌──────────────▼────────────────────────────┐
│           API Layer                        │
│  - TransactionBuilder                     │
│  - Wallet API                              │
│  - ProofGenerator                          │
│  - StateSync                               │
│  - OfflineTransfer                         │
└──────────────┬────────────────────────────┘

┌──────────────▼────────────────────────────┐
│           Core Layer                       │
│  - Note Management                         │
│  - Commitment Operations                   │
│  - Nullifier Generation                    │
│  - Merkle Tree Operations                  │
│  - Key Management                          │
└──────────────┬────────────────────────────┘

┌──────────────▼────────────────────────────┐
│        Protocol Layer                      │
│  - Shielded State Tree                     │
│  - ZK Circuit Execution                    │
│  - Proof Generation/Verification            │
│  - Transaction Validation                 │
└──────────────┬────────────────────────────┘

┌──────────────▼────────────────────────────┐
│         Network Layer                      │
│  - Encrypted RPC Client                    │
│  - WebSocket Connections                   │
│  - State Synchronization                  │
│  - Transaction Broadcasting               │
└────────────────────────────────────────────┘

Layer Responsibilities

API Layer:

  • Provides high-level, developer-friendly interfaces

  • Handles input validation and error formatting

  • Manages async operations and callbacks

  • Abstracts protocol complexity

Core Layer:

  • Implements cryptographic primitives

  • Manages local state and caching

  • Handles key derivation and storage

  • Performs note selection and management

Protocol Layer:

  • Executes zero-knowledge circuits

  • Generates and verifies proofs

  • Manages shielded state tree

  • Validates transactions

Network Layer:

  • Handles all network communication

  • Manages connection pooling and retries

  • Implements encryption and authentication

  • Synchronizes state with infrastructure

Core Components

RoruClient

The RoruClient is the main entry point for SDK operations. It manages connections, configuration, and provides access to all SDK functionality.

Rust Implementation:

use roru_sdk::prelude::*;

pub struct RoruClient {
    config: ClientConfig,
    network_client: NetworkClient,
    state_manager: StateManager,
    key_manager: KeyManager,
    proof_generator: ProofGenerator,
}

impl RoruClient {
    /// Create a new Roru client with configuration
    pub async fn new(config: ClientConfig) -> Result<Self> {
        // Initialize network client with encrypted connection
        let network_client = NetworkClient::new(
            config.infra_endpoint.clone(),
            config.api_key.clone(),
        ).await?;
        
        // Initialize state manager with local cache
        let state_manager = StateManager::new(
            config.state_cache_path.clone(),
            network_client.clone(),
        ).await?;
        
        // Initialize key manager with secure storage
        let key_manager = KeyManager::new(
            config.key_storage_path.clone(),
        )?;
        
        // Initialize proof generator with circuit keys
        let proof_generator = ProofGenerator::new(
            config.proving_key_path.clone(),
        )?;
        
        Ok(Self {
            config,
            network_client,
            state_manager,
            key_manager,
            proof_generator,
        })
    }
    
    /// Create a new wallet instance
    pub async fn create_wallet(&self) -> Result<Wallet> {
        // Generate master key
        let master_key = self.key_manager.generate_master_key()?;
        
        // Derive wallet keys
        let spending_key = self.key_manager.derive_spending_key(&master_key)?;
        let viewing_key = self.key_manager.derive_viewing_key(&master_key)?;
        
        // Create wallet with keys
        Wallet::new(
            spending_key,
            viewing_key,
            self.state_manager.clone(),
            self.proof_generator.clone(),
            self.network_client.clone(),
        )
    }
    
    /// Load existing wallet from keys
    pub async fn load_wallet(&self, master_key: &MasterKey) -> Result<Wallet> {
        let spending_key = self.key_manager.derive_spending_key(master_key)?;
        let viewing_key = self.key_manager.derive_viewing_key(master_key)?;
        
        Wallet::new(
            spending_key,
            viewing_key,
            self.state_manager.clone(),
            self.proof_generator.clone(),
            self.network_client.clone(),
        )
    }
    
    /// Get current state root
    pub async fn get_state_root(&self) -> Result<StateRoot> {
        self.state_manager.get_current_root().await
    }
    
    /// Sync state with network
    pub async fn sync_state(&self) -> Result<SyncResult> {
        self.state_manager.sync().await
    }
}

TypeScript Implementation:

import { RoruClient, ClientConfig, Wallet } from '@roru/sdk';

export class RoruClient {
    private config: ClientConfig;
    private networkClient: NetworkClient;
    private stateManager: StateManager;
    private keyManager: KeyManager;
    private proofGenerator: ProofGenerator;
    
    constructor(config: ClientConfig) {
        this.config = config;
        // Initialize components
        this.networkClient = new NetworkClient(config.infraEndpoint, config.apiKey);
        this.stateManager = new StateManager(config.stateCachePath, this.networkClient);
        this.keyManager = new KeyManager(config.keyStoragePath);
        this.proofGenerator = new ProofGenerator(config.provingKeyPath);
    }
    
    async createWallet(): Promise<Wallet> {
        const masterKey = await this.keyManager.generateMasterKey();
        const spendingKey = await this.keyManager.deriveSpendingKey(masterKey);
        const viewingKey = await this.keyManager.deriveViewingKey(masterKey);
        
        return new Wallet(
            spendingKey,
            viewingKey,
            this.stateManager,
            this.proofGenerator,
            this.networkClient
        );
    }
    
    async loadWallet(masterKey: MasterKey): Promise<Wallet> {
        const spendingKey = await this.keyManager.deriveSpendingKey(masterKey);
        const viewingKey = await this.keyManager.deriveViewingKey(masterKey);
        
        return new Wallet(
            spendingKey,
            viewingKey,
            this.stateManager,
            this.proofGenerator,
            this.networkClient
        );
    }
    
    async getStateRoot(): Promise<StateRoot> {
        return await this.stateManager.getCurrentRoot();
    }
    
    async syncState(): Promise<SyncResult> {
        return await this.stateManager.sync();
    }
}

Wallet

The Wallet component manages user funds, transactions, and state synchronization.

Rust Implementation:

pub struct Wallet {
    spending_key: SpendingKey,
    viewing_key: ViewingKey,
    state_manager: StateManager,
    proof_generator: ProofGenerator,
    network_client: NetworkClient,
    local_state: LocalState,
}

impl Wallet {
    pub fn new(
        spending_key: SpendingKey,
        viewing_key: ViewingKey,
        state_manager: StateManager,
        proof_generator: ProofGenerator,
        network_client: NetworkClient,
    ) -> Result<Self> {
        let local_state = LocalState::new();
        
        Ok(Self {
            spending_key,
            viewing_key,
            state_manager,
            proof_generator,
            network_client,
            local_state,
        })
    }
    
    /// Generate a new shielded address
    pub fn generate_address(&self) -> Result<ShieldedAddress> {
        let address_index = self.local_state.next_address_index();
        let address = self.viewing_key.derive_address(address_index)?;
        self.local_state.add_address(address.clone());
        Ok(address)
    }
    
    /// Get shielded balance
    pub async fn get_balance(&self) -> Result<Balance> {
        // Sync state first
        self.sync_state().await?;
        
        // Calculate balance from local notes
        let notes = self.local_state.get_notes(&self.viewing_key)?;
        let balance = notes.iter()
            .map(|note| note.value)
            .sum();
        
        Ok(Balance {
            shielded: balance,
            unconfirmed: 0,
        })
    }
    
    /// Send a private transaction
    pub async fn send(
        &self,
        recipient: ShieldedAddress,
        amount: u64,
    ) -> Result<TransactionResult> {
        // Sync state to get latest notes
        self.sync_state().await?;
        
        // Select input notes
        let input_notes = self.select_notes(amount)?;
        
        // Create output notes
        let change = self.calculate_change(&input_notes, amount)?;
        let mut outputs = vec![
            Note::new(amount, recipient.clone(), &self.viewing_key)?,
        ];
        
        if change > 0 {
            let change_address = self.generate_address()?;
            outputs.push(Note::new(change, change_address, &self.viewing_key)?);
        }
        
        // Build transaction
        let tx = TransactionBuilder::new()
            .inputs(input_notes)
            .outputs(outputs)
            .build()?;
        
        // Generate proof
        let proof = self.proof_generator.generate_proof(&tx).await?;
        
        // Sign transaction
        let signed_tx = self.sign_transaction(tx, proof)?;
        
        // Broadcast transaction
        let tx_hash = self.network_client.broadcast_transaction(&signed_tx).await?;
        
        Ok(TransactionResult {
            tx_hash,
            status: TransactionStatus::Pending,
        })
    }
    
    /// Sync state with network
    pub async fn sync_state(&self) -> Result<SyncResult> {
        // Get current state root from network
        let network_root = self.network_client.get_state_root().await?;
        let local_root = self.local_state.get_state_root();
        
        if network_root != local_root {
            // Download incremental updates
            let updates = self.network_client.get_state_updates(
                local_root,
                network_root,
            ).await?;
            
            // Apply updates to local state
            for update in updates {
                self.local_state.apply_update(update, &self.viewing_key)?;
            }
        }
        
        Ok(SyncResult {
            synced: true,
            new_notes: self.local_state.get_new_notes_count(),
        })
    }
    
    fn select_notes(&self, amount: u64) -> Result<Vec<Note>> {
        let available_notes = self.local_state.get_notes(&self.viewing_key)?;
        
        // Greedy selection algorithm
        let mut selected = Vec::new();
        let mut total = 0u64;
        
        for note in available_notes {
            if total >= amount {
                break;
            }
            selected.push(note);
            total += note.value;
        }
        
        if total < amount {
            return Err(Error::InsufficientBalance);
        }
        
        Ok(selected)
    }
    
    fn sign_transaction(
        &self,
        tx: Transaction,
        proof: Proof,
    ) -> Result<SignedTransaction> {
        // Create transaction bundle
        let bundle = TransactionBundle {
            transaction: tx,
            proof,
            signature: self.spending_key.sign(&tx)?,
        };
        
        Ok(SignedTransaction { bundle })
    }
}

Module Structure

Transaction Module

Purpose: Build and manage private transactions

Key Types:

pub struct TransactionBuilder {
    inputs: Vec<InputNote>,
    outputs: Vec<OutputNote>,
    fee: Option<u64>,
    memo: Option<Vec<u8>>,
}

pub struct Transaction {
    pub inputs: Vec<Input>,
    pub outputs: Vec<Output>,
    pub proof: Proof,
    pub nullifiers: Vec<Nullifier>,
    pub public_data: PublicData,
}

Proof Module

Purpose: Generate and verify zero-knowledge proofs

Key Types:

pub struct ProofGenerator {
    proving_key: ProvingKey,
    circuit: Circuit,
}

pub struct Proof {
    pub a: Point,
    pub b: Point,
    pub c: Point,
    pub public_inputs: Vec<Scalar>,
}

State Module

Purpose: Manage and synchronize shielded state

Key Types:

pub struct StateManager {
    local_cache: StateCache,
    network_client: NetworkClient,
    state_tree: StateTree,
}

pub struct StateSync {
    current_root: StateRoot,
    updates: Vec<StateUpdate>,
}

Network Module

Purpose: Handle network communication

Key Types:

pub struct NetworkClient {
    endpoint: String,
    api_key: String,
    http_client: HttpClient,
    ws_client: Option<WebSocketClient>,
}

pub struct NetworkResponse<T> {
    pub data: T,
    pub status: u16,
    pub headers: HashMap<String, String>,
}

Data Flow

Transaction Creation Flow

User calls wallet.send()

    ├─> Sync state (get latest notes)

    ├─> Select input notes

    ├─> Create output notes

    ├─> Build transaction

    ├─> Generate proof

    ├─> Sign transaction

    └─> Broadcast to network

State Synchronization Flow

wallet.sync_state()

    ├─> Get network state root

    ├─> Compare with local root

    ├─> Request incremental updates

    ├─> Download Merkle proofs

    ├─> Verify proofs

    └─> Update local state

Configuration

ClientConfig

Rust:

pub struct ClientConfig {
    pub infra_endpoint: String,
    pub api_key: Option<String>,
    pub state_cache_path: PathBuf,
    pub key_storage_path: PathBuf,
    pub proving_key_path: PathBuf,
    pub network_id: NetworkId,
    pub timeout: Duration,
    pub retry_config: RetryConfig,
}

TypeScript:

interface ClientConfig {
    infraEndpoint: string;
    apiKey?: string;
    stateCachePath: string;
    keyStoragePath: string;
    provingKeyPath: string;
    networkId: NetworkId;
    timeout?: number;
    retryConfig?: RetryConfig;
}

Error Handling

Error Types

#[derive(Debug, Error)]
pub enum RoruError {
    #[error("Network error: {0}")]
    Network(#[from] NetworkError),
    
    #[error("Insufficient balance")]
    InsufficientBalance,
    
    #[error("Invalid transaction: {0}")]
    InvalidTransaction(String),
    
    #[error("Proof generation failed: {0}")]
    ProofGeneration(String),
    
    #[error("State sync failed: {0}")]
    StateSync(String),
    
    #[error("Key management error: {0}")]
    KeyManagement(#[from] KeyError),
}

Performance Considerations

Caching

  • State cache: Reduces network calls

  • Proof cache: Reuses proofs when possible

  • Address cache: Fast address lookups

Optimization

  • Batch operations: Multiple transactions at once

  • Parallel proof generation: Multiple proofs concurrently

  • Incremental sync: Only download changes

Conclusion

The SDK architecture provides:

  • Modularity: Clear separation of concerns

  • Extensibility: Easy to add new features

  • Performance: Optimized for production use

  • Developer Experience: Clean, intuitive APIs

  • Security: Secure key management and operations

Understanding the SDK architecture is essential for effective development with Roru Labs.

Last updated