Skip to content

Vendored engine/codec has drifted: witness-nonce fix is in health/forks/mempool but not kernel #6

Description

@melvincarvalho

engine/codec/ is vendored into five repos. Seven of the eight consensus files are byte-identical across all of them; blocks.js is not.

b8d482b8  367 lines   kernel, browser-node
4c49c625  368 lines   health, forks, mempool

The single line of difference is a fix, in witnessCommitmentHash:

const reserved = hexToBytes(block.transactions[0].witness?.[0]?.[0] ?? '00'.repeat(32));
if (reserved.length !== 32) return null; // malformed reserved value can never match the commitment

Without it, a coinbase whose witness reserved value is longer than 32 bytes reaches cat.set(reserved, 32) and throws:

THREW: RangeError: offset is out of bounds
  -> uncaught exception instead of a validation failure

Core requires the coinbase witness stack to be exactly one item of exactly 32 bytes and rejects otherwise with bad-witness-nonce-size. The patched version produces the correct verdict (verified: the rule returns false, and the block verdict is false), so the fix is right — it is just in the wrong place.

The problem

It landed in the three downstream copies (all pushed 2026-07-24) and never reached kernel, the canonical source, or browser-node. Consequences:

  • kernel and browser-node still throw on hostile block data
  • anyone importing the library gets the unpatched version
  • the published spec and the live test suite are generated from the unpatched version

Since health, forks and mempool consume untrusted blocks from nostr and WebRTC peers, this is the kind of input that arrives unannounced.

The general issue

Nothing checks that the five copies agree. build.js vendors into the site; nothing verifies the copies against the source. This time the drift is a fix travelling downstream-only. Next time it could be a fix in kernel that never reaches the monitors, leaving three tools validating live mainnet data against stale consensus rules — and nothing would report it.

Suggested fix

  1. Port the one-line guard back into kernel (and re-vendor to browser-node).
  2. Add a drift check to CI: hash every engine/codec/*.js against packages/kernel/codec/*.js and fail on mismatch. A few lines, and it makes this class of divergence impossible to land silently.

Worth deciding explicitly whether the vendored copies are allowed to diverge at all. If they are not, the check should be strict equality. If they are, the exceptions need recording somewhere the next person will find them.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions