@norionsoft/w2qr-shamir
v1.0.0
Published
Secure multi-share wallet backup using Shamir Secret Sharing, password-protected QR payloads, and BIP39 mnemonic support
Readme
@norionsoft/w2qr-shamir
Secure multi-share wallet backup using Shamir Secret Sharing, password-protected QR payloads, and BIP39 mnemonic support.
Security Warning: This package has not been independently audited. Use at your own risk for production wallet protection. Always test restore after creation. Never upload shares to untrusted servers.
Installation
npm install @norionsoft/w2qr-shamirHow it works
mnemonic/entropy → Shamir split → each share encrypted with password → each encrypted share encoded as QR code
QR payloads + passwords → decrypted shares → threshold validation → mnemonic recoveryUsage
Create a 3-of-5 backup
import { createMultishareWallet } from '@norionsoft/w2qr-shamir'
const backup = await createMultishareWallet({
threshold: 3,
totalShares: 5,
passwords: ['p1', 'p2', 'p3', 'p4', 'p5'],
qr: { format: 'svg' }
})
console.log('Mnemonic:', backup.mnemonic)
console.log('Wallet ID:', backup.walletId)
for (const share of backup.shares) {
console.log(`Share ${share.index}:`, share.qrPayload)
// share.qrSvg contains SVG markup
}Restore from 3 shares
import { restoreMultishareWallet } from '@norionsoft/w2qr-shamir'
const restored = await restoreMultishareWallet({
shares: [
{ qrPayload: backup.shares[0].qrPayload, password: 'p1' },
{ qrPayload: backup.shares[2].qrPayload, password: 'p3' },
{ qrPayload: backup.shares[4].qrPayload, password: 'p5' }
]
})
if (restored.success) {
console.log('Recovered mnemonic:', restored.mnemonic)
}Backup an existing mnemonic
const backup = await createMultishareWallet({
mnemonic: 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about',
threshold: 2,
totalShares: 3,
passwords: ['password-a', 'password-b', 'password-c']
})Supported Schemes
| Scheme | Description | |--------|-------------| | 2-of-2 | Both shares required | | 2-of-3 | Any 2 of 3 shares | | 3-of-3 | All 3 shares required | | 3-of-4 | Any 3 of 4 shares | | 3-of-5 | Any 3 of 5 shares | | 4-of-5 | Any 4 of 5 shares |
Generic: threshold >= 2, totalShares >= threshold, totalShares <= 10
API Reference
createMultishareWallet(options)
Creates a multi-share wallet backup.
Options:
mnemonic?— Existing BIP39 mnemonic to splitentropy?— Raw entropy bytesstrength?— Entropy strength: 128, 160, 192, 224, or 256 (default: 256)threshold— Minimum shares needed to restoretotalShares— Total number of shares to createpasswords— Array of passwords, one per sharewordlist?— Custom BIP39 wordlistkdf?— KDF options ({ iterations?: number })qr?— QR options ({ format?: 'svg' | 'dataUrl' | 'both' })
Returns: MultishareWalletResult with walletId, fingerprint, mnemonic, and shares[]
restoreMultishareWallet(options)
Restores a wallet from share payloads and passwords.
Options:
shares— Array of{ qrPayload: string, password: string }wordlist?— Custom BIP39 wordlist
Returns: RestoreMultishareWalletResult — discriminated union with success: true (includes mnemonic, entropy) or success: false (includes reason, optional decoyMnemonic)
generateMnemonic(strength?, wordlist?)
Generates a new BIP39 mnemonic phrase.
validateSharePayload(payload)
Returns true if the payload string is a valid w2qr-shamir share.
inspectShare(payload)
Returns public metadata from a share payload without decryption.
encodeSharePayload(data) / decodeSharePayload(payload)
Low-level payload encoding/decoding.
estimateQrSize(payloadLength)
Estimates QR code version needed for a payload.
Payload Format
Each share is encoded as: w2qrs1:<base64url-encoded-json>
Public metadata (visible without password):
{
"v": 1,
"t": "w2qr-s",
"wid": "<walletId>",
"i": 2,
"th": 3,
"n": 5,
"fp": "<fingerprint>",
"enc": {
"alg": "AES-256-GCM",
"kdf": "PBKDF2-SHA256",
"iter": 600000,
"salt": "<base64url>",
"nonce": "<base64url>",
"ct": "<base64url>"
}
}Browser / Node Compatibility
- Node.js >= 18 (uses
crypto.subtlefor AES-GCM) - Modern browsers with WebCrypto API support
- ESM-only (no CommonJS)
Honey Mode
v1 implements basic honey mode: wrong passwords do not throw raw cryptographic errors. Instead, failed decryption returns a plausible decoy mnemonic derived from the password and share salt.
Limitation: v1 honey mode is plausibility/decoy behavior, not formal distribution-transforming honey encryption. Future versions may add full honey-wallet reconstruction where wrong passwords produce valid decoy wallets.
QR Size Notes
Typical share payloads for a 24-word (256-bit) mnemonic with AES-256-GCM are ~800-1200 bytes encoded. This fits comfortably in QR version 15-25 (error correction level M). For 12-word mnemonics, payloads are smaller.
Security Recommendations
- Use strong, unique passwords for each share
- Store shares in separate physical locations
- Always test restore after creation
- Do not upload shares to untrusted servers
- Print or store QR codes on durable media
- Consider laminating printed QR codes
- Entropy is split (not the mnemonic text) to avoid leaking word boundaries
Cryptographic Details
- Secret Sharing: Shamir over GF(256) — each byte of entropy is split independently
- Encryption: AES-256-GCM via WebCrypto API (authenticated encryption)
- KDF: PBKDF2-SHA256 with 600,000 iterations (configurable)
- Random:
crypto.getRandomValues(CSPRNG) - Checksums: SHA-256 with domain separation
- Independent salt and nonce per share
Roadmap
- [ ] Formal honey encryption (distribution-transforming)
- [ ] SLIP-0039 compatibility
- [ ] Web Worker KDF offloading
- [ ] CLI tool
- [ ] Independent security audit
- [ ] Argon2id KDF support
License
MIT
