-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Build Cryptographic Utilities with Secure Key Management
Description:
Implement cryptographic utilities for key generation, signing, verification, and secure storage. This is a security-critical component that must follow best practices for non-custodial wallet architecture.
Context:
The wallet needs to generate keypairs, sign transactions, and securely store private keys encrypted with user passwords. Critical principle: Secret keys must NEVER be transmitted or stored on any server - they should only exist encrypted on the user's device. This package must be auditable and well-tested.
Security Architecture:
This implementation follows a non-custodial approach where:
- Secret keys are generated locally on the user's device
- Keys are encrypted with user password before storage
- Signing happens locally (keys never leave the device)
- Only signed transactions (not keys) are transmitted
For browser extensions, use chrome.storage.local for encrypted key storage. For mobile apps, use platform-specific secure storage (iOS Keychain, Android Shared Preferences with encryption).
Requirements:
- Implement keypair generation using
@stellar/stellar-sdk(Ed25519) - Add BIP39 mnemonic generation and validation (12 words)
- Derive keypair from mnemonic (support account index)
- Implement PBKDF2 key derivation from passwords (100k iterations, salt)
- Add AES-256-GCM encryption/decryption for secret keys
- Implement local transaction signing (keys never transmitted)
- Add signature verification functions
- Password strength validator (using zxcvbn)
- Secure random IV generation for each encryption
- Zero out sensitive data from memory after use
- Unit tests for all crypto functions (100% coverage required)
- Security documentation explaining all algorithms and threat model
Implementation Guide:
import * as StellarSdk from '@stellar/stellar-sdk';
import * as bip39 from 'bip39';
import { hkdf } from '@noble/hashes/hkdf';
import { sha256 } from '@noble/hashes/sha256';
import { pbkdf2 } from '@noble/hashes/pbkdf2';
import { xchacha20poly1305 } from '@noble/ciphers/chacha';
import { randomBytes } from '@noble/hashes/utils';
// 1. Generate mnemonic and keypair
export function generateMnemonic(): string {
return bip39.generateMnemonic(128); // 12 words
}
export function deriveKeypairFromMnemonic(
mnemonic: string,
accountIndex: number = 0
): StellarSdk.Keypair {
// Validate mnemonic
if (!bip39.validateMnemonic(mnemonic)) {
throw new Error('Invalid mnemonic');
}
// Generate seed from mnemonic
const seed = bip39.mnemonicToSeedSync(mnemonic);
// Derive key using HKDF
const path = `m/44'/148'/${accountIndex}'`;
const derivedKey = hkdf(sha256, seed, undefined, path, 32);
return StellarSdk.Keypair.fromRawEd25519Seed(Buffer.from(derivedKey));
}
// 2. Encrypt secret key with password (NEVER store unencrypted!)
export async function encryptSecretKey(
secretKey: string,
password: string
): Promise<{ ciphertext: string; salt: string; nonce: string }> {
// Generate random salt
const salt = randomBytes(32);
// Derive encryption key from password using PBKDF2
const derivedKey = pbkdf2(sha256, password, salt, {
c: 100_000, // 100k iterations
dkLen: 32
});
// Generate random nonce
const nonce = randomBytes(24);
// Encrypt using XChaCha20-Poly1305
const cipher = xchacha20poly1305(derivedKey, nonce);
const secretBytes = Buffer.from(secretKey, 'utf-8');
const ciphertext = cipher.encrypt(secretBytes);
return {
ciphertext: Buffer.from(ciphertext).toString('base64'),
salt: Buffer.from(salt).toString('base64'),
nonce: Buffer.from(nonce).toString('base64')
};
}
// 3. Decrypt secret key
export async function decryptSecretKey(
encrypted: { ciphertext: string; salt: string; nonce: string },
password: string
): Promise<string> {
const salt = Buffer.from(encrypted.salt, 'base64');
const nonce = Buffer.from(encrypted.nonce, 'base64');
const ciphertext = Buffer.from(encrypted.ciphertext, 'base64');
// Derive key from password
const derivedKey = pbkdf2(sha256, password, salt, {
c: 100_000,
dkLen: 32
});
// Decrypt
const cipher = xchacha20poly1305(derivedKey, nonce);
const decrypted = cipher.decrypt(ciphertext);
return Buffer.from(decrypted).toString('utf-8');
}
// 4. Sign transaction locally (key never leaves device)
export function signTransaction(
transaction: StellarSdk.Transaction,
secretKey: string
): StellarSdk.Transaction {
const keypair = StellarSdk.Keypair.fromSecret(secretKey);
transaction.sign(keypair);
return transaction;
}
// 5. Verify signature
export function verifySignature(
message: Buffer,
signature: Buffer,
publicKey: string
): boolean {
const keypair = StellarSdk.Keypair.fromPublicKey(publicKey);
return keypair.verify(message, signature);
}
// 6. Password strength validation
import zxcvbn from 'zxcvbn';
export function validatePasswordStrength(password: string): {
score: number; // 0-4
feedback: string[];
isValid: boolean;
} {
const result = zxcvbn(password);
return {
score: result.score,
feedback: [
...(result.feedback.warning ? [result.feedback.warning] : []),
...result.feedback.suggestions
],
isValid: result.score >= 3 // Require "strong" or better
};
}Usage:
import { Crypto } from '@ancore/crypto';
// Onboarding: Generate new wallet
const mnemonic = Crypto.generateMnemonic();
const keypair = Crypto.deriveKeypairFromMnemonic(mnemonic, 0);
// Validate password
const strength = Crypto.validatePasswordStrength('myPassword123!');
if (!strength.isValid) {
console.error('Password too weak:', strength.feedback);
}
// Encrypt and store secret key
const encrypted = await Crypto.encryptSecretKey(
keypair.secret(),
'userPassword'
);
// Store encrypted in chrome.storage.local
// Later: Sign transaction
const decrypted = await Crypto.decryptSecretKey(encrypted, 'userPassword');
const signedTx = Crypto.signTransaction(transaction, decrypted);
// Submit signedTx to networkKey Security Principles:
- Never transmit secret keys - Only send signed transactions
- Always encrypt before storage - Use user password + PBKDF2 + AES-GCM
- Use secure random - For salts, nonces, and IVs
- Zero out sensitive data - Clear keys from memory after use
- Validate mnemonics - Check BIP39 checksum before deriving keys
Files to Create:
packages/crypto/src/mnemonic.ts(BIP39 generation and derivation)packages/crypto/src/encryption.ts(PBKDF2 + XChaCha20-Poly1305)packages/crypto/src/signing.ts(Transaction signing and verification)packages/crypto/src/password.ts(Strength validation with zxcvbn)packages/crypto/src/types.ts(EncryptedKey type)packages/crypto/src/__tests__/mnemonic.test.tspackages/crypto/src/__tests__/encryption.test.tspackages/crypto/src/__tests__/signing.test.tspackages/crypto/src/__tests__/password.test.tspackages/crypto/SECURITY.md(Threat model and algorithm choices)packages/crypto/README.md(Usage examples and security warnings)
Dependencies:
@stellar/stellar-sdk(^12.0.0) - Ed25519 keypairs and signing@noble/hashes(^1.3.0) - PBKDF2, SHA256, HKDF@noble/ciphers(^0.4.0) - XChaCha20-Poly1305 encryptionbip39(^3.1.0) - BIP39 mnemonic generationzxcvbn(^4.6.0) - Password strength estimation
Note: Use @noble/* packages instead of WebCrypto for better compatibility and auditable code.
Success Criteria:
- All cryptographic operations use industry-standard algorithms (PBKDF2, XChaCha20-Poly1305, Ed25519)
- Secret keys NEVER logged, transmitted, or stored unencrypted
- Each encryption uses unique random salt and nonce
- Password must be "strong" (zxcvbn score ≥ 3)
- All edge cases tested (wrong password, corrupted data, invalid mnemonic)
- Sensitive data zeroed from memory after use
Definition of Done:
- All requirements implemented with security-first approach
- 100% test coverage for all crypto functions
- Security checklist completed (no key leaks, proper randomness)
- No secrets or real keys in tests or logs (use test fixtures)
- SECURITY.md explains algorithms, threat model, and custody approach
- Code follows non-custodial architecture (keys never leave device)
Additional Resources:
- Stellar Key Management Best Practices
- Application Design Considerations
- SEP-30: Account Recovery (future enhancement)
- OpenZeppelin Crypto Utilities (for Soroban contracts)
Labels: crypto, security, critical, needs-review
Complexity: 200 points (High)
Estimated Effort: 4-5 days
Priority: Critical
Questions or need help? Join our Telegram community