Setup 5d15508bba929f1f074c052ac933863eadf6eb8e56984ebd5a1af75e80626643 Portable a5d346b97f5a13d85fa3bd301a72075ddb82fe636d7b1a51840ffd5a16d879f4 Bundled since 0.3.27: 32d4b75 - Aegis (bchwallet) gains its own update card in Settings > General beside Ariadne. Check for updates hits the same signed OTA endpoint the boot timer uses; Restart to apply appears when a signed newer version is staged. Uses the existing addons-check-updates + a new app-restart IPC. New Aegis versions ship without a Theseus release. 32d4b75 (same commit) - DevTools (F12 / Ctrl+Shift+I) opens docked to the right of the tab (mode: 'right') instead of a detached window. Matches stock Chrome. Users who prefer detached can drag out via the DevTools own toolbar. b71c925 - Search-engine favicons in Settings > Search now use Google's /s2/favicons service — DuckDuckGo's ip3 source returned 404 for enough hosts (Brave, Bing, Yandex, etc.) that half the list was falling through to the emoji placeholder. Deployed. Verified LIVE 0.3.28.
66 lines
3 KiB
JavaScript
66 lines
3 KiB
JavaScript
// HD key tree for the wallet. Root = 32 bytes from api.vault.derive treated as
|
|
// a BIP32 master seed; account = m/44'/145'/0' (BCH, SLIP-44). Branch 0 is
|
|
// receive, branch 1 is change. Private keys never leave this module except
|
|
// through sign() / signRecoverable() for a specific entry.
|
|
module.exports = function makeKeys({ HDKey, secp256k1, sha256, ripemd160, cashaddr }) {
|
|
const hash160 = (b) => ripemd160(sha256(b));
|
|
const p2pkhScript = (h160) => Uint8Array.from([0x76, 0xa9, 0x14, ...h160, 0x88, 0xac]);
|
|
const p2shScript = (h160) => Uint8Array.from([0xa9, 0x14, ...h160, 0x87]);
|
|
const toHex = (b) => Array.from(b, (x) => x.toString(16).padStart(2, "0")).join("");
|
|
// electrum scripthash: sha256(script), byte-reversed, hex.
|
|
const scripthash = (script) => toHex(sha256(script).slice().reverse());
|
|
|
|
class WalletKeys {
|
|
constructor(root32, accountPath, prefix) {
|
|
this.prefix = prefix;
|
|
this.accountPath = accountPath;
|
|
this._account = HDKey.fromMasterSeed(root32).derive(accountPath);
|
|
this._branch = [this._account.deriveChild(0), this._account.deriveChild(1)];
|
|
this._cache = new Map(); // "branch/index" -> entry
|
|
}
|
|
get xpub() { return this._account.publicExtendedKey; }
|
|
// Revealed only on explicit user action in Settings (show recovery info).
|
|
get xprv() { return this._account.privateExtendedKey; }
|
|
entry(branch, index) {
|
|
const k = branch + "/" + index;
|
|
let e = this._cache.get(k);
|
|
if (!e) {
|
|
const node = this._branch[branch].deriveChild(index);
|
|
const h160 = hash160(node.publicKey);
|
|
const script = p2pkhScript(h160);
|
|
e = {
|
|
branch, index, path: this.accountPath + "/" + branch + "/" + index,
|
|
publicKey: node.publicKey, h160, script, scriptHex: toHex(script),
|
|
scripthash: scripthash(script),
|
|
address: cashaddr.encode(this.prefix, 0, h160),
|
|
_node: node,
|
|
};
|
|
this._cache.set(k, e);
|
|
}
|
|
return e;
|
|
}
|
|
findByScriptHex(scriptHex) {
|
|
for (const e of this._cache.values()) if (e.scriptHex === scriptHex) return e;
|
|
return null;
|
|
}
|
|
// ECDSA over a 32-byte digest, DER-encoded, low-S (BCH consensus rule).
|
|
sign(entry, digest32) {
|
|
return secp256k1.sign(digest32, entry._node.privateKey, { prehash: false, lowS: true, format: "der" });
|
|
}
|
|
// 65-byte BIP-137 signature: [27 + recid + 4 (compressed)] || r || s.
|
|
signRecoverable(entry, digest32) {
|
|
const sig = secp256k1.sign(digest32, entry._node.privateKey, { prehash: false, lowS: true, format: "recovered" });
|
|
const out = new Uint8Array(65);
|
|
out[0] = 27 + sig[0] + 4;
|
|
out.set(sig.subarray(1), 1);
|
|
return out;
|
|
}
|
|
wipe() {
|
|
for (const e of this._cache.values()) { try { e._node.wipePrivateData(); } catch {} }
|
|
this._cache.clear();
|
|
for (const b of this._branch) { try { b.wipePrivateData(); } catch {} }
|
|
try { this._account.wipePrivateData(); } catch {}
|
|
}
|
|
}
|
|
return { WalletKeys, hash160, p2pkhScript, p2shScript, scripthash, toHex };
|
|
};
|