@majikah/majik-key 0.2.11 → 0.2.13

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.
@@ -27,6 +27,7 @@ import type { MajikKeyDangerousJSON, MajikKeyJSON, MajikKeyMetadata, MnemonicJSO
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";
30
+ import { MajikKeyWeb3Namespace, BitcoinDerivationOptions, BitcoinKeypairMaterial, SolanaKeypairMaterial } from "./core/web3";
30
31
  export interface MajikKeyIdentity {
31
32
  id: string;
32
33
  publicKey: CryptoKey | {
@@ -45,11 +46,14 @@ export interface MajikKeyIdentity {
45
46
  edSecretKey?: Uint8Array;
46
47
  mlDsaPublicKey?: Uint8Array;
47
48
  mlDsaSecretKey?: Uint8Array;
49
+ btcPublicKey?: Uint8Array;
50
+ btcSecretKey?: Uint8Array;
48
51
  }
49
52
  export type MajikKeyDerivedIdentity = MajikKeyIdentity & {
50
53
  encryptedMlKemSecretKey: ArrayBuffer;
51
54
  encryptedEdSecretKey: ArrayBuffer;
52
55
  encryptedMlDsaSecretKey: ArrayBuffer;
56
+ encryptedBtcSecretKey: ArrayBuffer;
53
57
  };
54
58
  export interface SerializedIdentity {
55
59
  id: string;
@@ -88,6 +92,10 @@ export interface MajikKeyConstructorOptions {
88
92
  encryptedMlDsaSecretKeyBase64?: string;
89
93
  edSecretKey?: Uint8Array;
90
94
  mlDsaSecretKey?: Uint8Array;
95
+ btcPublicKey?: Uint8Array;
96
+ encryptedBtcSecretKey?: ArrayBuffer;
97
+ encryptedBtcSecretKeyBase64?: string;
98
+ btcSecretKey?: Uint8Array;
91
99
  mnemonicLanguage?: MnemonicLanguage;
92
100
  }
93
101
  export declare class MajikKey {
@@ -117,6 +125,11 @@ export declare class MajikKey {
117
125
  private _mlDsaSecretKey?;
118
126
  private _encryptedMlDsaSecretKey?;
119
127
  private _encryptedMlDsaSecretKeyBase64?;
128
+ private _solanaKeypairMaterial?;
129
+ private _btcPublicKey?;
130
+ private _btcSecretKey?;
131
+ private _encryptedBtcSecretKey?;
132
+ private _encryptedBtcSecretKeyBase64?;
120
133
  private constructor();
121
134
  get id(): string;
122
135
  get fingerprint(): string;
@@ -136,6 +149,8 @@ export declare class MajikKey {
136
149
  get mlKemSecretKey(): Uint8Array | undefined;
137
150
  get hasMlKem(): boolean;
138
151
  get isFullyUpgraded(): boolean;
152
+ get btcPublicKey(): Uint8Array | undefined;
153
+ get hasBitcoin(): boolean;
139
154
  get metadata(): MajikKeyMetadata;
140
155
  get edPublicKey(): Uint8Array | undefined;
141
156
  get mlDsaPublicKey(): Uint8Array | undefined;
@@ -221,4 +236,62 @@ export declare class MajikKey {
221
236
  private static _exportMnemonicBackup;
222
237
  private static _deriveLegacyMnemonicKey;
223
238
  private static _exportKeyToBase64;
239
+ getBtcSecretKey(): Uint8Array;
240
+ get web3(): MajikKeyWeb3Namespace | undefined;
241
+ /**
242
+ * @experimental True if this MajikKey can currently produce Bitcoin
243
+ * material (i.e. it's unlocked and has a Bitcoin secret key).
244
+ */
245
+ get hasBitcoinKeypair(): boolean;
246
+ /**
247
+ * @experimental Raw Bitcoin keypair material. Pass `{ standard: true }` to
248
+ * get the REAL BIP-84 mainnet key (recoverable in any standard wallet from
249
+ * the mnemonic alone) instead of Majik's default domain-separated key.
250
+ *
251
+ * NOTE: `{ standard: true }` re-derives from the raw seed on demand and is
252
+ * NOT the same key as `web3.bitcoin` (which is always the stored,
253
+ * domain-separated default) — it requires the mnemonic to reproduce again
254
+ * outside Majik, whereas the stored default does not.
255
+ */
256
+ getBitcoinKeypairMaterial(options?: BitcoinDerivationOptions): BitcoinKeypairMaterial;
257
+ /**
258
+ * @experimental Derive the REAL BIP-84 mainnet Bitcoin keypair straight
259
+ * from a mnemonic — for one-off export/verification. Does not require
260
+ * an unlocked MajikKey instance.
261
+ */
262
+ static deriveStandardBitcoinFromMnemonic(mnemonic: string, mnemonicLanguage?: MnemonicLanguage): Promise<BitcoinKeypairMaterial>;
263
+ /**
264
+ * @experimental WIF export of the default (domain-separated) Bitcoin key.
265
+ */
266
+ getBitcoinWIF(options?: {
267
+ compressed?: boolean;
268
+ }): string;
269
+ /**
270
+ * @experimental True if this MajikKey can currently produce a Solana
271
+ * keypair (i.e. it's unlocked and has an Ed25519 signing key).
272
+ */
273
+ get hasSolanaKeypair(): boolean;
274
+ private _getOrDeriveSolanaMaterial;
275
+ /**
276
+ * @experimental Raw Solana keypair material (public/secret key bytes).
277
+ * Pass `{ reuseMessageKey: true }` to reuse the MajikKey's message signing
278
+ * Ed25519 key directly instead of the domain-separated derivation.
279
+ */
280
+ getSolanaKeypairMaterial(options?: {
281
+ reuseMessageKey?: boolean;
282
+ }): SolanaKeypairMaterial;
283
+ /**
284
+ * @experimental Real @solana/kit Keypair instance. Lazily loads
285
+ * @solana/kit — throws a MajikKeyError with install instructions if
286
+ * it isn't present in the consuming project.
287
+ */
288
+ getSolanaKeypair(options?: {
289
+ reuseMessageKey?: boolean;
290
+ }): Promise<any>;
291
+ /**
292
+ * @experimental Base58 Solana address. Does NOT require @solana/kit.
293
+ */
294
+ getSolanaAddress(options?: {
295
+ reuseMessageKey?: boolean;
296
+ }): string;
224
297
  }
package/dist/majik-key.js CHANGED
@@ -21,7 +21,7 @@
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 { generateMnemonic as bip39GenerateMnemonic, validateMnemonic, } from "@scure/bip39";
24
+ import { generateMnemonic as bip39GenerateMnemonic, mnemonicToSeed, validateMnemonic, } from "@scure/bip39";
25
25
  import { aesGcmDecrypt, aesGcmEncrypt, deriveKeyFromPassphraseArgon2, deriveKeyFromMnemonicArgon2, deriveKeyFromPassphrase, generateRandomBytes, IV_LENGTH, } from "./core/crypto/crypto-provider";
26
26
  import { EncryptionEngine } from "./core/crypto/encryption-engine";
27
27
  import { MajikContact } from "@majikah/majik-contact";
@@ -31,6 +31,7 @@ import { MajikKeyValidator } from "./core/validator";
31
31
  import { MajikKeyError } from "./core/error";
32
32
  import { MajikMessageIdentity } from "./core/database/system/identity";
33
33
  import { WORDLISTS } from "./core/crypto/wordlist";
34
+ import { deriveBitcoinKeypairFromSeed, signWithBitcoinMaterial, toBitcoinAddress, toWIF, deriveSolanaKeypairFromEdSecretKey, signWithSolanaMaterial, solanaAddressFromPublicKey, solanaMaterialFromEd25519SecretKey, toSolanaAddress, toSolanaKeyPairSigner, } from "./core/web3";
34
35
  const SALT_SIZE = 32;
35
36
  // ─── MajikKey ─────────────────────────────────────────────────────────────────
36
37
  export class MajikKey {
@@ -60,6 +61,12 @@ export class MajikKey {
60
61
  _mlDsaSecretKey;
61
62
  _encryptedMlDsaSecretKey;
62
63
  _encryptedMlDsaSecretKeyBase64;
64
+ //Experimental
65
+ _solanaKeypairMaterial;
66
+ _btcPublicKey;
67
+ _btcSecretKey;
68
+ _encryptedBtcSecretKey;
69
+ _encryptedBtcSecretKeyBase64;
63
70
  constructor(options) {
64
71
  this._id = options.id;
65
72
  this._publicKey = options.publicKey;
@@ -86,6 +93,10 @@ export class MajikKey {
86
93
  this._encryptedMlDsaSecretKeyBase64 = options.encryptedMlDsaSecretKeyBase64;
87
94
  this._edSecretKey = options.edSecretKey;
88
95
  this._mlDsaSecretKey = options.mlDsaSecretKey;
96
+ this._btcPublicKey = options.btcPublicKey;
97
+ this._btcSecretKey = options.btcSecretKey;
98
+ this._encryptedBtcSecretKey = options.encryptedBtcSecretKey;
99
+ this._encryptedBtcSecretKeyBase64 = options.encryptedBtcSecretKeyBase64;
89
100
  this._mnemonicLanguage = options.mnemonicLanguage || "en";
90
101
  }
91
102
  // ── Getters ─────────────────────────────────────────────────────────────────
@@ -137,6 +148,12 @@ export class MajikKey {
137
148
  get isFullyUpgraded() {
138
149
  return this.isArgon2id && this.hasMlKem;
139
150
  }
151
+ get btcPublicKey() {
152
+ return this._btcPublicKey;
153
+ }
154
+ get hasBitcoin() {
155
+ return this._btcPublicKey !== undefined;
156
+ }
140
157
  get metadata() {
141
158
  return {
142
159
  id: this.id,
@@ -146,6 +163,10 @@ export class MajikKey {
146
163
  isLocked: this.isLocked,
147
164
  kdfVersion: this.kdfVersion,
148
165
  hasMlKem: this.hasMlKem,
166
+ web3: {
167
+ hasBitcoin: this.hasBitcoin,
168
+ hasSolana: this.hasSolanaKeypair,
169
+ },
149
170
  mnemonicLanguage: this.mnemonicLanguage || "en",
150
171
  };
151
172
  }
@@ -198,6 +219,10 @@ export class MajikKey {
198
219
  encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
199
220
  edSecretKey: identity.edSecretKey,
200
221
  mlDsaSecretKey: identity.mlDsaSecretKey,
222
+ btcPublicKey: identity.btcPublicKey,
223
+ encryptedBtcSecretKey: identity.encryptedBtcSecretKey,
224
+ encryptedBtcSecretKeyBase64: arrayBufferToBase64(identity.encryptedBtcSecretKey),
225
+ btcSecretKey: identity.btcSecretKey,
201
226
  mnemonicLanguage: mnemonicLanguage,
202
227
  });
203
228
  }
@@ -240,6 +265,15 @@ export class MajikKey {
240
265
  encryptedMlDsaSecretKeyBase64 = anyParsed.encryptedMlDsaSecretKey;
241
266
  encryptedMlDsaSecretKey = base64ToArrayBuffer(anyParsed.encryptedMlDsaSecretKey);
242
267
  }
268
+ const btcPublicKey = anyParsed.btcPublicKey
269
+ ? base64ToUint8Array(anyParsed.btcPublicKey)
270
+ : undefined;
271
+ let encryptedBtcSecretKey;
272
+ let encryptedBtcSecretKeyBase64;
273
+ if (anyParsed.encryptedBtcSecretKey) {
274
+ encryptedBtcSecretKeyBase64 = anyParsed.encryptedBtcSecretKey;
275
+ encryptedBtcSecretKey = base64ToArrayBuffer(anyParsed.encryptedBtcSecretKey);
276
+ }
243
277
  return new MajikKey({
244
278
  id: validated.id,
245
279
  publicKey: { raw: new Uint8Array(publicKeyBuffer) },
@@ -262,6 +296,9 @@ export class MajikKey {
262
296
  mlDsaPublicKey,
263
297
  encryptedMlDsaSecretKey,
264
298
  encryptedMlDsaSecretKeyBase64,
299
+ btcPublicKey,
300
+ encryptedBtcSecretKey,
301
+ encryptedBtcSecretKeyBase64,
265
302
  mnemonicLanguage: validated?.mnemonicLanguage || "en",
266
303
  });
267
304
  }
@@ -291,6 +328,9 @@ export class MajikKey {
291
328
  mlKemSecretKeyBase64: arrayToBase64(this._mlKemSecretKey),
292
329
  edSecretKeyBase64: arrayToBase64(this._edSecretKey),
293
330
  mlDsaSecretKeyBase64: arrayToBase64(this._mlDsaSecretKey),
331
+ btcSecretKeyBase64: this._btcSecretKey
332
+ ? arrayToBase64(this._btcSecretKey)
333
+ : undefined,
294
334
  };
295
335
  }
296
336
  /**
@@ -320,6 +360,12 @@ export class MajikKey {
320
360
  const mlDsaSecretKey = base64ToUint8Array(parsed.mlDsaSecretKeyBase64);
321
361
  const mlKemPublicKey = base64ToUint8Array(parsed.mlKemPublicKey);
322
362
  const mlKemSecretKey = base64ToUint8Array(parsed.mlKemSecretKeyBase64);
363
+ const btcPublicKey = parsed.btcPublicKey
364
+ ? base64ToUint8Array(parsed.btcPublicKey)
365
+ : undefined;
366
+ const btcSecretKey = parsed.btcSecretKeyBase64
367
+ ? base64ToUint8Array(parsed.btcSecretKeyBase64)
368
+ : undefined;
323
369
  return new MajikKey({
324
370
  id: parsed.id,
325
371
  fingerprint: parsed.fingerprint,
@@ -338,6 +384,8 @@ export class MajikKey {
338
384
  edSecretKey,
339
385
  mlDsaPublicKey,
340
386
  mlDsaSecretKey,
387
+ btcPublicKey,
388
+ btcSecretKey,
341
389
  });
342
390
  }
343
391
  catch (err) {
@@ -418,6 +466,13 @@ export class MajikKey {
418
466
  this._encryptedMlDsaSecretKeyBase64 = arrayBufferToBase64(encDsa);
419
467
  this._mlDsaSecretKey = mlDsaSecretKeyBytes;
420
468
  }
469
+ if (this._encryptedBtcSecretKey) {
470
+ const btcSecretKeyBytes = await MajikKey._decryptSigningKey(this._encryptedBtcSecretKey, currentPassphrase, salt);
471
+ const encBtc = await MajikKey._encryptSigningKey(btcSecretKeyBytes, newPassphrase, newSalt);
472
+ this._encryptedBtcSecretKey = encBtc;
473
+ this._encryptedBtcSecretKeyBase64 = arrayBufferToBase64(encBtc);
474
+ this._btcSecretKey = btcSecretKeyBytes;
475
+ }
421
476
  return this;
422
477
  }
423
478
  catch (err) {
@@ -457,8 +512,10 @@ export class MajikKey {
457
512
  this._privateKey = undefined;
458
513
  this._privateKeyBase64 = undefined;
459
514
  this._mlKemSecretKey = undefined;
460
- this._edSecretKey = undefined; // ADD
461
- this._mlDsaSecretKey = undefined; // ADD
515
+ this._edSecretKey = undefined;
516
+ this._mlDsaSecretKey = undefined;
517
+ this._btcSecretKey = undefined;
518
+ this._solanaKeypairMaterial = undefined;
462
519
  return this;
463
520
  }
464
521
  async unlock(passphrase) {
@@ -489,6 +546,9 @@ export class MajikKey {
489
546
  if (this._encryptedMlDsaSecretKey) {
490
547
  this._mlDsaSecretKey = await MajikKey._decryptSigningKey(this._encryptedMlDsaSecretKey, passphrase, salt);
491
548
  }
549
+ if (this._encryptedBtcSecretKey) {
550
+ this._btcSecretKey = await MajikKey._decryptSigningKey(this._encryptedBtcSecretKey, passphrase, salt);
551
+ }
492
552
  return this;
493
553
  }
494
554
  catch (err) {
@@ -562,6 +622,10 @@ export class MajikKey {
562
622
  ? arrayToBase64(this._mlDsaPublicKey)
563
623
  : undefined,
564
624
  encryptedMlDsaSecretKey: this._encryptedMlDsaSecretKeyBase64,
625
+ btcPublicKey: this._btcPublicKey
626
+ ? arrayToBase64(this._btcPublicKey)
627
+ : undefined,
628
+ encryptedBtcSecretKey: this._encryptedBtcSecretKeyBase64,
565
629
  mnemonicLanguage: this._mnemonicLanguage,
566
630
  };
567
631
  }
@@ -719,6 +783,10 @@ export class MajikKey {
719
783
  encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
720
784
  edSecretKey: identity.edSecretKey,
721
785
  mlDsaSecretKey: identity.mlDsaSecretKey,
786
+ btcPublicKey: identity.btcPublicKey,
787
+ encryptedBtcSecretKey: identity.encryptedBtcSecretKey,
788
+ encryptedBtcSecretKeyBase64: arrayBufferToBase64(identity.encryptedBtcSecretKey),
789
+ btcSecretKey: identity.btcSecretKey,
722
790
  });
723
791
  }
724
792
  catch (err) {
@@ -758,6 +826,12 @@ export class MajikKey {
758
826
  const encryptedEdSecretKey = await MajikKey._encryptSigningKey(edSecretKey, passphrase, salt);
759
827
  const mlDsaSecretKey = encIdentity.mlDsaSecretKey;
760
828
  const encryptedMlDsaSecretKey = await MajikKey._encryptSigningKey(mlDsaSecretKey, passphrase, salt);
829
+ // Bitcoin — real BIP-32/BIP-84 off the raw 64-byte BIP-39 seed, using
830
+ // Majik's domain-separated path by default. Same salt, different IV,
831
+ // same pattern as ML-KEM/Ed25519/ML-DSA above.
832
+ const rawSeed = await mnemonicToSeed(mnemonic);
833
+ const btcMaterial = deriveBitcoinKeypairFromSeed(rawSeed);
834
+ const encryptedBtcSecretKey = await MajikKey._encryptSigningKey(btcMaterial.privateKey, passphrase, salt);
761
835
  return {
762
836
  id: encIdentity.fingerprint,
763
837
  publicKey: encIdentity.publicKey,
@@ -775,6 +849,9 @@ export class MajikKey {
775
849
  mlDsaPublicKey: encIdentity.mlDsaPublicKey,
776
850
  mlDsaSecretKey,
777
851
  encryptedMlDsaSecretKey,
852
+ btcPublicKey: btcMaterial.publicKey,
853
+ btcSecretKey: btcMaterial.privateKey,
854
+ encryptedBtcSecretKey,
778
855
  };
779
856
  }
780
857
  // ── PRIVATE: Encryption/Decryption ───────────────────────────────────────────
@@ -906,4 +983,133 @@ export class MajikKey {
906
983
  const raw = await crypto.subtle.exportKey("raw", key);
907
984
  return arrayBufferToBase64(raw);
908
985
  }
986
+ // ── WEB3 (EXPERIMENTAL) ─────────────────────────────────────────────────────
987
+ getBtcSecretKey() {
988
+ if (this.isLocked)
989
+ throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
990
+ if (!this._btcSecretKey)
991
+ throw new MajikKeyError("No Bitcoin secret key — re-import via importFromMnemonicBackup() for full migration.");
992
+ return this._btcSecretKey;
993
+ }
994
+ // ── WEB3 (EXPERIMENTAL) — updated getter ────────────────────────────────────
995
+ get web3() {
996
+ if (!this.hasSolanaKeypair)
997
+ return undefined;
998
+ const solanaMaterial = this._getOrDeriveSolanaMaterial();
999
+ const btcMaterial = this._btcSecretKey && this._btcPublicKey
1000
+ ? { privateKey: this._btcSecretKey, publicKey: this._btcPublicKey }
1001
+ : undefined;
1002
+ return {
1003
+ solana: {
1004
+ publicKey: solanaMaterial.publicKey,
1005
+ secretKey: solanaMaterial.secretKey,
1006
+ address: solanaAddressFromPublicKey(solanaMaterial.publicKey),
1007
+ getSolanaKeypair: () => toSolanaKeyPairSigner(solanaMaterial),
1008
+ getSolanaAddress: () => toSolanaAddress(solanaMaterial),
1009
+ sign: (message) => signWithSolanaMaterial(solanaMaterial, message),
1010
+ },
1011
+ bitcoin: btcMaterial && {
1012
+ publicKey: btcMaterial.publicKey,
1013
+ privateKey: btcMaterial.privateKey,
1014
+ getBitcoinAddress: () => toBitcoinAddress(btcMaterial),
1015
+ getWIF: (options) => toWIF(btcMaterial, options),
1016
+ sign: (hash, scheme) => signWithBitcoinMaterial(btcMaterial, hash, scheme),
1017
+ },
1018
+ };
1019
+ }
1020
+ // ── BITCON (EXPERIMENTAL) ────────────────────────────────────
1021
+ /**
1022
+ * @experimental True if this MajikKey can currently produce Bitcoin
1023
+ * material (i.e. it's unlocked and has a Bitcoin secret key).
1024
+ */
1025
+ get hasBitcoinKeypair() {
1026
+ return this.isUnlocked && this._btcSecretKey !== undefined;
1027
+ }
1028
+ /**
1029
+ * @experimental Raw Bitcoin keypair material. Pass `{ standard: true }` to
1030
+ * get the REAL BIP-84 mainnet key (recoverable in any standard wallet from
1031
+ * the mnemonic alone) instead of Majik's default domain-separated key.
1032
+ *
1033
+ * NOTE: `{ standard: true }` re-derives from the raw seed on demand and is
1034
+ * NOT the same key as `web3.bitcoin` (which is always the stored,
1035
+ * domain-separated default) — it requires the mnemonic to reproduce again
1036
+ * outside Majik, whereas the stored default does not.
1037
+ */
1038
+ getBitcoinKeypairMaterial(options) {
1039
+ if (this.isLocked)
1040
+ throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
1041
+ if (!options?.standard && !options?.path) {
1042
+ if (!this._btcSecretKey || !this._btcPublicKey)
1043
+ throw new MajikKeyError("No Bitcoin secret key — re-import via importFromMnemonicBackup() first.");
1044
+ return { privateKey: this._btcSecretKey, publicKey: this._btcPublicKey };
1045
+ }
1046
+ throw new MajikKeyError("Deriving the standard BIP-84 path requires the mnemonic — " +
1047
+ "use MajikKey.deriveStandardBitcoinFromMnemonic(mnemonic) instead.");
1048
+ }
1049
+ /**
1050
+ * @experimental Derive the REAL BIP-84 mainnet Bitcoin keypair straight
1051
+ * from a mnemonic — for one-off export/verification. Does not require
1052
+ * an unlocked MajikKey instance.
1053
+ */
1054
+ static async deriveStandardBitcoinFromMnemonic(mnemonic, mnemonicLanguage = "en") {
1055
+ MajikKeyValidator.validateMnemonic(mnemonic);
1056
+ const wordlist = await MajikKey._getWordlist(mnemonicLanguage);
1057
+ if (!validateMnemonic(mnemonic, wordlist)) {
1058
+ throw new MajikKeyError("Invalid BIP39 mnemonic phrase");
1059
+ }
1060
+ const seed = await mnemonicToSeed(mnemonic);
1061
+ return deriveBitcoinKeypairFromSeed(seed, { standard: true });
1062
+ }
1063
+ /**
1064
+ * @experimental WIF export of the default (domain-separated) Bitcoin key.
1065
+ */
1066
+ getBitcoinWIF(options) {
1067
+ const material = this.getBitcoinKeypairMaterial();
1068
+ return toWIF(material, options);
1069
+ }
1070
+ // ── SOLANA (EXPERIMENTAL) ────────────────────────────────────
1071
+ /**
1072
+ * @experimental True if this MajikKey can currently produce a Solana
1073
+ * keypair (i.e. it's unlocked and has an Ed25519 signing key).
1074
+ */
1075
+ get hasSolanaKeypair() {
1076
+ return this.isUnlocked && this._edSecretKey !== undefined;
1077
+ }
1078
+ _getOrDeriveSolanaMaterial() {
1079
+ if (!this._edSecretKey)
1080
+ throw new MajikKeyError("No Ed25519 secret key — MajikKey must be unlocked and have signing keys.");
1081
+ if (!this._solanaKeypairMaterial) {
1082
+ this._solanaKeypairMaterial = deriveSolanaKeypairFromEdSecretKey(this._edSecretKey);
1083
+ }
1084
+ return this._solanaKeypairMaterial;
1085
+ }
1086
+ /**
1087
+ * @experimental Raw Solana keypair material (public/secret key bytes).
1088
+ * Pass `{ reuseMessageKey: true }` to reuse the MajikKey's message signing
1089
+ * Ed25519 key directly instead of the domain-separated derivation.
1090
+ */
1091
+ getSolanaKeypairMaterial(options) {
1092
+ if (this.isLocked)
1093
+ throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
1094
+ if (!this._edSecretKey)
1095
+ throw new MajikKeyError("No Ed25519 secret key — re-import via importFromMnemonicBackup() first.");
1096
+ if (options?.reuseMessageKey) {
1097
+ return solanaMaterialFromEd25519SecretKey(this._edSecretKey);
1098
+ }
1099
+ return this._getOrDeriveSolanaMaterial();
1100
+ }
1101
+ /**
1102
+ * @experimental Real @solana/kit Keypair instance. Lazily loads
1103
+ * @solana/kit — throws a MajikKeyError with install instructions if
1104
+ * it isn't present in the consuming project.
1105
+ */
1106
+ async getSolanaKeypair(options) {
1107
+ return toSolanaKeyPairSigner(this.getSolanaKeypairMaterial(options));
1108
+ }
1109
+ /**
1110
+ * @experimental Base58 Solana address. Does NOT require @solana/kit.
1111
+ */
1112
+ getSolanaAddress(options) {
1113
+ return solanaAddressFromPublicKey(this.getSolanaKeypairMaterial(options).publicKey);
1114
+ }
909
1115
  }
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.11",
5
+ "version": "0.2.13",
6
6
  "license": "Apache-2.0",
7
7
  "author": "Zelijah",
8
8
  "main": "./dist/index.js",
@@ -47,12 +47,18 @@
47
47
  "prepublishOnly": "npm run build",
48
48
  "package": "npm run build && npm version patch && git push && git push --tags",
49
49
  "test": "vitest run",
50
- "test:watch": "vitest"
50
+ "test:watch": "vitest",
51
+ "test:web3:bitcoin": "npx vitest test/majik-key-bitcoin.test.ts",
52
+ "test:web3:solana": "npx vitest test/majik-key-solana.test.ts",
53
+ "test:web3": "npx vitest test/majik-key-bitcoin.test.ts test/majik-key-solana.test.ts",
54
+ "test:core": "npx vitest test/majik-key.test.ts"
51
55
  },
52
56
  "dependencies": {
53
57
  "@majikah/majik-contact": "^0.0.6",
58
+ "@noble/curves": "^2.2.0",
54
59
  "@noble/hashes": "^2.2.0",
55
60
  "@noble/post-quantum": "^0.6.1",
61
+ "@scure/bip32": "^2.2.0",
56
62
  "@scure/bip39": "^2.2.0",
57
63
  "@stablelib/aes": "^2.0.1",
58
64
  "@stablelib/ed25519": "^2.1.0",
@@ -60,14 +66,28 @@
60
66
  "@stablelib/pbkdf2": "^2.0.1",
61
67
  "@stablelib/sha256": "^2.0.1",
62
68
  "@stablelib/x25519": "^2.0.1",
63
- "@thezelijah/majik-user": "^1.0.6",
69
+ "@thezelijah/majik-user": "^1.0.9",
64
70
  "ed2curve": "^0.3.0",
65
71
  "hash-wasm": "^4.12.0"
66
72
  },
67
73
  "devDependencies": {
74
+ "@scure/btc-signer": "^2.2.0",
75
+ "@solana/kit": "^7.0.0",
68
76
  "@types/ed2curve": "^0.2.4",
69
- "@types/node": "^26.0.0",
77
+ "@types/node": "^26.1.0",
70
78
  "typescript": "^6.0.3",
71
79
  "vitest": "^4.1.9"
80
+ },
81
+ "peerDependencies": {
82
+ "@scure/btc-signer": "^2.2.0",
83
+ "@solana/kit": "^7.0.0"
84
+ },
85
+ "peerDependenciesMeta": {
86
+ "@solana/kit": {
87
+ "optional": true
88
+ },
89
+ "@scure/btc-signer": {
90
+ "optional": true
91
+ }
72
92
  }
73
93
  }