@majikah/majik-key 0.2.13 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2025 Josef Elijah Delos Santos Fabian
1
+ Copyright (c) 2026 Majikah Solutions OPC
2
2
 
3
3
  Licensed under the Apache License, Version 2.0 (the "License");
4
4
  you may not use this file except in compliance with the License.
package/README.md CHANGED
@@ -3,26 +3,37 @@
3
3
  [![Developed by Zelijah](https://img.shields.io/badge/Developed%20by-Zelijah-red?logo=github&logoColor=white)](https://www.thezelijah.world) ![GitHub Sponsors](https://img.shields.io/github/sponsors/jedlsf?style=plastic&label=Sponsors&link=https%3A%2F%2Fgithub.com%2Fsponsors%2Fjedlsf)
4
4
  ![npm](https://img.shields.io/npm/v/@majikah/majik-key) ![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-key) ![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
5
5
 
6
- **Majik Key** is a next-generation seed phrase account library for creating and managing mnemonic-based identities. It serves as a post-quantum ready, high-security bridge between BIP39 mnemonics and the broader Majikah ecosystem.
6
+ **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.
7
7
 
8
8
  ---
9
9
 
10
- ## Next-Gen Security Architecture
10
+ ## Why Majik Key
11
11
 
12
- Majik Key is engineered to meet and exceed modern cryptographic standards.
12
+ - **One seed, one identity, five key pairs.** A 12- or 24-word mnemonic deterministically derives X25519, ML-KEM-768, Ed25519, ML-DSA-87, and (by default) a domain-separated Bitcoin key — all reproducible from the mnemonic alone.
13
+ - **Post-quantum from day one.** Every account gets an ML-KEM-768 (FIPS-203) encryption keypair and an ML-DSA-87 signing keypair alongside their classical counterparts (X25519, Ed25519) — no separate migration project required later.
14
+ - **Encrypted at rest, always.** Private key material is never persisted in plaintext. Everything is AES-256-GCM encrypted using a key derived with Argon2id.
15
+ - **Local-first.** Key generation and derivation run entirely offline — no network request is made in the process, verifiable directly in source.
16
+ - **Built for the Majikah ecosystem**, but usable standalone in any TypeScript/JavaScript project.
13
17
 
14
- * **Self-Encrypted at Rest:** Majik Keys are self-encrypted by default. Private keys are **always Argon2id hashed at rest**, which completely protects them from unauthorized access even if the underlying storage medium is compromised.
15
- * **Post-Quantum Ready (ML-KEM):** Generates a deterministic dual-key system from a 64-byte BIP39 seed, featuring **X25519** for legacy compatibility and **ML-KEM-768 (FIPS-203)** for post-quantum key encapsulation.
16
- * **Argon2id Key Derivation:** Private keys at rest are protected by memory-hard **Argon2id (KDF v2)**, configured to defeat GPU/ASIC brute-force attacks (64 MB memory / 3 iterations / 4 parallelism).
17
- * **Seamless Auto-Migration:** Automatically detects and upgrades legacy v1 (PBKDF2) accounts to v2 upon import, deterministically re-deriving missing ML-KEM keys from the seed.
18
-
19
18
  ---
20
19
 
21
- ## The Majik Key
20
+ ## Security Architecture
21
+
22
+ - **Encrypted at rest, not "hashed."** Private keys are **AES-256-GCM encrypted**, using a 256-bit key **derived via Argon2id** from your passphrase. (Argon2id is a key-derivation function, not applied to the private key directly — the private key itself is encrypted, not hashed.)
23
+ - **Argon2id KDF (v2), memory-hard by design.** Passphrase-based encryption uses Argon2id at **64 MB memory / 3 iterations / 4 parallel lanes**, tuned to resist GPU/ASIC brute-force attacks. A WASM implementation (`hash-wasm`) is used when available in the runtime, with an automatic, transparent fallback to a pure-JS implementation (`@noble/hashes`) — output is bit-identical either way, so switching implementations never breaks decryption.
24
+ - **Post-quantum ready.** ML-KEM-768 (FIPS-203) is derived from the full 64-byte BIP-39 seed for encryption/key-encapsulation, and ML-DSA-87 is derived from a domain-separated hash of that same seed for signing — both deterministic and fully recoverable from the mnemonic.
25
+ - **Legacy KDF read support.** Older accounts encrypted with KDF v1 (PBKDF2-SHA256, 200k–250k iterations) can still be unlocked. New accounts, and any account whose passphrase is changed via `updatePassphrase()`, always land on Argon2id (v2).
26
+ - **Full migration path.** `importFromMnemonicBackup()` re-derives a complete identity straight from the mnemonic — X25519, ML-KEM-768, Ed25519, ML-DSA-87, and Bitcoin — and re-encrypts everything with Argon2id in one step, so an old account becomes fully post-quantum capable automatically. A lighter `migrate()` method is also available if you only want to upgrade the KDF version without re-deriving the newer key types.
27
+ - **Isomorphic by design.** Uses native WebCrypto (ECDH/X25519) where the runtime supports it, and transparently falls back to a raw keypair representation where it doesn't (e.g. Node environments without X25519 in WebCrypto) — the public API is identical either way.
28
+ - **Multi-language mnemonics.** BIP-39 wordlists for English, French, Spanish, Italian, Japanese, Korean, Czech, Portuguese, Simplified Chinese, and Traditional Chinese are supported, lazy-loaded per language so you only pay for the ones you use.
29
+
30
+ ---
31
+
32
+ ## Architecture
22
33
 
23
34
  ```mermaid
24
35
  flowchart TD
25
- A[12-word BIP-39 Seed Phrase] --> B[Majik Key]
36
+ A[12/24-word BIP-39 Seed Phrase] --> B[Majik Key]
26
37
 
27
38
  %% Signing branch
28
39
  B --> S[Signing]
@@ -39,6 +50,11 @@ flowchart TD
39
50
  I --> I1[BIP-39]
40
51
  I --> I2[X25519]
41
52
 
53
+ %% Experimental Web3 branch
54
+ B -.-> W[Web3 - Experimental]
55
+ W -.-> W1[Bitcoin - BIP-32/84]
56
+ W -.-> W2[Solana - Ed25519-derived]
57
+
42
58
  %% Products (fan-in)
43
59
  S1 --> P1[Majik Signature]
44
60
  S2 --> P1
@@ -49,6 +65,7 @@ flowchart TD
49
65
  E2 --> P2
50
66
  I1 --> P2
51
67
  I2 --> P2
68
+
52
69
 
53
70
  E1 --> P3[Majik Message]
54
71
  E2 --> P3
@@ -57,50 +74,124 @@ flowchart TD
57
74
  I2 --> P4
58
75
 
59
76
  P4 --> P5[Majik SLink]
60
-
61
77
  ```
62
78
 
63
79
  Your Majik Key is generated entirely offline. No network request is made during key creation — verifiable in source code.
64
80
 
65
81
  ---
66
82
 
67
- ## Experimental Web3 Support
83
+ ## Powering the Majikah Ecosystem
68
84
 
69
- Majik Key features experimental integration for deriving keys natively compatible with modern Web3 ecosystems, specifically **Bitcoin** and **Solana**.
85
+ Majik Key is the shared identity layer underneath every Majikah product. Here's what each one draws from it.
70
86
 
71
- To utilize these features, you must install the optional peer dependencies associated with your target chain:
87
+ ### [Majik Signature](https://majikah.solutions/products/majik-signature) — Flagship
72
88
 
73
- ### Bitcoin
74
- Requires the `@scure/btc-signer` peer dependency.
75
- ```bash
76
- npm install @scure/btc-signer
89
+ **Post-quantum cryptographic file signing and verification.**
90
+
91
+ [![npm](https://img.shields.io/npm/v/@majikah/majik-signature)](https://www.npmjs.com/package/@majikah/majik-signature) [![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-signature)](https://www.npmjs.com/package/@majikah/majik-signature) [![npm bundle size](https://img.shields.io/bundlephobia/min/%40majikah%2Fmajik-signature)](https://bundlephobia.com/package/@majikah/majik-signature) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
92
+
93
+ [![Majik Signature Hero](https://github.com/user-attachments/assets/781bb778-9535-4b1f-bbc5-820550ecc864)](https://signature.majikah.solutions)
94
+
95
+ Majik Signature consumes a Majik Key's **Ed25519** and **ML-DSA-87** signing keypairs to require *both* a classical and a post-quantum signature before a file verifies — hybrid security with forward secrecy against future quantum attacks on either scheme alone.
96
+
97
+ ```typescript
98
+ import { MajikKey } from '@majikah/majik-key';
99
+ import { MajikSignature } from '@majikah/majik-signature';
100
+
101
+ // 1. Sign a file and embed the signature (requires an unlocked key with signing keys)
102
+ const { blob, signature } = await MajikSignature.signFile(myFileBlob, myUnlockedKey, {
103
+ // Optional: restrict future signers
104
+ expectedSigners: [ MajikSignature.expectedSignerFromKey(myUnlockedKey) ]
105
+ });
106
+
107
+ // 2. Verify a signed file's embedded signatures
108
+ const results = await MajikSignature.verifyFile(blob, myUnlockedKey);
109
+ results.forEach(res => {
110
+ console.log(`Signer ${res.signerId} valid?`, res.valid);
111
+ });
112
+
113
+ // 3. Seal a multi-sig file to prevent further signatures
114
+ const { sealInfo } = await MajikSignature.seal(blob, myUnlockedKey);
115
+ console.log("File sealed at:", sealInfo.sealTimestamp);
77
116
  ```
78
117
 
79
- ### Solana
80
- Requires the `@solana/kit` peer dependency.
81
- ```bash
82
- npm install @solana/kit
118
+ ### Majik Message
119
+
120
+ **Post-quantum secure messaging envelopes.**
121
+
122
+ Majik Key derives an ML-KEM-768 keypair specifically so it can be used for Majik Message's v3 secure envelopes — the ML-KEM-768 keypair handles post-quantum key encapsulation, and AES-256-GCM handles the actual payload encryption once a shared secret is established.
123
+
124
+ Majik Key ships a direct integration point for this: `toMajikMessageIdentity()` converts an unlocked key into a `MajikMessageIdentity`, ready to hand to Majik Message.
125
+
126
+ ```typescript
127
+ import { MajikKey } from '@majikah/majik-key';
128
+
129
+ // user: an existing MajikUser instance (from @thezelijah/majik-user)
130
+ const identity = await key.toMajikMessageIdentity(user, {
131
+ label: 'My Device',
132
+ restricted: false,
133
+ });
83
134
  ```
84
135
 
85
- ---
136
+ ### Majik Buwiz
86
137
 
87
- ## Overview
138
+ **Multi-key custody built on the full Majik Key stack.**
88
139
 
89
- Majik Key provides a secure, intuitive way to create, store, and manage mnemonic-based cryptographic identities.
140
+ Majik Buwiz is built on Majik Key's complete key set: Ed25519/ML-DSA-87 for signing, ML-KEM-768/AES-256-GCM for encryption, and X25519/BIP-39 for identity — plus the experimental Bitcoin and Solana keys described below for multi-chain support. Everything a Buwiz account needs is derivable from, and recoverable with, the same mnemonic.
90
141
 
91
- ### Use Cases
92
- * **Majik Message Integration:** Natively generate identities compatible with Majik Message v3 secure envelopes.
93
- * **Cryptographic Identity Management:** Manage multiple identities with deterministic multi-key derivation.
94
- * **Secure Storage & Recovery:** Built-in AES-GCM authenticated encryption for private keys at rest, with secure backup/recovery workflows.
142
+ ### Majik Universal ID & Majik SLink
143
+
144
+ **A portable identity primitive, and shareable links built on top of it.**
145
+
146
+ Majik Universal ID is built on the Identity branch of Majik Key — the BIP-39-derived X25519 keypair, public key, and fingerprint, exportable via `toContact()` as a `MajikContact` for use across apps. Majik SLink extends that identity layer downstream, per the architecture above. Both are separate Majikah packages; consult [majikah.solutions](https://majikah.solutions) for the latest on their APIs.
95
147
 
96
148
  ---
97
149
 
98
- ## Features
150
+ ## Experimental Web3 Support
151
+
152
+ Majik Key can derive **Bitcoin** and **Solana** key material directly from the same mnemonic. This is marked experimental — the shape of the `web3` namespace may change without a major version bump.
153
+
154
+ ### What's built in vs. what needs an extra install
155
+
156
+ - **Bitcoin key derivation is automatic.** Every account created via `create()` or `importFromMnemonicBackup()` also derives and encrypts a Bitcoin keypair (real BIP-32 HD derivation off the raw 64-byte seed, using a Majik-specific domain-separated path by default). This works out of the box — no extra install needed for the private key, public key, or WIF export.
157
+ - **Solana key derivation is on-demand.** Rather than storing a separate Solana keypair, Majik Key derives it deterministically from your Ed25519 signing key each time you access `key.web3.solana` (and caches it in memory for as long as the key stays unlocked). The base58 Solana address also works with no extra install.
158
+ - **The optional peer dependencies are only needed for chain-native address/transaction objects:**
159
+
160
+ | Chain | Peer dependency | Needed for |
161
+ | :--- | :--- | :--- |
162
+ | Bitcoin | `@scure/btc-signer` | Native SegWit (bech32) address encoding, PSBT construction |
163
+ | Solana | `@solana/kit` | Real `KeyPairSigner` instances, kit-native `Address` type |
164
+
165
+ ```bash
166
+ npm install @scure/btc-signer # for Bitcoin addresses
167
+ npm install @solana/kit # for Solana signer/address objects
168
+ ```
169
+
170
+ Everything else — raw key bytes, WIF export, message signing (ECDSA/Schnorr for Bitcoin, Ed25519 for Solana), and base58 Solana addresses — works with zero extra dependencies.
171
+
172
+ ### Two Bitcoin paths, on purpose
173
+
174
+ By default, Bitcoin keys use `MAJIK_BITCOIN_DOMAIN_PATH` — a real, standard BIP-32 derivation, but not the path a generic wallet would derive by default, so it stays effectively private to Majik. Pass `{ standard: true }` to derive the actual BIP-84 mainnet path instead — the address any standard wallet would show for the same mnemonic:
99
175
 
100
- * **Maximum Security:** ML-KEM-768 readiness and Argon2id derivation. Private keys are purged from memory immediately upon calling `.lock()`.
101
- * **BIP39 Compliance:** High-entropy 12 or 24-word seed generation with built-in validation.
102
- * **Isomorphic DX:** First-class TypeScript support, fluent method chaining, and compatibility across Node.js and modern browsers.
103
- * **Portable Storage:** Safe JSON serialization and MnemonicJSON formats that never expose raw private keys.
176
+ ```typescript
177
+ // Majik's default (domain-separated, stored on the key)
178
+ const wif = key.getBitcoinWIF();
179
+
180
+ // The real BIP-84 mainnet key — recoverable in any standard wallet
181
+ const standardBtc = await MajikKey.deriveStandardBitcoinFromMnemonic(mnemonic);
182
+ ```
183
+
184
+ ### Two Solana paths, on purpose
185
+
186
+ By default (`deriveSolanaKeypairFromEdSecretKey`), the Solana keypair is domain-separated from your Ed25519 message-signing key via `SHA256(edSeed || "MajikMessageSolanaSeed")`, so the same private key never secures two different protocols. You can opt into reusing your Ed25519 message-signing key directly instead:
187
+
188
+ ```typescript
189
+ // Recommended: domain-separated Solana key
190
+ const solanaAddress = key.getSolanaAddress();
191
+
192
+ // Opt-in: reuse the Ed25519 message-signing key as-is (not recommended)
193
+ const reusedAddress = key.getSolanaAddress({ reuseMessageKey: true });
194
+ ```
104
195
 
105
196
  ---
106
197
 
@@ -114,28 +205,26 @@ npm install @majikah/majik-key
114
205
 
115
206
  ## Quick Start (Core Identity)
116
207
 
117
- Get up and running with a secure, unlockable account in seconds.
118
-
119
208
  ```typescript
120
209
  import { MajikKey } from '@majikah/majik-key';
121
210
 
122
211
  // 1. Generate & Create
123
- const mnemonic = MajikKey.generateMnemonic(); // Generates 12 words
212
+ const mnemonic = await MajikKey.generateMnemonic(); // 12 words (128-bit)
124
213
  const key = await MajikKey.create(mnemonic, 'super-secure-passphrase', 'My PQ Account');
125
214
 
126
215
  // 2. Access Identity
127
216
  console.log('Fingerprint:', key.fingerprint);
128
217
  console.log('Key ID:', key.id);
129
- console.log('Unlocked?', key.isUnlocked); // true
218
+ console.log('Unlocked?', key.isUnlocked); // true — create() returns an already-unlocked key
130
219
 
131
- // 3. Lock to purge private keys from memory
220
+ // 3. Lock to purge private key material from memory
132
221
  key.lock();
133
222
 
134
- // 4. Unlock when cryptographic operations are needed
223
+ // 4. Unlock again when cryptographic operations are needed
135
224
  await key.unlock('super-secure-passphrase');
136
225
  const privateKeyBase64 = key.getPrivateKeyBase64();
137
226
 
138
- // 5. Safe Storage (Private keys are encrypted at rest)
227
+ // 5. Safe storage — toJSON()/toString() never include raw private keys
139
228
  localStorage.setItem('myKey', key.toString());
140
229
  ```
141
230
 
@@ -145,41 +234,59 @@ localStorage.setItem('myKey', key.toString());
145
234
 
146
235
  ### Static Methods (Lifecycle & Generation)
147
236
 
148
- | Method | Parameters | Returns | Description |
149
- | :--------------------------- | :------------------------------------------- | :------------------ | :-------------------------------------------------- |
150
- | `create()` | `mnemonic`, `passphrase`, `label?` | `Promise<MajikKey>` | Creates a new Argon2id-protected account. |
151
- | `fromJSON()` | `json` | `MajikKey` | Loads a locked key from safe JSON storage. |
152
- | `fromMnemonicJSON()` | `mnemonicJson`, `passphrase`, `label?` | `Promise<MajikKey>` | Auto-migrates legacy accounts to Argon2id + ML-KEM. |
153
- | `importFromMnemonicBackup()` | `backup`, `mnemonic`, `passphrase`, `label?` | `Promise<MajikKey>` | Restores a key from a mnemonic-encrypted string. |
154
- | `generateMnemonic()` | `strength?` *(128 \| 256)* | `string` | Generates a 12 or 24-word BIP39 phrase. |
155
- | `validateMnemonic()` | `mnemonic` | `boolean` | Validates a BIP39 mnemonic phrase. |
237
+ | Method | Parameters | Returns | Description |
238
+ | :--- | :--- | :--- | :--- |
239
+ | `create()` | `mnemonic`, `passphrase`, `label?`, `mnemonicLanguage?` | `Promise<MajikKey>` | Creates a new Argon2id-protected, fully post-quantum-capable account. |
240
+ | `fromJSON()` | `json` | `MajikKey` | Loads a locked key from safe JSON storage. |
241
+ | `fromMnemonicJSON()` | `mnemonicJson`, `passphrase`, `label?` | `Promise<MajikKey>` | Rebuilds a key straight from a portable seed export. |
242
+ | `importFromMnemonicBackup()` | `backup`, `mnemonic`, `passphrase`, `label?`, `mnemonicLanguage?` | `Promise<MajikKey>` | Full migration path — verifies the mnemonic, then re-derives and re-encrypts the complete identity with Argon2id. |
243
+ | `fromDangerousJSON()` | `json` | `MajikKey` | Reconstructs an already-unlocked key from a dangerous export. Server-side only — see warning below. |
244
+ | `generateMnemonic()` | `strength?` *(128 \| 256)*, `language?` | `Promise<string>` | Generates a 12- or 24-word BIP-39 phrase. |
245
+ | `validateMnemonic()` | `mnemonic` | `boolean` | Validates a BIP-39 mnemonic phrase. |
246
+ | `deriveStandardBitcoinFromMnemonic()` *(experimental)* | `mnemonic`, `mnemonicLanguage?` | `Promise<BitcoinKeypairMaterial>` | Derives the real BIP-84 mainnet Bitcoin key without needing a `MajikKey` instance. |
156
247
 
157
248
  ### Instance Methods (State & Management)
158
249
 
159
- | Method | Parameters | Returns | Description |
160
- | :------------------- | :----------------------- | :----------------- | :------------------------------------------------- |
161
- | `unlock()` | `passphrase` | `Promise<this>` | Decrypts keys into memory. Chainable. |
162
- | `lock()` | None | `this` | Purges private keys from memory. Chainable. |
163
- | `verify()` | `passphrase` | `Promise<boolean>` | Tests a passphrase without keeping keys in memory. |
164
- | `updatePassphrase()` | `currentPass`, `newPass` | `Promise<this>` | Changes passphrase and auto-migrates to KDF v2. |
165
- | `updateLabel()` | `newLabel` | `this` | Updates the human-readable account label. |
250
+ | Method | Parameters | Returns | Description |
251
+ | :--- | :--- | :--- | :--- |
252
+ | `unlock()` | `passphrase` | `Promise<this>` | Decrypts keys into memory. Chainable. |
253
+ | `lock()` | None | `this` | Purges all private key material (including cached Web3 keys) from memory. Chainable. |
254
+ | `verify()` | `passphrase` | `Promise<boolean>` | Tests a passphrase without keeping keys in memory or requiring an unlock. |
255
+ | `updatePassphrase()` | `currentPass`, `newPass` | `Promise<this>` | Re-encrypts every stored key under a new passphrase and migrates to KDF v2 if needed. |
256
+ | `migrate()` | `passphrase` | `Promise<this>` | Upgrades the X25519 key's KDF from v1 to v2 only — does **not** add ML-KEM/Ed25519/ML-DSA/Bitcoin keys. Use `importFromMnemonicBackup()` for a full upgrade. |
257
+ | `updateLabel()` | `newLabel` | `this` | Updates the human-readable account label. |
166
258
 
167
259
  ### Export & Integration Methods
168
260
 
169
- | Method | Returns | Description |
170
- | :------------------------- | :------------------------ | :-------------------------------------------------- |
171
- | `toJSON()` / `toString()` | `MajikKeyJSON` / `string` | Safe export for DB/LocalStorage. No raw keys. |
172
- | `toMnemonicJSON()` | `MnemonicJSON` | Portable seed format (Requires key to be unlocked). |
173
- | `exportMnemonicBackup()` | `Promise<string>` | Base64-encoded encrypted backup string. |
174
- | `toContact()` | `MajikContact` | Extracts public identity data for sharing. |
175
- | `toMajikMessageIdentity()` | `Promise<Identity>` | Formats key for direct use in Majik Message. |
261
+ | Method | Returns | Description |
262
+ | :--- | :--- | :--- |
263
+ | `toJSON()` / `toString()` | `MajikKeyJSON` / `string` | Safe export for DB/LocalStorage. No raw keys. |
264
+ | `toDangerousJSON()` | `MajikKeyDangerousJSON` | ⚠️ Contains every raw private key. Server-side secret injection only — see warning below. |
265
+ | `toMnemonicJSON()` | `MnemonicJSON` | ⚠️ Contains the raw mnemonic words (and passphrase, if you pass one) in plaintext — a transport format, not an at-rest storage format. Requires the key to be unlocked. |
266
+ | `exportMnemonicBackup()` | `Promise<string>` | Encrypted backup string, decryptable only with the original mnemonic — used to verify a mnemonic before `importFromMnemonicBackup()` re-derives the identity. |
267
+ | `toContact()` | `MajikContact` | Extracts public identity data for sharing (the basis for Majik Universal ID). |
268
+ | `toMajikMessageIdentity()` | `Promise<MajikMessageIdentity>` | Formats the key for direct use in Majik Message. Requires a `MajikUser`. |
176
269
 
177
270
  ### Instance Getters
178
- *Access public data at any time:*
179
- `id`, `fingerprint`, `publicKey`, `publicKeyBase64`, `label`, `backup`, `timestamp`, `isLocked`, `isUnlocked`, `metadata`.
180
271
 
181
- *Access restricted data (Throws `MajikKeyError` if locked):*
182
- `getPrivateKey()`, `getPrivateKeyBase64()`.
272
+ *Public — available at any time, regardless of lock state:*
273
+
274
+ `id`, `fingerprint`, `publicKey`, `publicKeyBase64`, `label`, `backup`, `timestamp`, `mnemonicLanguage`, `kdfVersion`, `isArgon2id`, `isLocked`, `isUnlocked`, `isFullyUpgraded`, `mlKemPublicKey`, `hasMlKem`, `edPublicKey`, `mlDsaPublicKey`, `hasSigningKeys`, `btcPublicKey`, `hasBitcoin`, `hasSolanaKeypair`, `hasBitcoinKeypair`, `metadata`.
275
+
276
+ *Restricted — throws `MajikKeyError` if locked (or if that key type isn't present, e.g. on an account not yet fully migrated):*
277
+
278
+ `getPrivateKey()`, `getPrivateKeyBase64()`, `getMlKemSecretKey()`, `getEdSecretKey()`, `getMlDsaSecretKey()`, `getBtcSecretKey()`.
279
+
280
+ ### Web3 (Experimental)
281
+
282
+ | Member | Returns | Notes |
283
+ | :--- | :--- | :--- |
284
+ | `web3` *(getter)* | `{ solana, bitcoin? } \| undefined` | `undefined` if locked or has no Ed25519 signing key. `bitcoin` is present only if the account has stored Bitcoin key material. |
285
+ | `getBitcoinKeypairMaterial()` | `BitcoinKeypairMaterial` | Raw Bitcoin keypair bytes. |
286
+ | `getBitcoinWIF()` | `string` | Wallet Import Format string, pastes into any standard Bitcoin wallet. |
287
+ | `getSolanaKeypairMaterial()` | `SolanaKeypairMaterial` | Raw Solana keypair bytes. |
288
+ | `getSolanaKeypair()` | `Promise<any>` | Real `@solana/kit` `KeyPairSigner`. Requires `@solana/kit`. |
289
+ | `getSolanaAddress()` | `string` | Base58 Solana address. No extra dependency required. |
183
290
 
184
291
  ---
185
292
 
@@ -191,17 +298,21 @@ localStorage.setItem('myKey', key.toString());
191
298
  import { MajikKey } from '@majikah/majik-key';
192
299
 
193
300
  // -- EXPORTING --
301
+ // ⚠️ jsonData contains the raw mnemonic (and passphrase, if provided) in
302
+ // plaintext. Treat this exactly like the mnemonic itself — encrypt the
303
+ // file yourself, or keep it offline. This is a transport format, not a
304
+ // safe-storage format.
194
305
  const jsonData = key.toMnemonicJSON(mnemonic, 'password123');
195
306
  const blob = new Blob([JSON.stringify(jsonData)], { type: "application/json" });
196
- // Save blob locally...
307
+ // Save blob to a secure location...
197
308
 
198
309
  // -- RECOVERING --
199
310
  const recoveredData = JSON.parse(await blob.text());
200
311
  const recoveredKey = await MajikKey.importFromMnemonicBackup(
201
312
  recoveredData.id,
202
- recoveredData.seed.join(" "),
313
+ recoveredData.seed.join(" "),
203
314
  recoveredData.phrase,
204
- 'Recovered Key'
315
+ 'Recovered Key',
205
316
  );
206
317
  ```
207
318
 
@@ -219,77 +330,74 @@ if (await key.verify('user-input-password')) {
219
330
  }
220
331
  ```
221
332
 
333
+ ### 3. Server-Side Secret Injection (Dangerous JSON)
222
334
 
223
- ---
224
-
225
- ### [Majik Signature](https://majikah.solutions/products/majik-signature) — Flagship
226
- **Post-quantum cryptographic file signing and verification.**
227
-
228
- [![npm](https://img.shields.io/npm/v/@majikah/majik-signature)](https://www.npmjs.com/package/@majikah/majik-signature) [![npm downloads](https://img.shields.io/npm/dm/@majikah/majik-signature)](https://www.npmjs.com/package/@majikah/majik-signature) [![npm bundle size](https://img.shields.io/bundlephobia/min/%40majikah%2Fmajik-signature)](https://bundlephobia.com/package/@majikah/majik-signature) [![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
229
-
230
- [![Majik Signature Hero](https://github.com/user-attachments/assets/781bb778-9535-4b1f-bbc5-820550ecc864)](https://signature.majikah.solutions)
231
-
232
-
233
- **Majik Signature** is the flagship feature of the ecosystem, providing hybrid classical and post-quantum content signing. It secures your files by requiring both an **Ed25519** (classical) and an **ML-DSA-87** (post-quantum) signature to pass verification.
234
-
235
- * **Hybrid Security:** Verification requires BOTH signatures to pass, ensuring robust forward-secrecy.
236
- * **Embedded Multi-Sig:** Seamlessly embeds signatures into files with full support for multi-signature envelopes.
237
- * **Cryptographic Allowlists:** Establish an expected list of signers. Non-listed signers are rejected cryptographically.
238
- * **Sealing:** The issuer can compute a SHA3-512 seal over the signatories, preventing any further signing attempts.
239
-
240
- ### Majik Signature Quick Start
335
+ `toDangerousJSON()` / `fromDangerousJSON()` skip encryption entirely — no KDF, no AES-GCM, instant reconstruction. This exists for one narrow case: injecting a pre-unlocked signing key into a server process, not for anything that touches a database, log, or the network.
241
336
 
242
337
  ```typescript
243
- import { MajikSignature, MajikKey } from '@majikah/majik-key';
338
+ // At deploy time, generated once and stored in your secrets manager:
339
+ const dangerousJson = unlockedKey.toDangerousJSON();
244
340
 
245
- // 1. Sign a file and embed the signature (Requires an unlocked key with signing keys)
246
- const { blob, signature } = await MajikSignature.signFile(myFileBlob, myUnlockedKey, {
247
- // Optional: restrict future signers
248
- expectedSigners: [ MajikSignature.expectedSignerFromKey(myUnlockedKey) ]
249
- });
341
+ // At server boot:
342
+ const serverKey = MajikKey.fromDangerousJSON(process.env.MAJIK_SIGNING_KEY!);
343
+ // serverKey is already unlocked — no passphrase needed, no KDF cost.
344
+ ```
250
345
 
251
- // 2. Verify a signed file's embedded signatures
252
- const results = await MajikSignature.verifyFile(blob, myUnlockedKey);
346
+ ### 4. Experimental Web3 Usage
253
347
 
254
- results.forEach(res => {
255
- console.log(`Signer ${res.signerId} valid?`, res.valid);
256
- });
348
+ ```typescript
349
+ // Bitcoin — key material is already derived and stored on any account
350
+ console.log('Bitcoin WIF:', key.getBitcoinWIF());
351
+ console.log('Bitcoin address:', await key.web3?.bitcoin?.getBitcoinAddress()); // needs @scure/btc-signer
257
352
 
258
- // 3. Seal a multi-sig file to prevent further signatures
259
- const { sealInfo } = await MajikSignature.seal(blob, myUnlockedKey);
260
- console.log("File sealed at:", sealInfo.sealTimestamp);
353
+ // Solana — derived on demand from your Ed25519 signing key
354
+ console.log('Solana address:', key.getSolanaAddress()); // no extra dependency
355
+ const solanaSigner = await key.getSolanaKeypair(); // needs @solana/kit
261
356
  ```
262
357
 
263
-
264
358
  ---
265
359
 
266
360
  ## Security Best Practices
267
361
 
268
- ✅ **DO:**
269
- * Lock keys (`.lock()`) immediately after signing or decrypting payloads to free memory.
270
- * Utilize `mlKemPublicKey` for all new communication protocols to ensure PQ-readiness.
271
- * Keep `@scure/bip39` and underlying crypto dependencies updated.
362
+ ✅ **DO:**
363
+ - Call `.lock()` immediately after signing or decrypting payloads to free key material from memory.
364
+ - Use `mlKemPublicKey` for all new communication protocols to stay post-quantum ready.
365
+ - Keep `@scure/bip39` and the underlying crypto dependencies up to date.
366
+ - Treat any `toMnemonicJSON()` export, and the mnemonic itself, as the master secret — it recovers everything.
272
367
 
273
- ❌ **DON'T:**
274
- * Log `mnemonic` phrases or `privateKeyBase64` outputs in production environments.
275
- * Use `toDangerousJSON()` unless handling highly specific server-side secret injections.
368
+ ❌ **DON'T:**
369
+ - Log `mnemonic` phrases, `privateKeyBase64`, or any `*SecretKeyBase64` value in production.
370
+ - Use `toDangerousJSON()` / `fromDangerousJSON()` outside of controlled, server-side secret injection.
371
+ - Store the output of `toMnemonicJSON()` unencrypted — it is not the same as `toJSON()`/`toString()`.
276
372
 
277
373
  ---
278
374
 
279
375
  ## Ecosystem
280
376
 
281
- * [Majik Signature Web App](https://signature.majikah.solutions)
282
- * [Majik Signature on Microsoft Store](https://apps.microsoft.com/detail/9pl9g3xzvd1x)
283
- * [Majik Signature Official Repository](https://github.com/Majikah/majik-signature)
377
+ - [Majik Signature Web App](https://signature.majikah.solutions)
378
+ - [Majik Signature on Microsoft Store](https://apps.microsoft.com/detail/9pl9g3xzvd1x)
379
+ - [Majik Signature Official Repository](https://github.com/Majikah/majik-signature)
380
+ - [Majikah Solutions](https://majikah.solutions)
284
381
 
285
382
  ---
286
383
 
287
- ## License & Author
384
+ ## License
288
385
 
289
386
  **License:** [Apache-2.0](LICENSE) — free for personal and commercial use.
290
387
 
388
+ ## Author
389
+
291
390
  Developed by **Josef Elijah Fabian (Zelijah)** | [Majikah Solutions OPC](https://majikah.solutions/about)
292
391
 
293
- * **GitHub:** [@jedlsf](https://github.com/jedlsf)
294
- * **Website:** [https://www.thezelijah.world](https://www.thezelijah.world)
295
- * **Email:** [business@thezelijah.world](mailto:business@thezelijah.world)
392
+
393
+ **Developer**: [Josef Elijah Fabian](https://github.com/jedlsf)
394
+ **GitHub**: [https://github.com/Majikah](https://github.com/Majikah)
395
+ **Project Repository**: [https://github.com/Majikah/majik-signature](https://github.com/Majikah/majik-signature)
396
+
397
+ ---
398
+
399
+ ## Contact
400
+
401
+ - **Business Email**: [business@majikah.solutions](mailto:business@majikah.solutions)
402
+ - **Official Website**: [https://www.thezelijah.world](https://www.thezelijah.world)
403
+ - **Majikah Ecosystem**: [https://majikah.solutions](https://majikah.solutions)
@@ -1,51 +1,135 @@
1
1
  import { MnemonicLanguage } from "./crypto/wordlist";
2
+ /** ISO 8601 timestamp string, e.g. `"2026-07-11T00:00:00.000Z"`. */
2
3
  export type ISODateString = string;
3
4
  export type MajikMessageAccountID = string;
4
5
  export type MajikMessagePublicKey = string;
5
6
  export type MajikMessageChatID = string;
7
+ /** Base64-encoded public key material. Safe to store, log, or transmit. */
8
+ export type MajikKeyAddress = string;
9
+ /** Base64-encoded SHA-256 digest of a MajikKey's X25519 public key. Doubles as the account `id`. */
10
+ export type MajikKeyFingerprint = string;
11
+ /**
12
+ * Safe, serializable snapshot of a MajikKey — what `toJSON()` / `toString()` produce.
13
+ *
14
+ * Every `encrypted*` field is an AES-256-GCM ciphertext (IV + ciphertext,
15
+ * base64-encoded) protected by a passphrase-derived Argon2id key (or legacy
16
+ * PBKDF2, see `kdfVersion`). None of these fields ever contain raw private
17
+ * key material — this shape is safe to persist in a database, localStorage,
18
+ * or anywhere else at rest.
19
+ *
20
+ * Load one of these back into a live instance with `MajikKey.fromJSON()`.
21
+ */
6
22
  export interface MajikKeyJSON {
23
+ /** Account identifier. Equal to `fingerprint` for accounts created by this library. */
7
24
  id: string;
25
+ /** Human-readable, user-editable account name. */
8
26
  label: string;
9
- publicKey: string;
10
- fingerprint: string;
27
+ /** X25519 public key, base64. */
28
+ publicKey: MajikKeyAddress;
29
+ /** SHA-256 fingerprint of `publicKey`. Stable identity anchor for the account. */
30
+ fingerprint: MajikKeyFingerprint;
31
+ /** AES-256-GCM-encrypted X25519 private key (IV + ciphertext), base64. Requires the passphrase to decrypt. */
11
32
  encryptedPrivateKey: string;
33
+ /** Random salt used to derive the passphrase-based encryption key. Shared across all key types on this account. */
12
34
  salt: string;
35
+ /**
36
+ * Encrypted, mnemonic-verification blob (base64 JSON). Decryptable only with
37
+ * the original mnemonic — used internally to verify a supplied mnemonic
38
+ * before `importFromMnemonicBackup()` re-derives the full identity. Not a
39
+ * general-purpose backup of the private key.
40
+ */
13
41
  backup: string;
42
+ /** Account creation time, ISO 8601. */
14
43
  timestamp: string;
44
+ /** KDF used for every `encrypted*` field on this account: `1` = legacy PBKDF2 (read-only), `2` = Argon2id (current). Defaults to `1` if omitted. */
15
45
  kdfVersion?: number;
46
+ /** ML-KEM-768 (FIPS-203) public key, base64. Post-quantum key encapsulation. */
16
47
  mlKemPublicKey?: string;
48
+ /** AES-256-GCM-encrypted ML-KEM-768 secret key, base64. */
17
49
  encryptedMlKemSecretKey?: string;
50
+ /** Ed25519 public key, base64. Classical signing — same keypair the X25519 identity key is converted from. */
18
51
  edPublicKey?: string;
52
+ /** AES-256-GCM-encrypted Ed25519 secret key, base64. */
19
53
  encryptedEdSecretKey?: string;
54
+ /** ML-DSA-87 (FIPS-204) public key, base64. Post-quantum signing. */
20
55
  mlDsaPublicKey?: string;
56
+ /** AES-256-GCM-encrypted ML-DSA-87 secret key, base64. */
21
57
  encryptedMlDsaSecretKey?: string;
58
+ /** @experimental secp256k1 Bitcoin public key, base64. Domain-separated BIP-32/84 derivation by default — see `MajikKeyBitcoinNamespace`. */
22
59
  btcPublicKey?: string;
60
+ /** @experimental AES-256-GCM-encrypted Bitcoin private key, base64. */
23
61
  encryptedBtcSecretKey?: string;
62
+ /** BIP-39 wordlist language the original mnemonic was generated/validated against. Defaults to `"en"`. */
24
63
  mnemonicLanguage?: MnemonicLanguage;
25
64
  }
65
+ /**
66
+ * ⚠️ DANGEROUS. Every field below is a *raw, unencrypted* private key,
67
+ * base64-encoded — no passphrase, no KDF, no AES-GCM. Anyone with this
68
+ * object has full control of the account.
69
+ *
70
+ * Intended for one narrow use case: injecting a pre-unlocked signing key
71
+ * into a server process at boot (e.g. loaded from a secrets manager). Never
72
+ * log, store in a database, send over the network, or write to disk outside
73
+ * of a secrets manager.
74
+ *
75
+ * Produced by `toDangerousJSON()`, consumed by `MajikKey.fromDangerousJSON()`.
76
+ */
26
77
  export interface MajikKeyDangerousJSON extends MajikKeyJSON {
78
+ /** ⚠️ Raw X25519 private key, base64. Unencrypted. */
27
79
  privateKeyBase64: string;
80
+ /** ⚠️ Raw ML-KEM-768 secret key, base64. Unencrypted. */
28
81
  mlKemSecretKeyBase64: string;
82
+ /** ⚠️ Raw Ed25519 secret key, base64. Unencrypted. */
29
83
  edSecretKeyBase64: string;
84
+ /** ⚠️ Raw ML-DSA-87 secret key, base64. Unencrypted. */
30
85
  mlDsaSecretKeyBase64: string;
86
+ /** @experimental ⚠️ Raw Bitcoin private key, base64. Unencrypted. */
31
87
  btcSecretKeyBase64?: string;
32
88
  }
89
+ /**
90
+ * Lightweight, non-secret summary of a MajikKey — useful for account
91
+ * pickers, dashboards, or anywhere you want to display account state
92
+ * without touching encrypted key material. Contains no key bytes at all
93
+ * (not even encrypted ones), so it's cheaper to pass around than `MajikKeyJSON`.
94
+ *
95
+ * Get one via the `metadata` getter on a live `MajikKey` instance.
96
+ */
33
97
  export interface MajikKeyMetadata {
34
98
  id: string;
35
- fingerprint: string;
99
+ fingerprint: MajikKeyFingerprint;
36
100
  label: string;
37
101
  timestamp: Date;
102
+ /** `true` if private key material is currently purged from memory (i.e. `lock()` was called, or it hasn't been `unlock()`ed yet). */
38
103
  isLocked: boolean;
104
+ /** `1` = legacy PBKDF2, `2` = Argon2id. See `MajikKeyJSON.kdfVersion`. */
39
105
  kdfVersion: number;
106
+ /** `true` if this account has ML-KEM-768 keys (i.e. is post-quantum-encryption capable). `false` means it's a legacy account pending migration. */
40
107
  hasMlKem: boolean;
108
+ /** @experimental Presence flags for optional Web3 key material. */
41
109
  web3: {
110
+ /** @experimental `true` if this account has a stored Bitcoin keypair. */
42
111
  hasBitcoin?: boolean;
112
+ /** @experimental `true` if this account can derive a Solana keypair (i.e. has an Ed25519 signing key and is unlocked). */
43
113
  hasSolana?: boolean;
44
114
  };
45
115
  mnemonicLanguage?: MnemonicLanguage;
46
116
  }
117
+ /**
118
+ * Portable seed export — the format behind `toMnemonicJSON()` /
119
+ * `MajikKey.fromMnemonicJSON()`.
120
+ *
121
+ * ⚠️ Unlike `MajikKeyJSON`, this is **not an encrypted-at-rest format**.
122
+ * `seed` is the raw mnemonic, split into words, in plaintext. If a
123
+ * passphrase is included, it's plaintext too. Treat any `MnemonicJSON`
124
+ * exactly like the mnemonic itself — fine for a one-time, protected
125
+ * transport (e.g. into an encrypted file you control), not for long-term
126
+ * storage. Use `MajikKeyJSON` / `toJSON()` for anything persisted at rest.
127
+ */
47
128
  export interface MnemonicJSON {
129
+ /** Raw mnemonic, split into individual words. ⚠️ Plaintext — this *is* the recovery phrase. */
48
130
  seed: string[];
131
+ /** The account's encrypted backup blob (`MajikKeyJSON.backup`), carried along so this object alone is enough to call `importFromMnemonicBackup()`. */
49
132
  id: string;
133
+ /** Optional passphrase, carried in plaintext for convenience during export/import. ⚠️ Not encrypted. */
50
134
  phrase?: string;
51
135
  }