@majikah/majik-key 0.6.0 → 1.0.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/README.md +566 -175
- package/dist/core/backup/index.d.ts +4 -4
- package/dist/core/backup/index.js +3 -3
- package/dist/core/backup/majik-key-backup.d.ts +3 -3
- package/dist/core/backup/majik-key-backup.js +5 -5
- package/dist/core/backup/types.d.ts +1 -1
- package/dist/core/backup/utils.js +1 -1
- package/dist/core/backup/validator.d.ts +1 -1
- package/dist/core/backup/validator.js +1 -1
- package/dist/core/crypto/constants.d.ts +35 -11
- package/dist/core/crypto/constants.js +33 -11
- package/dist/core/crypto/crypto-provider.js +8 -8
- package/dist/core/crypto/encryption-engine.d.ts +7 -16
- package/dist/core/crypto/encryption-engine.js +27 -62
- package/dist/core/database/system/identity.d.ts +2 -2
- package/dist/core/database/system/identity.js +2 -2
- package/dist/core/keys/hkdf-recipe.d.ts +4 -0
- package/dist/core/keys/hkdf-recipe.js +26 -0
- package/dist/core/keys/key-id.d.ts +50 -0
- package/dist/core/keys/key-id.js +64 -0
- package/dist/core/keys/key-impls.d.ts +12 -0
- package/dist/core/keys/key-impls.js +163 -0
- package/dist/core/keys/key-store.d.ts +72 -0
- package/dist/core/keys/key-store.js +264 -0
- package/dist/core/keys/keypair-handle.d.ts +36 -0
- package/dist/core/keys/keypair-handle.js +43 -0
- package/dist/core/keys/registry.d.ts +16 -0
- package/dist/core/keys/registry.js +144 -0
- package/dist/core/keys/types.d.ts +47 -0
- package/dist/core/keys/types.js +1 -0
- package/dist/core/types.d.ts +12 -1
- package/dist/core/utils.d.ts +1 -1
- package/dist/core/utils.js +1 -1
- package/dist/core/validator.d.ts +1 -1
- package/dist/core/validator.js +1 -1
- package/dist/core/web3/bitcoin/bitcoin.d.ts +1 -1
- package/dist/core/web3/bitcoin/bitcoin.js +4 -4
- package/dist/core/web3/bitcoin/types.d.ts +1 -1
- package/dist/core/web3/ethereum/constants.d.ts +1 -0
- package/dist/core/web3/ethereum/constants.js +4 -0
- package/dist/core/web3/ethereum/ethereum.d.ts +21 -0
- package/dist/core/web3/ethereum/ethereum.js +91 -0
- package/dist/core/web3/ethereum/types.d.ts +32 -0
- package/dist/core/web3/ethereum/types.js +1 -0
- package/dist/core/web3/index.d.ts +10 -5
- package/dist/core/web3/index.js +7 -2
- package/dist/core/web3/solana/solana.d.ts +2 -2
- package/dist/core/web3/solana/solana.js +6 -6
- package/dist/core/web3/solana/types.d.ts +1 -1
- package/dist/core/web3/types.d.ts +4 -2
- package/dist/index.d.ts +13 -6
- package/dist/index.js +11 -5
- package/dist/majik-key.d.ts +181 -281
- package/dist/majik-key.js +628 -740
- package/package.json +23 -8
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { MajikKeyBackup } from "./majik-key-backup";
|
|
2
|
-
export type { BackupSource, CreateBackupParams, ToZipOptions } from "./types";
|
|
3
|
-
export { BACKUP_FORMAT_VERSION } from "./types";
|
|
4
|
-
export { MajikKeyBackupError, MissingOptionalDependencyError, InvalidBackupJSONError, InvalidBackupPNGError, InvalidBackupZipError, BackupIntegrityMismatchError, } from "./error";
|
|
1
|
+
export { MajikKeyBackup } from "./majik-key-backup.js";
|
|
2
|
+
export type { BackupSource, CreateBackupParams, ToZipOptions } from "./types.js";
|
|
3
|
+
export { BACKUP_FORMAT_VERSION } from "./types.js";
|
|
4
|
+
export { MajikKeyBackupError, MissingOptionalDependencyError, InvalidBackupJSONError, InvalidBackupPNGError, InvalidBackupZipError, BackupIntegrityMismatchError, } from "./error.js";
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { MajikKeyBackup } from "./majik-key-backup";
|
|
2
|
-
export { BACKUP_FORMAT_VERSION } from "./types";
|
|
3
|
-
export { MajikKeyBackupError, MissingOptionalDependencyError, InvalidBackupJSONError, InvalidBackupPNGError, InvalidBackupZipError, BackupIntegrityMismatchError, } from "./error";
|
|
1
|
+
export { MajikKeyBackup } from "./majik-key-backup.js";
|
|
2
|
+
export { BACKUP_FORMAT_VERSION } from "./types.js";
|
|
3
|
+
export { MajikKeyBackupError, MissingOptionalDependencyError, InvalidBackupJSONError, InvalidBackupPNGError, InvalidBackupZipError, BackupIntegrityMismatchError, } from "./error.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type { MnemonicLanguage } from "../crypto/wordlist";
|
|
2
|
-
import { MnemonicJSON } from "../types";
|
|
3
|
-
import { CreateBackupParams, ToZipOptions } from "./types";
|
|
1
|
+
import type { MnemonicLanguage } from "../crypto/wordlist.js";
|
|
2
|
+
import { MnemonicJSON } from "../types.js";
|
|
3
|
+
import { CreateBackupParams, ToZipOptions } from "./types.js";
|
|
4
4
|
/**
|
|
5
5
|
* A validated Majik Key backup payload, and the single place that knows
|
|
6
6
|
* how to read/write it as JSON, a MajikByte PNG, or a .zip archive
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { validateMnemonicJSONShape } from "./validator";
|
|
2
|
-
import { InvalidBackupPNGError, InvalidBackupZipError, BackupIntegrityMismatchError, } from "./error";
|
|
3
|
-
import { getJSZip, getMajikBytes, looksLikePNG, toSafeFileName, buildReadmeText, } from "./utils";
|
|
4
|
-
import { BACKUP_FORMAT_VERSION, } from "./types";
|
|
5
|
-
import { base64ToUtf8, utf8ToBase64 } from "../utils";
|
|
1
|
+
import { validateMnemonicJSONShape } from "./validator.js";
|
|
2
|
+
import { InvalidBackupPNGError, InvalidBackupZipError, BackupIntegrityMismatchError, } from "./error.js";
|
|
3
|
+
import { getJSZip, getMajikBytes, looksLikePNG, toSafeFileName, buildReadmeText, } from "./utils.js";
|
|
4
|
+
import { BACKUP_FORMAT_VERSION, } from "./types.js";
|
|
5
|
+
import { base64ToUtf8, utf8ToBase64 } from "../utils.js";
|
|
6
6
|
const BACKUP_JSON_FILENAME = "backup.json";
|
|
7
7
|
const BACKUP_PNG_FILENAME = "backup.png";
|
|
8
8
|
const README_FILENAME = "IMPORTANT README.txt";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { MissingOptionalDependencyError } from "./error";
|
|
1
|
+
import { MissingOptionalDependencyError } from "./error.js";
|
|
2
2
|
// ────────────────────────────────────────────────────────────────
|
|
3
3
|
// Lazy-loaded optional dependencies
|
|
4
4
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -2,8 +2,40 @@ export declare const KEY_ALGO: {
|
|
|
2
2
|
readonly name: "ECDH";
|
|
3
3
|
readonly namedCurve: "X25519";
|
|
4
4
|
};
|
|
5
|
-
export declare const MAJIK_SALT = "
|
|
6
|
-
export declare const MAJIK_MNEMONIC_SALT = "
|
|
5
|
+
export declare const MAJIK_SALT = "MajikKeySalt";
|
|
6
|
+
export declare const MAJIK_MNEMONIC_SALT = "MajikKeyMnemonicSalt";
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Pre-0.8 salts ("MajikMessage…"). READ-ONLY: kept so backups and
|
|
9
|
+
* data written by older versions still decrypt. Never use for new writes.
|
|
10
|
+
* Changing/removing these would make every existing mnemonic backup unreadable.
|
|
11
|
+
*/
|
|
12
|
+
export declare const LEGACY_MAJIK_SALT = "MajikMessageSalt";
|
|
13
|
+
/** @deprecated See LEGACY_MAJIK_SALT. Existing mnemonic backups were encrypted with this salt. */
|
|
14
|
+
export declare const LEGACY_MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
|
|
15
|
+
/**
|
|
16
|
+
* Mnemonic-backup salt generations. The backup blob records which one it used
|
|
17
|
+
* (`backupSaltVersion`); blobs without the field are generation 1.
|
|
18
|
+
* 1 → LEGACY_MAJIK_MNEMONIC_SALT (every backup written before 0.8)
|
|
19
|
+
* 2 → MAJIK_MNEMONIC_SALT (written by 0.8+)
|
|
20
|
+
*/
|
|
21
|
+
export declare const BACKUP_SALT_VERSION: {
|
|
22
|
+
readonly LEGACY: 1;
|
|
23
|
+
readonly CURRENT: 2;
|
|
24
|
+
};
|
|
25
|
+
export type BACKUP_SALT_VERSION = (typeof BACKUP_SALT_VERSION)[keyof typeof BACKUP_SALT_VERSION];
|
|
26
|
+
/**
|
|
27
|
+
* Which generation NEW backups are written with. Readers handle both.
|
|
28
|
+
* ⚠️ Older library versions (and any port that hasn't been updated, e.g.
|
|
29
|
+
* majik-key-rs) can only read generation 1. Flip to LEGACY to keep newly
|
|
30
|
+
* created backups readable by them until they ship the new reader.
|
|
31
|
+
*/
|
|
32
|
+
export declare const BACKUP_SALT_WRITE_VERSION: BACKUP_SALT_VERSION;
|
|
33
|
+
export declare const backupSaltFor: (v: number | undefined) => string;
|
|
34
|
+
/**
|
|
35
|
+
* ⚠️ FROZEN. This string is part of the legacy-v1 ML-DSA-87 derivation
|
|
36
|
+
* (sha256(seed || this)). Changing it changes every existing user's ML-DSA key.
|
|
37
|
+
* It intentionally still says "Majik…" and must NOT be renamed.
|
|
38
|
+
*/
|
|
7
39
|
export declare const MAJIK_SIGNATURE_SEED = "MajikSignatureSeedDSA";
|
|
8
40
|
/**
|
|
9
41
|
* KDF version identifiers.
|
|
@@ -15,15 +47,7 @@ export declare const KDF_VERSION: {
|
|
|
15
47
|
readonly ARGON2ID: 2;
|
|
16
48
|
};
|
|
17
49
|
export type KDF_VERSION = (typeof KDF_VERSION)[keyof typeof KDF_VERSION];
|
|
18
|
-
/**
|
|
19
|
-
* Argon2id parameters.
|
|
20
|
-
*
|
|
21
|
-
* PASSPHRASE (protecting the private key at rest):
|
|
22
|
-
* m=131072 (64 MB) — double OWASP "high security" tier (64 MB)
|
|
23
|
-
* t=4 — 4 passes
|
|
24
|
-
* p=4 — 4 parallel lanes
|
|
25
|
-
*
|
|
26
|
-
*/
|
|
50
|
+
/** Argon2id parameters. */
|
|
27
51
|
export declare const ARGON2_PARAMS: {
|
|
28
52
|
readonly PASSPHRASE: {
|
|
29
53
|
readonly m: 65536;
|
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
export const KEY_ALGO = { name: "ECDH", namedCurve: "X25519" };
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
// ── Salts ─────────────────────────────────────────────────────────────────────
|
|
3
|
+
// Current names (MajikKey). Used for everything written by this version.
|
|
4
|
+
export const MAJIK_SALT = "MajikKeySalt";
|
|
5
|
+
export const MAJIK_MNEMONIC_SALT = "MajikKeyMnemonicSalt";
|
|
6
|
+
/**
|
|
7
|
+
* @deprecated Pre-0.8 salts ("MajikMessage…"). READ-ONLY: kept so backups and
|
|
8
|
+
* data written by older versions still decrypt. Never use for new writes.
|
|
9
|
+
* Changing/removing these would make every existing mnemonic backup unreadable.
|
|
10
|
+
*/
|
|
11
|
+
export const LEGACY_MAJIK_SALT = "MajikMessageSalt";
|
|
12
|
+
/** @deprecated See LEGACY_MAJIK_SALT. Existing mnemonic backups were encrypted with this salt. */
|
|
13
|
+
export const LEGACY_MAJIK_MNEMONIC_SALT = "MajikMessageMnemonicSalt";
|
|
14
|
+
/**
|
|
15
|
+
* Mnemonic-backup salt generations. The backup blob records which one it used
|
|
16
|
+
* (`backupSaltVersion`); blobs without the field are generation 1.
|
|
17
|
+
* 1 → LEGACY_MAJIK_MNEMONIC_SALT (every backup written before 0.8)
|
|
18
|
+
* 2 → MAJIK_MNEMONIC_SALT (written by 0.8+)
|
|
19
|
+
*/
|
|
20
|
+
export const BACKUP_SALT_VERSION = { LEGACY: 1, CURRENT: 2 };
|
|
21
|
+
/**
|
|
22
|
+
* Which generation NEW backups are written with. Readers handle both.
|
|
23
|
+
* ⚠️ Older library versions (and any port that hasn't been updated, e.g.
|
|
24
|
+
* majik-key-rs) can only read generation 1. Flip to LEGACY to keep newly
|
|
25
|
+
* created backups readable by them until they ship the new reader.
|
|
26
|
+
*/
|
|
27
|
+
export const BACKUP_SALT_WRITE_VERSION = BACKUP_SALT_VERSION.CURRENT;
|
|
28
|
+
export const backupSaltFor = (v) => v === BACKUP_SALT_VERSION.CURRENT ? MAJIK_MNEMONIC_SALT : LEGACY_MAJIK_MNEMONIC_SALT;
|
|
29
|
+
/**
|
|
30
|
+
* ⚠️ FROZEN. This string is part of the legacy-v1 ML-DSA-87 derivation
|
|
31
|
+
* (sha256(seed || this)). Changing it changes every existing user's ML-DSA key.
|
|
32
|
+
* It intentionally still says "Majik…" and must NOT be renamed.
|
|
33
|
+
*/
|
|
4
34
|
export const MAJIK_SIGNATURE_SEED = "MajikSignatureSeedDSA";
|
|
5
35
|
/**
|
|
6
36
|
* KDF version identifiers.
|
|
@@ -11,15 +41,7 @@ export const KDF_VERSION = {
|
|
|
11
41
|
PBKDF2: 1, // legacy — read-only support for existing accounts
|
|
12
42
|
ARGON2ID: 2, // current — all new accounts and re-encryptions
|
|
13
43
|
};
|
|
14
|
-
/**
|
|
15
|
-
* Argon2id parameters.
|
|
16
|
-
*
|
|
17
|
-
* PASSPHRASE (protecting the private key at rest):
|
|
18
|
-
* m=131072 (64 MB) — double OWASP "high security" tier (64 MB)
|
|
19
|
-
* t=4 — 4 passes
|
|
20
|
-
* p=4 — 4 parallel lanes
|
|
21
|
-
*
|
|
22
|
-
*/
|
|
44
|
+
/** Argon2id parameters. */
|
|
23
45
|
export const ARGON2_PARAMS = {
|
|
24
46
|
PASSPHRASE: {
|
|
25
47
|
m: 65536, // memory in KB (64 MB)
|
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// crypto-provider.ts from @majikah/majik-key
|
|
2
|
-
import * as ed25519 from "@stablelib/ed25519";
|
|
2
|
+
import * as ed25519 from "@stablelib/ed25519/ed25519.js";
|
|
3
3
|
import ed2curve from "ed2curve";
|
|
4
|
-
import { AES } from "@stablelib/aes";
|
|
5
|
-
import { GCM } from "@stablelib/gcm";
|
|
6
|
-
import { deriveKey } from "@stablelib/pbkdf2";
|
|
7
|
-
import { hash, SHA256 } from "@stablelib/sha256";
|
|
8
|
-
import * as x25519 from "@stablelib/x25519";
|
|
9
|
-
import { arrayToBase64 } from "../utils";
|
|
4
|
+
import { AES } from "@stablelib/aes/aes.js";
|
|
5
|
+
import { GCM } from "@stablelib/gcm/gcm.js";
|
|
6
|
+
import { deriveKey } from "@stablelib/pbkdf2/pbkdf2.js";
|
|
7
|
+
import { hash, SHA256 } from "@stablelib/sha256/sha256.js";
|
|
8
|
+
import * as x25519 from "@stablelib/x25519/x25519.js";
|
|
9
|
+
import { arrayToBase64 } from "../utils.js";
|
|
10
10
|
import { argon2id as nobleArgon2id } from "@noble/hashes/argon2.js";
|
|
11
|
-
import { ARGON2_PARAMS } from "./constants";
|
|
11
|
+
import { ARGON2_PARAMS } from "./constants.js";
|
|
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);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ED25519RawPublicKey, MajikKeyFingerprint, MLDSA87RawPublicKey, MLKEM768RawPublicKey, X25519RawKey } from "../types";
|
|
1
|
+
import type { ED25519RawPublicKey, MajikKeyFingerprint, MLDSA87RawPublicKey, MLKEM768RawPublicKey, X25519RawKey } from "../types.js";
|
|
2
2
|
export interface EncryptionIdentity {
|
|
3
3
|
publicKey: X25519RawKey;
|
|
4
4
|
privateKey: X25519RawKey;
|
|
@@ -17,22 +17,13 @@ export interface EncryptionIdentity {
|
|
|
17
17
|
*/
|
|
18
18
|
export declare class EncryptionEngine {
|
|
19
19
|
/**
|
|
20
|
-
* Derive
|
|
20
|
+
* Derive the core identity (X25519, Ed25519, ML-KEM-768, ML-DSA-87) from a
|
|
21
|
+
* BIP-39 mnemonic.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* seed[0..32] → Ed25519 keypair via generateKeyPairFromSeed
|
|
27
|
-
* → X25519 via ed2curve conversion
|
|
28
|
-
*
|
|
29
|
-
* ML-KEM-768 derivation (new):
|
|
30
|
-
* seed[0..64] → ml_kem768.keygen(seed)
|
|
31
|
-
* → { publicKey: 1184 bytes, secretKey: 2400 bytes }
|
|
32
|
-
*
|
|
33
|
-
* The noble library accepts the full 64-byte BIP-39 seed directly.
|
|
34
|
-
* Internally it uses seed[0..32] for the lattice key matrix and
|
|
35
|
-
* seed[32..64] for the implicit rejection parameter `z`.
|
|
23
|
+
* Since 0.8 this DELEGATES to the key registry (core/keys/key-impls.ts),
|
|
24
|
+
* which is the single source of truth for every derivation recipe. Output is
|
|
25
|
+
* byte-for-byte identical to the previous implementation (pinned by
|
|
26
|
+
* vectors/legacy-v1.vectors.json).
|
|
36
27
|
*/
|
|
37
28
|
static deriveIdentityFromMnemonic(mnemonic: string): Promise<EncryptionIdentity>;
|
|
38
29
|
/**
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
// encryption-engine.ts from @majikah/majik-key
|
|
2
2
|
import { mnemonicToSeedSync } from "@scure/bip39";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
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";
|
|
3
|
+
import { fingerprintFromPublicRaw } from "./crypto-provider.js";
|
|
4
|
+
import { deriveKeys } from "../keys/key-impls.js";
|
|
5
|
+
import { KeyId } from "../keys/key-id.js";
|
|
10
6
|
const secureFill = Uint8Array.prototype.fill;
|
|
11
7
|
/**
|
|
12
8
|
* EncryptionEngine
|
|
@@ -14,66 +10,41 @@ const secureFill = Uint8Array.prototype.fill;
|
|
|
14
10
|
* Core cryptographic engine.
|
|
15
11
|
*/
|
|
16
12
|
export class EncryptionEngine {
|
|
17
|
-
/* ================================
|
|
18
|
-
* Identity
|
|
19
|
-
* ================================ */
|
|
20
13
|
/**
|
|
21
|
-
* Derive
|
|
22
|
-
*
|
|
23
|
-
* Seed derivation:
|
|
24
|
-
* mnemonicToSeedSync(mnemonic) → 64-byte BIP-39 seed
|
|
25
|
-
*
|
|
26
|
-
* X25519 derivation (unchanged from before):
|
|
27
|
-
* seed[0..32] → Ed25519 keypair via generateKeyPairFromSeed
|
|
28
|
-
* → X25519 via ed2curve conversion
|
|
29
|
-
*
|
|
30
|
-
* ML-KEM-768 derivation (new):
|
|
31
|
-
* seed[0..64] → ml_kem768.keygen(seed)
|
|
32
|
-
* → { publicKey: 1184 bytes, secretKey: 2400 bytes }
|
|
14
|
+
* Derive the core identity (X25519, Ed25519, ML-KEM-768, ML-DSA-87) from a
|
|
15
|
+
* BIP-39 mnemonic.
|
|
33
16
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
17
|
+
* Since 0.8 this DELEGATES to the key registry (core/keys/key-impls.ts),
|
|
18
|
+
* which is the single source of truth for every derivation recipe. Output is
|
|
19
|
+
* byte-for-byte identical to the previous implementation (pinned by
|
|
20
|
+
* vectors/legacy-v1.vectors.json).
|
|
37
21
|
*/
|
|
38
22
|
static async deriveIdentityFromMnemonic(mnemonic) {
|
|
39
23
|
if (typeof mnemonic !== "string" || mnemonic.trim().length === 0) {
|
|
40
24
|
throw new CryptoError("Mnemonic must be a non-empty string");
|
|
41
25
|
}
|
|
42
|
-
|
|
43
|
-
const seed = mnemonicToSeedSync(mnemonic); // returns Buffer (Node) or Uint8Array
|
|
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);
|
|
52
|
-
// Step 2: X25519 identity from first 32 bytes (existing path)
|
|
53
|
-
const seed32 = seed64.subarray(0, 32);
|
|
26
|
+
const seed64 = new Uint8Array(mnemonicToSeedSync(mnemonic));
|
|
54
27
|
try {
|
|
55
|
-
const
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
const
|
|
64
|
-
const
|
|
65
|
-
const fingerprint = fingerprintFromPublicRaw(pkCurveBytes);
|
|
66
|
-
const mlDsaKeypair = ml_dsa87.keygen(mlDsaSeed);
|
|
28
|
+
const k = deriveKeys(seed64, [
|
|
29
|
+
KeyId.X25519,
|
|
30
|
+
KeyId.ED25519,
|
|
31
|
+
KeyId.ML_KEM_768,
|
|
32
|
+
KeyId.ML_DSA_87,
|
|
33
|
+
]);
|
|
34
|
+
const x = k.get(KeyId.X25519);
|
|
35
|
+
const ed = k.get(KeyId.ED25519);
|
|
36
|
+
const kem = k.get(KeyId.ML_KEM_768);
|
|
37
|
+
const dsa = k.get(KeyId.ML_DSA_87);
|
|
67
38
|
return {
|
|
68
|
-
publicKey,
|
|
69
|
-
privateKey,
|
|
70
|
-
fingerprint,
|
|
71
|
-
mlKemPublicKey:
|
|
72
|
-
mlKemSecretKey:
|
|
39
|
+
publicKey: { type: "public", raw: x.publicKey },
|
|
40
|
+
privateKey: { type: "private", raw: x.secretKey },
|
|
41
|
+
fingerprint: fingerprintFromPublicRaw(x.publicKey),
|
|
42
|
+
mlKemPublicKey: kem.publicKey, // 1184 bytes
|
|
43
|
+
mlKemSecretKey: kem.secretKey, // 2400 bytes
|
|
73
44
|
edPublicKey: ed.publicKey, // 32 bytes
|
|
74
45
|
edSecretKey: ed.secretKey, // 64 bytes
|
|
75
|
-
mlDsaPublicKey:
|
|
76
|
-
mlDsaSecretKey:
|
|
46
|
+
mlDsaPublicKey: dsa.publicKey, // 2592 bytes
|
|
47
|
+
mlDsaSecretKey: dsa.secretKey, // 4896 bytes
|
|
77
48
|
};
|
|
78
49
|
}
|
|
79
50
|
catch (err) {
|
|
@@ -81,11 +52,6 @@ export class EncryptionEngine {
|
|
|
81
52
|
}
|
|
82
53
|
finally {
|
|
83
54
|
secureFill.call(seed64, 0);
|
|
84
|
-
secureFill.call(seed32, 0);
|
|
85
|
-
secureFill.call(mlDsaSeedInput, 0);
|
|
86
|
-
secureFill.call(mlDsaSeed, 0);
|
|
87
|
-
if (seed instanceof Uint8Array)
|
|
88
|
-
secureFill.call(seed, 0);
|
|
89
55
|
}
|
|
90
56
|
}
|
|
91
57
|
/* ================================
|
|
@@ -95,7 +61,6 @@ export class EncryptionEngine {
|
|
|
95
61
|
* Generates a SHA-256 fingerprint from a public key.
|
|
96
62
|
*/
|
|
97
63
|
static async fingerprintFromPublicKey(publicKey) {
|
|
98
|
-
// Accept both CryptoKey and raw wrappers; use stablelib sha256 via provider
|
|
99
64
|
const anyKey = publicKey;
|
|
100
65
|
let rawBytes;
|
|
101
66
|
if (anyKey && anyKey.raw instanceof Uint8Array) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { MajikUser } from "@thezelijah/majik-user";
|
|
2
|
-
import { SerializedMajikContact } from "@majikah/majik-contact";
|
|
1
|
+
import { MajikUser } from "@thezelijah/majik-user/dist/core/majik-user.js";
|
|
2
|
+
import { SerializedMajikContact } from "@majikah/majik-contact/dist/types.js";
|
|
3
3
|
export interface MajikMessageIdentityJSON {
|
|
4
4
|
id: string;
|
|
5
5
|
user_id: string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hkdf-recipe.ts — the "hkdf-sha512-v1" derivation recipe for every key
|
|
3
|
+
* added AFTER the registry (phase 4+). Frozen once released: changing any
|
|
4
|
+
* constant here changes every key derived with it. Pinned by
|
|
5
|
+
* vectors/hkdf-v1.vectors.json.
|
|
6
|
+
*
|
|
7
|
+
* seed_k = HKDF-SHA512( ikm = 64-byte BIP-39 seed,
|
|
8
|
+
* salt = "MajikKey/hkdf-sha512/v1",
|
|
9
|
+
* info = "majik/v1/<namespaced key id>",
|
|
10
|
+
* L = the algorithm's seed length )
|
|
11
|
+
*
|
|
12
|
+
* Domain separation is by `info`, so no two algorithms (or parameter sets of
|
|
13
|
+
* the same algorithm) ever receive related seed material, and adding a new
|
|
14
|
+
* algorithm can never change an existing key.
|
|
15
|
+
*/
|
|
16
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
17
|
+
import { sha512 } from "@noble/hashes/sha2.js";
|
|
18
|
+
import { MajikKeyError } from "../error.js";
|
|
19
|
+
export const HKDF_SALT = "MajikKey/hkdf-sha512/v1";
|
|
20
|
+
export const hkdfInfo = (id) => `majik/v1/${id}`;
|
|
21
|
+
export function deriveSeedHkdf(seed64, id, length) {
|
|
22
|
+
if (seed64.length !== 64)
|
|
23
|
+
throw new MajikKeyError(`Expected the 64-byte BIP-39 seed, got ${seed64.length} bytes`);
|
|
24
|
+
const enc = new TextEncoder();
|
|
25
|
+
return hkdf(sha512, seed64, enc.encode(HKDF_SALT), enc.encode(hkdfInfo(id)), length);
|
|
26
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-id.ts
|
|
3
|
+
* Namespaced identifiers for every key algorithm the MajikKey registry knows.
|
|
4
|
+
* Format: "<family>:<name>". Adding an algorithm = one line here + one entry
|
|
5
|
+
* in registry.ts. Existence of an id does NOT mean it is usable — see
|
|
6
|
+
* `status` / `implemented` in registry.ts.
|
|
7
|
+
*/
|
|
8
|
+
export declare const KeyFamily: {
|
|
9
|
+
readonly CLASSIC: "classic";
|
|
10
|
+
readonly PQ: "pq";
|
|
11
|
+
readonly WEB3: "web3";
|
|
12
|
+
};
|
|
13
|
+
export type KeyFamily = (typeof KeyFamily)[keyof typeof KeyFamily];
|
|
14
|
+
export declare const KeyId: {
|
|
15
|
+
readonly X25519: "classic:x25519";
|
|
16
|
+
readonly ED25519: "classic:ed25519";
|
|
17
|
+
readonly ML_KEM_512: "pq:ml-kem-512";
|
|
18
|
+
readonly ML_KEM_768: "pq:ml-kem-768";
|
|
19
|
+
readonly ML_KEM_1024: "pq:ml-kem-1024";
|
|
20
|
+
readonly HQC_128: "pq:hqc-128";
|
|
21
|
+
readonly HQC_192: "pq:hqc-192";
|
|
22
|
+
readonly HQC_256: "pq:hqc-256";
|
|
23
|
+
readonly ML_DSA_44: "pq:ml-dsa-44";
|
|
24
|
+
readonly ML_DSA_65: "pq:ml-dsa-65";
|
|
25
|
+
readonly ML_DSA_87: "pq:ml-dsa-87";
|
|
26
|
+
readonly SLH_DSA_SHA2_128S: "pq:slh-dsa-sha2-128s";
|
|
27
|
+
readonly SLH_DSA_SHA2_128F: "pq:slh-dsa-sha2-128f";
|
|
28
|
+
readonly SLH_DSA_SHA2_192S: "pq:slh-dsa-sha2-192s";
|
|
29
|
+
readonly SLH_DSA_SHA2_192F: "pq:slh-dsa-sha2-192f";
|
|
30
|
+
readonly SLH_DSA_SHA2_256S: "pq:slh-dsa-sha2-256s";
|
|
31
|
+
readonly SLH_DSA_SHA2_256F: "pq:slh-dsa-sha2-256f";
|
|
32
|
+
readonly SLH_DSA_SHAKE_128S: "pq:slh-dsa-shake-128s";
|
|
33
|
+
readonly SLH_DSA_SHAKE_128F: "pq:slh-dsa-shake-128f";
|
|
34
|
+
readonly SLH_DSA_SHAKE_192S: "pq:slh-dsa-shake-192s";
|
|
35
|
+
readonly SLH_DSA_SHAKE_192F: "pq:slh-dsa-shake-192f";
|
|
36
|
+
readonly SLH_DSA_SHAKE_256S: "pq:slh-dsa-shake-256s";
|
|
37
|
+
readonly SLH_DSA_SHAKE_256F: "pq:slh-dsa-shake-256f";
|
|
38
|
+
readonly FALCON_512: "pq:falcon-512";
|
|
39
|
+
readonly FALCON_1024: "pq:falcon-1024";
|
|
40
|
+
readonly FN_DSA_512: "pq:fn-dsa-512";
|
|
41
|
+
readonly FN_DSA_1024: "pq:fn-dsa-1024";
|
|
42
|
+
readonly LMS: "pq:lms";
|
|
43
|
+
readonly BTC: "web3:btc";
|
|
44
|
+
readonly ETH: "web3:eth";
|
|
45
|
+
readonly SOL: "web3:sol";
|
|
46
|
+
};
|
|
47
|
+
export type KeyId = (typeof KeyId)[keyof typeof KeyId];
|
|
48
|
+
/** Keys every account must hold from this version on (backward-compat baseline). */
|
|
49
|
+
export declare const CORE_KEYS: readonly ["classic:x25519", "classic:ed25519", "pq:ml-kem-768", "pq:ml-dsa-87"];
|
|
50
|
+
export declare function keyFamilyOf(id: KeyId): KeyFamily;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* key-id.ts
|
|
3
|
+
* Namespaced identifiers for every key algorithm the MajikKey registry knows.
|
|
4
|
+
* Format: "<family>:<name>". Adding an algorithm = one line here + one entry
|
|
5
|
+
* in registry.ts. Existence of an id does NOT mean it is usable — see
|
|
6
|
+
* `status` / `implemented` in registry.ts.
|
|
7
|
+
*/
|
|
8
|
+
export const KeyFamily = {
|
|
9
|
+
CLASSIC: "classic",
|
|
10
|
+
PQ: "pq",
|
|
11
|
+
WEB3: "web3",
|
|
12
|
+
};
|
|
13
|
+
export const KeyId = {
|
|
14
|
+
// ── classic ──
|
|
15
|
+
X25519: "classic:x25519",
|
|
16
|
+
ED25519: "classic:ed25519",
|
|
17
|
+
// ── pq: KEM (FIPS 203) ──
|
|
18
|
+
ML_KEM_512: "pq:ml-kem-512",
|
|
19
|
+
ML_KEM_768: "pq:ml-kem-768",
|
|
20
|
+
ML_KEM_1024: "pq:ml-kem-1024",
|
|
21
|
+
// ── pq: KEM (HQC — NIST backup KEM, not yet final; no vetted JS impl) ──
|
|
22
|
+
HQC_128: "pq:hqc-128",
|
|
23
|
+
HQC_192: "pq:hqc-192",
|
|
24
|
+
HQC_256: "pq:hqc-256",
|
|
25
|
+
// ── pq: signatures (FIPS 204) ──
|
|
26
|
+
ML_DSA_44: "pq:ml-dsa-44",
|
|
27
|
+
ML_DSA_65: "pq:ml-dsa-65",
|
|
28
|
+
ML_DSA_87: "pq:ml-dsa-87",
|
|
29
|
+
// ── pq: signatures (FIPS 205, stateless hash-based) ──
|
|
30
|
+
SLH_DSA_SHA2_128S: "pq:slh-dsa-sha2-128s",
|
|
31
|
+
SLH_DSA_SHA2_128F: "pq:slh-dsa-sha2-128f",
|
|
32
|
+
SLH_DSA_SHA2_192S: "pq:slh-dsa-sha2-192s",
|
|
33
|
+
SLH_DSA_SHA2_192F: "pq:slh-dsa-sha2-192f",
|
|
34
|
+
SLH_DSA_SHA2_256S: "pq:slh-dsa-sha2-256s",
|
|
35
|
+
SLH_DSA_SHA2_256F: "pq:slh-dsa-sha2-256f",
|
|
36
|
+
SLH_DSA_SHAKE_128S: "pq:slh-dsa-shake-128s",
|
|
37
|
+
SLH_DSA_SHAKE_128F: "pq:slh-dsa-shake-128f",
|
|
38
|
+
SLH_DSA_SHAKE_192S: "pq:slh-dsa-shake-192s",
|
|
39
|
+
SLH_DSA_SHAKE_192F: "pq:slh-dsa-shake-192f",
|
|
40
|
+
SLH_DSA_SHAKE_256S: "pq:slh-dsa-shake-256s",
|
|
41
|
+
SLH_DSA_SHAKE_256F: "pq:slh-dsa-shake-256f",
|
|
42
|
+
// ── pq: Falcon Round 3 (what libraries ship today) ──
|
|
43
|
+
FALCON_512: "pq:falcon-512",
|
|
44
|
+
FALCON_1024: "pq:falcon-1024",
|
|
45
|
+
// ── pq: FN-DSA (FIPS 206) — RESERVED until the standard is final ──
|
|
46
|
+
FN_DSA_512: "pq:fn-dsa-512",
|
|
47
|
+
FN_DSA_1024: "pq:fn-dsa-1024",
|
|
48
|
+
// ── pq: stateful hash-based (NIST SP 800-208) — NOT SUPPORTED, see registry ──
|
|
49
|
+
LMS: "pq:lms",
|
|
50
|
+
// ── web3 ──
|
|
51
|
+
BTC: "web3:btc", // Majik domain-separated path (legacy default)
|
|
52
|
+
ETH: "web3:eth", // standard BIP-44 m/44'/60'/0'/0/0
|
|
53
|
+
SOL: "web3:sol", // derived view over classic:ed25519
|
|
54
|
+
};
|
|
55
|
+
/** Keys every account must hold from this version on (backward-compat baseline). */
|
|
56
|
+
export const CORE_KEYS = [
|
|
57
|
+
KeyId.X25519,
|
|
58
|
+
KeyId.ED25519,
|
|
59
|
+
KeyId.ML_KEM_768,
|
|
60
|
+
KeyId.ML_DSA_87,
|
|
61
|
+
];
|
|
62
|
+
export function keyFamilyOf(id) {
|
|
63
|
+
return id.split(":")[0];
|
|
64
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { KeyId } from "./key-id.js";
|
|
2
|
+
export interface DerivedKeypair {
|
|
3
|
+
publicKey: Uint8Array;
|
|
4
|
+
secretKey: Uint8Array;
|
|
5
|
+
}
|
|
6
|
+
export interface KeyImpl {
|
|
7
|
+
id: KeyId;
|
|
8
|
+
derive(seed64: Uint8Array): DerivedKeypair;
|
|
9
|
+
}
|
|
10
|
+
export declare const KEY_IMPLS: Readonly<Partial<Record<KeyId, KeyImpl>>>;
|
|
11
|
+
/** Derive the requested STORED keys from one BIP-39 seed. Caller zeroizes the seed. */
|
|
12
|
+
export declare function deriveKeys(seed64: Uint8Array, ids: readonly KeyId[]): Map<KeyId, DerivedKeypair>;
|