@majikah/majik-key 0.3.2 → 0.3.3
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.
|
@@ -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/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,
|
|
@@ -511,6 +507,8 @@ export class MajikKey {
|
|
|
511
507
|
return this;
|
|
512
508
|
}
|
|
513
509
|
async updatePassphrase(currentPassphrase, newPassphrase) {
|
|
510
|
+
if (this.isLocked)
|
|
511
|
+
throw new MajikKeyError("MajikKey must be unlocked to update passphrase");
|
|
514
512
|
MajikKeyValidator.validatePassphrase(currentPassphrase, "Current passphrase");
|
|
515
513
|
MajikKeyValidator.validatePassphrase(newPassphrase, "New passphrase");
|
|
516
514
|
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
@@ -530,6 +528,8 @@ export class MajikKey {
|
|
|
530
528
|
const encMlKem = await MajikKey._encryptMlKemSecretKey(mlKemSecretKeyBytes, newPassphrase, newSalt);
|
|
531
529
|
this._encryptedMlKemSecretKey = encMlKem;
|
|
532
530
|
this._encryptedMlKemSecretKeyBase64 = arrayBufferToBase64(encMlKem);
|
|
531
|
+
if (this._mlKemSecretKey)
|
|
532
|
+
secureFill.call(this._mlKemSecretKey, 0);
|
|
533
533
|
this._mlKemSecretKey = mlKemSecretKeyBytes;
|
|
534
534
|
}
|
|
535
535
|
if (this._encryptedEdSecretKey) {
|
|
@@ -537,6 +537,8 @@ export class MajikKey {
|
|
|
537
537
|
const encEd = await MajikKey._encryptSigningKey(edSecretKeyBytes, newPassphrase, newSalt);
|
|
538
538
|
this._encryptedEdSecretKey = encEd;
|
|
539
539
|
this._encryptedEdSecretKeyBase64 = arrayBufferToBase64(encEd);
|
|
540
|
+
if (this._edSecretKey)
|
|
541
|
+
secureFill.call(this._edSecretKey, 0);
|
|
540
542
|
this._edSecretKey = edSecretKeyBytes;
|
|
541
543
|
}
|
|
542
544
|
if (this._encryptedMlDsaSecretKey) {
|
|
@@ -544,6 +546,8 @@ export class MajikKey {
|
|
|
544
546
|
const encDsa = await MajikKey._encryptSigningKey(mlDsaSecretKeyBytes, newPassphrase, newSalt);
|
|
545
547
|
this._encryptedMlDsaSecretKey = encDsa;
|
|
546
548
|
this._encryptedMlDsaSecretKeyBase64 = arrayBufferToBase64(encDsa);
|
|
549
|
+
if (this._mlDsaSecretKey)
|
|
550
|
+
secureFill.call(this._mlDsaSecretKey, 0);
|
|
547
551
|
this._mlDsaSecretKey = mlDsaSecretKeyBytes;
|
|
548
552
|
}
|
|
549
553
|
if (this._encryptedBtcSecretKey) {
|
|
@@ -551,6 +555,8 @@ export class MajikKey {
|
|
|
551
555
|
const encBtc = await MajikKey._encryptSigningKey(btcSecretKeyBytes, newPassphrase, newSalt);
|
|
552
556
|
this._encryptedBtcSecretKey = encBtc;
|
|
553
557
|
this._encryptedBtcSecretKeyBase64 = arrayBufferToBase64(encBtc);
|
|
558
|
+
if (this._btcSecretKey)
|
|
559
|
+
secureFill.call(this._btcSecretKey, 0);
|
|
554
560
|
this._btcSecretKey = btcSecretKeyBytes;
|
|
555
561
|
}
|
|
556
562
|
return this;
|
|
@@ -571,12 +577,13 @@ export class MajikKey {
|
|
|
571
577
|
* NOTE: Does not add ML-KEM keys — use importFromMnemonicBackup() for full upgrade.
|
|
572
578
|
*/
|
|
573
579
|
async migrate(passphrase) {
|
|
580
|
+
MajikKeyValidator.validatePassphrase(passphrase);
|
|
581
|
+
if (this._kdfVersion === KDF_VERSION.ARGON2ID)
|
|
582
|
+
return this;
|
|
583
|
+
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
584
|
+
let privateKeyBuffer;
|
|
574
585
|
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);
|
|
586
|
+
privateKeyBuffer = await MajikKey._decryptPrivateKey(this._encryptedPrivateKey, passphrase, salt, KDF_VERSION.PBKDF2);
|
|
580
587
|
const newSalt = generateRandomBytes(SALT_SIZE);
|
|
581
588
|
const { blob } = await MajikKey._encryptPrivateKey(privateKeyBuffer, passphrase, newSalt);
|
|
582
589
|
this._encryptedPrivateKey = blob;
|
|
@@ -590,6 +597,11 @@ export class MajikKey {
|
|
|
590
597
|
throw err;
|
|
591
598
|
throw new MajikKeyError("Failed to migrate MajikKey to Argon2id", err);
|
|
592
599
|
}
|
|
600
|
+
finally {
|
|
601
|
+
if (privateKeyBuffer)
|
|
602
|
+
secureFill.call(new Uint8Array(privateKeyBuffer), 0);
|
|
603
|
+
secureFill.call(salt, 0);
|
|
604
|
+
}
|
|
593
605
|
}
|
|
594
606
|
// ── LOCK / UNLOCK ────────────────────────────────────────────────────────────
|
|
595
607
|
// required
|
|
@@ -613,7 +625,6 @@ export class MajikKey {
|
|
|
613
625
|
secureFill.call(this._solanaKeypairMaterial.secretKey, 0);
|
|
614
626
|
}
|
|
615
627
|
this._privateKey = undefined;
|
|
616
|
-
this._privateKeyBase64 = undefined;
|
|
617
628
|
this._mlKemSecretKey = undefined;
|
|
618
629
|
this._edSecretKey = undefined;
|
|
619
630
|
this._mlDsaSecretKey = undefined;
|
|
@@ -628,18 +639,11 @@ export class MajikKey {
|
|
|
628
639
|
MajikKeyValidator.validatePassphrase(passphrase);
|
|
629
640
|
const salt = new Uint8Array(base64ToArrayBuffer(this._salt));
|
|
630
641
|
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
|
-
}
|
|
642
|
+
const privateKey = {
|
|
643
|
+
type: "private",
|
|
644
|
+
raw: new Uint8Array(privateKeyBuffer),
|
|
645
|
+
};
|
|
641
646
|
this._privateKey = privateKey;
|
|
642
|
-
this._privateKeyBase64 = arrayBufferToBase64(privateKeyBuffer);
|
|
643
647
|
if (this._encryptedMlKemSecretKey) {
|
|
644
648
|
this._mlKemSecretKey = await MajikKey._decryptMlKemSecretKey(this._encryptedMlKemSecretKey, passphrase, salt);
|
|
645
649
|
}
|
|
@@ -678,7 +682,7 @@ export class MajikKey {
|
|
|
678
682
|
getPrivateKeyBase64() {
|
|
679
683
|
if (this.isLocked)
|
|
680
684
|
throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
|
|
681
|
-
return this.
|
|
685
|
+
return arrayToBase64(this._privateKey.raw);
|
|
682
686
|
}
|
|
683
687
|
getMlKemSecretKey() {
|
|
684
688
|
if (this.isLocked)
|
|
@@ -873,7 +877,6 @@ export class MajikKey {
|
|
|
873
877
|
encryptedMlKemSecretKey: identity.encryptedMlKemSecretKey,
|
|
874
878
|
encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
|
|
875
879
|
privateKey: identity.privateKey,
|
|
876
|
-
privateKeyBase64,
|
|
877
880
|
edPublicKey: identity.edPublicKey,
|
|
878
881
|
encryptedEdSecretKey: identity.encryptedEdSecretKey,
|
|
879
882
|
encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
|
|
@@ -910,19 +913,8 @@ export class MajikKey {
|
|
|
910
913
|
static async _deriveAndEncryptFromMnemonic(mnemonic, passphrase, options) {
|
|
911
914
|
const deriveBitcoin = options?.deriveBitcoin ?? true;
|
|
912
915
|
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
|
-
}
|
|
916
|
+
const anyPriv = encIdentity.privateKey;
|
|
917
|
+
const exportedXPrivate = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
|
|
926
918
|
// Single salt — one Argon2id derivation unlocks every key below
|
|
927
919
|
const salt = generateRandomBytes(SALT_SIZE);
|
|
928
920
|
const { blob: encryptedPrivateKey } = await MajikKey._encryptPrivateKey(exportedXPrivate, passphrase, salt);
|
|
@@ -1021,13 +1013,18 @@ export class MajikKey {
|
|
|
1021
1013
|
}
|
|
1022
1014
|
static async _decryptSigningKey(buffer, passphrase, salt) {
|
|
1023
1015
|
const keyBytes = await deriveKeyFromPassphraseArgon2(passphrase, salt);
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1016
|
+
try {
|
|
1017
|
+
const full = new Uint8Array(buffer);
|
|
1018
|
+
const iv = full.slice(0, IV_LENGTH);
|
|
1019
|
+
const ciphertext = full.slice(IV_LENGTH);
|
|
1020
|
+
const plain = aesGcmDecrypt(keyBytes, iv, ciphertext);
|
|
1021
|
+
if (!plain)
|
|
1022
|
+
throw new MajikKeyError("Failed to decrypt signing key");
|
|
1023
|
+
return plain;
|
|
1024
|
+
}
|
|
1025
|
+
finally {
|
|
1026
|
+
secureFill.call(keyBytes, 0);
|
|
1027
|
+
}
|
|
1031
1028
|
}
|
|
1032
1029
|
// ── PRIVATE: Backup ──────────────────────────────────────────────────────────
|
|
1033
1030
|
static async _verifyBackupDecryption(ivBase64, ciphertextBase64, mnemonic, backupKdfVersion) {
|
|
@@ -1053,26 +1050,10 @@ export class MajikKey {
|
|
|
1053
1050
|
static async _exportMnemonicBackup(identity, mnemonic) {
|
|
1054
1051
|
if (!identity?.privateKey)
|
|
1055
1052
|
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
|
-
}
|
|
1053
|
+
const anyPriv = identity.privateKey;
|
|
1054
|
+
const anyPub = identity.publicKey;
|
|
1055
|
+
const privRawBuf = anyPriv.raw.buffer.slice(anyPriv.raw.byteOffset, anyPriv.raw.byteOffset + anyPriv.raw.byteLength);
|
|
1056
|
+
const pubRawBuf = anyPub.raw.buffer.slice(anyPub.raw.byteOffset, anyPub.raw.byteOffset + anyPub.raw.byteLength);
|
|
1076
1057
|
const mnemonicSalt = new TextEncoder().encode(MAJIK_MNEMONIC_SALT);
|
|
1077
1058
|
const keyBytes = await deriveKeyFromMnemonicArgon2(mnemonic, mnemonicSalt);
|
|
1078
1059
|
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.3",
|
|
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",
|