Post-Quantum Cryptography wallet library for Ethereum/Quorum with BIP-39 mnemonic, ML-DSA-65 keys, and Bech32m addresses
npm install @cheny56/pqc-walletA Post-Quantum Cryptography wallet library for Ethereum/Quorum with BIP-39 mnemonic support, ML-DSA (Dilithium) keys, and Bech32m addresses.
- BIP-39 Mnemonic: Standard 12/24-word mnemonic generation and recovery
- Quantum-Safe Keys: ML-DSA (NIST FIPS 204) post-quantum signatures
- Multiple Algorithms: ML-DSA-44, ML-DSA-65, ML-DSA-87 support
- Hybrid Scheme: Combined ECDSA + PQC for gradual migration
- Bech32m Addresses: Human-readable addresses with error detection
- Explicit Algorithm HRPs: Algorithm-specific address encoding
- Multi-Algorithm Future: Support for SLH-DSA and FN-DSA HRPs
- 32-byte Addresses: Enhanced security with full hash addresses
- TypeScript: Full type definitions included
``bash`
npm install @pqc-chain/wallet
`typescript
import { createWallet, restoreWallet } from '@pqc-chain/wallet';
// Create a new hybrid wallet (ECDSA + PQC)
const wallet = createWallet({ addressScheme: 'hybrid' });
console.log('Mnemonic:', wallet.mnemonic);
// Get first address
const address = wallet.getAddress(0);
console.log('Address:', address.address); // pqch1p... (Bech32m)
console.log('Legacy:', address.legacyAddress); // pqch1q... (Bech32)
// Restore from existing mnemonic
const restored = restoreWallet('abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about');
`
The library supports all three ML-DSA (Dilithium) variants from NIST FIPS 204:
| Algorithm | Security Level | Public Key | Private Key | Signature | Use Case |
|-----------|---------------|------------|-------------|-----------|----------|
| ML-DSA-44 | NIST Level 2 | 1,312 bytes | 2,560 bytes | 2,420 bytes | IoT, constrained devices |
| ML-DSA-65 | NIST Level 3 | 1,952 bytes | 4,032 bytes | 3,309 bytes | Recommended (default) |
| ML-DSA-87 | NIST Level 5 | 2,592 bytes | 4,896 bytes | 4,627 bytes | High-value, long-term |
`typescript`
// Use different algorithms
const wallet44 = createWallet({ pqcAlgorithm: 'ML-DSA-44' });
const wallet65 = createWallet({ pqcAlgorithm: 'ML-DSA-65' }); // default
const wallet87 = createWallet({ pqcAlgorithm: 'ML-DSA-87' });
| Strength | Words | Entropy | Checksum | Security |
|----------|-------|---------|----------|----------|
| 128 bits | 12 | 128 | 4 bits | Standard |
| 256 bits | 24 | 256 | 8 bits | High |
The seed is always 64 bytes (512 bits), derived using PBKDF2-HMAC-SHA512 with 2048 iterations.
#### ECDSA (secp256k1)
- Method: BIP-32/BIP-44 hierarchical deterministic derivation
- Default Path: m/44'/60'/0'/0 (Ethereum compatible)
- Private Key: 32 bytes
- Public Key: 33 bytes (compressed) / 65 bytes (uncompressed)
#### PQC (ML-DSA)
- Method: HKDF-SHA256 with domain separation
- Domain: pqc-wallet-v1 (default){domain}/{algorithm}/key/{index}
- Info Format:
``
HKDF-Expand(seed, info="pqc-wallet-v1/ML-DSA-65/key/{index}", length=32) → ML-DSA keygen seed
The library supports two HRP naming conventions for maximum flexibility.
#### HRP Format Overview
`
Address Format:
HRP Types:
1. Generic (algorithm auto-detected): pqc, pqch
2. Explicit (algorithm embedded): mld65, slh128s, fnd512, etc.
Version Characters:
q - Version 0 (20 bytes, legacy, Bech32)
p - Version 1 (32 bytes, quantum-safe, Bech32m)
`
#### Generic HRPs (Auto-detect Algorithm)
| Type | Version | Bytes | Format | Example |
|------|---------|-------|--------|---------|
| PQC (legacy) | 0 | 20 | pqc1q | pqc1q... |pqc1p
| PQC (quantum-safe) | 1 | 32 | | pqc1p... |pqch1q
| Hybrid (legacy) | 0 | 20 | | pqch1q... |pqch1p
| Hybrid (quantum-safe) | 1 | 32 | | pqch1p... |tpqc1...
| Testnet PQC | 0/1 | 20/32 | | tpqc1p... |tpqch1...
| Testnet Hybrid | 0/1 | 20/32 | | tpqch1p... |
#### Explicit Algorithm HRPs
The explicit format embeds the algorithm in the address HRP:
##### ML-DSA (Dilithium) - NIST FIPS 204
| Algorithm | PQC HRP | Hybrid HRP | Testnet PQC | Testnet Hybrid |
|-----------|---------|------------|-------------|----------------|
| ML-DSA-44 | mld44 | mld44h | tmld44 | tmld44h |mld65
| ML-DSA-65 | | mld65h | tmld65 | tmld65h |mld87
| ML-DSA-87 | | mld87h | tmld87 | tmld87h |
##### SLH-DSA (SPHINCS+) - NIST FIPS 205
| Algorithm | PQC HRP | Hybrid HRP | Description |
|-----------|---------|------------|-------------|
| SLH-DSA-128s | slh128s | slh128sh | Small signatures, slower |slh128f
| SLH-DSA-128f | | slh128fh | Fast signatures, larger |slh192s
| SLH-DSA-192s | | slh192sh | Medium security, small |slh192f
| SLH-DSA-192f | | slh192fh | Medium security, fast |slh256s
| SLH-DSA-256s | | slh256sh | Highest security, small |slh256f
| SLH-DSA-256f | | slh256fh | Highest security, fast |
##### FN-DSA (Falcon) - Expected NIST 2024
| Algorithm | PQC HRP | Hybrid HRP | Description |
|-----------|---------|------------|-------------|
| FN-DSA-512 | fnd512 | fnd512h | Compact signatures |fnd1024
| FN-DSA-1024 | | fnd1024h | Maximum security |
#### Using Explicit Algorithm HRPs
`typescript
import {
createWallet,
createPQCAddress,
parseAddressWithAlgorithm,
getHRPForFullAlgorithm
} from '@pqc-chain/wallet';
// Method 1: Create wallet with explicit algorithm in addresses
const wallet = createWallet({
addressScheme: 'hybrid',
pqcAlgorithm: 'ML-DSA-65',
useExplicitAlgorithmHRP: true // Addresses will start with mld65h1p...
});
// Method 2: Create individual addresses with explicit algorithm
const address = createPQCAddress(publicKey, {
explicitAlgorithm: 'ML-DSA-65' // Results in mld651p...
});
// Parse address to extract algorithm hint
const parsed = parseAddressWithAlgorithm('mld651p...');
console.log(parsed.algorithm); // 'ML-DSA-65'
console.log(parsed.hrp); // 'mld65'
// Get HRP for any algorithm
const hrp = getHRPForFullAlgorithm('SLH-DSA-128s', false, false); // 'slh128s'
const hybridHrp = getHRPForFullAlgorithm('FN-DSA-512', true, false); // 'fnd512h'
`
#### When to Use Each HRP Style
| Scenario | Recommended HRP | Reason |
|----------|-----------------|--------|
| General use | Generic (pqc, pqch) | Maximum compatibility |mld65
| Multi-algorithm deployment | Explicit (, etc.) | Clear algorithm identification |
| Debugging | Explicit | Immediate algorithm visibility |
| UI display | Explicit | Better user understanding |
| Future-proofing | Generic | Flexibility for upgrades |
#### Address Derivation
`
PQC Address:
address = keccak256(pqc_public_key)
v0 (20 bytes) = address[12:32]
v1 (32 bytes) = address[0:32]
Hybrid Address:
address = keccak256(ecdsa_public_key || pqc_public_key)
v0 (20 bytes) = address[12:32]
v1 (32 bytes) = address[0:32]
`
#### createWallet(options?)
Create a new wallet with a generated mnemonic.
`typescript`
const wallet = createWallet({
mnemonicStrength: 256, // 128 (12 words) or 256 (24 words)
passphrase: '', // Optional BIP-39 passphrase
addressScheme: 'hybrid', // 'ecdsa', 'pqc', or 'hybrid'
bip44Path: "m/44'/60'/0'/0", // BIP-44 path for ECDSA
pqcAlgorithm: 'ML-DSA-65', // 'ML-DSA-44', 'ML-DSA-65', or 'ML-DSA-87'
pqcDomain: 'pqc-wallet-v1', // Domain separator for PQC
testnet: false, // Use testnet HRP
useLegacyFormat: false, // Use 20-byte addresses
useExplicitAlgorithmHRP: false, // Use explicit algorithm in HRP
});
#### restoreWallet(mnemonic, options?)
Restore a wallet from an existing mnemonic.
`typescript`
const wallet = restoreWallet('word1 word2 ...', {
addressScheme: 'hybrid',
});
#### wallet.getAddress(index)
Get address info at a specific derivation index.
`typescript`
const info = wallet.getAddress(0);
console.log(info.address); // Primary Bech32m address
console.log(info.legacyAddress); // Legacy Bech32 address
console.log(info.addressBytes); // Raw address bytes
console.log(info.ecdsaKeyPair); // ECDSA key pair (if hybrid/ecdsa)
console.log(info.pqcKeyPair); // PQC key pair (if hybrid/pqc)
`typescript
import {
generateMnemonic,
validateMnemonic,
mnemonicToSeed,
getMnemonicInfo,
} from '@pqc-chain/wallet';
// Generate new mnemonic
const mnemonic = generateMnemonic(256); // 24 words
// Validate mnemonic
const isValid = validateMnemonic(mnemonic); // true
// Convert to seed
const seed = mnemonicToSeed(mnemonic, 'optional-passphrase');
console.log(seed.length); // 64
// Get mnemonic info
const info = getMnemonicInfo(mnemonic);
console.log(info.wordCount); // 24
console.log(info.entropyBits); // 256
`
`typescript
import {
deriveECDSAKey,
derivePQCKey,
derivePQCKey44,
derivePQCKey87,
deriveHybridKeys,
signPQC,
verifyPQC,
} from '@pqc-chain/wallet';
// Derive ECDSA key
const ecdsaKey = deriveECDSAKey(seed, "m/44'/60'/0'/0", 0);
// Derive PQC key (default ML-DSA-65)
const pqcKey = derivePQCKey(seed, 0);
// Derive PQC key with specific algorithm
const pqcKey44 = derivePQCKey44(seed, 0);
const pqcKey87 = derivePQCKey87(seed, 0);
// Derive both (hybrid)
const { ecdsa, pqc } = deriveHybridKeys(seed, 0, 'ML-DSA-65');
// Sign with PQC
const signature = signPQC(messageHash, pqcKey.privateKey);
// Verify PQC signature (auto-detects algorithm from key size)
const isValid = verifyPQC(messageHash, signature, pqcKey.publicKey);
`
`typescript
import {
createPQCAddress,
createHybridAddress,
detectAddressType,
parseAddress,
parseAddressWithAlgorithm,
validateAddress,
toEVMAddress,
toBech32m,
getHRPForFullAlgorithm,
getFullAlgorithmFromHRP,
getAlgorithmFamilyFromHRP,
} from '@pqc-chain/wallet';
// Create PQC address (generic HRP)
const pqcAddr = createPQCAddress(publicKey, { testnet: false });
// Create PQC address (explicit algorithm HRP)
const explicitAddr = createPQCAddress(publicKey, {
explicitAlgorithm: 'ML-DSA-65'
});
// Create hybrid address
const hybridAddr = createHybridAddress(ecdsaPubKey, pqcPubKey);
// Create hybrid address with explicit algorithm
const hybridExplicit = createHybridAddress(ecdsaPubKey, pqcPubKey, {
explicitAlgorithm: 'ML-DSA-87'
});
// Detect address type
const type = detectAddressType('pqc1p...'); // 'pqc-bech32m'
// Parse address with algorithm extraction
const parsed = parseAddressWithAlgorithm('mld651p...');
console.log(parsed.hrp); // 'mld65'
console.log(parsed.algorithm); // 'ML-DSA-65'
console.log(parsed.data); // Uint8Array
// Get HRP for algorithm
const hrp = getHRPForFullAlgorithm('SLH-DSA-128s', true, false); // 'slh128sh'
// Extract algorithm from HRP
const algo = getFullAlgorithmFromHRP('mld87h'); // 'ML-DSA-87'
const family = getAlgorithmFamilyFromHRP('slh256s'); // 'SLH-DSA'
// Validate address
const valid = validateAddress('pqc1p...'); // true
// Convert to EVM address
const evmAddr = toEVMAddress('pqc1p...'); // 0x...
`
`typescript
import {
HRP,
isHybridHRP,
isTestnetHRP,
isValidHRP,
} from '@pqc-chain/wallet';
// Access HRP constants
console.log(HRP.PQC); // 'pqc'
console.log(HRP.MLDSA_65); // 'mld65'
console.log(HRP.SLHDSA_128S); // 'slh128s'
console.log(HRP.FNDSA_512); // 'fnd512'
// Check HRP properties
console.log(isHybridHRP('mld65h')); // true
console.log(isTestnetHRP('tmld65')); // true
console.log(isValidHRP('mld65')); // true
`
`typescript
import {
encodePQCAddress,
decodePQCAddress,
validatePQCBech32Address,
} from '@pqc-chain/wallet';
// Encode address with any valid HRP
const address = encodePQCAddress('mld65', AddressVersion.V1_QUANTUM, addressBytes);
// Decode address
const { hrp, version, data } = decodePQCAddress('mld651p...');
// Validate
const valid = validatePQCBech32Address('mld651p...');
`
| Component | Classical Security | Post-Quantum Security |
|-----------|-------------------|----------------------|
| ECDSA (secp256k1) | 128 bits | 0 bits (broken by Shor) |
| ML-DSA-44 | 128 bits | ~100 bits (NIST Level 2) |
| ML-DSA-65 | 192 bits | ~128 bits (NIST Level 3) |
| ML-DSA-87 | 256 bits | ~192 bits (NIST Level 5) |
| Hybrid | min(ECDSA, PQC) | min(ECDSA, PQC) |
| 20-byte address | 80 bits | ~53 bits* |
| 32-byte address | 128 bits | ~85 bits* |
\* Against preimage attacks
\** Against quantum collision attacks (Grover/BHT)
1. Use Hybrid Scheme: Provides security even if one algorithm is broken
2. Use 32-byte Addresses: Better quantum collision resistance
3. Use 24-word Mnemonic: Higher entropy for long-term security
4. Use ML-DSA-65 or Higher: Recommended for most use cases
5. Use Explicit Algorithm HRPs: When clarity about algorithm is important
6. Backup Securely: Store mnemonic offline in multiple locations
7. Use Passphrase: Additional protection layer for high-value wallets
The wallet library is compatible with the Quorum node's PQC implementation:
- Same address derivation (keccak256)
- Same Bech32m encoding with all HRP variants
- Same ML-DSA parameters (44, 65, 87)
- Full HRP recognition (generic and explicit)
- Interoperable signatures
32-byte PQC addresses can be converted to 20-byte EVM addresses for smart contract interaction:
`typescript`
const evmAddress = toEVMAddress(pqcAddress);
// Takes last 20 bytes of the 32-byte address
The examples/ directory contains comprehensive examples:
| Example | Description |
|---------|-------------|
| 01-basic-wallet.js | Creating and restoring wallets |02-ml-dsa-algorithms.js
| | Using ML-DSA-44, ML-DSA-65, ML-DSA-87 |03-signing-verification.js
| | Signing and verifying messages |04-address-formats.js
| | Bech32m encoding, HRPs, and conversion |05-mnemonic-advanced.js
| | Mnemonic generation and validation |06-typescript-usage.ts
| | Type-safe TypeScript usage |07-key-derivation.js
| | BIP-44 and HKDF key derivation |08-explicit-algorithm-hrp.js
| | Explicit algorithm HRPs comprehensive guide |
Run an example:
`bash`
cd pqc-wallet
npm install
node examples/01-basic-wallet.js
`typescript
import { createWallet } from '@pqc-chain/wallet';
// Create wallet with explicit ML-DSA-65 in address HRP
const wallet = createWallet({
mnemonicStrength: 256,
addressScheme: 'hybrid',
pqcAlgorithm: 'ML-DSA-65',
useExplicitAlgorithmHRP: true, // Enable explicit HRP
});
// Address will start with mld65h1p... instead of pqch1p...
const address = wallet.getAddress(0);
console.log(address.address); // mld65h1p...
`
`typescript
import {
parseAddressWithAlgorithm,
getAlgorithmFamilyFromHRP
} from '@pqc-chain/wallet';
const address = 'mld651pqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq...';
const parsed = parseAddressWithAlgorithm(address);
console.log('HRP:', parsed.hrp); // 'mld65'
console.log('Algorithm:', parsed.algorithm); // 'ML-DSA-65'
console.log('Family:', getAlgorithmFamilyFromHRP(parsed.hrp)); // 'ML-DSA'
console.log('Is Hybrid:', parsed.hrp.endsWith('h')); // false
``
MIT
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
- Quorum PQC Node - The Quorum node with PQC support
- NIST FIPS 204 - ML-DSA specification
- NIST FIPS 205 - SLH-DSA specification
- BIP-39 - Mnemonic code standard
- BIP-350 - Bech32m specification