Security Best Practices
Comprehensive guide to securing your Hoosat applications and protecting user funds.
Private Key Security
Never Hardcode Private Keys
Bad:
// NEVER DO THIS
const privateKey = '33a4a81ecd31615c51385299969121707897fb1e167634196f31bd311de5fe43';
const wallet = HoosatCrypto.importKeyPair(privateKey);
Good:
// Use environment variables
import 'dotenv/config';
const privateKey = process.env.WALLET_PRIVATE_KEY;
if (!privateKey) {
throw new Error('WALLET_PRIVATE_KEY not set');
}
const wallet = HoosatCrypto.importKeyPair(privateKey);
.env file:
WALLET_PRIVATE_KEY=33a4a81ecd31615c51385299969121707897fb1e167634196f31bd311de5fe43
WALLET_NETWORK=mainnet
.gitignore:
.env
.env.*
!.env.example
Encrypted Storage
Store private keys encrypted at rest:
import { scryptSync, randomBytes, createCipheriv, createDecipheriv } from 'crypto';
import { readFileSync, writeFileSync, mkdirSync } from 'fs';
class SecureKeyStore {
private storePath: string;
constructor(storePath: string = './.keys') {
this.storePath = storePath;
// Create directory if it doesn't exist
try {
mkdirSync(storePath, { recursive: true });
} catch (error) {
// Directory exists
}
}
// Encrypt and save private key
saveKey(name: string, privateKey: string, password: string): void {
// Generate salt and IV
const salt = randomBytes(32);
const iv = randomBytes(16);
// Derive encryption key from password
const key = scryptSync(password, salt, 32);
// Encrypt private key
const cipher = createCipheriv('aes-256-cbc', key, iv);
const encrypted = Buffer.concat([
cipher.update(privateKey, 'utf8'),
cipher.final()
]);
// Create encrypted data structure
const encryptedData = {
version: 1,
salt: salt.toString('hex'),
iv: iv.toString('hex'),
encrypted: encrypted.toString('hex'),
createdAt: new Date().toISOString()
};
// Save to file
const filePath = `${this.storePath}/${name}.enc`;
writeFileSync(filePath, JSON.stringify(encryptedData, null, 2));
console.log(`Key saved to: ${filePath}`);
}
// Load and decrypt private key
loadKey(name: string, password: string): string {
const filePath = `${this.storePath}/${name}.enc`;
try {
// Read encrypted data
const encryptedData = JSON.parse(readFileSync(filePath, 'utf8'));
// Extract components
const salt = Buffer.from(encryptedData.salt, 'hex');
const iv = Buffer.from(encryptedData.iv, 'hex');
const encrypted = Buffer.from(encryptedData.encrypted, 'hex');
// Derive decryption key
const key = scryptSync(password, salt, 32);
// Decrypt
const decipher = createDecipheriv('aes-256-cbc', key, iv);
const decrypted = Buffer.concat([
decipher.update(encrypted),
decipher.final()
]);
return decrypted.toString('utf8');
} catch (error) {
if (error.message.includes('bad decrypt')) {
throw new Error('Incorrect password');
}
throw error;
}
}
// Check if key exists
keyExists(name: string): boolean {
const filePath = `${this.storePath}/${name}.enc`;
try {
readFileSync(filePath);
return true;
} catch {
return false;
}
}
}
// Usage
const keyStore = new SecureKeyStore();
// Save key
const privateKey = wallet.privateKey.toString('hex');
keyStore.saveKey('main-wallet', privateKey, 'strong-password-here');
// Load key
const loadedKey = keyStore.loadKey('main-wallet', 'strong-password-here');
const wallet = HoosatCrypto.importKeyPair(loadedKey);
Memory Security
Clear sensitive data from memory after use:
function processTransaction(privateKeyHex: string) {
// Convert to Buffer
let privateKey = Buffer.from(privateKeyHex, 'hex');
try {
// Use the key
const wallet = HoosatCrypto.importKeyPair(privateKey.toString('hex'));
const signedTx = builder.sign(privateKey);
// ... submit transaction
} finally {
// Clear private key from memory
if (privateKey) {
privateKey.fill(0);
privateKey = null as any;
}
}
}
// Clear string variables
let password = getUserPassword();
try {
// Use password
const key = keyStore.loadKey('wallet', password);
} finally {
// Overwrite password string
password = '0'.repeat(password.length);
password = null as any;
}
Hardware Security Modules (HSM)
For production/enterprise use, store keys in HSM:
// Example with AWS KMS
import { KMSClient, DecryptCommand } from '@aws-sdk/client-kms';
class HSMKeyManager {
private kmsClient: KMSClient;
constructor() {
this.kmsClient = new KMSClient({ region: 'us-east-1' });
}
async getPrivateKey(encryptedKeyBlob: string): Promise<string> {
const command = new DecryptCommand({
CiphertextBlob: Buffer.from(encryptedKeyBlob, 'base64')
});
const response = await this.kmsClient.send(command);
const privateKey = Buffer.from(response.Plaintext!).toString('utf8');
return privateKey;
}
}
// Usage
const hsmManager = new HSMKeyManager();
const privateKey = await hsmManager.getPrivateKey(process.env.ENCRYPTED_KEY_BLOB!);
const wallet = HoosatCrypto.importKeyPair(privateKey);
Input Validation
Validate All User Inputs
import { HoosatUtils } from 'hoosat-sdk';
function validateTransactionInputs(
recipientAddress: string,
amount: string
): void {
// Validate address
if (!recipientAddress || recipientAddress.trim() === '') {
throw new Error('Recipient address is required');
}
if (!HoosatUtils.isValidAddress(recipientAddress)) {
throw new Error('Invalid recipient address format');
}
// Check network prefix
const network = HoosatUtils.getAddressNetwork(recipientAddress);
const expectedNetwork = process.env.WALLET_NETWORK || 'mainnet';
if (network !== expectedNetwork) {
throw new Error(
`Address is for ${network} but wallet is on ${expectedNetwork}`
);
}
// Validate amount
if (!amount || amount.trim() === '') {
throw new Error('Amount is required');
}
if (!HoosatUtils.isValidAmount(amount)) {
throw new Error('Invalid amount format');
}
// Check for negative or zero
const amountBigInt = BigInt(amount);
if (amountBigInt <= 0n) {
throw new Error('Amount must be greater than zero');
}
// Check for dust
const DUST_THRESHOLD = 1000n;
if (amountBigInt < DUST_THRESHOLD) {
throw new Error(`Amount below dust threshold (${DUST_THRESHOLD} sompi)`);
}
// Check for unreasonable amounts
const MAX_AMOUNT = HoosatUtils.amountToSompi('1000000'); // 1M HTN
if (amountBigInt > BigInt(MAX_AMOUNT)) {
throw new Error('Amount exceeds maximum allowed');
}
}
// Usage
try {
validateTransactionInputs(userAddress, userAmount);
// Proceed with transaction
} catch (error) {
console.error('Validation failed:', error.message);
// Show error to user
}
Sanitize Inputs
function sanitizeAddress(address: string): string {
// Trim whitespace
address = address.trim();
// Remove common mistakes
address = address.replace(/\s+/g, ''); // Remove all spaces
address = address.toLowerCase(); // Normalize case
return address;
}
function sanitizeAmount(amount: string): string {
// Remove commas, spaces
amount = amount.replace(/[,\s]/g, '');
// Handle scientific notation
if (amount.includes('e') || amount.includes('E')) {
amount = Number(amount).toFixed(8);
}
return amount;
}
// Usage
const cleanAddress = sanitizeAddress(userInputAddress);
const cleanAmount = sanitizeAmount(userInputAmount);
validateTransactionInputs(cleanAddress, cleanAmount);
Transaction Security
Double-Check Before Signing
async function confirmAndSend(
client: HoosatClient,
wallet: KeyPair,
recipientAddress: string,
amount: string
): Promise<string> {
// Build transaction
const builder = new HoosatTxBuilder();
const utxos = await getUtxos(client, wallet.address);
for (const utxo of utxos) {
builder.addInput(utxo, wallet.privateKey);
}
builder.addOutput(recipientAddress, amount);
const feeEstimator = new HoosatFeeEstimator(client);
const fee = await feeEstimator.estimateFee(FeePriority.Normal, utxos.length, 2);
builder.setFee(fee.totalFee);
builder.addChangeOutput(wallet.address);
// Calculate actual amounts
const totalIn = builder.getTotalInputAmount();
const totalOut = builder.getTotalOutputAmount();
const change = totalIn - totalOut - BigInt(fee.totalFee);
// Display confirmation
console.log('\n=== Transaction Confirmation ===');
console.log('Sending to:', recipientAddress);
console.log('Amount:', HoosatUtils.sompiToAmount(amount), 'HTN');
console.log('Fee:', HoosatUtils.sompiToAmount(fee.totalFee), 'HTN');
console.log('Change:', HoosatUtils.sompiToAmount(change), 'HTN');
console.log('Total cost:', HoosatUtils.sompiToAmount(BigInt(amount) + BigInt(fee.totalFee)), 'HTN');
console.log('================================\n');
// Require explicit confirmation
const confirmed = await getUserConfirmation('Send this transaction?');
if (!confirmed) {
throw new Error('Transaction cancelled by user');
}
// Sign and submit
const signedTx = builder.sign();
const result = await client.submitTransaction(signedTx);
if (!result.ok) {
throw new Error(`Transaction failed: ${result.error}`);
}
return result.result.transactionId;
}
async function getUserConfirmation(prompt: string): Promise<boolean> {
// Implement your confirmation UI
// For CLI: use readline
// For web: show modal
// For mobile: show alert
return true; // Placeholder
}
Rate Limiting
Prevent abuse and accidental rapid transactions:
class RateLimiter {
private attempts: Map<string, number[]> = new Map();
private maxAttempts: number;
private windowMs: number;
constructor(maxAttempts: number = 5, windowMs: number = 60000) {
this.maxAttempts = maxAttempts;
this.windowMs = windowMs;
}
checkLimit(key: string): boolean {
const now = Date.now();
const attempts = this.attempts.get(key) || [];
// Remove old attempts outside window
const recentAttempts = attempts.filter(time => now - time < this.windowMs);
if (recentAttempts.length >= this.maxAttempts) {
return false; // Rate limit exceeded
}
// Record this attempt
recentAttempts.push(now);
this.attempts.set(key, recentAttempts);
return true;
}
reset(key: string): void {
this.attempts.delete(key);
}
}
// Usage
const rateLimiter = new RateLimiter(5, 60000); // 5 transactions per minute
async function sendTransactionWithRateLimit(
wallet: KeyPair,
recipientAddress: string,
amount: string
): Promise<string> {
const key = wallet.address;
if (!rateLimiter.checkLimit(key)) {
throw new Error('Rate limit exceeded. Please wait before sending another transaction.');
}
try {
return await sendTransaction(wallet, recipientAddress, amount);
} catch (error) {
// Don't count failed transactions
rateLimiter.reset(key);
throw error;
}
}
Amount Limits
Set transaction limits for safety:
interface TransactionLimits {
minAmount: string; // Minimum transaction
maxAmount: string; // Maximum single transaction
dailyLimit: string; // Maximum per 24 hours
requireApproval: string; // Require manual approval above this
}
class TransactionLimitChecker {
private limits: TransactionLimits;
private dailyTotal: Map<string, { amount: bigint; date: string }> = new Map();
constructor(limits: TransactionLimits) {
this.limits = limits;
}
checkLimits(address: string, amount: string): LimitCheckResult {
const amountBigInt = BigInt(amount);
const today = new Date().toISOString().split('T')[0];
// Check minimum
if (amountBigInt < BigInt(this.limits.minAmount)) {
return {
allowed: false,
reason: `Amount below minimum (${HoosatUtils.sompiToAmount(this.limits.minAmount)} HTN)`
};
}
// Check maximum
if (amountBigInt > BigInt(this.limits.maxAmount)) {
return {
allowed: false,
reason: `Amount exceeds maximum (${HoosatUtils.sompiToAmount(this.limits.maxAmount)} HTN)`
};
}
// Check daily limit
const dailyData = this.dailyTotal.get(address);
let dailyAmount = 0n;
if (dailyData && dailyData.date === today) {
dailyAmount = dailyData.amount;
}
const newDailyTotal = dailyAmount + amountBigInt;
if (newDailyTotal > BigInt(this.limits.dailyLimit)) {
return {
allowed: false,
reason: `Would exceed daily limit (${HoosatUtils.sompiToAmount(this.limits.dailyLimit)} HTN)`
};
}
// Check if approval required
const requiresApproval = amountBigInt >= BigInt(this.limits.requireApproval);
// Update daily total
this.dailyTotal.set(address, {
amount: newDailyTotal,
date: today
});
return {
allowed: true,
requiresApproval
};
}
}
interface LimitCheckResult {
allowed: boolean;
reason?: string;
requiresApproval?: boolean;
}
// Usage
const limits: TransactionLimits = {
minAmount: HoosatUtils.amountToSompi('0.001'), // 0.001 HTN
maxAmount: HoosatUtils.amountToSompi('100'), // 100 HTN
dailyLimit: HoosatUtils.amountToSompi('1000'), // 1000 HTN
requireApproval: HoosatUtils.amountToSompi('50') // 50 HTN
};
const limitChecker = new TransactionLimitChecker(limits);
const check = limitChecker.checkLimits(wallet.address, amount);
if (!check.allowed) {
throw new Error(check.reason);
}
if (check.requiresApproval) {
console.log('Large transaction - manual approval required');
await requestManualApproval(wallet.address, amount);
}
// Proceed with transaction
Network Security
Use HTTPS/TLS
// Always use secure connections in production
const client = new HoosatClient({
host: 'node.hoosat.fi',
port: 42420,
// TLS is handled by gRPC automatically for standard ports
});
// For self-hosted nodes, ensure TLS is configured
Node Authentication
Authenticate with private nodes:
import { credentials, Metadata } from '@grpc/grpc-js';
const client = new HoosatClient({
host: 'private-node.example.com',
port: 42420,
credentials: credentials.createSsl(),
metadata: new Metadata({
'authorization': `Bearer ${process.env.NODE_API_KEY}`
})
});
Verify Node Identity
async function verifyNodeConnection(client: HoosatClient): Promise<void> {
try {
const infoResult = await client.getInfo();
if (!infoResult.ok) {
throw new Error('Failed to get node info');
}
const info = infoResult.result;
// Check expected network
if (info.serverVersion && !info.serverVersion.includes('hoosat')) {
console.warn('Warning: Connected to non-Hoosat node');
}
// Verify network matches expectations
const networkResult = await client.getCurrentNetwork();
if (networkResult.ok) {
const network = networkResult.result.currentNetwork;
const expectedNetwork = process.env.EXPECTED_NETWORK || 'hoosat-mainnet';
if (network !== expectedNetwork) {
throw new Error(
`Network mismatch: expected ${expectedNetwork}, got ${network}`
);
}
}
console.log('Node verification successful');
} catch (error) {
throw new Error(`Node verification failed: ${error.message}`);
}
}
// Usage
await verifyNodeConnection(client);
Multi-Node Redundancy
Use multiple nodes for high availability:
const client = new HoosatClient({
nodes: [
{ host: 'node1.hoosat.fi', port: 42420 },
{ host: 'node2.hoosat.fi', port: 42420 },
{ host: 'node3.hoosat.fi', port: 42420 }
],
nodeSelectionStrategy: 'round-robin'
});
// Client automatically fails over to healthy nodes
Application Security
Separate Hot and Cold Wallets
class WalletManager {
private hotWallet: KeyPair; // Small balance, always online
private coldWallet: string; // Large balance, offline storage
constructor() {
// Hot wallet: encrypted in environment
this.hotWallet = HoosatCrypto.importKeyPair(
process.env.HOT_WALLET_KEY!
);
// Cold wallet: address only, keys stored offline
this.coldWallet = process.env.COLD_WALLET_ADDRESS!;
}
// Use hot wallet for regular transactions
async processPayment(recipientAddress: string, amount: string): Promise<string> {
// Check hot wallet balance
const balance = await this.getHotWalletBalance();
if (balance < BigInt(amount)) {
throw new Error('Insufficient hot wallet balance - needs refill from cold storage');
}
return await this.sendFromHotWallet(recipientAddress, amount);
}
// Periodically move excess funds to cold storage
async rebalanceToColed(): Promise<void> {
const balance = await this.getHotWalletBalance();
const threshold = BigInt(HoosatUtils.amountToSompi('100')); // Keep 100 HTN hot
if (balance > threshold) {
const excess = balance - threshold;
console.log(`Moving ${HoosatUtils.sompiToAmount(excess)} HTN to cold storage`);
await this.sendFromHotWallet(this.coldWallet, excess.toString());
}
}
private async getHotWalletBalance(): Promise<bigint> {
const result = await client.getBalance(this.hotWallet.address);
return result.ok ? BigInt(result.result.balance) : 0n;
}
private async sendFromHotWallet(to: string, amount: string): Promise<string> {
// Use hot wallet to send
// ...
return txId;
}
}
Implement Audit Logging
import { appendFileSync } from 'fs';
interface AuditLog {
timestamp: Date;
event: string;
user?: string;
details: any;
ip?: string;
}
class AuditLogger {
private logFile: string;
constructor(logFile: string = './audit.log') {
this.logFile = logFile;
}
log(event: string, details: any, user?: string, ip?: string): void {
const entry: AuditLog = {
timestamp: new Date(),
event,
user,
details,
ip
};
const line = JSON.stringify(entry) + '\n';
appendFileSync(this.logFile, line);
// Also log sensitive events to console
if (this.isSensitiveEvent(event)) {
console.log('[AUDIT]', event, user || 'system');
}
}
private isSensitiveEvent(event: string): boolean {
return [
'transaction_sent',
'wallet_created',
'wallet_imported',
'large_withdrawal',
'rate_limit_exceeded',
'failed_authentication'
].includes(event);
}
}
// Usage
const auditLogger = new AuditLogger();
// Log transaction
auditLogger.log('transaction_sent', {
from: wallet.address,
to: recipientAddress,
amount: HoosatUtils.sompiToAmount(amount),
txId: result.transactionId
}, userId, userIP);
// Log security events
auditLogger.log('failed_authentication', {
reason: 'incorrect_password',
attempts: 3
}, userId, userIP);
Environment-Specific Configuration
interface EnvironmentConfig {
nodeHost: string;
nodePort: number;
network: 'mainnet' | 'testnet';
enableDebug: boolean;
maxTransactionAmount: string;
requireManualApproval: boolean;
}
function getConfig(): EnvironmentConfig {
const env = process.env.NODE_ENV || 'development';
const configs: Record<string, EnvironmentConfig> = {
development: {
nodeHost: 'testnet.hoosat.fi',
nodePort: 42420,
network: 'testnet',
enableDebug: true,
maxTransactionAmount: HoosatUtils.amountToSompi('1'),
requireManualApproval: false
},
production: {
nodeHost: process.env.NODE_HOST || 'node.hoosat.fi',
nodePort: parseInt(process.env.NODE_PORT || '42420'),
network: 'mainnet',
enableDebug: false,
maxTransactionAmount: HoosatUtils.amountToSompi('100'),
requireManualApproval: true
}
};
return configs[env] || configs.development;
}
// Usage
const config = getConfig();
const client = new HoosatClient({
host: config.nodeHost,
port: config.nodePort
});
Monitoring and Alerts
Detect Suspicious Activity
class SecurityMonitor {
private alertThresholds = {
rapidTransactions: 10, // More than 10 tx in 1 minute
largeTransaction: HoosatUtils.amountToSompi('1000'),
failedAttempts: 5, // 5 failed attempts in 10 minutes
unusualTime: { start: 2, end: 6 } // 2 AM - 6 AM
};
async monitorTransaction(
wallet: string,
amount: string,
timestamp: Date
): Promise<void> {
const alerts: string[] = [];
// Check for large transaction
if (BigInt(amount) >= BigInt(this.alertThresholds.largeTransaction)) {
alerts.push('Large transaction detected');
}
// Check for unusual time
const hour = timestamp.getHours();
if (hour >= this.alertThresholds.unusualTime.start &&
hour <= this.alertThresholds.unusualTime.end) {
alerts.push('Transaction at unusual hour');
}
// Send alerts
if (alerts.length > 0) {
await this.sendAlert({
type: 'suspicious_activity',
wallet,
amount: HoosatUtils.sompiToAmount(amount),
timestamp,
alerts
});
}
}
private async sendAlert(alert: any): Promise<void> {
console.error('[SECURITY ALERT]', alert);
// Send to monitoring system
}
}
Best Practices Summary
Development Checklist
- Never hardcode private keys
- Use environment variables for secrets
- Store private keys encrypted
- Clear sensitive data from memory
- Validate all user inputs
- Sanitize inputs before processing
- Implement rate limiting
- Set transaction amount limits
- Use HTTPS/TLS for connections
- Verify node identity
- Implement audit logging
- Use separate hot/cold wallets
- Require confirmation for large transactions
- Monitor for suspicious activity
- Test on testnet first
- Keep dependencies updated
- Use TypeScript for type safety
- Handle errors gracefully
- Log security events
- Regular security audits
Production Checklist
- All keys stored in HSM or encrypted storage
- Multi-signature for large amounts
- Automated backups of encrypted keys
- 24/7 monitoring and alerting
- Incident response plan
- Regular security audits
- Penetration testing
- Code reviews for all changes
- Least privilege access
- Multi-factor authentication
- Disaster recovery plan
- Insurance for large holdings
- Regular security training
- Bug bounty program
- Compliance with regulations
Next Steps
- Wallet Management - Secure wallet handling
- Transaction Guide - Safe transaction processing
- Batch Payments - Secure batch processing