dgb-wallet/packages/core/scripts/recover-from-seed.ts
Digibyte.X d50286a9a5 DigiByte.X Wallet — recovery-first web wallet monorepo
Packages: core (BIP39/32/44/49/84/86 derivation, WIF), keystore (PBKDF2 +
AES-GCM), psbt, p2p (BIP157/158 SPV daemon, Dandelion++ broadcast with
relay-echo verification), blockbook, rpc, electrum, net, bns, web-ui
(React/Vite), mobile (Expo). ops/ holds the nginx vhost, systemd unit and
push.sh deploy script for digibyte.x/web.
2026-09-20 20:16:52 +02:00

91 lines
3.1 KiB
TypeScript

// Recovery demo — takes a seed phrase and prints candidate addresses at
// every standard DGB derivation path across the first 5 accounts and
// first 20 addresses per account. Meant for offline verification against
// a block explorer or Ian Coleman; the actual wallet's "scan all paths"
// import flow will run the same shape of derivation and then check each
// address for balance via the P2P+BIP158 client.
import {
seedFromPhrase,
validateSeedPhrase,
rootFromSeed,
accountNode,
addressNode,
p2pkhAddress,
p2shP2wpkhAddress,
p2shP2wpkhAddressesBoth,
p2wpkhAddress,
p2trAddress,
PURPOSE_LABEL,
derivationPath,
digibyteLegacyP2SH,
type Purpose,
} from '../src/index.js';
const args = process.argv.slice(2);
if (args.length === 0) {
console.error('Usage: npm run recover -- "word1 word2 ... word12"');
console.error('');
console.error('WARNING: never paste a real seed here. Generate a throwaway seed with');
console.error('`npm run demo` and use that. Terminal history persists.');
process.exit(1);
}
const phrase = args.join(' ').trim();
const wordCount = phrase.split(/\s+/).length;
const validCounts = [12, 15, 18, 21, 24];
if (!validCounts.includes(wordCount)) {
console.error(`Got ${wordCount} words. BIP39 requires exactly one of: ${validCounts.join(', ')}.`);
console.error('');
console.error('Common cause: pasting placeholder text along with the real words.');
console.error('Quote just the seed phrase itself, with no extra words before or after.');
process.exit(1);
}
if (!validateSeedPhrase(phrase)) {
console.error(`Got ${wordCount} words at a valid length, but the BIP39 checksum failed.`);
console.error('Check for typos or non-BIP39 wordlist words.');
process.exit(1);
}
const seed = await seedFromPhrase(phrase);
const root = rootFromSeed(seed);
const ACCOUNTS = 5;
const ADDRESSES = 20;
console.log(`\n=== Recovery Scan ===`);
console.log(`Seed: ${phrase.split(/\s+/).length}-word BIP39`);
console.log(`Accounts: 0..${ACCOUNTS - 1}`);
console.log(`Addresses: 0..${ADDRESSES - 1} per account, external chain only\n`);
function scan(
label: string,
purpose: Purpose,
derive: (n: ReturnType<typeof addressNode>) => string,
): void {
console.log(`── ${label}`);
for (let a = 0; a < ACCOUNTS; a++) {
const acct = accountNode(root, purpose, a);
for (let i = 0; i < ADDRESSES; i++) {
const key = addressNode(acct, 0, i);
console.log(` ${derivationPath(purpose, a, 0, i).padEnd(24)} ${derive(key)}`);
}
}
console.log();
}
scan(PURPOSE_LABEL[44], 44, p2pkhAddress);
scan(`${PURPOSE_LABEL[49]} (modern "S..." — DGB Core default)`, 49, p2shP2wpkhAddress);
scan(
`${PURPOSE_LABEL[49]} (legacy "3..." — Ian Coleman / older tools)`,
49,
(n) => p2shP2wpkhAddressesBoth(n).legacy,
);
scan(PURPOSE_LABEL[84], 84, p2wpkhAddress);
scan(PURPOSE_LABEL[86], 86, p2trAddress);
console.log('Note: BIP49 shown twice on purpose. Same underlying key, two');
console.log('valid DGB P2SH version bytes. Any recovery scan must check both.');
console.log(`(legacyP2SH network for reference: scriptHash=0x${digibyteLegacyP2SH.scriptHash.toString(16)})\n`);