Conversation
The rollup version was a constructor argument, computed by the deploy script
as a hash of the config and genesis state. Two rollups deployed on the same
chain with the same config therefore shared a version, and any deployer could
pass an arbitrary one.
RollupCore now computes it in the constructor as the first 4 bytes of
keccak256(abi.encode("aztec_rollup_version", block.chainid, address(this))),
and `version` is removed from RollupConfigInput. The version already feeds the
tx context, Inbox/Outbox message scoping, the Registry key and the epoch proof
public inputs, so each of those becomes deployment-specific.
The version now hashes the chain id, the rollup address, the config and the genesis state, computed at the top of the constructor so it covers the config as supplied.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Refs AztecProtocol/aztec-claude#2043
Problem
The rollup
versionidentifies a rollup instance. It is used in four places:tx_contextsigned into every transaction;Inbox/Outboxmessage scoping (recipient.version,sender.version);Registry.versionToRollupkey;But the rollup never computed it.
RollupCoretook it as a constructor argument (VERSION = _config.version), and the deploy script filled it in asuint32(bytes4(keccak256(abi.encode(config, genesisState)))). As a result:Rollupcould pass any version.The only uniqueness check was
Registry.addRolluprejecting a duplicate, and that only covers rollups in the same registry.Change
As its first statement,
RollupCore's constructor computes:The preimage keeps the config and genesis state the deploy script already hashed, and adds the chain id and the rollup's own address.
It sits at the top of the constructor rather than where
VERSIONused to be assigned._initializeRewardswrites the freshly deployed booster address into_config.rewardConfig.boosterin memory when it was supplied as zero, which is the default. Hashing after that point would cover the mutated config instead of the one supplied.versionis removed fromRollupConfigInput, andInbox/Outboxare constructed withVERSION.Full extent of the change
src/core/RollupCore.solVERSIONat the top of the constructor from chain id,address(this), config and genesis state; passVERSIONto Inbox/Outboxsrc/core/interfaces/IRollup.soluint32 versionfromRollupConfigInput(ABI change to the constructor struct)src/governance/Registry.solscript/deploy/RollupConfiguration.sol_computeConfigVersionand the lines assigningconfig.versiontest/harnesses/TestConstants.solconfig.version(and theTMNT-139TODO about computing it at deployment)test/builder/RollupBuilder.solversiontest/staking/initiateWithdrawByAttester.t.solconfig.versiontest/RollupVersion.t.sol(new)Every removal is a read or write of the deleted
versionfield, or the helper that computed it. No other code is touched.No circuit, constants or TS changes are needed.
aztec-nodereads the version back withrollup.getVersion()after the forge deploy (DeployRollupLibserialisesrollupVersionfrom the deployed contract), and passes config to forge via env vars rather than ABI-encodingRollupConfigInput.Knock-on changes outside this PR
DeployRollupForUpgradeV6.s.solends_config()withconfig.version = uint32(bytes4(keccak256(abi.encode(config, _genesisState(_c)))));. That assignment and its comment block have to be deleted, or that branch won't compile onceversionleaves the struct. Itsverify()checks (inbox.VERSION() == rollup.getVersion()and so on) still hold as written.end-to-end/src/multi-node/governance/add_rollup.test.tshas a comment saying the new rollup's genesis archive root must differ, or the version collides. That's no longer true. The test still passes, but the comment should be updated.Impact and what this does not fix
This is hygiene, not a fix for an exploitable issue on the canonical rollup. L1 consumers resolve the rollup by address or through the Registry, which rejects duplicate versions. A proof or tx replayed onto a clone only affects the clone's own state.
The version also stays 32 bits (
uint32on L1, a JSnumberin the node), so a deployer grinding about 2³² CREATE2 salts can still land on another rollup's version. Closing that needs either a widerversionor also binding the full rollup address in the root rollup public inputs. That's why this PR usesRefs, notCloses, for the issue.Testing
Run locally from
l1-contracts, withConstantsGen.solgenerated and a local stubgenerated/HonkVerifier.solused only so the deploy scripts compile (it is gitignored and not committed):forge buildofsrc+test: compiles.forge fmt --checkon the touched files: clean.forge teston the newRollupVersion.t.solplus the staking,DeployRollupForUpgrade, registry,AddRollup, portals, fee portal,Inbox,OutboxandRollupsuites: all pass.The full suite, gas snapshots and e2e are left to CI.
Created by claudebox · group:
slackbot· requested by Mike (@iAmMichaelConnor) · Slack thread