Key Management
Manage the ECDH keypairs used to encrypt and decrypt market maker offers in the RFQ system.
Why Keys Matter
Every RFQ you create includes your ECDH compressed public key. Market makers use it to encrypt their offers so that only you can decrypt them (sealed-bid privacy). The corresponding private key must be available when you want to:
Decrypt an offer for early settlement
Debug a decryption failure by comparing stored vs on-chain public keys
If you lose your private key, you cannot decrypt any offers made against RFQs that used the corresponding public key. There is no recovery mechanism.
Storage Providers
The SDK automatically selects a storage provider based on your runtime environment:
Node.js
FileStorageProvider
Persistent
.thetanuts-keys/ directory (permissions 0o600)
Browser
LocalStorageProvider
Persistent
Browser localStorage
Testing
MemoryStorageProvider
Lost on exit
In-memory only
Automatic Key Management
The simplest usage: call getOrCreateKeyPair() and the SDK handles everything.
Custom Storage Location (Node.js)
To save keys to a non-default directory, pass a FileStorageProvider at initialization:
Memory Storage (Testing Only)
Use MemoryStorageProvider in tests or CI environments where you don't need persistence:
The SDK logs a warning when MemoryStorageProvider is used, as a reminder that the keys will not persist.
Custom Storage Provider
Implement KeyStorageProvider to store keys in a database, cloud secret manager, or any other backend:
Key Backup Warning
CRITICAL: Back up your RFQ private keys. If lost, you cannot decrypt offers made to your public key. There is no recovery mechanism.
Node.js: Keys are in
.thetanuts-keys/with 0o600 permissions — back up this directoryBrowser: Keys are in
localStorage— cleared if the user clears browser data
Export for Backup
Import from Backup
RFQKeyManagerModule Method Reference
generateKeyPair()
Generate a new ECDH keypair (not stored)
No
getOrCreateKeyPair()
Get from storage, or generate and store a new one
No
loadKeyPair()
Load existing keypair from storage
No
hasStoredKey()
Check whether a key exists in storage
No
storeKeyPair(keypair)
Explicitly save a keypair to storage
No
removeStoredKey()
Delete the stored keypair
No
exportPrivateKey()
Export private key hex string for backup
No
importFromPrivateKey(key, store?)
Import a keypair from a private key hex
No
encryptOffer(amount, nonce, pubKey)
Encrypt an offer for a given public key
No
decryptOffer(data, pubKey)
Decrypt an incoming offer using stored private key
No
generateNonce()
Generate a random nonce
No
getPublicKeyFromPrivate(key)
Derive compressed public key from private key
No
isValidPublicKey(key)
Validate a compressed public key format
No
getStorageKeyId()
Get the storage identifier for the current key
No
Encryption Technical Details
ECDH Key Exchange
The SDK uses secp256k1 ECDH for secure key exchange between requester and market maker:
Nonce Format
The nonce field in decrypted offers can be in two formats depending on the source:
MM Bot
16-char hex string
"987563ef5fde9655"
SDK
Decimal string
"391788778684598574"
The SDK automatically detects and handles both formats during decryption.
Key Mismatch Prevention
Common mistakes that cause "Authentication failed" or "KeyNotFoundError":
Regenerating keys between RFQ creation and decryption — Always use
getOrCreateKeyPair(), nevergenerateKeyPair(), for production flowsRunning on a different machine without copying
.thetanuts-keys/Using
MemoryStorageProviderin a long-running process that restarts
Best practices:
Use
getOrCreateKeyPair()for all production RFQ flowsBack up
.thetanuts-keys/before deploying to new infrastructureVerify the stored key matches the on-chain key before attempting decryption:
Decryption Troubleshooting
"KeyNotFoundError: RFQ key not found"
No key in storage for the current
keyStorageProviderCheck that
.thetanuts-keys/exists (Node.js)If lost: create a new RFQ with a freshly generated key; old RFQ offers cannot be recovered
"DecryptionError: Invalid ciphertext"
Wrong private key, or the encrypted offer data is truncated
Verify
encryptedOfferis complete (from the event log, not truncated)
"DecryptionError: Authentication failed"
AES-GCM auth tag verification failed — shared secret is wrong
Confirm the stored keypair was used when creating the RFQ (compare public keys)
See Also
Create an RFQ — Where
getOrCreateKeyPair()is used in contextEarly Settlement — Decrypting an offer to accept it before the deadline
RFQ Lifecycle — How the sealed-bid auction uses ECDH encryption
Last updated

