BLOCKCHAIN AI.NEWS

Infrastructure · Analysis

Mysten Rewrote a Sui Verifier for a Feature No Chain Has Turned On

A 1,300-line rewrite of Sui's programmable-transaction memory-safety check went from pull request to release branch in under a day. Read the test file the engineer added and you can see what he was worried about — and read the protocol config and you can see it cannot happen to anyone yet.

Editorial illustration: a vast branching lattice of polished chrome filaments multiplying outward, collapsing at right into a single compact row of warmly lit frosted glass cells
✓ Reported by this desk from primary sources: Sui pull requests #27975 and #27984 and their diffs · protocol constants read from sui-protocol-config/src/lib.rs · live protocol version queried from the Sui mainnet GraphQL endpoint, Sep 12 · release context from mainnet-v1.79.1 release notes · prior audit scope per the Sui security page. No outlet has covered this change; no advisory has been published.

At 01:49 UTC on September 11, Todd Nowacki, a Mysten Labs engineer who has worked on the Move language since its Libra days, opened a pull request against the Sui repository titled "[ptbs] Rewrite memory invariant check." It merged twenty hours later. Forty-eight minutes after that, a cherry-pick carried it into the v1.80 release branch and merged in thirty-four minutes.

The description is four words of substance: the check has been rewritten "to a more efficient representation that uses bitsets to model relationships between references." No advisory accompanied it. No CVE. The only user-facing note is a single ticked checkbox in the release-notes template — "Protocol: Adds new memory invariant check for reference/value usage in PTBs."

A 1,301-line change landing in one file, on a release branch, on a Friday night, with a one-line description, is the shape of thing this desk's monitor is built to flag. It flagged it. What follows is what the code actually says, which is less alarming than the shape suggests and more interesting than the description admits.

The thing that was rewritten is the second opinion, not the referee

Almost all of the change lands in one file: sui-execution/latest/sui-adapter/src/static_programmable_transactions/typing/invariant_checks/memory_safety.rs. The path is the first clue. This is not Sui's memory-safety enforcement. It is an invariant check — a second, independently written implementation that runs alongside the real verifier and screams if the two disagree.

The old file said so itself, in a doc comment the rewrite deletes. The approach "stems from research work for the Move borrow checker, but would normally not be expressive enough in the presence of control flow. Luckily, PTBs do not have control flow so we can use this approach as a safety net for the Regex based implementation until that code is sufficiently" — the sentence has a stray period here in the original — "tested and hardened."

So: the primary borrow checker for programmable transaction blocks is a regex-based implementation. Because it is new, Mysten wrote a cheaper, structurally different checker to run behind it as a cross-check. That safety net is what got rewritten. The new doc comment describes the replacement: it "represents memory locations and call returns as graph nodes," where "each node tracks its transitive ancestors in a bitset."

The old version is not gone. It has been moved, wholesale, into a module named legacy inside the same file, and the entry point now branches on a feature flag: if memory_safety_invariant_check_v2 is off, execution calls legacy::verify. Both implementations ship. Only one runs.

The test file names the problem out loud

The most informative thing in the pull request is not the 1,241 changed lines of Rust. It is a 25-line test the author added, and the name he gave it: reference_path_explosion.move.

It publishes a Move module with one function, p16, that takes sixteen &u64 references and returns the same sixteen references. Then it builds a programmable transaction that calls p16 four times in a row, each call consuming all sixteen reference results of the call before it. The comment above it explains why that shape matters:

"Every call takes all 16 reference results from the preceding call and returns 16 references. The regex borrow graph must preserve all possible paths through each call, while the set-based verifier represents each result as a node with 16 parents."

That is the whole engineering argument in two sentences. One representation has to keep every distinct path through the chain; the other keeps a node with sixteen edges. The first grows multiplicatively with each call in the chain. The second grows by addition.

Distinct paths through a chain of 16-reference calls

1 call16
2 calls256
3 calls4,096
4 calls — the new test65,536
Bar widths scale with the number of chained calls, not with the path count — the counts themselves are 16n and would be unreadable on a linear axis. Arithmetic by this desk from the limits set in protocol version 136 (max_ptb_returned_references = 16). This is the naive upper bound on distinct paths, not a measurement of memory used by either implementation.

Push the same arithmetic to the ceiling the protocol already allows and the number gets memorable. Protocol version 136 caps a transaction at 256 total returned references, which is sixteen calls of sixteen. Sixteen chained calls give 1616 paths — exactly 264, about 1.8 × 1019. That is arithmetic on published constants, and it is worth being precise about what it is not: the diff does not establish that the old checker ever materialised anything close to that, and nobody at Mysten has said it did.

The limits arrived one version before the rewrite

The constants in that paragraph are not incidental. Protocol version 136 — the version running on Sui mainnet today — introduced the whole set of them at once: max_ptb_live_references of 64, max_ptb_returned_references of 16, max_ptb_total_returned_references of 256, and a gas charge of one unit per live reference translated.

Read in order, the sequence is legible. First bound the inputs, then fix the structure. The caps went in at v136; a month later the checker whose cost those caps were bounding got replaced with one whose cost does not need bounding as tightly. The new test uses sixteen references per call because sixteen is exactly what the v136 cap permits — it is a deliberate probe of the worst case the rules allow.

Nobody can reach any of this

Here is the part that decides how the story should be told. The test file begins with a directive: --enable-feature-flags allow_references_in_ptbs. It has to, because references inside programmable transactions are not switched on.

Search the protocol configuration for that flag and you find it declared, referenced in four doc comments, and never once assigned true — not for mainnet, not for testnet, not for devnet. The ability to pass a reference from one PTB command into the next is a feature Mysten is building, not one anybody is using.

The fix is not live either. memory_safety_invariant_check_v2 is enabled at protocol version 137. Sui mainnet is on 136 — the GraphQL endpoint returns it, and the mainnet-v1.79.1 release notes, published September 9, state it plainly: "Sui Protocol Version in this release: 136." Version 137 does not exist on any public network yet. When it arrives it will flip the flag on every chain at once, with no network guard — unlike three other flags in the same version block, which Mysten explicitly withholds from mainnet and testnet.

So there is no exposure to report, because there is no reachable code path, because the feature is off. What there is instead is a decision, made quickly, about an unshipped component.

What the record does not say

Three things are absent, and they are worth listing rather than filling in. Mysten has published no advisory and no post-mortem for this change, which is consistent with it not being an incident. The pull request contains no discussion — no reviewer comments beyond an automated deployment bot, no stated trigger, nothing explaining why a rewrite of this size needed to be in v1.80 rather than v1.81. And neither pull request says whether anyone found a concrete failing case, or whether the author simply looked at the representation and did the same multiplication anyone can do.

The adapter this file lives in has been looked at from outside. Per Sui's security page, OtterSec "conducted a security assessment of the Sui Adapter to verify its ability to statically load and verify programmable transactions while enforcing strict safety invariants," reported October 30, 2025, and Zellic assessed the Move VM rewrite that December. Neither review is public evidence about this specific check, and neither should be read as one.

The Take

The honest headline on this is not "Sui quietly patched a vulnerability," and anyone who writes that will be wrong on two counts: the feature is disabled everywhere, and the file is a redundant checker rather than the enforcement path. What actually happened is better. An engineer looked at a representation whose cost multiplies per call, wrote the worst case the protocol's own limits permit into a test, replaced the representation, and kept the old one behind a flag so the change can be rolled back at a protocol boundary. Then he shipped it to a release branch before the feature it protects has ever been enabled. That is the correct order of operations, and it is rare enough to note: most of the incidents this desk covers are the same sequence run backwards, with the limits and the structural fix arriving after somebody demonstrated why they were needed. The one criticism that lands is about the record, not the engineering. A 1,300-line rewrite of a safety net, cherry-picked in forty-eight minutes, deserves a sentence somewhere saying what prompted it. "More efficient representation" is true and it is not an explanation, and the people who run this software should not have to read a test file's name to find the reasoning.

More on the subject