w3pk 0.9.3 → 0.10.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -0
- package/dist/index.d.mts +218 -1
- package/dist/index.d.ts +218 -1
- package/dist/index.js +107 -105
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +107 -105
- package/dist/index.mjs.map +1 -1
- package/dist/inspect/browser.d.mts +5 -0
- package/dist/inspect/browser.d.ts +5 -0
- package/dist/inspect/browser.js +20 -3
- package/dist/inspect/browser.js.map +1 -1
- package/dist/inspect/browser.mjs +20 -3
- package/dist/inspect/browser.mjs.map +1 -1
- package/dist/inspect/index.js +20 -3
- package/dist/inspect/index.js.map +1 -1
- package/dist/inspect/index.mjs +20 -3
- package/dist/inspect/index.mjs.map +1 -1
- package/dist/inspect/node.d.mts +5 -0
- package/dist/inspect/node.d.ts +5 -0
- package/dist/inspect/node.js +13 -2
- package/dist/inspect/node.js.map +1 -1
- package/dist/inspect/node.mjs +13 -2
- package/dist/inspect/node.mjs.map +1 -1
- package/docs/API_REFERENCE.md +270 -2
- package/docs/ARCHITECTURE.md +42 -45
- package/docs/BUILD_VERIFICATION.md +3 -3
- package/docs/INTEGRATION_GUIDELINES.md +2 -2
- package/docs/POST_QUANTUM.md +288 -3
- package/docs/SECURITY.md +239 -20
- package/package.json +13 -10
package/docs/ARCHITECTURE.md
CHANGED
|
@@ -148,33 +148,42 @@ m/44'/60'/0'/0/2 ← Third address
|
|
|
148
148
|
|
|
149
149
|
**Purpose**: Link WebAuthn authentication to wallet encryption
|
|
150
150
|
|
|
151
|
-
**Implementation**: [src/wallet/crypto.ts
|
|
151
|
+
**Implementation**: [src/wallet/crypto.ts](src/wallet/crypto.ts)
|
|
152
|
+
|
|
153
|
+
✅ **SECURITY**: Uses PRF-based encryption with authenticator-held secrets and random salts
|
|
152
154
|
|
|
153
|
-
**
|
|
155
|
+
**SECURE Implementation (PRF-based)**:
|
|
154
156
|
```typescript
|
|
155
157
|
function deriveEncryptionKeyFromWebAuthn(
|
|
156
|
-
|
|
157
|
-
|
|
158
|
+
prfOutput: ArrayBuffer, // From WebAuthn PRF extension
|
|
159
|
+
salt: Uint8Array // Random 32-byte salt
|
|
158
160
|
): CryptoKey {
|
|
159
|
-
// Step 1:
|
|
160
|
-
|
|
161
|
+
// Step 1: Validate inputs
|
|
162
|
+
assert(prfOutput.byteLength === 32);
|
|
163
|
+
assert(salt.byteLength === 32);
|
|
161
164
|
|
|
162
|
-
// Step 2:
|
|
163
|
-
const
|
|
165
|
+
// Step 2: Import PRF output (authenticator-held secret)
|
|
166
|
+
const keyMaterial = importKey(prfOutput);
|
|
164
167
|
|
|
165
|
-
// Step 3: PBKDF2 key derivation
|
|
166
|
-
const encryptionKey = PBKDF2(
|
|
168
|
+
// Step 3: PBKDF2 key derivation with random salt
|
|
169
|
+
const encryptionKey = PBKDF2(keyMaterial, salt, 210000);
|
|
167
170
|
|
|
168
171
|
// Result: AES-256-GCM key (NEVER STORED)
|
|
169
172
|
return encryptionKey;
|
|
170
173
|
}
|
|
171
174
|
```
|
|
172
175
|
|
|
173
|
-
**Properties**:
|
|
174
|
-
- **
|
|
175
|
-
- **
|
|
176
|
-
- **
|
|
177
|
-
- **
|
|
176
|
+
**Security Properties**:
|
|
177
|
+
- **Authenticator Secret**: Uses PRF output from hardware (never exposed to application)
|
|
178
|
+
- **Random Salts**: Each encryption uses unique 32-byte salt (prevents precomputation)
|
|
179
|
+
- **PBKDF2**: 210,000 iterations (OWASP 2023 recommendation)
|
|
180
|
+
- **AES-256-GCM**: Strong authenticated encryption
|
|
181
|
+
- **Authentication-gated**: Requires biometric/PIN to trigger PRF
|
|
182
|
+
|
|
183
|
+
**Implementation Note**:
|
|
184
|
+
The SDK uses `deriveEncryptionKeyAuto()` as a helper that:
|
|
185
|
+
1. Uses PRF-based encryption when available (secure)
|
|
186
|
+
2. Falls back to v2 implementation for existing wallets without PRF support
|
|
178
187
|
|
|
179
188
|
---
|
|
180
189
|
|
|
@@ -217,11 +226,12 @@ STEP 2: Create WebAuthn Credential (INDEPENDENT)
|
|
|
217
226
|
|
|
218
227
|
STEP 3: Derive Encryption Key
|
|
219
228
|
┌──────────────────────────────────────┐
|
|
220
|
-
│
|
|
229
|
+
│ deriveEncryptionKeyFromWebAuthn() │
|
|
221
230
|
│ │
|
|
222
|
-
│
|
|
231
|
+
│ Input: PRF output (32 bytes secret) │
|
|
232
|
+
│ + random salt (32 bytes) │
|
|
223
233
|
│ ↓ │
|
|
224
|
-
│
|
|
234
|
+
│ Import PRF as key material │
|
|
225
235
|
│ ↓ │
|
|
226
236
|
│ PBKDF2 (210,000 iterations) │
|
|
227
237
|
│ ↓ │
|
|
@@ -486,43 +496,29 @@ const signedTx = await wallet.signTransaction(tx);
|
|
|
486
496
|
**Implementation**: [src/wallet/crypto.ts:21-66](src/wallet/crypto.ts#L21-L66)
|
|
487
497
|
|
|
488
498
|
```javascript
|
|
499
|
+
// SECURE: PRF-based key derivation
|
|
489
500
|
async function deriveEncryptionKeyFromWebAuthn(
|
|
490
|
-
|
|
491
|
-
|
|
501
|
+
prfOutput: ArrayBuffer, // From WebAuthn PRF extension
|
|
502
|
+
salt: Uint8Array // Random 32-byte salt
|
|
492
503
|
): Promise<CryptoKey> {
|
|
493
|
-
// 1.
|
|
494
|
-
const keyMaterial =
|
|
495
|
-
|
|
496
|
-
// 2. SHA-256 hash
|
|
497
|
-
const keyMaterialHash = await crypto.subtle.digest(
|
|
498
|
-
"SHA-256",
|
|
499
|
-
new TextEncoder().encode(keyMaterial)
|
|
500
|
-
);
|
|
501
|
-
|
|
502
|
-
// 3. Import as PBKDF2 base key
|
|
503
|
-
const importedKey = await crypto.subtle.importKey(
|
|
504
|
+
// 1. Import PRF output as key material (authenticator secret)
|
|
505
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
504
506
|
"raw",
|
|
505
|
-
|
|
507
|
+
prfOutput, // SECRET from authenticator
|
|
506
508
|
{ name: "PBKDF2" },
|
|
507
509
|
false,
|
|
508
510
|
["deriveKey"]
|
|
509
511
|
);
|
|
510
512
|
|
|
511
|
-
//
|
|
512
|
-
const salt = await crypto.subtle.digest(
|
|
513
|
-
"SHA-256",
|
|
514
|
-
new TextEncoder().encode("w3pk-salt-v4")
|
|
515
|
-
);
|
|
516
|
-
|
|
517
|
-
// 5. Derive AES-256-GCM key
|
|
513
|
+
// 2. Derive AES-256-GCM key with PBKDF2
|
|
518
514
|
return crypto.subtle.deriveKey(
|
|
519
515
|
{
|
|
520
516
|
name: "PBKDF2",
|
|
521
|
-
salt:
|
|
517
|
+
salt: salt, // Random 32-byte salt (stored with ciphertext)
|
|
522
518
|
iterations: 210000, // OWASP 2023 recommendation
|
|
523
519
|
hash: "SHA-256",
|
|
524
520
|
},
|
|
525
|
-
|
|
521
|
+
keyMaterial, // PRF output from authenticator
|
|
526
522
|
{ name: "AES-GCM", length: 256 },
|
|
527
523
|
false,
|
|
528
524
|
["encrypt", "decrypt"]
|
|
@@ -530,11 +526,12 @@ async function deriveEncryptionKeyFromWebAuthn(
|
|
|
530
526
|
}
|
|
531
527
|
```
|
|
532
528
|
|
|
533
|
-
**Properties**:
|
|
534
|
-
- **
|
|
535
|
-
- **
|
|
529
|
+
**Security Properties**:
|
|
530
|
+
- **Authenticator-Bound**: PRF output is hardware-held secret (never exposed)
|
|
531
|
+
- **Unique Salts**: Each encryption uses random salt (no precomputation)
|
|
532
|
+
- **Strong KDF**: 210,000 PBKDF2 iterations make brute-force impractical (OWASP 2023)
|
|
536
533
|
- **Stateless**: No need to store the encryption key
|
|
537
|
-
- **Fast
|
|
534
|
+
- **Fast**: ~100-200ms on modern devices
|
|
538
535
|
|
|
539
536
|
---
|
|
540
537
|
|
|
@@ -72,7 +72,7 @@ import { getCurrentBuildHash } from 'w3pk';
|
|
|
72
72
|
|
|
73
73
|
const hash = await getCurrentBuildHash();
|
|
74
74
|
console.log('Build hash:', hash);
|
|
75
|
-
// =>
|
|
75
|
+
// => bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
### `getW3pkBuildHash(distUrl)`
|
|
@@ -108,7 +108,7 @@ Verifies if the current build matches an expected hash.
|
|
|
108
108
|
```typescript
|
|
109
109
|
import { verifyBuildHash } from 'w3pk';
|
|
110
110
|
|
|
111
|
-
const trustedHash = '
|
|
111
|
+
const trustedHash = 'bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy';
|
|
112
112
|
const isValid = await verifyBuildHash(trustedHash);
|
|
113
113
|
|
|
114
114
|
if (isValid) {
|
|
@@ -152,7 +152,7 @@ Output:
|
|
|
152
152
|
📊 Total size: 273992 bytes
|
|
153
153
|
|
|
154
154
|
🔐 IPFS Build Hash (CIDv1):
|
|
155
|
-
|
|
155
|
+
bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy
|
|
156
156
|
bafyxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
|
|
157
157
|
|
|
158
158
|
📌 Version: x.x.x
|
|
@@ -1426,7 +1426,7 @@ const strictWallet = await w3pk.deriveWallet('STRICT')
|
|
|
1426
1426
|
// ✅ Verify package integrity on app initialization
|
|
1427
1427
|
import { verifyBuildHash } from 'w3pk'
|
|
1428
1428
|
|
|
1429
|
-
const TRUSTED_HASH = '
|
|
1429
|
+
const TRUSTED_HASH = 'bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy'
|
|
1430
1430
|
|
|
1431
1431
|
async function initializeApp() {
|
|
1432
1432
|
const isValid = await verifyBuildHash(TRUSTED_HASH)
|
|
@@ -1486,7 +1486,7 @@ class WalletManager {
|
|
|
1486
1486
|
|
|
1487
1487
|
async initialize() {
|
|
1488
1488
|
// Verify package integrity
|
|
1489
|
-
const TRUSTED_HASH = '
|
|
1489
|
+
const TRUSTED_HASH = 'bafybeicce26ztznqcdn6n6j6hhnoye37nb7nt7gtubelwk2ku4txbp7acy'
|
|
1490
1490
|
const isValid = await verifyBuildHash(TRUSTED_HASH)
|
|
1491
1491
|
if (!isValid) throw new Error('Package integrity check failed')
|
|
1492
1492
|
}
|
package/docs/POST_QUANTUM.md
CHANGED
|
@@ -1290,14 +1290,299 @@ const delegation = await w3pk.requestExternalWalletDelegation({
|
|
|
1290
1290
|
|
|
1291
1291
|
---
|
|
1292
1292
|
|
|
1293
|
+
## ML-KEM Encryption
|
|
1294
|
+
|
|
1295
|
+
w3pk supports ML-KEM-1024 encryption for quantum-resistant data protection with multi-recipient support and deterministic key derivation from private keys.
|
|
1296
|
+
|
|
1297
|
+
### Deterministic Key Derivation
|
|
1298
|
+
|
|
1299
|
+
Derive ML-KEM keypairs deterministically from any private key material (Ethereum keys, random seeds, etc.):
|
|
1300
|
+
|
|
1301
|
+
```typescript
|
|
1302
|
+
import { deriveMLKemKeypair, mlkemEncryptWithKey, mlkemDecryptWithKey } from 'w3pk';
|
|
1303
|
+
|
|
1304
|
+
// Derive ML-KEM keypair from Ethereum private key
|
|
1305
|
+
const ethPrivateKey = '0x1234...'; // Your wallet private key
|
|
1306
|
+
const keypair = await deriveMLKemKeypair(ethPrivateKey, 'my-app');
|
|
1307
|
+
// Returns: { publicKey: Uint8Array(1568), privateKey: Uint8Array(3168) }
|
|
1308
|
+
|
|
1309
|
+
// Share your public key with others
|
|
1310
|
+
const publicKeyBase64 = Buffer.from(keypair.publicKey).toString('base64');
|
|
1311
|
+
|
|
1312
|
+
// Later: Recreate keypair from same private key (deterministic!)
|
|
1313
|
+
const sameKeypair = await deriveMLKemKeypair(ethPrivateKey, 'my-app');
|
|
1314
|
+
// sameKeypair.publicKey === keypair.publicKey ✅
|
|
1315
|
+
```
|
|
1316
|
+
|
|
1317
|
+
### Encrypt/Decrypt with w3pk Instance (Recommended)
|
|
1318
|
+
|
|
1319
|
+
The easiest way to use ML-KEM encryption - no private key exposure:
|
|
1320
|
+
|
|
1321
|
+
```typescript
|
|
1322
|
+
import { createWeb3Passkey } from 'w3pk';
|
|
1323
|
+
|
|
1324
|
+
const w3pk = createWeb3Passkey();
|
|
1325
|
+
await w3pk.login();
|
|
1326
|
+
|
|
1327
|
+
// 1. Share your public key with others
|
|
1328
|
+
const myPublicKey = await w3pk.deriveMLKemPublicKey();
|
|
1329
|
+
// Send myPublicKey to the server or publish it
|
|
1330
|
+
|
|
1331
|
+
// 2. Get server's public key
|
|
1332
|
+
const serverPubKey = await fetch('/api/mlkem-public-key').then(r => r.text());
|
|
1333
|
+
|
|
1334
|
+
// 3. Encrypt for yourself + server
|
|
1335
|
+
const encrypted = await w3pk.mlkemEncrypt(
|
|
1336
|
+
'my secret data',
|
|
1337
|
+
[serverPubKey] // Server can decrypt
|
|
1338
|
+
);
|
|
1339
|
+
// You can also decrypt because you're auto-added as recipient
|
|
1340
|
+
|
|
1341
|
+
// 4. Later: Decrypt locally (server never sees plaintext!)
|
|
1342
|
+
const plaintext = await w3pk.mlkemDecrypt(encrypted);
|
|
1343
|
+
```
|
|
1344
|
+
|
|
1345
|
+
**Security:** Your Ethereum private key never leaves the w3pk instance, matching the security model of `signMessage()` and `sendTransaction()`.
|
|
1346
|
+
|
|
1347
|
+
**Supported Modes:**
|
|
1348
|
+
- ✅ **STANDARD** (default) - Private key derived internally, not exposed to app
|
|
1349
|
+
- ✅ **STRICT** - Same as STANDARD but always requires WebAuthn re-authentication
|
|
1350
|
+
- ✅ **YOLO** - Private key available to app
|
|
1351
|
+
- ❌ **PRIMARY** - Not supported (uses P-256 WebAuthn keys, not Ethereum keys)
|
|
1352
|
+
|
|
1353
|
+
### Encrypt/Decrypt with Key Derivation (Low-level API)
|
|
1354
|
+
|
|
1355
|
+
For advanced use cases where you manage private keys directly:
|
|
1356
|
+
|
|
1357
|
+
```typescript
|
|
1358
|
+
// Client encrypts for themselves + server
|
|
1359
|
+
const serverKeypair = await deriveMLKemKeypair(serverEthKey, 'server');
|
|
1360
|
+
const encrypted = await mlkemEncryptWithKey(
|
|
1361
|
+
'my secret data',
|
|
1362
|
+
myEthPrivateKey, // Sender's private key (auto-added as recipient)
|
|
1363
|
+
[serverKeypair.publicKey], // Additional recipients
|
|
1364
|
+
'client' // Context for sender's key derivation
|
|
1365
|
+
);
|
|
1366
|
+
|
|
1367
|
+
// Later: Client decrypts locally (no server needed!)
|
|
1368
|
+
const plaintext = await mlkemDecryptWithKey(
|
|
1369
|
+
encrypted,
|
|
1370
|
+
myEthPrivateKey, // Same private key used for encryption
|
|
1371
|
+
'client' // Same context
|
|
1372
|
+
);
|
|
1373
|
+
|
|
1374
|
+
// Or: Server decrypts for operations
|
|
1375
|
+
const plaintext = await mlkemDecryptWithKey(
|
|
1376
|
+
encrypted,
|
|
1377
|
+
serverEthKey,
|
|
1378
|
+
'server'
|
|
1379
|
+
);
|
|
1380
|
+
```
|
|
1381
|
+
|
|
1382
|
+
### Direct Encryption (Without Key Derivation)
|
|
1383
|
+
|
|
1384
|
+
For maximum flexibility, use raw public keys directly:
|
|
1385
|
+
|
|
1386
|
+
```typescript
|
|
1387
|
+
import { mlkemEncrypt, mlkemDecrypt } from 'w3pk';
|
|
1388
|
+
|
|
1389
|
+
// Single recipient
|
|
1390
|
+
const publicKey = 'base64_encoded_public_key...'; // 1568 bytes
|
|
1391
|
+
const encrypted = await mlkemEncrypt('my secret data', publicKey);
|
|
1392
|
+
|
|
1393
|
+
// Multiple recipients (encrypt once, multiple people can decrypt)
|
|
1394
|
+
const encrypted = await mlkemEncrypt('my secret data', [
|
|
1395
|
+
publicKey1,
|
|
1396
|
+
publicKey2,
|
|
1397
|
+
publicKey3
|
|
1398
|
+
]);
|
|
1399
|
+
// Returns: { recipients: [...], encryptedData, iv, authTag }
|
|
1400
|
+
|
|
1401
|
+
// Decrypt with your private key (auto-detects which recipient you are)
|
|
1402
|
+
const privateKey = 'base64_encoded_private_key...'; // 3168 bytes
|
|
1403
|
+
const plaintext = await mlkemDecrypt(encrypted, privateKey);
|
|
1404
|
+
// Returns: 'my secret data'
|
|
1405
|
+
|
|
1406
|
+
// Or specify your public key for faster lookup
|
|
1407
|
+
const plaintext = await mlkemDecrypt(encrypted, privateKey, publicKey);
|
|
1408
|
+
```
|
|
1409
|
+
|
|
1410
|
+
### Security Properties
|
|
1411
|
+
|
|
1412
|
+
- ✅ **ML-KEM-1024** (NIST FIPS 203) - Post-quantum secure
|
|
1413
|
+
- ✅ **AES-256-GCM** - 128-bit quantum security (sufficient)
|
|
1414
|
+
- ✅ **Key zeroization** - Shared secrets securely wiped from memory
|
|
1415
|
+
- ✅ **Hybrid encryption** - KEM + symmetric cipher for efficiency
|
|
1416
|
+
- ✅ **Cross-platform** - Works in browser and Node.js
|
|
1417
|
+
|
|
1418
|
+
### API Reference
|
|
1419
|
+
|
|
1420
|
+
#### Instance Methods (Recommended)
|
|
1421
|
+
|
|
1422
|
+
These methods work with your w3pk wallet instance - no private key exposure:
|
|
1423
|
+
|
|
1424
|
+
#### `w3pk.deriveMLKemPublicKey(options?): Promise<string>`
|
|
1425
|
+
|
|
1426
|
+
Derives your ML-KEM-1024 public key that can be shared for encryption.
|
|
1427
|
+
|
|
1428
|
+
**Parameters:**
|
|
1429
|
+
- `options.context` - Context string for domain separation (default: `'mlkem-v1'`)
|
|
1430
|
+
- `options.mode` - Security mode: `'STANDARD'`, `'STRICT'`, or `'YOLO'` (default: `'STANDARD'`)
|
|
1431
|
+
- `options.tag` - Tag for address derivation (default: `'MAIN'`)
|
|
1432
|
+
- `options.origin` - Origin for derivation (default: current origin)
|
|
1433
|
+
- `options.requireAuth` - Force WebAuthn re-authentication (default: false, always true in STRICT mode)
|
|
1434
|
+
|
|
1435
|
+
**Note:** PRIMARY mode is not supported as it uses P-256 WebAuthn keys instead of Ethereum keys.
|
|
1436
|
+
|
|
1437
|
+
**Returns:** Base64-encoded ML-KEM public key (1568 bytes)
|
|
1438
|
+
|
|
1439
|
+
**Example:**
|
|
1440
|
+
```typescript
|
|
1441
|
+
const w3pk = createWeb3Passkey();
|
|
1442
|
+
await w3pk.login();
|
|
1443
|
+
const pubKey = await w3pk.deriveMLKemPublicKey();
|
|
1444
|
+
```
|
|
1445
|
+
|
|
1446
|
+
#### `w3pk.mlkemEncrypt(plaintext: string, recipientPublicKeys: Array<string | Uint8Array>, options?): Promise<EncryptedPayload>`
|
|
1447
|
+
|
|
1448
|
+
Encrypts data for yourself and additional recipients.
|
|
1449
|
+
|
|
1450
|
+
**Parameters:**
|
|
1451
|
+
- `plaintext` - The data to encrypt
|
|
1452
|
+
- `recipientPublicKeys` - Array of recipient ML-KEM public keys (base64 or Uint8Array)
|
|
1453
|
+
- `options.context` - Context string (default: `'mlkem-v1'`)
|
|
1454
|
+
- `options.mode` - Security mode: `'STANDARD'`, `'STRICT'`, or `'YOLO'` (default: `'STANDARD'`)
|
|
1455
|
+
- `options.tag` - Tag for address derivation (default: `'MAIN'`)
|
|
1456
|
+
- `options.origin` - Origin (default: current origin)
|
|
1457
|
+
- `options.requireAuth` - Force re-authentication (default: false, always true in STRICT mode)
|
|
1458
|
+
|
|
1459
|
+
**Returns:** Encrypted payload
|
|
1460
|
+
|
|
1461
|
+
**Example:**
|
|
1462
|
+
```typescript
|
|
1463
|
+
const encrypted = await w3pk.mlkemEncrypt('secret', [serverPubKey]);
|
|
1464
|
+
```
|
|
1465
|
+
|
|
1466
|
+
#### `w3pk.mlkemDecrypt(payload: EncryptedPayload, options?): Promise<string>`
|
|
1467
|
+
|
|
1468
|
+
Decrypts data encrypted for your wallet.
|
|
1469
|
+
|
|
1470
|
+
**Parameters:**
|
|
1471
|
+
- `payload` - The encrypted payload
|
|
1472
|
+
- `options.context` - Context string (default: `'mlkem-v1'`, must match encryption)
|
|
1473
|
+
- `options.mode` - Security mode: `'STANDARD'`, `'STRICT'`, or `'YOLO'` (default: `'STANDARD'`)
|
|
1474
|
+
- `options.tag` - Tag (default: `'MAIN'`)
|
|
1475
|
+
- `options.origin` - Origin (default: current origin)
|
|
1476
|
+
- `options.requireAuth` - Force re-authentication (default: false, always true in STRICT mode)
|
|
1477
|
+
|
|
1478
|
+
**Returns:** Decrypted plaintext
|
|
1479
|
+
|
|
1480
|
+
**Example:**
|
|
1481
|
+
```typescript
|
|
1482
|
+
const plaintext = await w3pk.mlkemDecrypt(encrypted);
|
|
1483
|
+
```
|
|
1484
|
+
|
|
1485
|
+
---
|
|
1486
|
+
|
|
1487
|
+
#### Low-Level Functions
|
|
1488
|
+
|
|
1489
|
+
These functions require managing private keys directly:
|
|
1490
|
+
|
|
1491
|
+
#### `deriveMLKemKeypair(privateKey: string | Uint8Array, context?: string): Promise<MLKemKeypair>`
|
|
1492
|
+
|
|
1493
|
+
Derives a deterministic ML-KEM-1024 keypair from any private key material using HKDF-SHA256.
|
|
1494
|
+
|
|
1495
|
+
**Parameters:**
|
|
1496
|
+
- `privateKey` - Private key material (hex string with optional `0x` prefix, or Uint8Array)
|
|
1497
|
+
- `context` - Optional context string for domain separation (default: `'mlkem-v1'`)
|
|
1498
|
+
|
|
1499
|
+
**Returns:** `MLKemKeypair` object containing:
|
|
1500
|
+
- `publicKey` - ML-KEM-1024 public key (Uint8Array, 1568 bytes)
|
|
1501
|
+
- `privateKey` - ML-KEM-1024 private key (Uint8Array, 3168 bytes)
|
|
1502
|
+
|
|
1503
|
+
**Security:** Uses HKDF with salt `"mlkem-keypair-v1"` and the provided context to derive a 64-byte seed, then generates a deterministic ML-KEM keypair. Same input always produces same output. All sensitive material is zeroized after use.
|
|
1504
|
+
|
|
1505
|
+
#### `mlkemEncryptWithKey(plaintext: string, senderPrivateKey: string | Uint8Array, recipientPublicKeys: Array<string | Uint8Array>, senderContext?: string): Promise<EncryptedPayload>`
|
|
1506
|
+
|
|
1507
|
+
Convenience function that derives the sender's ML-KEM keypair, then encrypts for sender + recipients.
|
|
1508
|
+
|
|
1509
|
+
**Parameters:**
|
|
1510
|
+
- `plaintext` - The data to encrypt
|
|
1511
|
+
- `senderPrivateKey` - Sender's private key (hex string or Uint8Array)
|
|
1512
|
+
- `recipientPublicKeys` - Array of recipient ML-KEM public keys
|
|
1513
|
+
- `senderContext` - Optional context for sender's key derivation (default: `'mlkem-v1'`)
|
|
1514
|
+
|
|
1515
|
+
**Returns:** `EncryptedPayload` (sender's public key is automatically included as first recipient)
|
|
1516
|
+
|
|
1517
|
+
**Use case:** Encrypt data so both you and a server can decrypt independently.
|
|
1518
|
+
|
|
1519
|
+
#### `mlkemDecryptWithKey(payload: EncryptedPayload, privateKey: string | Uint8Array, context?: string): Promise<string>`
|
|
1520
|
+
|
|
1521
|
+
Convenience function that derives an ML-KEM keypair, then decrypts the payload.
|
|
1522
|
+
|
|
1523
|
+
**Parameters:**
|
|
1524
|
+
- `payload` - The encrypted payload
|
|
1525
|
+
- `privateKey` - Private key material (hex string or Uint8Array)
|
|
1526
|
+
- `context` - Optional context for key derivation (default: `'mlkem-v1'`)
|
|
1527
|
+
|
|
1528
|
+
**Returns:** Decrypted plaintext string
|
|
1529
|
+
|
|
1530
|
+
**Use case:** Decrypt data using your Ethereum private key without managing separate ML-KEM keys.
|
|
1531
|
+
|
|
1532
|
+
#### `mlkemEncrypt(plaintext: string, publicKeys: (string | Uint8Array) | Array<string | Uint8Array>): Promise<EncryptedPayload>`
|
|
1533
|
+
|
|
1534
|
+
Encrypts data using ML-KEM-1024 + AES-256-GCM for one or more recipients.
|
|
1535
|
+
|
|
1536
|
+
**Parameters:**
|
|
1537
|
+
- `plaintext` - The data to encrypt
|
|
1538
|
+
- `publicKeys` - Single public key or array of ML-KEM-1024 public keys (base64 strings or Uint8Arrays, 1568 bytes each)
|
|
1539
|
+
|
|
1540
|
+
**Returns:** `EncryptedPayload` object containing:
|
|
1541
|
+
- `recipients` - Array of recipient entries, each containing:
|
|
1542
|
+
- `publicKey` - Base64 recipient public key (1568 bytes)
|
|
1543
|
+
- `ciphertext` - Base64 ML-KEM ciphertext for this recipient (1600 bytes: 1568 KEM + 32 encrypted AES key)
|
|
1544
|
+
- `encryptedData` - Base64 AES-encrypted data (shared across all recipients)
|
|
1545
|
+
- `iv` - Base64 initialization vector (12 bytes)
|
|
1546
|
+
- `authTag` - Base64 authentication tag (16 bytes)
|
|
1547
|
+
|
|
1548
|
+
**How it works:**
|
|
1549
|
+
1. Generates a random AES-256 key
|
|
1550
|
+
2. Encrypts plaintext with AES-256-GCM using that key
|
|
1551
|
+
3. For each recipient: encapsulates a shared secret with their ML-KEM public key, then XOR-encrypts the AES key with the shared secret
|
|
1552
|
+
4. Each recipient can decrypt using their private key to recover the AES key, then decrypt the data
|
|
1553
|
+
|
|
1554
|
+
#### `mlkemDecrypt(payload: EncryptedPayload, privateKey: string | Uint8Array, publicKey?: string | Uint8Array): Promise<string>`
|
|
1555
|
+
|
|
1556
|
+
Decrypts data encrypted with `mlkemEncrypt()`.
|
|
1557
|
+
|
|
1558
|
+
**Parameters:**
|
|
1559
|
+
- `payload` - The encrypted payload from `mlkemEncrypt()`
|
|
1560
|
+
- `privateKey` - ML-KEM-1024 private key (base64 string or Uint8Array, 3168 bytes)
|
|
1561
|
+
- `publicKey` - (Optional) Your ML-KEM-1024 public key for faster recipient lookup (base64 string or Uint8Array, 1568 bytes)
|
|
1562
|
+
|
|
1563
|
+
**Returns:** Decrypted plaintext string
|
|
1564
|
+
|
|
1565
|
+
**Throws:** Error if decryption fails (invalid key, no matching recipient, corrupted data, or tampered auth tag)
|
|
1566
|
+
|
|
1567
|
+
**Note:** If `publicKey` is not provided, the function will try each recipient until it finds a match (slower but convenient)
|
|
1568
|
+
|
|
1569
|
+
---
|
|
1570
|
+
|
|
1293
1571
|
## Document Maintenance
|
|
1294
1572
|
|
|
1295
|
-
**Version:** 1.
|
|
1296
|
-
**Last Updated:** 2026-
|
|
1297
|
-
**Next Review:** 2026-
|
|
1573
|
+
**Version:** 1.2
|
|
1574
|
+
**Last Updated:** 2026-03-21
|
|
1575
|
+
**Next Review:** 2026-09-21 (6 months)
|
|
1298
1576
|
**Maintained By:** Julien Béranger ([@julienbrg](https://github.com/julienbrg))
|
|
1299
1577
|
|
|
1300
1578
|
**Changelog:**
|
|
1579
|
+
- 2026-03-21: Added ML-KEM encryption utilities with deterministic key derivation
|
|
1580
|
+
- Documented `deriveMLKemKeypair()` function for HKDF-based key derivation from private keys
|
|
1581
|
+
- Documented `mlkemEncryptWithKey()` and `mlkemDecryptWithKey()` convenience functions
|
|
1582
|
+
- Documented `mlkemEncrypt()` function for direct post-quantum encryption
|
|
1583
|
+
- Documented `mlkemDecrypt()` function for decryption
|
|
1584
|
+
- Added security properties and comprehensive API reference
|
|
1585
|
+
- Linked to NIST FIPS 203 (ML-KEM) standard
|
|
1301
1586
|
- 2026-02-27: **Updated to align with [Ethereum quantum resistance roadmap](https://x.com/VitalikButerin/status/2027075026378543132)** (February 2026)
|
|
1302
1587
|
- Added Ethereum's four quantum-vulnerable components
|
|
1303
1588
|
- Integrated [EIP-8141](https://eips.ethereum.org/EIPS/eip-8141) (native AA) as long-term solution
|