@majikah/majik-key 0.3.2 → 0.3.4

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
@@ -1,13 +1,16 @@
1
1
  # Majik Key
2
2
 
3
+ [![ZENODO](https://img.shields.io/badge/Read_the_Technical_Whitepaper_Here-1682D4?style=for-the-badge&logo=zenodo&logoColor=white)](https://doi.org/10.5281/zenodo.21339132)
3
4
 
4
5
 
5
6
  [![Developed by Zelijah](https://img.shields.io/badge/Developed%20by-Zelijah-red?logo=github&logoColor=white)](https://www.thezelijah.world) ![GitHub Sponsors](https://img.shields.io/github/sponsors/jedlsf?style=plastic&label=Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fjedlsf)
6
7
 
7
- [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21339132.svg)](https://doi.org/10.5281/zenodo.21339132) ![npm](https://img.shields.io/npm/v/@majikah/majik-key) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-key) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
8
+
8
9
 
9
10
  **Majik Key** turns a single BIP-39 mnemonic into a complete cryptographic identity — encryption, classical + post-quantum signing, and (experimentally) Bitcoin and Solana keys — encrypted at rest and ready to plug into the rest of the Majikah ecosystem.
10
11
 
12
+ [![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.21339132.svg)](https://doi.org/10.5281/zenodo.21339132) ![npm](https://img.shields.io/npm/v/@majikah/majik-key) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-key) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
13
+
11
14
  ---
12
15
 
13
16
  ## Why Majik Key
@@ -12,6 +12,7 @@ import { ARGON2_PARAMS } from "./constants";
12
12
  import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
13
13
  import { argon2id as hashWasmArgon2id } from "hash-wasm";
14
14
  const secureGetRandomValues = crypto.getRandomValues.bind(crypto);
15
+ const secureFill = Uint8Array.prototype.fill;
15
16
  export const IV_LENGTH = 12;
16
17
  export function generateRandomBytes(len) {
17
18
  const b = new Uint8Array(len);
@@ -134,8 +135,13 @@ async function _argon2id(input, salt, params) {
134
135
  */
135
136
  export async function deriveKeyFromPassphraseArgon2(passphrase, salt) {
136
137
  const pw = new TextEncoder().encode(passphrase);
137
- const result = await _argon2id(pw, salt, ARGON2_PARAMS.PASSPHRASE);
138
- return result.slice(); // ← force a copy out of WASM memory
138
+ try {
139
+ const result = await _argon2id(pw, salt, ARGON2_PARAMS.PASSPHRASE);
140
+ return result.slice();
141
+ }
142
+ finally {
143
+ secureFill.call(pw, 0);
144
+ }
139
145
  }
140
146
  /**
141
147
  * Derive a 32-byte AES key from a BIP-39 mnemonic using Argon2id.
@@ -147,8 +153,13 @@ export async function deriveKeyFromPassphraseArgon2(passphrase, salt) {
147
153
  */
148
154
  export async function deriveKeyFromMnemonicArgon2(mnemonic, salt) {
149
155
  const m = new TextEncoder().encode(mnemonic);
150
- const result = await _argon2id(m, salt, ARGON2_PARAMS.MNEMONIC);
151
- return result.slice(); // ← force a copy out of WASM memory
156
+ try {
157
+ const result = await _argon2id(m, salt, ARGON2_PARAMS.MNEMONIC);
158
+ return result.slice(); // ← force a copy out of WASM memory
159
+ }
160
+ finally {
161
+ secureFill.call(m, 0);
162
+ }
152
163
  }
153
164
  // ─── KDF v1: PBKDF2-SHA256 (legacy — do not use for new operations) ───────────
154
165
  /**
@@ -1,8 +1,8 @@
1
1
  export interface EncryptionIdentity {
2
- publicKey: CryptoKey | {
2
+ publicKey: {
3
3
  raw: Uint8Array;
4
4
  };
5
- privateKey: CryptoKey | {
5
+ privateKey: {
6
6
  raw: Uint8Array;
7
7
  };
8
8
  fingerprint: string;
@@ -42,6 +42,13 @@ export class EncryptionEngine {
42
42
  // Step 1: BIP-39 seed → 64 bytes
43
43
  const seed = mnemonicToSeedSync(mnemonic); // returns Buffer (Node) or Uint8Array
44
44
  const seed64 = new Uint8Array(seed); // normalize to Uint8Array
45
+ // Step 3: ML-KEM-768 keypair from FULL 64-byte seed (new)
46
+ // ml_kem768.keygen() accepts a 64-byte seed directly.
47
+ // seed[0..32] → lattice key matrix expansion (K-PKE keygen)
48
+ // seed[32..64] → implicit rejection parameter z (stored in secretKey)
49
+ const mlKemKeypair = deriveMlKemKeypairFromSeed(seed64);
50
+ const mlDsaSeedInput = concatUint8Arrays(seed64, new TextEncoder().encode(MAJIK_SIGNATURE_SEED));
51
+ const mlDsaSeed = hash(mlDsaSeedInput);
45
52
  // Step 2: X25519 identity from first 32 bytes (existing path)
46
53
  const seed32 = seed64.subarray(0, 32);
47
54
  try {
@@ -56,12 +63,6 @@ export class EncryptionEngine {
56
63
  const publicKey = { type: "public", raw: pkCurveBytes };
57
64
  const privateKey = { type: "private", raw: skCurveBytes };
58
65
  const fingerprint = fingerprintFromPublicRaw(pkCurveBytes);
59
- // Step 3: ML-KEM-768 keypair from FULL 64-byte seed (new)
60
- // ml_kem768.keygen() accepts a 64-byte seed directly.
61
- // seed[0..32] → lattice key matrix expansion (K-PKE keygen)
62
- // seed[32..64] → implicit rejection parameter z (stored in secretKey)
63
- const mlKemKeypair = deriveMlKemKeypairFromSeed(seed64);
64
- const mlDsaSeed = hash(concatUint8Arrays(seed64, new TextEncoder().encode(MAJIK_SIGNATURE_SEED))); // 32 bytes, deterministic, domain-separated
65
66
  const mlDsaKeypair = ml_dsa87.keygen(mlDsaSeed);
66
67
  return {
67
68
  publicKey,
@@ -79,12 +80,12 @@ export class EncryptionEngine {
79
80
  throw new CryptoError("Failed to derive identity from mnemonic", err);
80
81
  }
81
82
  finally {
82
- // CRITICAL: Zeroize the master seed
83
83
  secureFill.call(seed64, 0);
84
84
  secureFill.call(seed32, 0);
85
- if (seed instanceof Uint8Array) {
85
+ secureFill.call(mlDsaSeedInput, 0);
86
+ secureFill.call(mlDsaSeed, 0);
87
+ if (seed instanceof Uint8Array)
86
88
  secureFill.call(seed, 0);
87
- }
88
89
  }
89
90
  }
90
91
  /* ================================
@@ -132,4 +132,5 @@ export interface MnemonicJSON {
132
132
  id: string;
133
133
  /** Optional passphrase, carried in plaintext for convenience during export/import. ⚠️ Not encrypted. */
134
134
  phrase?: string;
135
+ language?: MnemonicLanguage;
135
136
  }
@@ -26,13 +26,13 @@ export interface MajikKeyIdentity {
26
26
  /** Account identifier. Equal to `fingerprint` for accounts created by this library. */
27
27
  id: string;
28
28
  /** X25519 public key — native `CryptoKey` where WebCrypto supports it, otherwise a raw-bytes wrapper. */
29
- publicKey: CryptoKey | {
29
+ publicKey: {
30
30
  raw: Uint8Array;
31
31
  };
32
32
  /** SHA-256 fingerprint of `publicKey`. */
33
33
  fingerprint: MajikKeyFingerprint;
34
34
  /** X25519 private key, decrypted into memory. ⚠️ Live key material — do not log or serialize directly. */
35
- privateKey: CryptoKey | {
35
+ privateKey: {
36
36
  raw: Uint8Array;
37
37
  };
38
38
  /** AES-256-GCM-encrypted X25519 private key (IV + ciphertext), as stored at rest. */
@@ -106,7 +106,7 @@ export interface SerializedIdentity {
106
106
  */
107
107
  export interface MajikKeyConstructorOptions {
108
108
  id: string;
109
- publicKey: CryptoKey | {
109
+ publicKey: {
110
110
  raw: Uint8Array;
111
111
  };
112
112
  publicKeyBase64: MajikKeyAddress;
@@ -126,11 +126,9 @@ export interface MajikKeyConstructorOptions {
126
126
  encryptedMlKemSecretKey?: ArrayBuffer;
127
127
  encryptedMlKemSecretKeyBase64?: string;
128
128
  /** Present only when constructing an already-unlocked instance. ⚠️ Live key material. */
129
- privateKey?: CryptoKey | {
129
+ privateKey?: {
130
130
  raw: Uint8Array;
131
131
  };
132
- /** Present only when constructing an already-unlocked instance. ⚠️ Live key material. */
133
- privateKeyBase64?: string;
134
132
  edPublicKey?: Uint8Array;
135
133
  encryptedEdSecretKey?: ArrayBuffer;
136
134
  encryptedEdSecretKeyBase64?: string;
@@ -192,7 +190,6 @@ export declare class MajikKey {
192
190
  private _encryptedMlKemSecretKey?;
193
191
  private _encryptedMlKemSecretKeyBase64?;
194
192
  private _privateKey?;
195
- private _privateKeyBase64?;
196
193
  private _edPublicKey?;
197
194
  private _edSecretKey?;
198
195
  private _encryptedEdSecretKey?;
package/dist/majik-key.js CHANGED
@@ -8,7 +8,7 @@ import { aesGcmDecrypt, aesGcmEncrypt, deriveKeyFromPassphraseArgon2, deriveKeyF
8
8
  import { EncryptionEngine } from "./core/crypto/encryption-engine";
9
9
  import { MajikContact } from "@majikah/majik-contact";
10
10
  import { arrayBufferToBase64, arrayToBase64, base64ToArrayBuffer, concatUint8Arrays, utf8ToBase64, base64ToUtf8, seedStringToArray, seedArrayToString, base64ToUint8Array, } from "./core/utils";
11
- import { KDF_VERSION, KEY_ALGO, MAJIK_MNEMONIC_SALT, } from "./core/crypto/constants";
11
+ import { KDF_VERSION, MAJIK_MNEMONIC_SALT, } from "./core/crypto/constants";
12
12
  import { MajikKeyValidator } from "./core/validator";
13
13
  import { MajikKeyError } from "./core/error";
14
14
  import { MajikMessageIdentity } from "./core/database/system/identity";
@@ -57,7 +57,6 @@ export class MajikKey {
57
57
  _encryptedMlKemSecretKey;
58
58
  _encryptedMlKemSecretKeyBase64;
59
59
  _privateKey;
60
- _privateKeyBase64;
61
60
  _edPublicKey;
62
61
  _edSecretKey;
63
62
  _encryptedEdSecretKey;
@@ -103,7 +102,6 @@ export class MajikKey {
103
102
  this._encryptedMlKemSecretKey = options.encryptedMlKemSecretKey;
104
103
  this._encryptedMlKemSecretKeyBase64 = options.encryptedMlKemSecretKeyBase64;
105
104
  this._privateKey = options.privateKey;
106
- this._privateKeyBase64 = options.privateKeyBase64;
107
105
  this._edPublicKey = options.edPublicKey;
108
106
  this._encryptedEdSecretKey = options.encryptedEdSecretKey;
109
107
  this._encryptedEdSecretKeyBase64 = options.encryptedEdSecretKeyBase64;
@@ -285,7 +283,6 @@ export class MajikKey {
285
283
  encryptedMlKemSecretKey: identity.encryptedMlKemSecretKey,
286
284
  encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
287
285
  privateKey: identity.privateKey,
288
- privateKeyBase64,
289
286
  edPublicKey: identity.edPublicKey,
290
287
  encryptedEdSecretKey: identity.encryptedEdSecretKey,
291
288
  encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
@@ -397,11 +394,11 @@ export class MajikKey {
397
394
  if (!this._edSecretKey ||
398
395
  !this._mlDsaSecretKey ||
399
396
  !this._mlKemSecretKey ||
400
- !this._privateKeyBase64)
397
+ !this._privateKey)
401
398
  throw new MajikKeyError("MajikKey is missing secret keys — re-import via importFromMnemonicBackup() first.");
402
399
  return {
403
400
  ...this.toJSON(),
404
- privateKeyBase64: this._privateKeyBase64,
401
+ privateKeyBase64: arrayToBase64(this._privateKey.raw),
405
402
  mlKemSecretKeyBase64: arrayToBase64(this._mlKemSecretKey),
406
403
  edSecretKeyBase64: arrayToBase64(this._edSecretKey),
407
404
  mlDsaSecretKeyBase64: arrayToBase64(this._mlDsaSecretKey),
@@ -449,7 +446,6 @@ export class MajikKey {
449
446
  publicKey: { raw: base64ToUint8Array(parsed.publicKey) },
450
447
  publicKeyBase64: parsed.publicKey,
451
448
  privateKey: { raw: privateKeyBytes },
452
- privateKeyBase64: parsed.privateKeyBase64,
453
449
  encryptedPrivateKey: new ArrayBuffer(0),
454
450
  encryptedPrivateKeyBase64: parsed.encryptedPrivateKey,
455
451
  salt: parsed.salt,
@@ -482,6 +478,7 @@ export class MajikKey {
482
478
  id: this._backup,
483
479
  seed: seedStringToArray(mnemonic.trim()),
484
480
  phrase: passphrase?.trim() || undefined,
481
+ language: this._mnemonicLanguage,
485
482
  };
486
483
  }
487
484
  static async fromMnemonicJSON(mnemonicJson, passphrase, label, options = {
@@ -511,6 +508,8 @@ export class MajikKey {
511
508
  return this;
512
509
  }
513
510
  async updatePassphrase(currentPassphrase, newPassphrase) {
511
+ if (this.isLocked)
512
+ throw new MajikKeyError("MajikKey must be unlocked to update passphrase");
514
513
  MajikKeyValidator.validatePassphrase(currentPassphrase, "Current passphrase");
515
514
  MajikKeyValidator.validatePassphrase(newPassphrase, "New passphrase");
516
515
  const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
@@ -530,6 +529,8 @@ export class MajikKey {
530
529
  const encMlKem = await MajikKey._encryptMlKemSecretKey(mlKemSecretKeyBytes, newPassphrase, newSalt);
531
530
  this._encryptedMlKemSecretKey = encMlKem;
532
531
  this._encryptedMlKemSecretKeyBase64 = arrayBufferToBase64(encMlKem);
532
+ if (this._mlKemSecretKey)
533
+ secureFill.call(this._mlKemSecretKey, 0);
533
534
  this._mlKemSecretKey = mlKemSecretKeyBytes;
534
535
  }
535
536
  if (this._encryptedEdSecretKey) {
@@ -537,6 +538,8 @@ export class MajikKey {
537
538
  const encEd = await MajikKey._encryptSigningKey(edSecretKeyBytes, newPassphrase, newSalt);
538
539
  this._encryptedEdSecretKey = encEd;
539
540
  this._encryptedEdSecretKeyBase64 = arrayBufferToBase64(encEd);
541
+ if (this._edSecretKey)
542
+ secureFill.call(this._edSecretKey, 0);
540
543
  this._edSecretKey = edSecretKeyBytes;
541
544
  }
542
545
  if (this._encryptedMlDsaSecretKey) {
@@ -544,6 +547,8 @@ export class MajikKey {
544
547
  const encDsa = await MajikKey._encryptSigningKey(mlDsaSecretKeyBytes, newPassphrase, newSalt);
545
548
  this._encryptedMlDsaSecretKey = encDsa;
546
549
  this._encryptedMlDsaSecretKeyBase64 = arrayBufferToBase64(encDsa);
550
+ if (this._mlDsaSecretKey)
551
+ secureFill.call(this._mlDsaSecretKey, 0);
547
552
  this._mlDsaSecretKey = mlDsaSecretKeyBytes;
548
553
  }
549
554
  if (this._encryptedBtcSecretKey) {
@@ -551,6 +556,8 @@ export class MajikKey {
551
556
  const encBtc = await MajikKey._encryptSigningKey(btcSecretKeyBytes, newPassphrase, newSalt);
552
557
  this._encryptedBtcSecretKey = encBtc;
553
558
  this._encryptedBtcSecretKeyBase64 = arrayBufferToBase64(encBtc);
559
+ if (this._btcSecretKey)
560
+ secureFill.call(this._btcSecretKey, 0);
554
561
  this._btcSecretKey = btcSecretKeyBytes;
555
562
  }
556
563
  return this;
@@ -571,12 +578,13 @@ export class MajikKey {
571
578
  * NOTE: Does not add ML-KEM keys — use importFromMnemonicBackup() for full upgrade.
572
579
  */
573
580
  async migrate(passphrase) {
581
+ MajikKeyValidator.validatePassphrase(passphrase);
582
+ if (this._kdfVersion === KDF_VERSION.ARGON2ID)
583
+ return this;
584
+ const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
585
+ let privateKeyBuffer;
574
586
  try {
575
- MajikKeyValidator.validatePassphrase(passphrase);
576
- if (this._kdfVersion === KDF_VERSION.ARGON2ID)
577
- return this;
578
- const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
579
- const privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, KDF_VERSION.PBKDF2);
587
+ privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, KDF_VERSION.PBKDF2);
580
588
  const newSalt = generateRandomBytes(SALT_SIZE);
581
589
  const { blob } = await MajikKey._encryptPrivateKey(privateKeyBuffer, passphrase, newSalt);
582
590
  this._encryptedPrivateKey = blob;
@@ -590,6 +598,11 @@ export class MajikKey {
590
598
  throw err;
591
599
  throw new MajikKeyError("Failed to migrate MajikKey to Argon2id", err);
592
600
  }
601
+ finally {
602
+ if (privateKeyBuffer)
603
+ secureFill.call(new Uint8Array(privateKeyBuffer), 0);
604
+ secureFill.call(salt, 0);
605
+ }
593
606
  }
594
607
  // ── LOCK / UNLOCK ────────────────────────────────────────────────────────────
595
608
  // required
@@ -613,7 +626,6 @@ export class MajikKey {
613
626
  secureFill.call(this._solanaKeypairMaterial.secretKey, 0);
614
627
  }
615
628
  this._privateKey = undefined;
616
- this._privateKeyBase64 = undefined;
617
629
  this._mlKemSecretKey = undefined;
618
630
  this._edSecretKey = undefined;
619
631
  this._mlDsaSecretKey = undefined;
@@ -628,18 +640,11 @@ export class MajikKey {
628
640
  MajikKeyValidator.validatePassphrase(passphrase);
629
641
  const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
630
642
  const privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, this._kdfVersion);
631
- let privateKey;
632
- try {
633
- privateKey = await crypto.subtle.importKey("raw", privateKeyBuffer, KEY_ALGO, true, ["sign"]);
634
- }
635
- catch {
636
- privateKey = {
637
- type: "private",
638
- raw: new Uint8Array(privateKeyBuffer),
639
- };
640
- }
643
+ const privateKey = {
644
+ type: "private",
645
+ raw: new Uint8Array(privateKeyBuffer),
646
+ };
641
647
  this._privateKey = privateKey;
642
- this._privateKeyBase64 = arrayBufferToBase64(privateKeyBuffer);
643
648
  if (this._encryptedMlKemSecretKey) {
644
649
  this._mlKemSecretKey = await MajikKey._decryptMlKemSecretKey(this._encryptedMlKemSecretKey, passphrase, salt);
645
650
  }
@@ -678,7 +683,7 @@ export class MajikKey {
678
683
  getPrivateKeyBase64() {
679
684
  if (this.isLocked)
680
685
  throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
681
- return this._privateKeyBase64;
686
+ return arrayToBase64(this._privateKey.raw);
682
687
  }
683
688
  getMlKemSecretKey() {
684
689
  if (this.isLocked)
@@ -873,7 +878,6 @@ export class MajikKey {
873
878
  encryptedMlKemSecretKey: identity.encryptedMlKemSecretKey,
874
879
  encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
875
880
  privateKey: identity.privateKey,
876
- privateKeyBase64,
877
881
  edPublicKey: identity.edPublicKey,
878
882
  encryptedEdSecretKey: identity.encryptedEdSecretKey,
879
883
  encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
@@ -910,19 +914,8 @@ export class MajikKey {
910
914
  static async _deriveAndEncryptFromMnemonic(mnemonic, passphrase, options) {
911
915
  const deriveBitcoin = options?.deriveBitcoin ?? true;
912
916
  const encIdentity = await EncryptionEngine.deriveIdentityFromMnemonic(mnemonic);
913
- let exportedXPrivate;
914
- try {
915
- exportedXPrivate = await crypto.subtle.exportKey("raw", encIdentity.privateKey);
916
- }
917
- catch {
918
- const anyPriv = encIdentity.privateKey;
919
- if (anyPriv?.raw instanceof Uint8Array) {
920
- exportedXPrivate = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
921
- }
922
- else {
923
- throw new MajikKeyError("Cannot export private key: unsupported format");
924
- }
925
- }
917
+ const anyPriv = encIdentity.privateKey;
918
+ const exportedXPrivate = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
926
919
  // Single salt — one Argon2id derivation unlocks every key below
927
920
  const salt = generateRandomBytes(SALT_SIZE);
928
921
  const { blob: encryptedPrivateKey } = await MajikKey._encryptPrivateKey(exportedXPrivate, passphrase, salt);
@@ -1021,13 +1014,18 @@ export class MajikKey {
1021
1014
  }
1022
1015
  static async _decryptSigningKey(buffer, passphrase, salt) {
1023
1016
  const keyBytes = await deriveKeyFromPassphraseArgon2(passphrase, salt);
1024
- const full = new Uint8Array(buffer);
1025
- const iv = full.slice(0, IV_LENGTH);
1026
- const ciphertext = full.slice(IV_LENGTH);
1027
- const plain = aesGcmDecrypt(keyBytes, iv, ciphertext);
1028
- if (!plain)
1029
- throw new MajikKeyError("Failed to decrypt signing key");
1030
- return plain;
1017
+ try {
1018
+ const full = new Uint8Array(buffer);
1019
+ const iv = full.slice(0, IV_LENGTH);
1020
+ const ciphertext = full.slice(IV_LENGTH);
1021
+ const plain = aesGcmDecrypt(keyBytes, iv, ciphertext);
1022
+ if (!plain)
1023
+ throw new MajikKeyError("Failed to decrypt signing key");
1024
+ return plain;
1025
+ }
1026
+ finally {
1027
+ secureFill.call(keyBytes, 0);
1028
+ }
1031
1029
  }
1032
1030
  // ── PRIVATE: Backup ──────────────────────────────────────────────────────────
1033
1031
  static async _verifyBackupDecryption(ivBase64, ciphertextBase64, mnemonic, backupKdfVersion) {
@@ -1053,26 +1051,10 @@ export class MajikKey {
1053
1051
  static async _exportMnemonicBackup(identity, mnemonic) {
1054
1052
  if (!identity?.privateKey)
1055
1053
  throw new MajikKeyError("Identity must have privateKey to export backup");
1056
- let privRawBuf;
1057
- let pubRawBuf;
1058
- try {
1059
- privRawBuf = await crypto.subtle.exportKey("raw", identity.privateKey);
1060
- pubRawBuf = await crypto.subtle.exportKey("raw", identity.publicKey);
1061
- }
1062
- catch {
1063
- const anyPriv = identity.privateKey;
1064
- const anyPub = identity.publicKey;
1065
- if (anyPriv?.raw instanceof Uint8Array) {
1066
- privRawBuf = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
1067
- }
1068
- else
1069
- throw new MajikKeyError("Cannot export private key");
1070
- if (anyPub?.raw instanceof Uint8Array) {
1071
- pubRawBuf = anyPub.raw.buffer.slice(anyPub.raw.byteOffset, anyPub.raw.byteOffset + anyPub.raw.byteLength);
1072
- }
1073
- else
1074
- throw new MajikKeyError("Cannot export public key");
1075
- }
1054
+ const anyPriv = identity.privateKey;
1055
+ const anyPub = identity.publicKey;
1056
+ const privRawBuf = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
1057
+ const pubRawBuf = anyPub.raw.buffer.slice(anyPub.raw.byteOffset, anyPub.raw.byteOffset + anyPub.raw.byteLength);
1076
1058
  const mnemonicSalt = new TextEncoder().encode(MAJIK_MNEMONIC_SALT);
1077
1059
  const keyBytes = await deriveKeyFromMnemonicArgon2(mnemonic, mnemonicSalt);
1078
1060
  const iv = generateRandomBytes(IV_LENGTH);
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.3.2",
5
+ "version": "0.3.4",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -75,9 +75,9 @@
75
75
  "@scure/btc-signer": "^2.2.0",
76
76
  "@solana/kit": "^7.0.0",
77
77
  "@types/ed2curve": "^0.2.4",
78
- "@types/node": "^26.1.0",
79
- "typescript": "^6.0.3",
80
- "vitest": "^4.1.9"
78
+ "@types/node": "^26.1.1",
79
+ "typescript": "^7.0.2",
80
+ "vitest": "^4.1.10"
81
81
  },
82
82
  "peerDependencies": {
83
83
  "@scure/btc-signer": "^2.2.0",