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
}