@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.
- package/README.md +179 -820
- package/dist/core/types.d.ts +7 -0
- package/dist/core/web3/bitcoin/bitcoin.d.ts +71 -0
- package/dist/core/web3/bitcoin/bitcoin.js +126 -0
- package/dist/core/web3/bitcoin/constants.d.ts +2 -0
- package/dist/core/web3/bitcoin/constants.js +7 -0
- package/dist/core/web3/bitcoin/types.d.ts +23 -0
- package/dist/core/web3/bitcoin/types.js +1 -0
- package/dist/core/web3/index.d.ts +5 -0
- package/dist/core/web3/index.js +2 -0
- package/dist/core/web3/solana/constants.d.ts +1 -0
- package/dist/core/web3/solana/constants.js +1 -0
- package/dist/core/web3/solana/solana.d.ts +73 -0
- package/dist/core/web3/solana/solana.js +120 -0
- package/dist/core/web3/solana/types.d.ts +18 -0
- package/dist/core/web3/solana/types.js +1 -0
- package/dist/core/web3/types.d.ts +7 -0
- package/dist/core/web3/types.js +1 -0
- package/dist/core/web3/utils.d.ts +1 -0
- package/dist/core/web3/utils.js +27 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/majik-key.d.ts +73 -0
- package/dist/majik-key.js +209 -3
- package/package.json +24 -4
package/dist/majik-key.d.ts
CHANGED
|
@@ -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;
|
|
461
|
-
this._mlDsaSecretKey = undefined;
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
}
|