Aegis: refuse a Tron transaction whose fields appear twice

The decoder read the first copy of a singular protobuf field; java-tron
keeps the last. Any.value is opaque bytes, so a TransferContract carrying
two recipients and two amounts hashes to the same txid either way: the
overlay showed "1 TRX to X" while the chain would move 999 TRX to
another address. It also defeated the plan-time and sign-time draft checks
against a hostile node. A repeated singular field, or a known field with
the wrong wire type, now refuses the transaction.
This commit is contained in:
Local Dev 2026-10-04 02:10:51 +02:00
parent 8b74f5cbf9
commit 1db2c4265b

View file

@ -81,9 +81,20 @@ module.exports = function makeTronDecode({ sha256, base58check }) {
}
return out;
}
const one = (fields, n) => fields.find((f) => f.field === n);
const bytesOf = (fields, n) => { const f = one(fields, n); return f && f.wire === 2 ? f.value : null; };
const numOf = (fields, n) => { const f = one(fields, n); return f && f.wire === 0 ? f.value : null; };
// A singular field that appears twice is legal protobuf: the parser keeps
// the LAST copy (and merges repeated sub-messages). java-tron does that, so
// reading the first copy would show the user one recipient and amount while
// the chain executes another — and Any.value is opaque bytes, so the signed
// hash is the same either way. Refuse instead of guessing, and refuse a
// known field sent with the wrong wire type for the same reason.
const one = (fields, n, wire) => {
const hits = fields.filter((f) => f.field === n);
if (hits.length > 1) throw new Error(`field ${n} appears ${hits.length} times; refusing an ambiguous transaction`);
if (hits[0] && hits[0].wire !== wire) throw new Error(`field ${n} has wire type ${hits[0].wire}, expected ${wire}`);
return hits[0];
};
const bytesOf = (fields, n) => { const f = one(fields, n, 2); return f ? f.value : null; };
const numOf = (fields, n) => { const f = one(fields, n, 0); return f ? f.value : null; };
// 21-byte Tron address (0x41 || h20) → "T…" base58check. Anything else is
// returned as hex so the overlay never hides a malformed field.