Skip to content

docs: Heima EVM level is Cancun, not London (correct the rationale; keep the foundry pin)#168

Merged
hanwencheng merged 1 commit into
mainfrom
fix/heima-evm-level-is-cancun-not-london
Jun 2, 2026
Merged

docs: Heima EVM level is Cancun, not London (correct the rationale; keep the foundry pin)#168
hanwencheng merged 1 commit into
mainfrom
fix/heima-evm-level-is-cancun-not-london

Conversation

@Kailai-Wang
Copy link
Copy Markdown
Contributor

What & why

Our docs assert "Heima is London EVM level" and pin evm_version = \"london\" in crates/agentkeys-chain/foundry.toml on that basis. The EVM-level claim is wrong, and the reason for the pin is mis-stated. This PR corrects the rationale across the canonical CLAUDE.md section + foundry.toml comment + 5 docs that repeated it. The london pin itself is unchanged — it's still needed, just for a different reason.

Root cause of the error

The "London" conclusion came from block-header introspection (baseFeePerGas present; mixHash/withdrawalsRoot/blobGasUsed absent → inferred < Shanghai). But Heima is a Substrate/Aura parachain via Frontier — those missing fields are Ethereum PoS-consensus header fields, not opcode-capability signals. The EVM's opcode level is set independently by Frontier's pallet_evm::config().

Verified on-chain (local heima-node --dev, 2026-06-01)

Probe Opcode Introduced Result on Heima
set(42) (Shanghai-compiled) PUSH0 (0x5f) Shanghai ✅ deployed + executed; x() == 42
rt(99) TSTORE/TLOAD (EIP-1153) Cancun ✅ returned 99

Source confirms it: Frontier stable2412 frame/evm/src/lib.rs config() returns &CANCUN_CONFIG (the // London doc-comment one line up is stale upstream).

➡️ Heima's EVM execution level is Cancun. It does not reject PUSH0 or other ≤Cancun opcodes — the old foundry.toml note ("london avoids PUSH0 which Heima would reject") was false.

Why the london pin still stays (the real reason)

forge script ... --broadcast runs a local simulation that validates the fetched block header against the target EVM revision before broadcasting. Heima's header has no prevrandao, so a paris+ target errors:

EVM error; header validation error: `prevrandao` not set

Verified: running the real DeployAgentKeysV1.s.sol against the dev chain with FOUNDRY_EVM_VERSION=cancun reproduces this; with london it deploys. (Note: forge create --broadcast with cancun does not hit this — it's specific to forge script's simulator, which is our deploy path. So the pin stays.)

So: opcode level = Cancun; the london pin = a forge script header-validation workaround, not an EVM-capability ceiling. These are two independent facts the docs had conflated.

Files changed (docs/config only — no code, no pin value change)

  • CLAUDE.md — rewrote "Heima EVM compatibility level" to separate the two facts + note that header-introspection is the wrong way to read opcode level.
  • crates/agentkeys-chain/foundry.toml — corrected the comment above evm_version = \"london\" (value unchanged).
  • docs/chain-setup.md, docs/v2-stage2-heima-deploy-and-test.md, docs/wiki/heima-setup-faq.md, docs/spec/deployed-contracts.md, docs/research/ai-hardware-companion-office-hours.md — corrected the repeated "Heima is London" framing.

Note

The P-256 precompile absence mentioned in stage-2 docs is true and unaffected — Heima has no RIP-7212 0x100 precompile regardless of EVM version; that's why we ship the pure-Solidity P256Verifier. This PR just stops tying that fact to a wrong "London" claim. (Broader P-256/4337 feasibility findings are in the comment on #163.)

How to re-verify

heima-node --dev --rpc-port 9944 --rpc-methods=unsafe -- --dev-block-time 1000
# fund an EVM acct (transfer //Alice -> blake2_256("evm:"++H160)), then:
# deploy a TSTORE/TLOAD (Cancun-only) contract, call it -> returns its input  => EVM >= Cancun
# run DeployAgentKeysV1.s.sol with FOUNDRY_EVM_VERSION=cancun -> 'prevrandao not set' (forge script only)

…don foundry pin, fix the rationale)

The "Heima is London EVM level" claim was derived from block-header
introspection (baseFeePerGas present; mixHash/withdrawalsRoot/blobGasUsed
absent). That reflects the Substrate/Aura consensus header FORMAT, not the
EVM opcode-execution level — which Frontier sets independently via
pallet_evm::config().

Verified on a local heima-node --dev chain (2026-06-01):
- Frontier stable2412 config() returns &CANCUN_CONFIG.
- PUSH0 (Shanghai): a Shanghai-compiled set(42) executed; x() == 42.
- TSTORE/TLOAD (EIP-1153, Cancun-only): rt(99) == 99.
So Heima does NOT reject PUSH0 / post-London opcodes (the old note was wrong).

The evm_version = "london" pin in foundry.toml is RETAINED, but its reason is
corrected: it is a 'forge script' simulator header-validation workaround
(forge script validates the prevrandao-less Heima header against the target
revision and rejects paris+), NOT an EVM-capability ceiling. Verified:
DeployAgentKeysV1.s.sol with FOUNDRY_EVM_VERSION=cancun reproduces
"header validation error: prevrandao not set"; london deploys. (forge create
--broadcast with cancun does NOT hit this.)

Updates the canonical CLAUDE.md section + foundry.toml comment + 5 docs that
repeated the "Heima is London" framing.
@hanwencheng hanwencheng merged commit 01ccf2c into main Jun 2, 2026
7 checks passed
@hanwencheng hanwencheng deleted the fix/heima-evm-level-is-cancun-not-london branch June 2, 2026 03:06
hanwencheng added a commit that referenced this pull request Jun 2, 2026
…live mainnet infra & demo (#171)

* agentkeys: ERC-4337 P-256 master plan (#164) + correct stale London→Cancun comments (#168)

* agentkeys: ERC-4337 P256Account + CREATE2 factory (#164 E1/E2) — WebAuthn-gated master, 14 tests green

* agentkeys: ERC-4337 threat-model delta (#164 E0) + plan cross-link

* agentkeys: P256Account map verifier/decode reverts to SIG_VALIDATION_FAILED (codex P2) +3 tests

* agentkeys: #164 docs — ED/Sybil funding model + E3 design + build status

* agentkeys: ERC-4337 E3 — thin AgentKeysScope to account-auth (retire in-contract K11 + scopeNonce); 59 tests

* agentkeys: record #164 E1 live addresses (EntryPoint v0.7 + factory) + E3 status + cutover ripples

* agentkeys: consolidate deployed addresses into docs/contracts.md + arch.md §5 anchor; deployed-contracts.md→redirect

* agentkeys: ERC-4337 E5 — guardian M-of-N social recovery (generation rotation, independent of primary passkey); +6 tests

* agentkeys: ERC-4337 E6 — VerifyingPaymaster (broker-co-signed sponsorship = Sybil gate) +6 tests; unsafe-mode bundler runner

* agentkeys: ERC-4337 E7+E8 — WebAuthn UserOp signer + passkey-only master demo landed on phase1-wire-demo.sh (phase6); ran green on Heima mainnet (acct 0x3e79925F, addSigner via UserOp, no secp256k1 key)

* agentkeys: #164 plan — E4-E8 complete (E8 ran green on mainnet); cutover redeploy notes

* docs(runbook): document Phase 6 ERC-4337 passkey-master (#164 E8) in operator-runbook-wire.md

* agentkeys: make #164 E8 (Phase 6) default in --real wire demo (skip in --light; opt out --skip-6 / AGENTKEYS_ERC4337_E8=0) + runbook

* agentkeys: E8 fresh-per-run (local) / reuse-one-account (CI auto) + Phase-0 agentkeys-cli guard (#164)

- erc4337-master-e8.sh: fresh (default) vs reuse (auto when $CI) modes; reuse uses a
  persistent passkey+fixed salt (deterministic account), funds only when low; delta
  assertion (count +1) works for both. Fix _has_code (cast code '0x' != empty).
- webauthn-sign.py: keygen idempotent (load-if-exists) so reuse keeps its address.
- phase1-wire-demo.sh: 0.2b real-mode guard fails loud if the on-PATH agentkeys lacks
  the 'agent' subcommand (the stale-binary cascade).
- runbook: document the two E8 modes + env vars.

* agentkeys: harness Phase-0 builds the host agentkeys CLI (self-heal the stale-binary cascade) + runbook

0.2b now runs 'cargo build --release -p agentkeys-cli' so the master-side CLI Phase P
uses ($REPO_ROOT/target/release/agentkeys — already preferred at P.0/P.1) is current,
then verifies the actual binary has the 'agent' subcommand. Opt out AGENTKEYS_SKIP_CLI_BUILD=1.
Runbook: real-mode prereq + troubleshooting row document the build + manual fallback.

* fix(harness): brace ${BEFORE}/${AFTER} in E8 assert — UTF-8 locale absorbed the → byte into the var name (set -u: 'BEFORE: unbound variable')

* agentkeys: unhook E8 from the wire demo — it's a standalone mechanism smoke, not a phase

Per arch.md §9 the 4337 account binds to the MASTER ONBOARDING ceremony (K11 at
stage 2, register the account at stage 4) = #164 E7, landing with the registry
cutover. The standalone erc4337-master-e8.sh uses a throwaway software passkey, so
it proves the mechanism but is NOT the ceremony (the real master K11 is in the
platform authenticator → needs a Touch ID assert). Remove phase6 + the call + the
--real header note from phase1-wire-demo.sh (keep the 0.2b Phase-P CLI guard).
Runbook + plan reframed; #164 E8 row → mechanism smoke, E7 → ceremony binding.

* agentkeys: address codex adversarial review (#164)

- #1 HIGH: VerifyingPaymaster.getHash now binds the paymaster gas limits
  (paymasterAndData[20:52]) so a valid broker sig can't be reused with inflated
  limits. +test_RejectsTamperedGasLimits.
- #3 MED: recover() dedups guardians by (pubX,pubY), not just credIdHash, so one
  physical key under two credIds can't satisfy an M>=2 quorum. +test.
- #2 HIGH (deployment-ordering): warn in AgentKeysScope @dev + threat-model §10 —
  never deploy the thinned scope before the registry cutover (else an EOA master
  could setScope with no biometric).
- #4/#5 documented in threat-model §10 (malleability is nonce-mitigated; recover-to-
  unusable-signer is operator error). 73 tests green.
hanwencheng added a commit that referenced this pull request Jun 2, 2026
…4337, #168 Cancun, #166 bootstrap)

The chain side this plan waited on has landed; reconcile the web-wiring plan with it
and correct two now-false facts.

- Top status banner: ERC-4337 P-256 master account is IMPLEMENTED (#171, Solution A) —
  EntryPoint v0.7 + P256Account + factory + VerifyingPaymaster live on Heima mainnet; #164
  closed. Heima is Cancun, not London (#168). Bootstrap front-run fixed (#166). Defers to
  docs/plan/chain/erc4337-master-account.md.
- §11 (gating): retitled '✅ RESOLVED by #171 (historical)' + a correction note; the
  'does-NOT-auto-fix' block rewritten as 'all addressed' (bootstrap #166; full-intent
  structural via userOpHash; agent-bind/multi-device #171 E3/E4/E5); fixed the false
  'London / no PUSH0' claim → Cancun + pure-Solidity P-256 (~707k gas, no RIP-7212).
- §4.3 + §12 X4: the K11→chain bridge is now an ERC-4337 UserOp (passkey-signs userOpHash
  → broker-gated bundler / handleOps → P256Account); reference erc4337-webauthn-sign.py +
  erc4337-master-e8.sh. Retired the cast/--assertion-file path.
- §5a/§5b: onboarding stage 2 derives the P256Account (CREATE2 from passkey); stage 4 =
  one initCode+registerFirstMasterDevice UserOp (= chain-plan E7, routed through #163);
  bind/grant via UserOp; setScopeWithWebauthn→setScope (E3). §6 W2 + §9 risk updated.
- security-review.md: header marks every finding addressed (#166/#171); Cancun.
- data-model.md phone-first note + arch.md §22c.3: 'decided/London/delegate' → 'implemented
  #171 / Cancun #168 / passkey-signed UserOp'.

Incorporates #163 comments (codex prerequisites + Kailai's Cancun/P-256 feasibility).
hanwencheng added a commit that referenced this pull request Jun 2, 2026
…162)

* docs(web-flow): plan to wire parent-control UI to real backends (learn from wire demo)

Add docs/plan/web-flow/wire-real-paths.md — an execution plan for turning every
narrated / in-memory-stub path in the parent-control UI + daemon ui-bridge into the
SAME real calls harness/phase1-wire-demo.sh makes (broker auth, broker cap-mint,
on-chain cast writes, S3-backed memory worker).

Key decisions captured:
- Daemon-as-orchestrator: browser → daemon ui-bridge only; daemon makes the real
  broker/chain/worker calls (the data-model.md seam).
- Reuse what exists: the daemon's proxy.rs broker client (reqwest + bearer +
  fail-closed) for broker calls; shell out to the existing agentkeys CLI +
  scripts/heima-*.sh (cast) for chain writes — no new Rust chain client.
- The load-bearing K11 bridge: browser does navigator.credentials WebAuthn → daemon
  injects the assertion into the chain tx via a new --assertion-file mode on
  heima-scope-set.sh (avoids a double Touch ID).
- Per-flow wiring tables (onboarding §9 stages 0–4, pairing §10.2, memory) mapping
  UI surface → data-model.md endpoint → real backend call → arch ref.
- Sequenced phases W0–W6, harness-parity test, and reconciliation of stale specs
  (superseded bootstrap endpoints, --upgrade no-op).

Cross-linked from issue-9step-flow.md as the execution detail for P2.1–P2.4.

* docs(web-flow): phone-first host model + verified gating decision + WASM lift scope

Amends the wiring plan for the phone-first reality (most operators have only a
phone, no desktop), keeping one consistent implementation across hosts:

- wire-real-paths.md §0.5 (host-model decision): factor the master-plane logic into
  one portable agentkeys-core hosted as WASM (web) / native lib (mobile, via UniFFI) /
  daemon (desktop), all behind the same lib/client AgentKeysClient contract. The daemon
  is demoted to one host, not a requirement; the broker is the only always-on component;
  the master plane is event-driven + biometric-gated (push-woken).
- wire-real-paths.md §11 (gating decision, VERIFIED): read SidecarRegistry.sol +
  AgentKeysScope.sol. Every master write is msg.sender-bound to the operator secp256k1
  key; the K11 P-256 assertion is an additional gate, not a substitute. => no relayer /
  key-free path without a contract change. Phone holds the secp256k1 key in the Keychain
  (SE is P-256-only, so it seals the K11 passkey, not the EVM key); browser/WASM cannot
  custody it and must delegate the broadcast. Fork (A) keep msg.sender-bound vs (B) move
  to assertion-only auth — recommend (A) for the phone MVP.
- wire-real-paths.md §12 (WASM lift scope): agentkeys-core carve-out, wasm-bindgen
  exports, CoreBackend, WebAuthn interop, chain-write delegation — shared with the future
  mobile UniFFI shell.
- data-model.md: flag the browser-direct prohibition as desktop-first, relaxed for the
  master plane (chain writes still constrained per §11).
- arch.md §22c.3: master control-plane host-model paragraph (defers to the plan).

* docs(web-flow): adversarial security review of §11 gating (codex) — (A) not sound as written

Codex adversarial review of the §11 gating decision, verified against the contracts.
Verdict: fork (A) is the right direction (contracts are NOT assertion-only-safe) but
NOT sound as written. Findings (full doc: wire-real-paths-security-review.md):
- CRITICAL: registerFirstMasterDevice unauthenticated first-call-wins → front-runnable
  operator lockout (SidecarRegistry.sol:100-123).
- HIGH: registerAgentDevice/revokeAgentDevice are msg.sender-only, no K11 (:214-251) —
  a compromised master EVM key binds rogue agents with no biometric.
- HIGH: add-master K11 challenge omits newActorOmni + K11 cred/pubkey/attestation (:167-193).
- HIGH: 'phone holds the key' = software secp256k1 root (not SE-sealed) — weaker than the
  K11 hardware promise; model it as a first-class key.
- HIGH: single global operatorMasterWallet (:66) ⇒ multi-device/recovery story incomplete.
- HIGH: browser→host delegation needs a native confirmation that re-derives the challenge.
- MEDIUM: AgentKeysScope doesn't update WebAuthn signCount; fork (B) unsafe until full-intent
  K11 binding lands on every path.
Confirms the relayer analysis: non-custodial relayer impossible under (A) without
meta-tx/ERC-4337 (EIP-2771 sponsors gas but still needs the secp key).
§11 recommendation updated to reflect 'not sound as written' + required-changes checklist.

* docs(web-flow): record ERC-4337 P-256 smart-account master as the confirmed decision

Supersedes the §11 fork-A-as-MVP framing. The master becomes an ERC-4337 smart account
whose validateUserOp verifies a P-256 (K11/passkey) signature; a bundler broadcasts, an
optional paymaster sponsors gas. Resolves the codex findings:
- removes the software-secp256k1 root (clients sign UserOps with the SE-sealed passkey only);
- key-free + relayer in one (no custodial relayer; bundler + paymaster);
- account address is the stable master → multiple passkeys + quorum recovery (multi-device gap);
- web + mobile become symmetric full masters → the browser→host delegation hop dissolves;
- reuses existing on-chain P-256 verify (K11Verifier.sol).
Residual (folded into the contract-hardening issue): authenticated first-master bootstrap
(CRITICAL), full-intent binding in validateUserOp, and Heima EntryPoint + Solidity-P-256
(London-level, no RIP-7212). Updated §0.5 table, §11 (decision block), §12 (X4/scope),
the security-review doc, and arch.md §22c.3.

* docs(web-flow): revise #162 plan for the landed chain work (#171 ERC-4337, #168 Cancun, #166 bootstrap)

The chain side this plan waited on has landed; reconcile the web-wiring plan with it
and correct two now-false facts.

- Top status banner: ERC-4337 P-256 master account is IMPLEMENTED (#171, Solution A) —
  EntryPoint v0.7 + P256Account + factory + VerifyingPaymaster live on Heima mainnet; #164
  closed. Heima is Cancun, not London (#168). Bootstrap front-run fixed (#166). Defers to
  docs/plan/chain/erc4337-master-account.md.
- §11 (gating): retitled '✅ RESOLVED by #171 (historical)' + a correction note; the
  'does-NOT-auto-fix' block rewritten as 'all addressed' (bootstrap #166; full-intent
  structural via userOpHash; agent-bind/multi-device #171 E3/E4/E5); fixed the false
  'London / no PUSH0' claim → Cancun + pure-Solidity P-256 (~707k gas, no RIP-7212).
- §4.3 + §12 X4: the K11→chain bridge is now an ERC-4337 UserOp (passkey-signs userOpHash
  → broker-gated bundler / handleOps → P256Account); reference erc4337-webauthn-sign.py +
  erc4337-master-e8.sh. Retired the cast/--assertion-file path.
- §5a/§5b: onboarding stage 2 derives the P256Account (CREATE2 from passkey); stage 4 =
  one initCode+registerFirstMasterDevice UserOp (= chain-plan E7, routed through #163);
  bind/grant via UserOp; setScopeWithWebauthn→setScope (E3). §6 W2 + §9 risk updated.
- security-review.md: header marks every finding addressed (#166/#171); Cancun.
- data-model.md phone-first note + arch.md §22c.3: 'decided/London/delegate' → 'implemented
  #171 / Cancun #168 / passkey-signed UserOp'.

Incorporates #163 comments (codex prerequisites + Kailai's Cancun/P-256 feasibility).
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.

2 participants