Vault Package - Secure Secret Management

Overview

The Vault package (packages/vault/) provides centralized, secure storage for mnemonics and private keys with encryption, caching, and audit capabilities.

Quick Start

Installation

cd /opt/cryptotel/enterprise/stacks/gitinext/gitinext-golang
# Already in go.work - just sync
go work sync

Basic Usage

package main

import (
    "log"
    "time"
    
    "github.com/gitinext/gitinext-golang/packages/vault"
)

func main() {
    // Create vault with encryption key
    v, err := vault.New(vault.Config{
        KEK:      "your-32-byte-master-encryption-key",
        CacheTTL: 5 * time.Minute,
    })
    if err != nil {
        log.Fatal(err)
    }
    defer v.Close()
    
    // Store a mnemonic
    mnemonic := "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about"
    err = v.StoreMnemonic("wallet-123", mnemonic, "TON")
    if err != nil {
        log.Fatal(err)
    }
    
    // Retrieve mnemonic (cached for 5 minutes)
    retrieved, err := v.RetrieveMnemonic("wallet-123")
    if err != nil {
        log.Fatal(err)
    }
    
    log.Printf("Retrieved: %s", retrieved)
}

Features

1. Encryption

  • Algorithm: XChaCha20-Poly1305 (AEAD)
  • Key Derivation: PBKDF2 with 100,000 iterations
  • Random Nonces: Each secret has unique nonce

2. Caching

  • TTL-based: Default 5 minutes
  • Automatic cleanup: Background goroutine
  • Thread-safe: Mutex-protected

3. Audit Trail

  • Access counting: Track how many times accessed
  • Last access time: When was it last retrieved
  • Metadata: Chain, user ID, wallet ID

4. Security Features

  • Memory zeroing: Sensitive data cleared on delete
  • Lockable vault: Emergency lock clears all cache
  • Export/Import: Encrypted backup/migration

API Reference

Creating Vault

v, err := vault.New(vault.Config{
    KEK:      os.Getenv("VAULT_KEK"), // From environment
    CacheTTL: 5 * time.Minute,
    StoragePath: "", // Optional: future disk persistence
})

Storing Secrets

// Store mnemonic
err = v.StoreMnemonic("wallet-123", mnemonic, "TON")

// Store private key
err = v.StorePrivateKey("key-456", privKeyBytes, "TRON")

// Generic store
err = v.Store("id", data, vault.SecretTypeMnemonic, "ETH")

Retrieving Secrets

// Retrieve mnemonic (string)
mnemonic, err := v.RetrieveMnemonic("wallet-123")

// Retrieve private key (bytes)
privKey, err := v.RetrievePrivateKey("key-456")

// Generic retrieve
data, err := v.Retrieve("id")

Vault Management

// Lock vault (clears all decrypted cache)
v.Lock()

// Unlock vault
err = v.Unlock()

// Check if locked
if v.IsLocked() {
    log.Println("Vault is locked")
}

// Close vault (cleanup)
v.Close()

Metadata & Audit

// Get metadata without decrypting
meta, err := v.GetMetadata("wallet-123")
log.Printf("Accessed %d times, last: %v", meta.AccessCount, meta.LastAccess)

// List all secret IDs
ids := v.List()
for _, id := range ids {
    log.Println(id)
}

Backup & Migration

// Export all secrets (encrypted)
exportData, err := v.Export()
err = os.WriteFile("vault-backup.json", exportData, 0600)

// Import secrets
importData, err := os.ReadFile("vault-backup.json")
err = v.Import(importData)

Integration Examples

With Wallet Service

package service

import (
    "github.com/gitinext/gitinext-golang/packages/vault"
)

type WalletService struct {
    vault *vault.Vault
}

func NewWalletService() (*WalletService, error) {
    v, err := vault.New(vault.Config{
        KEK: os.Getenv("VAULT_KEK"),
        CacheTTL: 5 * time.Minute,
    })
    if err != nil {
        return nil, err
    }
    
    return &WalletService{vault: v}, nil
}

func (s *WalletService) CreateWallet(userID uint, chain string) error {
    // Generate mnemonic
    mnemonic, err := generateMnemonic()
    if err != nil {
        return err
    }
    
    // Store in vault
    vaultID := fmt.Sprintf("user-%d-wallet-%s", userID, chain)
    err = s.vault.StoreMnemonic(vaultID, mnemonic, chain)
    if err != nil {
        return err
    }
    
    // Store vault ID in database
    return s.db.StoreWalletVaultID(userID, chain, vaultID)
}

func (s *WalletService) SignTransaction(userID uint, chain string, tx []byte) ([]byte, error) {
    // Get vault ID from database
    vaultID, err := s.db.GetWalletVaultID(userID, chain)
    if err != nil {
        return nil, err
    }
    
    // Retrieve mnemonic from vault (cached)
    mnemonic, err := s.vault.RetrieveMnemonic(vaultID)
    if err != nil {
        return nil, err
    }
    
    // Sign transaction
    return signTx(mnemonic, tx)
}

With TWC Signer

func (s *TWCSigner) SignWithMnemonic(walletID string, chain string, payload []byte) ([]byte, error) {
    // Retrieve mnemonic from vault
    mnemonic, err := s.vault.RetrieveMnemonic(walletID)
    if err != nil {
        return nil, err
    }
    defer zeroString(&mnemonic) // Clear from memory
    
    // Derive keys using TWC
    privKey, _, err := s.FromMnemonic(mnemonic, derivationPath(chain), curve(chain))
    if err != nil {
        return nil, err
    }
    defer zeroBytes(privKey) // Clear from memory
    
    // Sign transaction
    return s.SignTx(chain, privKey, payload)
}

Environment Variables

# Required: Master encryption key (32+ bytes)
VAULT_KEK="your-very-secure-master-key-from-kms-or-env"

# Optional: Cache TTL (default: 5m)
VAULT_CACHE_TTL="5m"

# Optional: Storage path for persistence (future feature)
VAULT_STORAGE_PATH="/var/lib/gitinext/vault"

Security Best Practices

1. Key Management

# ❌ BAD: Hardcoded key
VAULT_KEK="insecure-key"

# ✅ GOOD: From KMS
VAULT_KEK=$(aws kms decrypt --ciphertext-blob fileb://encrypted-key.bin --query Plaintext --output text | base64 -d)

# ✅ GOOD: From secret manager
VAULT_KEK=$(docker secret inspect vault_kek -f '{{.Spec.Data}}' | base64 -d)

2. Memory Safety

// Always clear sensitive data
mnemonic, err := vault.RetrieveMnemonic("wallet-123")
if err != nil {
    return err
}
defer zeroString(&mnemonic) // Clear when done

// Use short cache TTL for high-security
v, _ := vault.New(vault.Config{
    CacheTTL: 30 * time.Second, // 30s instead of 5m
})

3. Audit & Monitoring

// Regular audit checks
ids := vault.List()
for _, id := range ids {
    meta, _ := vault.GetMetadata(id)
    if meta.AccessCount > 1000 {
        log.Warnf("High access count for %s: %d", id, meta.AccessCount)
    }
}

// Lock vault during maintenance
vault.Lock()
defer vault.Unlock()

4. Backup Strategy

# Regular encrypted backups
vault_backup() {
    export_data=$(curl -s http://wallet-service/admin/vault/export)
    echo "$export_data" | gpg --encrypt --recipient admin@company.com > vault-$(date +%Y%m%d).json.gpg
}

# Rotate KEK periodically
rotate_kek() {
    # 1. Export with old KEK
    # 2. Create new vault with new KEK
    # 3. Import data
    # 4. Verify
    # 5. Update environment
}

Performance

Cache Hit Rates

  • First access: ~1ms (decrypt)
  • Cached access: ~0.01ms (memory lookup)
  • Cache expiry: Automatic cleanup every 1 minute

Memory Usage

  • ~100 bytes per encrypted secret
  • ~varies per decrypted cache entry (depends on secret size)
  • Max memory: (num_secrets * 100) + (cached_secrets * avg_size)

Benchmarks

// Run benchmarks
cd packages/vault
go test -bench=. -benchmem

// Example results:
// BenchmarkStore-8        100000    10500 ns/op    2048 B/op    12 allocs/op
// BenchmarkRetrieve-8     200000     8500 ns/op    1024 B/op     8 allocs/op
// BenchmarkCached-8      5000000      250 ns/op     128 B/op     2 allocs/op

Testing

Unit Tests

cd packages/vault
go test -v

Integration Test

func TestVaultIntegration(t *testing.T) {
    v, _ := vault.New(vault.Config{
        KEK: "test-key-32-bytes-long-enough",
        CacheTTL: 1 * time.Minute,
    })
    defer v.Close()
    
    // Test full workflow
    mnemonic := "test mnemonic phrase"
    v.StoreMnemonic("test-1", mnemonic, "TON")
    
    retrieved, _ := v.RetrieveMnemonic("test-1")
    assert.Equal(t, mnemonic, retrieved)
    
    meta, _ := v.GetMetadata("test-1")
    assert.Equal(t, int64(1), meta.AccessCount)
}

Troubleshooting

Common Issues

Issue: vault: invalid encryption key

# Solution: Ensure KEK is set and at least 32 bytes
export VAULT_KEK="$(openssl rand -base64 32)"

Issue: vault: vault is locked

# Solution: Unlock the vault
vault.Unlock()

Issue: vault: secret not found

# Solution: Check if secret was stored
ids := vault.List()
log.Printf("Available secrets: %v", ids)

Migration from Existing Systems

From Database Storage

// Migrate from plaintext database
func MigrateToVault(db *DB, vault *vault.Vault) error {
    wallets, _ := db.GetAllWallets()
    
    for _, w := range wallets {
        vaultID := fmt.Sprintf("wallet-%d", w.ID)
        err := vault.StoreMnemonic(vaultID, w.Mnemonic, w.Chain)
        if err != nil {
            return err
        }
        
        // Update database to store vault ID instead
        db.UpdateWalletVaultID(w.ID, vaultID)
        
        // Clear plaintext mnemonic from database
        db.ClearMnemonic(w.ID)
    }
    
    return nil
}

See Also

© 2025 GitiNext - Enterprise Crypto Infrastructure | GitHub | Website