Skip to content

Node 4: close C4's code-deposit half by running the pinned init images under Ξ - #19

Draft
Th0rgal wants to merge 10 commits into
mainfrom
feat/node4-create-pinned-source-356667ce
Draft

Node 4: close C4's code-deposit half by running the pinned init images under Ξ#19
Th0rgal wants to merge 10 commits into
mainfrom
feat/node4-create-pinned-source-356667ce

Conversation

@Th0rgal

@Th0rgal Th0rgal commented Aug 25, 2026

Copy link
Copy Markdown
Member

Stacked on #18. Base is main (same as #18), but #18 is not merged, so
the diff below includes its commits. Review only the top commit bf4a16c
that is the entire Node 4 change. Do not merge this before #18.

What this strengthens

The registered parent P-CONTROL-1 (pcontrol1_forall_parent) — no new parent ID.
P-SUBMIT-1 and P-DRAIN-1 registered parents are unchanged.

C4 previously stopped at a CFG prefix. Ctor.lean's opcode facts are rfl on the
first ++ chunk of each init hex — 32 of 638 bytes for deposit, 64 of 503
for exit — and never execute the image. Three things were unchecked:

  1. that the init image reaches its RETURN at all;
  2. that the buffer it returns is the pinned runtime byte for byte, rather than
    merely a copy starting at a plausible offset;
  3. that the exit ctor's SSTORE actually lands, as opposed to being present as
    an opcode 34 bytes in.

Eip8282/Audit/Guarantees/PControl1/CtorXi.lean executes the full pinned
638-byte deposit and 503-byte exit init images under EvmYul.EVM.Ξ and compares
the returned buffer — the value Λ's step (115) installs as the new account's
code — against the pinned runtime images, with the exit ctor leaving slot 0 at
INHIBITOR and slots 1–3 zero.

pcontrol1_ctor_xi_parent is added as a conjunct of pcontrol1_c4_ctor_forall,
so the registered parent is strictly stronger.

This is what ties pinned/bytecode/*/ctor.hex to pinned/bytecode/*/main.hex.
audit/artifacts.lock.json hashes the two files independently, so until this
theorem nothing in the repo ruled out a ctor that deploys something other than the
runtime every other guarantee is proved about.

Why this is not Λ / a CREATE trace

Deliberate. Λ derives its target address as KEC(RLP(sender, nonce)), and the
EIP-8282 contracts are genesis predeploys at 0x0000bFF4… / 0x000064D6…,
not CREATE outputs. Driving Λ here would pin an address that is provably not
the predeploy address, and would pull the opaque @[extern] keccak FFI into the
trusted base for no gain. The bytes an init image actually executes are its
preamble — 10 bytes for deposit, 45 for exit; everything after is the runtime
carried as CODECOPY data — and neither preamble contains SHA3, CREATE, a
call, or precompile dispatch, so A-NATIVE-DECIDE is disclosed exactly as Ctor
and Wave-1 already state it.

A-PINNED-SOURCE: still OPEN

Recorded honestly in audit/assumptions.yaml — no conjunct was invented. This
links ctor.hex to main.hex inside the pin; it observes nothing on chain.
Within the pin the two bytecode files are no longer independent, so
keccak(deployed code) is determined by ctor.hex. What remains assumed is that
these files are the images actually at the two predeploy addresses. The
removal_path is now a single observation: read the live codehash at those
addresses and check it against keccak of the pinned main.hex.

Kill-line

Cut on the init images rather than the runtimes — bytes no runtime trace can
reach, because the init code is not the deployed code:

image offset mutation effect
deposit ctor 5 CODECOPY src 0x0a0x0b deployed copy shifted one byte
exit ctor 34 SSTORE 0x55POP 0x50 exit contract un-inhibited at genesis

ctor_mutant_refutes_parent feeds each to the same ctorXiFacts the parent
contains. ctor_mutants_are_independent shows each cut refutes only its own half.
ctor_mutants_leave_runtime_guarantees_intact proves both cuts leave
controlFacts and PSubmit1.submitFacts true on the pinned runtimes — so the
new conjunct is load-bearing on bytes nothing else in this repository constrained,
not a restatement of a sibling.

The pre-existing one-byte runtime kill-lines still refute the parent, unchanged.

Files changed (top commit only)

  • Eip8282/Audit/Guarantees/PControl1/CtorXi.lean (new)
  • Eip8282/Audit/Guarantees/PControl1.lean — C4 conjunct
  • Eip8282/Tests/PControl1Mutant.lean — C4 kill-line
  • Eip8282/Audit/Trust.lean#print axioms for the new theorems
  • Eip8282.lean — import
  • audit/guarantees.yaml, audit/assumptions.yaml, README.md

No sorry, no project axiom.

…guidance

The receipt claimed in guarantees.yaml ("the four D_J jumpdest tables and
their validJumps bridges") stopped after the two runtime bridges. Add
#print axioms for depositInit_validJumps_eq_D_J, exitInit_validJumps_eq_D_J
and the parent-facing validJumps_are_Xi_tables so the printed output matches
the claim, and list them in the A-NATIVE-DECIDE validation text.

The campaign Method section still told workers D_J_aux was `partial` and to
reach for native_decide on the F1 tables, a constraint EVMYulLean 0ff72b2
removed. Restate it as structurally recursive / decide +kernel in CAMPAIGN.md
and CLOUD_ORCHESTRATOR.md.

No new IDs, no proof or gate changes.
The A-NATIVE-DECIDE justification named EvmYul.FFI.keccak256 / sha256 /
BLAKE2Compress and the partial RLP decoders as what forces native_decide on
the kept Xi traces. Neither is reachable on this path.

Disassembling the four pinned images (depositRuntime 628, depositInit 638,
exitRuntime 458, exitInit 503 bytes) finds no SHA3, no BLOCKHASH, no
call/create opcode and therefore no precompile dispatch, so the evaluator
branches that mention those opaque @[extern] constants are never entered;
importing them does not put them into kernel reduction. EvmRunner.run builds
a world and applies EVM.Xi to it directly, so no transaction RLP is decoded
and separateListRLP / deserializeRLP are never called.

What actually blocks decide +kernel is the cost of evaluating the trace: up
to FUEL = 80000 interpreter steps (300000 for the deposit-cap traces) over
EVMYulLean's monad-transformer stack, AccountMap / TreeSet lookups and
UInt256 / ByteArray operations. Restate the assumption, its removal_path and
the repeated copies of the same claim in Trust.lean, the three bytecode
parent docstrings, WAVE0.md and README.md as a cost limitation. The FFI
dynlib build step stays, because native_decide links the whole interpreter.

Documentation only: no proof, tactic, gate or #print change. audit-check ok.
The Wave-5 nonempty-queue docstring still deferred to "the same FFI reason
as the Wave-1 parent" after Wave 1 in this file was corrected to state the
cost reason. Spell the cost reason out here too: the pinned images reach
neither the opaque @[extern] FFI constants nor the partial RLP decoders, so
the blocker is kernel evaluation of the interpreter steps, not irreducibility.

Docstring only; no proof, assumption YAML or bytecode change.
C4's ctor lemmas are a no-jump init preamble and step against no jumpdest
table, so validJumps_are_Xi_tables cannot transport into or constrain
pcontrol1_c4_ctor_forall; its two init-image equalities are independent
facts carried alongside. P-DRAIN carries the same over-claim and has no
ctor fragment at all.
…t half

C4 previously stopped at a CFG prefix: Ctor.lean's opcode facts are `rfl`
on the first `++` chunk of each init hex -- 32 of 638 bytes for deposit,
64 of 503 for exit -- and never execute the image. Three things were
therefore unchecked: that the init image reaches its RETURN, that the
buffer it returns is the pinned runtime byte for byte rather than merely
a copy starting at a plausible offset, and that the exit ctor's SSTORE
actually lands.

PControl1/CtorXi.lean executes the full pinned init images under
EvmYul.EVM.Xi and compares the returned buffer -- the value Lambda's
step (115) installs as the new account's code -- against the pinned
runtime images. `pcontrol1_ctor_xi_parent` is added as a conjunct of
`pcontrol1_c4_ctor_forall`, so the registered parent
`pcontrol1_forall_parent` is strictly stronger. No new parent ID.

This is deliberately not Lambda itself. Lambda derives its address as
KEC(RLP(sender, nonce)); the EIP-8282 contracts are genesis predeploys,
not CREATE outputs, so driving Lambda would pin an address that is
provably not the predeploy address and would pull the opaque @[extern]
keccak FFI into the trusted base for no gain.

Kill-line, on the init images rather than the runtimes: deposit ctor
byte 5 CODECOPY source 0x0a -> 0x0b (shifts the deployed copy one byte)
and exit ctor byte 34 SSTORE 0x55 -> POP 0x50 (leaves the exit contract
un-inhibited at genesis). `ctor_mutants_leave_runtime_guarantees_intact`
shows both cuts leave controlFacts and submitFacts true on the pinned
runtimes, so the new conjunct is load-bearing on bytes nothing else in
this repository constrained.

A-PINNED-SOURCE stays OPEN. This links ctor.hex to main.hex inside the
pin; it observes nothing on chain. The residual open part -- the
deployed-address / deployed-codehash observation -- is recorded verbatim
in audit/assumptions.yaml along with the now-single-step removal path.

P-SUBMIT-1 and P-DRAIN-1 registered parents are unchanged.
…67ce

main's 7204723 is the squash-merge of PR #18 (Node 3), whose tree is
byte-identical to this branch's own a917c91. Both textual conflicts
(PControl1.lean, guarantees.yaml) had a "theirs" side equal to a917c91,
so the Node 4 C4 code-deposit additions are the only new content and
the merge preserves all of main.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant