@majikah/majik-key 0.1.10 → 0.2.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.
@@ -4,6 +4,7 @@ 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
+ export declare const MAJIK_SIGNATURE_SEED = "MajikSignatureSeedDSA";
7
8
  /**
8
9
  * KDF version identifiers.
9
10
  * Stored alongside every encrypted private key blob so the correct
@@ -18,26 +19,10 @@ export type KDF_VERSION = (typeof KDF_VERSION)[keyof typeof KDF_VERSION];
18
19
  * Argon2id parameters.
19
20
  *
20
21
  * PASSPHRASE (protecting the private key at rest):
21
- * m=131072 (128 MB) — double OWASP "high security" tier (64 MB)
22
+ * m=131072 (64 MB) — double OWASP "high security" tier (64 MB)
22
23
  * t=4 — 4 passes
23
24
  * p=4 — 4 parallel lanes
24
25
  *
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
26
  */
42
27
  export declare const ARGON2_PARAMS: {
43
28
  readonly PASSPHRASE: {
@@ -1,6 +1,7 @@
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
+ export const MAJIK_SIGNATURE_SEED = "MajikSignatureSeedDSA";
4
5
  /**
5
6
  * KDF version identifiers.
6
7
  * Stored alongside every encrypted private key blob so the correct
@@ -14,30 +15,14 @@ export const KDF_VERSION = {
14
15
  * Argon2id parameters.
15
16
  *
16
17
  * PASSPHRASE (protecting the private key at rest):
17
- * m=131072 (128 MB) — double OWASP "high security" tier (64 MB)
18
+ * m=131072 (64 MB) — double OWASP "high security" tier (64 MB)
18
19
  * t=4 — 4 passes
19
20
  * p=4 — 4 parallel lanes
20
21
  *
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
22
  */
38
23
  export const ARGON2_PARAMS = {
39
24
  PASSPHRASE: {
40
- m: 65536, // memory in KB (128 MB)
25
+ m: 65536, // memory in KB (64 MB)
41
26
  t: 3, // time cost (passes)
42
27
  p: 4, // parallelism (lanes)
43
28
  dkLen: 32, // output length in bytes (256-bit AES key)
@@ -51,7 +51,8 @@ export function aesGcmEncrypt(keyBytes, iv, plaintext) {
51
51
  export function aesGcmDecrypt(keyBytes, iv, ciphertext) {
52
52
  const aes = new AES(keyBytes);
53
53
  const gcm = new GCM(aes);
54
- return gcm.open(iv, ciphertext);
54
+ const result = gcm.open(iv, ciphertext);
55
+ return result ? result.slice() : null;
55
56
  }
56
57
  // ─── WASM-first Argon2id with @noble/hashes fallback ─────────────────────────
57
58
  /**
@@ -132,7 +133,8 @@ async function _argon2id(input, salt, params) {
132
133
  */
133
134
  export async function deriveKeyFromPassphraseArgon2(passphrase, salt) {
134
135
  const pw = new TextEncoder().encode(passphrase);
135
- return _argon2id(pw, salt, ARGON2_PARAMS.PASSPHRASE);
136
+ const result = await _argon2id(pw, salt, ARGON2_PARAMS.PASSPHRASE);
137
+ return result.slice(); // ← force a copy out of WASM memory
136
138
  }
137
139
  /**
138
140
  * Derive a 32-byte AES key from a BIP-39 mnemonic using Argon2id.
@@ -144,7 +146,8 @@ export async function deriveKeyFromPassphraseArgon2(passphrase, salt) {
144
146
  */
145
147
  export async function deriveKeyFromMnemonicArgon2(mnemonic, salt) {
146
148
  const m = new TextEncoder().encode(mnemonic);
147
- return _argon2id(m, salt, ARGON2_PARAMS.MNEMONIC);
149
+ const result = await _argon2id(m, salt, ARGON2_PARAMS.MNEMONIC);
150
+ return result.slice(); // ← force a copy out of WASM memory
148
151
  }
149
152
  // ─── KDF v1: PBKDF2-SHA256 (legacy — do not use for new operations) ───────────
150
153
  /**
@@ -8,6 +8,10 @@ export interface EncryptionIdentity {
8
8
  fingerprint: string;
9
9
  mlKemPublicKey: Uint8Array;
10
10
  mlKemSecretKey?: Uint8Array;
11
+ edPublicKey: Uint8Array;
12
+ edSecretKey: Uint8Array;
13
+ mlDsaPublicKey: Uint8Array;
14
+ mlDsaSecretKey: Uint8Array;
11
15
  }
12
16
  /**
13
17
  * EncryptionEngine
@@ -2,7 +2,11 @@
2
2
  import { mnemonicToSeedSync } from "@scure/bip39";
3
3
  import * as ed25519 from "@stablelib/ed25519";
4
4
  import ed2curve from "ed2curve";
5
+ import { ml_dsa87 } from "@noble/post-quantum/ml-dsa.js";
5
6
  import { deriveMlKemKeypairFromSeed, fingerprintFromPublicRaw, } from "./crypto-provider";
7
+ import { concatUint8Arrays } from "../utils";
8
+ import { hash } from "@stablelib/sha256";
9
+ import { MAJIK_SIGNATURE_SEED } from "./constants";
6
10
  /**
7
11
  * EncryptionEngine
8
12
  * ----------------
@@ -56,12 +60,18 @@ export class EncryptionEngine {
56
60
  // seed[0..32] → lattice key matrix expansion (K-PKE keygen)
57
61
  // seed[32..64] → implicit rejection parameter z (stored in secretKey)
58
62
  const mlKemKeypair = deriveMlKemKeypairFromSeed(seed64);
63
+ const mlDsaSeed = hash(concatUint8Arrays(seed64, new TextEncoder().encode(MAJIK_SIGNATURE_SEED))); // 32 bytes, deterministic, domain-separated
64
+ const mlDsaKeypair = ml_dsa87.keygen(mlDsaSeed);
59
65
  return {
60
66
  publicKey,
61
67
  privateKey,
62
68
  fingerprint,
63
69
  mlKemPublicKey: mlKemKeypair.publicKey, // 1184 bytes
64
70
  mlKemSecretKey: mlKemKeypair.secretKey, // 2400 bytes
71
+ edPublicKey: ed.publicKey, // 32 bytes
72
+ edSecretKey: ed.secretKey, // 64 bytes
73
+ mlDsaPublicKey: mlDsaKeypair.publicKey, // 2592 bytes
74
+ mlDsaSecretKey: mlDsaKeypair.secretKey, // 4896 bytes
65
75
  };
66
76
  }
67
77
  catch (err) {
@@ -14,6 +14,10 @@ export interface MajikKeyJSON {
14
14
  kdfVersion?: number;
15
15
  mlKemPublicKey?: string;
16
16
  encryptedMlKemSecretKey?: string;
17
+ edPublicKey?: string;
18
+ encryptedEdSecretKey?: string;
19
+ mlDsaPublicKey?: string;
20
+ encryptedMlDsaSecretKey?: string;
17
21
  }
18
22
  export interface MajikKeyMetadata {
19
23
  id: string;
@@ -40,7 +40,16 @@ export interface MajikKeyIdentity {
40
40
  kdfVersion: KDF_VERSION;
41
41
  mlKemPublicKey: Uint8Array;
42
42
  mlKemSecretKey?: Uint8Array;
43
+ edPublicKey?: Uint8Array;
44
+ edSecretKey?: Uint8Array;
45
+ mlDsaPublicKey?: Uint8Array;
46
+ mlDsaSecretKey?: Uint8Array;
43
47
  }
48
+ export type MajikKeyDerivedIdentity = MajikKeyIdentity & {
49
+ encryptedMlKemSecretKey: ArrayBuffer;
50
+ encryptedEdSecretKey: ArrayBuffer;
51
+ encryptedMlDsaSecretKey: ArrayBuffer;
52
+ };
44
53
  export interface SerializedIdentity {
45
54
  id: string;
46
55
  publicKey: string;
@@ -70,6 +79,12 @@ export interface MajikKeyConstructorOptions {
70
79
  raw: Uint8Array;
71
80
  };
72
81
  privateKeyBase64?: string;
82
+ edPublicKey?: Uint8Array;
83
+ encryptedEdSecretKey?: ArrayBuffer;
84
+ encryptedEdSecretKeyBase64?: string;
85
+ mlDsaPublicKey?: Uint8Array;
86
+ encryptedMlDsaSecretKey?: ArrayBuffer;
87
+ encryptedMlDsaSecretKeyBase64?: string;
73
88
  }
74
89
  export declare class MajikKey {
75
90
  private readonly _id;
@@ -89,6 +104,14 @@ export declare class MajikKey {
89
104
  private _encryptedMlKemSecretKeyBase64?;
90
105
  private _privateKey?;
91
106
  private _privateKeyBase64?;
107
+ private _edPublicKey?;
108
+ private _edSecretKey?;
109
+ private _encryptedEdSecretKey?;
110
+ private _encryptedEdSecretKeyBase64?;
111
+ private _mlDsaPublicKey?;
112
+ private _mlDsaSecretKey?;
113
+ private _encryptedMlDsaSecretKey?;
114
+ private _encryptedMlDsaSecretKeyBase64?;
92
115
  private constructor();
93
116
  get id(): string;
94
117
  get fingerprint(): string;
@@ -108,6 +131,9 @@ export declare class MajikKey {
108
131
  get hasMlKem(): boolean;
109
132
  get isFullyUpgraded(): boolean;
110
133
  get metadata(): MajikKeyMetadata;
134
+ get edPublicKey(): Uint8Array | undefined;
135
+ get mlDsaPublicKey(): Uint8Array | undefined;
136
+ get hasSigningKeys(): boolean;
111
137
  static create(mnemonic: string, passphrase: string, label?: string): Promise<MajikKey>;
112
138
  static fromJSON(json: MajikKeyJSON | string): MajikKey;
113
139
  toMnemonicJSON(mnemonic: string, passphrase?: string): MnemonicJSON;
@@ -127,6 +153,8 @@ export declare class MajikKey {
127
153
  };
128
154
  getPrivateKeyBase64(): string;
129
155
  getMlKemSecretKey(): Uint8Array;
156
+ getEdSecretKey(): Uint8Array;
157
+ getMlDsaSecretKey(): Uint8Array;
130
158
  toJSON(): MajikKeyJSON;
131
159
  toString(pretty?: boolean): string;
132
160
  static generateMnemonic(strength?: 128 | 256): string;
@@ -160,8 +188,10 @@ export declare class MajikKey {
160
188
  * computation → two independently encrypted blobs.
161
189
  */
162
190
  private static _encryptMlKemSecretKey;
191
+ private static _encryptSigningKey;
163
192
  private static _decryptPrivateKey;
164
193
  private static _decryptMlKemSecretKey;
194
+ private static _decryptSigningKey;
165
195
  private static _verifyBackupDecryption;
166
196
  private static _exportMnemonicBackup;
167
197
  private static _deriveLegacyMnemonicKey;
package/dist/majik-key.js CHANGED
@@ -51,6 +51,14 @@ export class MajikKey {
51
51
  _encryptedMlKemSecretKeyBase64;
52
52
  _privateKey;
53
53
  _privateKeyBase64;
54
+ _edPublicKey;
55
+ _edSecretKey;
56
+ _encryptedEdSecretKey;
57
+ _encryptedEdSecretKeyBase64;
58
+ _mlDsaPublicKey;
59
+ _mlDsaSecretKey;
60
+ _encryptedMlDsaSecretKey;
61
+ _encryptedMlDsaSecretKeyBase64;
54
62
  constructor(options) {
55
63
  this._id = options.id;
56
64
  this._publicKey = options.publicKey;
@@ -69,6 +77,12 @@ export class MajikKey {
69
77
  this._encryptedMlKemSecretKeyBase64 = options.encryptedMlKemSecretKeyBase64;
70
78
  this._privateKey = options.privateKey;
71
79
  this._privateKeyBase64 = options.privateKeyBase64;
80
+ this._edPublicKey = options.edPublicKey;
81
+ this._encryptedEdSecretKey = options.encryptedEdSecretKey;
82
+ this._encryptedEdSecretKeyBase64 = options.encryptedEdSecretKeyBase64;
83
+ this._mlDsaPublicKey = options.mlDsaPublicKey;
84
+ this._encryptedMlDsaSecretKey = options.encryptedMlDsaSecretKey;
85
+ this._encryptedMlDsaSecretKeyBase64 = options.encryptedMlDsaSecretKeyBase64;
72
86
  }
73
87
  // ── Getters ─────────────────────────────────────────────────────────────────
74
88
  get id() {
@@ -127,6 +141,15 @@ export class MajikKey {
127
141
  hasMlKem: this.hasMlKem,
128
142
  };
129
143
  }
144
+ get edPublicKey() {
145
+ return this._edPublicKey;
146
+ }
147
+ get mlDsaPublicKey() {
148
+ return this._mlDsaPublicKey;
149
+ }
150
+ get hasSigningKeys() {
151
+ return (this._edPublicKey !== undefined && this._mlDsaPublicKey !== undefined);
152
+ }
130
153
  // ── CREATE ──────────────────────────────────────────────────────────────────
131
154
  static async create(mnemonic, passphrase, label) {
132
155
  try {
@@ -155,6 +178,12 @@ export class MajikKey {
155
178
  encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
156
179
  privateKey: identity.privateKey,
157
180
  privateKeyBase64,
181
+ edPublicKey: identity.edPublicKey,
182
+ encryptedEdSecretKey: identity.encryptedEdSecretKey,
183
+ encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
184
+ mlDsaPublicKey: identity.mlDsaPublicKey,
185
+ encryptedMlDsaSecretKey: identity.encryptedMlDsaSecretKey,
186
+ encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
158
187
  });
159
188
  }
160
189
  catch (err) {
@@ -178,6 +207,24 @@ export class MajikKey {
178
207
  encryptedMlKemSecretKeyBase64 = anyParsed.encryptedMlKemSecretKey;
179
208
  encryptedMlKemSecretKey = base64ToArrayBuffer(anyParsed.encryptedMlKemSecretKey);
180
209
  }
210
+ const edPublicKey = anyParsed.edPublicKey
211
+ ? base64ToUint8Array(anyParsed.edPublicKey)
212
+ : undefined;
213
+ let encryptedEdSecretKey;
214
+ let encryptedEdSecretKeyBase64;
215
+ if (anyParsed.encryptedEdSecretKey) {
216
+ encryptedEdSecretKeyBase64 = anyParsed.encryptedEdSecretKey;
217
+ encryptedEdSecretKey = base64ToArrayBuffer(anyParsed.encryptedEdSecretKey);
218
+ }
219
+ const mlDsaPublicKey = anyParsed.mlDsaPublicKey
220
+ ? base64ToUint8Array(anyParsed.mlDsaPublicKey)
221
+ : undefined;
222
+ let encryptedMlDsaSecretKey;
223
+ let encryptedMlDsaSecretKeyBase64;
224
+ if (anyParsed.encryptedMlDsaSecretKey) {
225
+ encryptedMlDsaSecretKeyBase64 = anyParsed.encryptedMlDsaSecretKey;
226
+ encryptedMlDsaSecretKey = base64ToArrayBuffer(anyParsed.encryptedMlDsaSecretKey);
227
+ }
181
228
  return new MajikKey({
182
229
  id: validated.id,
183
230
  publicKey: { raw: new Uint8Array(publicKeyBuffer) },
@@ -194,6 +241,12 @@ export class MajikKey {
194
241
  mlKemPublicKey,
195
242
  encryptedMlKemSecretKey,
196
243
  encryptedMlKemSecretKeyBase64,
244
+ edPublicKey,
245
+ encryptedEdSecretKey,
246
+ encryptedEdSecretKeyBase64,
247
+ mlDsaPublicKey,
248
+ encryptedMlDsaSecretKey,
249
+ encryptedMlDsaSecretKeyBase64,
197
250
  });
198
251
  }
199
252
  catch (err) {
@@ -260,6 +313,20 @@ export class MajikKey {
260
313
  this._encryptedMlKemSecretKeyBase64 = arrayBufferToBase64(encMlKem);
261
314
  this._mlKemSecretKey = mlKemSecretKeyBytes;
262
315
  }
316
+ if (this._encryptedEdSecretKey) {
317
+ const edSecretKeyBytes = await MajikKey._decryptSigningKey(this._encryptedEdSecretKey, currentPassphrase, salt);
318
+ const encEd = await MajikKey._encryptSigningKey(edSecretKeyBytes, newPassphrase, newSalt);
319
+ this._encryptedEdSecretKey = encEd;
320
+ this._encryptedEdSecretKeyBase64 = arrayBufferToBase64(encEd);
321
+ this._edSecretKey = edSecretKeyBytes;
322
+ }
323
+ if (this._encryptedMlDsaSecretKey) {
324
+ const mlDsaSecretKeyBytes = await MajikKey._decryptSigningKey(this._encryptedMlDsaSecretKey, currentPassphrase, salt);
325
+ const encDsa = await MajikKey._encryptSigningKey(mlDsaSecretKeyBytes, newPassphrase, newSalt);
326
+ this._encryptedMlDsaSecretKey = encDsa;
327
+ this._encryptedMlDsaSecretKeyBase64 = arrayBufferToBase64(encDsa);
328
+ this._mlDsaSecretKey = mlDsaSecretKeyBytes;
329
+ }
263
330
  return this;
264
331
  }
265
332
  catch (err) {
@@ -294,10 +361,13 @@ export class MajikKey {
294
361
  }
295
362
  }
296
363
  // ── LOCK / UNLOCK ────────────────────────────────────────────────────────────
364
+ // required
297
365
  lock() {
298
366
  this._privateKey = undefined;
299
367
  this._privateKeyBase64 = undefined;
300
368
  this._mlKemSecretKey = undefined;
369
+ this._edSecretKey = undefined; // ADD
370
+ this._mlDsaSecretKey = undefined; // ADD
301
371
  return this;
302
372
  }
303
373
  async unlock(passphrase) {
@@ -322,6 +392,12 @@ export class MajikKey {
322
392
  if (this._encryptedMlKemSecretKey) {
323
393
  this._mlKemSecretKey = await MajikKey._decryptMlKemSecretKey(this._encryptedMlKemSecretKey, passphrase, salt);
324
394
  }
395
+ if (this._encryptedEdSecretKey) {
396
+ this._edSecretKey = await MajikKey._decryptSigningKey(this._encryptedEdSecretKey, passphrase, salt);
397
+ }
398
+ if (this._encryptedMlDsaSecretKey) {
399
+ this._mlDsaSecretKey = await MajikKey._decryptSigningKey(this._encryptedMlDsaSecretKey, passphrase, salt);
400
+ }
325
401
  return this;
326
402
  }
327
403
  catch (err) {
@@ -357,6 +433,20 @@ export class MajikKey {
357
433
  throw new MajikKeyError("No ML-KEM secret key — re-import via importFromMnemonicBackup() for full migration.");
358
434
  return this._mlKemSecretKey;
359
435
  }
436
+ getEdSecretKey() {
437
+ if (this.isLocked)
438
+ throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
439
+ if (!this._edSecretKey)
440
+ throw new MajikKeyError("No Ed25519 secret key — re-import via importFromMnemonicBackup() for full migration.");
441
+ return this._edSecretKey;
442
+ }
443
+ getMlDsaSecretKey() {
444
+ if (this.isLocked)
445
+ throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
446
+ if (!this._mlDsaSecretKey)
447
+ throw new MajikKeyError("No ML-DSA secret key — re-import via importFromMnemonicBackup() for full migration.");
448
+ return this._mlDsaSecretKey;
449
+ }
360
450
  // ── SERIALIZATION ────────────────────────────────────────────────────────────
361
451
  toJSON() {
362
452
  return {
@@ -373,6 +463,14 @@ export class MajikKey {
373
463
  ? arrayToBase64(this._mlKemPublicKey)
374
464
  : undefined,
375
465
  encryptedMlKemSecretKey: this._encryptedMlKemSecretKeyBase64,
466
+ edPublicKey: this._edPublicKey
467
+ ? arrayToBase64(this._edPublicKey)
468
+ : undefined,
469
+ encryptedEdSecretKey: this._encryptedEdSecretKeyBase64,
470
+ mlDsaPublicKey: this._mlDsaPublicKey
471
+ ? arrayToBase64(this._mlDsaPublicKey)
472
+ : undefined,
473
+ encryptedMlDsaSecretKey: this._encryptedMlDsaSecretKeyBase64,
376
474
  };
377
475
  }
378
476
  toString(pretty = false) {
@@ -498,6 +596,12 @@ export class MajikKey {
498
596
  encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
499
597
  privateKey: identity.privateKey,
500
598
  privateKeyBase64,
599
+ edPublicKey: identity.edPublicKey,
600
+ encryptedEdSecretKey: identity.encryptedEdSecretKey,
601
+ encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
602
+ mlDsaPublicKey: identity.mlDsaPublicKey,
603
+ encryptedMlDsaSecretKey: identity.encryptedMlDsaSecretKey,
604
+ encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
501
605
  });
502
606
  }
503
607
  catch (err) {
@@ -527,6 +631,11 @@ export class MajikKey {
527
631
  const { blob: encryptedPrivateKey } = await MajikKey._encryptPrivateKey(exportedXPrivate, passphrase, salt);
528
632
  const mlKemSecretKey = encIdentity.mlKemSecretKey;
529
633
  const encryptedMlKemSecretKey = await MajikKey._encryptMlKemSecretKey(mlKemSecretKey, passphrase, salt);
634
+ // after the existing ML-KEM encryption block:
635
+ const edSecretKey = encIdentity.edSecretKey;
636
+ const encryptedEdSecretKey = await MajikKey._encryptSigningKey(edSecretKey, passphrase, salt);
637
+ const mlDsaSecretKey = encIdentity.mlDsaSecretKey;
638
+ const encryptedMlDsaSecretKey = await MajikKey._encryptSigningKey(mlDsaSecretKey, passphrase, salt);
530
639
  return {
531
640
  id: encIdentity.fingerprint,
532
641
  publicKey: encIdentity.publicKey,
@@ -538,6 +647,12 @@ export class MajikKey {
538
647
  mlKemPublicKey: encIdentity.mlKemPublicKey,
539
648
  mlKemSecretKey,
540
649
  encryptedMlKemSecretKey,
650
+ edPublicKey: encIdentity.edPublicKey,
651
+ edSecretKey,
652
+ encryptedEdSecretKey,
653
+ mlDsaPublicKey: encIdentity.mlDsaPublicKey,
654
+ mlDsaSecretKey,
655
+ encryptedMlDsaSecretKey,
541
656
  };
542
657
  }
543
658
  // ── PRIVATE: Encryption/Decryption ───────────────────────────────────────────
@@ -561,6 +676,12 @@ export class MajikKey {
561
676
  const ciphertext = aesGcmEncrypt(keyBytes, iv, mlKemSecretKey);
562
677
  return concatUint8Arrays(iv, ciphertext).buffer;
563
678
  }
679
+ static async _encryptSigningKey(keyBytes_, passphrase, salt) {
680
+ const aesKey = await deriveKeyFromPassphraseArgon2(passphrase, salt);
681
+ const iv = generateRandomBytes(IV_LENGTH);
682
+ const ciphertext = aesGcmEncrypt(aesKey, iv, keyBytes_);
683
+ return concatUint8Arrays(iv, ciphertext).buffer;
684
+ }
564
685
  static async _decryptPrivateKey(buffer, passphrase, salt, kdfVersion = KDF_VERSION.PBKDF2) {
565
686
  const keyBytes = kdfVersion === KDF_VERSION.ARGON2ID
566
687
  ? await deriveKeyFromPassphraseArgon2(passphrase, salt)
@@ -584,6 +705,16 @@ export class MajikKey {
584
705
  throw new MajikKeyError("Failed to decrypt ML-KEM secret key");
585
706
  return plain;
586
707
  }
708
+ static async _decryptSigningKey(buffer, passphrase, salt) {
709
+ const keyBytes = await deriveKeyFromPassphraseArgon2(passphrase, salt);
710
+ const full = new Uint8Array(buffer);
711
+ const iv = full.slice(0, IV_LENGTH);
712
+ const ciphertext = full.slice(IV_LENGTH);
713
+ const plain = aesGcmDecrypt(keyBytes, iv, ciphertext);
714
+ if (!plain)
715
+ throw new MajikKeyError("Failed to decrypt signing key");
716
+ return plain;
717
+ }
587
718
  // ── PRIVATE: Backup ──────────────────────────────────────────────────────────
588
719
  static async _verifyBackupDecryption(ivBase64, ciphertextBase64, mnemonic, backupKdfVersion) {
589
720
  const iv = new Uint8Array(base64ToArrayBuffer(ivBase64));
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@majikah/majik-key",
3
3
  "type": "module",
4
4
  "description": "A post-quantum ready seed phrase account library for the Majikah ecosystem. Manages deterministic X25519 and ML-KEM-768 identities with Argon2id protection and seamless legacy account migration.",
5
- "version": "0.1.10",
5
+ "version": "0.2.1",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",