fix(aegis): DigiByte import failed on every OTA install

Reported as "the Aegis DigiByte import error". The vendored @dgb-wallet/core
modules under lib/dgb/ imported "bitcoinjs-lib", "bip32", "ecpair",
"bip39" and "@bitcoinerlab/secp256k1" as bare specifiers. Node's ESM resolver
walks up from the importing file, so that resolves in a dev checkout — where
Theseus's node_modules sits above the add-on — and resolves nowhere once
Aegis is running from <userData>/extensions/aegis/, which is every OTA
install. The import threw, index.js caught it and set dgbCore = null, and any
DigiByte import then died in digibyteNetwork() with "DGB adapter not
available".

So DigiByte worked on a fresh install and disappeared after the first update.
Confirmed on this machine: the only node_modules reachable from the installed
extension carries bip39 and none of the other four.

lib/dgb/deps.js now holds the dependencies, injected by index.js from
api.require (which resolves against the app tree) before anything under
lib/dgb/ is imported — the same pattern every other lib/*.js in Aegis already
uses, and the reason that pattern exists. Consumers read them through
accessors rather than capturing them at module scope, so import order is no
longer load-bearing: hd.js builds its bip32 on first use and the two
initEccLib callers go through a once-only ensureEcc(). Using a DGB module
without injection now throws a named error instead of a resolver failure
swallowed into a null.

Verified by loading the modules from a directory with no reachable
node_modules and deriving real addresses: BIP44 D…, BIP49 S… with its legacy
3… pair, BIP84 dgb1q…, BIP86 dgb1p… taproot, plus a WIF round-trip and the
non-DGB WIF rejection.
This commit is contained in:
Local Dev 2026-10-03 13:59:01 +02:00
parent 40683c84cf
commit c1988b9674
7 changed files with 105 additions and 39 deletions

View file

@ -71,17 +71,30 @@ async function loadDeps(api) {
// userData/addons/, Node's ESM resolver can't reach Theseus's node_modules
// from that path — so the import throws. Wrap it: DGB just becomes
// unavailable, the rest of Aegis keeps working.
const bitcoinjs = api.require("bitcoinjs-lib");
const { BIP32Factory } = api.require("bip32");
const { ECPairFactory } = api.require("ecpair");
const ecc = api.require("@bitcoinerlab/secp256k1");
let dgbCore = null, dgbPsbt = null, dgbAdapter = null;
try {
// Inject before importing anything under lib/dgb/. Those modules used to
// import "bitcoinjs-lib" and friends as bare specifiers, which only
// resolves when the add-on sits inside the app tree — so DigiByte worked
// on a fresh install and vanished after the first OTA update, where Aegis
// runs from <userData>/extensions/aegis/. See lib/dgb/deps.js.
const dgbDeps = await import(pathToFileURL(path.join(api.folder, "lib/dgb/deps.js")).href);
dgbDeps.setDgbDeps({
bitcoinjs, ecc,
bip32Factory: BIP32Factory,
ecpairFactory: ECPairFactory,
bip39: api.require("bip39"),
});
dgbCore = await import(pathToFileURL(path.join(api.folder, "lib/dgb/core/index.js")).href);
dgbPsbt = await import(pathToFileURL(path.join(api.folder, "lib/dgb/psbt/index.js")).href);
} catch (e) {
api.log("dgb unavailable:", e?.message || e);
}
const bitcoinjs = api.require("bitcoinjs-lib");
const { BIP32Factory } = api.require("bip32");
const { ECPairFactory } = api.require("ecpair");
const ecc = api.require("@bitcoinerlab/secp256k1");
if (dgbCore && dgbPsbt) {
dgbAdapter = require("./lib/chain-dgb.js")({
dgbCore, dgbPsbt, bitcoinjs,

View file

@ -1,10 +1,7 @@
import { payments, initEccLib } from 'bitcoinjs-lib';
import * as ecc from '@bitcoinerlab/secp256k1';
import { payments, ensureEcc } from '../deps.js';
import { digibyte, digibyteLegacyP2SH } from './network.js';
// Required for Taproot address derivation (P2TR).
initEccLib(ecc);
export function p2pkhAddress(node, network = digibyte) {
const { address } = payments.p2pkh({
const { address } = payments().p2pkh({
pubkey: Buffer.from(node.publicKey),
network,
});
@ -13,11 +10,11 @@ export function p2pkhAddress(node, network = digibyte) {
return address;
}
export function p2shP2wpkhAddress(node, network = digibyte) {
const redeem = payments.p2wpkh({
const redeem = payments().p2wpkh({
pubkey: Buffer.from(node.publicKey),
network,
});
const { address } = payments.p2sh({ redeem, network });
const { address } = payments().p2sh({ redeem, network });
if (!address)
throw new Error('p2sh-p2wpkh derivation returned no address');
return address;
@ -33,7 +30,7 @@ export function p2shP2wpkhAddressesBoth(node) {
};
}
export function p2wpkhAddress(node, network = digibyte) {
const { address } = payments.p2wpkh({
const { address } = payments().p2wpkh({
pubkey: Buffer.from(node.publicKey),
network,
});
@ -44,8 +41,9 @@ export function p2wpkhAddress(node, network = digibyte) {
// BIP86 Taproot address using the x-only pubkey with no script tree,
// which applies the standard BIP86 tweak internally in bitcoinjs-lib.
export function p2trAddress(node, network = digibyte) {
ensureEcc(); // Taproot needs initEccLib; done on demand, once.
const internalPubkey = Buffer.from(node.publicKey.subarray(1, 33));
const { address } = payments.p2tr({ internalPubkey, network });
const { address } = payments().p2tr({ internalPubkey, network });
if (!address)
throw new Error('p2tr derivation returned no address');
return address;

View file

@ -1,7 +1,5 @@
import { BIP32Factory } from 'bip32';
import * as ecc from '@bitcoinerlab/secp256k1';
import { bip32 } from '../deps.js';
import { digibyte, DGB_COIN_TYPE } from './network.js';
const bip32 = BIP32Factory(ecc);
export const PURPOSE_LABEL = {
44: 'BIP44 legacy P2PKH',
49: 'BIP49 P2SH-wrapped SegWit',
@ -9,7 +7,7 @@ export const PURPOSE_LABEL = {
86: 'BIP86 Taproot (SegWit v1)',
};
export function rootFromSeed(seed, network = digibyte) {
return bip32.fromSeed(seed, network);
return bip32().fromSeed(seed, network);
}
// Standard account-level derivation: m/purpose'/coin'/account'.
// account defaults to 0 (the first account).

View file

@ -1,17 +1,17 @@
import { generateMnemonic, validateMnemonic, mnemonicToSeed, wordlists, } from 'bip39';
import { bip39 } from '../deps.js';
// Word count → entropy strength for BIP39.
// 12 → 128, 15 → 160, 18 → 192, 21 → 224, 24 → 256.
export function generateSeedPhrase(strength = 128) {
return generateMnemonic(strength);
return bip39().generateMnemonic(strength);
}
// BIP39 checksum + wordlist validation. Returns false for typos, bad
// word counts, and out-of-wordlist words.
export function validateSeedPhrase(phrase, wordlist = wordlists.english) {
return validateMnemonic(phrase.trim(), wordlist);
export function validateSeedPhrase(phrase, wordlist = undefined) {
return bip39().validateMnemonic(phrase.trim(), wordlist || bip39().wordlists.english);
}
// BIP39 seed derivation. Passphrase is the optional "25th word";
// changing it produces a different wallet from the same mnemonic.
export async function seedFromPhrase(phrase, passphrase = '') {
return mnemonicToSeed(phrase.trim(), passphrase);
return bip39().mnemonicToSeed(phrase.trim(), passphrase);
}
//# sourceMappingURL=seed.js.map

View file

@ -1,9 +1,5 @@
import { ECPairFactory } from 'ecpair';
import * as ecc from '@bitcoinerlab/secp256k1';
import { payments, initEccLib } from 'bitcoinjs-lib';
import { payments, ECPair, ensureEcc } from '../deps.js';
import { digibyte, digibyteLegacyP2SH, digibyteLegacyWIF } from './network.js';
const ECPair = ECPairFactory(ecc);
initEccLib(ecc);
// Import a DigiByte private key in WIF (Wallet Import Format).
// Tries the modern 0x80 prefix first, falls back to the legacy 0x9e
// prefix that older DGB tools produced. Rejects anything else with a
@ -11,13 +7,13 @@ initEccLib(ecc);
export function importWif(wif) {
const trimmed = wif.trim();
try {
return { keyPair: ECPair.fromWIF(trimmed, digibyte), variant: 'modern' };
return { keyPair: ECPair().fromWIF(trimmed, digibyte), variant: 'modern' };
}
catch {
// fall through to legacy attempt
}
try {
return { keyPair: ECPair.fromWIF(trimmed, digibyteLegacyWIF), variant: 'legacy' };
return { keyPair: ECPair().fromWIF(trimmed, digibyteLegacyWIF), variant: 'legacy' };
}
catch (e) {
throw new Error(`Not a valid DigiByte WIF (tried both current 0x80 and legacy 0x9e prefixes). ` +
@ -26,23 +22,24 @@ export function importWif(wif) {
}
// Import against a specific network only (advanced / testing).
export function importWifStrict(wif, network = digibyte) {
return ECPair.fromWIF(wif.trim(), network);
return ECPair().fromWIF(wif.trim(), network);
}
export function exportWif(keyPair) {
return keyPair.toWIF();
}
export function addressesForPubkey(pubkey, network = digibyte) {
const p2pkh = payments.p2pkh({ pubkey, network });
const wpkhRedeem = payments.p2wpkh({ pubkey, network });
ensureEcc(); // p2tr below needs initEccLib.
const p2pkh = payments().p2pkh({ pubkey, network });
const wpkhRedeem = payments().p2wpkh({ pubkey, network });
// bitcoinjs-lib enforces `redeem.network === outerNetwork` (identity
// comparison, not shape). To render the legacy 3-prefix P2SH address
// we need a fresh redeem whose .network property is the legacy variant
// — same bytes on the wire, different object identity.
const wpkhRedeemLegacy = payments.p2wpkh({ pubkey, network: digibyteLegacyP2SH });
const p2shModern = payments.p2sh({ redeem: wpkhRedeem, network });
const p2shLegacy = payments.p2sh({ redeem: wpkhRedeemLegacy, network: digibyteLegacyP2SH });
const p2wpkh = payments.p2wpkh({ pubkey, network });
const p2tr = payments.p2tr({ internalPubkey: pubkey.subarray(1, 33), network });
const wpkhRedeemLegacy = payments().p2wpkh({ pubkey, network: digibyteLegacyP2SH });
const p2shModern = payments().p2sh({ redeem: wpkhRedeem, network });
const p2shLegacy = payments().p2sh({ redeem: wpkhRedeemLegacy, network: digibyteLegacyP2SH });
const p2wpkh = payments().p2wpkh({ pubkey, network });
const p2tr = payments().p2tr({ internalPubkey: pubkey.subarray(1, 33), network });
if (!p2pkh.address || !p2shModern.address || !p2shLegacy.address || !p2wpkh.address || !p2tr.address) {
throw new Error('bitcoinjs-lib returned an empty address for one of the payment types');
}

View file

@ -0,0 +1,60 @@
// Dependency injection for the vendored DGB modules.
//
// These files were vendored from @dgb-wallet/core and imported
// "bitcoinjs-lib", "bip32", "ecpair" and "@bitcoinerlab/secp256k1" as bare
// specifiers. That resolves fine in a dev checkout, where Theseus's
// node_modules sits above the add-on — and fails for every OTA install, where
// Aegis lives in <userData>/extensions/aegis/ and Node's ESM resolver has
// nowhere to find them. index.js caught the failure and set dgbCore = null,
// so DigiByte quietly stopped existing and an import died with "DGB adapter
// not available". It worked on a fresh install and broke after the first
// update, which is the worst possible shape for a bug.
//
// index.js already holds all of these via api.require (which resolves against
// the app tree), so it injects them here before importing anything under
// lib/dgb/. Every consumer reads them through the accessors below rather than
// capturing them at module scope, so nothing depends on import order and a
// missing injection fails loudly at the call instead of silently at load.
let deps = null;
export function setDgbDeps(d) {
const need = ["bitcoinjs", "ecc", "bip32Factory", "ecpairFactory", "bip39"];
for (const k of need) {
if (!d || !d[k]) throw new Error(`setDgbDeps: missing ${k}`);
}
deps = d;
}
function need() {
if (!deps) throw new Error("DGB modules used before setDgbDeps() — see lib/dgb/deps.js");
return deps;
}
export const bitcoinjs = () => need().bitcoinjs;
export const payments = () => need().bitcoinjs.payments;
export const Psbt = () => need().bitcoinjs.Psbt;
export const ecc = () => need().ecc;
export const bip39 = () => need().bip39;
// initEccLib must run before any Taproot derivation, and exactly once.
let eccInstalled = false;
export function ensureEcc() {
if (eccInstalled) return;
need().bitcoinjs.initEccLib(need().ecc);
eccInstalled = true;
}
// Built on first use rather than at module scope, so hd.js no longer forces
// the deps to exist merely by being imported.
let bip32Cached = null;
export function bip32() {
if (!bip32Cached) bip32Cached = need().bip32Factory(need().ecc);
return bip32Cached;
}
let ecpairCached = null;
export function ECPair() {
if (!ecpairCached) ecpairCached = need().ecpairFactory(need().ecc);
return ecpairCached;
}

View file

@ -1,11 +1,11 @@
import { Psbt } from 'bitcoinjs-lib';
import { Psbt } from '../deps.js';
import { digibyte } from '../core/index.js';
// Construct an unsigned PSBT from a set of UTXOs and destination outputs.
// Does not add a change output — the caller decides change amount and
// address. Does not compute fees — the caller must have already subtracted
// fee from outputs.
export function buildPsbt(params, network = digibyte) {
const psbt = new Psbt({ network });
const psbt = new (Psbt())({ network });
const inputs = params.sortBip69 === false ? params.inputs : sortInputs(params.inputs);
const outputs = params.sortBip69 === false ? params.outputs : sortOutputs(params.outputs);
for (const u of inputs) {