@majikah/majik-key 0.2.13 → 0.3.0
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/LICENSE +1 -1
- package/README.md +225 -117
- package/dist/core/types.d.ts +87 -3
- package/dist/majik-key.d.ts +192 -38
- package/dist/majik-key.js +142 -48
- package/package.json +1 -1
package/dist/majik-key.js
CHANGED
|
@@ -1,25 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* MajikKey.ts
|
|
3
|
+
* Seed phrase account library for the Majikah ecosystem.
|
|
3
4
|
*
|
|
4
|
-
* Seed phrase account library for Majik Message.
|
|
5
|
-
*
|
|
6
|
-
* Every account stores TWO keypairs derived deterministically from the mnemonic:
|
|
7
|
-
* 1. X25519 (Curve25519) — fingerprint, contact identity, legacy message compat
|
|
8
|
-
* 2. ML-KEM-768 (FIPS-203) — post-quantum key encapsulation for v3 envelopes
|
|
9
|
-
*
|
|
10
|
-
* Both are derived from the 64-byte BIP-39 seed:
|
|
11
|
-
* seed[0..32] → Ed25519 → X25519 via ed2curve
|
|
12
|
-
* seed[0..64] → ml_kem768.keygen(seed) — full seed, deterministic
|
|
13
|
-
*
|
|
14
|
-
* KDF versioning (passphrase encryption at rest):
|
|
15
|
-
* v1 — PBKDF2-SHA256, 250k iterations (legacy read-only)
|
|
16
|
-
* v2 — Argon2id, 128 MB / 4t / 4p (all new accounts)
|
|
17
|
-
*
|
|
18
|
-
* Migration policy:
|
|
19
|
-
* Old accounts (v1, no ML-KEM keys) are fully upgraded on first import
|
|
20
|
-
* via importFromMnemonicBackup(). The mnemonic is always available at that
|
|
21
|
-
* point, so ML-KEM keys can be deterministically re-derived and stored.
|
|
22
|
-
* No partial migration — either fully upgraded or not upgraded yet.
|
|
23
5
|
*/
|
|
24
6
|
import { generateMnemonic as bip39GenerateMnemonic, mnemonicToSeed, validateMnemonic, } from "@scure/bip39";
|
|
25
7
|
import { aesGcmDecrypt, aesGcmEncrypt, deriveKeyFromPassphraseArgon2, deriveKeyFromMnemonicArgon2, deriveKeyFromPassphrase, generateRandomBytes, IV_LENGTH, } from "./core/crypto/crypto-provider";
|
|
@@ -33,7 +15,29 @@ import { MajikMessageIdentity } from "./core/database/system/identity";
|
|
|
33
15
|
import { WORDLISTS } from "./core/crypto/wordlist";
|
|
34
16
|
import { deriveBitcoinKeypairFromSeed, signWithBitcoinMaterial, toBitcoinAddress, toWIF, deriveSolanaKeypairFromEdSecretKey, signWithSolanaMaterial, solanaAddressFromPublicKey, solanaMaterialFromEd25519SecretKey, toSolanaAddress, toSolanaKeyPairSigner, } from "./core/web3";
|
|
35
17
|
const SALT_SIZE = 32;
|
|
36
|
-
|
|
18
|
+
/**
|
|
19
|
+
* MajikKey
|
|
20
|
+
* ---
|
|
21
|
+
*
|
|
22
|
+
* Seed phrase account library for the Majikah ecosystem.
|
|
23
|
+
*
|
|
24
|
+
* Every account stores FIVE keypairs, all deterministically derived from a
|
|
25
|
+
* single BIP-39 mnemonic:
|
|
26
|
+
* 1. X25519 (Curve25519) — fingerprint, contact identity, legacy message compat
|
|
27
|
+
* 2. ML-KEM-768 (FIPS-203) — post-quantum key encapsulation for v3 envelopes
|
|
28
|
+
* 3. Ed25519 — classical signing
|
|
29
|
+
* 4. ML-DSA-87 (FIPS-204) — post-quantum signing
|
|
30
|
+
* 5. Bitcoin (secp256k1) — BIP-32/84 HD key, domain-separated by default (experimental)
|
|
31
|
+
*
|
|
32
|
+
* All derived from the 64-byte BIP-39 seed:
|
|
33
|
+
* seed[0..32] → Ed25519 keypair — used directly for signing, AND converted
|
|
34
|
+
* to X25519 via ed2curve for the encryption/identity keypair
|
|
35
|
+
* (one Ed25519 keypair, two roles)
|
|
36
|
+
* seed[0..64] → ml_kem768.keygen(seed) — full seed, deterministic
|
|
37
|
+
* hash(seed || "MajikSignatureSeedDSA") → 32-byte seed → ml_dsa87.keygen()
|
|
38
|
+
* seed[0..64] → HDKey.fromMasterSeed(seed).derive(path) — BIP-32/84 Bitcoin key
|
|
39
|
+
*
|
|
40
|
+
*/
|
|
37
41
|
export class MajikKey {
|
|
38
42
|
_id;
|
|
39
43
|
_publicKey;
|
|
@@ -61,11 +65,25 @@ export class MajikKey {
|
|
|
61
65
|
_mlDsaSecretKey;
|
|
62
66
|
_encryptedMlDsaSecretKey;
|
|
63
67
|
_encryptedMlDsaSecretKeyBase64;
|
|
64
|
-
|
|
68
|
+
/**
|
|
69
|
+
* @experimental
|
|
70
|
+
*/
|
|
65
71
|
_solanaKeypairMaterial;
|
|
72
|
+
/**
|
|
73
|
+
* @experimental
|
|
74
|
+
*/
|
|
66
75
|
_btcPublicKey;
|
|
76
|
+
/**
|
|
77
|
+
* @experimental
|
|
78
|
+
*/
|
|
67
79
|
_btcSecretKey;
|
|
80
|
+
/**
|
|
81
|
+
* @experimental
|
|
82
|
+
*/
|
|
68
83
|
_encryptedBtcSecretKey;
|
|
84
|
+
/**
|
|
85
|
+
* @experimental
|
|
86
|
+
*/
|
|
69
87
|
_encryptedBtcSecretKeyBase64;
|
|
70
88
|
constructor(options) {
|
|
71
89
|
this._id = options.id;
|
|
@@ -100,60 +118,92 @@ export class MajikKey {
|
|
|
100
118
|
this._mnemonicLanguage = options.mnemonicLanguage || "en";
|
|
101
119
|
}
|
|
102
120
|
// ── Getters ─────────────────────────────────────────────────────────────────
|
|
121
|
+
/** Account identifier. Equal to `fingerprint` for accounts created by this library. */
|
|
103
122
|
get id() {
|
|
104
123
|
return this._id;
|
|
105
124
|
}
|
|
125
|
+
/** SHA-256 fingerprint of the X25519 public key. Stable identity anchor for the account. */
|
|
106
126
|
get fingerprint() {
|
|
107
127
|
return this._fingerprint;
|
|
108
128
|
}
|
|
129
|
+
/** X25519 public key — native `CryptoKey` where WebCrypto supports it, otherwise a raw-bytes wrapper. Always available, even when locked. */
|
|
109
130
|
get publicKey() {
|
|
110
131
|
return this._publicKey;
|
|
111
132
|
}
|
|
133
|
+
/** X25519 public key, base64-encoded. Always available, even when locked. */
|
|
112
134
|
get publicKeyBase64() {
|
|
113
135
|
return this._publicKeyBase64;
|
|
114
136
|
}
|
|
137
|
+
/** Human-readable, user-editable account name. Update via `updateLabel()`. */
|
|
115
138
|
get label() {
|
|
116
139
|
return this._label;
|
|
117
140
|
}
|
|
141
|
+
/** BIP-39 wordlist language this account's mnemonic was generated/validated against. */
|
|
118
142
|
get mnemonicLanguage() {
|
|
119
143
|
return this._mnemonicLanguage;
|
|
120
144
|
}
|
|
145
|
+
/**
|
|
146
|
+
* Encrypted mnemonic-verification blob (base64 JSON). Decryptable only
|
|
147
|
+
* with the original mnemonic — used internally to verify a supplied
|
|
148
|
+
* mnemonic before `importFromMnemonicBackup()` re-derives the full
|
|
149
|
+
* identity. Not a general-purpose private-key backup.
|
|
150
|
+
*/
|
|
121
151
|
get backup() {
|
|
122
152
|
return this._backup;
|
|
123
153
|
}
|
|
154
|
+
/** Account creation time. */
|
|
124
155
|
get timestamp() {
|
|
125
156
|
return this._timestamp;
|
|
126
157
|
}
|
|
158
|
+
/** KDF currently protecting every `encrypted*` field on this account: `1` = legacy PBKDF2, `2` = Argon2id. */
|
|
127
159
|
get kdfVersion() {
|
|
128
160
|
return this._kdfVersion;
|
|
129
161
|
}
|
|
162
|
+
/** `true` if this account is on the current KDF (Argon2id). `false` means it's still on legacy PBKDF2 — see `migrate()` or `importFromMnemonicBackup()`. */
|
|
130
163
|
get isArgon2id() {
|
|
131
164
|
return this._kdfVersion === KDF_VERSION.ARGON2ID;
|
|
132
165
|
}
|
|
166
|
+
/** `true` if private key material is currently purged from memory (i.e. `lock()` was called, or `unlock()` hasn't been called yet). */
|
|
133
167
|
get isLocked() {
|
|
134
168
|
return this._privateKey === undefined;
|
|
135
169
|
}
|
|
170
|
+
/** `true` if private key material is currently decrypted in memory. The inverse of `isLocked`. */
|
|
136
171
|
get isUnlocked() {
|
|
137
172
|
return this._privateKey !== undefined;
|
|
138
173
|
}
|
|
174
|
+
/** ML-KEM-768 (FIPS-203) public key. Post-quantum key encapsulation. Always available, even when locked. */
|
|
139
175
|
get mlKemPublicKey() {
|
|
140
176
|
return this._mlKemPublicKey;
|
|
141
177
|
}
|
|
178
|
+
/** ML-KEM-768 secret key. `undefined` unless the account is unlocked. ⚠️ Live key material — prefer `getMlKemSecretKey()` if you want a thrown error instead of `undefined` on locked accounts. */
|
|
142
179
|
get mlKemSecretKey() {
|
|
143
180
|
return this._mlKemSecretKey;
|
|
144
181
|
}
|
|
182
|
+
/** `true` if this account has ML-KEM-768 keys (i.e. is post-quantum-encryption capable). `false` means it's a legacy account pending migration. */
|
|
145
183
|
get hasMlKem() {
|
|
146
184
|
return this._mlKemPublicKey !== undefined;
|
|
147
185
|
}
|
|
186
|
+
/** `true` if this account is on Argon2id *and* has ML-KEM-768 keys — i.e. fully migrated, nothing left to upgrade. */
|
|
148
187
|
get isFullyUpgraded() {
|
|
149
188
|
return this.isArgon2id && this.hasMlKem;
|
|
150
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* @experimental secp256k1 Bitcoin public key. `undefined` if this account
|
|
192
|
+
* has no stored Bitcoin key material (e.g. it predates Web3 support and
|
|
193
|
+
* hasn't been re-imported via `importFromMnemonicBackup()`).
|
|
194
|
+
*/
|
|
151
195
|
get btcPublicKey() {
|
|
152
196
|
return this._btcPublicKey;
|
|
153
197
|
}
|
|
198
|
+
/** @experimental `true` if this account has a stored Bitcoin keypair. */
|
|
154
199
|
get hasBitcoin() {
|
|
155
200
|
return this._btcPublicKey !== undefined;
|
|
156
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* Lightweight, non-secret snapshot of this account's state — no key bytes
|
|
204
|
+
* at all, encrypted or otherwise. Useful for account pickers, dashboards,
|
|
205
|
+
* or anywhere you want to display status without touching key material.
|
|
206
|
+
*/
|
|
157
207
|
get metadata() {
|
|
158
208
|
return {
|
|
159
209
|
id: this.id,
|
|
@@ -170,26 +220,50 @@ export class MajikKey {
|
|
|
170
220
|
mnemonicLanguage: this.mnemonicLanguage || "en",
|
|
171
221
|
};
|
|
172
222
|
}
|
|
223
|
+
/** Ed25519 public key. Classical signing — same keypair the X25519 identity key is converted from. Always available, even when locked. */
|
|
173
224
|
get edPublicKey() {
|
|
174
225
|
return this._edPublicKey;
|
|
175
226
|
}
|
|
227
|
+
/** ML-DSA-87 (FIPS-204) public key. Post-quantum signing. Always available, even when locked. */
|
|
176
228
|
get mlDsaPublicKey() {
|
|
177
229
|
return this._mlDsaPublicKey;
|
|
178
230
|
}
|
|
231
|
+
/** `true` if this account has both Ed25519 and ML-DSA-87 signing keys. `false` means it's a legacy account pending migration. */
|
|
179
232
|
get hasSigningKeys() {
|
|
180
233
|
return (this._edPublicKey !== undefined && this._mlDsaPublicKey !== undefined);
|
|
181
234
|
}
|
|
182
235
|
// ── CREATE ──────────────────────────────────────────────────────────────────
|
|
183
|
-
|
|
236
|
+
/**
|
|
237
|
+
* Creates a brand-new MajikKey account from a BIP-39 mnemonic.
|
|
238
|
+
*
|
|
239
|
+
* Derives the full key set in one pass — X25519, ML-KEM-768, Ed25519,
|
|
240
|
+
* ML-DSA-87, and a domain-separated Bitcoin key (see `MAJIK_BITCOIN_DOMAIN_PATH`)
|
|
241
|
+
* — encrypts every private key with Argon2id (KDF v2), and returns an
|
|
242
|
+
* **already-unlocked** instance (no `unlock()` call needed right after
|
|
243
|
+
* `create()`).
|
|
244
|
+
*
|
|
245
|
+
* @param mnemonic - A valid BIP-39 mnemonic phrase (12 or 24 words), matching `mnemonicLanguage`. Generate one with `MajikKey.generateMnemonic()`.
|
|
246
|
+
* @param passphrase - Passphrase used to derive the Argon2id encryption key for every private key on this account. This is *not* the mnemonic — losing it without the mnemonic makes the account unrecoverable.
|
|
247
|
+
* @param label - Optional human-readable account name. Defaults to an empty string. Update later via `updateLabel()`.
|
|
248
|
+
* @param mnemonicLanguage - BIP-39 wordlist to validate `mnemonic` against. Defaults to `"en"`.
|
|
249
|
+
* @param options.deriveBitcoin - @experimental Set `false` to skip deriving the Bitcoin keypair. Defaults to `true`.
|
|
250
|
+
* @returns An unlocked `MajikKey` instance, ready for immediate use — call `.lock()` when you're done with it.
|
|
251
|
+
* @throws {MajikKeyError} If `mnemonic` fails validation, `passphrase`/`label` fail their validators, or `mnemonic` doesn't match `mnemonicLanguage`'s wordlist.
|
|
252
|
+
*/
|
|
253
|
+
static async create(mnemonic, passphrase, label, options = {
|
|
254
|
+
deriveBitcoin: true,
|
|
255
|
+
mnemonicLanguage: "en",
|
|
256
|
+
}) {
|
|
184
257
|
try {
|
|
185
258
|
MajikKeyValidator.validateMnemonic(mnemonic);
|
|
186
259
|
MajikKeyValidator.validatePassphrase(passphrase);
|
|
187
260
|
MajikKeyValidator.validateLabel(label);
|
|
188
|
-
const
|
|
261
|
+
const { deriveBitcoin, mnemonicLanguage } = options;
|
|
262
|
+
const wordlist = await MajikKey._getWordlist(mnemonicLanguage || "en");
|
|
189
263
|
if (!validateMnemonic(mnemonic, wordlist)) {
|
|
190
264
|
throw new MajikKeyError("Invalid BIP39 mnemonic phrase");
|
|
191
265
|
}
|
|
192
|
-
const identity = await MajikKey._deriveAndEncryptFromMnemonic(mnemonic, passphrase);
|
|
266
|
+
const identity = await MajikKey._deriveAndEncryptFromMnemonic(mnemonic, passphrase, { deriveBitcoin: deriveBitcoin });
|
|
193
267
|
const privateKeyBase64 = await MajikKey._exportKeyToBase64(identity.privateKey);
|
|
194
268
|
const publicKeyBase64 = await MajikKey._exportKeyToBase64(identity.publicKey);
|
|
195
269
|
const backup = await MajikKey._exportMnemonicBackup(identity, mnemonic);
|
|
@@ -221,7 +295,9 @@ export class MajikKey {
|
|
|
221
295
|
mlDsaSecretKey: identity.mlDsaSecretKey,
|
|
222
296
|
btcPublicKey: identity.btcPublicKey,
|
|
223
297
|
encryptedBtcSecretKey: identity.encryptedBtcSecretKey,
|
|
224
|
-
encryptedBtcSecretKeyBase64:
|
|
298
|
+
encryptedBtcSecretKeyBase64: identity.encryptedBtcSecretKey
|
|
299
|
+
? arrayBufferToBase64(identity.encryptedBtcSecretKey)
|
|
300
|
+
: undefined,
|
|
225
301
|
btcSecretKey: identity.btcSecretKey,
|
|
226
302
|
mnemonicLanguage: mnemonicLanguage,
|
|
227
303
|
});
|
|
@@ -720,23 +796,19 @@ export class MajikKey {
|
|
|
720
796
|
/**
|
|
721
797
|
* Import a MajikKey from a mnemonic-encrypted backup.
|
|
722
798
|
*
|
|
723
|
-
* This is the FULL MIGRATION PATH for old accounts — Argon2id + ML-KEM in one step:
|
|
724
|
-
* 1. Verify the backup decrypts correctly (proves mnemonic is correct)
|
|
725
|
-
* 2. Re-derive the complete identity from the mnemonic (X25519 + ML-KEM-768)
|
|
726
|
-
* 3. Encrypt both private keys with Argon2id (v2) + fresh 32-byte salt
|
|
727
|
-
* 4. Return a fully-upgraded MajikKey with hasMlKem: true, isArgon2id: true
|
|
728
|
-
*
|
|
729
|
-
* Old accounts without ML-KEM keys become fully post-quantum capable
|
|
730
|
-
* automatically — no extra user steps. The mnemonic is the source of truth.
|
|
731
799
|
*/
|
|
732
|
-
static async importFromMnemonicBackup(backup, mnemonic, passphrase, label,
|
|
800
|
+
static async importFromMnemonicBackup(backup, mnemonic, passphrase, label, options = {
|
|
801
|
+
deriveBitcoin: true,
|
|
802
|
+
mnemonicLanguage: "en",
|
|
803
|
+
}) {
|
|
733
804
|
try {
|
|
734
805
|
if (!backup || typeof backup !== "string")
|
|
735
806
|
throw new MajikKeyError("Backup must be a non-empty string");
|
|
736
807
|
MajikKeyValidator.validateMnemonic(mnemonic);
|
|
737
808
|
MajikKeyValidator.validatePassphrase(passphrase);
|
|
738
809
|
MajikKeyValidator.validateLabel(label);
|
|
739
|
-
const
|
|
810
|
+
const { deriveBitcoin, mnemonicLanguage } = options;
|
|
811
|
+
const wordlist = await MajikKey._getWordlist(mnemonicLanguage || "en");
|
|
740
812
|
if (!validateMnemonic(mnemonic, wordlist)) {
|
|
741
813
|
throw new MajikKeyError("Invalid BIP39 mnemonic phrase");
|
|
742
814
|
}
|
|
@@ -753,7 +825,7 @@ export class MajikKey {
|
|
|
753
825
|
// Verify mnemonic is correct before doing expensive re-derivation
|
|
754
826
|
await MajikKey._verifyBackupDecryption(parsed.iv, parsed.ciphertext, mnemonic, backupKdfVersion);
|
|
755
827
|
// Re-derive complete identity from mnemonic — gets ML-KEM for free
|
|
756
|
-
const identity = await MajikKey._deriveAndEncryptFromMnemonic(mnemonic, passphrase);
|
|
828
|
+
const identity = await MajikKey._deriveAndEncryptFromMnemonic(mnemonic, passphrase, { deriveBitcoin: deriveBitcoin });
|
|
757
829
|
const privateKeyBase64 = await MajikKey._exportKeyToBase64(identity.privateKey);
|
|
758
830
|
const publicKeyBase64 = await MajikKey._exportKeyToBase64(identity.publicKey);
|
|
759
831
|
const id = parsed.id || identity.id;
|
|
@@ -785,7 +857,9 @@ export class MajikKey {
|
|
|
785
857
|
mlDsaSecretKey: identity.mlDsaSecretKey,
|
|
786
858
|
btcPublicKey: identity.btcPublicKey,
|
|
787
859
|
encryptedBtcSecretKey: identity.encryptedBtcSecretKey,
|
|
788
|
-
encryptedBtcSecretKeyBase64:
|
|
860
|
+
encryptedBtcSecretKeyBase64: identity.encryptedBtcSecretKey
|
|
861
|
+
? arrayBufferToBase64(identity.encryptedBtcSecretKey)
|
|
862
|
+
: undefined,
|
|
789
863
|
btcSecretKey: identity.btcSecretKey,
|
|
790
864
|
});
|
|
791
865
|
}
|
|
@@ -801,7 +875,13 @@ export class MajikKey {
|
|
|
801
875
|
const mod = await loader();
|
|
802
876
|
return mod.wordlist;
|
|
803
877
|
}
|
|
804
|
-
|
|
878
|
+
/**
|
|
879
|
+
* @param mnemonic - BIP-39 mnemonic to derive the full key set from.
|
|
880
|
+
* @param passphrase - Passphrase used to derive the Argon2id encryption key shared by every private key produced here.
|
|
881
|
+
* @param options.deriveBitcoin - @experimental Set `false` to skip deriving the Bitcoin keypair. Defaults to `true`.
|
|
882
|
+
*/
|
|
883
|
+
static async _deriveAndEncryptFromMnemonic(mnemonic, passphrase, options) {
|
|
884
|
+
const deriveBitcoin = options?.deriveBitcoin ?? true;
|
|
805
885
|
const encIdentity = await EncryptionEngine.deriveIdentityFromMnemonic(mnemonic);
|
|
806
886
|
let exportedXPrivate;
|
|
807
887
|
try {
|
|
@@ -816,22 +896,30 @@ export class MajikKey {
|
|
|
816
896
|
throw new MajikKeyError("Cannot export private key: unsupported format");
|
|
817
897
|
}
|
|
818
898
|
}
|
|
819
|
-
// Single salt — one Argon2id derivation unlocks
|
|
899
|
+
// Single salt — one Argon2id derivation unlocks every key below
|
|
820
900
|
const salt = generateRandomBytes(SALT_SIZE);
|
|
821
901
|
const { blob: encryptedPrivateKey } = await MajikKey._encryptPrivateKey(exportedXPrivate, passphrase, salt);
|
|
822
902
|
const mlKemSecretKey = encIdentity.mlKemSecretKey;
|
|
823
903
|
const encryptedMlKemSecretKey = await MajikKey._encryptMlKemSecretKey(mlKemSecretKey, passphrase, salt);
|
|
824
|
-
// after the existing ML-KEM encryption block:
|
|
825
904
|
const edSecretKey = encIdentity.edSecretKey;
|
|
826
905
|
const encryptedEdSecretKey = await MajikKey._encryptSigningKey(edSecretKey, passphrase, salt);
|
|
827
906
|
const mlDsaSecretKey = encIdentity.mlDsaSecretKey;
|
|
828
907
|
const encryptedMlDsaSecretKey = await MajikKey._encryptSigningKey(mlDsaSecretKey, passphrase, salt);
|
|
829
|
-
// Bitcoin — real BIP-32/BIP-84 off the raw 64-byte BIP-39
|
|
830
|
-
// Majik's domain-separated path by default. Same salt,
|
|
831
|
-
// same pattern as ML-KEM/Ed25519/ML-DSA above.
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
908
|
+
// @experimental Bitcoin — real BIP-32/BIP-84 off the raw 64-byte BIP-39
|
|
909
|
+
// seed, using Majik's domain-separated path by default. Same salt,
|
|
910
|
+
// different IV, same pattern as ML-KEM/Ed25519/ML-DSA above. Skipped
|
|
911
|
+
// entirely when `deriveBitcoin` is false — no derivation cost paid,
|
|
912
|
+
// no key material generated.
|
|
913
|
+
let btcPublicKey;
|
|
914
|
+
let btcSecretKey;
|
|
915
|
+
let encryptedBtcSecretKey;
|
|
916
|
+
if (deriveBitcoin) {
|
|
917
|
+
const rawSeed = await mnemonicToSeed(mnemonic);
|
|
918
|
+
const btcMaterial = deriveBitcoinKeypairFromSeed(rawSeed);
|
|
919
|
+
btcPublicKey = btcMaterial.publicKey;
|
|
920
|
+
btcSecretKey = btcMaterial.privateKey;
|
|
921
|
+
encryptedBtcSecretKey = await MajikKey._encryptSigningKey(btcMaterial.privateKey, passphrase, salt);
|
|
922
|
+
}
|
|
835
923
|
return {
|
|
836
924
|
id: encIdentity.fingerprint,
|
|
837
925
|
publicKey: encIdentity.publicKey,
|
|
@@ -849,8 +937,8 @@ export class MajikKey {
|
|
|
849
937
|
mlDsaPublicKey: encIdentity.mlDsaPublicKey,
|
|
850
938
|
mlDsaSecretKey,
|
|
851
939
|
encryptedMlDsaSecretKey,
|
|
852
|
-
btcPublicKey
|
|
853
|
-
btcSecretKey
|
|
940
|
+
btcPublicKey,
|
|
941
|
+
btcSecretKey,
|
|
854
942
|
encryptedBtcSecretKey,
|
|
855
943
|
};
|
|
856
944
|
}
|
|
@@ -984,6 +1072,9 @@ export class MajikKey {
|
|
|
984
1072
|
return arrayBufferToBase64(raw);
|
|
985
1073
|
}
|
|
986
1074
|
// ── WEB3 (EXPERIMENTAL) ─────────────────────────────────────────────────────
|
|
1075
|
+
/**
|
|
1076
|
+
* @experimental
|
|
1077
|
+
*/
|
|
987
1078
|
getBtcSecretKey() {
|
|
988
1079
|
if (this.isLocked)
|
|
989
1080
|
throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
|
|
@@ -992,6 +1083,9 @@ export class MajikKey {
|
|
|
992
1083
|
return this._btcSecretKey;
|
|
993
1084
|
}
|
|
994
1085
|
// ── WEB3 (EXPERIMENTAL) — updated getter ────────────────────────────────────
|
|
1086
|
+
/**
|
|
1087
|
+
* @experimental
|
|
1088
|
+
*/
|
|
995
1089
|
get web3() {
|
|
996
1090
|
if (!this.hasSolanaKeypair)
|
|
997
1091
|
return undefined;
|
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.
|
|
5
|
+
"version": "0.3.0",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|