Skip to content

Latest commit

 

History

History
463 lines (435 loc) · 43.5 KB

File metadata and controls

463 lines (435 loc) · 43.5 KB

Architecture & Flow Diagrams

This document provides visual representations of the Solana Secure Signer architecture.


🏗️ System Architecture

┌───────────────────────────────────────────────────────────────────────┐
│                         USER APPLICATION LAYER                        │
│  ┌─────────────────────────────────────────────────────────────┐     │
│  │               Your Python CLI (Coldstar SOL)                 │     │
│  │  • Transaction building                                      │     │
│  │  • Network communication                                     │     │
│  │  • User interface                                            │     │
│  └───────────────┬────────────────────────────┬─────────────────┘     │
│                  │                            │                       │
│                  │                            │                       │
└──────────────────┼────────────────────────────┼───────────────────────┘
                   │                            │
                   ▼                            ▼
┌───────────────────────────────────────────────────────────────────────┐
│                      INTEGRATION LAYER                                │
│  ┌──────────────────────────┐   ┌────────────────────────────────┐   │
│  │   FFI Integration        │   │   CLI Subprocess Integration   │   │
│  │   (Fast, Direct)         │   │   (Portable, Isolated)         │   │
│  │                          │   │                                │   │
│  │  • ctypes bindings       │   │  • subprocess.run()            │   │
│  │  • Shared library        │   │  • stdin/stdout JSON           │   │
│  │  • Direct function calls │   │  • Process isolation           │   │
│  └────────────┬─────────────┘   └────────────┬───────────────────┘   │
│               │                              │                       │
└───────────────┼──────────────────────────────┼───────────────────────┘
                │                              │
                └──────────┬───────────────────┘
                           ▼
┌───────────────────────────────────────────────────────────────────────┐
│                      RUST SIGNING CORE                                │
│  ┌─────────────────────────────────────────────────────────────┐     │
│  │                    Public API                                │     │
│  │  • sign_transaction(container, passphrase, tx)              │     │
│  │  • create_encrypted_container(key, passphrase)              │     │
│  └────────────┬────────────────────────────────────────────────┘     │
│               ▼                                                       │
│  ┌─────────────────────────────────────────────────────────────┐     │
│  │                 Signing Logic (signer.rs)                    │     │
│  │  1. Derive decryption key (Argon2id)                        │     │
│  │  2. Decrypt ciphertext (AES-256-GCM)                        │     │
│  │  3. Copy to secure buffer                                   │     │
│  │  4. Sign with Ed25519                                       │     │
│  │  5. Return signature                                        │     │
│  └────────────┬────────────────────────────────────────────────┘     │
│               ▼                                                       │
│  ┌─────────────────────────────────────────────────────────────┐     │
│  │            Secure Memory (secure_memory.rs)                  │     │
│  │  • SecureBuffer: mlock/VirtualLock                          │     │
│  │  • Automatic zeroization on drop                            │     │
│  │  • Panic-safe cleanup                                       │     │
│  └─────────────────────────────────────────────────────────────┘     │
│                                                                       │
│  Dependencies: ed25519-dalek, aes-gcm, argon2, zeroize, region       │
└───────────────────────────────────────────────────────────────────────┘

🔄 Signing Flow (Detailed)

┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 1: INPUT PREPARATION                                          │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Python Application                                                 │
│  ├─ Load encrypted container (JSON file)                           │
│  ├─ Get passphrase (user input, secure)                            │
│  └─ Prepare unsigned transaction bytes                             │
│                                                                     │
│  Status: ✅ No sensitive data yet                                   │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 2: ENTER RUST SECURE ZONE                                    │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  FFI Boundary / CLI Invocation                                      │
│  ├─ Serialize inputs to JSON/bytes                                 │
│  ├─ Call: sign_transaction_ffi() or subprocess                     │
│  └─ Enter Rust memory space                                        │
│                                                                     │
│  Status: ✅ Encrypted data crosses boundary                         │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 3: KEY DERIVATION                                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Argon2id Key Derivation                                           │
│  ├─ Input: passphrase + salt from container                        │
│  ├─ Compute: memory-hard hash                                      │
│  ├─ Output: 32-byte AES key                                        │
│  └─ Zeroize: passphrase copy                                       │
│                                                                     │
│  Status: ✅ Passphrase zeroized, no plaintext key yet               │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 4: MEMORY ALLOCATION & LOCKING                               │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  SecureKeyBuffer::new_secret_key()                                  │
│  ├─ Allocate: 32 bytes on heap                                     │
│  ├─ Lock: mlock()/VirtualLock() syscall                            │
│  ├─ Verify: lock succeeded or return error                         │
│  └─ Initialize: fill with zeros                                    │
│                                                                     │
│  Memory State:                                                      │
│  ┌────────────────────────────────────┐                            │
│  │  Locked RAM (32 bytes)             │                            │
│  │  [0, 0, 0, ..., 0]  (all zeros)    │                            │
│  │  Status: LOCKED, not swappable     │                            │
│  └────────────────────────────────────┘                            │
│                                                                     │
│  Status: ✅ Secure buffer ready, no sensitive data yet              │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 5: DECRYPTION                                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  AES-256-GCM Decryption                                             │
│  ├─ Input: ciphertext + nonce + derived key                        │
│  ├─ Decrypt: to temporary Vec<u8>                                  │
│  ├─ Verify: authentication tag                                     │
│  └─ Output: plaintext key (32 bytes)                               │
│                                                                     │
│  Temporary State:                                                   │
│  ┌────────────────────────────────────┐                            │
│  │  Stack/Heap (decrypted_bytes)      │                            │
│  │  [k₀, k₁, k₂, ..., k₃₁]            │                            │
│  │  Status: PLAINTEXT KEY! ⚠️          │                            │
│  └────────────────────────────────────┘                            │
│                                                                     │
│  Status: ⚠️ CRITICAL - Plaintext key in temporary buffer            │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 6: SECURE TRANSFER                                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Copy to Locked Buffer                                             │
│  ├─ Copy: decrypted_bytes → secure_key buffer                      │
│  ├─ Zeroize: decrypted_bytes (constant-time)                       │
│  └─ Verify: no copies remain                                       │
│                                                                     │
│  Before:                            After:                          │
│  ┌──────────────────────┐           ┌──────────────────────┐       │
│  │  Temporary Buffer    │           │  Temporary Buffer    │       │
│  │  [k₀, k₁, ..., k₃₁]  │  ───────> │  [0, 0, ..., 0]      │       │
│  │  (plaintext key)     │           │  (zeroized)          │       │
│  └──────────────────────┘           └──────────────────────┘       │
│                                                                     │
│  ┌──────────────────────┐           ┌──────────────────────┐       │
│  │  Locked Buffer       │           │  Locked Buffer       │       │
│  │  [0, 0, ..., 0]      │  ───────> │  [k₀, k₁, ..., k₃₁]  │       │
│  │  (empty)             │           │  (plaintext key)     │       │
│  └──────────────────────┘           └──────────────────────┘       │
│                                                                     │
│  Status: ✅ Key in locked memory, temporary copy zeroized           │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 7: SIGNING                                                   │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Ed25519 Signing                                                    │
│  ├─ Create: SigningKey from locked buffer (borrow, no copy)        │
│  ├─ Sign: signature = signing_key.sign(transaction)                │
│  ├─ Output: 64-byte signature                                      │
│  └─ Note: SigningKey borrows, doesn't own the key                  │
│                                                                     │
│  Memory State:                                                      │
│  ┌────────────────────────────────────┐                            │
│  │  Locked Buffer                     │                            │
│  │  [k₀, k₁, k₂, ..., k₃₁]  ◄─────── SigningKey borrows            │
│  │  Status: LOCKED, being used        │                            │
│  └────────────────────────────────────┘                            │
│                                                                     │
│  Status: ⚠️ Key still in memory, being used for signing             │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 8: RESULT PREPARATION                                        │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  Build Signed Transaction                                          │
│  ├─ Allocate: new Vec<u8> for result                               │
│  ├─ Append: signature (64 bytes, public data)                      │
│  ├─ Append: original transaction                                   │
│  └─ Return: SignedTransaction struct                               │
│                                                                     │
│  Status: ✅ Public signature ready, key still in locked buffer      │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 9: AUTOMATIC CLEANUP (Drop)                                  │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  SecureKeyBuffer::drop() automatically called                       │
│  ├─ Trigger: secure_key goes out of scope                          │
│  ├─ Zeroize: data.zeroize() (constant-time)                        │
│  ├─ Unlock: munlock()/VirtualUnlock()                              │
│  └─ Extra: manual write_bytes for paranoia                         │
│                                                                     │
│  Before Drop:                       After Drop:                     │
│  ┌──────────────────────┐           ┌──────────────────────┐       │
│  │  Locked Buffer       │           │  Unlocked Memory     │       │
│  │  [k₀, k₁, ..., k₃₁]  │  ───────> │  [0, 0, ..., 0]      │       │
│  │  (plaintext key)     │           │  (zeroized)          │       │
│  │  Status: LOCKED      │           │  Status: FREE        │       │
│  └──────────────────────┘           └──────────────────────┘       │
│                                                                     │
│  Note: This happens even if signing panicked!                      │
│                                                                     │
│  Status: ✅ Key completely erased from memory                       │
└──────────────────────────┬──────────────────────────────────────────┘
                           ▼
┌─────────────────────────────────────────────────────────────────────┐
│ PHASE 10: RETURN TO PYTHON                                         │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│  FFI Boundary / CLI Output                                          │
│  ├─ Serialize: SignedTransaction to JSON                           │
│  ├─ Return: to Python via FFI or stdout                            │
│  └─ Python receives: signature + signed transaction                │
│                                                                     │
│  Data Returned to Python:                                          │
│  • signature: [s₀, s₁, ..., s₆₃] (64 bytes, public)                │
│  • signed_tx: signature + transaction (public)                     │
│                                                                     │
│  Status: ✅ Only public data crosses boundary                       │
└─────────────────────────────────────────────────────────────────────┘

SECURITY SUMMARY:
═══════════════════════════════════════════════════════════════════════
• Plaintext key existed in locked memory for phases 6-8 only
• Key was zeroized immediately after use (phase 9)
• No copies of plaintext key exist outside locked buffer
• Cleanup is guaranteed even on panic
• Only public data (signature) returned to Python
• Private key NEVER exposed to Python memory space
═══════════════════════════════════════════════════════════════════════

📦 Data Flow (Input → Output)

INPUT (Python)                    SECURE ZONE (Rust)              OUTPUT (Python)
═══════════════                   ═══════════════════             ═══════════════

┌──────────────┐                                                 ┌──────────────┐
│  Encrypted   │                                                 │   Signature  │
│  Container   │────────┐                                        │  (64 bytes)  │
│  (JSON)      │        │                                        │              │
└──────────────┘        │                                        └──────────────┘
                        │                                                 ▲
┌──────────────┐        │         ┌─────────────────────┐                │
│  Passphrase  │────────┼────────>│  Rust Signing Core  │────────────────┤
│  (string)    │        │         │                     │                │
└──────────────┘        │         │  1. Decrypt         │        ┌──────────────┐
                        │         │  2. Lock memory     │        │   Signed     │
┌──────────────┐        │         │  3. Sign            │        │  Transaction │
│  Unsigned TX │────────┘         │  4. Zeroize         │        │   (bytes)    │
│  (bytes)     │                  │  5. Return          │        └──────────────┘
└──────────────┘                  └─────────────────────┘                 ▲
                                                                           │
                                     Private key exists                    │
                                     here only! Never                      │
                                     leaves this box.                      │
                                                                           │
                                     ┌──────────────────┐                  │
                                     │  Locked Memory   │                  │
                                     │  ┌────────────┐  │                  │
                                     │  │ Private    │  │                  │
                                     │  │ Key        │  │                  │
                                     │  │ (32 bytes) │  │                  │
                                     │  └────────────┘  │                  │
                                     │  Auto-zeroized!  │                  │
                                     └──────────────────┘                  │
                                                                           │
══════════════════════════════════════════════════════════════════════════│══
                              FFI / CLI Boundary                          │
══════════════════════════════════════════════════════════════════════════╧══

Legend:
────> Data flow
═══> Security boundary
┌───┐ Data container

🔒 Memory Safety Guarantees

╔═══════════════════════════════════════════════════════════════════════╗
║                     MEMORY SAFETY GUARANTEES                          ║
╚═══════════════════════════════════════════════════════════════════════╝

Guarantee 1: MEMORY LOCKING
┌─────────────────────────────────────────────────────────────────────┐
│  RAM                                                                 │
│  ┌───────────────────────────────────┐                              │
│  │  Locked Pages (via mlock)         │  ◄── Private key here        │
│  │  Cannot be swapped to disk        │                              │
│  └───────────────────────────────────┘                              │
│                    ║                                                 │
│                    ║ BLOCKED (no swap)                               │
│                    ▼                                                 │
│  ┌───────────────────────────────────┐                              │
│  │  Disk / Swap File                 │  ◄── Key never reaches here  │
│  │  ✘ Private key CANNOT be here     │                              │
│  └───────────────────────────────────┘                              │
└─────────────────────────────────────────────────────────────────────┘

Guarantee 2: ZEROIZATION
┌─────────────────────────────────────────────────────────────────────┐
│  Before Drop:                    After Drop:                         │
│  ┌─────────────────┐             ┌─────────────────┐                │
│  │ [k₀, k₁, ..., k₃₁]│             │ [0, 0, ..., 0]  │                │
│  │  Private key     │  ────────>  │  All zeros      │                │
│  │  SENSITIVE! ⚠️    │             │  Safe ✅         │                │
│  └─────────────────┘             └─────────────────┘                │
│                                                                      │
│  Method: zeroize crate (constant-time, not optimized out)           │
└─────────────────────────────────────────────────────────────────────┘

Guarantee 3: PANIC SAFETY
┌─────────────────────────────────────────────────────────────────────┐
│  Normal Flow:          Panic Flow:                                   │
│  ┌──────────┐          ┌──────────┐                                 │
│  │  Signing │          │  Signing │                                 │
│  │  Success │          │  PANIC!  │                                 │
│  └────┬─────┘          └────┬─────┘                                 │
│       │                     │                                        │
│       ▼                     ▼                                        │
│  ┌──────────┐          ┌──────────┐                                 │
│  │  Drop    │          │  Drop    │  ◄── Drop ALWAYS runs            │
│  │  Runs    │          │  Runs    │                                 │
│  └────┬─────┘          └────┬─────┘                                 │
│       │                     │                                        │
│       ▼                     ▼                                        │
│  ┌──────────┐          ┌──────────┐                                 │
│  │ Zeroized │          │ Zeroized │  ◄── Key erased in both cases   │
│  └──────────┘          └──────────┘                                 │
└─────────────────────────────────────────────────────────────────────┘

Guarantee 4: NO COPIES
┌─────────────────────────────────────────────────────────────────────┐
│  ONLY ONE instance of plaintext key exists:                          │
│                                                                      │
│  ┌────────────────────────────────────────────────┐                 │
│  │  SecureKeyBuffer                               │                 │
│  │  ┌──────────────────────────────────┐          │                 │
│  │  │  Private Key (32 bytes)          │  ◄─────── Single instance  │
│  │  │  [k₀, k₁, k₂, ..., k₃₁]          │          │                 │
│  │  └──────────────────────────────────┘          │                 │
│  │                   ▲                             │                 │
│  │                   │ borrow (no copy)            │                 │
│  │                   │                             │                 │
│  │         ┌─────────┴──────────┐                  │                 │
│  │         │   SigningKey        │                 │                 │
│  │         │   (borrows data)    │                 │                 │
│  │         └─────────────────────┘                 │                 │
│  └────────────────────────────────────────────────┘                 │
│                                                                      │
│  All other buffers (decryption, etc.) are zeroized immediately       │
└─────────────────────────────────────────────────────────────────────┘

Guarantee 5: EPHEMERAL LIFECYCLE
┌─────────────────────────────────────────────────────────────────────┐
│  Function Scope:                                                     │
│                                                                      │
│  fn sign_transaction(...) {                                         │
│      let secure_key = SecureKeyBuffer::new();  ◄── Created          │
│      // ... decrypt into secure_key ...                             │
│      // ... use for signing ...                                     │
│      return signature;                                              │
│  } ◄── secure_key dropped here, automatic cleanup                   │
│                                                                      │
│  Key exists ONLY during function execution                          │
│  No global variables, no caching, no persistence                    │
└─────────────────────────────────────────────────────────────────────┘

🎯 Integration Comparison

╔═══════════════════════════════════════════════════════════════════════╗
║                    INTEGRATION MODE COMPARISON                        ║
╚═══════════════════════════════════════════════════════════════════════╝

┌─────────────────────────────────────────────────────────────────────┐
│  FFI Mode (libsolana_secure_signer.so/dll/dylib)                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  Python ←──(ctypes)──→ Rust Library                                 │
│                                                                      │
│  Pros:                                                               │
│  ✅ Fastest (no process overhead)                                    │
│  ✅ Direct function calls                                            │
│  ✅ Minimal latency (~0.1ms overhead)                                │
│                                                                      │
│  Cons:                                                               │
│  ⚠️ Requires compiled library for platform                           │
│  ⚠️ Shared memory space (less isolated)                              │
│                                                                      │
│  Use When:                                                           │
│  • Production deployments                                            │
│  • High transaction throughput                                       │
│  • Minimal latency required                                          │
└─────────────────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────────────────┐
│  CLI Mode (solana-signer binary)                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                      │
│  Python ──(subprocess)──→ Rust Binary ──(exit)──→ All memory freed  │
│                                                                      │
│  Pros:                                                               │
│  ✅ Maximum isolation (separate process)                             │
│  ✅ Platform-agnostic (just needs binary)                            │
│  ✅ Automatic cleanup (process exit)                                 │
│  ✅ No FFI complexity                                                │
│                                                                      │
│  Cons:                                                               │
│  ⚠️ Slower (process startup ~10-50ms)                                │
│  ⚠️ IPC overhead (JSON serialization)                                │
│                                                                      │
│  Use When:                                                           │
│  • Development/testing                                               │
│  • Maximum security isolation                                        │
│  • Cross-platform portability                                        │
│  • Occasional signing (not high frequency)                           │
└─────────────────────────────────────────────────────────────────────┘

Performance Comparison:
┌────────────────┬──────────┬───────────┬──────────────┐
│  Operation     │   FFI    │    CLI    │  Difference  │
├────────────────┼──────────┼───────────┼──────────────┤
│  Overhead      │  ~0.1ms  │  ~10-50ms │  100-500x    │
│  Throughput    │  High    │  Low      │  -           │
│  Memory Safety │  Good    │  Excellent│  CLI better  │
│  Portability   │  Medium  │  High     │  CLI better  │
└────────────────┴──────────┴───────────┴──────────────┘

This completes the visual documentation! 🎉