@majikah/majik-key
v0.1.8
Published
A post-quantum ready seed phrase account library for the Majikah ecosystem. Manages deterministic X25519 and ML-KEM-768 identities with Argon2id protection and seamless legacy account migration.
Maintainers
Readme
Majik Key
Majik Key is a next-generation seed phrase account library for creating and managing mnemonic-based identities. It provides a post-quantum ready, high-security bridge between BIP39 mnemonics and the Majikah ecosystem.
- Majik Key
Next-Gen Security Architecture
Majik Key has been upgraded to meet modern and future cryptographic standards.
1. Post-Quantum Ready (ML-KEM)
Every identity now generates a dual-key system derived deterministically from a 64-byte BIP39 seed:
- X25519 (Curve25519): Derived from the first 32 bytes of the seed. Used for fingerprints, contact identity, and legacy compatibility.
- ML-KEM-768 (FIPS-203): Derived from the full 64-byte seed. Provides post-quantum key encapsulation for "v3" secure envelopes.
2. Argon2id Key Derivation
We have transitioned to Argon2id (KDF v2) for encrypting private keys at rest.
- Memory-Hard: Configured with 64 MB of memory, 3 iterations, and 4 parallelism factors.
- Brute-Force Resistant: Engineered to defeat GPU and ASIC-based cracking attempts that easily bypass older PBKDF2 implementations.
3. Seamless Auto-Migration
The library handles "Security Debt" automatically during standard workflows:
- On Import:
importFromMnemonicBackup()detects v1 (PBKDF2) accounts and performs a full upgrade to v2. - Deterministic Recovery: If ML-KEM keys are missing from an old backup, they are re-derived from the mnemonic during the upgrade process.
- Password Updates: Changing a passphrase via
updatePassphrase()automatically migrates the account to the latest Argon2id standard.
Overview
Majik Key is a comprehensive library for managing seed phrase-based cryptographic accounts. It provides a secure, intuitive way to create, store, and manage mnemonic-based identities with built-in encryption, backup, and recovery features.
What is a Majik Key?
A Majik Key is a seed phrase account that:
- Derives cryptographic key pairs from BIP39 mnemonic phrases
- Encrypts private keys at rest with a user-defined passphrase
- Supports secure backup and recovery via mnemonic encryption
- Provides locked/unlocked state management for enhanced security
- Is fully compatible with Majik Message and other Majikah products
Use Cases
- Majik Message Integration: Create seed phrase accounts that can be imported directly into Majik Message
- Cryptographic Identity Management: Manage multiple identities with deterministic key derivation
- Secure Messaging: Generate signing keys for end-to-end encrypted communication
- Blockchain Applications: Create wallet-like accounts from mnemonic phrases
- Majikah Ecosystem: Use across all Majikah products and services
Features
Security & Post-Quantum Readiness
- Post-Quantum Ready: Implements ML-KEM-768 (FIPS-203) for key encapsulation, ensuring identities are secure against future quantum computing threats.
- Argon2id Key Derivation: Uses memory-hard Argon2id (KDF v2) for passphrase encryption (64 MB / 3 iterations / 4 parallelism), providing industry-leading resistance to GPU/ASIC brute-force attacks.
- Seamless Auto-Migration: Automatically detects and upgrades legacy v1 (PBKDF2) accounts to v2 (Argon2id) during import, re-deriving missing ML-KEM keys deterministically from the seed.
- AES-GCM Authenticated Encryption: Industry-standard encryption for private keys at rest with unique, per-identity salts and random IVs.
- Locked/Unlocked States: Private keys are only decrypted into memory when explicitly unlocked and are purged immediately upon calling
.lock().
BIP39 Compliance & Key Derivation
- Standard Mnemonic Generation: Generate high-entropy 12 or 24-word seed phrases (128/256-bit strength).
- Deterministic Multi-Key Derivation:
- X25519 (Curve25519): Derived from the first 32 bytes of the seed for legacy compatibility and fingerprints.
- ML-KEM-768: Derived from the full 64-byte seed for post-quantum security.
- Built-in Validation: Full BIP39 mnemonic validation and error handling.
Developer Experience
- First-Class TypeScript Support: Full type definitions included for all interfaces and classes.
- Fluent API: Intuitive method chaining for common operations (e.g.,
key.unlock(p).updateLabel(l)). - Comprehensive Error Handling: Specialized
MajikKeyErrorandCryptoErrorclasses for precise debugging. - Isomorphic Support: Works across Node.js and modern browser environments.
Import / Export & Storage
- Security-Minded JSON Serialization: Export accounts to JSON format for storage without ever exposing raw private keys or seed phrases.
- MnemonicJSON Format: A secure, portable format for seed phrase storage and recovery.
- Mnemonic-Encrypted Backups: Export and import specialized backup strings that utilize the mnemonic as a secondary encryption layer.
Interoperability
- Majik Message Integration: Native support for exporting identities compatible with Majik Message v3 envelopes.
- Contact Portability: Convert Majik Keys directly into contact formats for easy sharing of public identities.
- Ecosystem Ready: Designed as the core identity provider for all current and future Majikah products.
Installation
# Using npm
npm install @majikah/majik-key
Quick Start
import { MajikKey } from '@majikah/majik-key';
// Generate a new mnemonic
const mnemonic = MajikKey.generateMnemonic(); // 12 words
console.log('Save this mnemonic:', mnemonic);
// Create a new Majik Key (unlocked state)
const key = await MajikKey.create(mnemonic, 'secure-passphrase', 'My PQ Account');
// 2. Access your keys (requires unlock)
console.log('Fingerprint:', key.fingerprint);
console.log('PQ Ready:', key.metadata.kdfVersion); // 'argon2id'
console.log('Key ID:', key.id);
console.log('Is Unlocked:', key.isUnlocked); // true
// Lock the key (clear private keys from memory)
key.lock();
console.log('Is Locked:', key.isLocked); // true
// Unlock when needed
await key.unlock('my-secure-passphrase');
console.log('Is Unlocked:', key.isUnlocked); // true
// Access private key (only when unlocked)
const privateKey = key.getPrivateKey();
const privateKeyBase64 = key.getPrivateKeyBase64();
// Save to storage (private keys never included)
const json = key.toJSON();
localStorage.setItem('myKey', JSON.stringify(json));
// Load from storage (locked state)
const loadedKey = MajikKey.fromJSON(json);
await loadedKey.unlock('my-secure-passphrase');API Reference
Static Methods
MajikKey.create(mnemonic, passphrase, label?)
Create a new Majik Key from a mnemonic phrase. Generates a new Argon2id-protected account.
Parameters:
mnemonic: string- BIP39 mnemonic phrase (12-24 words)passphrase: string- Passphrase to encrypt the private key at restlabel?: string- Optional label for the key
Returns: Promise<MajikKey> - A new unlocked MajikKey instance
Example:
const mnemonic = 'witch collapse practice feed shame open despair creek road again ice least';
const key = await MajikKey.create(mnemonic, 'my-password', 'Personal Account');
MajikKey.fromJSON(json)
Load a Majik Key from JSON (locked state).
Parameters:
json: MajikKeyJSON | string- JSON object or string
Returns: MajikKey - A locked MajikKey instance
Example:
const json = localStorage.getItem('myKey');
const key = MajikKey.fromJSON(json);
await key.unlock('my-password');MajikKey.fromMnemonicJSON(mnemonicJson, passphrase, label?)
Create a Majik Key from MnemonicJSON format. Auto-migrates legacy PBKDF2 accounts to Argon2id + ML-KEM.
Parameters:
mnemonicJson: MnemonicJSON | string- MnemonicJSON object or stringpassphrase: string- Passphrase to encrypt the key at restlabel?: string- Optional label for the key
Returns: Promise<MajikKey> - A new unlocked MajikKey instance
Example:
const mnemonicData = {
id: 'backup-id',
seed: ['word1', 'word2', ...],
phrase: 'optional-encryption-phrase'
};
const key = await MajikKey.fromMnemonicJSON(mnemonicData, 'my-password');MajikKey.importFromMnemonicBackup(backup, mnemonic, passphrase, label?)
Import a Majik Key from a mnemonic-encrypted backup. Auto-migrates legacy PBKDF2 accounts to Argon2id + ML-KEM.
Parameters:
backup: string- Base64-encoded backup stringmnemonic: string- The mnemonic phrase used to encrypt the backuppassphrase: string- Passphrase to encrypt the imported keylabel?: string- Optional label for the key
Returns: Promise<MajikKey> - A new unlocked MajikKey instance
Example:
const backupString = 'eT8xY2F...'; // From exportMnemonicBackup()
const key = await MajikKey.importFromMnemonicBackup(
backupString,
mnemonic,
'new-password',
'Restored Account'
);MajikKey.generateMnemonic(strength?)
Generate a new BIP39 mnemonic phrase.
Parameters:
strength?: 128 | 256- Entropy strength (128 = 12 words, 256 = 24 words). Default: 128
Returns: string - A new mnemonic phrase
Example:
const mnemonic12 = MajikKey.generateMnemonic(); // 12 words
const mnemonic24 = MajikKey.generateMnemonic(256); // 24 wordsMajikKey.validateMnemonic(mnemonic)
Validate a BIP39 mnemonic phrase.
Parameters:
mnemonic: string- Mnemonic phrase to validate
Returns: boolean - true if valid, false otherwise
Example:
const isValid = MajikKey.validateMnemonic('witch collapse practice...');Instance Methods
unlock(passphrase)
Unlock the Majik Key by decrypting the private key. Decrypts X25519 and ML-KEM private keys into memory.
Parameters:
passphrase: string- Passphrase to decrypt the private key
Returns: Promise<this> - This instance for chaining
Throws: MajikKeyError if passphrase is incorrect or key is already unlocked
Example:
await key.unlock('my-password');lock()
Lock the Majik Key by clearing private keys from memory.
Returns: this - This instance for chaining
Example:
key.lock();verify(passphrase)
Verify that a passphrase can decrypt the private key.
Parameters:
passphrase: string- Passphrase to verify
Returns: Promise<boolean> - true if valid, false otherwise
Example:
const isValid = await key.verify('my-password');updateLabel(newLabel)
Update the label of the Majik Key.
Parameters:
newLabel: string- New label value
Returns: this - This instance for chaining
Example:
key.updateLabel('Work Account');updatePassphrase(currentPassphrase, newPassphrase)
Change the passphrase used to encrypt the private key. Re-encrypts keys and triggers an auto-migration to KDF v2.
Parameters:
currentPassphrase: string- Current passphrasenewPassphrase: string- New passphrase
Returns: Promise<this> - This instance for chaining
Throws: MajikKeyError if current passphrase is incorrect
Example:
await key.updatePassphrase('old-password', 'new-password');getPrivateKey()
Get the private key (only when unlocked).
Returns: CryptoKey | { raw: Uint8Array } - The private key
Throws: MajikKeyError if the key is locked
Example:
const privateKey = key.getPrivateKey();getPrivateKeyBase64()
Get the private key as base64 (only when unlocked).
Returns: string - The private key in base64 format
Throws: MajikKeyError if the key is locked
Example:
const privateKeyBase64 = key.getPrivateKeyBase64();toJSON()
Export to JSON format (safe for storage).
Returns: MajikKeyJSON - JSON representation (private keys never included)
Example:
const json = key.toJSON();
localStorage.setItem('myKey', JSON.stringify(json));toString(pretty?)
Export to JSON string.
Parameters:
pretty?: boolean- Whether to pretty-print. Default: false
Returns: string - JSON string representation
Example:
const jsonString = key.toString(true);toMnemonicJSON(mnemonic, passphrase?)
Export to MnemonicJSON format.
Parameters:
mnemonic: string- The BIP39 mnemonic phrasepassphrase?: string- Optional passphrase
Returns: MnemonicJSON - MnemonicJSON object
Throws: MajikKeyError if the key is locked
Example:
const mnemonicData = key.toMnemonicJSON(mnemonic, 'encryption-phrase');exportMnemonicBackup(mnemonic)
Export a mnemonic-encrypted backup.
Parameters:
mnemonic: string- The original mnemonic phrase
Returns: Promise<string> - Base64-encoded backup string
Throws: MajikKeyError if the key is locked
Example:
const backup = await key.exportMnemonicBackup(mnemonic);toContact()
Create a MajikContact from this Majik Key.
Returns: MajikContact - A MajikContact instance
Example:
const contact = key.toContact();toMajikMessageIdentity(user, options?)
Convert to MajikMessageIdentity for use in Majik Message.
Parameters:
user: MajikUser- MajikUser instanceoptions?: { label?: string, restricted?: boolean }- Optional configuration
Returns: Promise<MajikMessageIdentity> - MajikMessageIdentity instance
Example:
const identity = await key.toMajikMessageIdentity(user, {
label: 'My Account',
restricted: false
});Getters
id: string
The unique identifier (fingerprint).
fingerprint: string
The cryptographic fingerprint.
publicKey: CryptoKey | { raw: Uint8Array }
The public key.
publicKeyBase64: string
The public key in base64 format.
label: string
The user-defined label.
backup: string
The mnemonic backup identifier.
timestamp: Date
The creation timestamp.
isLocked: boolean
Whether the key is currently locked.
isUnlocked: boolean
Whether the key is currently unlocked.
metadata: MajikKeyMetadata
Safe metadata object (no sensitive data).
Example:
console.log(key.metadata);
// {
// id: 'fingerprint-id',
// fingerprint: 'fingerprint-id',
// label: 'My Key',
// timestamp: Date,
// isLocked: false
// }Usage Examples
Example 1: Create and Manage a Key
import { MajikKey } from '@majikah/majik-key';
async function createKey() {
// Generate mnemonic
const mnemonic = MajikKey.generateMnemonic();
console.log('🔑 Save this mnemonic safely:', mnemonic);
// Create key
const key = await MajikKey.create(
mnemonic,
'secure-passphrase',
'Personal Account'
);
console.log('✅ Key created!');
console.log('ID:', key.id);
console.log('Fingerprint:', key.fingerprint);
console.log('Label:', key.label);
// Save to storage
const json = key.toJSON();
localStorage.setItem('myKey', JSON.stringify(json));
return { key, mnemonic };
}
createKey();Example 2: Lock/Unlock Pattern
import { MajikKey } from '@majikah/majik-key';
async function secureLockPattern() {
const json = localStorage.getItem('myKey');
const key = MajikKey.fromJSON(json);
// Key is locked by default when loaded from JSON
console.log('Locked:', key.isLocked); // true
try {
// This will throw an error
const privateKey = key.getPrivateKey();
} catch (error) {
console.log('❌ Cannot access private key when locked');
}
// Unlock to use private key
await key.unlock('secure-passphrase');
console.log('Unlocked:', key.isUnlocked); // true
// Now we can access private keys
const privateKey = key.getPrivateKey();
const privateKeyBase64 = key.getPrivateKeyBase64();
// Use the key for cryptographic operations
// ...
// Lock again when done
key.lock();
console.log('🔒 Key locked again');
}
secureLockPattern();Example 3: Backup and Recovery
import { MajikKey } from '@majikah/majik-key';
async function backupAndRecover() {
const mnemonic = MajikKey.generateMnemonic();
const key = await MajikKey.create(mnemonic, 'password123', 'Original Key');
//Download as Blob JSON File
const jsonData = await key.toMnemonicJSON(mnemonic, 'password123');
const jsonString = JSON.stringify(jsonData);
const blob = new Blob([jsonString], {
type: "application/json;charset=utf-8",
});
downloadBlob(
blob,
"json",
`${label} | ${key.id} | SEED KEY`,
);
// Later... recover from backup
//Parse the downloaded JSON into this object
const jsonData: MnemonicJSON = {
id: "abc123",
seed: ["word1", "word2", ...],
phrase: 'password123',
};
const recoveredKey = await MajikKey.importFromMnemonicBackup(
jsonData.id,
seedArrayToString(jsonData.seed),
jsonData.phrase,
'Recovered Key'
);
console.log('✅ Key recovered!');
console.log('Same fingerprint:', key.fingerprint === recoveredKey.fingerprint);
}
backupAndRecover();Example 4: Update Passphrase
import { MajikKey } from '@majikah/majik-key';
async function changePassphrase() {
const json = localStorage.getItem('myKey');
const key = MajikKey.fromJSON(json);
// Must unlock first
await key.unlock('old-password');
// Change passphrase
await key.updatePassphrase('old-password', 'new-secure-password');
console.log('✅ Passphrase updated!');
// Save updated key
localStorage.setItem('myKey', JSON.stringify(key.toJSON()));
// Verify new passphrase works
key.lock();
await key.unlock('new-secure-password');
console.log('✅ New passphrase verified!');
}
changePassphrase();Example 5: Verify Passphrase
import { MajikKey } from '@majikah/majik-key';
async function verifyPassphrase() {
const json = localStorage.getItem('myKey');
const key = MajikKey.fromJSON(json);
// Verify without unlocking
const isValid = await key.verify('user-entered-password');
if (isValid) {
console.log('✅ Passphrase is correct');
await key.unlock('user-entered-password');
// Proceed with operations...
} else {
console.log('❌ Invalid passphrase');
// Show error to user
}
}
verifyPassphrase();Integration with Majik Message
Majik Key is fully compatible with Majik Message as its seed phrase account implementation. Keys created with Majik Key can be directly imported into Majik Message.
Importing to Majik Message
import { MajikKey } from '@majikah/majik-key';
async function importToMajikMessage() {
// Create or load a Majik Key
const mnemonic = MajikKey.generateMnemonic();
const key = await MajikKey.create(mnemonic, 'password', 'Message Account');
// Export to MnemonicJSON format for Majik Message
const mnemonicData = key.toMnemonicJSON(mnemonic, 'password');
const jsonString = JSON.stringify(mnemonicData);
// Download this blob as a JSON locally
const blob = new Blob([jsonString], {
type: "application/json;charset=utf-8",
});
// This mnemonicData can be imported directly into Majik Message
// as a seed phrase account
console.log('Import the saved JSON to Majik Message:', mnemonicData);
}Converting to Majik Message Identity
import { MajikKey } from '@majikah/majik-key';
import { MajikUser } from '@thezelijah/majik-user';
async function createMessageIdentity() {
const mnemonic = MajikKey.generateMnemonic();
const key = await MajikKey.create(mnemonic, 'password', 'Message Identity');
// Create/parse a MajikUser instance
const user = new MajikUser({
username: 'myusername',
// ... other user properties
});
// Convert to Majik Message Identity
const identity = await key.toMajikMessageIdentity(user, {
label: 'My Message Account',
restricted: false
});
console.log('Majik Message Identity created:', identity);
}Security Considerations
Best Practices
Never expose mnemonics: Treat mnemonic phrases like root passwords. Never log or store them unencrypted.
Lock when not in use: Always call .lock() when private key access is no longer required to purge the heap.
PQ Readiness: For all new communication protocols, ensure you are utilizing the mlKemPublicKey.
Security Summary
Primary KDF: Argon2id (64 / 3t / 4p).
Legacy KDF: PBKDF2-SHA256 (250,000 iterations).
Encryption: AES-256-GCM with unique salts and IVs.
Post-Quantum: ML-KEM-768 (Lattice-based cryptography).
What NOT to Do
❌ DON'T store mnemonics in code or version control
❌ DON'T transmit mnemonics over insecure channels
❌ DON'T use weak passphrases like "password123"
❌ DON'T share mnemonics or passphrases with anyone
❌ DON'T screenshot or photograph mnemonics
What TO Do
✅ DO use password managers for mnemonic storage
✅ DO write mnemonics on paper and store securely
✅ DO use hardware security modules when possible
✅ DO test recovery procedures before relying on them
✅ DO keep multiple encrypted backups in different locations
Tips & Reminders
For Developers
Remember: Always validate user input before creating or unlocking keys.
Security: Never log sensitive data (mnemonics, private keys, passphrases) in production.
Performance: Lock keys when not in use to free memory and reduce attack surface.
Testing: Test backup/recovery procedures in development before deploying to production.
Dependencies: Keep
@scure/bip39and other crypto dependencies up to date.
For Users
Backup: Always keep multiple backups of your mnemonic phrase in secure locations.
Passphrase: Use a strong, unique passphrase for each Majik Key.
Recovery: Test your ability to recover keys from backups before you need to.
Organization: Use meaningful labels to identify different keys.
Loss Prevention: Losing your mnemonic phrase means permanent loss of access to your key.
Related Projects
Majik Message
Secure messaging platform using Majik Keys
Read more about Majik Message here
Click the image to try Majik Message live.
Also available on Microsoft Store for free.
Official Repository SDK Library
Contributing
If you want to contribute or help extend support to more platforms, reach out via email. All contributions are welcome!
License
Apache-2.0 — free for personal and commercial use.
Author
Made with 💙 by @thezelijah
About the Developer
- Developer: Josef Elijah Fabian
- GitHub: https://github.com/jedlsf
- Project Repository: https://github.com/jedlsf/majik-key
Contact
- Business Email: [email protected]
- Official Website: https://www.thezelijah.world
