> For the complete documentation index, see [llms.txt](https://docs.thetanuts.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.thetanuts.finance/sdk/rfq-factory/key-management.md).

# 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:

| Environment | Default Provider           | Persistence  | Location                                                                             |
| ----------- | -------------------------- | ------------ | ------------------------------------------------------------------------------------ |
| **Node.js** | internal file storage      | Persistent   | `.thetanuts-keys/` directory (permissions 0o600)                                     |
| **Browser** | explicit provider required | App-defined  | Use encrypted/passphrase-backed storage; plaintext `localStorage` is not the default |
| **Testing** | `MemoryStorageProvider`    | Lost on exit | In-memory only                                                                       |

***

## Automatic Key Management

The simplest usage: call `getOrCreateKeyPair()` and the SDK handles everything.

```typescript
import { ethers } from 'ethers';
import { ThetanutsClient } from '@thetanuts-finance/thetanuts-client';

const provider = new ethers.JsonRpcProvider('https://mainnet.base.org');
const client = new ThetanutsClient({ chainId: 8453, provider });

// Returns existing keypair from storage, or generates and stores a new one
const keyPair = await client.rfqKeys.getOrCreateKeyPair();
console.log('Public Key:', keyPair.compressedPublicKey);
// Keys are saved automatically and survive process restarts (Node.js)
```

***

## Custom Storage Provider

Node.js persists keys to `./.thetanuts-keys/` automatically. To use a different backend, pass any object that implements `KeyStorageProvider`:

```typescript
import { ThetanutsClient } from '@thetanuts-finance/thetanuts-client';
import type { KeyStorageProvider } from '@thetanuts-finance/thetanuts-client';

const customStorage: KeyStorageProvider = {
  async get(keyId) { return myStore.get(keyId); },
  async set(keyId, privateKey) { await myStore.set(keyId, privateKey); },
  async remove(keyId) { await myStore.delete(keyId); },
  async has(keyId) { return myStore.has(keyId); },
};

const client = new ThetanutsClient({
  chainId: 8453,
  provider,
  keyStorageProvider: customStorage,
});

const keyPair = await client.rfqKeys.getOrCreateKeyPair();
```

***

## Memory Storage (Testing Only)

Use `MemoryStorageProvider` in tests or CI environments where you don't need persistence:

```typescript
import { ThetanutsClient, MemoryStorageProvider } from '@thetanuts-finance/thetanuts-client';

// WARNING: Keys are LOST when the process exits!
const client = new ThetanutsClient({
  chainId: 8453,
  provider,
  keyStorageProvider: new MemoryStorageProvider(),
});
```

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:

```typescript
import { KeyStorageProvider } from '@thetanuts-finance/thetanuts-client';

class MyDatabaseStorage implements KeyStorageProvider {
  async get(keyId: string): Promise<string | null> {
    return await myDatabase.get(keyId);
  }

  async set(keyId: string, privateKey: string): Promise<void> {
    await myDatabase.set(keyId, privateKey);
  }

  async remove(keyId: string): Promise<void> {
    await myDatabase.delete(keyId);
  }

  async has(keyId: string): Promise<boolean> {
    return await myDatabase.exists(keyId);
  }
}

const client = new ThetanutsClient({
  chainId: 8453,
  provider,
  keyStorageProvider: new MyDatabaseStorage(),
});
```

***

## 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 directory
> * **Browser**: pass an explicit `keyStorageProvider`; do not store production RFQ private keys in plaintext `localStorage`

### Export for Backup

```typescript
// Export private key (store this somewhere secure)
const privateKeyHex = await client.rfqKeys.exportPrivateKey();
console.log('Backup this key:', privateKeyHex);
```

### Import from Backup

```typescript
// Restore from a backed-up private key
const keyPair = await client.rfqKeys.importFromPrivateKey(
  privateKeyHex,
  true  // store = true saves it to the configured storage provider
);
console.log('Restored public key:', keyPair.compressedPublicKey);
```

***

## RFQKeyManagerModule Method Reference

| Method                                | Description                                        | Requires Signer |
| ------------------------------------- | -------------------------------------------------- | --------------- |
| `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:

```
1. Requester generates keypair:
   - Private key: 32 random bytes
   - Public key: Compressed (33 bytes, 0x02/0x03 prefix)

2. Market Maker generates ephemeral keypair per offer:
   - New keypair for each offer (forward secrecy)

3. Shared secret computation:
   ECDH produces: 0x04 || x-coordinate (32 bytes) || y-coordinate (32 bytes)

4. AES key derivation:
   AES-256 key = x-coordinate (first 32 bytes after 0x04 prefix)
   Note: raw x-coordinate, NOT a SHA256 hash — matches MM bot behavior

5. Encryption:
   Algorithm: AES-256-GCM
   IV: 12 random bytes
   Plaintext: JSON { "offerAmount": "...", "nonce": "..." }
   Output: IV (12 bytes) + ciphertext + auth tag (16 bytes)
```

### Nonce Format

The `nonce` field in decrypted offers can be in two formats depending on the source:

| Source | Format             | Example                |
| ------ | ------------------ | ---------------------- |
| 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":

1. **Regenerating keys between RFQ creation and decryption** — Always use `getOrCreateKeyPair()`, never `generateKeyPair()`, for production flows
2. **Running on a different machine** without copying `.thetanuts-keys/`
3. **Using `MemoryStorageProvider`** in a long-running process that restarts

Best practices:

* Use `getOrCreateKeyPair()` for all production RFQ flows
* Back up `.thetanuts-keys/` before deploying to new infrastructure
* Verify the stored key matches the on-chain key before attempting decryption:

```typescript
// Verify key matches the RFQ before decrypting
const keyPair = await client.rfqKeys.loadKeyPair();
const quotation = await client.optionFactory.getQuotation(rfqId);

if (keyPair.compressedPublicKey !== quotation.params.requesterPublicKey) {
  throw new Error('Key mismatch — cannot decrypt offers for this RFQ');
}
```

***

## Decryption Troubleshooting

### "KeyNotFoundError: RFQ key not found"

* No key in storage for the current `keyStorageProvider`
* Check 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 `encryptedOffer` is 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](/sdk/rfq-factory/create-rfq.md) — Where `getOrCreateKeyPair()` is used in context
* [Early Settlement](/sdk/rfq-factory/early-settlement.md) — Decrypting an offer to accept it before the deadline
* [RFQ Lifecycle](/sdk/rfq-factory/lifecycle.md) — How the sealed-bid auction uses ECDH encryption


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.thetanuts.finance/sdk/rfq-factory/key-management.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
