@majikah/majik-key 0.1.9 → 0.2.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/dist/core/crypto/constants.d.ts +2 -17
- package/dist/core/crypto/constants.js +3 -18
- package/dist/core/crypto/crypto-provider.js +10 -8
- package/dist/core/crypto/encryption-engine.d.ts +4 -0
- package/dist/core/crypto/encryption-engine.js +10 -0
- package/dist/core/types.d.ts +4 -0
- package/dist/majik-key.d.ts +30 -0
- package/dist/majik-key.js +131 -0
- package/package.json +1 -1
|
@@ -4,6 +4,7 @@ export declare const KEY_ALGO: {
|
|
|
4
4
|
};
|
|
5
5
|
export declare const MAJIK_SALT = "MajikMessageSalt";
|
|
6
6
|
export declare const MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
|
|
7
|
+
export declare const MAJIK_SIGNATURE_SEED = "MajikSignatureSeedDSA";
|
|
7
8
|
/**
|
|
8
9
|
* KDF version identifiers.
|
|
9
10
|
* Stored alongside every encrypted private key blob so the correct
|
|
@@ -18,26 +19,10 @@ export type KDF_VERSION = (typeof KDF_VERSION)[keyof typeof KDF_VERSION];
|
|
|
18
19
|
* Argon2id parameters.
|
|
19
20
|
*
|
|
20
21
|
* PASSPHRASE (protecting the private key at rest):
|
|
21
|
-
* m=131072 (
|
|
22
|
+
* m=131072 (64 MB) — double OWASP "high security" tier (64 MB)
|
|
22
23
|
* t=4 — 4 passes
|
|
23
24
|
* p=4 — 4 parallel lanes
|
|
24
25
|
*
|
|
25
|
-
* Benchmark targets (approximate):
|
|
26
|
-
* Modern laptop (2020+): ~600–900ms ✓
|
|
27
|
-
* Mid-range desktop (2018): ~800–1200ms ✓
|
|
28
|
-
* Low-end / older machine: ~1500–2500ms — acceptable for a one-time unlock
|
|
29
|
-
* RTX 4090 brute-force attack: ~1–3 guesses/sec vs ~400,000/sec for PBKDF2
|
|
30
|
-
* Improvement over current PBKDF2: ~100,000–400,000×
|
|
31
|
-
*
|
|
32
|
-
* MNEMONIC BACKUP (protecting exported backup files):
|
|
33
|
-
* m=65536 (64 MB) — lower because the mnemonic itself is 128-bit entropy;
|
|
34
|
-
* the KDF is a domain separator, not a weak-password defense.
|
|
35
|
-
* t=3
|
|
36
|
-
* p=2
|
|
37
|
-
*
|
|
38
|
-
* If benchmarks on your lowest-spec target device exceed 3s for the passphrase
|
|
39
|
-
* parameters, reduce m to 65536 (64 MB) and t to 3. That is still ~50,000×
|
|
40
|
-
* harder than the current PBKDF2 setup.
|
|
41
26
|
*/
|
|
42
27
|
export declare const ARGON2_PARAMS: {
|
|
43
28
|
readonly PASSPHRASE: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export const KEY_ALGO = { name: "ECDH", namedCurve: "X25519" };
|
|
2
2
|
export const MAJIK_SALT = "MajikMessageSalt";
|
|
3
3
|
export const MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
|
|
4
|
+
export const MAJIK_SIGNATURE_SEED = "MajikSignatureSeedDSA";
|
|
4
5
|
/**
|
|
5
6
|
* KDF version identifiers.
|
|
6
7
|
* Stored alongside every encrypted private key blob so the correct
|
|
@@ -14,30 +15,14 @@ export const KDF_VERSION = {
|
|
|
14
15
|
* Argon2id parameters.
|
|
15
16
|
*
|
|
16
17
|
* PASSPHRASE (protecting the private key at rest):
|
|
17
|
-
* m=131072 (
|
|
18
|
+
* m=131072 (64 MB) — double OWASP "high security" tier (64 MB)
|
|
18
19
|
* t=4 — 4 passes
|
|
19
20
|
* p=4 — 4 parallel lanes
|
|
20
21
|
*
|
|
21
|
-
* Benchmark targets (approximate):
|
|
22
|
-
* Modern laptop (2020+): ~600–900ms ✓
|
|
23
|
-
* Mid-range desktop (2018): ~800–1200ms ✓
|
|
24
|
-
* Low-end / older machine: ~1500–2500ms — acceptable for a one-time unlock
|
|
25
|
-
* RTX 4090 brute-force attack: ~1–3 guesses/sec vs ~400,000/sec for PBKDF2
|
|
26
|
-
* Improvement over current PBKDF2: ~100,000–400,000×
|
|
27
|
-
*
|
|
28
|
-
* MNEMONIC BACKUP (protecting exported backup files):
|
|
29
|
-
* m=65536 (64 MB) — lower because the mnemonic itself is 128-bit entropy;
|
|
30
|
-
* the KDF is a domain separator, not a weak-password defense.
|
|
31
|
-
* t=3
|
|
32
|
-
* p=2
|
|
33
|
-
*
|
|
34
|
-
* If benchmarks on your lowest-spec target device exceed 3s for the passphrase
|
|
35
|
-
* parameters, reduce m to 65536 (64 MB) and t to 3. That is still ~50,000×
|
|
36
|
-
* harder than the current PBKDF2 setup.
|
|
37
22
|
*/
|
|
38
23
|
export const ARGON2_PARAMS = {
|
|
39
24
|
PASSPHRASE: {
|
|
40
|
-
m: 65536, // memory in KB (
|
|
25
|
+
m: 65536, // memory in KB (64 MB)
|
|
41
26
|
t: 3, // time cost (passes)
|
|
42
27
|
p: 4, // parallelism (lanes)
|
|
43
28
|
dkLen: 32, // output length in bytes (256-bit AES key)
|
|
@@ -10,6 +10,7 @@ import { arrayToBase64 } from "../utils";
|
|
|
10
10
|
import { argon2id as nobleArgon2id } from "@noble/hashes/argon2.js";
|
|
11
11
|
import { ARGON2_PARAMS } from "./constants";
|
|
12
12
|
import { ml_kem768 } from "@noble/post-quantum/ml-kem.js";
|
|
13
|
+
import { argon2id as hashWasmArgon2id } from "hash-wasm";
|
|
13
14
|
export const IV_LENGTH = 12;
|
|
14
15
|
export function generateRandomBytes(len) {
|
|
15
16
|
const b = new Uint8Array(len);
|
|
@@ -60,10 +61,12 @@ export function aesGcmDecrypt(keyBytes, iv, ciphertext) {
|
|
|
60
61
|
*/
|
|
61
62
|
let _wasmAvailable = null;
|
|
62
63
|
async function _probeWasm() {
|
|
64
|
+
// Never run WASM probe on server — KDF only runs client-side
|
|
65
|
+
if (typeof window === "undefined")
|
|
66
|
+
return false;
|
|
63
67
|
try {
|
|
64
|
-
const { argon2id } = await import("hash-wasm");
|
|
65
68
|
// Cheap smoke-test: tiny params, just proves the WASM module loads & runs
|
|
66
|
-
await
|
|
69
|
+
await hashWasmArgon2id({
|
|
67
70
|
password: new Uint8Array(4),
|
|
68
71
|
salt: new Uint8Array(8),
|
|
69
72
|
memorySize: 8,
|
|
@@ -79,14 +82,13 @@ async function _probeWasm() {
|
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
async function _argon2idWasm(input, salt, params) {
|
|
82
|
-
|
|
83
|
-
return argon2id({
|
|
85
|
+
return hashWasmArgon2id({
|
|
84
86
|
password: input,
|
|
85
87
|
salt,
|
|
86
|
-
memorySize: params.m,
|
|
87
|
-
iterations: params.t,
|
|
88
|
-
parallelism: params.p,
|
|
89
|
-
hashLength: params.dkLen,
|
|
88
|
+
memorySize: params.m,
|
|
89
|
+
iterations: params.t,
|
|
90
|
+
parallelism: params.p,
|
|
91
|
+
hashLength: params.dkLen,
|
|
90
92
|
outputType: "binary",
|
|
91
93
|
});
|
|
92
94
|
}
|
|
@@ -8,6 +8,10 @@ export interface EncryptionIdentity {
|
|
|
8
8
|
fingerprint: string;
|
|
9
9
|
mlKemPublicKey: Uint8Array;
|
|
10
10
|
mlKemSecretKey?: Uint8Array;
|
|
11
|
+
edPublicKey: Uint8Array;
|
|
12
|
+
edSecretKey: Uint8Array;
|
|
13
|
+
mlDsaPublicKey: Uint8Array;
|
|
14
|
+
mlDsaSecretKey: Uint8Array;
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
13
17
|
* EncryptionEngine
|
|
@@ -2,7 +2,11 @@
|
|
|
2
2
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
3
3
|
import * as ed25519 from "@stablelib/ed25519";
|
|
4
4
|
import ed2curve from "ed2curve";
|
|
5
|
+
import { ml_dsa87 } from "@noble/post-quantum/ml-dsa.js";
|
|
5
6
|
import { deriveMlKemKeypairFromSeed, fingerprintFromPublicRaw, } from "./crypto-provider";
|
|
7
|
+
import { concatUint8Arrays } from "../utils";
|
|
8
|
+
import { hash } from "@stablelib/sha256";
|
|
9
|
+
import { MAJIK_SIGNATURE_SEED } from "./constants";
|
|
6
10
|
/**
|
|
7
11
|
* EncryptionEngine
|
|
8
12
|
* ----------------
|
|
@@ -56,12 +60,18 @@ export class EncryptionEngine {
|
|
|
56
60
|
// seed[0..32] → lattice key matrix expansion (K-PKE keygen)
|
|
57
61
|
// seed[32..64] → implicit rejection parameter z (stored in secretKey)
|
|
58
62
|
const mlKemKeypair = deriveMlKemKeypairFromSeed(seed64);
|
|
63
|
+
const mlDsaSeed = hash(concatUint8Arrays(seed64, new TextEncoder().encode(MAJIK_SIGNATURE_SEED))); // 32 bytes, deterministic, domain-separated
|
|
64
|
+
const mlDsaKeypair = ml_dsa87.keygen(mlDsaSeed);
|
|
59
65
|
return {
|
|
60
66
|
publicKey,
|
|
61
67
|
privateKey,
|
|
62
68
|
fingerprint,
|
|
63
69
|
mlKemPublicKey: mlKemKeypair.publicKey, // 1184 bytes
|
|
64
70
|
mlKemSecretKey: mlKemKeypair.secretKey, // 2400 bytes
|
|
71
|
+
edPublicKey: ed.publicKey, // 32 bytes
|
|
72
|
+
edSecretKey: ed.secretKey, // 64 bytes
|
|
73
|
+
mlDsaPublicKey: mlDsaKeypair.publicKey, // 2592 bytes
|
|
74
|
+
mlDsaSecretKey: mlDsaKeypair.secretKey, // 4896 bytes
|
|
65
75
|
};
|
|
66
76
|
}
|
|
67
77
|
catch (err) {
|
package/dist/core/types.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export interface MajikKeyJSON {
|
|
|
14
14
|
kdfVersion?: number;
|
|
15
15
|
mlKemPublicKey?: string;
|
|
16
16
|
encryptedMlKemSecretKey?: string;
|
|
17
|
+
edPublicKey?: string;
|
|
18
|
+
encryptedEdSecretKey?: string;
|
|
19
|
+
mlDsaPublicKey?: string;
|
|
20
|
+
encryptedMlDsaSecretKey?: string;
|
|
17
21
|
}
|
|
18
22
|
export interface MajikKeyMetadata {
|
|
19
23
|
id: string;
|
package/dist/majik-key.d.ts
CHANGED
|
@@ -40,7 +40,16 @@ export interface MajikKeyIdentity {
|
|
|
40
40
|
kdfVersion: KDF_VERSION;
|
|
41
41
|
mlKemPublicKey: Uint8Array;
|
|
42
42
|
mlKemSecretKey?: Uint8Array;
|
|
43
|
+
edPublicKey?: Uint8Array;
|
|
44
|
+
edSecretKey?: Uint8Array;
|
|
45
|
+
mlDsaPublicKey?: Uint8Array;
|
|
46
|
+
mlDsaSecretKey?: Uint8Array;
|
|
43
47
|
}
|
|
48
|
+
export type MajikKeyDerivedIdentity = MajikKeyIdentity & {
|
|
49
|
+
encryptedMlKemSecretKey: ArrayBuffer;
|
|
50
|
+
encryptedEdSecretKey: ArrayBuffer;
|
|
51
|
+
encryptedMlDsaSecretKey: ArrayBuffer;
|
|
52
|
+
};
|
|
44
53
|
export interface SerializedIdentity {
|
|
45
54
|
id: string;
|
|
46
55
|
publicKey: string;
|
|
@@ -70,6 +79,12 @@ export interface MajikKeyConstructorOptions {
|
|
|
70
79
|
raw: Uint8Array;
|
|
71
80
|
};
|
|
72
81
|
privateKeyBase64?: string;
|
|
82
|
+
edPublicKey?: Uint8Array;
|
|
83
|
+
encryptedEdSecretKey?: ArrayBuffer;
|
|
84
|
+
encryptedEdSecretKeyBase64?: string;
|
|
85
|
+
mlDsaPublicKey?: Uint8Array;
|
|
86
|
+
encryptedMlDsaSecretKey?: ArrayBuffer;
|
|
87
|
+
encryptedMlDsaSecretKeyBase64?: string;
|
|
73
88
|
}
|
|
74
89
|
export declare class MajikKey {
|
|
75
90
|
private readonly _id;
|
|
@@ -89,6 +104,14 @@ export declare class MajikKey {
|
|
|
89
104
|
private _encryptedMlKemSecretKeyBase64?;
|
|
90
105
|
private _privateKey?;
|
|
91
106
|
private _privateKeyBase64?;
|
|
107
|
+
private _edPublicKey?;
|
|
108
|
+
private _edSecretKey?;
|
|
109
|
+
private _encryptedEdSecretKey?;
|
|
110
|
+
private _encryptedEdSecretKeyBase64?;
|
|
111
|
+
private _mlDsaPublicKey?;
|
|
112
|
+
private _mlDsaSecretKey?;
|
|
113
|
+
private _encryptedMlDsaSecretKey?;
|
|
114
|
+
private _encryptedMlDsaSecretKeyBase64?;
|
|
92
115
|
private constructor();
|
|
93
116
|
get id(): string;
|
|
94
117
|
get fingerprint(): string;
|
|
@@ -108,6 +131,9 @@ export declare class MajikKey {
|
|
|
108
131
|
get hasMlKem(): boolean;
|
|
109
132
|
get isFullyUpgraded(): boolean;
|
|
110
133
|
get metadata(): MajikKeyMetadata;
|
|
134
|
+
get edPublicKey(): Uint8Array | undefined;
|
|
135
|
+
get mlDsaPublicKey(): Uint8Array | undefined;
|
|
136
|
+
get hasSigningKeys(): boolean;
|
|
111
137
|
static create(mnemonic: string, passphrase: string, label?: string): Promise<MajikKey>;
|
|
112
138
|
static fromJSON(json: MajikKeyJSON | string): MajikKey;
|
|
113
139
|
toMnemonicJSON(mnemonic: string, passphrase?: string): MnemonicJSON;
|
|
@@ -127,6 +153,8 @@ export declare class MajikKey {
|
|
|
127
153
|
};
|
|
128
154
|
getPrivateKeyBase64(): string;
|
|
129
155
|
getMlKemSecretKey(): Uint8Array;
|
|
156
|
+
getEdSecretKey(): Uint8Array;
|
|
157
|
+
getMlDsaSecretKey(): Uint8Array;
|
|
130
158
|
toJSON(): MajikKeyJSON;
|
|
131
159
|
toString(pretty?: boolean): string;
|
|
132
160
|
static generateMnemonic(strength?: 128 | 256): string;
|
|
@@ -160,8 +188,10 @@ export declare class MajikKey {
|
|
|
160
188
|
* computation → two independently encrypted blobs.
|
|
161
189
|
*/
|
|
162
190
|
private static _encryptMlKemSecretKey;
|
|
191
|
+
private static _encryptSigningKey;
|
|
163
192
|
private static _decryptPrivateKey;
|
|
164
193
|
private static _decryptMlKemSecretKey;
|
|
194
|
+
private static _decryptSigningKey;
|
|
165
195
|
private static _verifyBackupDecryption;
|
|
166
196
|
private static _exportMnemonicBackup;
|
|
167
197
|
private static _deriveLegacyMnemonicKey;
|
package/dist/majik-key.js
CHANGED
|
@@ -51,6 +51,14 @@ export class MajikKey {
|
|
|
51
51
|
_encryptedMlKemSecretKeyBase64;
|
|
52
52
|
_privateKey;
|
|
53
53
|
_privateKeyBase64;
|
|
54
|
+
_edPublicKey;
|
|
55
|
+
_edSecretKey;
|
|
56
|
+
_encryptedEdSecretKey;
|
|
57
|
+
_encryptedEdSecretKeyBase64;
|
|
58
|
+
_mlDsaPublicKey;
|
|
59
|
+
_mlDsaSecretKey;
|
|
60
|
+
_encryptedMlDsaSecretKey;
|
|
61
|
+
_encryptedMlDsaSecretKeyBase64;
|
|
54
62
|
constructor(options) {
|
|
55
63
|
this._id = options.id;
|
|
56
64
|
this._publicKey = options.publicKey;
|
|
@@ -69,6 +77,12 @@ export class MajikKey {
|
|
|
69
77
|
this._encryptedMlKemSecretKeyBase64 = options.encryptedMlKemSecretKeyBase64;
|
|
70
78
|
this._privateKey = options.privateKey;
|
|
71
79
|
this._privateKeyBase64 = options.privateKeyBase64;
|
|
80
|
+
this._edPublicKey = options.edPublicKey;
|
|
81
|
+
this._encryptedEdSecretKey = options.encryptedEdSecretKey;
|
|
82
|
+
this._encryptedEdSecretKeyBase64 = options.encryptedEdSecretKeyBase64;
|
|
83
|
+
this._mlDsaPublicKey = options.mlDsaPublicKey;
|
|
84
|
+
this._encryptedMlDsaSecretKey = options.encryptedMlDsaSecretKey;
|
|
85
|
+
this._encryptedMlDsaSecretKeyBase64 = options.encryptedMlDsaSecretKeyBase64;
|
|
72
86
|
}
|
|
73
87
|
// ── Getters ─────────────────────────────────────────────────────────────────
|
|
74
88
|
get id() {
|
|
@@ -127,6 +141,15 @@ export class MajikKey {
|
|
|
127
141
|
hasMlKem: this.hasMlKem,
|
|
128
142
|
};
|
|
129
143
|
}
|
|
144
|
+
get edPublicKey() {
|
|
145
|
+
return this._edPublicKey;
|
|
146
|
+
}
|
|
147
|
+
get mlDsaPublicKey() {
|
|
148
|
+
return this._mlDsaPublicKey;
|
|
149
|
+
}
|
|
150
|
+
get hasSigningKeys() {
|
|
151
|
+
return (this._edPublicKey !== undefined && this._mlDsaPublicKey !== undefined);
|
|
152
|
+
}
|
|
130
153
|
// ── CREATE ──────────────────────────────────────────────────────────────────
|
|
131
154
|
static async create(mnemonic, passphrase, label) {
|
|
132
155
|
try {
|
|
@@ -155,6 +178,12 @@ export class MajikKey {
|
|
|
155
178
|
encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
|
|
156
179
|
privateKey: identity.privateKey,
|
|
157
180
|
privateKeyBase64,
|
|
181
|
+
edPublicKey: identity.edPublicKey,
|
|
182
|
+
encryptedEdSecretKey: identity.encryptedEdSecretKey,
|
|
183
|
+
encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
|
|
184
|
+
mlDsaPublicKey: identity.mlDsaPublicKey,
|
|
185
|
+
encryptedMlDsaSecretKey: identity.encryptedMlDsaSecretKey,
|
|
186
|
+
encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
|
|
158
187
|
});
|
|
159
188
|
}
|
|
160
189
|
catch (err) {
|
|
@@ -178,6 +207,24 @@ export class MajikKey {
|
|
|
178
207
|
encryptedMlKemSecretKeyBase64 = anyParsed.encryptedMlKemSecretKey;
|
|
179
208
|
encryptedMlKemSecretKey = base64ToArrayBuffer(anyParsed.encryptedMlKemSecretKey);
|
|
180
209
|
}
|
|
210
|
+
const edPublicKey = anyParsed.edPublicKey
|
|
211
|
+
? base64ToUint8Array(anyParsed.edPublicKey)
|
|
212
|
+
: undefined;
|
|
213
|
+
let encryptedEdSecretKey;
|
|
214
|
+
let encryptedEdSecretKeyBase64;
|
|
215
|
+
if (anyParsed.encryptedEdSecretKey) {
|
|
216
|
+
encryptedEdSecretKeyBase64 = anyParsed.encryptedEdSecretKey;
|
|
217
|
+
encryptedEdSecretKey = base64ToArrayBuffer(anyParsed.encryptedEdSecretKey);
|
|
218
|
+
}
|
|
219
|
+
const mlDsaPublicKey = anyParsed.mlDsaPublicKey
|
|
220
|
+
? base64ToUint8Array(anyParsed.mlDsaPublicKey)
|
|
221
|
+
: undefined;
|
|
222
|
+
let encryptedMlDsaSecretKey;
|
|
223
|
+
let encryptedMlDsaSecretKeyBase64;
|
|
224
|
+
if (anyParsed.encryptedMlDsaSecretKey) {
|
|
225
|
+
encryptedMlDsaSecretKeyBase64 = anyParsed.encryptedMlDsaSecretKey;
|
|
226
|
+
encryptedMlDsaSecretKey = base64ToArrayBuffer(anyParsed.encryptedMlDsaSecretKey);
|
|
227
|
+
}
|
|
181
228
|
return new MajikKey({
|
|
182
229
|
id: validated.id,
|
|
183
230
|
publicKey: { raw: new Uint8Array(publicKeyBuffer) },
|
|
@@ -194,6 +241,12 @@ export class MajikKey {
|
|
|
194
241
|
mlKemPublicKey,
|
|
195
242
|
encryptedMlKemSecretKey,
|
|
196
243
|
encryptedMlKemSecretKeyBase64,
|
|
244
|
+
edPublicKey,
|
|
245
|
+
encryptedEdSecretKey,
|
|
246
|
+
encryptedEdSecretKeyBase64,
|
|
247
|
+
mlDsaPublicKey,
|
|
248
|
+
encryptedMlDsaSecretKey,
|
|
249
|
+
encryptedMlDsaSecretKeyBase64,
|
|
197
250
|
});
|
|
198
251
|
}
|
|
199
252
|
catch (err) {
|
|
@@ -260,6 +313,20 @@ export class MajikKey {
|
|
|
260
313
|
this._encryptedMlKemSecretKeyBase64 = arrayBufferToBase64(encMlKem);
|
|
261
314
|
this._mlKemSecretKey = mlKemSecretKeyBytes;
|
|
262
315
|
}
|
|
316
|
+
if (this._encryptedEdSecretKey) {
|
|
317
|
+
const edSecretKeyBytes = await MajikKey._decryptSigningKey(this._encryptedEdSecretKey, currentPassphrase, salt);
|
|
318
|
+
const encEd = await MajikKey._encryptSigningKey(edSecretKeyBytes, newPassphrase, newSalt);
|
|
319
|
+
this._encryptedEdSecretKey = encEd;
|
|
320
|
+
this._encryptedEdSecretKeyBase64 = arrayBufferToBase64(encEd);
|
|
321
|
+
this._edSecretKey = edSecretKeyBytes;
|
|
322
|
+
}
|
|
323
|
+
if (this._encryptedMlDsaSecretKey) {
|
|
324
|
+
const mlDsaSecretKeyBytes = await MajikKey._decryptSigningKey(this._encryptedMlDsaSecretKey, currentPassphrase, salt);
|
|
325
|
+
const encDsa = await MajikKey._encryptSigningKey(mlDsaSecretKeyBytes, newPassphrase, newSalt);
|
|
326
|
+
this._encryptedMlDsaSecretKey = encDsa;
|
|
327
|
+
this._encryptedMlDsaSecretKeyBase64 = arrayBufferToBase64(encDsa);
|
|
328
|
+
this._mlDsaSecretKey = mlDsaSecretKeyBytes;
|
|
329
|
+
}
|
|
263
330
|
return this;
|
|
264
331
|
}
|
|
265
332
|
catch (err) {
|
|
@@ -294,10 +361,13 @@ export class MajikKey {
|
|
|
294
361
|
}
|
|
295
362
|
}
|
|
296
363
|
// ── LOCK / UNLOCK ────────────────────────────────────────────────────────────
|
|
364
|
+
// required
|
|
297
365
|
lock() {
|
|
298
366
|
this._privateKey = undefined;
|
|
299
367
|
this._privateKeyBase64 = undefined;
|
|
300
368
|
this._mlKemSecretKey = undefined;
|
|
369
|
+
this._edSecretKey = undefined; // ADD
|
|
370
|
+
this._mlDsaSecretKey = undefined; // ADD
|
|
301
371
|
return this;
|
|
302
372
|
}
|
|
303
373
|
async unlock(passphrase) {
|
|
@@ -322,6 +392,12 @@ export class MajikKey {
|
|
|
322
392
|
if (this._encryptedMlKemSecretKey) {
|
|
323
393
|
this._mlKemSecretKey = await MajikKey._decryptMlKemSecretKey(this._encryptedMlKemSecretKey, passphrase, salt);
|
|
324
394
|
}
|
|
395
|
+
if (this._encryptedEdSecretKey) {
|
|
396
|
+
this._edSecretKey = await MajikKey._decryptSigningKey(this._encryptedEdSecretKey, passphrase, salt);
|
|
397
|
+
}
|
|
398
|
+
if (this._encryptedMlDsaSecretKey) {
|
|
399
|
+
this._mlDsaSecretKey = await MajikKey._decryptSigningKey(this._encryptedMlDsaSecretKey, passphrase, salt);
|
|
400
|
+
}
|
|
325
401
|
return this;
|
|
326
402
|
}
|
|
327
403
|
catch (err) {
|
|
@@ -357,6 +433,20 @@ export class MajikKey {
|
|
|
357
433
|
throw new MajikKeyError("No ML-KEM secret key — re-import via importFromMnemonicBackup() for full migration.");
|
|
358
434
|
return this._mlKemSecretKey;
|
|
359
435
|
}
|
|
436
|
+
getEdSecretKey() {
|
|
437
|
+
if (this.isLocked)
|
|
438
|
+
throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
|
|
439
|
+
if (!this._edSecretKey)
|
|
440
|
+
throw new MajikKeyError("No Ed25519 secret key — re-import via importFromMnemonicBackup() for full migration.");
|
|
441
|
+
return this._edSecretKey;
|
|
442
|
+
}
|
|
443
|
+
getMlDsaSecretKey() {
|
|
444
|
+
if (this.isLocked)
|
|
445
|
+
throw new MajikKeyError("MajikKey is locked. Call unlock() first.");
|
|
446
|
+
if (!this._mlDsaSecretKey)
|
|
447
|
+
throw new MajikKeyError("No ML-DSA secret key — re-import via importFromMnemonicBackup() for full migration.");
|
|
448
|
+
return this._mlDsaSecretKey;
|
|
449
|
+
}
|
|
360
450
|
// ── SERIALIZATION ────────────────────────────────────────────────────────────
|
|
361
451
|
toJSON() {
|
|
362
452
|
return {
|
|
@@ -373,6 +463,14 @@ export class MajikKey {
|
|
|
373
463
|
? arrayToBase64(this._mlKemPublicKey)
|
|
374
464
|
: undefined,
|
|
375
465
|
encryptedMlKemSecretKey: this._encryptedMlKemSecretKeyBase64,
|
|
466
|
+
edPublicKey: this._edPublicKey
|
|
467
|
+
? arrayToBase64(this._edPublicKey)
|
|
468
|
+
: undefined,
|
|
469
|
+
encryptedEdSecretKey: this._encryptedEdSecretKeyBase64,
|
|
470
|
+
mlDsaPublicKey: this._mlDsaPublicKey
|
|
471
|
+
? arrayToBase64(this._mlDsaPublicKey)
|
|
472
|
+
: undefined,
|
|
473
|
+
encryptedMlDsaSecretKey: this._encryptedMlDsaSecretKeyBase64,
|
|
376
474
|
};
|
|
377
475
|
}
|
|
378
476
|
toString(pretty = false) {
|
|
@@ -498,6 +596,12 @@ export class MajikKey {
|
|
|
498
596
|
encryptedMlKemSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlKemSecretKey),
|
|
499
597
|
privateKey: identity.privateKey,
|
|
500
598
|
privateKeyBase64,
|
|
599
|
+
edPublicKey: identity.edPublicKey,
|
|
600
|
+
encryptedEdSecretKey: identity.encryptedEdSecretKey,
|
|
601
|
+
encryptedEdSecretKeyBase64: arrayBufferToBase64(identity.encryptedEdSecretKey),
|
|
602
|
+
mlDsaPublicKey: identity.mlDsaPublicKey,
|
|
603
|
+
encryptedMlDsaSecretKey: identity.encryptedMlDsaSecretKey,
|
|
604
|
+
encryptedMlDsaSecretKeyBase64: arrayBufferToBase64(identity.encryptedMlDsaSecretKey),
|
|
501
605
|
});
|
|
502
606
|
}
|
|
503
607
|
catch (err) {
|
|
@@ -527,6 +631,11 @@ export class MajikKey {
|
|
|
527
631
|
const { blob: encryptedPrivateKey } = await MajikKey._encryptPrivateKey(exportedXPrivate, passphrase, salt);
|
|
528
632
|
const mlKemSecretKey = encIdentity.mlKemSecretKey;
|
|
529
633
|
const encryptedMlKemSecretKey = await MajikKey._encryptMlKemSecretKey(mlKemSecretKey, passphrase, salt);
|
|
634
|
+
// after the existing ML-KEM encryption block:
|
|
635
|
+
const edSecretKey = encIdentity.edSecretKey;
|
|
636
|
+
const encryptedEdSecretKey = await MajikKey._encryptSigningKey(edSecretKey, passphrase, salt);
|
|
637
|
+
const mlDsaSecretKey = encIdentity.mlDsaSecretKey;
|
|
638
|
+
const encryptedMlDsaSecretKey = await MajikKey._encryptSigningKey(mlDsaSecretKey, passphrase, salt);
|
|
530
639
|
return {
|
|
531
640
|
id: encIdentity.fingerprint,
|
|
532
641
|
publicKey: encIdentity.publicKey,
|
|
@@ -538,6 +647,12 @@ export class MajikKey {
|
|
|
538
647
|
mlKemPublicKey: encIdentity.mlKemPublicKey,
|
|
539
648
|
mlKemSecretKey,
|
|
540
649
|
encryptedMlKemSecretKey,
|
|
650
|
+
edPublicKey: encIdentity.edPublicKey,
|
|
651
|
+
edSecretKey,
|
|
652
|
+
encryptedEdSecretKey,
|
|
653
|
+
mlDsaPublicKey: encIdentity.mlDsaPublicKey,
|
|
654
|
+
mlDsaSecretKey,
|
|
655
|
+
encryptedMlDsaSecretKey,
|
|
541
656
|
};
|
|
542
657
|
}
|
|
543
658
|
// ── PRIVATE: Encryption/Decryption ───────────────────────────────────────────
|
|
@@ -561,6 +676,12 @@ export class MajikKey {
|
|
|
561
676
|
const ciphertext = aesGcmEncrypt(keyBytes, iv, mlKemSecretKey);
|
|
562
677
|
return concatUint8Arrays(iv, ciphertext).buffer;
|
|
563
678
|
}
|
|
679
|
+
static async _encryptSigningKey(keyBytes_, passphrase, salt) {
|
|
680
|
+
const aesKey = await deriveKeyFromPassphraseArgon2(passphrase, salt);
|
|
681
|
+
const iv = generateRandomBytes(IV_LENGTH);
|
|
682
|
+
const ciphertext = aesGcmEncrypt(aesKey, iv, keyBytes_);
|
|
683
|
+
return concatUint8Arrays(iv, ciphertext).buffer;
|
|
684
|
+
}
|
|
564
685
|
static async _decryptPrivateKey(buffer, passphrase, salt, kdfVersion = KDF_VERSION.PBKDF2) {
|
|
565
686
|
const keyBytes = kdfVersion === KDF_VERSION.ARGON2ID
|
|
566
687
|
? await deriveKeyFromPassphraseArgon2(passphrase, salt)
|
|
@@ -584,6 +705,16 @@ export class MajikKey {
|
|
|
584
705
|
throw new MajikKeyError("Failed to decrypt ML-KEM secret key");
|
|
585
706
|
return plain;
|
|
586
707
|
}
|
|
708
|
+
static async _decryptSigningKey(buffer, passphrase, salt) {
|
|
709
|
+
const keyBytes = await deriveKeyFromPassphraseArgon2(passphrase, salt);
|
|
710
|
+
const full = new Uint8Array(buffer);
|
|
711
|
+
const iv = full.slice(0, IV_LENGTH);
|
|
712
|
+
const ciphertext = full.slice(IV_LENGTH);
|
|
713
|
+
const plain = aesGcmDecrypt(keyBytes, iv, ciphertext);
|
|
714
|
+
if (!plain)
|
|
715
|
+
throw new MajikKeyError("Failed to decrypt signing key");
|
|
716
|
+
return plain;
|
|
717
|
+
}
|
|
587
718
|
// ── PRIVATE: Backup ──────────────────────────────────────────────────────────
|
|
588
719
|
static async _verifyBackupDecryption(ivBase64, ciphertextBase64, mnemonic, backupKdfVersion) {
|
|
589
720
|
const iv = new Uint8Array(base64ToArrayBuffer(ivBase64));
|
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.2.0",
|
|
6
6
|
"license": "Apache-2.0",
|
|
7
7
|
"author": "Zelijah",
|
|
8
8
|
"main": "./dist/index.js",
|