@majikah/majik-key 0.3.0 → 0.3.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
@@ -1,7 +1,10 @@
1
1
  # Majik Key
2
2
 
3
+
4
+
3
5
  [![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)
4
- ![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)
6
+
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)
5
8
 
6
9
  **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.
7
10
 
@@ -157,10 +160,10 @@ Majik Key can derive **Bitcoin** and **Solana** key material directly from the s
157
160
  - **Solana key derivation is on-demand.** Rather than storing a separate Solana keypair, Majik Key derives it deterministically from your Ed25519 signing key each time you access `key.web3.solana` (and caches it in memory for as long as the key stays unlocked). The base58 Solana address also works with no extra install.
158
161
  - **The optional peer dependencies are only needed for chain-native address/transaction objects:**
159
162
 
160
- | Chain | Peer dependency | Needed for |
161
- | :--- | :--- | :--- |
163
+ | Chain | Peer dependency | Needed for |
164
+ | :------ | :------------------ | :--------------------------------------------------------- |
162
165
  | Bitcoin | `@scure/btc-signer` | Native SegWit (bech32) address encoding, PSBT construction |
163
- | Solana | `@solana/kit` | Real `KeyPairSigner` instances, kit-native `Address` type |
166
+ | Solana | `@solana/kit` | Real `KeyPairSigner` instances, kit-native `Address` type |
164
167
 
165
168
  ```bash
166
169
  npm install @scure/btc-signer # for Bitcoin addresses
@@ -183,7 +186,7 @@ const standardBtc = await MajikKey.deriveStandardBitcoinFromMnemonic(mnemonic);
183
186
 
184
187
  ### Two Solana paths, on purpose
185
188
 
186
- By default (`deriveSolanaKeypairFromEdSecretKey`), the Solana keypair is domain-separated from your Ed25519 message-signing key via `SHA256(edSeed || "MajikMessageSolanaSeed")`, so the same private key never secures two different protocols. You can opt into reusing your Ed25519 message-signing key directly instead:
189
+ By default (`deriveSolanaKeypairFromEdSecretKey`), the Solana keypair is domain-separated from your Ed25519 message-signing key via `SHA256(edSeed || "MajikKeySolanaSeed")`, so the same private key never secures two different protocols. You can opt into reusing your Ed25519 message-signing key directly instead:
187
190
 
188
191
  ```typescript
189
192
  // Recommended: domain-separated Solana key
@@ -234,38 +237,38 @@ localStorage.setItem('myKey', key.toString());
234
237
 
235
238
  ### Static Methods (Lifecycle & Generation)
236
239
 
237
- | Method | Parameters | Returns | Description |
238
- | :--- | :--- | :--- | :--- |
239
- | `create()` | `mnemonic`, `passphrase`, `label?`, `mnemonicLanguage?` | `Promise<MajikKey>` | Creates a new Argon2id-protected, fully post-quantum-capable account. |
240
- | `fromJSON()` | `json` | `MajikKey` | Loads a locked key from safe JSON storage. |
241
- | `fromMnemonicJSON()` | `mnemonicJson`, `passphrase`, `label?` | `Promise<MajikKey>` | Rebuilds a key straight from a portable seed export. |
242
- | `importFromMnemonicBackup()` | `backup`, `mnemonic`, `passphrase`, `label?`, `mnemonicLanguage?` | `Promise<MajikKey>` | Full migration path — verifies the mnemonic, then re-derives and re-encrypts the complete identity with Argon2id. |
243
- | `fromDangerousJSON()` | `json` | `MajikKey` | Reconstructs an already-unlocked key from a dangerous export. Server-side only — see warning below. |
244
- | `generateMnemonic()` | `strength?` *(128 \| 256)*, `language?` | `Promise<string>` | Generates a 12- or 24-word BIP-39 phrase. |
245
- | `validateMnemonic()` | `mnemonic` | `boolean` | Validates a BIP-39 mnemonic phrase. |
246
- | `deriveStandardBitcoinFromMnemonic()` *(experimental)* | `mnemonic`, `mnemonicLanguage?` | `Promise<BitcoinKeypairMaterial>` | Derives the real BIP-84 mainnet Bitcoin key without needing a `MajikKey` instance. |
240
+ | Method | Parameters | Returns | Description |
241
+ | :----------------------------------------------------- | :---------------------------------------------------------------- | :-------------------------------- | :---------------------------------------------------------------------------------------------------------------- |
242
+ | `create()` | `mnemonic`, `passphrase`, `label?`, `mnemonicLanguage?` | `Promise<MajikKey>` | Creates a new Argon2id-protected, fully post-quantum-capable account. |
243
+ | `fromJSON()` | `json` | `MajikKey` | Loads a locked key from safe JSON storage. |
244
+ | `fromMnemonicJSON()` | `mnemonicJson`, `passphrase`, `label?` | `Promise<MajikKey>` | Rebuilds a key straight from a portable seed export. |
245
+ | `importFromMnemonicBackup()` | `backup`, `mnemonic`, `passphrase`, `label?`, `mnemonicLanguage?` | `Promise<MajikKey>` | Full migration path — verifies the mnemonic, then re-derives and re-encrypts the complete identity with Argon2id. |
246
+ | `fromDangerousJSON()` | `json` | `MajikKey` | Reconstructs an already-unlocked key from a dangerous export. Server-side only — see warning below. |
247
+ | `generateMnemonic()` | `strength?` *(128 \| 256)*, `language?` | `Promise<string>` | Generates a 12- or 24-word BIP-39 phrase. |
248
+ | `validateMnemonic()` | `mnemonic` | `boolean` | Validates a BIP-39 mnemonic phrase. |
249
+ | `deriveStandardBitcoinFromMnemonic()` *(experimental)* | `mnemonic`, `mnemonicLanguage?` | `Promise<BitcoinKeypairMaterial>` | Derives the real BIP-84 mainnet Bitcoin key without needing a `MajikKey` instance. |
247
250
 
248
251
  ### Instance Methods (State & Management)
249
252
 
250
- | Method | Parameters | Returns | Description |
251
- | :--- | :--- | :--- | :--- |
252
- | `unlock()` | `passphrase` | `Promise<this>` | Decrypts keys into memory. Chainable. |
253
- | `lock()` | None | `this` | Purges all private key material (including cached Web3 keys) from memory. Chainable. |
254
- | `verify()` | `passphrase` | `Promise<boolean>` | Tests a passphrase without keeping keys in memory or requiring an unlock. |
255
- | `updatePassphrase()` | `currentPass`, `newPass` | `Promise<this>` | Re-encrypts every stored key under a new passphrase and migrates to KDF v2 if needed. |
256
- | `migrate()` | `passphrase` | `Promise<this>` | Upgrades the X25519 key's KDF from v1 to v2 only — does **not** add ML-KEM/Ed25519/ML-DSA/Bitcoin keys. Use `importFromMnemonicBackup()` for a full upgrade. |
257
- | `updateLabel()` | `newLabel` | `this` | Updates the human-readable account label. |
253
+ | Method | Parameters | Returns | Description |
254
+ | :------------------- | :----------------------- | :----------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------- |
255
+ | `unlock()` | `passphrase` | `Promise<this>` | Decrypts keys into memory. Chainable. |
256
+ | `lock()` | None | `this` | Purges all private key material (including cached Web3 keys) from memory. Chainable. |
257
+ | `verify()` | `passphrase` | `Promise<boolean>` | Tests a passphrase without keeping keys in memory or requiring an unlock. |
258
+ | `updatePassphrase()` | `currentPass`, `newPass` | `Promise<this>` | Re-encrypts every stored key under a new passphrase and migrates to KDF v2 if needed. |
259
+ | `migrate()` | `passphrase` | `Promise<this>` | Upgrades the X25519 key's KDF from v1 to v2 only — does **not** add ML-KEM/Ed25519/ML-DSA/Bitcoin keys. Use `importFromMnemonicBackup()` for a full upgrade. |
260
+ | `updateLabel()` | `newLabel` | `this` | Updates the human-readable account label. |
258
261
 
259
262
  ### Export & Integration Methods
260
263
 
261
- | Method | Returns | Description |
262
- | :--- | :--- | :--- |
263
- | `toJSON()` / `toString()` | `MajikKeyJSON` / `string` | Safe export for DB/LocalStorage. No raw keys. |
264
- | `toDangerousJSON()` | `MajikKeyDangerousJSON` | ⚠️ Contains every raw private key. Server-side secret injection only — see warning below. |
265
- | `toMnemonicJSON()` | `MnemonicJSON` | ⚠️ Contains the raw mnemonic words (and passphrase, if you pass one) in plaintext — a transport format, not an at-rest storage format. Requires the key to be unlocked. |
266
- | `exportMnemonicBackup()` | `Promise<string>` | Encrypted backup string, decryptable only with the original mnemonic — used to verify a mnemonic before `importFromMnemonicBackup()` re-derives the identity. |
267
- | `toContact()` | `MajikContact` | Extracts public identity data for sharing (the basis for Majik Universal ID). |
268
- | `toMajikMessageIdentity()` | `Promise<MajikMessageIdentity>` | Formats the key for direct use in Majik Message. Requires a `MajikUser`. |
264
+ | Method | Returns | Description |
265
+ | :------------------------- | :------------------------------ | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
266
+ | `toJSON()` / `toString()` | `MajikKeyJSON` / `string` | Safe export for DB/LocalStorage. No raw keys. |
267
+ | `toDangerousJSON()` | `MajikKeyDangerousJSON` | ⚠️ Contains every raw private key. Server-side secret injection only — see warning below. |
268
+ | `toMnemonicJSON()` | `MnemonicJSON` | ⚠️ Contains the raw mnemonic words (and passphrase, if you pass one) in plaintext — a transport format, not an at-rest storage format. Requires the key to be unlocked. |
269
+ | `exportMnemonicBackup()` | `Promise<string>` | Encrypted backup string, decryptable only with the original mnemonic — used to verify a mnemonic before `importFromMnemonicBackup()` re-derives the identity. |
270
+ | `toContact()` | `MajikContact` | Extracts public identity data for sharing (the basis for Majik Universal ID). |
271
+ | `toMajikMessageIdentity()` | `Promise<MajikMessageIdentity>` | Formats the key for direct use in Majik Message. Requires a `MajikUser`. |
269
272
 
270
273
  ### Instance Getters
271
274
 
@@ -279,14 +282,14 @@ localStorage.setItem('myKey', key.toString());
279
282
 
280
283
  ### Web3 (Experimental)
281
284
 
282
- | Member | Returns | Notes |
283
- | :--- | :--- | :--- |
284
- | `web3` *(getter)* | `{ solana, bitcoin? } \| undefined` | `undefined` if locked or has no Ed25519 signing key. `bitcoin` is present only if the account has stored Bitcoin key material. |
285
- | `getBitcoinKeypairMaterial()` | `BitcoinKeypairMaterial` | Raw Bitcoin keypair bytes. |
286
- | `getBitcoinWIF()` | `string` | Wallet Import Format string, pastes into any standard Bitcoin wallet. |
287
- | `getSolanaKeypairMaterial()` | `SolanaKeypairMaterial` | Raw Solana keypair bytes. |
288
- | `getSolanaKeypair()` | `Promise<any>` | Real `@solana/kit` `KeyPairSigner`. Requires `@solana/kit`. |
289
- | `getSolanaAddress()` | `string` | Base58 Solana address. No extra dependency required. |
285
+ | Member | Returns | Notes |
286
+ | :---------------------------- | :---------------------------------- | :----------------------------------------------------------------------------------------------------------------------------- |
287
+ | `web3` *(getter)* | `{ solana, bitcoin? } \| undefined` | `undefined` if locked or has no Ed25519 signing key. `bitcoin` is present only if the account has stored Bitcoin key material. |
288
+ | `getBitcoinKeypairMaterial()` | `BitcoinKeypairMaterial` | Raw Bitcoin keypair bytes. |
289
+ | `getBitcoinWIF()` | `string` | Wallet Import Format string, pastes into any standard Bitcoin wallet. |
290
+ | `getSolanaKeypairMaterial()` | `SolanaKeypairMaterial` | Raw Solana keypair bytes. |
291
+ | `getSolanaKeypair()` | `Promise<any>` | Real `@solana/kit` `KeyPairSigner`. Requires `@solana/kit`. |
292
+ | `getSolanaAddress()` | `string` | Base58 Solana address. No extra dependency required. |
290
293
 
291
294
  ---
292
295
 
@@ -391,9 +394,13 @@ Developed by **Josef Elijah Fabian (Zelijah)** | [Majikah Solutions OPC](https:/
391
394
 
392
395
 
393
396
  **Developer**: [Josef Elijah Fabian](https://github.com/jedlsf)
397
+
394
398
  **GitHub**: [https://github.com/Majikah](https://github.com/Majikah)
399
+
395
400
  **Project Repository**: [https://github.com/Majikah/majik-signature](https://github.com/Majikah/majik-signature)
396
401
 
402
+ **Technical Whitepaper**: [https://zenodo.org/records/21339132](https://zenodo.org/records/21339132)
403
+
397
404
  ---
398
405
 
399
406
  ## Contact
@@ -11,10 +11,11 @@ import { argon2id as nobleArgon2id } from "@noble/hashes/argon2.js";
11
11
  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
+ const secureGetRandomValues = crypto.getRandomValues.bind(crypto);
14
15
  export const IV_LENGTH = 12;
15
16
  export function generateRandomBytes(len) {
16
17
  const b = new Uint8Array(len);
17
- crypto.getRandomValues(b);
18
+ secureGetRandomValues(b);
18
19
  return b;
19
20
  }
20
21
  export function generateEd25519Keypair() {
@@ -7,6 +7,7 @@ import { deriveMlKemKeypairFromSeed, fingerprintFromPublicRaw, } from "./crypto-
7
7
  import { concatUint8Arrays } from "../utils";
8
8
  import { hash } from "@stablelib/sha256";
9
9
  import { MAJIK_SIGNATURE_SEED } from "./constants";
10
+ const secureFill = Uint8Array.prototype.fill;
10
11
  /**
11
12
  * EncryptionEngine
12
13
  * ----------------
@@ -35,15 +36,15 @@ export class EncryptionEngine {
35
36
  * seed[32..64] for the implicit rejection parameter `z`.
36
37
  */
37
38
  static async deriveIdentityFromMnemonic(mnemonic) {
39
+ if (typeof mnemonic !== "string" || mnemonic.trim().length === 0) {
40
+ throw new CryptoError("Mnemonic must be a non-empty string");
41
+ }
42
+ // Step 1: BIP-39 seed → 64 bytes
43
+ const seed = mnemonicToSeedSync(mnemonic); // returns Buffer (Node) or Uint8Array
44
+ const seed64 = new Uint8Array(seed); // normalize to Uint8Array
45
+ // Step 2: X25519 identity from first 32 bytes (existing path)
46
+ const seed32 = seed64.subarray(0, 32);
38
47
  try {
39
- if (typeof mnemonic !== "string" || mnemonic.trim().length === 0) {
40
- throw new CryptoError("Mnemonic must be a non-empty string");
41
- }
42
- // Step 1: BIP-39 seed → 64 bytes
43
- const seed = mnemonicToSeedSync(mnemonic); // returns Buffer (Node) or Uint8Array
44
- const seed64 = new Uint8Array(seed); // normalize to Uint8Array
45
- // Step 2: X25519 identity from first 32 bytes (existing path)
46
- const seed32 = seed64.subarray(0, 32);
47
48
  const ed = ed25519.generateKeyPairFromSeed(seed32);
48
49
  const skCurve = ed2curve.convertSecretKey(ed.secretKey);
49
50
  const pkCurve = ed2curve.convertPublicKey(ed.publicKey);
@@ -77,6 +78,14 @@ export class EncryptionEngine {
77
78
  catch (err) {
78
79
  throw new CryptoError("Failed to derive identity from mnemonic", err);
79
80
  }
81
+ finally {
82
+ // CRITICAL: Zeroize the master seed
83
+ secureFill.call(seed64, 0);
84
+ secureFill.call(seed32, 0);
85
+ if (seed instanceof Uint8Array) {
86
+ secureFill.call(seed, 0);
87
+ }
88
+ }
80
89
  }
81
90
  /* ================================
82
91
  * Fingerprinting
@@ -123,3 +132,5 @@ export class CryptoError extends Error {
123
132
  this.cause = cause;
124
133
  }
125
134
  }
135
+ Object.freeze(EncryptionEngine);
136
+ Object.freeze(EncryptionEngine.prototype);
@@ -73,3 +73,5 @@ export class MajikKeyValidator {
73
73
  this.assert(typeof value === "string" && value.trim().length > 0, `${field} must be a non-empty string`);
74
74
  }
75
75
  }
76
+ Object.freeze(MajikKeyValidator);
77
+ Object.freeze(MajikKeyValidator.prototype);
@@ -1,2 +1,2 @@
1
1
  export declare const MAJIK_BITCOIN_STANDARD_PATH = "m/84'/0'/0'/0/0";
2
- export declare const MAJIK_BITCOIN_DOMAIN_PATH = "m/84'/1989'/0'/0/0";
2
+ export declare const MAJIK_BITCOIN_DOMAIN_PATH = "m/84'/1971'/0'/0/0";
@@ -4,4 +4,4 @@ export const MAJIK_BITCOIN_STANDARD_PATH = "m/84'/0'/0'/0/0";
4
4
  // Unregistered/private coin-type index — domain-separates Majik's default
5
5
  // Bitcoin key from a user's "real" BTC wallet, while remaining 100% standard
6
6
  // BIP-32 math (just a different branch of the same tree).
7
- export const MAJIK_BITCOIN_DOMAIN_PATH = "m/84'/1989'/0'/0/0";
7
+ export const MAJIK_BITCOIN_DOMAIN_PATH = "m/84'/1971'/0'/0/0";
@@ -319,7 +319,11 @@ export declare class MajikKey {
319
319
  */
320
320
  static fromDangerousJSON(json: MajikKeyDangerousJSON | string): MajikKey;
321
321
  toMnemonicJSON(mnemonic: string, passphrase?: string): MnemonicJSON;
322
- static fromMnemonicJSON(mnemonicJson: MnemonicJSON | string, passphrase: string, label?: string): Promise<MajikKey>;
322
+ static fromMnemonicJSON(mnemonicJson: MnemonicJSON | string, passphrase: string, label?: string, options?: {
323
+ mnemonicLanguage?: MnemonicLanguage;
324
+ /** @experimental Set `false` to skip deriving the Bitcoin keypair. Defaults to `true` for backward compatibility. */
325
+ deriveBitcoin?: boolean;
326
+ }): Promise<MajikKey>;
323
327
  updateLabel(newLabel: string): this;
324
328
  updatePassphrase(currentPassphrase: string, newPassphrase: string): Promise<this>;
325
329
  /**
package/dist/majik-key.js CHANGED
@@ -14,6 +14,7 @@ import { MajikKeyError } from "./core/error";
14
14
  import { MajikMessageIdentity } from "./core/database/system/identity";
15
15
  import { WORDLISTS } from "./core/crypto/wordlist";
16
16
  import { deriveBitcoinKeypairFromSeed, signWithBitcoinMaterial, toBitcoinAddress, toWIF, deriveSolanaKeypairFromEdSecretKey, signWithSolanaMaterial, solanaAddressFromPublicKey, solanaMaterialFromEd25519SecretKey, toSolanaAddress, toSolanaKeyPairSigner, } from "./core/web3";
17
+ const secureFill = Uint8Array.prototype.fill;
17
18
  const SALT_SIZE = 32;
18
19
  /**
19
20
  * MajikKey
@@ -483,7 +484,10 @@ export class MajikKey {
483
484
  phrase: passphrase?.trim() || undefined,
484
485
  };
485
486
  }
486
- static async fromMnemonicJSON(mnemonicJson, passphrase, label) {
487
+ static async fromMnemonicJSON(mnemonicJson, passphrase, label, options = {
488
+ deriveBitcoin: true,
489
+ mnemonicLanguage: "en",
490
+ }) {
487
491
  try {
488
492
  const parsed = typeof mnemonicJson === "string"
489
493
  ? JSON.parse(mnemonicJson)
@@ -492,7 +496,7 @@ export class MajikKey {
492
496
  throw new MajikKeyError("Invalid MnemonicJSON");
493
497
  const mnemonic = seedArrayToString(parsed.seed);
494
498
  MajikKeyValidator.validateMnemonic(mnemonic);
495
- return await MajikKey.create(mnemonic, passphrase, label);
499
+ return await MajikKey.create(mnemonic, passphrase, label, options);
496
500
  }
497
501
  catch (err) {
498
502
  if (err instanceof MajikKeyError)
@@ -507,11 +511,11 @@ export class MajikKey {
507
511
  return this;
508
512
  }
509
513
  async updatePassphrase(currentPassphrase, newPassphrase) {
514
+ MajikKeyValidator.validatePassphrase(currentPassphrase, "Current passphrase");
515
+ MajikKeyValidator.validatePassphrase(newPassphrase, "New passphrase");
516
+ const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
517
+ const privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, currentPassphrase, salt, this._kdfVersion);
510
518
  try {
511
- MajikKeyValidator.validatePassphrase(currentPassphrase, "Current passphrase");
512
- MajikKeyValidator.validatePassphrase(newPassphrase, "New passphrase");
513
- const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
514
- const privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, currentPassphrase, salt, this._kdfVersion);
515
519
  let mlKemSecretKeyBytes;
516
520
  if (this._encryptedMlKemSecretKey) {
517
521
  mlKemSecretKeyBytes = await MajikKey._decryptMlKemSecretKey(this._encryptedMlKemSecretKey, currentPassphrase, salt);
@@ -556,6 +560,11 @@ export class MajikKey {
556
560
  throw err;
557
561
  throw new MajikKeyError("Failed to update passphrase", err);
558
562
  }
563
+ finally {
564
+ // Clear the temporary unencrypted buffer
565
+ secureFill.call(new Uint8Array(privateKeyBuffer), 0);
566
+ secureFill.call(salt, 0);
567
+ }
559
568
  }
560
569
  /**
561
570
  * Migrate KDF from PBKDF2 to Argon2id without changing passphrase.
@@ -585,6 +594,24 @@ export class MajikKey {
585
594
  // ── LOCK / UNLOCK ────────────────────────────────────────────────────────────
586
595
  // required
587
596
  lock() {
597
+ // 1. Zeroize raw bytes of all active keys
598
+ // Apply the secure fill using .call(targetArray, value)
599
+ if (this._privateKey &&
600
+ "raw" in this._privateKey &&
601
+ this._privateKey.raw instanceof Uint8Array) {
602
+ secureFill.call(this._privateKey.raw, 0);
603
+ }
604
+ if (this._mlKemSecretKey)
605
+ secureFill.call(this._mlKemSecretKey, 0);
606
+ if (this._edSecretKey)
607
+ secureFill.call(this._edSecretKey, 0);
608
+ if (this._mlDsaSecretKey)
609
+ secureFill.call(this._mlDsaSecretKey, 0);
610
+ if (this._btcSecretKey)
611
+ secureFill.call(this._btcSecretKey, 0);
612
+ if (this._solanaKeypairMaterial) {
613
+ secureFill.call(this._solanaKeypairMaterial.secretKey, 0);
614
+ }
588
615
  this._privateKey = undefined;
589
616
  this._privateKeyBase64 = undefined;
590
617
  this._mlKemSecretKey = undefined;
@@ -1207,3 +1234,7 @@ export class MajikKey {
1207
1234
  return solanaAddressFromPublicKey(this.getSolanaKeypairMaterial(options).publicKey);
1208
1235
  }
1209
1236
  }
1237
+ // Freeze static methods (e.g., MajikKey.create, MajikKey.fromJSON)
1238
+ Object.freeze(MajikKey);
1239
+ // Freeze instance methods (e.g., this.lock, this.unlock)
1240
+ Object.freeze(MajikKey.prototype);
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.0",
5
+ "version": "0.3.2",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -48,6 +48,7 @@
48
48
  "package": "npm run build && npm version patch && git push && git push --tags",
49
49
  "test": "vitest run",
50
50
  "test:watch": "vitest",
51
+ "test:compat": "npx vitest test/majik-key-cross-compat.test.ts",
51
52
  "test:web3:bitcoin": "npx vitest test/majik-key-bitcoin.test.ts",
52
53
  "test:web3:solana": "npx vitest test/majik-key-solana.test.ts",
53
54
  "test:web3": "npx vitest test/majik-key-bitcoin.test.ts test/majik-key-solana.test.ts",