@majikah/majik-key 0.2.9 → 0.2.11

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
@@ -893,7 +893,7 @@ Secure messaging platform using Majik Keys
893
893
 
894
894
  [Read more about Majik Message here](https://majikah.solutions/products/majik-message)
895
895
 
896
- [![Majik Message Thumbnail](https://github.com/user-attachments/assets/d433c6b8-1841-4fa1-a6da-b348029d1dbe)](https://message.majikah.solutions)
896
+ [![Majik Message Thumbnail](https://github.com/user-attachments/assets/6355cbd3-63e4-4a95-a370-64ba27cbb4a7)](https://message.majikah.solutions)
897
897
 
898
898
  > Click the image to try Majik Message live.
899
899
 
@@ -21,6 +21,12 @@ export interface MajikKeyJSON {
21
21
  encryptedMlDsaSecretKey?: string;
22
22
  mnemonicLanguage?: MnemonicLanguage;
23
23
  }
24
+ export interface MajikKeyDangerousJSON extends MajikKeyJSON {
25
+ privateKeyBase64: string;
26
+ mlKemSecretKeyBase64: string;
27
+ edSecretKeyBase64: string;
28
+ mlDsaSecretKeyBase64: string;
29
+ }
24
30
  export interface MajikKeyMetadata {
25
31
  id: string;
26
32
  fingerprint: string;
@@ -21,9 +21,9 @@
21
21
  * point, so ML-KEM keys can be deterministically re-derived and stored.
22
22
  * No partial migration — either fully upgraded or not upgraded yet.
23
23
  */
24
- import { MajikContact } from "@majikah/majik-contact";
24
+ import { MajikContact, MajikContactMeta } from "@majikah/majik-contact";
25
25
  import { KDF_VERSION } from "./core/crypto/constants";
26
- import type { MajikKeyJSON, MajikKeyMetadata, MnemonicJSON } from "./core/types";
26
+ import type { MajikKeyDangerousJSON, MajikKeyJSON, MajikKeyMetadata, MnemonicJSON } from "./core/types";
27
27
  import { MajikMessageIdentity } from "./core/database/system/identity";
28
28
  import { MajikUser } from "@thezelijah/majik-user";
29
29
  import { MnemonicLanguage } from "./core/crypto/wordlist";
@@ -142,6 +142,20 @@ export declare class MajikKey {
142
142
  get hasSigningKeys(): boolean;
143
143
  static create(mnemonic: string, passphrase: string, label?: string, mnemonicLanguage?: MnemonicLanguage): Promise<MajikKey>;
144
144
  static fromJSON(json: MajikKeyJSON | string): MajikKey;
145
+ /**
146
+ * Export a fully unlocked MajikKey with all raw private keys.
147
+ * ⚠️ DANGEROUS — output contains unencrypted private key material.
148
+ * Only use for server-side secrets injection.
149
+ * Never log, store in a database, or transmit over the network.
150
+ */
151
+ toDangerousJSON(): MajikKeyDangerousJSON;
152
+ /**
153
+ * Reconstruct a fully unlocked MajikKey from a dangerous JSON export.
154
+ * ⚠️ DANGEROUS — input contains unencrypted private key material.
155
+ * Intended for server-side use only (e.g. TSA signing key loaded from Cloudflare Secrets).
156
+ * No KDF is involved — reconstruction is instant.
157
+ */
158
+ static fromDangerousJSON(json: MajikKeyDangerousJSON | string): MajikKey;
145
159
  toMnemonicJSON(mnemonic: string, passphrase?: string): MnemonicJSON;
146
160
  static fromMnemonicJSON(mnemonicJson: MnemonicJSON | string, passphrase: string, label?: string): Promise<MajikKey>;
147
161
  updateLabel(newLabel: string): this;
@@ -165,7 +179,11 @@ export declare class MajikKey {
165
179
  toString(pretty?: boolean): string;
166
180
  static generateMnemonic(strength?: 128 | 256, language?: MnemonicLanguage): Promise<string>;
167
181
  static validateMnemonic(mnemonic: string): boolean;
168
- toContact(): MajikContact;
182
+ /**
183
+ * Converts the MajikKey to a MajikContact.
184
+ * You can pass a custom metadata type if needed, e.g., toContact<MyMeta>()
185
+ */
186
+ toContact<TMeta extends MajikContactMeta = MajikContactMeta>(initialMeta?: Partial<TMeta>): MajikContact<TMeta>;
169
187
  toKeyIdentity(): MajikKeyIdentity;
170
188
  toSerializedIdentity(): SerializedIdentity;
171
189
  toMajikMessageIdentity(user: MajikUser, options?: {
package/dist/majik-key.js CHANGED
@@ -198,7 +198,7 @@ export class MajikKey {
198
198
  encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
199
199
  edSecretKey: identity.edSecretKey,
200
200
  mlDsaSecretKey: identity.mlDsaSecretKey,
201
- mnemonicLanguage: mnemonicLanguage
201
+ mnemonicLanguage: mnemonicLanguage,
202
202
  });
203
203
  }
204
204
  catch (err) {
@@ -271,6 +271,81 @@ export class MajikKey {
271
271
  throw new MajikKeyError("Failed to parse MajikKey from JSON", err);
272
272
  }
273
273
  }
274
+ /**
275
+ * Export a fully unlocked MajikKey with all raw private keys.
276
+ * ⚠️ DANGEROUS — output contains unencrypted private key material.
277
+ * Only use for server-side secrets injection.
278
+ * Never log, store in a database, or transmit over the network.
279
+ */
280
+ toDangerousJSON() {
281
+ if (this.isLocked)
282
+ throw new MajikKeyError("MajikKey must be unlocked to export dangerous JSON.");
283
+ if (!this._edSecretKey ||
284
+ !this._mlDsaSecretKey ||
285
+ !this._mlKemSecretKey ||
286
+ !this._privateKeyBase64)
287
+ throw new MajikKeyError("MajikKey is missing secret keys — re-import via importFromMnemonicBackup() first.");
288
+ return {
289
+ ...this.toJSON(),
290
+ privateKeyBase64: this._privateKeyBase64,
291
+ mlKemSecretKeyBase64: arrayToBase64(this._mlKemSecretKey),
292
+ edSecretKeyBase64: arrayToBase64(this._edSecretKey),
293
+ mlDsaSecretKeyBase64: arrayToBase64(this._mlDsaSecretKey),
294
+ };
295
+ }
296
+ /**
297
+ * Reconstruct a fully unlocked MajikKey from a dangerous JSON export.
298
+ * ⚠️ DANGEROUS — input contains unencrypted private key material.
299
+ * Intended for server-side use only (e.g. TSA signing key loaded from Cloudflare Secrets).
300
+ * No KDF is involved — reconstruction is instant.
301
+ */
302
+ static fromDangerousJSON(json) {
303
+ try {
304
+ const parsed = typeof json === "string" ? JSON.parse(json) : json;
305
+ if (!parsed.id ||
306
+ !parsed.fingerprint ||
307
+ !parsed.publicKey ||
308
+ !parsed.privateKeyBase64 ||
309
+ !parsed.edPublicKey ||
310
+ !parsed.edSecretKeyBase64 ||
311
+ !parsed.mlDsaPublicKey ||
312
+ !parsed.mlDsaSecretKeyBase64 ||
313
+ !parsed.mlKemPublicKey ||
314
+ !parsed.mlKemSecretKeyBase64)
315
+ throw new MajikKeyError("Invalid MajikKeyDangerousJSON — missing required fields");
316
+ const privateKeyBytes = base64ToUint8Array(parsed.privateKeyBase64);
317
+ const edPublicKey = base64ToUint8Array(parsed.edPublicKey);
318
+ const edSecretKey = base64ToUint8Array(parsed.edSecretKeyBase64);
319
+ const mlDsaPublicKey = base64ToUint8Array(parsed.mlDsaPublicKey);
320
+ const mlDsaSecretKey = base64ToUint8Array(parsed.mlDsaSecretKeyBase64);
321
+ const mlKemPublicKey = base64ToUint8Array(parsed.mlKemPublicKey);
322
+ const mlKemSecretKey = base64ToUint8Array(parsed.mlKemSecretKeyBase64);
323
+ return new MajikKey({
324
+ id: parsed.id,
325
+ fingerprint: parsed.fingerprint,
326
+ publicKey: { raw: base64ToUint8Array(parsed.publicKey) },
327
+ publicKeyBase64: parsed.publicKey,
328
+ privateKey: { raw: privateKeyBytes },
329
+ privateKeyBase64: parsed.privateKeyBase64,
330
+ encryptedPrivateKey: new ArrayBuffer(0),
331
+ encryptedPrivateKeyBase64: parsed.encryptedPrivateKey,
332
+ salt: parsed.salt,
333
+ backup: parsed.backup,
334
+ kdfVersion: parsed?.kdfVersion || KDF_VERSION.ARGON2ID,
335
+ mlKemPublicKey,
336
+ mlKemSecretKey,
337
+ edPublicKey,
338
+ edSecretKey,
339
+ mlDsaPublicKey,
340
+ mlDsaSecretKey,
341
+ });
342
+ }
343
+ catch (err) {
344
+ if (err instanceof MajikKeyError)
345
+ throw err;
346
+ throw new MajikKeyError("Failed to reconstruct MajikKey from dangerous JSON", err);
347
+ }
348
+ }
274
349
  // ── MnemonicJSON ─────────────────────────────────────────────────────────────
275
350
  toMnemonicJSON(mnemonic, passphrase) {
276
351
  if (this.isLocked)
@@ -512,13 +587,22 @@ export class MajikKey {
512
587
  return false;
513
588
  }
514
589
  }
515
- toContact() {
590
+ /**
591
+ * Converts the MajikKey to a MajikContact.
592
+ * You can pass a custom metadata type if needed, e.g., toContact<MyMeta>()
593
+ */
594
+ toContact(initialMeta) {
516
595
  const mlKeyBase64 = arrayToBase64(this.mlKemPublicKey);
596
+ // We construct the base metadata and merge with any provided initialMeta
597
+ const meta = {
598
+ label: this._label,
599
+ ...initialMeta,
600
+ };
517
601
  return new MajikContact({
518
602
  id: this._id,
519
603
  publicKey: this._publicKey,
520
604
  fingerprint: this._fingerprint,
521
- meta: { label: this._label },
605
+ meta: meta,
522
606
  mlKey: mlKeyBase64,
523
607
  edPublicKeyBase64: this._edPublicKey
524
608
  ? arrayToBase64(this._edPublicKey)
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.2.9",
5
+ "version": "0.2.11",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -50,7 +50,7 @@
50
50
  "test:watch": "vitest"
51
51
  },
52
52
  "dependencies": {
53
- "@majikah/majik-contact": "^0.0.4",
53
+ "@majikah/majik-contact": "^0.0.6",
54
54
  "@noble/hashes": "^2.2.0",
55
55
  "@noble/post-quantum": "^0.6.1",
56
56
  "@scure/bip39": "^2.2.0",