@majikah/majik-key 0.2.13 → 0.2.14
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 +213 -116
- package/dist/core/types.d.ts +87 -3
- package/dist/majik-key.d.ts +192 -38
- package/dist/majik-key.js +142 -48
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -3,26 +3,37 @@
|
|
|
3
3
|
[](https://www.thezelijah.world) 
|
|
4
4
|
   [](https://opensource.org/licenses/Apache-2.0)
|
|
5
5
|
|
|
6
|
-
**Majik Key**
|
|
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
|
-
##
|
|
10
|
+
## Why Majik Key
|
|
11
11
|
|
|
12
|
-
|
|
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
|
-
##
|
|
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
|
-
##
|
|
83
|
+
## Powering the Majikah Ecosystem
|
|
68
84
|
|
|
69
|
-
Majik Key
|
|
85
|
+
Majik Key is the shared identity layer underneath every Majikah product. Here's what each one draws from it.
|
|
70
86
|
|
|
71
|
-
|
|
87
|
+
### [Majik Signature](https://majikah.solutions/products/majik-signature) — Flagship
|
|
72
88
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
89
|
+
**Post-quantum cryptographic file signing and verification.**
|
|
90
|
+
|
|
91
|
+
[](https://www.npmjs.com/package/@majikah/majik-signature) [](https://www.npmjs.com/package/@majikah/majik-signature) [](https://bundlephobia.com/package/@majikah/majik-signature) [](https://opensource.org/licenses/Apache-2.0)
|
|
92
|
+
|
|
93
|
+
[](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
|
-
###
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
137
|
+
|
|
138
|
+
**Multi-key custody built on the full Majik Key stack.**
|
|
86
139
|
|
|
87
|
-
|
|
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.
|
|
88
141
|
|
|
89
|
-
Majik
|
|
142
|
+
### Majik Universal ID & Majik SLink
|
|
90
143
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* **Secure Storage & Recovery:** Built-in AES-GCM authenticated encryption for private keys at rest, with secure backup/recovery workflows.
|
|
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
|
-
##
|
|
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:**
|
|
99
159
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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:
|
|
175
|
+
|
|
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(); //
|
|
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
|
|
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
|
|
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
|
|
149
|
-
|
|
|
150
|
-
| `create()`
|
|
151
|
-
| `fromJSON()`
|
|
152
|
-
| `fromMnemonicJSON()`
|
|
153
|
-
| `importFromMnemonicBackup()` | `backup`, `mnemonic`, `passphrase`, `label?` | `Promise<MajikKey>` |
|
|
154
|
-
| `
|
|
155
|
-
| `
|
|
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
|
|
160
|
-
|
|
|
161
|
-
| `unlock()`
|
|
162
|
-
| `lock()`
|
|
163
|
-
| `verify()`
|
|
164
|
-
| `updatePassphrase()` | `currentPass`, `newPass` | `Promise<this>`
|
|
165
|
-
| `
|
|
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
|
|
170
|
-
|
|
|
171
|
-
| `toJSON()` / `toString()`
|
|
172
|
-
| `
|
|
173
|
-
| `
|
|
174
|
-
| `
|
|
175
|
-
| `
|
|
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
|
-
*
|
|
182
|
-
|
|
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
|
|
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,68 +330,54 @@ 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
|
-
[](https://www.npmjs.com/package/@majikah/majik-signature) [](https://www.npmjs.com/package/@majikah/majik-signature) [](https://bundlephobia.com/package/@majikah/majik-signature) [](https://opensource.org/licenses/Apache-2.0)
|
|
229
|
-
|
|
230
|
-
[](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
|
-
|
|
338
|
+
// At deploy time, generated once and stored in your secrets manager:
|
|
339
|
+
const dangerousJson = unlockedKey.toDangerousJSON();
|
|
244
340
|
|
|
245
|
-
//
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
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
|
-
|
|
252
|
-
const results = await MajikSignature.verifyFile(blob, myUnlockedKey);
|
|
346
|
+
### 4. Experimental Web3 Usage
|
|
253
347
|
|
|
254
|
-
|
|
255
|
-
|
|
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
|
-
//
|
|
259
|
-
|
|
260
|
-
|
|
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
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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
|
-
|
|
275
|
-
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
|
|
@@ -290,6 +387,6 @@ console.log("File sealed at:", sealInfo.sealTimestamp);
|
|
|
290
387
|
|
|
291
388
|
Developed by **Josef Elijah Fabian (Zelijah)** | [Majikah Solutions OPC](https://majikah.solutions/about)
|
|
292
389
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
390
|
+
- **GitHub:** [@jedlsf](https://github.com/jedlsf)
|
|
391
|
+
- **Website:** [https://www.thezelijah.world](https://www.thezelijah.world)
|
|
392
|
+
- **Email:** [business@thezelijah.world](mailto:business@thezelijah.world)
|
package/dist/core/types.d.ts
CHANGED
|
@@ -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
|
-
|
|
10
|
-
|
|
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:
|
|
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
|
}
|