Note: This package is currently in beta. Please test thoroughly in development environments before using in production.
A small, security-focused utility for generating, encrypting, and managing wallet secrets. It provides:
- Versioned, self-describing payloads
- PBKDF2-SHA256 key derivation
- Authenticated encryption using libsodium
crypto_secretbox - BIP-39 mnemonic/seed helpers
- Memory zeroization utilities
This module is part of the WDK (Wallet Development Kit) project, which empowers developers to build secure, non-custodial wallets with unified blockchain access, stateless architecture, and full user control.
For detailed documentation about the complete WDK ecosystem, visit https://docs.wallet.tether.io.
- PBKDF2-SHA256 KDF: Derives strong keys from user passkeys and a 16-byte salt
- Versioned Payloads: Header includes version, KDF algorithm, iterations, salt, and nonce
- Authenticated Encryption: Uses libsodium
crypto_secretbox(XSalsa20-Poly1305) - BIP-39 Support: Convert entropy β mnemonic and derive the standard 64-byte seed
- Master-Key Override: Optionally provide a 32-byte key to skip PBKDF2
- Secure Memory Handling: Zeroizes sensitive buffers and supports
dispose() - Bare Runtime Support: Ships a
barebuild forbare-wdk-runtime
Install with npm:
npm install @tetherto/wdk-secret-managerimport WdkSecretManager from '@tetherto/wdk-secret-manager'If you are using the bare runtime, import as usual; the bare export is provided for compatible bundlers/environments.
import WdkSecretManager from '@tetherto/wdk-secret-manager'
const passkey = 'correct horse battery staple' // minimum 12 characters
const salt = WdkSecretManager.generateSalt() // 16-byte Buffer
// Optional: tune PBKDF2 iterations
const sm = new WdkSecretManager(passkey, salt, { iterations: 100_000 })// Generates 16-byte entropy, converts to BIP-39 mnemonic, derives 64-byte seed,
// and encrypts both with the manager's settings
const { encryptedEntropy, encryptedSeed } = await sm.generateAndEncrypt()
// Decrypt later
const entropy = sm.decrypt(encryptedEntropy) // 16 bytes
const seed = sm.decrypt(encryptedSeed) // 64 bytes// Accepts payloads between 16 and 64 bytes
const data = crypto.getRandomValues(new Uint8Array(32))
const payload = sm.encrypt(Buffer.from(data))
const out = sm.decrypt(payload)import { pbkdf2Sync } from 'crypto'
import b4a from 'b4a'
const masterKey = b4a.from(pbkdf2Sync(b4a.from(passkey), b4a.from(salt), 100_000, 32, 'sha256'))
const cipher = sm.encrypt(Buffer.from('0123456789abcdef0123456789abcdef'), masterKey)
const plain = sm.decrypt(cipher, masterKey)const entropy16 = sm.generateRandomBuffer() // 16 bytes
const mnemonic = sm.entropyToMnemonic(entropy16) // 12 words
const entropyRoundTrip = sm.mnemonicToEntropy(mnemonic) // back to 16 bytes// Wipes internal passkey/salt/iteration state from memory
sm.dispose()| Class | Description | Methods |
|---|---|---|
WdkSecretManager |
High-level manager for secret generation, encryption, and decryption. | constructor, generateSalt, generateAndEncrypt, encrypt, decrypt, entropyToMnemonic, mnemonicToEntropy, generateRandomBuffer, dispose |
The main class for generating and managing encrypted secrets.
new WdkSecretManager(passKey, salt, kdfParams?)Parameters:
passKey(string | Buffer | Uint8Array): User passkey (min 12 characters/bytes)salt(Buffer): 16-byte salt used for key derivationkdfParams(object, optional):iterations(number, optional): PBKDF2 iterations (default: 100_000)
generateSalt(): Buffer- Returns a new 16-byte random salt.
| Method | Description | Returns |
|---|---|---|
generateAndEncrypt(entropyOpt?, masterKeyOpt?) |
Generates 16-byte entropy, derives mnemonic + 64-byte seed, encrypts both. | { encryptedSeed: Buffer, encryptedEntropy: Buffer } |
encrypt(data, masterKeyOpt?) |
Encrypts 16β64 byte payload with header and MAC. | Buffer (payload) |
decrypt(payload, masterKeyOpt?) |
Decrypts a payload produced by this manager. | Buffer (plaintext) |
generateRandomBuffer() |
Returns 16 random bytes. | Buffer |
entropyToMnemonic(entropy) |
Converts 16-byte entropy to 12-word mnemonic. | string |
mnemonicToEntropy(mnemonic) |
Converts 12-word mnemonic to 16-byte entropy. | Buffer |
dispose() |
Zeroizes internal state; instance becomes unusable. | void |
Header [version(1), kdf_alg(1), iterations(u32le), reserved(u32le=0), salt(16), nonce(24)] followed by cipher = secretbox( [len(1) | data(16..64)], nonce, key).
Generates 16-byte entropy, converts it to a BIP-39 12-word mnemonic, derives the 64-byte BIP-39 seed, and encrypts both values.
Parameters:
entropyOpt(Buffer | null, optional): If provided, must be exactly 16 bytes. When not provided, secure random entropy is generated.masterKeyOpt(Buffer | null, optional): A 32-byte key. If provided, PBKDF2 derivation is skipped and this key is used for encryption.
Returns: { encryptedSeed: Buffer, encryptedEntropy: Buffer }
Example:
const { encryptedSeed, encryptedEntropy } = await sm.generateAndEncrypt()
const seed = sm.decrypt(encryptedSeed) // 64 bytes
const entropy = sm.decrypt(encryptedEntropy) // 16 bytesEncrypts a 16β64 byte payload using a versioned header and crypto_secretbox. The plaintext is prefixed with a single-byte length before encryption.
Parameters:
data(Buffer): Plaintext data. Must be between 16 and 64 bytes inclusive.masterKeyOpt(Buffer | null, optional): 32-byte master key. If omitted, a key is derived via PBKDF2-SHA256 from the manager's passkey + salt.
Returns: Buffer - Encrypted payload with header and MAC.
Throws: on invalid input length, missing/invalid passkey or salt, or other validation errors.
Example:
const data = Buffer.from('0123456789abcdef0123456789abcdef') // 32 bytes
const payload = sm.encrypt(data)Decrypts a payload produced by this manager, validates the header, and returns the original plaintext.
Parameters:
payload(Buffer): Encrypted payload produced byencrypt.masterKeyOpt(Buffer | null, optional): 32-byte master key. If omitted, a key is derived via PBKDF2-SHA256 using the header's salt and iteration count.
Returns: Buffer - Decrypted plaintext.
Throws: when authentication fails, payload is malformed, length prefix is out of bounds, or inputs are invalid.
Example:
const plain = sm.decrypt(payload)Generates 16 bytes of cryptographically secure random data using libsodium.
Returns: Buffer
Example:
const entropy16 = sm.generateRandomBuffer()Converts 16-byte entropy into a 12-word BIP-39 mnemonic.
Parameters:
entropy(Buffer): Exactly 16 bytes.
Returns: string - 12-word mnemonic.
Throws: on invalid type or length.
Example:
const mnemonic = sm.entropyToMnemonic(entropy16)Converts a 12-word mnemonic into its original 16-byte entropy buffer.
Parameters:
mnemonic(string): Non-empty 12-word BIP-39 mnemonic.
Returns: Buffer - 16-byte entropy.
Throws: on invalid/empty string or non-12-word mnemonics.
Example:
const entropy = sm.mnemonicToEntropy(mnemonic)Securely wipes internal state (passkey, salt, iterations) from memory. The instance should not be used after calling this.
Returns: void
Example:
sm.dispose()- Node.js: Uses
sodium-nativeand Nodecryptofor PBKDF2 - Bare Runtime: Ships a
barebuild; PBKDF2 provided bybare-crypto
- Passkey Strength: Enforce long, high-entropy user passkeys (min 12 characters)
- Salt Handling: Use a unique 16-byte salt per user/passkey, store it with the payload
- KDF Parameters: Tune PBKDF2 iterations to balance security and performance
- Integrity & Confidentiality:
crypto_secretboxprovides authenticated encryption - Master Key: Only use a 32-byte master key if you securely derive/transport it
- Memory Hygiene: Call
dispose()after use to wipe sensitive state
# Install dependencies
npm install
# Build TypeScript definitions
npm run build:types
# Lint code
npm run lint
# Fix linting issues
npm run lint:fix# Run bare runtime tests
npm run test:bareThis project is licensed under the Apache License 2.0 - see the LICENSE file for details.
Contributions are welcome! Please feel free to submit a Pull Request.
For support, please open an issue on the repository.