fix(aegis): 0.9.5 — WizardConnect signing actually works
Pairing already worked; signing would have thrown on the first request
a dapp ever sent. Found by testing against the real relay and the real
@wizardconnect/wallet library rather than reading the code.
Two bugs in wc-sign.js, both fatal:
- The WC message nests the whole WcSignTransactionRequest under
`.transaction`, so the tx is at request.transaction.transaction and
the spent outputs at request.transaction.sourceOutputs. We read
request.transaction as the tx and request.sourceOutputs as the
outputs, so tx.inputs was undefined. index.js already read the nested
request.transaction.userPrompt for the approval dialog, so only the
signer had it wrong. The flat shape is still accepted.
- generateSigningSerializationBCH takes TWO positional arguments,
(compilationContext, {coveredBytecode, signingSerializationType}).
We passed one merged object, leaving coveredBytecode undefined and
throwing inside libauth. For P2PKH the covered bytecode is the spent
output's locking script.
Now verified end to end: a two-input transaction spending from two
different derivation paths signs, decodes, and passes
createVirtualMachineBCH().verify() — consensus-valid, with
SIGHASH_ALL|FORKID|UTXOS (0x61) on every input as the protocol
requires.
Also: RelayStatus is an object ({status: "connected" | "reconnecting" |
"disconnected" | "session_deleted"}), and the snapshot read a
non-existent `.kind`, so every connection reported the literal
"[object Object]". Reads `.status` now, uses the documented
getConnections() accessor instead of the private connections Map, and
carries the library's own `label` ("dapp name once known, otherwise
Connecting…"). The panel shows a tag for anything other than connected
— "reconnecting" is the difference between a pairing that will see the
next signature and one that is dead, which was invisible before.
This commit is contained in:
parent
e9335f5e6b
commit
7b9049f6fc
4 changed files with 69 additions and 14 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"id": "aegis",
|
"id": "aegis",
|
||||||
"name": "Aegis Wallet",
|
"name": "Aegis Wallet",
|
||||||
"version": "0.9.4",
|
"version": "0.9.5",
|
||||||
"category": "plugin",
|
"category": "plugin",
|
||||||
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
|
"description": "Multi-chain wallet (BCH, BTC, TRX, ETH, SOL, SC, DGB) derived from your Theseus vault. Dapps get window.bitcoincash on .x sites; window.tronWeb / window.tronLink / window.ethereum / window.solana on any https page.",
|
||||||
"author": "Silent Mode",
|
"author": "Silent Mode",
|
||||||
|
|
|
||||||
|
|
@ -37,8 +37,23 @@ async function signTx({ request, account, branches, libauth, secp256k1 }) {
|
||||||
hash256, encodeTransaction,
|
hash256, encodeTransaction,
|
||||||
} = libauth;
|
} = libauth;
|
||||||
|
|
||||||
const tx = ensureTransaction(request.transaction, libauth);
|
// The WC message nests the whole WcSignTransactionRequest under
|
||||||
const sourceOutputs = (request.sourceOutputs || []).map((o, i) => {
|
// `.transaction`, so the real shape is:
|
||||||
|
// request.transaction.transaction — the tx (object or hex)
|
||||||
|
// request.transaction.sourceOutputs — the spent outputs
|
||||||
|
// request.inputPaths / request.sequence — on the outer message
|
||||||
|
// Reading request.transaction as the tx (and request.sourceOutputs as
|
||||||
|
// the outputs) meant `tx.inputs` was undefined and signing threw on the
|
||||||
|
// first real request. index.js already read the nested
|
||||||
|
// request.transaction.userPrompt for the approval dialog, so only this
|
||||||
|
// module had it wrong. The flat shape is still accepted so a caller
|
||||||
|
// that hands us an already-unwrapped payload keeps working.
|
||||||
|
const inner = (request.transaction && (request.transaction.transaction !== undefined
|
||||||
|
|| request.transaction.sourceOutputs !== undefined))
|
||||||
|
? request.transaction
|
||||||
|
: request;
|
||||||
|
const tx = ensureTransaction(inner.transaction, libauth);
|
||||||
|
const sourceOutputs = (inner.sourceOutputs || []).map((o, i) => {
|
||||||
if (o.contract) throw new Error(`wc-sign: input ${i} spends a contract — unsupported`);
|
if (o.contract) throw new Error(`wc-sign: input ${i} spends a contract — unsupported`);
|
||||||
return {
|
return {
|
||||||
lockingBytecode: o.lockingBytecode instanceof Uint8Array ? o.lockingBytecode : fromHex(o.lockingBytecode),
|
lockingBytecode: o.lockingBytecode instanceof Uint8Array ? o.lockingBytecode : fromHex(o.lockingBytecode),
|
||||||
|
|
@ -63,12 +78,22 @@ async function signTx({ request, account, branches, libauth, secp256k1 }) {
|
||||||
if (!branch) throw new Error(`wc-sign: unknown path "${hint.pathName}"`);
|
if (!branch) throw new Error(`wc-sign: unknown path "${hint.pathName}"`);
|
||||||
const node = branch.deriveChild(hint.addressIndex);
|
const node = branch.deriveChild(hint.addressIndex);
|
||||||
|
|
||||||
const preimage = generateSigningSerializationBCH({
|
// generateSigningSerializationBCH takes TWO positional arguments:
|
||||||
|
// (compilationContext, { coveredBytecode, signingSerializationType })
|
||||||
|
// Passing one merged object left coveredBytecode undefined, which threw
|
||||||
|
// "Cannot destructure property 'coveredBytecode' of 'undefined'".
|
||||||
|
// For P2PKH the covered bytecode is the spent output's locking script.
|
||||||
|
const preimage = generateSigningSerializationBCH(
|
||||||
|
{
|
||||||
inputIndex: i,
|
inputIndex: i,
|
||||||
signingSerializationType: new Uint8Array([REQUIRED_SIGHASH]),
|
|
||||||
sourceOutputs,
|
sourceOutputs,
|
||||||
transaction: { ...tx, inputs: signedInputs },
|
transaction: { ...tx, inputs: signedInputs },
|
||||||
});
|
},
|
||||||
|
{
|
||||||
|
coveredBytecode: sourceOutputs[i].lockingBytecode,
|
||||||
|
signingSerializationType: new Uint8Array([REQUIRED_SIGHASH]),
|
||||||
|
},
|
||||||
|
);
|
||||||
const digest = hash256(preimage);
|
const digest = hash256(preimage);
|
||||||
const sig = secp256k1.sign(digest, node.privateKey, { prehash: false, lowS: true, format: "der" });
|
const sig = secp256k1.sign(digest, node.privateKey, { prehash: false, lowS: true, format: "der" });
|
||||||
// signature || sighashType byte
|
// signature || sighashType byte
|
||||||
|
|
|
||||||
|
|
@ -171,12 +171,29 @@ module.exports = function makeWc({ HDKey, secp256k1, sha256, hkdf, WalletConnect
|
||||||
function snapshot() {
|
function snapshot() {
|
||||||
const out = {};
|
const out = {};
|
||||||
for (const [walletId, mgr] of managers) {
|
for (const [walletId, mgr] of managers) {
|
||||||
const list = [...(mgr.connections?.values?.() || [])].map((c) => ({
|
// getConnections() is the documented accessor and returns a plain
|
||||||
|
// {id: RelayConnectionState} record. We used to walk mgr.connections
|
||||||
|
// (a private Map) directly, which works but is one library refactor
|
||||||
|
// away from silently returning nothing.
|
||||||
|
const states = typeof mgr.getConnections === "function"
|
||||||
|
? Object.values(mgr.getConnections() || {})
|
||||||
|
: [...(mgr.connections?.values?.() || [])];
|
||||||
|
const list = states.map((c) => ({
|
||||||
id: c.id,
|
id: c.id,
|
||||||
uri: c.uri,
|
uri: c.uri,
|
||||||
|
// `label` is the library's own "dapp name once known, otherwise
|
||||||
|
// Connecting…", so it's the right thing to show while a pairing
|
||||||
|
// is still settling.
|
||||||
|
label: c.label || null,
|
||||||
dappName: c.dappName || null,
|
dappName: c.dappName || null,
|
||||||
dappIcon: c.dappIcon || null,
|
dappIcon: c.dappIcon || null,
|
||||||
status: c.status?.kind || String(c.status || "unknown"),
|
// RelayStatus is an OBJECT: { status: "connected" | "reconnecting"
|
||||||
|
// | "disconnected" | "session_deleted" }. Reading `.kind` (which
|
||||||
|
// does not exist) fell through to String(object) and put the
|
||||||
|
// literal "[object Object]" in the panel's status field.
|
||||||
|
status: typeof c.status === "string"
|
||||||
|
? c.status
|
||||||
|
: (c.status?.status || "unknown"),
|
||||||
connectedAt: c.connectedAt || null,
|
connectedAt: c.connectedAt || null,
|
||||||
}));
|
}));
|
||||||
out[walletId] = list;
|
out[walletId] = list;
|
||||||
|
|
|
||||||
|
|
@ -1825,6 +1825,19 @@ function paintRcvMode() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A paired dapp's relay status, from RelayStatus.status. Worth showing:
|
||||||
|
// "reconnecting" is the difference between "the dapp will see my next
|
||||||
|
// signature" and "this pairing is dead and I should re-pair", and that is
|
||||||
|
// invisible otherwise. "connected" is the normal case, so it stays quiet.
|
||||||
|
function wcStatusTag(status) {
|
||||||
|
const s = String(status || "");
|
||||||
|
if (!s || s === "connected") return "";
|
||||||
|
const label = s === "reconnecting" ? "RECONNECTING"
|
||||||
|
: s === "session_deleted" ? "SESSION GONE"
|
||||||
|
: s === "disconnected" ? "OFFLINE" : s.toUpperCase();
|
||||||
|
return `<span class="ttag" style="background:rgba(224,179,65,.18);color:#e0b341">${esc(label)}</span>`;
|
||||||
|
}
|
||||||
|
|
||||||
// Content of the Connect pane in the picker — WizardConnect pairing lives
|
// Content of the Connect pane in the picker — WizardConnect pairing lives
|
||||||
// here so users can paste a wiz:// URI without diving into per-wallet
|
// here so users can paste a wiz:// URI without diving into per-wallet
|
||||||
// Settings. If no BCH wallet is ready, we show a gate instead of the form.
|
// Settings. If no BCH wallet is ready, we show a gate instead of the form.
|
||||||
|
|
@ -1866,7 +1879,7 @@ function renderConnectPane(bchWallets) {
|
||||||
const rowsHtml = rows.length
|
const rowsHtml = rows.length
|
||||||
? rows.map((c) => `<div class="tx" style="grid-template-columns:auto 1fr auto;cursor:default;align-items:center;margin-top:6px">
|
? rows.map((c) => `<div class="tx" style="grid-template-columns:auto 1fr auto;cursor:default;align-items:center;margin-top:6px">
|
||||||
<div>${c.dappIcon ? `<img src="${esc(c.dappIcon)}" style="width:18px;height:18px;border-radius:4px" onerror="this.hidden=true">` : ""}</div>
|
<div>${c.dappIcon ? `<img src="${esc(c.dappIcon)}" style="width:18px;height:18px;border-radius:4px" onerror="this.hidden=true">` : ""}</div>
|
||||||
<div><div>${esc(c.dappName || "(pairing…)")}</div><div class="hint">on <b>${esc(c.walletLabel)}</b> · <span class="mono">${esc((c.uri || "").slice(0, 40))}…</span></div></div>
|
<div><div>${esc(c.dappName || c.label || "(pairing…)")} ${wcStatusTag(c.status)}</div><div class="hint">on <b>${esc(c.walletLabel)}</b> · <span class="mono">${esc((c.uri || "").slice(0, 40))}…</span></div></div>
|
||||||
<button class="btn sm" data-wcpick="${esc(c.walletId)}|${esc(c.id)}">Disconnect</button>
|
<button class="btn sm" data-wcpick="${esc(c.walletId)}|${esc(c.id)}">Disconnect</button>
|
||||||
</div>`).join("")
|
</div>`).join("")
|
||||||
: `<div class="hint" style="padding:6px 8px">No dapps paired yet.</div>`;
|
: `<div class="hint" style="padding:6px 8px">No dapps paired yet.</div>`;
|
||||||
|
|
@ -4207,11 +4220,11 @@ function renderWcSites() {
|
||||||
const conns = (state?.wc && state.wc[walletId]) || [];
|
const conns = (state?.wc && state.wc[walletId]) || [];
|
||||||
if (!conns.length) { el.innerHTML = `<div class="hint">No dapps paired yet.</div>`; return; }
|
if (!conns.length) { el.innerHTML = `<div class="hint">No dapps paired yet.</div>`; return; }
|
||||||
el.innerHTML = conns.map((c) => {
|
el.innerHTML = conns.map((c) => {
|
||||||
const label = c.dappName || "(pairing…)";
|
const label = c.dappName || c.label || "(pairing…)";
|
||||||
const iconHtml = c.dappIcon ? `<img src="${esc(c.dappIcon)}" style="width:18px;height:18px;border-radius:4px" onerror="this.hidden=true">` : "";
|
const iconHtml = c.dappIcon ? `<img src="${esc(c.dappIcon)}" style="width:18px;height:18px;border-radius:4px" onerror="this.hidden=true">` : "";
|
||||||
return `<div class="tx" style="grid-template-columns:auto 1fr auto;cursor:default;align-items:center">
|
return `<div class="tx" style="grid-template-columns:auto 1fr auto;cursor:default;align-items:center">
|
||||||
<div>${iconHtml}</div>
|
<div>${iconHtml}</div>
|
||||||
<div><div>${esc(label)}</div><div class="hint mono">${esc((c.uri || "").slice(0, 46))}…</div></div>
|
<div><div>${esc(label)} ${wcStatusTag(c.status)}</div><div class="hint mono">${esc((c.uri || "").slice(0, 46))}…</div></div>
|
||||||
<button class="btn sm" data-wcconn="${esc(c.id)}">Disconnect</button>
|
<button class="btn sm" data-wcconn="${esc(c.id)}">Disconnect</button>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join("");
|
}).join("");
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue