@majikah/majik-key 0.3.2 → 0.3.4
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 +4 -1
- package/dist/core/crypto/crypto-provider.js +15 -4
- package/dist/core/crypto/encryption-engine.d.ts +2 -2
- package/dist/core/crypto/encryption-engine.js +10 -9
- package/dist/core/types.d.ts +1 -0
- package/dist/majik-key.d.ts +4 -7
- package/dist/majik-key.js +48 -66
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
# Majik Key
|
|
2
2
|
|
|
3
|
+
[](https://doi.org/10.5281/zenodo.21339132)
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
[](https://www.thezelijah.world) 
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
|
|
8
9
|
|
|
9
10
|
**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.
|
|
10
11
|
|
|
12
|
+
[](https://doi.org/10.5281/zenodo.21339132)    [](https://opensource.org/licenses/Apache-2.0)
|
|
13
|
+
|
|
11
14
|
---
|
|
12
15
|
|
|
13
16
|
## Why Majik Key
|
|
@@ -12,6 +12,7 @@ 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
14
|
const secureGetRandomValues = crypto.getRandomValues.bind(crypto);
|
|
15
|
+
const secureFill = Uint8Array.prototype.fill;
|
|
15
16
|
export const IV_LENGTH = 12;
|
|
16
17
|
export function generateRandomBytes(len) {
|
|
17
18
|
const b = new Uint8Array(len);
|
|
@@ -134,8 +135,13 @@ async function _argon2id(input, salt, params) {
|
|
|
134
135
|
*/
|
|
135
136
|
export async function deriveKeyFromPassphraseArgon2(passphrase, salt) {
|
|
136
137
|
const pw = new TextEncoder().encode(passphrase);
|
|
137
|
-
|
|
138
|
-
|
|
138
|
+
try {
|
|
139
|
+
const result = await _argon2id(pw, salt, ARGON2_PARAMS.PASSPHRASE);
|
|
140
|
+
return result.slice();
|
|
141
|
+
}
|
|
142
|
+
finally {
|
|
143
|
+
secureFill.call(pw, 0);
|
|
144
|
+
}
|
|
139
145
|
}
|
|
140
146
|
/**
|
|
141
147
|
* Derive a 32-byte AES key from a BIP-39 mnemonic using Argon2id.
|
|
@@ -147,8 +153,13 @@ export async function deriveKeyFromPassphraseArgon2(passphrase, salt) {
|
|
|
147
153
|
*/
|
|
148
154
|
export async function deriveKeyFromMnemonicArgon2(mnemonic, salt) {
|
|
149
155
|
const m = new TextEncoder().encode(mnemonic);
|
|
150
|
-
|
|
151
|
-
|
|
156
|
+
try {
|
|
157
|
+
const result = await _argon2id(m, salt, ARGON2_PARAMS.MNEMONIC);
|
|
158
|
+
return result.slice(); // ← force a copy out of WASM memory
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
secureFill.call(m, 0);
|
|
162
|
+
}
|
|
152
163
|
}
|
|
153
164
|
// ─── KDF v1: PBKDF2-SHA256 (legacy — do not use for new operations) ───────────
|
|
154
165
|
/**
|
|
@@ -42,6 +42,13 @@ export class EncryptionEngine {
|
|
|
42
42
|
// Step 1: BIP-39 seed → 64 bytes
|
|
43
43
|
const seed = mnemonicToSeedSync(mnemonic); // returns Buffer (Node) or Uint8Array
|
|
44
44
|
const seed64 = new Uint8Array(seed); // normalize to Uint8Array
|
|
45
|
+
// Step 3: ML-KEM-768 keypair from FULL 64-byte seed (new)
|
|
46
|
+
// ml_kem768.keygen() accepts a 64-byte seed directly.
|
|
47
|
+
// seed[0..32] → lattice key matrix expansion (K-PKE keygen)
|
|
48
|
+
// seed[32..64] → implicit rejection parameter z (stored in secretKey)
|
|
49
|
+
const mlKemKeypair = deriveMlKemKeypairFromSeed(seed64);
|
|
50
|
+
const mlDsaSeedInput = concatUint8Arrays(seed64, new TextEncoder().encode(MAJIK_SIGNATURE_SEED));
|
|
51
|
+
const mlDsaSeed = hash(mlDsaSeedInput);
|
|
45
52
|
// Step 2: X25519 identity from first 32 bytes (existing path)
|
|
46
53
|
const seed32 = seed64.subarray(0, 32);
|
|
47
54
|
try {
|
|
@@ -56,12 +63,6 @@ export class EncryptionEngine {
|
|
|
56
63
|
const publicKey = { type: "public", raw: pkCurveBytes };
|
|
57
64
|
const privateKey = { type: "private", raw: skCurveBytes };
|
|
58
65
|
const fingerprint = fingerprintFromPublicRaw(pkCurveBytes);
|
|
59
|
-
// Step 3: ML-KEM-768 keypair from FULL 64-byte seed (new)
|
|
60
|
-
// ml_kem768.keygen() accepts a 64-byte seed directly.
|
|
61
|
-
// seed[0..32] → lattice key matrix expansion (K-PKE keygen)
|
|
62
|
-
// seed[32..64] → implicit rejection parameter z (stored in secretKey)
|
|
63
|
-
const mlKemKeypair = deriveMlKemKeypairFromSeed(seed64);
|
|
64
|
-
const mlDsaSeed = hash(concatUint8Arrays(seed64, new TextEncoder().encode(MAJIK_SIGNATURE_SEED))); // 32 bytes, deterministic, domain-separated
|
|
65
66
|
const mlDsaKeypair = ml_dsa87.keygen(mlDsaSeed);
|
|
66
67
|
return {
|
|
67
68
|
publicKey,
|
|
@@ -79,12 +80,12 @@ export class EncryptionEngine {
|
|
|
79
80
|
throw new CryptoError("Failed to derive identity from mnemonic", err);
|
|
80
81
|
}
|
|
81
82
|
finally {
|
|
82
|
-
// CRITICAL: Zeroize the master seed
|
|
83
83
|
secureFill.call(seed64, 0);
|
|
84
84
|
secureFill.call(seed32, 0);
|
|
85
|
-
|
|
85
|
+
secureFill.call(mlDsaSeedInput, 0);
|
|
86
|
+
secureFill.call(mlDsaSeed, 0);
|
|
87
|
+
if (seed instanceof Uint8Array)
|
|
86
88
|
secureFill.call(seed, 0);
|
|
87
|
-
}
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
91
|
/* ================================
|
package/dist/core/types.d.ts
CHANGED
package/dist/majik-key.d.ts
CHANGED
|
@@ -26,13 +26,13 @@ export interface MajikKeyIdentity {
|
|
|
26
26
|
/** Account identifier. Equal to `fingerprint` for accounts created by this library. */
|
|
27
27
|
id: string;
|
|
28
28
|
/** X25519 public key — native `CryptoKey` where WebCrypto supports it, otherwise a raw-bytes wrapper. */
|
|
29
|
-
publicKey:
|
|
29
|
+
publicKey: {
|
|
30
30
|
raw: Uint8Array;
|
|
31
31
|
};
|
|
32
32
|
/** SHA-256 fingerprint of `publicKey`. */
|
|
33
33
|
fingerprint: MajikKeyFingerprint;
|
|
34
34
|
/** X25519 private key, decrypted into memory. ⚠️ Live key material — do not log or serialize directly. */
|
|
35
|
-
privateKey:
|
|
35
|
+
privateKey: {
|
|
36
36
|
raw: Uint8Array;
|
|
37
37
|
};
|
|
38
38
|
/** AES-256-GCM-encrypted X25519 private key (IV + ciphertext), as stored at rest. */
|
|
@@ -106,7 +106,7 @@ export interface SerializedIdentity {
|
|
|
106
106
|
*/
|
|
107
107
|
export interface MajikKeyConstructorOptions {
|
|
108
108
|
id: string;
|
|
109
|
-
publicKey:
|
|
109
|
+
publicKey: {
|
|
110
110
|
raw: Uint8Array;
|
|
111
111
|
};
|
|
112
112
|
publicKeyBase64: MajikKeyAddress;
|
|
@@ -126,11 +126,9 @@ export interface MajikKeyConstructorOptions {
|
|
|
126
126
|
encryptedMlKemSecretKey?: ArrayBuffer;
|
|
127
127
|
encryptedMlKemSecretKeyBase64?: string;
|
|
128
128
|
/** Present only when constructing an already-unlocked instance. ⚠️ Live key material. */
|
|
129
|
-
privateKey?:
|
|
129
|
+
privateKey?: {
|
|
130
130
|
raw: Uint8Array;
|
|
131
131
|
};
|
|
132
|
-
/** Present only when constructing an already-unlocked instance. ⚠️ Live key material. */
|
|
133
|
-
privateKeyBase64?: string;
|
|
134
132
|
edPublicKey?: Uint8Array;
|
|
135
133
|
encryptedEdSecretKey?: ArrayBuffer;
|
|
136
134
|
encryptedEdSecretKeyBase64?: string;
|
|
@@ -192,7 +190,6 @@ export declare class MajikKey {
|
|
|
192
190
|
private _encryptedMlKemSecretKey?;
|
|
193
191
|
private _encryptedMlKemSecretKeyBase64?;
|
|
194
192
|
private _privateKey?;
|
|
195
|
-
private _privateKeyBase64?;
|
|
196
193
|
private _edPublicKey?;
|
|
197
194
|
private _edSecretKey?;
|
|
198
195
|
private _encryptedEdSecretKey?;
|
package/dist/majik-key.js
CHANGED
|
@@ -8,7 +8,7 @@ import { aesGcmDecrypt, aesGcmEncrypt, deriveKeyFromPassphraseArgon2, deriveKeyF
|
|
|
8
8
|
import { EncryptionEngine } from "./core/crypto/encryption-engine";
|
|
9
9
|
import { MajikContact } from "@majikah/majik-contact";
|
|
10
10
|
import { arrayBufferToBase64, arrayToBase64, base64ToArrayBuffer, concatUint8Arrays, utf8ToBase64, base64ToUtf8, seedStringToArray, seedArrayToString, base64ToUint8Array, } from "./core/utils";
|
|
11
|
-
import { KDF_VERSION,
|
|
11
|
+
import { KDF_VERSION, MAJIK_MNEMONIC_SALT, } from "./core/crypto/constants";
|
|
12
12
|
import { MajikKeyValidator } from "./core/validator";
|
|
13
13
|
import { MajikKeyError } from "./core/error";
|
|
14
14
|
import { MajikMessageIdentity } from "./core/database/system/identity";
|
|
@@ -57,7 +57,6 @@ export class MajikKey {
|
|
|
57
57
|
_encryptedMlKemSecretKey;
|
|
58
58
|
_encryptedMlKemSecretKeyBase64;
|
|
59
59
|
_privateKey;
|
|
60
|
-
_privateKeyBase64;
|
|
61
60
|
_edPublicKey;
|
|
62
61
|
_edSecretKey;
|
|
63
62
|
_encryptedEdSecretKey;
|
|
@@ -103,7 +102,6 @@ export class MajikKey {
|
|
|
103
102
|
this._encryptedMlKemSecretKey = options.encryptedMlKemSecretKey;
|
|
104
103
|
this._encryptedMlKemSecretKeyBase64 = options.encryptedMlKemSecretKeyBase64;
|
|
105
104
|
this._privateKey = options.privateKey;
|
|
106
|
-
this._privateKeyBase64 = options.privateKeyBase64;
|
|
107
105
|
this._edPublicKey = options.edPublicKey;
|
|
108
106
|
this._encryptedEdSecretKey = options.encryptedEdSecretKey;
|
|
109
107
|
this._encryptedEdSecretKeyBase64 = options.encryptedEdSecretKeyBase64;
|
|
@@ -285,7 +283,6 @@ export class MajikKey {
|
|
|
285
283
|
encryptedMlKemSecretKey: identity.encryptedMlKemSecretKey,
|
|
286
284
|
encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
|
|
287
285
|
privateKey: identity.privateKey,
|
|
288
|
-
privateKeyBase64,
|
|
289
286
|
edPublicKey: identity.edPublicKey,
|
|
290
287
|
encryptedEdSecretKey: identity.encryptedEdSecretKey,
|
|
291
288
|
encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
|
|
@@ -397,11 +394,11 @@ export class MajikKey {
|
|
|
397
394
|
if (!this._edSecretKey ||
|
|
398
395
|
!this._mlDsaSecretKey ||
|
|
399
396
|
!this._mlKemSecretKey ||
|
|
400
|
-
!this.
|
|
397
|
+
!this._privateKey)
|
|
401
398
|
throw new MajikKeyError("MajikKey is missing secret keys — re-import via importFromMnemonicBackup() first.");
|
|
402
399
|
return {
|
|
403
400
|
...this.toJSON(),
|
|
404
|
-
privateKeyBase64: this.
|
|
401
|
+
privateKeyBase64: arrayToBase64(this._privateKey.raw),
|
|
405
402
|
mlKemSecretKeyBase64: arrayToBase64(this._mlKemSecretKey),
|
|
406
403
|
edSecretKeyBase64: arrayToBase64(this._edSecretKey),
|
|
407
404
|
mlDsaSecretKeyBase64: arrayToBase64(this._mlDsaSecretKey),
|
|
@@ -449,7 +446,6 @@ export class MajikKey {
|
|
|
449
446
|
publicKey: { raw: base64ToUint8Array(parsed.publicKey) },
|
|
450
447
|
publicKeyBase64: parsed.publicKey,
|
|
451
448
|
privateKey: { raw: privateKeyBytes },
|
|
452
|
-
privateKeyBase64: parsed.privateKeyBase64,
|
|
453
449
|
encryptedPrivateKey: new ArrayBuffer(0),
|
|
454
450
|
encryptedPrivateKeyBase64: parsed.encryptedPrivateKey,
|
|
455
451
|
salt: parsed.salt,
|
|
@@ -482,6 +478,7 @@ export class MajikKey {
|
|
|
482
478
|
id: this._backup,
|
|
483
479
|
seed: seedStringToArray(mnemonic.trim()),
|
|
484
480
|
phrase: passphrase?.trim() || undefined,
|
|
481
|
+
language: this._mnemonicLanguage,
|
|
485
482
|
};
|
|
486
483
|
}
|
|
487
484
|
static async fromMnemonicJSON(mnemonicJson, passphrase, label, options = {
|
|
@@ -511,6 +508,8 @@ export class MajikKey {
|
|
|
511
508
|
return this;
|
|
512
509
|
}
|
|
513
510
|
async updatePassphrase(currentPassphrase, newPassphrase) {
|
|
511
|
+
if (this.isLocked)
|
|
512
|
+
throw new MajikKeyError("MajikKey must be unlocked to update passphrase");
|
|
514
513
|
MajikKeyValidator.validatePassphrase(currentPassphrase, "Current passphrase");
|
|
515
514
|
MajikKeyValidator.validatePassphrase(newPassphrase, "New passphrase");
|
|
516
515
|
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
@@ -530,6 +529,8 @@ export class MajikKey {
|
|
|
530
529
|
const encMlKem = await MajikKey._encryptMlKemSecretKey(mlKemSecretKeyBytes, newPassphrase, newSalt);
|
|
531
530
|
this._encryptedMlKemSecretKey = encMlKem;
|
|
532
531
|
this._encryptedMlKemSecretKeyBase64 = arrayBufferToBase64(encMlKem);
|
|
532
|
+
if (this._mlKemSecretKey)
|
|
533
|
+
secureFill.call(this._mlKemSecretKey, 0);
|
|
533
534
|
this._mlKemSecretKey = mlKemSecretKeyBytes;
|
|
534
535
|
}
|
|
535
536
|
if (this._encryptedEdSecretKey) {
|
|
@@ -537,6 +538,8 @@ export class MajikKey {
|
|
|
537
538
|
const encEd = await MajikKey._encryptSigningKey(edSecretKeyBytes, newPassphrase, newSalt);
|
|
538
539
|
this._encryptedEdSecretKey = encEd;
|
|
539
540
|
this._encryptedEdSecretKeyBase64 = arrayBufferToBase64(encEd);
|
|
541
|
+
if (this._edSecretKey)
|
|
542
|
+
secureFill.call(this._edSecretKey, 0);
|
|
540
543
|
this._edSecretKey = edSecretKeyBytes;
|
|
541
544
|
}
|
|
542
545
|
if (this._encryptedMlDsaSecretKey) {
|
|
@@ -544,6 +547,8 @@ export class MajikKey {
|
|
|
544
547
|
const encDsa = await MajikKey._encryptSigningKey(mlDsaSecretKeyBytes, newPassphrase, newSalt);
|
|
545
548
|
this._encryptedMlDsaSecretKey = encDsa;
|
|
546
549
|
this._encryptedMlDsaSecretKeyBase64 = arrayBufferToBase64(encDsa);
|
|
550
|
+
if (this._mlDsaSecretKey)
|
|
551
|
+
secureFill.call(this._mlDsaSecretKey, 0);
|
|
547
552
|
this._mlDsaSecretKey = mlDsaSecretKeyBytes;
|
|
548
553
|
}
|
|
549
554
|
if (this._encryptedBtcSecretKey) {
|
|
@@ -551,6 +556,8 @@ export class MajikKey {
|
|
|
551
556
|
const encBtc = await MajikKey._encryptSigningKey(btcSecretKeyBytes, newPassphrase, newSalt);
|
|
552
557
|
this._encryptedBtcSecretKey = encBtc;
|
|
553
558
|
this._encryptedBtcSecretKeyBase64 = arrayBufferToBase64(encBtc);
|
|
559
|
+
if (this._btcSecretKey)
|
|
560
|
+
secureFill.call(this._btcSecretKey, 0);
|
|
554
561
|
this._btcSecretKey = btcSecretKeyBytes;
|
|
555
562
|
}
|
|
556
563
|
return this;
|
|
@@ -571,12 +578,13 @@ export class MajikKey {
|
|
|
571
578
|
* NOTE: Does not add ML-KEM keys — use importFromMnemonicBackup() for full upgrade.
|
|
572
579
|
*/
|
|
573
580
|
async migrate(passphrase) {
|
|
581
|
+
MajikKeyValidator.validatePassphrase(passphrase);
|
|
582
|
+
if (this._kdfVersion === KDF_VERSION.ARGON2ID)
|
|
583
|
+
return this;
|
|
584
|
+
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
585
|
+
let privateKeyBuffer;
|
|
574
586
|
try {
|
|
575
|
-
|
|
576
|
-
if (this._kdfVersion === KDF_VERSION.ARGON2ID)
|
|
577
|
-
return this;
|
|
578
|
-
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
579
|
-
const privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, KDF_VERSION.PBKDF2);
|
|
587
|
+
privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, KDF_VERSION.PBKDF2);
|
|
580
588
|
const newSalt = generateRandomBytes(SALT_SIZE);
|
|
581
589
|
const { blob } = await MajikKey._encryptPrivateKey(privateKeyBuffer, passphrase, newSalt);
|
|
582
590
|
this._encryptedPrivateKey = blob;
|
|
@@ -590,6 +598,11 @@ export class MajikKey {
|
|
|
590
598
|
throw err;
|
|
591
599
|
throw new MajikKeyError("Failed to migrate MajikKey to Argon2id", err);
|
|
592
600
|
}
|
|
601
|
+
finally {
|
|
602
|
+
if (privateKeyBuffer)
|
|
603
|
+
secureFill.call(new Uint8Array(privateKeyBuffer), 0);
|
|
604
|
+
secureFill.call(salt, 0);
|
|
605
|
+
}
|
|
593
606
|
}
|
|
594
607
|
// ── LOCK / UNLOCK ────────────────────────────────────────────────────────────
|
|
595
608
|
// required
|
|
@@ -613,7 +626,6 @@ export class MajikKey {
|
|
|
613
626
|
secureFill.call(this._solanaKeypairMaterial.secretKey, 0);
|
|
614
627
|
}
|
|
615
628
|
this._privateKey = undefined;
|
|
616
|
-
this._privateKeyBase64 = undefined;
|
|
617
629
|
this._mlKemSecretKey = undefined;
|
|
618
630
|
this._edSecretKey = undefined;
|
|
619
631
|
this._mlDsaSecretKey = undefined;
|
|
@@ -628,18 +640,11 @@ export class MajikKey {
|
|
|
628
640
|
MajikKeyValidator.validatePassphrase(passphrase);
|
|
629
641
|
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
630
642
|
const privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, this._kdfVersion);
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
}
|
|
635
|
-
catch {
|
|
636
|
-
privateKey = {
|
|
637
|
-
type: "private",
|
|
638
|
-
raw: new Uint8Array(privateKeyBuffer),
|
|
639
|
-
};
|
|
640
|
-
}
|
|
643
|
+
const privateKey = {
|
|
644
|
+
type: "private",
|
|
645
|
+
raw: new Uint8Array(privateKeyBuffer),
|
|
646
|
+
};
|
|
641
647
|
this._privateKey = privateKey;
|
|
642
|
-
this._privateKeyBase64 = arrayBufferToBase64(privateKeyBuffer);
|
|
643
648
|
if (this._encryptedMlKemSecretKey) {
|
|
644
649
|
this._mlKemSecretKey = await MajikKey._decryptMlKemSecretKey(this._encryptedMlKemSecretKey, passphrase, salt);
|
|
645
650
|
}
|
|
@@ -678,7 +683,7 @@ export class MajikKey {
|
|
|
678
683
|
getPrivateKeyBase64() {
|
|
679
684
|
if (this.isLocked)
|
|
680
685
|
throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
|
|
681
|
-
return this.
|
|
686
|
+
return arrayToBase64(this._privateKey.raw);
|
|
682
687
|
}
|
|
683
688
|
getMlKemSecretKey() {
|
|
684
689
|
if (this.isLocked)
|
|
@@ -873,7 +878,6 @@ export class MajikKey {
|
|
|
873
878
|
encryptedMlKemSecretKey: identity.encryptedMlKemSecretKey,
|
|
874
879
|
encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
|
|
875
880
|
privateKey: identity.privateKey,
|
|
876
|
-
privateKeyBase64,
|
|
877
881
|
edPublicKey: identity.edPublicKey,
|
|
878
882
|
encryptedEdSecretKey: identity.encryptedEdSecretKey,
|
|
879
883
|
encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
|
|
@@ -910,19 +914,8 @@ export class MajikKey {
|
|
|
910
914
|
static async _deriveAndEncryptFromMnemonic(mnemonic, passphrase, options) {
|
|
911
915
|
const deriveBitcoin = options?.deriveBitcoin ?? true;
|
|
912
916
|
const encIdentity = await EncryptionEngine.deriveIdentityFromMnemonic(mnemonic);
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
exportedXPrivate = await crypto.subtle.exportKey("raw", encIdentity.privateKey);
|
|
916
|
-
}
|
|
917
|
-
catch {
|
|
918
|
-
const anyPriv = encIdentity.privateKey;
|
|
919
|
-
if (anyPriv?.raw instanceof Uint8Array) {
|
|
920
|
-
exportedXPrivate = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
|
|
921
|
-
}
|
|
922
|
-
else {
|
|
923
|
-
throw new MajikKeyError("Cannot export private key: unsupported format");
|
|
924
|
-
}
|
|
925
|
-
}
|
|
917
|
+
const anyPriv = encIdentity.privateKey;
|
|
918
|
+
const exportedXPrivate = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
|
|
926
919
|
// Single salt — one Argon2id derivation unlocks every key below
|
|
927
920
|
const salt = generateRandomBytes(SALT_SIZE);
|
|
928
921
|
const { blob: encryptedPrivateKey } = await MajikKey._encryptPrivateKey(exportedXPrivate, passphrase, salt);
|
|
@@ -1021,13 +1014,18 @@ export class MajikKey {
|
|
|
1021
1014
|
}
|
|
1022
1015
|
static async _decryptSigningKey(buffer, passphrase, salt) {
|
|
1023
1016
|
const keyBytes = await deriveKeyFromPassphraseArgon2(passphrase, salt);
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1017
|
+
try {
|
|
1018
|
+
const full = new Uint8Array(buffer);
|
|
1019
|
+
const iv = full.slice(0, IV_LENGTH);
|
|
1020
|
+
const ciphertext = full.slice(IV_LENGTH);
|
|
1021
|
+
const plain = aesGcmDecrypt(keyBytes, iv, ciphertext);
|
|
1022
|
+
if (!plain)
|
|
1023
|
+
throw new MajikKeyError("Failed to decrypt signing key");
|
|
1024
|
+
return plain;
|
|
1025
|
+
}
|
|
1026
|
+
finally {
|
|
1027
|
+
secureFill.call(keyBytes, 0);
|
|
1028
|
+
}
|
|
1031
1029
|
}
|
|
1032
1030
|
// ── PRIVATE: Backup ──────────────────────────────────────────────────────────
|
|
1033
1031
|
static async _verifyBackupDecryption(ivBase64, ciphertextBase64, mnemonic, backupKdfVersion) {
|
|
@@ -1053,26 +1051,10 @@ export class MajikKey {
|
|
|
1053
1051
|
static async _exportMnemonicBackup(identity, mnemonic) {
|
|
1054
1052
|
if (!identity?.privateKey)
|
|
1055
1053
|
throw new MajikKeyError("Identity must have privateKey to export backup");
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
pubRawBuf = await crypto.subtle.exportKey("raw", identity.publicKey);
|
|
1061
|
-
}
|
|
1062
|
-
catch {
|
|
1063
|
-
const anyPriv = identity.privateKey;
|
|
1064
|
-
const anyPub = identity.publicKey;
|
|
1065
|
-
if (anyPriv?.raw instanceof Uint8Array) {
|
|
1066
|
-
privRawBuf = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
|
|
1067
|
-
}
|
|
1068
|
-
else
|
|
1069
|
-
throw new MajikKeyError("Cannot export private key");
|
|
1070
|
-
if (anyPub?.raw instanceof Uint8Array) {
|
|
1071
|
-
pubRawBuf = anyPub.raw.buffer.slice(anyPub.raw.byteOffset, anyPub.raw.byteOffset + anyPub.raw.byteLength);
|
|
1072
|
-
}
|
|
1073
|
-
else
|
|
1074
|
-
throw new MajikKeyError("Cannot export public key");
|
|
1075
|
-
}
|
|
1054
|
+
const anyPriv = identity.privateKey;
|
|
1055
|
+
const anyPub = identity.publicKey;
|
|
1056
|
+
const privRawBuf = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
|
|
1057
|
+
const pubRawBuf = anyPub.raw.buffer.slice(anyPub.raw.byteOffset, anyPub.raw.byteOffset + anyPub.raw.byteLength);
|
|
1076
1058
|
const mnemonicSalt = new TextEncoder().encode(MAJIK_MNEMONIC_SALT);
|
|
1077
1059
|
const keyBytes = await deriveKeyFromMnemonicArgon2(mnemonic, mnemonicSalt);
|
|
1078
1060
|
const iv = generateRandomBytes(IV_LENGTH);
|
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.
|
|
5
|
+
"version": "0.3.4",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|
|
@@ -75,9 +75,9 @@
|
|
|
75
75
|
"@scure/btc-signer": "^2.2.0",
|
|
76
76
|
"@solana/kit": "^7.0.0",
|
|
77
77
|
"@types/ed2curve": "^0.2.4",
|
|
78
|
-
"@types/node": "^26.1.
|
|
79
|
-
"typescript": "^
|
|
80
|
-
"vitest": "^4.1.
|
|
78
|
+
"@types/node": "^26.1.1",
|
|
79
|
+
"typescript": "^7.0.2",
|
|
80
|
+
"vitest": "^4.1.10"
|
|
81
81
|
},
|
|
82
82
|
"peerDependencies": {
|
|
83
83
|
"@scure/btc-signer": "^2.2.0",
|