The Transaction That Changed Its Own Name — JavaScript Bug Hunt

Modelled on transaction malleability as exploited against Mt.

  • Language: JavaScript
  • Layer: Backend
  • Difficulty: Hard
  • Concepts: Hashing, Idempotency
  • Modelled on: Mt. Gox · 2014
  • Visible tests: different transfers have different ids; re-encoding the signature does not change the id
  • Reward: 50 XP for a complete fix

Briefing

Modelled on transaction malleability as exploited against Mt. Gox (2014): a transaction's id was derived from data that a third party could tamper with without invalidating it. Withdrawals were re-broadcast under a new id, the exchange's bookkeeping never saw the original settle, and it re-sent the funds.

txid.js computes a transaction id over fields that include a mutable signature blob.

Fix computeId so the id depends only on the immutable parts of the transaction.

Bug report

BUG-MALLEABLE · Priority: Critical (double spend) · Reported by: treasury

computeId(tx) must produce the same id for two transactions that move the same funds, even if their signature fields differ — the signature is not part of a transaction's identity.

Observed: re-encoding the signature yields a different id, so the ledger treats a re-broadcast withdrawal as brand new and pays out twice.

Logs

[ledger] withdrawal wd-7781 settled under two distinct ids
[ledger] duplicate payout detected after reconciliation: 1 of 219

The code as shipped

src/tx/txid.js (editable)

function hash(text) {
  var h = 2166136261;
  for (var i = 0; i < text.length; i++) {
    h = h ^ text.charCodeAt(i);
    h = (h * 16777619) >>> 0;
  }
  return h.toString(16);
}

// Identity of a transaction.
exports.computeId = function (tx) {
  return hash(tx.from + "|" + tx.to + "|" + tx.amount + "|" + tx.nonce + "|" + tx.signature);
};

Read-only context: src/tx/PROTOCOL.js.

Open the hunt to edit the files, run the visible tests and submit against the hidden ones. More JavaScript bug hunts.