@cheny56/pqc-wallet
v1.0.0
Published
Post-Quantum Cryptography wallet library for Ethereum/Quorum with BIP-39 mnemonic, ML-DSA-65 keys, and Bech32m addresses
Maintainers
Readme
@pqc-chain/wallet
A Post-Quantum Cryptography wallet library for Ethereum/Quorum with BIP-39 mnemonic support, ML-DSA (Dilithium) keys, and Bech32m addresses.
Features
- 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
Installation
npm install @pqc-chain/walletQuick Start
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');ML-DSA Algorithm Support
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 |
// 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' });Specifications
Mnemonic (BIP-39)
| 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.
Key Derivation
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) - Info Format:
{domain}/{algorithm}/key/{index}
HKDF-Expand(seed, info="pqc-wallet-v1/ML-DSA-65/key/{index}", length=32) → ML-DSA keygen seedAddress Formats
The library supports two HRP naming conventions for maximum flexibility.
HRP Format Overview
Address Format: <hrp>1<version_char><data><checksum>
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<data> | pqc1q... |
| PQC (quantum-safe) | 1 | 32 | pqc1p<data> | pqc1p... |
| Hybrid (legacy) | 0 | 20 | pqch1q<data> | pqch1q... |
| Hybrid (quantum-safe) | 1 | 32 | pqch1p<data> | pqch1p... |
| Testnet PQC | 0/1 | 20/32 | tpqc1... | tpqc1p... |
| Testnet Hybrid | 0/1 | 20/32 | tpqch1... | tpqch1p... |
Explicit Algorithm HRPs
The explicit format embeds the algorithm in the address HRP: <family><variant>[h]
ML-DSA (Dilithium) - NIST FIPS 204
| Algorithm | PQC HRP | Hybrid HRP | Testnet PQC | Testnet Hybrid |
|-----------|---------|------------|-------------|----------------|
| ML-DSA-44 | mld44 | mld44h | tmld44 | tmld44h |
| ML-DSA-65 | mld65 | mld65h | tmld65 | tmld65h |
| ML-DSA-87 | mld87 | mld87h | tmld87 | tmld87h |
SLH-DSA (SPHINCS+) - NIST FIPS 205
| Algorithm | PQC HRP | Hybrid HRP | Description |
|-----------|---------|------------|-------------|
| SLH-DSA-128s | slh128s | slh128sh | Small signatures, slower |
| SLH-DSA-128f | slh128f | slh128fh | Fast signatures, larger |
| SLH-DSA-192s | slh192s | slh192sh | Medium security, small |
| SLH-DSA-192f | slh192f | slh192fh | Medium security, fast |
| SLH-DSA-256s | slh256s | slh256sh | Highest security, small |
| SLH-DSA-256f | slh256f | slh256fh | Highest security, fast |
FN-DSA (Falcon) - Expected NIST 2024
| Algorithm | PQC HRP | Hybrid HRP | Description |
|-----------|---------|------------|-------------|
| FN-DSA-512 | fnd512 | fnd512h | Compact signatures |
| FN-DSA-1024 | fnd1024 | fnd1024h | Maximum security |
Using Explicit Algorithm HRPs
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 |
| Multi-algorithm deployment | Explicit (mld65, 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]API Reference
Wallet Functions
createWallet(options?)
Create a new wallet with a generated mnemonic.
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.
const wallet = restoreWallet('word1 word2 ...', {
addressScheme: 'hybrid',
});wallet.getAddress(index)
Get address info at a specific derivation index.
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)Mnemonic Functions
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); // 256Key Functions
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);Address Functions
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...HRP Utility Functions
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')); // trueBech32m Functions
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...');Security Considerations
Quantum Resilience
| 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)
Recommendations
- Use Hybrid Scheme: Provides security even if one algorithm is broken
- Use 32-byte Addresses: Better quantum collision resistance
- Use 24-word Mnemonic: Higher entropy for long-term security
- Use ML-DSA-65 or Higher: Recommended for most use cases
- Use Explicit Algorithm HRPs: When clarity about algorithm is important
- Backup Securely: Store mnemonic offline in multiple locations
- Use Passphrase: Additional protection layer for high-value wallets
Compatibility
Node Compatibility
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
EVM Compatibility
32-byte PQC addresses can be converted to 20-byte EVM addresses for smart contract interaction:
const evmAddress = toEVMAddress(pqcAddress);
// Takes last 20 bytes of the 32-byte addressExamples
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:
cd pqc-wallet
npm install
node examples/01-basic-wallet.jsExample: Create Wallet with Explicit Algorithm HRP
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...Example: Parse and Identify Algorithm
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')); // falseLicense
MIT
Contributing
Contributions are welcome! Please read our contributing guidelines before submitting PRs.
Related
- 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
