@majikah/majik-key 0.1.1 → 0.1.2

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 CHANGED
@@ -2,22 +2,26 @@
2
2
 
3
3
  [![Developed by Zelijah](https://img.shields.io/badge/Developed%20by-Zelijah-red?logo=github&logoColor=white)](https://thezelijah.world) ![GitHub Sponsors](https://img.shields.io/github/sponsors/jedlsf?style=plastic&label=Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fjedlsf)
4
4
 
5
- **Majik Key** is a seed phrase account library for creating, managing, and parsing mnemonic-based cryptographic accounts (Majik Keys). Generate deterministic key pairs from BIP39 seed phrases with simple, developer-friendly APIs.
5
+ **Majik Key** is a next-generation seed phrase account library for creating and managing mnemonic-based identities. It provides a post-quantum ready, high-security bridge between BIP39 mnemonics and the Majikah ecosystem.
6
6
 
7
- ![npm](https://img.shields.io/npm/v/@majikah/majik-key) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-key) ![npm bundle size](https://img.shields.io/bundlephobia/min/%40thezelijah%2Fmajik-key) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
7
+ ![npm](https://img.shields.io/npm/v/@majikah/majik-key) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-key) ![npm bundle size](https://img.shields.io/bundlephobia/min/%40majikah%2Fmajik-key) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue)
8
8
 
9
9
 
10
10
 
11
11
  ---
12
12
  - [Majik Key](#majik-key)
13
+ - [Next-Gen Security Architecture](#next-gen-security-architecture)
14
+ - [1. Post-Quantum Ready (ML-KEM)](#1-post-quantum-ready-ml-kem)
15
+ - [2. Argon2id Key Derivation](#2-argon2id-key-derivation)
16
+ - [3. Seamless Auto-Migration](#3-seamless-auto-migration)
13
17
  - [Overview](#overview)
14
18
  - [What is a Majik Key?](#what-is-a-majik-key)
15
19
  - [Use Cases](#use-cases)
16
20
  - [Features](#features)
17
- - [Security First](#security-first)
18
- - [BIP39 Compliance](#bip39-compliance)
19
- - [Developer Friendly](#developer-friendly)
20
- - [Import/Export](#importexport)
21
+ - [Security \& Post-Quantum Readiness](#security--post-quantum-readiness)
22
+ - [BIP39 Compliance \& Key Derivation](#bip39-compliance--key-derivation)
23
+ - [Developer Experience](#developer-experience)
24
+ - [Import / Export \& Storage](#import--export--storage)
21
25
  - [Interoperability](#interoperability)
22
26
  - [Installation](#installation)
23
27
  - [Quick Start](#quick-start)
@@ -65,7 +69,6 @@
65
69
  - [Converting to Majik Message Identity](#converting-to-majik-message-identity)
66
70
  - [Security Considerations](#security-considerations)
67
71
  - [Best Practices](#best-practices)
68
- - [Security Features](#security-features)
69
72
  - [What NOT to Do](#what-not-to-do)
70
73
  - [What TO Do](#what-to-do)
71
74
  - [Tips \& Reminders](#tips--reminders)
@@ -80,8 +83,33 @@
80
83
  - [Contact](#contact)
81
84
 
82
85
 
86
+
87
+
83
88
  ---
84
89
 
90
+ ## Next-Gen Security Architecture
91
+
92
+ Majik Key has been upgraded to meet modern and future cryptographic standards.
93
+
94
+ ### 1. Post-Quantum Ready (ML-KEM)
95
+ Every identity now generates a dual-key system derived deterministically from a 64-byte BIP39 seed:
96
+ * **X25519 (Curve25519):** Derived from the first 32 bytes of the seed. Used for fingerprints, contact identity, and legacy compatibility.
97
+ * **ML-KEM-768 (FIPS-203):** Derived from the full 64-byte seed. Provides post-quantum key encapsulation for "v3" secure envelopes.
98
+
99
+ ### 2. Argon2id Key Derivation
100
+ We have transitioned to **Argon2id** (KDF v2) for encrypting private keys at rest.
101
+ * **Memory-Hard:** Configured with 128 MB of memory, 4 iterations, and 4 parallelism factors.
102
+ * **Brute-Force Resistant:** Engineered to defeat GPU and ASIC-based cracking attempts that easily bypass older PBKDF2 implementations.
103
+
104
+ ### 3. Seamless Auto-Migration
105
+ The library handles "Security Debt" automatically during standard workflows:
106
+ * **On Import:** `importFromMnemonicBackup()` detects v1 (PBKDF2) accounts and performs a full upgrade to v2.
107
+ * **Deterministic Recovery:** If ML-KEM keys are missing from an old backup, they are re-derived from the mnemonic during the upgrade process.
108
+ * **Password Updates:** Changing a passphrase via `updatePassphrase()` automatically migrates the account to the latest Argon2id standard.
109
+ ---
110
+
111
+
112
+
85
113
  ## Overview
86
114
 
87
115
  **Majik Key** is a comprehensive library for managing seed phrase-based cryptographic accounts. It provides a secure, intuitive way to create, store, and manage mnemonic-based identities with built-in encryption, backup, and recovery features.
@@ -107,33 +135,35 @@ A Majik Key is a seed phrase account that:
107
135
 
108
136
  ## Features
109
137
 
110
- ### Security First
111
- - **Encrypted at Rest**: Private keys are encrypted with PBKDF2-derived keys (200,000 iterations)
112
- - **AES-GCM Encryption**: Industry-standard authenticated encryption
113
- - **Locked/Unlocked States**: Private keys only exist in memory when explicitly unlocked
114
- - **Per-Identity Salts**: Each account uses a unique salt for encryption
115
-
116
- ### BIP39 Compliance
117
- - **Standard Mnemonic Generation**: Generate 12 or 24-word seed phrases
118
- - **Mnemonic Validation**: Built-in BIP39 validation
119
- - **Deterministic Key Derivation**: Same mnemonic always produces the same keys
120
-
121
- ### Developer Friendly
122
- - **TypeScript Support**: Full type definitions included
123
- - **Simple API**: Intuitive CRUD operations
124
- - **Error Handling**: Comprehensive error messages with `MajikKeyError`
125
- - **Method Chaining**: Fluent API for common operations
126
-
127
- ### Import/Export
128
- - **JSON Serialization**: Safe storage format (no private keys exposed)
129
- - **Mnemonic Backup**: Export/import encrypted backups using mnemonic phrases
130
- - **MnemonicJSON Format**: Compatible format for seed phrase storage
138
+ ### Security & Post-Quantum Readiness
139
+ - **Post-Quantum Ready**: Implements **ML-KEM-768 (FIPS-203)** for key encapsulation, ensuring identities are secure against future quantum computing threats.
140
+ - **Argon2id Key Derivation**: Uses memory-hard **Argon2id** (KDF v2) for passphrase encryption (128 MB / 4 iterations / 4 parallelism), providing industry-leading resistance to GPU/ASIC brute-force attacks.
141
+ - **Seamless Auto-Migration**: Automatically detects and upgrades legacy v1 (PBKDF2) accounts to v2 (Argon2id) during import, re-deriving missing ML-KEM keys deterministically from the seed.
142
+ - **AES-GCM Authenticated Encryption**: Industry-standard encryption for private keys at rest with unique, per-identity salts and random IVs.
143
+ - **Locked/Unlocked States**: Private keys are only decrypted into memory when explicitly unlocked and are purged immediately upon calling `.lock()`.
144
+
145
+ ### BIP39 Compliance & Key Derivation
146
+ - **Standard Mnemonic Generation**: Generate high-entropy 12 or 24-word seed phrases (128/256-bit strength).
147
+ - **Deterministic Multi-Key Derivation**:
148
+ - **X25519 (Curve25519)**: Derived from the first 32 bytes of the seed for legacy compatibility and fingerprints.
149
+ - **ML-KEM-768**: Derived from the full 64-byte seed for post-quantum security.
150
+ - **Built-in Validation**: Full BIP39 mnemonic validation and error handling.
151
+
152
+ ### Developer Experience
153
+ - **First-Class TypeScript Support**: Full type definitions included for all interfaces and classes.
154
+ - **Fluent API**: Intuitive method chaining for common operations (e.g., `key.unlock(p).updateLabel(l)`).
155
+ - **Comprehensive Error Handling**: Specialized `MajikKeyError` and `CryptoError` classes for precise debugging.
156
+ - **Isomorphic Support**: Works across Node.js and modern browser environments.
157
+
158
+ ### Import / Export & Storage
159
+ - **Security-Minded JSON Serialization**: Export accounts to JSON format for storage without ever exposing raw private keys or seed phrases.
160
+ - **MnemonicJSON Format**: A secure, portable format for seed phrase storage and recovery.
161
+ - **Mnemonic-Encrypted Backups**: Export and import specialized backup strings that utilize the mnemonic as a secondary encryption layer.
131
162
 
132
163
  ### Interoperability
133
- - **Majik Message Compatible**: Seamlessly import/export to Majik Message
134
- - **Majik Contact Integration**: Convert keys to contact format
135
- - **Majikah Ecosystem**: Works across all Majikah products
136
-
164
+ - **Majik Message Integration**: Native support for exporting identities compatible with **Majik Message v3** envelopes.
165
+ - **Contact Portability**: Convert Majik Keys directly into contact formats for easy sharing of public identities.
166
+ - **Ecosystem Ready**: Designed as the core identity provider for all current and future Majikah products.
137
167
  ---
138
168
 
139
169
  ## Installation
@@ -156,14 +186,12 @@ const mnemonic = MajikKey.generateMnemonic(); // 12 words
156
186
  console.log('Save this mnemonic:', mnemonic);
157
187
 
158
188
  // Create a new Majik Key (unlocked state)
159
- const key = await MajikKey.create(
160
- mnemonic,
161
- 'my-secure-passphrase',
162
- 'My First Key'
163
- );
189
+ const key = await MajikKey.create(mnemonic, 'secure-passphrase', 'My PQ Account');
164
190
 
165
- console.log('Key ID:', key.id);
191
+ // 2. Access your keys (requires unlock)
166
192
  console.log('Fingerprint:', key.fingerprint);
193
+ console.log('PQ Ready:', key.metadata.kdfVersion); // 'argon2id'
194
+ console.log('Key ID:', key.id);
167
195
  console.log('Is Unlocked:', key.isUnlocked); // true
168
196
 
169
197
  // Lock the key (clear private keys from memory)
@@ -194,7 +222,7 @@ await loadedKey.unlock('my-secure-passphrase');
194
222
  ### Static Methods
195
223
 
196
224
  #### `MajikKey.create(mnemonic, passphrase, label?)`
197
- Create a new Majik Key from a mnemonic phrase.
225
+ Create a new Majik Key from a mnemonic phrase. Generates a new Argon2id-protected account.
198
226
 
199
227
  **Parameters:**
200
228
  - `mnemonic: string` - BIP39 mnemonic phrase (12-24 words)
@@ -230,7 +258,7 @@ await key.unlock('my-password');
230
258
  ---
231
259
 
232
260
  #### `MajikKey.fromMnemonicJSON(mnemonicJson, passphrase, label?)`
233
- Create a Majik Key from MnemonicJSON format.
261
+ Create a Majik Key from MnemonicJSON format. Auto-migrates legacy PBKDF2 accounts to Argon2id + ML-KEM.
234
262
 
235
263
  **Parameters:**
236
264
  - `mnemonicJson: MnemonicJSON | string` - MnemonicJSON object or string
@@ -253,7 +281,7 @@ const key = await MajikKey.fromMnemonicJSON(mnemonicData, 'my-password');
253
281
  ---
254
282
 
255
283
  #### `MajikKey.importFromMnemonicBackup(backup, mnemonic, passphrase, label?)`
256
- Import a Majik Key from a mnemonic-encrypted backup.
284
+ Import a Majik Key from a mnemonic-encrypted backup. Auto-migrates legacy PBKDF2 accounts to Argon2id + ML-KEM.
257
285
 
258
286
  **Parameters:**
259
287
  - `backup: string` - Base64-encoded backup string
@@ -310,7 +338,7 @@ const isValid = MajikKey.validateMnemonic('witch collapse practice...');
310
338
  ### Instance Methods
311
339
 
312
340
  #### `unlock(passphrase)`
313
- Unlock the Majik Key by decrypting the private key.
341
+ Unlock the Majik Key by decrypting the private key. Decrypts X25519 and ML-KEM private keys into memory.
314
342
 
315
343
  **Parameters:**
316
344
  - `passphrase: string` - Passphrase to decrypt the private key
@@ -369,7 +397,7 @@ key.updateLabel('Work Account');
369
397
  ---
370
398
 
371
399
  #### `updatePassphrase(currentPassphrase, newPassphrase)`
372
- Change the passphrase used to encrypt the private key.
400
+ Change the passphrase used to encrypt the private key. Re-encrypts keys and triggers an auto-migration to KDF v2.
373
401
 
374
402
  **Parameters:**
375
403
  - `currentPassphrase: string` - Current passphrase
@@ -798,23 +826,20 @@ async function createMessageIdentity() {
798
826
 
799
827
  ### Best Practices
800
828
 
801
- 1. **Never expose mnemonics**: Treat mnemonic phrases like passwords. Never log, transmit, or store them unencrypted.
829
+ 1. **Never expose mnemonics**: Treat mnemonic phrases like root passwords. Never log or store them unencrypted.
802
830
 
803
- 2. **Use strong passphrases**: Choose passphrases with high entropy (mix of letters, numbers, symbols).
831
+ 2. **Lock when not in use**: Always call .lock() when private key access is no longer required to purge the heap.
804
832
 
805
- 3. **Lock when not in use**: Always lock keys when private key access is not needed.
833
+ 3. **PQ Readiness**: For all new communication protocols, ensure you are utilizing the mlKemPublicKey.
806
834
 
807
- 4. **Secure storage**: Store JSON exports in secure locations (encrypted databases, secure storage APIs).
835
+ Security Summary
836
+ - **Primary KDF**: Argon2id (128MB / 4t / 4p).
808
837
 
809
- 5. **Backup mnemonics**: Store mnemonic phrases in multiple secure locations (password manager, paper backup, hardware wallet).
838
+ - **Legacy KDF**: PBKDF2-SHA256 (250,000 iterations).
810
839
 
811
- ### Security Features
840
+ - **Encryption**: AES-256-GCM with unique salts and IVs.
812
841
 
813
- - **PBKDF2 Key Derivation**: 200,000 iterations with SHA-256
814
- - **AES-GCM Encryption**: Authenticated encryption with random IVs
815
- - **Per-Identity Salts**: Unique salt for each key prevents rainbow table attacks
816
- - **No Private Key Exposure**: Private keys never included in JSON exports
817
- - **Memory Management**: Private keys cleared from memory when locked
842
+ - **Post-Quantum**: ML-KEM-768 (Lattice-based cryptography).
818
843
 
819
844
  ### What NOT to Do
820
845
 
@@ -4,3 +4,53 @@ export declare const KEY_ALGO: {
4
4
  };
5
5
  export declare const MAJIK_SALT = "MajikMessageSalt";
6
6
  export declare const MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
7
+ /**
8
+ * KDF version identifiers.
9
+ * Stored alongside every encrypted private key blob so the correct
10
+ * derivation function is always used on decryption.
11
+ */
12
+ export declare const KDF_VERSION: {
13
+ readonly PBKDF2: 1;
14
+ readonly ARGON2ID: 2;
15
+ };
16
+ export type KDF_VERSION = (typeof KDF_VERSION)[keyof typeof KDF_VERSION];
17
+ /**
18
+ * Argon2id parameters.
19
+ *
20
+ * PASSPHRASE (protecting the private key at rest):
21
+ * m=131072 (128 MB) — double OWASP "high security" tier (64 MB)
22
+ * t=4 — 4 passes
23
+ * p=4 — 4 parallel lanes
24
+ *
25
+ * Benchmark targets (approximate):
26
+ * Modern laptop (2020+): ~600–900ms ✓
27
+ * Mid-range desktop (2018): ~800–1200ms ✓
28
+ * Low-end / older machine: ~1500–2500ms — acceptable for a one-time unlock
29
+ * RTX 4090 brute-force attack: ~1–3 guesses/sec vs ~400,000/sec for PBKDF2
30
+ * Improvement over current PBKDF2: ~100,000–400,000×
31
+ *
32
+ * MNEMONIC BACKUP (protecting exported backup files):
33
+ * m=65536 (64 MB) — lower because the mnemonic itself is 128-bit entropy;
34
+ * the KDF is a domain separator, not a weak-password defense.
35
+ * t=3
36
+ * p=2
37
+ *
38
+ * If benchmarks on your lowest-spec target device exceed 3s for the passphrase
39
+ * parameters, reduce m to 65536 (64 MB) and t to 3. That is still ~50,000×
40
+ * harder than the current PBKDF2 setup.
41
+ */
42
+ export declare const ARGON2_PARAMS: {
43
+ readonly PASSPHRASE: {
44
+ readonly m: 131072;
45
+ readonly t: 4;
46
+ readonly p: 4;
47
+ readonly dkLen: 32;
48
+ };
49
+ readonly MNEMONIC: {
50
+ readonly m: 65536;
51
+ readonly t: 3;
52
+ readonly p: 2;
53
+ readonly dkLen: 32;
54
+ };
55
+ };
56
+ export type ARGON2_PARAMS = (typeof ARGON2_PARAMS)[keyof typeof ARGON2_PARAMS];
@@ -1,3 +1,51 @@
1
1
  export const KEY_ALGO = { name: "ECDH", namedCurve: "X25519" };
2
2
  export const MAJIK_SALT = "MajikMessageSalt";
3
3
  export const MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
4
+ /**
5
+ * KDF version identifiers.
6
+ * Stored alongside every encrypted private key blob so the correct
7
+ * derivation function is always used on decryption.
8
+ */
9
+ export const KDF_VERSION = {
10
+ PBKDF2: 1, // legacy — read-only support for existing accounts
11
+ ARGON2ID: 2, // current — all new accounts and re-encryptions
12
+ };
13
+ /**
14
+ * Argon2id parameters.
15
+ *
16
+ * PASSPHRASE (protecting the private key at rest):
17
+ * m=131072 (128 MB) — double OWASP "high security" tier (64 MB)
18
+ * t=4 — 4 passes
19
+ * p=4 — 4 parallel lanes
20
+ *
21
+ * Benchmark targets (approximate):
22
+ * Modern laptop (2020+): ~600–900ms ✓
23
+ * Mid-range desktop (2018): ~800–1200ms ✓
24
+ * Low-end / older machine: ~1500–2500ms — acceptable for a one-time unlock
25
+ * RTX 4090 brute-force attack: ~1–3 guesses/sec vs ~400,000/sec for PBKDF2
26
+ * Improvement over current PBKDF2: ~100,000–400,000×
27
+ *
28
+ * MNEMONIC BACKUP (protecting exported backup files):
29
+ * m=65536 (64 MB) — lower because the mnemonic itself is 128-bit entropy;
30
+ * the KDF is a domain separator, not a weak-password defense.
31
+ * t=3
32
+ * p=2
33
+ *
34
+ * If benchmarks on your lowest-spec target device exceed 3s for the passphrase
35
+ * parameters, reduce m to 65536 (64 MB) and t to 3. That is still ~50,000×
36
+ * harder than the current PBKDF2 setup.
37
+ */
38
+ export const ARGON2_PARAMS = {
39
+ PASSPHRASE: {
40
+ m: 131072, // memory in KB (128 MB)
41
+ t: 4, // time cost (passes)
42
+ p: 4, // parallelism (lanes)
43
+ dkLen: 32, // output length in bytes (256-bit AES key)
44
+ },
45
+ MNEMONIC: {
46
+ m: 65536, // 64 MB
47
+ t: 3,
48
+ p: 2,
49
+ dkLen: 32,
50
+ },
51
+ };
@@ -15,7 +15,104 @@ export declare function deriveEd25519FromSeed(seed32: Uint8Array): {
15
15
  export declare function fingerprintFromPublicRaw(rawPublic: Uint8Array): string;
16
16
  export declare function aesGcmEncrypt(keyBytes: Uint8Array, iv: Uint8Array, plaintext: Uint8Array): Uint8Array;
17
17
  export declare function aesGcmDecrypt(keyBytes: Uint8Array, iv: Uint8Array, ciphertext: Uint8Array): Uint8Array | null;
18
+ /**
19
+ * Derive a 32-byte AES key from a user passphrase using Argon2id.
20
+ *
21
+ * Use this for all NEW account creation and any re-encryption operations.
22
+ * Works in browser, Node.js, Electron, and Chrome Extension environments.
23
+ *
24
+ * @param passphrase - The user's passphrase (plaintext string)
25
+ * @param salt - Per-identity random salt (32 bytes recommended)
26
+ * @returns - 32-byte key suitable for AES-256-GCM
27
+ */
28
+ export declare function deriveKeyFromPassphraseArgon2(passphrase: string, salt: Uint8Array): Uint8Array;
29
+ /**
30
+ * Derive a 32-byte AES key from a BIP-39 mnemonic using Argon2id.
31
+ *
32
+ * Used for encrypting and decrypting mnemonic backup exports.
33
+ * Lower memory parameters than the passphrase KDF because the mnemonic
34
+ * itself provides 128-bit entropy — brute-force is infeasible regardless.
35
+ *
36
+ * @param mnemonic - The 12-word BIP-39 mnemonic (plaintext string)
37
+ * @param salt - Domain-separator salt (can be a fixed constant)
38
+ * @returns - 32-byte key suitable for AES-256-GCM
39
+ */
40
+ export declare function deriveKeyFromMnemonicArgon2(mnemonic: string, salt: Uint8Array): Uint8Array;
41
+ /**
42
+ * @deprecated KDF v1. Kept for reading existing accounts created before the
43
+ * Argon2id migration. Do NOT use this for new key derivation or re-encryption.
44
+ * When an existing account's passphrase is changed, it will automatically
45
+ * be re-encrypted with Argon2id (kdfVersion: 2).
46
+ */
18
47
  export declare function deriveKeyFromPassphrase(passphrase: string, salt: Uint8Array, iterations?: number, keyLen?: number): Uint8Array;
48
+ /**
49
+ * @deprecated KDF v1. Kept for importing mnemonic backups created before the
50
+ * Argon2id migration. Do NOT use this for new backup exports.
51
+ */
19
52
  export declare function deriveKeyFromMnemonic(mnemonic: string, salt: Uint8Array, iterations?: number, keyLen?: number): Uint8Array;
20
53
  export declare function x25519SharedSecret(privRaw: Uint8Array, pubRaw: Uint8Array): Uint8Array;
21
54
  export declare function sha256(input: string): string;
55
+ /**
56
+ * Derive a deterministic ML-KEM-768 keypair from a BIP-39 mnemonic seed.
57
+ *
58
+ * How the seed mapping works (from the noble source):
59
+ * ml_kem768.keygen(seed) where seed = 64 bytes
60
+ * └── seed.subarray(0, 32) → KPKE key generation (lattice matrix expansion)
61
+ * └── seed.subarray(32) → stored as `z` in secret key (implicit rejection)
62
+ *
63
+ * BIP-39 seed mapping:
64
+ * mnemonicToSeedSync(mnemonic) → 64 bytes (PBKDF2-SHA512 of mnemonic)
65
+ * ├── seed[0..32] → Ed25519 keypair → X25519 via ed2curve (existing)
66
+ * └── seed[0..64] → ML-KEM-768 keypair (new, uses full 64 bytes)
67
+ *
68
+ * IMPORTANT: ML-KEM gets the FULL 64-byte BIP-39 seed, not just the first 32.
69
+ * This gives ML-KEM its own 64 bits of additional entropy (seed[32..64]) for
70
+ * the implicit rejection parameter `z`, completely independent of the X25519 key.
71
+ *
72
+ * Both keypairs are deterministically derived from the same mnemonic — so
73
+ * recovering the mnemonic recovers both X25519 and ML-KEM keys automatically.
74
+ *
75
+ * @param bip39Seed - Full 64-byte BIP-39 seed from mnemonicToSeedSync()
76
+ * @returns ML-KEM-768 keypair
77
+ */
78
+ export declare function deriveMlKemKeypairFromSeed(bip39Seed: Uint8Array): {
79
+ publicKey: Uint8Array;
80
+ secretKey: Uint8Array;
81
+ };
82
+ /**
83
+ * Generate a random ML-KEM-768 keypair.
84
+ * Use this for testing only — production identities should use
85
+ * deriveMlKemKeypairFromSeed() for deterministic derivation from mnemonic.
86
+ */
87
+ export declare function generateMlKemKeypair(): {
88
+ publicKey: Uint8Array;
89
+ secretKey: Uint8Array;
90
+ };
91
+ /**
92
+ * ML-KEM encapsulation: generate a shared secret and ciphertext.
93
+ *
94
+ * The post-quantum replacement for X25519 ephemeral key exchange.
95
+ * The sender calls this with the recipient's ML-KEM public key.
96
+ * Only the holder of the corresponding ML-KEM secret key can decapsulate.
97
+ *
98
+ * @param recipientPublicKey - ML-KEM-768 public key (1184 bytes)
99
+ * @returns { sharedSecret: 32 bytes, cipherText: 1088 bytes }
100
+ *
101
+ * Note: The noble library uses `cipherText` (camelCase T) — not `ciphertext`.
102
+ */
103
+ export declare function mlKemEncapsulate(recipientPublicKey: Uint8Array): {
104
+ sharedSecret: Uint8Array;
105
+ cipherText: Uint8Array;
106
+ };
107
+ /**
108
+ * ML-KEM decapsulation: recover the shared secret from ciphertext.
109
+ *
110
+ * IMPORTANT: ML-KEM decapsulation NEVER throws on wrong key — it returns
111
+ * a different (useless) shared secret instead. AES-GCM authentication will
112
+ * catch this: decryption will fail with an auth tag mismatch.
113
+ *
114
+ * @param cipherText - ML-KEM-768 ciphertext (1088 bytes)
115
+ * @param recipientSecretKey - ML-KEM-768 secret key (2400 bytes)
116
+ * @returns sharedSecret (32 bytes)
117
+ */
118
+ export declare function mlKemDecapsulate(cipherText: Uint8Array, recipientSecretKey: Uint8Array): Uint8Array;
@@ -6,6 +6,9 @@ import { deriveKey } from "@stablelib/pbkdf2";
6
6
  import { hash, SHA256 } from "@stablelib/sha256";
7
7
  import * as x25519 from "@stablelib/x25519";
8
8
  import { arrayToBase64 } from "../utils";
9
+ import { argon2id } from "@noble/hashes/argon2.js";
10
+ import { ARGON2_PARAMS } from "./constants";
11
+ import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
9
12
  export const IV_LENGTH = 12;
10
13
  export function generateRandomBytes(len) {
11
14
  const b = new Uint8Array(len);
@@ -48,10 +51,51 @@ export function aesGcmDecrypt(keyBytes, iv, ciphertext) {
48
51
  const gcm = new GCM(aes);
49
52
  return gcm.open(iv, ciphertext);
50
53
  }
54
+ // ─── KDF v2: Argon2id (current) ───────────────────────────────────────────────
55
+ /**
56
+ * Derive a 32-byte AES key from a user passphrase using Argon2id.
57
+ *
58
+ * Use this for all NEW account creation and any re-encryption operations.
59
+ * Works in browser, Node.js, Electron, and Chrome Extension environments.
60
+ *
61
+ * @param passphrase - The user's passphrase (plaintext string)
62
+ * @param salt - Per-identity random salt (32 bytes recommended)
63
+ * @returns - 32-byte key suitable for AES-256-GCM
64
+ */
65
+ export function deriveKeyFromPassphraseArgon2(passphrase, salt) {
66
+ const pw = new TextEncoder().encode(passphrase);
67
+ return argon2id(pw, salt, ARGON2_PARAMS.PASSPHRASE);
68
+ }
69
+ /**
70
+ * Derive a 32-byte AES key from a BIP-39 mnemonic using Argon2id.
71
+ *
72
+ * Used for encrypting and decrypting mnemonic backup exports.
73
+ * Lower memory parameters than the passphrase KDF because the mnemonic
74
+ * itself provides 128-bit entropy — brute-force is infeasible regardless.
75
+ *
76
+ * @param mnemonic - The 12-word BIP-39 mnemonic (plaintext string)
77
+ * @param salt - Domain-separator salt (can be a fixed constant)
78
+ * @returns - 32-byte key suitable for AES-256-GCM
79
+ */
80
+ export function deriveKeyFromMnemonicArgon2(mnemonic, salt) {
81
+ const m = new TextEncoder().encode(mnemonic);
82
+ return argon2id(m, salt, ARGON2_PARAMS.MNEMONIC);
83
+ }
84
+ // ─── KDF v1: PBKDF2-SHA256 (legacy — do not use for new operations) ───────────
85
+ /**
86
+ * @deprecated KDF v1. Kept for reading existing accounts created before the
87
+ * Argon2id migration. Do NOT use this for new key derivation or re-encryption.
88
+ * When an existing account's passphrase is changed, it will automatically
89
+ * be re-encrypted with Argon2id (kdfVersion: 2).
90
+ */
51
91
  export function deriveKeyFromPassphrase(passphrase, salt, iterations = 250000, keyLen = 32) {
52
92
  const pw = new TextEncoder().encode(passphrase);
53
93
  return deriveKey(SHA256, pw, salt, iterations, keyLen);
54
94
  }
95
+ /**
96
+ * @deprecated KDF v1. Kept for importing mnemonic backups created before the
97
+ * Argon2id migration. Do NOT use this for new backup exports.
98
+ */
55
99
  export function deriveKeyFromMnemonic(mnemonic, salt, iterations = 200000, keyLen = 32) {
56
100
  const m = new TextEncoder().encode(mnemonic);
57
101
  return deriveKey(SHA256, m, salt, iterations, keyLen);
@@ -72,3 +116,71 @@ export function sha256(input) {
72
116
  const hashed = hash(new TextEncoder().encode(input));
73
117
  return arrayToBase64(hashed);
74
118
  }
119
+ // ─── ML-KEM-768: Post-Quantum Key Encapsulation ───────────────────────────────
120
+ /**
121
+ * Derive a deterministic ML-KEM-768 keypair from a BIP-39 mnemonic seed.
122
+ *
123
+ * How the seed mapping works (from the noble source):
124
+ * ml_kem768.keygen(seed) where seed = 64 bytes
125
+ * └── seed.subarray(0, 32) → KPKE key generation (lattice matrix expansion)
126
+ * └── seed.subarray(32) → stored as `z` in secret key (implicit rejection)
127
+ *
128
+ * BIP-39 seed mapping:
129
+ * mnemonicToSeedSync(mnemonic) → 64 bytes (PBKDF2-SHA512 of mnemonic)
130
+ * ├── seed[0..32] → Ed25519 keypair → X25519 via ed2curve (existing)
131
+ * └── seed[0..64] → ML-KEM-768 keypair (new, uses full 64 bytes)
132
+ *
133
+ * IMPORTANT: ML-KEM gets the FULL 64-byte BIP-39 seed, not just the first 32.
134
+ * This gives ML-KEM its own 64 bits of additional entropy (seed[32..64]) for
135
+ * the implicit rejection parameter `z`, completely independent of the X25519 key.
136
+ *
137
+ * Both keypairs are deterministically derived from the same mnemonic — so
138
+ * recovering the mnemonic recovers both X25519 and ML-KEM keys automatically.
139
+ *
140
+ * @param bip39Seed - Full 64-byte BIP-39 seed from mnemonicToSeedSync()
141
+ * @returns ML-KEM-768 keypair
142
+ */
143
+ export function deriveMlKemKeypairFromSeed(bip39Seed) {
144
+ if (bip39Seed.length !== 64) {
145
+ throw new Error(`ML-KEM seed must be 64 bytes (got ${bip39Seed.length}). ` +
146
+ `Pass the full output of mnemonicToSeedSync(), not a truncated slice.`);
147
+ }
148
+ return ml_kem768.keygen(bip39Seed);
149
+ }
150
+ /**
151
+ * Generate a random ML-KEM-768 keypair.
152
+ * Use this for testing only — production identities should use
153
+ * deriveMlKemKeypairFromSeed() for deterministic derivation from mnemonic.
154
+ */
155
+ export function generateMlKemKeypair() {
156
+ return ml_kem768.keygen(); // uses crypto.getRandomValues() internally
157
+ }
158
+ /**
159
+ * ML-KEM encapsulation: generate a shared secret and ciphertext.
160
+ *
161
+ * The post-quantum replacement for X25519 ephemeral key exchange.
162
+ * The sender calls this with the recipient's ML-KEM public key.
163
+ * Only the holder of the corresponding ML-KEM secret key can decapsulate.
164
+ *
165
+ * @param recipientPublicKey - ML-KEM-768 public key (1184 bytes)
166
+ * @returns { sharedSecret: 32 bytes, cipherText: 1088 bytes }
167
+ *
168
+ * Note: The noble library uses `cipherText` (camelCase T) — not `ciphertext`.
169
+ */
170
+ export function mlKemEncapsulate(recipientPublicKey) {
171
+ return ml_kem768.encapsulate(recipientPublicKey);
172
+ }
173
+ /**
174
+ * ML-KEM decapsulation: recover the shared secret from ciphertext.
175
+ *
176
+ * IMPORTANT: ML-KEM decapsulation NEVER throws on wrong key — it returns
177
+ * a different (useless) shared secret instead. AES-GCM authentication will
178
+ * catch this: decryption will fail with an auth tag mismatch.
179
+ *
180
+ * @param cipherText - ML-KEM-768 ciphertext (1088 bytes)
181
+ * @param recipientSecretKey - ML-KEM-768 secret key (2400 bytes)
182
+ * @returns sharedSecret (32 bytes)
183
+ */
184
+ export function mlKemDecapsulate(cipherText, recipientSecretKey) {
185
+ return ml_kem768.decapsulate(cipherText, recipientSecretKey);
186
+ }
@@ -6,6 +6,8 @@ export interface EncryptionIdentity {
6
6
  raw: Uint8Array;
7
7
  };
8
8
  fingerprint: string;
9
+ mlKemPublicKey?: Uint8Array;
10
+ mlKemSecretKey?: Uint8Array;
9
11
  }
10
12
  /**
11
13
  * EncryptionEngine
@@ -14,12 +16,28 @@ export interface EncryptionIdentity {
14
16
  */
15
17
  export declare class EncryptionEngine {
16
18
  /**
17
- * Generates a long-term X25519 identity keypair.
19
+ * Generates a random long-term identity keypair (X25519 only).
20
+ * ML-KEM keys are not generated here since random identities
21
+ * cannot be deterministically recovered from a mnemonic.
18
22
  */
19
23
  static generateIdentity(): Promise<EncryptionIdentity>;
20
24
  /**
21
- * Derive an identity deterministically from a BIP39 mnemonic.
22
- * Uses Stablelib Ed25519 to derive a keypair from seed and converts to X25519.
25
+ * Derive a complete identity from a BIP-39 mnemonic.
26
+ *
27
+ * Seed derivation:
28
+ * mnemonicToSeedSync(mnemonic) → 64-byte BIP-39 seed
29
+ *
30
+ * X25519 derivation (unchanged from before):
31
+ * seed[0..32] → Ed25519 keypair via generateKeyPairFromSeed
32
+ * → X25519 via ed2curve conversion
33
+ *
34
+ * ML-KEM-768 derivation (new):
35
+ * seed[0..64] → ml_kem768.keygen(seed)
36
+ * → { publicKey: 1184 bytes, secretKey: 2400 bytes }
37
+ *
38
+ * The noble library accepts the full 64-byte BIP-39 seed directly.
39
+ * Internally it uses seed[0..32] for the lattice key matrix and
40
+ * seed[32..64] for the implicit rejection parameter `z`.
23
41
  */
24
42
  static deriveIdentityFromMnemonic(mnemonic: string): Promise<EncryptionIdentity>;
25
43
  /**