From 117eac406b6dcba6655a6eba2ef57540474a8a11 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 07:54:16 -0700 Subject: [PATCH 01/15] feat(kms): use verified Phala L2 light client in default compose Signed-off-by: Kevin Wang --- dstack/kms/README.md | 6 +- dstack/kms/auth-eth/jest.config.js | 2 +- dstack/kms/auth-eth/src/ethereum.test.ts | 85 + dstack/kms/auth-eth/src/ethereum.ts | 38 +- dstack/kms/auth-eth/src/server.ts | 14 +- dstack/kms/dstack-app/docker-compose.yaml | 84 +- dstack/kms/light-client/.dockerignore | 5 + dstack/kms/light-client/Dockerfile | 30 + dstack/kms/light-client/README.md | 106 + dstack/kms/light-client/VALIDATION.md | 70 + dstack/kms/light-client/auth-api.Dockerfile | 17 + dstack/kms/light-client/compose.yaml | 51 + .../fixtures/phala-signed-commitment.json | 1 + dstack/kms/light-client/helios.patch | 1846 +++++++++++++++++ 14 files changed, 2305 insertions(+), 50 deletions(-) create mode 100644 dstack/kms/auth-eth/src/ethereum.test.ts create mode 100644 dstack/kms/light-client/.dockerignore create mode 100644 dstack/kms/light-client/Dockerfile create mode 100644 dstack/kms/light-client/README.md create mode 100644 dstack/kms/light-client/VALIDATION.md create mode 100644 dstack/kms/light-client/auth-api.Dockerfile create mode 100644 dstack/kms/light-client/compose.yaml create mode 100644 dstack/kms/light-client/fixtures/phala-signed-commitment.json create mode 100644 dstack/kms/light-client/helios.patch diff --git a/dstack/kms/README.md b/dstack/kms/README.md index f837488d9..23e40ba54 100644 --- a/dstack/kms/README.md +++ b/dstack/kms/README.md @@ -103,7 +103,11 @@ certificate. `core.onboard.public_key_handover = true` (the default) keeps ### Deployment The first two components are deployed as an dstack app on dstack in Local-Key-Provider mode. -The docker compose file would look like [this](dstack-app/docker-compose.yaml). +The default [Docker Compose file](dstack-app/docker-compose.yaml) uses the +[Phala L2 light client](light-client/README.md). Set `KMS_CONTRACT_ADDR` to a +Phala contract compatible with the current auth API before building from this +repository checkout. Reads use sequencer-authenticated, proof-verified state, +not L1-settled consensus. See the light-client guide for deployment requirements. The solidity contracts are deployed on an ethereum compatible chain. diff --git a/dstack/kms/auth-eth/jest.config.js b/dstack/kms/auth-eth/jest.config.js index dafec6254..d097ee000 100644 --- a/dstack/kms/auth-eth/jest.config.js +++ b/dstack/kms/auth-eth/jest.config.js @@ -6,7 +6,7 @@ module.exports = { preset: 'ts-jest', testEnvironment: 'node', roots: ['/src'], - testMatch: ['**/main.test.ts'], + testMatch: ['**/*.test.ts'], moduleFileExtensions: ['ts', 'js', 'json', 'node'], collectCoverageFrom: [ 'src/**/*.ts', diff --git a/dstack/kms/auth-eth/src/ethereum.test.ts b/dstack/kms/auth-eth/src/ethereum.test.ts new file mode 100644 index 000000000..4086c49aa --- /dev/null +++ b/dstack/kms/auth-eth/src/ethereum.test.ts @@ -0,0 +1,85 @@ +// SPDX-FileCopyrightText: © 2026 Phala Network +// SPDX-License-Identifier: Apache-2.0 + +import { ethers } from 'ethers'; +import { EthereumBackend } from './ethereum'; +import { BootInfo } from './types'; + +jest.mock('ethers', () => ({ + ethers: { ...jest.requireActual('ethers').ethers, Contract: jest.fn() }, +})); + +const bootInfo: BootInfo = { + appId: '00'.repeat(20), instanceId: '00'.repeat(20), + composeHash: '00'.repeat(32), deviceId: '00'.repeat(32), + mrAggregated: '00'.repeat(32), mrSystem: '00'.repeat(32), + osImageHash: '00'.repeat(32), tcbStatus: 'UpToDate', advisoryIds: [], +}; + +describe('verified authorization reads', () => { + const contract = { + isAppAllowed: jest.fn(), isKmsAllowed: jest.fn(), gatewayAppId: jest.fn(), + }; + const provider = { + getNetwork: jest.fn(), getBlockNumber: jest.fn(), getBlock: jest.fn(), + }; + let backend: EthereumBackend; + + beforeEach(() => { + jest.resetAllMocks(); + (ethers.Contract as unknown as jest.Mock).mockReturnValue(contract); + provider.getNetwork.mockResolvedValue({ chainId: 2035n }); + provider.getBlockNumber.mockResolvedValue(100); + provider.getBlock.mockResolvedValue({ number: 98, timestamp: Math.floor(Date.now() / 1000) - 20 }); + contract.isAppAllowed.mockResolvedValue([true, '']); + contract.isKmsAllowed.mockResolvedValue([true, '']); + contract.gatewayAppId.mockResolvedValue('gateway'); + backend = new EthereumBackend(provider as unknown as ethers.JsonRpcProvider, + ethers.ZeroAddress, { chainId: 2035, blockLag: 2, maxAgeSeconds: 60 }); + }); + + it.each([false, true])('pins all boot reads to the same fresh block (kms=%s)', async (isKms) => { + await expect(backend.checkBoot(bootInfo, isKms)).resolves.toEqual({ + isAllowed: true, reason: '', gatewayAppId: 'gateway', + }); + expect(provider.getBlock).toHaveBeenCalledWith(98); + expect(isKms ? contract.isKmsAllowed : contract.isAppAllowed) + .toHaveBeenCalledWith(expect.any(Object), { blockTag: 98 }); + expect(contract.gatewayAppId).toHaveBeenCalledWith({ blockTag: 98 }); + }); + + it('rejects a different chain before contract execution', async () => { + provider.getNetwork.mockResolvedValue({ chainId: 1n }); + await expect(backend.checkBoot(bootInfo, false)).rejects.toThrow('chain ID mismatch'); + expect(contract.isAppAllowed).not.toHaveBeenCalled(); + }); + + it.each([null, { number: 98, timestamp: 0 }, { number: 98, timestamp: 9999999999 }])( + 'rejects unavailable, stale, or future state: %j', async (block) => { + provider.getBlock.mockResolvedValue(block); + await expect(backend.checkBoot(bootInfo, false)).rejects.toThrow('not fresh'); + expect(contract.isAppAllowed).not.toHaveBeenCalled(); + }, + ); + + it('rejects state that expires while contract calls are in flight', async () => { + const started = Date.now(); + const clock = jest.spyOn(Date, 'now').mockReturnValue(started); + contract.gatewayAppId.mockImplementation(async () => { + clock.mockReturnValue(started + 61_000); + return 'gateway'; + }); + try { + await expect(backend.checkBoot(bootInfo, true)).rejects.toThrow('not fresh'); + } finally { + clock.mockRestore(); + } + }); + + it('does not fallback when proof execution fails', async () => { + contract.isAppAllowed.mockRejectedValue(new Error('invalid proof')); + await expect(backend.checkBoot(bootInfo, false)).rejects.toThrow('invalid proof'); + expect(contract.isAppAllowed).toHaveBeenCalledTimes(1); + expect(contract.gatewayAppId).not.toHaveBeenCalled(); + }); +}); diff --git a/dstack/kms/auth-eth/src/ethereum.ts b/dstack/kms/auth-eth/src/ethereum.ts index ebd655140..149010b01 100644 --- a/dstack/kms/auth-eth/src/ethereum.ts +++ b/dstack/kms/auth-eth/src/ethereum.ts @@ -13,11 +13,17 @@ const DSTACK_KMS_ABI = [ "function appImplementation() view returns (address)" ]; +export interface VerifiedReadPolicy { + chainId: number; + blockLag: number; + maxAgeSeconds: number; +} + export class EthereumBackend { private provider: ethers.JsonRpcProvider; private kmsContract: ethers.Contract; - constructor(provider: ethers.JsonRpcProvider, kmsContractAddr: string) { + constructor(provider: ethers.JsonRpcProvider, kmsContractAddr: string, private readPolicy?: VerifiedReadPolicy) { this.provider = provider; this.kmsContract = new ethers.Contract( ethers.getAddress(kmsContractAddr), @@ -26,6 +32,28 @@ export class EthereumBackend { ); } + private async authorizationBlock(): Promise<{ blockTag: ethers.BlockTag; timestamp?: number }> { + if (!this.readPolicy) return { blockTag: await this.provider.getBlockNumber() }; + const { chainId, blockLag } = this.readPolicy; + if (Number((await this.provider.getNetwork()).chainId) !== chainId) { + throw new Error('authorization chain ID mismatch'); + } + const head = await this.provider.getBlockNumber(); + if (head < blockLag) throw new Error('authorization head unavailable'); + const block = await this.provider.getBlock(head - blockLag); + if (!block) throw new Error('authorization block is not fresh'); + this.checkFreshness(block.timestamp); + return { blockTag: block.number, timestamp: block.timestamp }; + } + + private checkFreshness(timestamp?: number): void { + if (!this.readPolicy || timestamp === undefined) return; + const now = Math.floor(Date.now() / 1000); + if (now - timestamp > this.readPolicy.maxAgeSeconds || timestamp > now + 5) { + throw new Error('authorization block is not fresh'); + } + } + private decodeHex(hex: string, sz: number = 32): string { // Remove '0x' prefix if present hex = hex.startsWith('0x') ? hex.slice(2) : hex; @@ -50,8 +78,7 @@ export class EthereumBackend { tcbStatus: bootInfo.tcbStatus, advisoryIds: bootInfo.advisoryIds }; - // Read the whole decision from one block. - const blockTag = await this.provider.getBlockNumber(); + const { blockTag, timestamp } = await this.authorizationBlock(); let response; if (isKms) { response = await this.kmsContract.isKmsAllowed(bootInfoStruct, { blockTag }); @@ -60,6 +87,7 @@ export class EthereumBackend { } const [isAllowed, reason] = response; const gatewayAppId = await this.kmsContract.gatewayAppId({ blockTag }); + this.checkFreshness(timestamp); return { isAllowed, reason, @@ -68,7 +96,7 @@ export class EthereumBackend { } async getGatewayAppId(): Promise { - return await this.kmsContract.gatewayAppId(); + return await this.kmsContract.gatewayAppId({ blockTag: (await this.authorizationBlock()).blockTag }); } async getChainId(): Promise { @@ -77,6 +105,6 @@ export class EthereumBackend { } async getAppImplementation(): Promise { - return await this.kmsContract.appImplementation(); + return await this.kmsContract.appImplementation({ blockTag: (await this.authorizationBlock()).blockTag }); } } diff --git a/dstack/kms/auth-eth/src/server.ts b/dstack/kms/auth-eth/src/server.ts index 54dd14346..ac04c27f3 100644 --- a/dstack/kms/auth-eth/src/server.ts +++ b/dstack/kms/auth-eth/src/server.ts @@ -57,7 +57,19 @@ export async function build(): Promise { const rpcUrl = process.env.ETH_RPC_URL || 'http://localhost:8545'; const kmsContractAddr = process.env.KMS_CONTRACT_ADDR || '0x0000000000000000000000000000000000000000'; const provider = new ethers.JsonRpcProvider(rpcUrl); - server.decorate('ethereum', new EthereumBackend(provider, kmsContractAddr)); + const integerSetting = (name: string, fallback: number): number => { + const value = process.env[name] ?? String(fallback); + if (!/^(0|[1-9][0-9]*)$/.test(value) || !Number.isSafeInteger(Number(value))) { + throw new Error(`invalid ${name}`); + } + return Number(value); + }; + const readPolicy = process.env.ETH_CHAIN_ID === undefined ? undefined : { + chainId: integerSetting('ETH_CHAIN_ID', 2035), + blockLag: integerSetting('ETH_BLOCK_LAG', 1), + maxAgeSeconds: integerSetting('ETH_MAX_BLOCK_AGE_SECONDS', 60), + }; + server.decorate('ethereum', new EthereumBackend(provider, kmsContractAddr, readPolicy)); const publicRpcEndpoint = (value: string): string => { try { diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index 4fb1b603f..232149a8c 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -3,52 +3,52 @@ # SPDX-License-Identifier: Apache-2.0 services: - # Helios is a Ethereum light client + relay: + image: dstack-phala-light-client:local + build: ../light-client + restart: unless-stopped + command: + - opstack-server + - --network=phala + - --execution-rpc=https://rpc.phala.network + - --server-address=0.0.0.0:3000 + - --gossip-address=0.0.0.0:9876 + environment: + OP_NODE_P2P_STATIC: /ip4/8.229.99.126/tcp/9222/p2p/16Uiu2HAmA7EmxHixJhunY6dZKi7nriyUfBCWP3aBPvGUsQBbigm1 + cap_drop: [ALL] + security_opt: [no-new-privileges:true] + helios: - build: - context: . - dockerfile_inline: | - FROM rust:1.92.0@sha256:48851a839d6a67370c9dbe0e709bedc138e3e404b161c5233aedcf2b717366e4 - WORKDIR /app - RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/* build-base openssl-dev protobuf protobuf-dev perl - RUN git clone https://github.com/a16z/helios && \ - cd helios && \ - git checkout 5c61864a167c16141a9a12b976c0e9398b332f07 - WORKDIR /app/helios - RUN cargo build --release - command: [ - "./target/release/helios", - "ethereum", - "--network", "mainnet", - "--checkpoint", "0xbee4f32f91e62060d2aa41c652f6c69431829cfb09b02ea3cad92f65bd15dcce", - "--rpc-bind-ip", "0.0.0.0", - "--rpc-port", "8545", - "--consensus-rpc", "https://ethereum.operationsolarstorm.org", - "--execution-rpc", "https://ethereum-rpc.publicnode.com" - ] + image: dstack-phala-light-client:local + restart: unless-stopped + command: + - helios + - opstack + - --network=phala + - --execution-rpc=https://rpc.phala.network + - --consensus-rpc=http://relay:3000 + - --rpc-bind-ip=0.0.0.0 + - --rpc-port=8545 + depends_on: [relay] + cap_drop: [ALL] + security_opt: [no-new-privileges:true] - # Auth API is a webhook server that authenticates KMS instances and Apps launches auth-api: build: - context: . - dockerfile_inline: | - FROM node:22.23.4-bookworm-slim@sha256:de170b39439eb0e5a3444ad7d60b015614a381e10fecae2e30ec9e735122f38d - WORKDIR /app - - RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/* - RUN git clone https://github.com/Dstack-TEE/dstack.git && \ - cd dstack && \ - git checkout 78057c975fe4b9e21f557fb888d72eeecfb21178 - WORKDIR /app/dstack/kms/auth-eth - RUN npm ci && npm run build - CMD node dist/main.js + context: ../auth-eth + dockerfile: ../light-client/auth-api.Dockerfile + restart: unless-stopped environment: - - HOST=0.0.0.0 - - PORT=8000 - - ETH_RPC_URL=http://helios:8545 - - KMS_CONTRACT_ADDR=0x515cd0dd984Adc75EE8625131FcC3d2D91f7F140 - depends_on: - - helios + HOST: 0.0.0.0 + PORT: "8000" + ETH_RPC_URL: http://helios:8545 + ETH_CHAIN_ID: "2035" + ETH_BLOCK_LAG: "2" + ETH_MAX_BLOCK_AGE_SECONDS: "60" + KMS_CONTRACT_ADDR: ${KMS_CONTRACT_ADDR:?Set the Phala KMS contract address} + depends_on: [helios] + cap_drop: [ALL] + security_opt: [no-new-privileges:true] # KMS handles the TEE Remote Attestation kms: @@ -71,7 +71,7 @@ services: cd dstack && \ git checkout 78057c975fe4b9e21f557fb888d72eeecfb21178 WORKDIR /app/dstack - RUN cargo build --release --locked -p dstack-kms + RUN cargo build --release -p dstack-kms CMD ["./target/release/dstack-kms"] volumes: - kms-volume:/etc/kms diff --git a/dstack/kms/light-client/.dockerignore b/dstack/kms/light-client/.dockerignore new file mode 100644 index 000000000..761ee1f2c --- /dev/null +++ b/dstack/kms/light-client/.dockerignore @@ -0,0 +1,5 @@ +* +!Dockerfile +!helios.patch +!fixtures/ +!fixtures/phala-signed-commitment.json diff --git a/dstack/kms/light-client/Dockerfile b/dstack/kms/light-client/Dockerfile new file mode 100644 index 000000000..d990589a9 --- /dev/null +++ b/dstack/kms/light-client/Dockerfile @@ -0,0 +1,30 @@ +# SPDX-FileCopyrightText: © 2026 Phala Network +# SPDX-License-Identifier: Apache-2.0 + +FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS build +RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ + && rm -rf /var/lib/apt/lists/* +WORKDIR /build +RUN git init helios && cd helios \ + && git remote add origin https://github.com/a16z/helios \ + && git fetch --depth 1 origin d20aa4f4de5886b6c78004447ee197a1cf744362 \ + && git checkout --detach FETCH_HEAD +WORKDIR /build/helios +COPY helios.patch /build/helios.patch +RUN git apply --check /build/helios.patch && git apply /build/helios.patch +COPY fixtures/phala-signed-commitment.json opstack/tests/fixtures/phala-signed-commitment.json +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/build/helios/target \ + cargo test --locked -p helios-opstack --lib \ + && cargo build --locked --release -p helios-cli -p helios-opstack --bins \ + && install -D target/release/helios /out/helios \ + && install -D target/release/opstack-server /out/opstack-server + +FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 +RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libssl3 \ + && rm -rf /var/lib/apt/lists/* \ + && useradd --create-home --uid 10001 helios +COPY --from=build /out/ /usr/local/bin/ +USER helios +ENV HOME=/home/helios +CMD ["helios", "opstack", "--network", "phala"] diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md new file mode 100644 index 000000000..4190261c7 --- /dev/null +++ b/dstack/kms/light-client/README.md @@ -0,0 +1,106 @@ +# Phala KMS light client + +Phala L2 light client for KMS authorization, with sequencer authentication, +proof verification, and local on-chain contract execution. + +## Trust model + +This integration verifies Phala sequencer signatures (chain ID 2035), reconstructs +and checks the signed block hash, verifies account/storage proofs and code hashes, +and executes `eth_call` locally. The execution RPC cannot invent an authorization +result. There is no fallback to unverified RPC execution. + +**This is not L1-settled L2 consensus verification.** It trusts the pinned Phala +sequencer signer `0xF63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC`, initially obtained +from public SystemConfig observations. An operator must independently approve this +pin as part of the measured deployment. A signer change stops synchronization; +update the pin only after independent verification and redeploy. Do not enable +upstream's optional signer-discovery implementation: it is not used by this +configuration and has unresolved verification/readiness problems. + +The signer and L1 SystemConfig are part of `helios.patch`; they are not accepted +from the execution RPC at runtime. The relay's static peer is only a data source, +not a trust anchor. A malicious relay can withhold blocks but cannot forge the +pinned sequencer signature. + +Put **both Helios and auth-api inside the KMS CVM's measured compose**. Running +Helios on an untrusted external machine and trusting its JSON-RPC defeats this +verification boundary. A relay may be external because its output is verified. + +## Build and local smoke deployment + +Helios upstream revision, Cargo lockfile changes, and container base-image digests +are pinned. `helios.patch` includes a regression test against a real public Phala +signed block. The build copies its fixture and runs that test before compiling. + +From this directory: + +```sh +export KMS_CONTRACT_ADDR=0xYourPhalaKmsContract +# The address must have the ABI expected by ../auth-eth. +docker compose build +docker compose up -d +``` + +The smoke auth API is exposed only on `127.0.0.1:18000`; Helios and relay have no +host ports. The default [KMS Compose](../dstack-app/docker-compose.yaml) includes +these services alongside KMS without exposing their ports. Both Compose files +build the auth API from current local sources and require a repository checkout +for their build contexts. Set `KMS_CONTRACT_ADDR` explicitly to a Phala deployment +with the current auth ABI; no legacy contract is selected automatically. +Configure KMS to use `http://auth-api:8000`, never the public execution RPC. + +## Read policy + +`auth-eth` enables the verified-read policy when `ETH_CHAIN_ID` is set: + +- `ETH_CHAIN_ID=2035`: reject another reported chain ID. +- `ETH_BLOCK_LAG=2`: read a slightly older **locally authenticated cached block**. + This is proof-availability lag, not settlement finality. In observed public RPC + samples, proofs for the latest head failed while previous-block proofs worked. +- `ETH_MAX_BLOCK_AGE_SECONDS=60`: reject missing, stale or more-than-five-seconds + future blocks. Boot authorization and gateway ID use the same selected height. + Freshness is checked again before returning the authorization result, so slow + upstream requests cannot extend the acceptance window. + +If the selected authenticated block or its proofs are unavailable, reject the +request. Do not increase lag/age arbitrarily to make failures disappear. Following +restart, allow time for sufficient authenticated blocks to enter the local cache. + +The upstream client stores its own empty configuration under its isolated container +HOME. No external host configuration or credentials are mounted. + +## Compatibility and outstanding acceptance + +Phala currently gossips Isthmus-style V4 envelopes on `/optimism/2035/3/blocks`. +The patch adds that topic, Yamux transport, static peers with reconnect, payload +bounds, and fixes header reconstruction (including logs bloom and withdrawal root). +It is scoped to current V4 operation, not historical pre-Isthmus synchronization. + +The patch upgrades op-revm to 20.0.0 and revm to 38.0.0 and selects Jovian/Karst +at Phala's actual activation timestamps. The Karst CLZ opcode and a verified +L1Block read were compared with the public RPC at the same height; the results +matched. eth_call disables the transaction-only gas cap, preserving simulation +behavior after the upgrade. This image is scoped to KMS read calls, not advertised +as a general-purpose transaction submission or gas-estimation service. + +The discovered on-chain KMS used in initial read-only tests is +`0xae43c1d4814f665aa4e08ec3589309dff939a049`. It is an older contract, not a +user-confirmed current production deployment: its ABI has `mrImage` where this +repository uses `osImageHash`, and lacks `appImplementation()`. Boot endpoints can +be tested against it, but the current information endpoint will fail. No fallback +or fabricated implementation address was added to hide that incompatibility. + +Before production sign-off: + +1. Confirm the intended current KMS contract and semantics, or explicitly implement + and test supported legacy compatibility. +2. Run target-contract acceptance inside the intended KMS CVM. Local container + build, HTTP authorization, stale-head rejection and relay restart recovery pass. +3. Approve sequencer trust, signer update procedure and freshness policy for + irreversible KMS key release. + +## Validation + +See [VALIDATION.md](VALIDATION.md) for test conditions, results and limitations. +The public signed-block regression fixture is in `fixtures/`. diff --git a/dstack/kms/light-client/VALIDATION.md b/dstack/kms/light-client/VALIDATION.md new file mode 100644 index 000000000..744eaa5df --- /dev/null +++ b/dstack/kms/light-client/VALIDATION.md @@ -0,0 +1,70 @@ +# Validation record + +Date: 2026-09-22 UTC. Repository base: +`ccd96638e334fb3df658661743bb641239ed6f71`. + +## Conditions + +- Cloud Linux x86_64, native Rust/Cargo 1.94.1, Node 20.20.2, Python 3.12.3, + curl 8.5.0, Docker Server 29.5.3. No CVM, attestation or key release was tested. +- Helios base `d20aa4f4de5886b6c78004447ee197a1cf744362` plus `helios.patch`; + op-revm 20.0.0, revm 38.0.0. Container toolchain/base images are digest-pinned + in the Dockerfiles. Cargo dependencies are locked. +- Public Phala execution RPC `https://rpc.phala.network`, chain ID 2035, sequencer + signer pinned in the patch. Signed blocks obtained from the public Conduit static + peer documented in README. No credentials, chain writes or paid transactions. +- KMS proxy `0xae43c1d4814f665aa4e08ec3589309dff939a049`, an independently + discovered **legacy** deployment, not a user-confirmed production target. +- Synthetic boot inputs from its public allowlist events. Tests do not represent + successful TEE attestation or possession of a real KMS device identity. +- Auth read lag 2 blocks and max age 60 seconds. The Karst opcode comparison used + lag 3 and the same fixed block 5569954 for local and remote calls. +- Local auth HTTP port 18000; internal relay/client ports only. Separate native + fault-test clients had empty caches per mode. Fault proxies bound to loopback. + +## Results + +- Native TypeScript build passes; 13 Jest tests pass (existing API tests plus + block pinning, chain mismatch, unavailable/stale/future state, expiration during + in-flight contract calls, and no fallback). +- Two Rust tests pass: authentic Phala signed payload/header with signature/chain/ + payload/root negative cases and exact Jovian/Karst activation boundaries. +- Both images build. Real container HTTP KMS authorization returns allow for the + public allowlist vector and deny for an unlisted MR. +- Upgraded Karst execution supports CLZ: `0x5f1e5f5260205ff3` returns 256. + L1Block.number() and CLZ match the public RPC at the same authenticated height. +- Real auth API through the upgraded native client rejects corrupted account + proofs, corrupted contract code, wrong chain ID, replayed stale genuine signed + head, and unavailable execution RPC. The positive control allows. +- The observing execution proxy is programmed to lie on eth_call. Method counts + showed proof/code/access-list RPC requests and **zero forwarded eth_call calls**. +- Stopping the Docker relay and waiting at least 75 seconds causes the previously + allowed vector to be denied under the 60-second freshness limit. Restarting the + relay restores fresh synchronization and allow; the negative vector remains denied. + +## Limits / remaining acceptance + +The trust model is sequencer authentication, not L1 settlement. The tests do not +validate a current KMS CVM, actual attestation/key release, or a user-selected +production contract. The tested legacy contract lacks appImplementation() and +uses old image-measurement semantics. Consequently this is not final sign-off on +current-repository production KMS compatibility; no health response or contract +implementation address was fabricated to hide the mismatch. + +## Live contract reads + +A fresh read-only acceptance run at 2026-09-22T14:43:10.310Z used the existing +Docker auth container's ethers client, connected exclusively to internal Helios +at http://helios:8545 (not directly to the public RPC). Chain ID was 2035; the +selected authenticated block was 5570092, two blocks behind the observed head, +31 seconds old, hash +0x4a6c928109fb0a203e2c76a8b095edd88aa38855ddf811424eeb87e51fc2a811. +The same Docker/toolchain, pinned signer, public peer and execution-provider +conditions listed above apply. No chain transactions, CVM or key release. + +- KMS gatewayAppId() at 0xae43c1d4814f665aa4e08ec3589309dff939a049 returned + 29af9606ffff15d7b95cb1bfb774137d7445c59a. +- L1Block.number() at 0x4200000000000000000000000000000000000015 returned 26033691. + +Both calls executed locally using proof-verified state under a sequencer-signed +root. This does not change the stated sequencer-trust/L1-settlement distinction. diff --git a/dstack/kms/light-client/auth-api.Dockerfile b/dstack/kms/light-client/auth-api.Dockerfile new file mode 100644 index 000000000..bd653ecf4 --- /dev/null +++ b/dstack/kms/light-client/auth-api.Dockerfile @@ -0,0 +1,17 @@ +# SPDX-FileCopyrightText: © 2026 Phala Network +# SPDX-License-Identifier: Apache-2.0 + +FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 AS build +WORKDIR /app +COPY package.json package-lock.json ./ +RUN npm ci --ignore-scripts +COPY tsconfig.json ./ +COPY src/ src/ +RUN npm run build && npm prune --omit=dev --ignore-scripts + +FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 +WORKDIR /app +COPY --from=build /app/node_modules/ node_modules/ +COPY --from=build /app/dist/ dist/ +USER node +CMD ["node", "dist/main.js"] diff --git a/dstack/kms/light-client/compose.yaml b/dstack/kms/light-client/compose.yaml new file mode 100644 index 000000000..4612a7f99 --- /dev/null +++ b/dstack/kms/light-client/compose.yaml @@ -0,0 +1,51 @@ +# SPDX-FileCopyrightText: © 2026 Phala Network +# SPDX-License-Identifier: Apache-2.0 + +services: + relay: + image: dstack-phala-light-client:local + build: . + restart: unless-stopped + command: + - opstack-server + - --network=phala + - --execution-rpc=https://rpc.phala.network + - --server-address=0.0.0.0:3000 + - --gossip-address=0.0.0.0:9876 + environment: + OP_NODE_P2P_STATIC: /ip4/8.229.99.126/tcp/9222/p2p/16Uiu2HAmA7EmxHixJhunY6dZKi7nriyUfBCWP3aBPvGUsQBbigm1 + cap_drop: [ALL] + security_opt: [no-new-privileges:true] + + helios: + image: dstack-phala-light-client:local + restart: unless-stopped + command: + - helios + - opstack + - --network=phala + - --execution-rpc=https://rpc.phala.network + - --consensus-rpc=http://relay:3000 + - --rpc-bind-ip=0.0.0.0 + - --rpc-port=8545 + depends_on: [relay] + cap_drop: [ALL] + security_opt: [no-new-privileges:true] + + auth-api: + build: + context: ../auth-eth + dockerfile: ../light-client/auth-api.Dockerfile + restart: unless-stopped + environment: + HOST: 0.0.0.0 + PORT: "8000" + ETH_RPC_URL: http://helios:8545 + ETH_CHAIN_ID: "2035" + ETH_BLOCK_LAG: "2" + ETH_MAX_BLOCK_AGE_SECONDS: "60" + KMS_CONTRACT_ADDR: ${KMS_CONTRACT_ADDR:?Set the Phala KMS contract address} + ports: ["127.0.0.1:18000:8000"] + depends_on: [helios] + cap_drop: [ALL] + security_opt: [no-new-privileges:true] diff --git a/dstack/kms/light-client/fixtures/phala-signed-commitment.json b/dstack/kms/light-client/fixtures/phala-signed-commitment.json new file mode 100644 index 000000000..03105a62f --- /dev/null +++ b/dstack/kms/light-client/fixtures/phala-signed-commitment.json @@ -0,0 +1 @@ +{"data":"0x6f9ed5e529315d012bac7d1cd0050fd01f86c141cb18682e2867186c0f0f7656bb6ec5b9e7754d5d87512bafe2b45aecba77cb2c2c111bad4f860ee3e084c7f142000000000000000000000000000000000000111370bfdfd1c4bfb69d18c917626a37106630eae2408814606dab36141ac2d763bb6703fe88093cb7fd18ac19c131bcec52ec9ded35381a36437d5726054de0c100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c1df53ef06e66b52c81f9c2b723effe8c8e8d4472f0abc8826be30f4bbfc525a10fd54000000000080c3c901000000005ad5000000000000c788b26a0000000030020000fc00000000000000000000000000000000000000000000000000000000000000f4daa5a0aca5f2a89f1dfc2211db19eaf74e8f8cda77f2ec18897277dc4ad301410200004f0300000000000000000000000000000000000041527ab4c263c1b0c02610d9d2e564acdc00df53029e4ddbf54d679379b385b001000000fa000000060000000000000000040000007ef90106a077fe7dc410171f1621fa54e8a4f0bab0c44f845afaa5af752041e0412358b41594deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b23db6be2b00342f60000f79c50000000000000000000000006ab2888300000000018d3d31000000000000000000000000000000000000000000000000000000000c2794340000000000000000000000000000000000000000000000000000000000af01ce3f65480ab18d1a14035b6418c486794cf1ac00380cf5498264b32217f65d48390000000000000000000000009fb23129982c993743eb9bb156af8cc8fa2ac7610000000000000000000000000190","signature":{"r":"0x92dd2f259dd35ef4f11236e9d6f54caf3d5da6caab799ac66328b10ea1be0cc2","s":"0x6ea7433e43d0a7efb619d5d84b2392876f241621882d6036f9a8bae0e5b83fb8","yParity":"0x1","v":"0x1"}} \ No newline at end of file diff --git a/dstack/kms/light-client/helios.patch b/dstack/kms/light-client/helios.patch new file mode 100644 index 000000000..dc3646e1e --- /dev/null +++ b/dstack/kms/light-client/helios.patch @@ -0,0 +1,1846 @@ +diff --git a/Cargo.lock b/Cargo.lock +index 57d7eea..23c8171 100644 +--- a/Cargo.lock ++++ b/Cargo.lock +@@ -23,7 +23,7 @@ version = "0.5.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" + dependencies = [ +- "crypto-common", ++ "crypto-common 0.1.6", + "generic-array", + ] + +@@ -35,7 +35,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" + dependencies = [ + "cfg-if", + "cipher", +- "cpufeatures", ++ "cpufeatures 0.2.17", + ] + + [[package]] +@@ -240,41 +240,59 @@ dependencies = [ + + [[package]] + name = "alloy-eip2930" +-version = "0.2.1" ++version = "0.2.4" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" ++checksum = "e64579d931b3f8eacc7c9ab0b220e87e9c4816e5c724ede1947b55c2f8e92ae5" + dependencies = [ + "alloy-primitives", + "alloy-rlp", ++ "borsh", + "serde", + ] + + [[package]] + name = "alloy-eip7702" +-version = "0.6.1" ++version = "0.6.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" ++checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" + dependencies = [ + "alloy-primitives", + "alloy-rlp", ++ "borsh", + "k256", + "serde", + "thiserror 2.0.12", + ] + ++[[package]] ++name = "alloy-eip7928" ++version = "0.3.7" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" ++dependencies = [ ++ "alloy-primitives", ++ "alloy-rlp", ++ "borsh", ++ "once_cell", ++ "serde", ++ "thiserror 2.0.12", ++] ++ + [[package]] + name = "alloy-eips" +-version = "1.0.37" ++version = "1.8.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "ac7f1c9a1ccc7f3e03c36976455751a6166a4f0d2d2c530c3f87dfe7d0cdc836" ++checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" + dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", ++ "alloy-eip7928", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", ++ "borsh", + "c-kzg", + "derive_more", + "either", +@@ -283,7 +301,6 @@ dependencies = [ + "serde", + "serde_with", + "sha2 0.10.9", +- "thiserror 2.0.12", + ] + + [[package]] +@@ -368,18 +385,19 @@ dependencies = [ + + [[package]] + name = "alloy-primitives" +-version = "1.3.0" ++version = "1.7.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "3cfebde8c581a5d37b678d0a48a32decb51efd7a63a08ce2517ddec26db705c8" ++checksum = "d5e9dbd49258ac3ab893a481d46be29b58be7f734dcac46cd80b6b13ee36566c" + dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more", +- "foldhash", +- "getrandom 0.3.3", +- "hashbrown 0.15.4", ++ "fixed-cache", ++ "foldhash 0.2.0", ++ "getrandom 0.4.3", ++ "hashbrown 0.17.1", + "indexmap 2.10.0", + "itoa", + "k256", +@@ -387,11 +405,12 @@ dependencies = [ + "paste", + "proptest", + "rand 0.9.2", ++ "rapidhash", + "ruint", + "rustc-hash 2.1.1", ++ "secp256k1 0.31.1", + "serde", +- "sha3", +- "tiny-keccak", ++ "sha3 0.11.0", + ] + + [[package]] +@@ -650,9 +669,9 @@ dependencies = [ + + [[package]] + name = "alloy-serde" +-version = "1.0.37" ++version = "1.8.3" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5413814be7a22fbc81e0f04a2401fcc3eb25e56fd53b04683e8acecc6e1fe01b" ++checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" + dependencies = [ + "alloy-primitives", + "serde", +@@ -1049,6 +1068,23 @@ dependencies = [ + "zeroize", + ] + ++[[package]] ++name = "ark-ff" ++version = "0.6.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" ++dependencies = [ ++ "ark-ff-asm 0.6.0", ++ "ark-ff-macros 0.6.0", ++ "ark-serialize 0.6.0", ++ "ark-std 0.6.0", ++ "digest 0.10.7", ++ "educe", ++ "num-bigint", ++ "num-traits", ++ "zeroize", ++] ++ + [[package]] + name = "ark-ff-asm" + version = "0.3.0" +@@ -1079,6 +1115,16 @@ dependencies = [ + "syn 2.0.104", + ] + ++[[package]] ++name = "ark-ff-asm" ++version = "0.6.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" ++dependencies = [ ++ "quote", ++ "syn 2.0.104", ++] ++ + [[package]] + name = "ark-ff-macros" + version = "0.3.0" +@@ -1117,6 +1163,19 @@ dependencies = [ + "syn 2.0.104", + ] + ++[[package]] ++name = "ark-ff-macros" ++version = "0.6.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" ++dependencies = [ ++ "num-bigint", ++ "num-traits", ++ "proc-macro2", ++ "quote", ++ "syn 2.0.104", ++] ++ + [[package]] + name = "ark-poly" + version = "0.5.0" +@@ -1188,13 +1247,26 @@ version = "0.5.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" + dependencies = [ +- "ark-serialize-derive", ++ "ark-serialize-derive 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint", + ] + ++[[package]] ++name = "ark-serialize" ++version = "0.6.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" ++dependencies = [ ++ "ark-serialize-derive 0.6.0", ++ "ark-std 0.6.0", ++ "digest 0.10.7", ++ "num-bigint", ++ "serde_with", ++] ++ + [[package]] + name = "ark-serialize-derive" + version = "0.5.0" +@@ -1206,6 +1278,17 @@ dependencies = [ + "syn 2.0.104", + ] + ++[[package]] ++name = "ark-serialize-derive" ++version = "0.6.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "syn 2.0.104", ++] ++ + [[package]] + name = "ark-std" + version = "0.3.0" +@@ -1236,6 +1319,16 @@ dependencies = [ + "rand 0.8.5", + ] + ++[[package]] ++name = "ark-std" ++version = "0.6.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" ++dependencies = [ ++ "num-traits", ++ "rand 0.8.5", ++] ++ + [[package]] + name = "arrayref" + version = "0.3.9" +@@ -1574,12 +1667,6 @@ dependencies = [ + "tower-service", + ] + +-[[package]] +-name = "az" +-version = "1.2.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" +- + [[package]] + name = "backtrace" + version = "0.3.75" +@@ -1646,21 +1733,6 @@ version = "0.6.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" + +-[[package]] +-name = "bit-set" +-version = "0.8.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +-dependencies = [ +- "bit-vec", +-] +- +-[[package]] +-name = "bit-vec" +-version = "0.8.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" +- + [[package]] + name = "bitcoin-io" + version = "0.1.3" +@@ -1685,11 +1757,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + + [[package]] + name = "bitflags" +-version = "2.9.1" ++version = "2.13.2" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" ++checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" + dependencies = [ +- "serde", ++ "serde_core", + ] + + [[package]] +@@ -1732,6 +1804,15 @@ dependencies = [ + "generic-array", + ] + ++[[package]] ++name = "block-buffer" ++version = "0.12.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" ++dependencies = [ ++ "hybrid-array", ++] ++ + [[package]] + name = "bls12_381" + version = "0.8.0" +@@ -1748,9 +1829,9 @@ dependencies = [ + + [[package]] + name = "blst" +-version = "0.3.15" ++version = "0.3.17" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" ++checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" + dependencies = [ + "cc", + "glob", +@@ -1758,6 +1839,30 @@ dependencies = [ + "zeroize", + ] + ++[[package]] ++name = "borsh" ++version = "1.8.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" ++dependencies = [ ++ "borsh-derive", ++ "bytes", ++ "cfg_aliases", ++] ++ ++[[package]] ++name = "borsh-derive" ++version = "1.8.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" ++dependencies = [ ++ "once_cell", ++ "proc-macro-crate 3.3.0", ++ "proc-macro2", ++ "quote", ++ "syn 3.0.6", ++] ++ + [[package]] + name = "brotli" + version = "8.0.1" +@@ -1830,9 +1935,9 @@ dependencies = [ + + [[package]] + name = "c-kzg" +-version = "2.1.1" ++version = "2.1.8" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" ++checksum = "38d04308254695569fdb9bfe3bacc1c91837a670d0806605eb82d63748fbd3a6" + dependencies = [ + "blst", + "cc", +@@ -1895,7 +2000,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" + dependencies = [ + "cfg-if", + "cipher", +- "cpufeatures", ++ "cpufeatures 0.2.17", + ] + + [[package]] +@@ -1959,7 +2064,7 @@ version = "0.4.4" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" + dependencies = [ +- "crypto-common", ++ "crypto-common 0.1.6", + "inout", + "zeroize", + ] +@@ -2055,7 +2160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" + dependencies = [ + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "hex", + "proptest", + "serde", +@@ -2157,6 +2262,15 @@ dependencies = [ + "libc", + ] + ++[[package]] ++name = "cpufeatures" ++version = "0.3.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" ++dependencies = [ ++ "libc", ++] ++ + [[package]] + name = "crc" + version = "3.3.0" +@@ -2250,7 +2364,7 @@ version = "0.27.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "crossterm_winapi", + "libc", + "mio 0.8.11", +@@ -2298,6 +2412,15 @@ dependencies = [ + "typenum", + ] + ++[[package]] ++name = "crypto-common" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" ++dependencies = [ ++ "hybrid-array", ++] ++ + [[package]] + name = "crypto-mac" + version = "0.8.0" +@@ -2347,7 +2470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" + dependencies = [ + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", +@@ -2570,13 +2693,13 @@ dependencies = [ + + [[package]] + name = "derive-where" +-version = "1.5.0" ++version = "1.7.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" ++checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" + dependencies = [ + "proc-macro2", + "quote", +- "syn 2.0.104", ++ "syn 3.0.6", + ] + + [[package]] +@@ -2623,10 +2746,20 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" + dependencies = [ + "block-buffer 0.10.4", + "const-oid", +- "crypto-common", ++ "crypto-common 0.1.6", + "subtle", + ] + ++[[package]] ++name = "digest" ++version = "0.11.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" ++dependencies = [ ++ "block-buffer 0.12.1", ++ "crypto-common 0.2.2", ++] ++ + [[package]] + name = "dirs" + version = "5.0.1" +@@ -2867,7 +3000,7 @@ dependencies = [ + "log", + "rand 0.8.5", + "serde", +- "sha3", ++ "sha3 0.10.8", + "zeroize", + ] + +@@ -2936,7 +3069,7 @@ name = "ethereum_hashing" + version = "0.7.0" + source = "git+https://github.com/ncitron/ethereum_hashing?rev=7ee70944ed4fabe301551da8c447e4f4ae5e6c35#7ee70944ed4fabe301551da8c447e4f4ae5e6c35" + dependencies = [ +- "cpufeatures", ++ "cpufeatures 0.2.17", + "sha2 0.10.9", + ] + +@@ -3079,6 +3212,16 @@ dependencies = [ + "version_check", + ] + ++[[package]] ++name = "fixed-cache" ++version = "0.1.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "2fe63500644ef0269fe6b744e7e5dc5c20b5eebf3d881bc2be53f194636f6583" ++dependencies = [ ++ "equivalent", ++ "rapidhash", ++] ++ + [[package]] + name = "fixed-hash" + version = "0.8.0" +@@ -3119,13 +3262,19 @@ version = "0.1.5" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + ++[[package]] ++name = "foldhash" ++version = "0.2.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" ++ + [[package]] + name = "font-kit" + version = "0.14.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "byteorder", + "core-foundation", + "core-graphics", +@@ -3379,11 +3528,22 @@ dependencies = [ + "cfg-if", + "js-sys", + "libc", +- "r-efi", ++ "r-efi 5.3.0", + "wasi 0.14.2+wasi-0.2.4", + "wasm-bindgen", + ] + ++[[package]] ++name = "getrandom" ++version = "0.4.3" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" ++dependencies = [ ++ "cfg-if", ++ "libc", ++ "r-efi 6.0.0", ++] ++ + [[package]] + name = "ghash" + version = "0.5.1" +@@ -3429,16 +3589,6 @@ dependencies = [ + "regex-syntax 0.8.5", + ] + +-[[package]] +-name = "gmp-mpfr-sys" +-version = "1.6.8" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" +-dependencies = [ +- "libc", +- "windows-sys 0.60.2", +-] +- + [[package]] + name = "group" + version = "0.13.0" +@@ -3528,8 +3678,18 @@ checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" + dependencies = [ + "allocator-api2", + "equivalent", +- "foldhash", ++ "foldhash 0.1.5", ++] ++ ++[[package]] ++name = "hashbrown" ++version = "0.17.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" ++dependencies = [ ++ "foldhash 0.2.0", + "serde", ++ "serde_core", + ] + + [[package]] +@@ -3772,6 +3932,7 @@ dependencies = [ + "reqwest 0.12.22", + "revm", + "serde", ++ "serde_json", + "sha2 0.9.9", + "snap", + "ssz_types", +@@ -4074,6 +4235,15 @@ version = "1.0.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + ++[[package]] ++name = "hybrid-array" ++version = "0.4.10" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" ++dependencies = [ ++ "typenum", ++] ++ + [[package]] + name = "hyper" + version = "0.14.32" +@@ -4482,7 +4652,7 @@ version = "0.7.9" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "cfg-if", + "libc", + ] +@@ -4705,14 +4875,24 @@ version = "0.1.5" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" + dependencies = [ +- "cpufeatures", ++ "cpufeatures 0.2.17", ++] ++ ++[[package]] ++name = "keccak" ++version = "0.2.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" ++dependencies = [ ++ "cfg-if", ++ "cpufeatures 0.3.1", + ] + + [[package]] + name = "keccak-asm" +-version = "0.1.4" ++version = "0.1.8" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" ++checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" + dependencies = [ + "digest 0.10.7", + "sha3-asm", +@@ -4765,12 +4945,12 @@ dependencies = [ + "libp2p-identity", + "libp2p-mdns", + "libp2p-metrics", +- "libp2p-mplex", + "libp2p-noise", + "libp2p-ping", + "libp2p-quic", + "libp2p-swarm", + "libp2p-tcp", ++ "libp2p-yamux", + "multiaddr", + "pin-project", + ] +@@ -4927,24 +5107,6 @@ dependencies = [ + "prometheus-client", + ] + +-[[package]] +-name = "libp2p-mplex" +-version = "0.39.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "4d34780b514b159e6f3fd70ba3e72664ec89da28dca2d1e7856ee55e2c7031ba" +-dependencies = [ +- "asynchronous-codec", +- "bytes", +- "futures", +- "libp2p-core", +- "log", +- "nohash-hasher", +- "parking_lot 0.12.4", +- "rand 0.8.5", +- "smallvec", +- "unsigned-varint", +-] +- + [[package]] + name = "libp2p-noise" + version = "0.42.2" +@@ -5074,13 +5236,26 @@ dependencies = [ + "yasna", + ] + ++[[package]] ++name = "libp2p-yamux" ++version = "0.43.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" ++dependencies = [ ++ "futures", ++ "libp2p-core", ++ "log", ++ "thiserror 1.0.69", ++ "yamux", ++] ++ + [[package]] + name = "libredox" + version = "0.1.8" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "libc", + ] + +@@ -5471,7 +5646,7 @@ version = "0.30.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "cfg-if", + "cfg_aliases", + "libc", +@@ -5720,9 +5895,9 @@ dependencies = [ + + [[package]] + name = "op-revm" +-version = "10.1.0" ++version = "20.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f9ba4f4693811e73449193c8bd656d3978f265871916882e6a51a487e4f96217" ++checksum = "a5cfe618e3426e7a65b74f105aa706cfc92cd100ade8a6588a15c6ac3002e467" + dependencies = [ + "auto_impl", + "revm", +@@ -5741,7 +5916,7 @@ version = "0.10.73" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "cfg-if", + "foreign-types 0.3.2", + "libc", +@@ -5990,9 +6165,9 @@ dependencies = [ + + [[package]] + name = "phf" +-version = "0.11.3" ++version = "0.13.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" ++checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" + dependencies = [ + "phf_macros", + "phf_shared", +@@ -6001,19 +6176,19 @@ dependencies = [ + + [[package]] + name = "phf_generator" +-version = "0.11.3" ++version = "0.13.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" ++checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" + dependencies = [ ++ "fastrand", + "phf_shared", +- "rand 0.8.5", + ] + + [[package]] + name = "phf_macros" +-version = "0.11.3" ++version = "0.13.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" ++checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" + dependencies = [ + "phf_generator", + "phf_shared", +@@ -6024,9 +6199,9 @@ dependencies = [ + + [[package]] + name = "phf_shared" +-version = "0.11.3" ++version = "0.13.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" ++checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" + dependencies = [ + "siphasher", + ] +@@ -6158,7 +6333,7 @@ version = "0.8.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" + dependencies = [ +- "cpufeatures", ++ "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", + ] +@@ -6170,7 +6345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" + dependencies = [ + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", + ] +@@ -6345,26 +6520,16 @@ version = "1.7.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" + dependencies = [ +- "bit-set", +- "bit-vec", +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "lazy_static", + "num-traits", + "rand 0.9.2", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax 0.8.5", +- "rusty-fork", +- "tempfile", + "unarray", + ] + +-[[package]] +-name = "quick-error" +-version = "1.2.3" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +- + [[package]] + name = "quick-protobuf" + version = "0.8.1" +@@ -6475,6 +6640,12 @@ version = "5.3.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + ++[[package]] ++name = "r-efi" ++version = "6.0.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" ++ + [[package]] + name = "radium" + version = "0.7.0" +@@ -6561,13 +6732,23 @@ dependencies = [ + "rand_core 0.9.3", + ] + ++[[package]] ++name = "rapidhash" ++version = "4.5.1" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" ++dependencies = [ ++ "getrandom 0.3.3", ++ "rustversion", ++] ++ + [[package]] + name = "ratatui" + version = "0.26.3" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "cassowary", + "compact_str", + "crossterm", +@@ -6634,7 +6815,7 @@ version = "0.5.17" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + ] + + [[package]] +@@ -6876,9 +7057,9 @@ dependencies = [ + + [[package]] + name = "revm" +-version = "29.0.1" ++version = "38.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "718d90dce5f07e115d0e66450b1b8aa29694c1cf3f89ebddaddccc2ccbd2f13e" ++checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" + dependencies = [ + "revm-bytecode", + "revm-context", +@@ -6895,9 +7076,9 @@ dependencies = [ + + [[package]] + name = "revm-bytecode" +-version = "6.2.2" ++version = "10.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "66c52031b73cae95d84cd1b07725808b5fd1500da3e5e24574a3b2dc13d9f16d" ++checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" + dependencies = [ + "bitvec", + "phf", +@@ -6907,9 +7088,9 @@ dependencies = [ + + [[package]] + name = "revm-context" +-version = "9.1.0" ++version = "16.0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5a20c98e7008591a6f012550c2a00aa36cba8c14cc88eb88dec32eb9102554b4" ++checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" + dependencies = [ + "bitvec", + "cfg-if", +@@ -6924,9 +7105,9 @@ dependencies = [ + + [[package]] + name = "revm-context-interface" +-version = "10.2.0" ++version = "17.0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "b50d241ed1ce647b94caf174fcd0239b7651318b2c4c06b825b59b973dfb8495" ++checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" + dependencies = [ + "alloy-eip2930", + "alloy-eip7702", +@@ -6940,9 +7121,9 @@ dependencies = [ + + [[package]] + name = "revm-database" +-version = "7.0.5" ++version = "13.0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "39a276ed142b4718dcf64bc9624f474373ed82ef20611025045c3fb23edbef9c" ++checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" + dependencies = [ + "alloy-eips", + "revm-bytecode", +@@ -6954,22 +7135,23 @@ dependencies = [ + + [[package]] + name = "revm-database-interface" +-version = "7.0.5" ++version = "11.0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "8c523c77e74eeedbac5d6f7c092e3851dbe9c7fec6f418b85992bd79229db361" ++checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" + dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "serde", ++ "thiserror 2.0.12", + ] + + [[package]] + name = "revm-handler" +-version = "10.0.1" ++version = "18.1.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "550331ea85c1d257686e672081576172fe3d5a10526248b663bbf54f1bef226a" ++checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" + dependencies = [ + "auto_impl", + "derive-where", +@@ -6986,9 +7168,9 @@ dependencies = [ + + [[package]] + name = "revm-inspector" +-version = "10.0.1" ++version = "19.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "7c0a6e9ccc2ae006f5bed8bd80cd6f8d3832cd55c5e861b9402fdd556098512f" ++checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" + dependencies = [ + "auto_impl", + "either", +@@ -7004,21 +7186,22 @@ dependencies = [ + + [[package]] + name = "revm-interpreter" +-version = "25.0.3" ++version = "35.0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "06575dc51b1d8f5091daa12a435733a90b4a132dca7ccee0666c7db3851bc30c" ++checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" + dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", ++ "revm-state", + "serde", + ] + + [[package]] + name = "revm-precompile" +-version = "27.0.0" ++version = "34.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "25b57d4bd9e6b5fe469da5452a8a137bc2d030a3cd47c46908efc615bbc699da" ++checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" + dependencies = [ + "ark-bls12-381", + "ark-bn254", +@@ -7030,20 +7213,19 @@ dependencies = [ + "c-kzg", + "cfg-if", + "k256", +- "libsecp256k1", + "p256", ++ "revm-context-interface", + "revm-primitives", + "ripemd", +- "rug", + "secp256k1 0.31.1", + "sha2 0.10.9", + ] + + [[package]] + name = "revm-primitives" +-version = "20.2.1" ++version = "23.0.0" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" ++checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" + dependencies = [ + "alloy-primitives", + "num_enum", +@@ -7053,11 +7235,12 @@ dependencies = [ + + [[package]] + name = "revm-state" +-version = "7.0.5" ++version = "11.0.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d" ++checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" + dependencies = [ +- "bitflags 2.9.1", ++ "alloy-eip7928", ++ "bitflags 2.13.2", + "revm-bytecode", + "revm-primitives", + "serde", +@@ -7139,27 +7322,17 @@ dependencies = [ + "tokio", + ] + +-[[package]] +-name = "rug" +-version = "1.28.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "58ad2e973fe3c3214251a840a621812a4f40468da814b1a3d6947d433c2af11f" +-dependencies = [ +- "az", +- "gmp-mpfr-sys", +- "libc", +- "libm", +-] +- + [[package]] + name = "ruint" +-version = "1.15.0" ++version = "1.20.1" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" ++checksum = "2973657b5127d510e230f5c63d2d106af9c8f79393d8b9f4647323e8196bdde5" + dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", ++ "ark-ff 0.5.0", ++ "ark-ff 0.6.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", +@@ -7173,7 +7346,7 @@ dependencies = [ + "rand 0.9.2", + "rlp", + "ruint-macro", +- "serde", ++ "serde_core", + "valuable", + "zeroize", + ] +@@ -7244,7 +7417,7 @@ version = "1.0.8" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "errno", + "libc", + "linux-raw-sys", +@@ -7313,18 +7486,6 @@ version = "1.0.21" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" + +-[[package]] +-name = "rusty-fork" +-version = "0.3.0" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +-dependencies = [ +- "fnv", +- "quick-error", +- "tempfile", +- "wait-timeout", +-] +- + [[package]] + name = "rw-stream-sink" + version = "0.3.0" +@@ -7488,7 +7649,7 @@ version = "2.11.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "core-foundation", + "core-foundation-sys", + "libc", +@@ -7721,7 +7882,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" + dependencies = [ + "block-buffer 0.9.0", + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", + ] +@@ -7733,7 +7894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" + dependencies = [ + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "digest 0.10.7", + ] + +@@ -7745,7 +7906,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" + dependencies = [ + "block-buffer 0.9.0", + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "digest 0.9.0", + "opaque-debug", + ] +@@ -7757,7 +7918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" + dependencies = [ + "cfg-if", +- "cpufeatures", ++ "cpufeatures 0.2.17", + "digest 0.10.7", + ] + +@@ -7768,14 +7929,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" + dependencies = [ + "digest 0.10.7", +- "keccak", ++ "keccak 0.1.5", ++] ++ ++[[package]] ++name = "sha3" ++version = "0.11.0" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" ++dependencies = [ ++ "digest 0.11.3", ++ "keccak 0.2.2", + ] + + [[package]] + name = "sha3-asm" +-version = "0.1.4" ++version = "0.1.8" + source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" ++checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" + dependencies = [ + "cc", + "cfg-if", +@@ -8083,6 +8254,17 @@ dependencies = [ + "unicode-ident", + ] + ++[[package]] ++name = "syn" ++version = "3.0.6" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" ++dependencies = [ ++ "proc-macro2", ++ "quote", ++ "unicode-ident", ++] ++ + [[package]] + name = "syn-solidity" + version = "1.3.0" +@@ -8150,7 +8332,7 @@ version = "0.6.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "core-foundation", + "system-configuration-sys 0.6.0", + ] +@@ -8505,7 +8687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" + dependencies = [ + "async-compression", +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + "bytes", + "futures-core", + "futures-util", +@@ -8822,7 +9004,7 @@ version = "0.5.1" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" + dependencies = [ +- "crypto-common", ++ "crypto-common 0.1.6", + "subtle", + ] + +@@ -8908,15 +9090,6 @@ version = "1.0.2" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + +-[[package]] +-name = "wait-timeout" +-version = "0.2.1" +-source = "registry+https://github.com/rust-lang/crates.io-index" +-checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +-dependencies = [ +- "libc", +-] +- + [[package]] + name = "walkdir" + version = "2.5.0" +@@ -9527,7 +9700,7 @@ version = "0.39.0" + source = "registry+https://github.com/rust-lang/crates.io-index" + checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" + dependencies = [ +- "bitflags 2.9.1", ++ "bitflags 2.13.2", + ] + + [[package]] +@@ -9593,6 +9766,20 @@ dependencies = [ + "time", + ] + ++[[package]] ++name = "yamux" ++version = "0.10.2" ++source = "registry+https://github.com/rust-lang/crates.io-index" ++checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" ++dependencies = [ ++ "futures", ++ "log", ++ "nohash-hasher", ++ "parking_lot 0.12.4", ++ "rand 0.8.5", ++ "static_assertions", ++] ++ + [[package]] + name = "yansi" + version = "1.0.1" +diff --git a/Cargo.toml b/Cargo.toml +index 5cf402f..0a3e393 100644 +--- a/Cargo.toml ++++ b/Cargo.toml +@@ -54,7 +54,7 @@ alloy = { version = "1.0.37", features = [ + ] } + alloy-trie = { version = "0.9.1", features = ["ethereum"] } + op-alloy-rpc-types = "0.20.0" +-revm = { version = "29.0.1", default-features = false, features = [ ++revm = { version = "38.0.0", default-features = false, features = [ + "std", + "serde", + "optional_block_gas_limit", +diff --git a/common/src/fork_schedule.rs b/common/src/fork_schedule.rs +index 21d6aec..ca4b433 100644 +--- a/common/src/fork_schedule.rs ++++ b/common/src/fork_schedule.rs +@@ -34,6 +34,8 @@ pub struct ForkSchedule { + pub holocene_timestamp: u64, + pub isthmus_timestamp: u64, + pub jovian_timestamp: u64, ++ #[serde(default = "disabled_fork")] ++ pub karst_timestamp: u64, + } + + impl Default for ForkSchedule { +@@ -70,6 +72,7 @@ impl Default for ForkSchedule { + holocene_timestamp: u64::MAX, + isthmus_timestamp: u64::MAX, + jovian_timestamp: u64::MAX, ++ karst_timestamp: u64::MAX, + } + } + } +@@ -85,3 +88,7 @@ impl ForkSchedule { + } + } + } ++ ++fn disabled_fork() -> u64 { ++ u64::MAX ++} +diff --git a/ethereum/src/evm.rs b/ethereum/src/evm.rs +index c6c415e..23666d2 100644 +--- a/ethereum/src/evm.rs ++++ b/ethereum/src/evm.rs +@@ -129,6 +129,7 @@ impl> EthereumEvm { + cfg.spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule); + cfg.chain_id = self.chain_id; + cfg.disable_block_gas_limit = !validate_tx; ++ cfg.tx_gas_limit_cap = (!validate_tx).then_some(u64::MAX); + cfg.disable_eip3607 = !validate_tx; + cfg.disable_base_fee = !validate_tx; + cfg.disable_nonce_check = !validate_tx; +@@ -188,6 +189,7 @@ impl> EthereumEvm { + + BlockEnv { + number: U256::from(block.header.number()), ++ slot_num: 0, + beneficiary: block.header.beneficiary(), + timestamp: U256::from(block.header.timestamp()), + gas_limit: block.header.gas_limit(), +diff --git a/opstack/Cargo.toml b/opstack/Cargo.toml +index b80f63a..fc8b310 100644 +--- a/opstack/Cargo.toml ++++ b/opstack/Cargo.toml +@@ -24,7 +24,7 @@ async-trait.workspace = true + alloy.workspace = true + alloy-trie.workspace = true + revm.workspace = true +-op-revm = { version = "10.1.0", default-features = false, features = [ ++op-revm = { version = "20.0.0", default-features = false, features = [ + "std", + "serde", + "optional_block_gas_limit", +@@ -56,7 +56,7 @@ helios-revm-utils = { path = "../revm-utils" } + axum = "0.7.6" + clap = { version = "4.5.4", features = ["derive", "env"] } + # networking +-libp2p = { version = "0.51.3", features = ["macros", "tokio", "tcp", "mplex", "noise", "gossipsub", "ping"] } ++libp2p = { version = "0.51.3", features = ["macros", "tokio", "tcp", "yamux", "noise", "gossipsub", "ping"] } + discv5 = "0.7.0" + libp2p-identity = { version = "0.1.2", features = ["secp256k1"] } + unsigned-varint = "0.7.1" +@@ -67,3 +67,6 @@ parking_lot = { version = "0.12.2" } + [target.'cfg(target_arch = "wasm32")'.dependencies] + wasm-bindgen-futures = "0.4.37" + getrandom = { version = "0.2.1", features = ["js"] } ++ ++[dev-dependencies] ++serde_json.workspace = true +diff --git a/opstack/src/config.rs b/opstack/src/config.rs +index 74dad94..01c0651 100644 +--- a/opstack/src/config.rs ++++ b/opstack/src/config.rs +@@ -45,6 +45,7 @@ pub struct NetworkConfig { + + #[derive(Copy, Clone, Debug)] + pub enum Network { ++ Phala, + OpMainnet, + Base, + BaseSepolia, +@@ -56,6 +57,7 @@ pub enum Network { + impl Display for Network { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { ++ Self::Phala => f.write_str("phala"), + Self::OpMainnet => f.write_str("op-mainnet"), + Self::Base => f.write_str("base"), + Self::BaseSepolia => f.write_str("base-sepolia"), +@@ -71,6 +73,7 @@ impl FromStr for Network { + + fn from_str(s: &str) -> Result { + match s { ++ "phala" => Ok(Self::Phala), + "op-mainnet" => Ok(Self::OpMainnet), + "base" => Ok(Self::Base), + "base-sepolia" => Ok(Self::BaseSepolia), +@@ -85,6 +88,30 @@ impl FromStr for Network { + impl From for NetworkConfig { + fn from(value: Network) -> Self { + match value { ++ Network::Phala => NetworkConfig { ++ consensus_rpc: Some("http://127.0.0.1:3000".parse().unwrap()), ++ chain: ChainConfig { ++ chain_id: 2035, ++ unsafe_signer: address!("F63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC"), ++ system_config_contract: address!("eBf5859b7646ca9cf8A981613569bF28394F2571"), ++ eth_network: EthNetwork::Mainnet, ++ forks: ForkSchedule { ++ bedrock_timestamp: 0, ++ regolith_timestamp: 0, ++ canyon_timestamp: 0, ++ delta_timestamp: 0, ++ ecotone_timestamp: 0, ++ fjord_timestamp: 0, ++ granite_timestamp: 1736272801, ++ holocene_timestamp: 1736445601, ++ isthmus_timestamp: 1746806401, ++ jovian_timestamp: 1769491801, ++ karst_timestamp: 1787673601, ++ ..Default::default() ++ }, ++ }, ++ verify_unsafe_signer: false, ++ }, + Network::OpMainnet => NetworkConfig { + consensus_rpc: Some( + "https://op-mainnet.operationsolarstorm.org" +diff --git a/opstack/src/consensus.rs b/opstack/src/consensus.rs +index b2532bc..ab6956b 100644 +--- a/opstack/src/consensus.rs ++++ b/opstack/src/consensus.rs +@@ -4,11 +4,11 @@ use std::time::Duration; + + use url::Url; + +-use alloy::consensus::proofs::{calculate_transaction_root, calculate_withdrawals_root}; ++use alloy::consensus::proofs::calculate_transaction_root; + use alloy::consensus::transaction::SignerRecoverable; + use alloy::consensus::{Header as ConsensusHeader, Transaction as TxTrait}; + use alloy::eips::eip4895::{Withdrawal, Withdrawals}; +-use alloy::primitives::{b256, fixed_bytes, Address, Bloom, BloomInput, B256, U256}; ++use alloy::primitives::{b256, fixed_bytes, Address, Bloom, B256, U256}; + use alloy::rlp::Decodable; + use alloy::rpc::types::{ + Block, EIP1186AccountProofResponse, Header, Transaction as EthTransaction, +@@ -159,7 +159,9 @@ impl Inner { + let age = now.saturating_sub(timestamp); + let number = payload.block_number; + +- if let Ok(block) = payload_to_block(payload) { ++ { ++ let block = ++ payload_to_block(payload, B256::from_slice(&commitment.data[..32]))?; + self.latest_block = Some(block.header.number); + _ = self.block_send.send(block).await; + +@@ -168,8 +170,6 @@ impl Inner { + number, + age.as_secs() + ); +- } else { +- tracing::warn!("invalid block received"); + } + } + } +@@ -271,7 +271,10 @@ fn verify_unsafe_signer(config: Config, signer: Arc>) { + }); + } + +-fn payload_to_block(value: ExecutionPayload) -> Result> { ++fn payload_to_block( ++ value: ExecutionPayload, ++ parent_beacon_block_root: B256, ++) -> Result> { + let empty_nonce = fixed_bytes!("0000000000000000"); + let empty_uncle_hash = + b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); +@@ -283,7 +286,7 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { + .map(|(i, tx_bytes)| { + let tx_bytes = tx_bytes.to_vec(); + let mut tx_bytes_slice = tx_bytes.as_slice(); +- let tx_envelope = OpTxEnvelope::decode(&mut tx_bytes_slice).unwrap(); ++ let tx_envelope = OpTxEnvelope::decode(&mut tx_bytes_slice)?; + let base_fee = tx_envelope.effective_gas_price(Some(value.base_fee_per_gas.to())); + let recovered = tx_envelope.try_into_recovered()?; + +@@ -323,9 +326,9 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { + let txs_root = calculate_transaction_root(&tx_envelopes); + + let withdrawals: Vec = value.withdrawals.into_iter().map(|w| w.into()).collect(); +- let withdrawals_root = calculate_withdrawals_root(&withdrawals); ++ let withdrawals_root = value.withdrawals_root; + +- let logs_bloom: Bloom = Bloom::from(BloomInput::Raw(&value.logs_bloom)); ++ let logs_bloom: Bloom = Bloom::from_slice(&value.logs_bloom); + + let consensus_header = ConsensusHeader { + parent_hash: value.parent_hash, +@@ -345,12 +348,19 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { + base_fee_per_gas: Some(value.base_fee_per_gas.to::()), + blob_gas_used: Some(value.blob_gas_used), + excess_blob_gas: Some(value.excess_blob_gas), +- parent_beacon_block_root: None, ++ parent_beacon_block_root: Some(parent_beacon_block_root), + extra_data: value.extra_data.to_vec().into(), +- requests_hash: None, ++ requests_hash: Some(b256!( ++ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" ++ )), + logs_bloom, + }; + ++ eyre::ensure!( ++ consensus_header.hash_slow() == value.block_hash, ++ "payload block hash mismatch" ++ ); ++ + let header = Header { + hash: value.block_hash, + inner: consensus_header, +@@ -361,3 +371,38 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { + Ok(Block::new(header, BlockTransactions::Full(txs)) + .with_withdrawals(Some(Withdrawals::new(withdrawals)))) + } ++ ++#[cfg(test)] ++mod phala_tests { ++ use super::*; ++ use alloy::primitives::address; ++ ++ #[test] ++ fn authentic_phala_payload_reconstructs_header() { ++ let commitment: SequencerCommitment = serde_json::from_str(include_str!( ++ "../tests/fixtures/phala-signed-commitment.json" ++ )) ++ .unwrap(); ++ let signer = address!("F63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC"); ++ commitment.verify(signer, 2035).unwrap(); ++ assert!(commitment.verify(signer, 8453).is_err()); ++ assert!(commitment.verify(Address::ZERO, 2035).is_err()); ++ let payload = ExecutionPayload::try_from(&commitment).unwrap(); ++ let parent_root = B256::from_slice(&commitment.data[..32]); ++ let block = payload_to_block(payload.clone(), parent_root).unwrap(); ++ assert_eq!(block.header.number, 5569808); ++ let truncated = snap::raw::Encoder::new().compress_vec(&[0; 10]).unwrap(); ++ assert!(SequencerCommitment::new(&truncated).is_err()); ++ let mut empty = commitment.clone(); ++ empty.data = Default::default(); ++ assert!(ExecutionPayload::try_from(&empty).is_err()); ++ let mut forged = commitment.clone(); ++ let mut data = forged.data.to_vec(); ++ data[0] ^= 1; ++ forged.data = data.into(); ++ assert!(forged.verify(signer, 2035).is_err()); ++ let mut corrupt = payload; ++ corrupt.state_root = B256::ZERO; ++ assert!(payload_to_block(corrupt, parent_root).is_err()); ++ } ++} +diff --git a/opstack/src/evm.rs b/opstack/src/evm.rs +index c86c5a9..4fb991e 100644 +--- a/opstack/src/evm.rs ++++ b/opstack/src/evm.rs +@@ -132,6 +132,7 @@ impl> OpStackEvm { + cfg.spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule); + cfg.chain_id = self.chain_id; + cfg.disable_block_gas_limit = !validate_tx; ++ cfg.tx_gas_limit_cap = (!validate_tx).then_some(u64::MAX); + cfg.disable_eip3607 = !validate_tx; + cfg.disable_base_fee = !validate_tx; + cfg.disable_nonce_check = !validate_tx; +@@ -191,6 +192,7 @@ impl> OpStackEvm { + + BlockEnv { + number: U256::from(block.header.number()), ++ slot_num: 0, + beneficiary: block.header.beneficiary(), + timestamp: U256::from(block.header.timestamp()), + gas_limit: block.header.gas_limit(), +@@ -203,7 +205,11 @@ impl> OpStackEvm { + } + + pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSchedule) -> OpSpecId { +- if timestamp >= fork_schedule.isthmus_timestamp { ++ if timestamp >= fork_schedule.karst_timestamp { ++ OpSpecId::KARST ++ } else if timestamp >= fork_schedule.jovian_timestamp { ++ OpSpecId::JOVIAN ++ } else if timestamp >= fork_schedule.isthmus_timestamp { + OpSpecId::ISTHMUS + } else if timestamp >= fork_schedule.holocene_timestamp { + OpSpecId::HOLOCENE +@@ -223,3 +229,30 @@ pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSched + OpSpecId::default() + } + } ++ ++#[cfg(test)] ++mod phala_fork_tests { ++ use super::*; ++ use crate::config::{Network, NetworkConfig}; ++ ++ #[test] ++ fn selects_phala_forks_at_exact_activation() { ++ let forks = NetworkConfig::from(Network::Phala).chain.forks; ++ assert_eq!( ++ get_spec_id_for_block_timestamp(forks.jovian_timestamp - 1, &forks), ++ OpSpecId::ISTHMUS ++ ); ++ assert_eq!( ++ get_spec_id_for_block_timestamp(forks.jovian_timestamp, &forks), ++ OpSpecId::JOVIAN ++ ); ++ assert_eq!( ++ get_spec_id_for_block_timestamp(forks.karst_timestamp - 1, &forks), ++ OpSpecId::JOVIAN ++ ); ++ assert_eq!( ++ get_spec_id_for_block_timestamp(forks.karst_timestamp, &forks), ++ OpSpecId::KARST ++ ); ++ } ++} +diff --git a/opstack/src/lib.rs b/opstack/src/lib.rs +index d4796d5..044270e 100644 +--- a/opstack/src/lib.rs ++++ b/opstack/src/lib.rs +@@ -30,10 +30,17 @@ pub struct SequencerCommitment { + + impl SequencerCommitment { + pub fn new(data: &[u8]) -> Result { ++ eyre::ensure!( ++ snap::raw::decompress_len(data)? <= 10 * 1024 * 1024, ++ "oversized payload" ++ ); + let mut decoder = snap::raw::Decoder::new(); + let decompressed = decoder.decompress_vec(data)?; + +- let signature = Signature::try_from(&decompressed[..65])?; ++ let signature_bytes = decompressed ++ .get(..65) ++ .ok_or_else(|| eyre::eyre!("truncated signature"))?; ++ let signature = Signature::try_from(signature_bytes)?; + let data = Bytes::from(decompressed[65..].to_vec()); + + Ok(SequencerCommitment { data, signature }) +@@ -56,7 +63,10 @@ impl TryFrom<&SequencerCommitment> for ExecutionPayload { + type Error = eyre::Report; + + fn try_from(value: &SequencerCommitment) -> Result { +- let payload_bytes = &value.data[32..]; ++ let payload_bytes = value ++ .data ++ .get(32..) ++ .ok_or_else(|| eyre::eyre!("truncated payload envelope"))?; + ExecutionPayload::from_ssz_bytes(payload_bytes).map_err(|_| eyre::eyre!("decode failed")) + } + } +diff --git a/opstack/src/server/net/block_handler.rs b/opstack/src/server/net/block_handler.rs +index ef4a825..0dc560c 100644 +--- a/opstack/src/server/net/block_handler.rs ++++ b/opstack/src/server/net/block_handler.rs +@@ -17,7 +17,7 @@ impl BlockHandler { + chain_id, + signer, + commitment_sender: sender, +- blocks_v3_topic: IdentTopic::new(format!("/optimism/{chain_id}/2/blocks")), ++ blocks_v3_topic: IdentTopic::new(format!("/optimism/{chain_id}/3/blocks")), + } + } + +diff --git a/opstack/src/server/net/gossip.rs b/opstack/src/server/net/gossip.rs +index 0a65b63..d4bba8a 100644 +--- a/opstack/src/server/net/gossip.rs ++++ b/opstack/src/server/net/gossip.rs +@@ -7,11 +7,10 @@ use eyre::Result; + use libp2p::{ + futures::StreamExt, + gossipsub::{self, IdentTopic, Message, MessageId}, +- mplex::MplexConfig, + multiaddr::Protocol, + noise, ping, + swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, +- tcp, Multiaddr, PeerId, Swarm, Transport, ++ tcp, yamux, Multiaddr, PeerId, Swarm, Transport, + }; + use libp2p_identity::Keypair; + use sha2::{Digest, Sha256}; +@@ -61,9 +60,28 @@ impl GossipService { + .listen_on(multiaddr) + .map_err(|_| eyre::eyre!("swarm listen failed"))?; + ++ let peers = std::env::var("OP_NODE_P2P_STATIC").unwrap_or_default(); ++ let static_peers = peers ++ .split(',') ++ .filter(|peer| !peer.is_empty()) ++ .map(str::parse::) ++ .collect::, _>>()?; ++ + tokio::spawn(async move { ++ let mut reconnect = tokio::time::interval(Duration::from_secs(5)); + loop { + select! { ++ _ = reconnect.tick() => { ++ for peer in &static_peers { ++ if let Some(Protocol::P2p(id)) = peer.iter().last() { ++ if let Ok(id) = PeerId::from_multihash(id) { ++ swarm.behaviour_mut().gossipsub.add_explicit_peer(&id); ++ if swarm.is_connected(&id) { continue; } ++ } ++ } ++ let _ = swarm.dial(peer.clone()); ++ } ++ }, + peer = peer_recv.recv() => { + if let Some(peer) = peer { + tracing::info!("adding peer"); +@@ -72,8 +90,9 @@ impl GossipService { + } + }, + event = swarm.select_next_some() => { +- if let SwarmEvent::Behaviour(event) = event { +- event.handle(&mut swarm, &self.block_handler); ++ match event { ++ SwarmEvent::Behaviour(event) => event.handle(&mut swarm, &self.block_handler), ++ other => tracing::info!("swarm event: {:?}", other), + } + }, + } +@@ -96,31 +115,17 @@ fn socket_to_multiaddr(socket: SocketAddr) -> Multiaddr { + + /// Computes the message ID of a `gossipsub` message + fn compute_message_id(msg: &Message) -> MessageId { +- let mut decoder = snap::raw::Decoder::new(); +- let id = match decoder.decompress_vec(&msg.data) { +- Ok(data) => { +- let domain_valid_snappy: Vec = vec![0x1, 0x0, 0x0, 0x0]; +- let mut hasher = Sha256::new(); +- hasher.update( +- [domain_valid_snappy.as_slice(), data.as_slice()] +- .concat() +- .as_slice(), +- ); +- hasher.finalize()[..20].to_vec() +- } +- Err(_) => { +- let domain_invalid_snappy: Vec = vec![0x0, 0x0, 0x0, 0x0]; +- let mut hasher = Sha256::new(); +- hasher.update( +- [domain_invalid_snappy.as_slice(), msg.data.as_slice()] +- .concat() +- .as_slice(), +- ); +- hasher.finalize()[..20].to_vec() +- } +- }; +- +- MessageId(id) ++ let decoded = snap::raw::decompress_len(&msg.data) ++ .ok() ++ .filter(|len| *len <= 10 * 1024 * 1024) ++ .and_then(|_| snap::raw::Decoder::new().decompress_vec(&msg.data).ok()); ++ let topic = msg.topic.as_str().as_bytes(); ++ let mut hasher = Sha256::new(); ++ hasher.update([u8::from(decoded.is_some()), 0, 0, 0]); ++ hasher.update((topic.len() as u64).to_le_bytes()); ++ hasher.update(topic); ++ hasher.update(decoded.as_deref().unwrap_or(&msg.data)); ++ MessageId(hasher.finalize()[..20].to_vec()) + } + + /// Creates the libp2p [Swarm] +@@ -128,7 +133,7 @@ fn create_swarm(keypair: Keypair, handler: &BlockHandler) -> Result Date: Tue, 22 Sep 2026 08:19:49 -0700 Subject: [PATCH 02/15] refactor(kms): package Phala verifier from pinned Helios fork Signed-off-by: Kevin Wang --- .github/workflows/kms-release.yml | 14 +- dstack/kms/README.md | 4 +- dstack/kms/dstack-app/builder/Dockerfile | 21 + dstack/kms/dstack-app/builder/README.md | 8 +- dstack/kms/dstack-app/compose-dev.yaml | 80 +- dstack/kms/dstack-app/deploy-to-vmm.sh | 32 +- dstack/kms/dstack-app/docker-compose.yaml | 74 +- dstack/kms/light-client/.dockerignore | 5 - dstack/kms/light-client/Dockerfile | 30 - dstack/kms/light-client/README.md | 132 +- dstack/kms/light-client/VALIDATION.md | 70 - dstack/kms/light-client/auth-api.Dockerfile | 17 - dstack/kms/light-client/compose.yaml | 51 - .../fixtures/phala-signed-commitment.json | 1 - dstack/kms/light-client/helios.patch | 1846 ----------------- 15 files changed, 128 insertions(+), 2257 deletions(-) mode change 100644 => 120000 dstack/kms/dstack-app/compose-dev.yaml delete mode 100644 dstack/kms/light-client/.dockerignore delete mode 100644 dstack/kms/light-client/Dockerfile delete mode 100644 dstack/kms/light-client/VALIDATION.md delete mode 100644 dstack/kms/light-client/auth-api.Dockerfile delete mode 100644 dstack/kms/light-client/compose.yaml delete mode 100644 dstack/kms/light-client/fixtures/phala-signed-commitment.json delete mode 100644 dstack/kms/light-client/helios.patch diff --git a/.github/workflows/kms-release.yml b/.github/workflows/kms-release.yml index eedb5d66a..b883c63fe 100644 --- a/.github/workflows/kms-release.yml +++ b/.github/workflows/kms-release.yml @@ -8,9 +8,13 @@ on: workflow_dispatch: inputs: version: - description: Existing release tag version without the kms-v prefix + description: Release tag version without kms-v, or a unique preview image tag required: true type: string + preview: + description: Publish a preview image from the selected ref without a GitHub release + default: false + type: boolean push: tags: - 'kms-v*' @@ -43,13 +47,14 @@ jobs: echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "RELEASE_TAG=kms-v$VERSION" >> "$GITHUB_ENV" - # Manual runs rebuild an existing release tag, not the selected branch. + # Non-preview manual runs rebuild an existing release tag. - name: Checkout repository uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: - ref: ${{ github.event_name == 'workflow_dispatch' && env.RELEASE_TAG || github.sha }} + ref: ${{ github.event_name == 'workflow_dispatch' && !inputs.preview && env.RELEASE_TAG || github.sha }} - name: Checkout contract dependencies + if: ${{ !inputs.preview }} run: | git submodule update --init --recursive --depth 1 -- \ dstack/kms/auth-eth/lib/forge-std \ @@ -112,14 +117,17 @@ jobs: push-to-registry: true - name: Install Foundry + if: ${{ !inputs.preview }} uses: foundry-rs/foundry-toolchain@908c540300062bd5a7e473851cdb4282204cee09 # v1 - name: Compile contracts with Foundry + if: ${{ !inputs.preview }} run: | cd dstack/kms/auth-eth forge build - name: GitHub Release + if: ${{ !inputs.preview }} uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: ${{ env.RELEASE_TAG }} diff --git a/dstack/kms/README.md b/dstack/kms/README.md index 23e40ba54..9ededadff 100644 --- a/dstack/kms/README.md +++ b/dstack/kms/README.md @@ -105,8 +105,8 @@ certificate. `core.onboard.public_key_handover = true` (the default) keeps The first two components are deployed as an dstack app on dstack in Local-Key-Provider mode. The default [Docker Compose file](dstack-app/docker-compose.yaml) uses the [Phala L2 light client](light-client/README.md). Set `KMS_CONTRACT_ADDR` to a -Phala contract compatible with the current auth API before building from this -repository checkout. Reads use sequencer-authenticated, proof-verified state, +Phala contract compatible with the current auth API, and use a published KMS +image digest; deployment does not build images. Reads use sequencer-authenticated, proof-verified state, not L1-settled consensus. See the light-client guide for deployment requirements. The solidity contracts are deployed on an ethereum compatible chain. diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 426dd6ab9..f0d314edb 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -26,6 +26,22 @@ RUN rustup target add x86_64-unknown-linux-musl RUN cd repo/dstack && cargo build --release --locked -p dstack-kms --target x86_64-unknown-linux-musl RUN echo "${DSTACK_REV}" > /build/.GIT_REV +FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS helios-builder +WORKDIR /build/helios +RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ + && git fetch --depth 1 origin 535e59414ff36178b41566af0c7f3e34f71f4fd5 \ + && git checkout --detach FETCH_HEAD +RUN cargo test --locked -p helios-opstack --lib \ + && cargo build --locked --release -p helios-cli -p helios-opstack --bins + +FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 AS auth-builder +WORKDIR /app +COPY --from=kms-builder /build/repo/dstack/kms/auth-eth/package*.json ./ +RUN npm ci --ignore-scripts +COPY --from=kms-builder /build/repo/dstack/kms/auth-eth/tsconfig.json ./ +COPY --from=kms-builder /build/repo/dstack/kms/auth-eth/src/ src/ +RUN npm run build && npm prune --omit=dev --ignore-scripts + FROM debian:bookworm@sha256:0d8498a0e9e6a60011df39aab78534cfe940785e7c59d19dfae1eb53ea59babe COPY --from=build-shared /pin-packages.sh /build/ COPY ./shared/pinned-packages.txt /build/ @@ -41,6 +57,11 @@ RUN ./pin-packages.sh ./pinned-packages.txt && \ && rm -rf /var/lib/apt/lists/* /var/log/* /var/cache/ldconfig/aux-cache WORKDIR / COPY --from=kms-builder /build/repo/dstack/target/x86_64-unknown-linux-musl/release/dstack-kms /usr/local/bin/dstack-kms +COPY --from=helios-builder /build/helios/target/release/helios /usr/local/bin/helios +COPY --from=helios-builder /build/helios/target/release/opstack-server /usr/local/bin/opstack-server +COPY --from=auth-builder /usr/local/bin/node /usr/local/bin/node +COPY --from=auth-builder /app/dist/ /opt/auth-eth/dist/ +COPY --from=auth-builder /app/node_modules/ /opt/auth-eth/node_modules/ COPY --from=kms-builder /build/.GIT_REV /etc/ # The OCI metadata is generated once by build-lib.sh and injected here, as # the config labels and as the manifest annotations, so the three views of diff --git a/dstack/kms/dstack-app/builder/README.md b/dstack/kms/dstack-app/builder/README.md index 39815e762..e53604264 100644 --- a/dstack/kms/dstack-app/builder/README.md +++ b/dstack/kms/dstack-app/builder/README.md @@ -7,6 +7,8 @@ This directory contains the necessary files to build and run the dstack-kms Dock The builder creates a Docker image that includes: - The dstack-kms service compiled from Rust source code - Pure-Rust ACPI measurement support built into the dstack-kms binary +- Helios and its gossip relay from a commit-pinned Dstack-TEE fork +- The auth-eth API and Node runtime, invoked as `node /opt/auth-eth/dist/main.js` ## Prerequisites @@ -40,8 +42,10 @@ Optional environment variables: Publication and OCI export happen only after both package lists pass validation. `NO_CACHE` applies to the validation builds; export then reuses their cached result. -Manual release workflows require an existing component release tag and check out -that tag, rather than building the branch selected in the workflow UI. +Manual release workflows normally require an existing component release tag. +With `preview=true`, the workflow instead publishes the selected ref under the +provided unique image tag, without creating a GitHub release. Use the resulting +registry digest in Compose; never use a moving preview tag in measured deployment. ## Reproducing a released image diff --git a/dstack/kms/dstack-app/compose-dev.yaml b/dstack/kms/dstack-app/compose-dev.yaml deleted file mode 100644 index 02e17c4b5..000000000 --- a/dstack/kms/dstack-app/compose-dev.yaml +++ /dev/null @@ -1,79 +0,0 @@ -# SPDX-FileCopyrightText: © 2025 Phala Network -# SPDX-FileCopyrightText: © 2025 Test in Prod -# -# SPDX-License-Identifier: Apache-2.0 - -services: - auth-api: - build: - context: . - dockerfile_inline: | - FROM node:22.23.4-bookworm-slim@sha256:de170b39439eb0e5a3444ad7d60b015614a381e10fecae2e30ec9e735122f38d - WORKDIR /app - - RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates && rm -rf /var/lib/apt/lists/* - RUN git clone ${GIT_REPOSITORY} && \ - cd dstack && \ - git checkout ${GIT_REV} - WORKDIR /app/dstack/dstack/kms/auth-eth - RUN npm ci - RUN npm run build - CMD node dist/main.js - environment: - - HOST=0.0.0.0 - - PORT=8000 - - ETH_RPC_URL=${ETH_RPC_URL} - - KMS_CONTRACT_ADDR=${KMS_CONTRACT_ADDR} - restart: unless-stopped - ports: - - 8001:8000 - - kms: - image: ${KMS_IMAGE} - volumes: - - kms-volume:/kms - - /var/run/dstack.sock:/var/run/dstack.sock - ports: - - 8000:8000 - depends_on: - - auth-api - restart: unless-stopped - configs: - - source: kms_config - target: /kms/kms.toml - command: sh -c 'mkdir -p /kms/certs /kms/images && exec dstack-kms -c /kms/kms.toml' - -volumes: - kms-volume: - -configs: - kms_config: - content: | - [rpc] - address = "0.0.0.0" - port = 8000 - - [rpc.tls] - key = "/kms/certs/rpc.key" - certs = "/kms/certs/rpc.crt" - - [core] - cert_dir = "/kms/certs" - - [core.image] - verify = true - cache_dir = "/kms/images" - download_url = "${IMAGE_DOWNLOAD_URL}" - download_timeout = "2m" - - [core.auth_api] - type = "webhook" - - [core.auth_api.webhook] - url = "http://auth-api:8000" - - [core.onboard] - enabled = true - auto_bootstrap_domain = "" - address = "0.0.0.0" - port = 8000 diff --git a/dstack/kms/dstack-app/compose-dev.yaml b/dstack/kms/dstack-app/compose-dev.yaml new file mode 120000 index 000000000..e736cbabe --- /dev/null +++ b/dstack/kms/dstack-app/compose-dev.yaml @@ -0,0 +1 @@ +docker-compose.yaml \ No newline at end of file diff --git a/dstack/kms/dstack-app/deploy-to-vmm.sh b/dstack/kms/dstack-app/deploy-to-vmm.sh index 23f8c2917..c2880a8c2 100755 --- a/dstack/kms/dstack-app/deploy-to-vmm.sh +++ b/dstack/kms/dstack-app/deploy-to-vmm.sh @@ -26,9 +26,6 @@ else # The address of the KMS contract # KMS_CONTRACT_ADDR=0x59E4a36B01a87fD9D1A4C12377253FE9a7b018Ba -# The address of the auth-api service listening on Host machine -# AUTH_API_RPC_ADDR=0.0.0.0:8001 - # The address of the KMS service listening on Host machine # KMS_RPC_ADDR=0.0.0.0:9201 @@ -41,20 +38,15 @@ else # Image hash verification feature flag VERIFY_IMAGE=true -# The URL of the Ethereum RPC service -ETH_RPC_URL=https://rpc.phala.network - -# The Git repository to deploy -GIT_REPOSITORY=https://github.com/Dstack-TEE/dstack.git - -# The Git revision to deploy -GIT_REV=HEAD +# Maximum age of sequencer-authenticated state accepted for authorization. +# Choose according to the acceptable revocation delay and observed RPC latency. +# ETH_MAX_BLOCK_AGE_SECONDS= # The dstack OS image name to use for the KMS app OS_IMAGE=dstack-0.5.5 # The dstack KMS image name to use for the KMS app -KMS_IMAGE=dstacktee/dstack-kms@sha256:11ac59f524a22462ccd2152219b0bec48a28ceb734e32500152d4abefab7a62a +# KMS_IMAGE=ghcr.io/dstack-tee/dstack-kms@sha256: # The admin token for the KMS app ADMIN_TOKEN=$(tr -dc 'a-zA-Z0-9' < /dev/urandom | fold -w 32 | head -n 1) @@ -66,13 +58,12 @@ fi required_env_vars=( "VMM_RPC" "KMS_RPC_ADDR" - "AUTH_API_RPC_ADDR" "GUEST_AGENT_ADDR" "KMS_CONTRACT_ADDR" - "ETH_RPC_URL" + "ETH_MAX_BLOCK_AGE_SECONDS" "IMAGE_DOWNLOAD_URL" "VERIFY_IMAGE" - "GIT_REPOSITORY" + "KMS_IMAGE" ) for var in "${required_env_vars[@]}"; do @@ -87,24 +78,20 @@ CLI="../../vmm/src/vmm-cli.py --url $VMM_RPC" COMPOSE_TMP=$(mktemp) -GIT_REV=$(git rev-parse "$GIT_REV") - # shellcheck disable=SC2034 # consumed via `subvar` into compose-*.yaml ADMIN_TOKEN_HASH=$(echo -n "$ADMIN_TOKEN" | sha256sum | cut -d' ' -f1) -cp compose-dev.yaml "$COMPOSE_TMP" +cp docker-compose.yaml "$COMPOSE_TMP" subvar() { - sed -i "s|\${$1}|${!1}|g" "$COMPOSE_TMP" + sed -i "s|\${$1\(:?[^}]*\)\?}|${!1}|g" "$COMPOSE_TMP" } -subvar ETH_RPC_URL +subvar ETH_MAX_BLOCK_AGE_SECONDS subvar KMS_CONTRACT_ADDR -subvar GIT_REV subvar IMAGE_DOWNLOAD_URL subvar ADMIN_TOKEN_HASH subvar VERIFY_IMAGE -subvar GIT_REPOSITORY subvar KMS_IMAGE echo "Docker compose file:" @@ -141,7 +128,6 @@ $CLI deploy \ --compose .app-compose.json \ --image "$OS_IMAGE" \ --port tcp:"$KMS_RPC_ADDR":8000 \ - --port tcp:"$AUTH_API_RPC_ADDR":8001 \ --port tcp:"$GUEST_AGENT_ADDR":8090 \ --vcpu 8 \ --memory 8G \ diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index 232149a8c..85b8491c9 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -4,8 +4,7 @@ services: relay: - image: dstack-phala-light-client:local - build: ../light-client + image: ${KMS_IMAGE:?Set a published KMS image digest} restart: unless-stopped command: - opstack-server @@ -19,7 +18,7 @@ services: security_opt: [no-new-privileges:true] helios: - image: dstack-phala-light-client:local + image: ${KMS_IMAGE:?Set a published KMS image digest} restart: unless-stopped command: - helios @@ -34,9 +33,8 @@ services: security_opt: [no-new-privileges:true] auth-api: - build: - context: ../auth-eth - dockerfile: ../light-client/auth-api.Dockerfile + image: ${KMS_IMAGE:?Set a published KMS image digest} + command: [node, /opt/auth-eth/dist/main.js] restart: unless-stopped environment: HOST: 0.0.0.0 @@ -44,42 +42,58 @@ services: ETH_RPC_URL: http://helios:8545 ETH_CHAIN_ID: "2035" ETH_BLOCK_LAG: "2" - ETH_MAX_BLOCK_AGE_SECONDS: "60" + ETH_MAX_BLOCK_AGE_SECONDS: ${ETH_MAX_BLOCK_AGE_SECONDS:?Set the maximum accepted block age} KMS_CONTRACT_ADDR: ${KMS_CONTRACT_ADDR:?Set the Phala KMS contract address} depends_on: [helios] cap_drop: [ALL] security_opt: [no-new-privileges:true] - # KMS handles the TEE Remote Attestation kms: - build: - context: . - dockerfile_inline: | - FROM rust:1.92.0@sha256:48851a839d6a67370c9dbe0e709bedc138e3e404b161c5233aedcf2b717366e4 - WORKDIR /app - RUN apt-get update && apt-get install -y \ - git \ - build-essential \ - libssl-dev \ - protobuf-compiler \ - libprotobuf-dev \ - clang \ - libclang-dev \ - --no-install-recommends \ - && rm -rf /var/lib/apt/lists/* - RUN git clone https://github.com/Dstack-TEE/dstack.git && \ - cd dstack && \ - git checkout 78057c975fe4b9e21f557fb888d72eeecfb21178 - WORKDIR /app/dstack - RUN cargo build --release -p dstack-kms - CMD ["./target/release/dstack-kms"] + image: ${KMS_IMAGE:?Set a published KMS image digest} volumes: - - kms-volume:/etc/kms + - kms-volume:/kms - /var/run/dstack.sock:/var/run/dstack.sock ports: - 8000:8000 depends_on: - auth-api + restart: unless-stopped + configs: + - source: kms_config + target: /kms/kms.toml + command: sh -c 'mkdir -p /kms/certs /kms/images && exec dstack-kms -c /kms/kms.toml' volumes: kms-volume: + +configs: + kms_config: + content: | + [rpc] + address = "0.0.0.0" + port = 8000 + + [rpc.tls] + key = "/kms/certs/rpc.key" + certs = "/kms/certs/rpc.crt" + + [core] + cert_dir = "/kms/certs" + + [core.image] + verify = true + cache_dir = "/kms/images" + download_url = "${IMAGE_DOWNLOAD_URL}" + download_timeout = "2m" + + [core.auth_api] + type = "webhook" + + [core.auth_api.webhook] + url = "http://auth-api:8000" + + [core.onboard] + enabled = true + auto_bootstrap_domain = "" + address = "0.0.0.0" + port = 8000 diff --git a/dstack/kms/light-client/.dockerignore b/dstack/kms/light-client/.dockerignore deleted file mode 100644 index 761ee1f2c..000000000 --- a/dstack/kms/light-client/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -* -!Dockerfile -!helios.patch -!fixtures/ -!fixtures/phala-signed-commitment.json diff --git a/dstack/kms/light-client/Dockerfile b/dstack/kms/light-client/Dockerfile deleted file mode 100644 index d990589a9..000000000 --- a/dstack/kms/light-client/Dockerfile +++ /dev/null @@ -1,30 +0,0 @@ -# SPDX-FileCopyrightText: © 2026 Phala Network -# SPDX-License-Identifier: Apache-2.0 - -FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS build -RUN apt-get update && apt-get install -y --no-install-recommends git ca-certificates \ - && rm -rf /var/lib/apt/lists/* -WORKDIR /build -RUN git init helios && cd helios \ - && git remote add origin https://github.com/a16z/helios \ - && git fetch --depth 1 origin d20aa4f4de5886b6c78004447ee197a1cf744362 \ - && git checkout --detach FETCH_HEAD -WORKDIR /build/helios -COPY helios.patch /build/helios.patch -RUN git apply --check /build/helios.patch && git apply /build/helios.patch -COPY fixtures/phala-signed-commitment.json opstack/tests/fixtures/phala-signed-commitment.json -RUN --mount=type=cache,target=/usr/local/cargo/registry \ - --mount=type=cache,target=/build/helios/target \ - cargo test --locked -p helios-opstack --lib \ - && cargo build --locked --release -p helios-cli -p helios-opstack --bins \ - && install -D target/release/helios /out/helios \ - && install -D target/release/opstack-server /out/opstack-server - -FROM debian:bookworm-slim@sha256:3783cc01769c7b2b1b83a5c5ad96c815348e28ed7da68e2e3687004faa906251 -RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates libssl3 \ - && rm -rf /var/lib/apt/lists/* \ - && useradd --create-home --uid 10001 helios -COPY --from=build /out/ /usr/local/bin/ -USER helios -ENV HOME=/home/helios -CMD ["helios", "opstack", "--network", "phala"] diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 4190261c7..4b4980eea 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -1,106 +1,42 @@ -# Phala KMS light client +# Phala L2 light client -Phala L2 light client for KMS authorization, with sequencer authentication, -proof verification, and local on-chain contract execution. +The default [KMS Compose](../dstack-app/docker-compose.yaml) runs KMS, auth-api, +Helios and its gossip relay inside the same CVM. All services use one published +KMS image; no deployment-time builds or source downloads are required. The KMS +builder pins the [Helios fork](https://github.com/Dstack-TEE/helios/pull/1) by commit. -## Trust model +## Deployment -This integration verifies Phala sequencer signatures (chain ID 2035), reconstructs -and checks the signed block hash, verifies account/storage proofs and code hashes, -and executes `eth_call` locally. The execution RPC cannot invent an authorization -result. There is no fallback to unverified RPC execution. +Set these variables and render Compose before passing it to dstack: -**This is not L1-settled L2 consensus verification.** It trusts the pinned Phala -sequencer signer `0xF63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC`, initially obtained -from public SystemConfig observations. An operator must independently approve this -pin as part of the measured deployment. A signer change stops synchronization; -update the pin only after independent verification and redeploy. Do not enable -upstream's optional signer-discovery implementation: it is not used by this -configuration and has unresolved verification/readiness problems. - -The signer and L1 SystemConfig are part of `helios.patch`; they are not accepted -from the execution RPC at runtime. The relay's static peer is only a data source, -not a trust anchor. A malicious relay can withhold blocks but cannot forge the -pinned sequencer signature. - -Put **both Helios and auth-api inside the KMS CVM's measured compose**. Running -Helios on an untrusted external machine and trusting its JSON-RPC defeats this -verification boundary. A relay may be external because its output is verified. - -## Build and local smoke deployment - -Helios upstream revision, Cargo lockfile changes, and container base-image digests -are pinned. `helios.patch` includes a regression test against a real public Phala -signed block. The build copies its fixture and runs that test before compiling. - -From this directory: +- `KMS_IMAGE`: the published `ghcr.io/dstack-tee/dstack-kms@sha256:...` digest. +- `KMS_CONTRACT_ADDR`: a Phala contract compatible with the current auth API. +- `ETH_MAX_BLOCK_AGE_SECONDS`: the maximum accepted age of authenticated state. + Choose explicitly based on acceptable revocation delay and observed latency. +- `IMAGE_DOWNLOAD_URL`: the verified OS image download URL used by KMS. ```sh -export KMS_CONTRACT_ADDR=0xYourPhalaKmsContract -# The address must have the ABI expected by ../auth-eth. -docker compose build -docker compose up -d +docker compose -f dstack/kms/dstack-app/docker-compose.yaml config > kms-compose.yaml ``` -The smoke auth API is exposed only on `127.0.0.1:18000`; Helios and relay have no -host ports. The default [KMS Compose](../dstack-app/docker-compose.yaml) includes -these services alongside KMS without exposing their ports. Both Compose files -build the auth API from current local sources and require a repository checkout -for their build contexts. Set `KMS_CONTRACT_ADDR` explicitly to a Phala deployment -with the current auth ABI; no legacy contract is selected automatically. -Configure KMS to use `http://auth-api:8000`, never the public execution RPC. - -## Read policy - -`auth-eth` enables the verified-read policy when `ETH_CHAIN_ID` is set: - -- `ETH_CHAIN_ID=2035`: reject another reported chain ID. -- `ETH_BLOCK_LAG=2`: read a slightly older **locally authenticated cached block**. - This is proof-availability lag, not settlement finality. In observed public RPC - samples, proofs for the latest head failed while previous-block proofs worked. -- `ETH_MAX_BLOCK_AGE_SECONDS=60`: reject missing, stale or more-than-five-seconds - future blocks. Boot authorization and gateway ID use the same selected height. - Freshness is checked again before returning the authorization result, so slow - upstream requests cannot extend the acceptance window. - -If the selected authenticated block or its proofs are unavailable, reject the -request. Do not increase lag/age arbitrarily to make failures disappear. Following -restart, allow time for sufficient authenticated blocks to enter the local cache. - -The upstream client stores its own empty configuration under its isolated container -HOME. No external host configuration or credentials are mounted. - -## Compatibility and outstanding acceptance - -Phala currently gossips Isthmus-style V4 envelopes on `/optimism/2035/3/blocks`. -The patch adds that topic, Yamux transport, static peers with reconnect, payload -bounds, and fixes header reconstruction (including logs bloom and withdrawal root). -It is scoped to current V4 operation, not historical pre-Isthmus synchronization. - -The patch upgrades op-revm to 20.0.0 and revm to 38.0.0 and selects Jovian/Karst -at Phala's actual activation timestamps. The Karst CLZ opcode and a verified -L1Block read were compared with the public RPC at the same height; the results -matched. eth_call disables the transaction-only gas cap, preserving simulation -behavior after the upgrade. This image is scoped to KMS read calls, not advertised -as a general-purpose transaction submission or gas-estimation service. - -The discovered on-chain KMS used in initial read-only tests is -`0xae43c1d4814f665aa4e08ec3589309dff939a049`. It is an older contract, not a -user-confirmed current production deployment: its ABI has `mrImage` where this -repository uses `osImageHash`, and lacks `appImplementation()`. Boot endpoints can -be tested against it, but the current information endpoint will fail. No fallback -or fabricated implementation address was added to hide that incompatibility. - -Before production sign-off: - -1. Confirm the intended current KMS contract and semantics, or explicitly implement - and test supported legacy compatibility. -2. Run target-contract acceptance inside the intended KMS CVM. Local container - build, HTTP authorization, stale-head rejection and relay restart recovery pass. -3. Approve sequencer trust, signer update procedure and freshness policy for - irreversible KMS key release. - -## Validation - -See [VALIDATION.md](VALIDATION.md) for test conditions, results and limitations. -The public signed-block regression fixture is in `fixtures/`. +Only the KMS port is published. Helios verifies sequencer-signed blocks, account +and storage proofs, and code hashes, then executes contract calls locally. The +public execution RPC cannot supply an unchecked `eth_call` result. The auth API +checks chain ID 2035, uses a two-block proof-availability lag, and rejects stale +state, including when a call expires in flight. The lag is not settlement finality +and consumes part of the configured age budget. RPC outages, delayed proofs or +relay stalls fail closed; startup needs enough authenticated blocks in the cache. + +## Trust and compatibility + +This is **sequencer authentication, not L1-settled consensus verification**. +The pinned signer is `0xF63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC`. +Approve this trust anchor independently; signer rotation requires a reviewed +fork update and image rebuild. The relay is only a data source and cannot forge +signatures. Helios and auth-api must remain inside the measured KMS boundary. + +The observed legacy contract `0xae43c1d4814f665aa4e08ec3589309dff939a049` +permits read-only light-client checks, but lacks `appImplementation()` and uses +older image-measurement semantics. It is deliberately not a production default. +Live light-client contract calls were verified; CVM attestation and key release +are outside that validation scope. diff --git a/dstack/kms/light-client/VALIDATION.md b/dstack/kms/light-client/VALIDATION.md deleted file mode 100644 index 744eaa5df..000000000 --- a/dstack/kms/light-client/VALIDATION.md +++ /dev/null @@ -1,70 +0,0 @@ -# Validation record - -Date: 2026-09-22 UTC. Repository base: -`ccd96638e334fb3df658661743bb641239ed6f71`. - -## Conditions - -- Cloud Linux x86_64, native Rust/Cargo 1.94.1, Node 20.20.2, Python 3.12.3, - curl 8.5.0, Docker Server 29.5.3. No CVM, attestation or key release was tested. -- Helios base `d20aa4f4de5886b6c78004447ee197a1cf744362` plus `helios.patch`; - op-revm 20.0.0, revm 38.0.0. Container toolchain/base images are digest-pinned - in the Dockerfiles. Cargo dependencies are locked. -- Public Phala execution RPC `https://rpc.phala.network`, chain ID 2035, sequencer - signer pinned in the patch. Signed blocks obtained from the public Conduit static - peer documented in README. No credentials, chain writes or paid transactions. -- KMS proxy `0xae43c1d4814f665aa4e08ec3589309dff939a049`, an independently - discovered **legacy** deployment, not a user-confirmed production target. -- Synthetic boot inputs from its public allowlist events. Tests do not represent - successful TEE attestation or possession of a real KMS device identity. -- Auth read lag 2 blocks and max age 60 seconds. The Karst opcode comparison used - lag 3 and the same fixed block 5569954 for local and remote calls. -- Local auth HTTP port 18000; internal relay/client ports only. Separate native - fault-test clients had empty caches per mode. Fault proxies bound to loopback. - -## Results - -- Native TypeScript build passes; 13 Jest tests pass (existing API tests plus - block pinning, chain mismatch, unavailable/stale/future state, expiration during - in-flight contract calls, and no fallback). -- Two Rust tests pass: authentic Phala signed payload/header with signature/chain/ - payload/root negative cases and exact Jovian/Karst activation boundaries. -- Both images build. Real container HTTP KMS authorization returns allow for the - public allowlist vector and deny for an unlisted MR. -- Upgraded Karst execution supports CLZ: `0x5f1e5f5260205ff3` returns 256. - L1Block.number() and CLZ match the public RPC at the same authenticated height. -- Real auth API through the upgraded native client rejects corrupted account - proofs, corrupted contract code, wrong chain ID, replayed stale genuine signed - head, and unavailable execution RPC. The positive control allows. -- The observing execution proxy is programmed to lie on eth_call. Method counts - showed proof/code/access-list RPC requests and **zero forwarded eth_call calls**. -- Stopping the Docker relay and waiting at least 75 seconds causes the previously - allowed vector to be denied under the 60-second freshness limit. Restarting the - relay restores fresh synchronization and allow; the negative vector remains denied. - -## Limits / remaining acceptance - -The trust model is sequencer authentication, not L1 settlement. The tests do not -validate a current KMS CVM, actual attestation/key release, or a user-selected -production contract. The tested legacy contract lacks appImplementation() and -uses old image-measurement semantics. Consequently this is not final sign-off on -current-repository production KMS compatibility; no health response or contract -implementation address was fabricated to hide the mismatch. - -## Live contract reads - -A fresh read-only acceptance run at 2026-09-22T14:43:10.310Z used the existing -Docker auth container's ethers client, connected exclusively to internal Helios -at http://helios:8545 (not directly to the public RPC). Chain ID was 2035; the -selected authenticated block was 5570092, two blocks behind the observed head, -31 seconds old, hash -0x4a6c928109fb0a203e2c76a8b095edd88aa38855ddf811424eeb87e51fc2a811. -The same Docker/toolchain, pinned signer, public peer and execution-provider -conditions listed above apply. No chain transactions, CVM or key release. - -- KMS gatewayAppId() at 0xae43c1d4814f665aa4e08ec3589309dff939a049 returned - 29af9606ffff15d7b95cb1bfb774137d7445c59a. -- L1Block.number() at 0x4200000000000000000000000000000000000015 returned 26033691. - -Both calls executed locally using proof-verified state under a sequencer-signed -root. This does not change the stated sequencer-trust/L1-settlement distinction. diff --git a/dstack/kms/light-client/auth-api.Dockerfile b/dstack/kms/light-client/auth-api.Dockerfile deleted file mode 100644 index bd653ecf4..000000000 --- a/dstack/kms/light-client/auth-api.Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# SPDX-FileCopyrightText: © 2026 Phala Network -# SPDX-License-Identifier: Apache-2.0 - -FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 AS build -WORKDIR /app -COPY package.json package-lock.json ./ -RUN npm ci --ignore-scripts -COPY tsconfig.json ./ -COPY src/ src/ -RUN npm run build && npm prune --omit=dev --ignore-scripts - -FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 -WORKDIR /app -COPY --from=build /app/node_modules/ node_modules/ -COPY --from=build /app/dist/ dist/ -USER node -CMD ["node", "dist/main.js"] diff --git a/dstack/kms/light-client/compose.yaml b/dstack/kms/light-client/compose.yaml deleted file mode 100644 index 4612a7f99..000000000 --- a/dstack/kms/light-client/compose.yaml +++ /dev/null @@ -1,51 +0,0 @@ -# SPDX-FileCopyrightText: © 2026 Phala Network -# SPDX-License-Identifier: Apache-2.0 - -services: - relay: - image: dstack-phala-light-client:local - build: . - restart: unless-stopped - command: - - opstack-server - - --network=phala - - --execution-rpc=https://rpc.phala.network - - --server-address=0.0.0.0:3000 - - --gossip-address=0.0.0.0:9876 - environment: - OP_NODE_P2P_STATIC: /ip4/8.229.99.126/tcp/9222/p2p/16Uiu2HAmA7EmxHixJhunY6dZKi7nriyUfBCWP3aBPvGUsQBbigm1 - cap_drop: [ALL] - security_opt: [no-new-privileges:true] - - helios: - image: dstack-phala-light-client:local - restart: unless-stopped - command: - - helios - - opstack - - --network=phala - - --execution-rpc=https://rpc.phala.network - - --consensus-rpc=http://relay:3000 - - --rpc-bind-ip=0.0.0.0 - - --rpc-port=8545 - depends_on: [relay] - cap_drop: [ALL] - security_opt: [no-new-privileges:true] - - auth-api: - build: - context: ../auth-eth - dockerfile: ../light-client/auth-api.Dockerfile - restart: unless-stopped - environment: - HOST: 0.0.0.0 - PORT: "8000" - ETH_RPC_URL: http://helios:8545 - ETH_CHAIN_ID: "2035" - ETH_BLOCK_LAG: "2" - ETH_MAX_BLOCK_AGE_SECONDS: "60" - KMS_CONTRACT_ADDR: ${KMS_CONTRACT_ADDR:?Set the Phala KMS contract address} - ports: ["127.0.0.1:18000:8000"] - depends_on: [helios] - cap_drop: [ALL] - security_opt: [no-new-privileges:true] diff --git a/dstack/kms/light-client/fixtures/phala-signed-commitment.json b/dstack/kms/light-client/fixtures/phala-signed-commitment.json deleted file mode 100644 index 03105a62f..000000000 --- a/dstack/kms/light-client/fixtures/phala-signed-commitment.json +++ /dev/null @@ -1 +0,0 @@ -{"data":"0x6f9ed5e529315d012bac7d1cd0050fd01f86c141cb18682e2867186c0f0f7656bb6ec5b9e7754d5d87512bafe2b45aecba77cb2c2c111bad4f860ee3e084c7f142000000000000000000000000000000000000111370bfdfd1c4bfb69d18c917626a37106630eae2408814606dab36141ac2d763bb6703fe88093cb7fd18ac19c131bcec52ec9ded35381a36437d5726054de0c100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c1df53ef06e66b52c81f9c2b723effe8c8e8d4472f0abc8826be30f4bbfc525a10fd54000000000080c3c901000000005ad5000000000000c788b26a0000000030020000fc00000000000000000000000000000000000000000000000000000000000000f4daa5a0aca5f2a89f1dfc2211db19eaf74e8f8cda77f2ec18897277dc4ad301410200004f0300000000000000000000000000000000000041527ab4c263c1b0c02610d9d2e564acdc00df53029e4ddbf54d679379b385b001000000fa000000060000000000000000040000007ef90106a077fe7dc410171f1621fa54e8a4f0bab0c44f845afaa5af752041e0412358b41594deaddeaddeaddeaddeaddeaddeaddeaddead00019442000000000000000000000000000000000000158080830f424080b8b23db6be2b00342f60000f79c50000000000000000000000006ab2888300000000018d3d31000000000000000000000000000000000000000000000000000000000c2794340000000000000000000000000000000000000000000000000000000000af01ce3f65480ab18d1a14035b6418c486794cf1ac00380cf5498264b32217f65d48390000000000000000000000009fb23129982c993743eb9bb156af8cc8fa2ac7610000000000000000000000000190","signature":{"r":"0x92dd2f259dd35ef4f11236e9d6f54caf3d5da6caab799ac66328b10ea1be0cc2","s":"0x6ea7433e43d0a7efb619d5d84b2392876f241621882d6036f9a8bae0e5b83fb8","yParity":"0x1","v":"0x1"}} \ No newline at end of file diff --git a/dstack/kms/light-client/helios.patch b/dstack/kms/light-client/helios.patch deleted file mode 100644 index dc3646e1e..000000000 --- a/dstack/kms/light-client/helios.patch +++ /dev/null @@ -1,1846 +0,0 @@ -diff --git a/Cargo.lock b/Cargo.lock -index 57d7eea..23c8171 100644 ---- a/Cargo.lock -+++ b/Cargo.lock -@@ -23,7 +23,7 @@ version = "0.5.2" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" - dependencies = [ -- "crypto-common", -+ "crypto-common 0.1.6", - "generic-array", - ] - -@@ -35,7 +35,7 @@ checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" - dependencies = [ - "cfg-if", - "cipher", -- "cpufeatures", -+ "cpufeatures 0.2.17", - ] - - [[package]] -@@ -240,41 +240,59 @@ dependencies = [ - - [[package]] - name = "alloy-eip2930" --version = "0.2.1" -+version = "0.2.4" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "7b82752a889170df67bbb36d42ca63c531eb16274f0d7299ae2a680facba17bd" -+checksum = "e64579d931b3f8eacc7c9ab0b220e87e9c4816e5c724ede1947b55c2f8e92ae5" - dependencies = [ - "alloy-primitives", - "alloy-rlp", -+ "borsh", - "serde", - ] - - [[package]] - name = "alloy-eip7702" --version = "0.6.1" -+version = "0.6.3" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "9d4769c6ffddca380b0070d71c8b7f30bed375543fe76bb2f74ec0acf4b7cd16" -+checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" - dependencies = [ - "alloy-primitives", - "alloy-rlp", -+ "borsh", - "k256", - "serde", - "thiserror 2.0.12", - ] - -+[[package]] -+name = "alloy-eip7928" -+version = "0.3.7" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "6b827a6d7784fe3eb3489d40699407a4cdcce74271421a01bdffe60cf573bb16" -+dependencies = [ -+ "alloy-primitives", -+ "alloy-rlp", -+ "borsh", -+ "once_cell", -+ "serde", -+ "thiserror 2.0.12", -+] -+ - [[package]] - name = "alloy-eips" --version = "1.0.37" -+version = "1.8.3" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "ac7f1c9a1ccc7f3e03c36976455751a6166a4f0d2d2c530c3f87dfe7d0cdc836" -+checksum = "e6ef28c9fdad22d4eec52d894f5f2673a0895f1e5ef196734568e68c0f6caca8" - dependencies = [ - "alloy-eip2124", - "alloy-eip2930", - "alloy-eip7702", -+ "alloy-eip7928", - "alloy-primitives", - "alloy-rlp", - "alloy-serde", - "auto_impl", -+ "borsh", - "c-kzg", - "derive_more", - "either", -@@ -283,7 +301,6 @@ dependencies = [ - "serde", - "serde_with", - "sha2 0.10.9", -- "thiserror 2.0.12", - ] - - [[package]] -@@ -368,18 +385,19 @@ dependencies = [ - - [[package]] - name = "alloy-primitives" --version = "1.3.0" -+version = "1.7.3" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "3cfebde8c581a5d37b678d0a48a32decb51efd7a63a08ce2517ddec26db705c8" -+checksum = "d5e9dbd49258ac3ab893a481d46be29b58be7f734dcac46cd80b6b13ee36566c" - dependencies = [ - "alloy-rlp", - "bytes", - "cfg-if", - "const-hex", - "derive_more", -- "foldhash", -- "getrandom 0.3.3", -- "hashbrown 0.15.4", -+ "fixed-cache", -+ "foldhash 0.2.0", -+ "getrandom 0.4.3", -+ "hashbrown 0.17.1", - "indexmap 2.10.0", - "itoa", - "k256", -@@ -387,11 +405,12 @@ dependencies = [ - "paste", - "proptest", - "rand 0.9.2", -+ "rapidhash", - "ruint", - "rustc-hash 2.1.1", -+ "secp256k1 0.31.1", - "serde", -- "sha3", -- "tiny-keccak", -+ "sha3 0.11.0", - ] - - [[package]] -@@ -650,9 +669,9 @@ dependencies = [ - - [[package]] - name = "alloy-serde" --version = "1.0.37" -+version = "1.8.3" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "5413814be7a22fbc81e0f04a2401fcc3eb25e56fd53b04683e8acecc6e1fe01b" -+checksum = "11ece63b89294b8614ab3f483560c08d016930f842bf36da56bf0b764a15c11e" - dependencies = [ - "alloy-primitives", - "serde", -@@ -1049,6 +1068,23 @@ dependencies = [ - "zeroize", - ] - -+[[package]] -+name = "ark-ff" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f7a806ac6c8307b929df4645776290a50ee2aac754ad09d8bdf73391309e43af" -+dependencies = [ -+ "ark-ff-asm 0.6.0", -+ "ark-ff-macros 0.6.0", -+ "ark-serialize 0.6.0", -+ "ark-std 0.6.0", -+ "digest 0.10.7", -+ "educe", -+ "num-bigint", -+ "num-traits", -+ "zeroize", -+] -+ - [[package]] - name = "ark-ff-asm" - version = "0.3.0" -@@ -1079,6 +1115,16 @@ dependencies = [ - "syn 2.0.104", - ] - -+[[package]] -+name = "ark-ff-asm" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "1479009684adc073dff49a1025d3a7065b317a9ead25aaaca38cdc70058ba8a2" -+dependencies = [ -+ "quote", -+ "syn 2.0.104", -+] -+ - [[package]] - name = "ark-ff-macros" - version = "0.3.0" -@@ -1117,6 +1163,19 @@ dependencies = [ - "syn 2.0.104", - ] - -+[[package]] -+name = "ark-ff-macros" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4a0691ed21ef00ef89c1e9bda832eba493dda3ec2f8d892fb25b705f73f06bb8" -+dependencies = [ -+ "num-bigint", -+ "num-traits", -+ "proc-macro2", -+ "quote", -+ "syn 2.0.104", -+] -+ - [[package]] - name = "ark-poly" - version = "0.5.0" -@@ -1188,13 +1247,26 @@ version = "0.5.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" - dependencies = [ -- "ark-serialize-derive", -+ "ark-serialize-derive 0.5.0", - "ark-std 0.5.0", - "arrayvec", - "digest 0.10.7", - "num-bigint", - ] - -+[[package]] -+name = "ark-serialize" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "a74dd304fd536fb95d0a328e72be759209cc496a9da094c5bc56e5fea4f9e86b" -+dependencies = [ -+ "ark-serialize-derive 0.6.0", -+ "ark-std 0.6.0", -+ "digest 0.10.7", -+ "num-bigint", -+ "serde_with", -+] -+ - [[package]] - name = "ark-serialize-derive" - version = "0.5.0" -@@ -1206,6 +1278,17 @@ dependencies = [ - "syn 2.0.104", - ] - -+[[package]] -+name = "ark-serialize-derive" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4f153690697a2b91e5e1251ff98411ee5371500a111a0fd317a70e588eb300f9" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "syn 2.0.104", -+] -+ - [[package]] - name = "ark-std" - version = "0.3.0" -@@ -1236,6 +1319,16 @@ dependencies = [ - "rand 0.8.5", - ] - -+[[package]] -+name = "ark-std" -+version = "0.6.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "367c9c827ed431bff6868b7aa926e05b16eb46603cc8b6e768e4a5553fa1d155" -+dependencies = [ -+ "num-traits", -+ "rand 0.8.5", -+] -+ - [[package]] - name = "arrayref" - version = "0.3.9" -@@ -1574,12 +1667,6 @@ dependencies = [ - "tower-service", - ] - --[[package]] --name = "az" --version = "1.2.1" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" -- - [[package]] - name = "backtrace" - version = "0.3.75" -@@ -1646,21 +1733,6 @@ version = "0.6.3" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "230c5f1ca6a325a32553f8640d31ac9b49f2411e901e427570154868b46da4f7" - --[[package]] --name = "bit-set" --version = "0.8.0" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" --dependencies = [ -- "bit-vec", --] -- --[[package]] --name = "bit-vec" --version = "0.8.0" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" -- - [[package]] - name = "bitcoin-io" - version = "0.1.3" -@@ -1685,11 +1757,11 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - - [[package]] - name = "bitflags" --version = "2.9.1" -+version = "2.13.2" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" -+checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" - dependencies = [ -- "serde", -+ "serde_core", - ] - - [[package]] -@@ -1732,6 +1804,15 @@ dependencies = [ - "generic-array", - ] - -+[[package]] -+name = "block-buffer" -+version = "0.12.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d2f6c7dbe95a6ed67ad9f18e57daf93a2f034c524b99fd2b76d18fdfeb6660aa" -+dependencies = [ -+ "hybrid-array", -+] -+ - [[package]] - name = "bls12_381" - version = "0.8.0" -@@ -1748,9 +1829,9 @@ dependencies = [ - - [[package]] - name = "blst" --version = "0.3.15" -+version = "0.3.17" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "4fd49896f12ac9b6dcd7a5998466b9b58263a695a3dd1ecc1aaca2e12a90b080" -+checksum = "c20659f9bbee16cbbd2f7393e40ab6309f5a98f76a2eb57a995ec508b72387fe" - dependencies = [ - "cc", - "glob", -@@ -1758,6 +1839,30 @@ dependencies = [ - "zeroize", - ] - -+[[package]] -+name = "borsh" -+version = "1.8.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "553c5d846a6ba5150c65e3b1b8ec073bcf1abc20f9b7220de384a4443ea4e20a" -+dependencies = [ -+ "borsh-derive", -+ "bytes", -+ "cfg_aliases", -+] -+ -+[[package]] -+name = "borsh-derive" -+version = "1.8.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "12cdfe656708a01f89b451a7d36466e6fe6c414de0aa18fc54f864f6f9ca9f56" -+dependencies = [ -+ "once_cell", -+ "proc-macro-crate 3.3.0", -+ "proc-macro2", -+ "quote", -+ "syn 3.0.6", -+] -+ - [[package]] - name = "brotli" - version = "8.0.1" -@@ -1830,9 +1935,9 @@ dependencies = [ - - [[package]] - name = "c-kzg" --version = "2.1.1" -+version = "2.1.8" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "7318cfa722931cb5fe0838b98d3ce5621e75f6a6408abc21721d80de9223f2e4" -+checksum = "38d04308254695569fdb9bfe3bacc1c91837a670d0806605eb82d63748fbd3a6" - dependencies = [ - "blst", - "cc", -@@ -1895,7 +2000,7 @@ checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" - dependencies = [ - "cfg-if", - "cipher", -- "cpufeatures", -+ "cpufeatures 0.2.17", - ] - - [[package]] -@@ -1959,7 +2064,7 @@ version = "0.4.4" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" - dependencies = [ -- "crypto-common", -+ "crypto-common 0.1.6", - "inout", - "zeroize", - ] -@@ -2055,7 +2160,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "83e22e0ed40b96a48d3db274f72fd365bd78f67af39b6bbd47e8a15e1c6207ff" - dependencies = [ - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "hex", - "proptest", - "serde", -@@ -2157,6 +2262,15 @@ dependencies = [ - "libc", - ] - -+[[package]] -+name = "cpufeatures" -+version = "0.3.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" -+dependencies = [ -+ "libc", -+] -+ - [[package]] - name = "crc" - version = "3.3.0" -@@ -2250,7 +2364,7 @@ version = "0.27.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "f476fe445d41c9e991fd07515a6f463074b782242ccf4a5b7b1d1012e70824df" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "crossterm_winapi", - "libc", - "mio 0.8.11", -@@ -2298,6 +2412,15 @@ dependencies = [ - "typenum", - ] - -+[[package]] -+name = "crypto-common" -+version = "0.2.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" -+dependencies = [ -+ "hybrid-array", -+] -+ - [[package]] - name = "crypto-mac" - version = "0.8.0" -@@ -2347,7 +2470,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" - dependencies = [ - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "curve25519-dalek-derive", - "digest 0.10.7", - "fiat-crypto", -@@ -2570,13 +2693,13 @@ dependencies = [ - - [[package]] - name = "derive-where" --version = "1.5.0" -+version = "1.7.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "510c292c8cf384b1a340b816a9a6cf2599eb8f566a44949024af88418000c50b" -+checksum = "2e2b94854e8576378ccda7c8de8a66ed8b4e8acbd2c50ec3418ea6c8aaf4b567" - dependencies = [ - "proc-macro2", - "quote", -- "syn 2.0.104", -+ "syn 3.0.6", - ] - - [[package]] -@@ -2623,10 +2746,20 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" - dependencies = [ - "block-buffer 0.10.4", - "const-oid", -- "crypto-common", -+ "crypto-common 0.1.6", - "subtle", - ] - -+[[package]] -+name = "digest" -+version = "0.11.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" -+dependencies = [ -+ "block-buffer 0.12.1", -+ "crypto-common 0.2.2", -+] -+ - [[package]] - name = "dirs" - version = "5.0.1" -@@ -2867,7 +3000,7 @@ dependencies = [ - "log", - "rand 0.8.5", - "serde", -- "sha3", -+ "sha3 0.10.8", - "zeroize", - ] - -@@ -2936,7 +3069,7 @@ name = "ethereum_hashing" - version = "0.7.0" - source = "git+https://github.com/ncitron/ethereum_hashing?rev=7ee70944ed4fabe301551da8c447e4f4ae5e6c35#7ee70944ed4fabe301551da8c447e4f4ae5e6c35" - dependencies = [ -- "cpufeatures", -+ "cpufeatures 0.2.17", - "sha2 0.10.9", - ] - -@@ -3079,6 +3212,16 @@ dependencies = [ - "version_check", - ] - -+[[package]] -+name = "fixed-cache" -+version = "0.1.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "2fe63500644ef0269fe6b744e7e5dc5c20b5eebf3d881bc2be53f194636f6583" -+dependencies = [ -+ "equivalent", -+ "rapidhash", -+] -+ - [[package]] - name = "fixed-hash" - version = "0.8.0" -@@ -3119,13 +3262,19 @@ version = "0.1.5" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" - -+[[package]] -+name = "foldhash" -+version = "0.2.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" -+ - [[package]] - name = "font-kit" - version = "0.14.3" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "2c7e611d49285d4c4b2e1727b72cf05353558885cc5252f93707b845dfcaf3d3" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "byteorder", - "core-foundation", - "core-graphics", -@@ -3379,11 +3528,22 @@ dependencies = [ - "cfg-if", - "js-sys", - "libc", -- "r-efi", -+ "r-efi 5.3.0", - "wasi 0.14.2+wasi-0.2.4", - "wasm-bindgen", - ] - -+[[package]] -+name = "getrandom" -+version = "0.4.3" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" -+dependencies = [ -+ "cfg-if", -+ "libc", -+ "r-efi 6.0.0", -+] -+ - [[package]] - name = "ghash" - version = "0.5.1" -@@ -3429,16 +3589,6 @@ dependencies = [ - "regex-syntax 0.8.5", - ] - --[[package]] --name = "gmp-mpfr-sys" --version = "1.6.8" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "60f8970a75c006bb2f8ae79c6768a116dd215fa8346a87aed99bf9d82ca43394" --dependencies = [ -- "libc", -- "windows-sys 0.60.2", --] -- - [[package]] - name = "group" - version = "0.13.0" -@@ -3528,8 +3678,18 @@ checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" - dependencies = [ - "allocator-api2", - "equivalent", -- "foldhash", -+ "foldhash 0.1.5", -+] -+ -+[[package]] -+name = "hashbrown" -+version = "0.17.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" -+dependencies = [ -+ "foldhash 0.2.0", - "serde", -+ "serde_core", - ] - - [[package]] -@@ -3772,6 +3932,7 @@ dependencies = [ - "reqwest 0.12.22", - "revm", - "serde", -+ "serde_json", - "sha2 0.9.9", - "snap", - "ssz_types", -@@ -4074,6 +4235,15 @@ version = "1.0.3" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" - -+[[package]] -+name = "hybrid-array" -+version = "0.4.10" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" -+dependencies = [ -+ "typenum", -+] -+ - [[package]] - name = "hyper" - version = "0.14.32" -@@ -4482,7 +4652,7 @@ version = "0.7.9" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "d93587f37623a1a17d94ef2bc9ada592f5465fe7732084ab7beefabe5c77c0c4" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "cfg-if", - "libc", - ] -@@ -4705,14 +4875,24 @@ version = "0.1.5" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "ecc2af9a1119c51f12a14607e783cb977bde58bc069ff0c3da1095e635d70654" - dependencies = [ -- "cpufeatures", -+ "cpufeatures 0.2.17", -+] -+ -+[[package]] -+name = "keccak" -+version = "0.2.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "d8f198d1db720e4940b5a493201d199d9f24f568f8f746bd13706243a2f71598" -+dependencies = [ -+ "cfg-if", -+ "cpufeatures 0.3.1", - ] - - [[package]] - name = "keccak-asm" --version = "0.1.4" -+version = "0.1.8" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "505d1856a39b200489082f90d897c3f07c455563880bc5952e38eabf731c83b6" -+checksum = "dd5dc2c0d691cbf7595cde551ced329cca99c2387c2cbc97754c5d0cd045d3ee" - dependencies = [ - "digest 0.10.7", - "sha3-asm", -@@ -4765,12 +4945,12 @@ dependencies = [ - "libp2p-identity", - "libp2p-mdns", - "libp2p-metrics", -- "libp2p-mplex", - "libp2p-noise", - "libp2p-ping", - "libp2p-quic", - "libp2p-swarm", - "libp2p-tcp", -+ "libp2p-yamux", - "multiaddr", - "pin-project", - ] -@@ -4927,24 +5107,6 @@ dependencies = [ - "prometheus-client", - ] - --[[package]] --name = "libp2p-mplex" --version = "0.39.0" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "4d34780b514b159e6f3fd70ba3e72664ec89da28dca2d1e7856ee55e2c7031ba" --dependencies = [ -- "asynchronous-codec", -- "bytes", -- "futures", -- "libp2p-core", -- "log", -- "nohash-hasher", -- "parking_lot 0.12.4", -- "rand 0.8.5", -- "smallvec", -- "unsigned-varint", --] -- - [[package]] - name = "libp2p-noise" - version = "0.42.2" -@@ -5074,13 +5236,26 @@ dependencies = [ - "yasna", - ] - -+[[package]] -+name = "libp2p-yamux" -+version = "0.43.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "4dcd21d950662700a385d4c6d68e2f5f54d778e97068cdd718522222ef513bda" -+dependencies = [ -+ "futures", -+ "libp2p-core", -+ "log", -+ "thiserror 1.0.69", -+ "yamux", -+] -+ - [[package]] - name = "libredox" - version = "0.1.8" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "360e552c93fa0e8152ab463bc4c4837fce76a225df11dfaeea66c313de5e61f7" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "libc", - ] - -@@ -5471,7 +5646,7 @@ version = "0.30.1" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "cfg-if", - "cfg_aliases", - "libc", -@@ -5720,9 +5895,9 @@ dependencies = [ - - [[package]] - name = "op-revm" --version = "10.1.0" -+version = "20.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "f9ba4f4693811e73449193c8bd656d3978f265871916882e6a51a487e4f96217" -+checksum = "a5cfe618e3426e7a65b74f105aa706cfc92cd100ade8a6588a15c6ac3002e467" - dependencies = [ - "auto_impl", - "revm", -@@ -5741,7 +5916,7 @@ version = "0.10.73" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "8505734d46c8ab1e19a1dce3aef597ad87dcb4c37e7188231769bd6bd51cebf8" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "cfg-if", - "foreign-types 0.3.2", - "libc", -@@ -5990,9 +6165,9 @@ dependencies = [ - - [[package]] - name = "phf" --version = "0.11.3" -+version = "0.13.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "1fd6780a80ae0c52cc120a26a1a42c1ae51b247a253e4e06113d23d2c2edd078" -+checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" - dependencies = [ - "phf_macros", - "phf_shared", -@@ -6001,19 +6176,19 @@ dependencies = [ - - [[package]] - name = "phf_generator" --version = "0.11.3" -+version = "0.13.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "3c80231409c20246a13fddb31776fb942c38553c51e871f8cbd687a4cfb5843d" -+checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" - dependencies = [ -+ "fastrand", - "phf_shared", -- "rand 0.8.5", - ] - - [[package]] - name = "phf_macros" --version = "0.11.3" -+version = "0.13.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "f84ac04429c13a7ff43785d75ad27569f2951ce0ffd30a3321230db2fc727216" -+checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" - dependencies = [ - "phf_generator", - "phf_shared", -@@ -6024,9 +6199,9 @@ dependencies = [ - - [[package]] - name = "phf_shared" --version = "0.11.3" -+version = "0.13.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "67eabc2ef2a60eb7faa00097bd1ffdb5bd28e62bf39990626a582201b7a754e5" -+checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" - dependencies = [ - "siphasher", - ] -@@ -6158,7 +6333,7 @@ version = "0.8.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" - dependencies = [ -- "cpufeatures", -+ "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", - ] -@@ -6170,7 +6345,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" - dependencies = [ - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "opaque-debug", - "universal-hash", - ] -@@ -6345,26 +6520,16 @@ version = "1.7.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "6fcdab19deb5195a31cf7726a210015ff1496ba1464fd42cb4f537b8b01b471f" - dependencies = [ -- "bit-set", -- "bit-vec", -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "lazy_static", - "num-traits", - "rand 0.9.2", - "rand_chacha 0.9.0", - "rand_xorshift", - "regex-syntax 0.8.5", -- "rusty-fork", -- "tempfile", - "unarray", - ] - --[[package]] --name = "quick-error" --version = "1.2.3" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" -- - [[package]] - name = "quick-protobuf" - version = "0.8.1" -@@ -6475,6 +6640,12 @@ version = "5.3.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" - -+[[package]] -+name = "r-efi" -+version = "6.0.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" -+ - [[package]] - name = "radium" - version = "0.7.0" -@@ -6561,13 +6732,23 @@ dependencies = [ - "rand_core 0.9.3", - ] - -+[[package]] -+name = "rapidhash" -+version = "4.5.1" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e" -+dependencies = [ -+ "getrandom 0.3.3", -+ "rustversion", -+] -+ - [[package]] - name = "ratatui" - version = "0.26.3" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "f44c9e68fd46eda15c646fbb85e1040b657a58cdc8c98db1d97a55930d991eef" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "cassowary", - "compact_str", - "crossterm", -@@ -6634,7 +6815,7 @@ version = "0.5.17" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - ] - - [[package]] -@@ -6876,9 +7057,9 @@ dependencies = [ - - [[package]] - name = "revm" --version = "29.0.1" -+version = "38.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "718d90dce5f07e115d0e66450b1b8aa29694c1cf3f89ebddaddccc2ccbd2f13e" -+checksum = "91202d39dbe8e8d10e9e8f2b76c30da68ecd1d25be69ba6d853ad0d03a3a398a" - dependencies = [ - "revm-bytecode", - "revm-context", -@@ -6895,9 +7076,9 @@ dependencies = [ - - [[package]] - name = "revm-bytecode" --version = "6.2.2" -+version = "10.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "66c52031b73cae95d84cd1b07725808b5fd1500da3e5e24574a3b2dc13d9f16d" -+checksum = "bdbb3a3d735efa94c91f2ef6bf20a35f99a77bc78f3e25bd758336901bdf9661" - dependencies = [ - "bitvec", - "phf", -@@ -6907,9 +7088,9 @@ dependencies = [ - - [[package]] - name = "revm-context" --version = "9.1.0" -+version = "16.0.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "5a20c98e7008591a6f012550c2a00aa36cba8c14cc88eb88dec32eb9102554b4" -+checksum = "c5f68d928d8b228e0faeb1c6ed75c4fde7d124f1ddf9119b67e7a0ad4041237d" - dependencies = [ - "bitvec", - "cfg-if", -@@ -6924,9 +7105,9 @@ dependencies = [ - - [[package]] - name = "revm-context-interface" --version = "10.2.0" -+version = "17.0.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "b50d241ed1ce647b94caf174fcd0239b7651318b2c4c06b825b59b973dfb8495" -+checksum = "1f3758e6167c4ba7a59a689c519a047edaefcd4c37d74f279b93ed87bc8aece4" - dependencies = [ - "alloy-eip2930", - "alloy-eip7702", -@@ -6940,9 +7121,9 @@ dependencies = [ - - [[package]] - name = "revm-database" --version = "7.0.5" -+version = "13.0.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "39a276ed142b4718dcf64bc9624f474373ed82ef20611025045c3fb23edbef9c" -+checksum = "c281a1f11d3bcb8c0bba1199ed6bcb001d1aeb3d4fb366819e14f88723989a4e" - dependencies = [ - "alloy-eips", - "revm-bytecode", -@@ -6954,22 +7135,23 @@ dependencies = [ - - [[package]] - name = "revm-database-interface" --version = "7.0.5" -+version = "11.0.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "8c523c77e74eeedbac5d6f7c092e3851dbe9c7fec6f418b85992bd79229db361" -+checksum = "d89efb9832a4e3742bb4ded5f7fe5bf905e8860e69427d4dfec153484fc6d304" - dependencies = [ - "auto_impl", - "either", - "revm-primitives", - "revm-state", - "serde", -+ "thiserror 2.0.12", - ] - - [[package]] - name = "revm-handler" --version = "10.0.1" -+version = "18.1.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "550331ea85c1d257686e672081576172fe3d5a10526248b663bbf54f1bef226a" -+checksum = "783e903d6922b7f5f9a940d1bb229530502d2924b1aed9d5ca5a94ebf065d460" - dependencies = [ - "auto_impl", - "derive-where", -@@ -6986,9 +7168,9 @@ dependencies = [ - - [[package]] - name = "revm-inspector" --version = "10.0.1" -+version = "19.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "7c0a6e9ccc2ae006f5bed8bd80cd6f8d3832cd55c5e861b9402fdd556098512f" -+checksum = "8216ad58422090d0daa9eb430e0a081f7ad07e7fd30681dee71f8420c99624e0" - dependencies = [ - "auto_impl", - "either", -@@ -7004,21 +7186,22 @@ dependencies = [ - - [[package]] - name = "revm-interpreter" --version = "25.0.3" -+version = "35.0.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "06575dc51b1d8f5091daa12a435733a90b4a132dca7ccee0666c7db3851bc30c" -+checksum = "1ece9f41b69658c15d748288a4dbdfc06a63f3ce93d983af440de3f1631dce6a" - dependencies = [ - "revm-bytecode", - "revm-context-interface", - "revm-primitives", -+ "revm-state", - "serde", - ] - - [[package]] - name = "revm-precompile" --version = "27.0.0" -+version = "34.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "25b57d4bd9e6b5fe469da5452a8a137bc2d030a3cd47c46908efc615bbc699da" -+checksum = "a346a8cc6c8c39bd65306641c692191299c0a7b63d38810e39e8fe9b92378660" - dependencies = [ - "ark-bls12-381", - "ark-bn254", -@@ -7030,20 +7213,19 @@ dependencies = [ - "c-kzg", - "cfg-if", - "k256", -- "libsecp256k1", - "p256", -+ "revm-context-interface", - "revm-primitives", - "ripemd", -- "rug", - "secp256k1 0.31.1", - "sha2 0.10.9", - ] - - [[package]] - name = "revm-primitives" --version = "20.2.1" -+version = "23.0.0" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "5aa29d9da06fe03b249b6419b33968ecdf92ad6428e2f012dc57bcd619b5d94e" -+checksum = "0c99bda77d9661521ba0b4bc04558c6692074f01e65dd420fa3b893033d9b8a2" - dependencies = [ - "alloy-primitives", - "num_enum", -@@ -7053,11 +7235,12 @@ dependencies = [ - - [[package]] - name = "revm-state" --version = "7.0.5" -+version = "11.0.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "1f64fbacb86008394aaebd3454f9643b7d5a782bd251135e17c5b33da592d84d" -+checksum = "c32490ed687dba31c3c882beb8c20408bdd30ef96690d8f145b0ee9a87040bfe" - dependencies = [ -- "bitflags 2.9.1", -+ "alloy-eip7928", -+ "bitflags 2.13.2", - "revm-bytecode", - "revm-primitives", - "serde", -@@ -7139,27 +7322,17 @@ dependencies = [ - "tokio", - ] - --[[package]] --name = "rug" --version = "1.28.0" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "58ad2e973fe3c3214251a840a621812a4f40468da814b1a3d6947d433c2af11f" --dependencies = [ -- "az", -- "gmp-mpfr-sys", -- "libc", -- "libm", --] -- - [[package]] - name = "ruint" --version = "1.15.0" -+version = "1.20.1" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "11256b5fe8c68f56ac6f39ef0720e592f33d2367a4782740d9c9142e889c7fb4" -+checksum = "2973657b5127d510e230f5c63d2d106af9c8f79393d8b9f4647323e8196bdde5" - dependencies = [ - "alloy-rlp", - "ark-ff 0.3.0", - "ark-ff 0.4.2", -+ "ark-ff 0.5.0", -+ "ark-ff 0.6.0", - "bytes", - "fastrlp 0.3.1", - "fastrlp 0.4.0", -@@ -7173,7 +7346,7 @@ dependencies = [ - "rand 0.9.2", - "rlp", - "ruint-macro", -- "serde", -+ "serde_core", - "valuable", - "zeroize", - ] -@@ -7244,7 +7417,7 @@ version = "1.0.8" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "errno", - "libc", - "linux-raw-sys", -@@ -7313,18 +7486,6 @@ version = "1.0.21" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "8a0d197bd2c9dc6e53b84da9556a69ba4cdfab8619eb41a8bd1cc2027a0f6b1d" - --[[package]] --name = "rusty-fork" --version = "0.3.0" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" --dependencies = [ -- "fnv", -- "quick-error", -- "tempfile", -- "wait-timeout", --] -- - [[package]] - name = "rw-stream-sink" - version = "0.3.0" -@@ -7488,7 +7649,7 @@ version = "2.11.1" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "core-foundation", - "core-foundation-sys", - "libc", -@@ -7721,7 +7882,7 @@ checksum = "99cd6713db3cf16b6c84e06321e049a9b9f699826e16096d23bbcc44d15d51a6" - dependencies = [ - "block-buffer 0.9.0", - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "digest 0.9.0", - "opaque-debug", - ] -@@ -7733,7 +7894,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" - dependencies = [ - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "digest 0.10.7", - ] - -@@ -7745,7 +7906,7 @@ checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" - dependencies = [ - "block-buffer 0.9.0", - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "digest 0.9.0", - "opaque-debug", - ] -@@ -7757,7 +7918,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" - dependencies = [ - "cfg-if", -- "cpufeatures", -+ "cpufeatures 0.2.17", - "digest 0.10.7", - ] - -@@ -7768,14 +7929,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "75872d278a8f37ef87fa0ddbda7802605cb18344497949862c0d4dcb291eba60" - dependencies = [ - "digest 0.10.7", -- "keccak", -+ "keccak 0.1.5", -+] -+ -+[[package]] -+name = "sha3" -+version = "0.11.0" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "be176f1a57ce4e3d31c1a166222d9768de5954f811601fb7ca06fc8203905ce1" -+dependencies = [ -+ "digest 0.11.3", -+ "keccak 0.2.2", - ] - - [[package]] - name = "sha3-asm" --version = "0.1.4" -+version = "0.1.8" - source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "c28efc5e327c837aa837c59eae585fc250715ef939ac32881bcc11677cd02d46" -+checksum = "a6287fd675f713484342a89cbf0a386abef5f15919cfad607e5e1f19e1e15331" - dependencies = [ - "cc", - "cfg-if", -@@ -8083,6 +8254,17 @@ dependencies = [ - "unicode-ident", - ] - -+[[package]] -+name = "syn" -+version = "3.0.6" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "8593e8e72159ed2257d083c7a454a85cbf854f37a0966d8d483aff8c8a3ebcee" -+dependencies = [ -+ "proc-macro2", -+ "quote", -+ "unicode-ident", -+] -+ - [[package]] - name = "syn-solidity" - version = "1.3.0" -@@ -8150,7 +8332,7 @@ version = "0.6.1" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "core-foundation", - "system-configuration-sys 0.6.0", - ] -@@ -8505,7 +8687,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "adc82fd73de2a9722ac5da747f12383d2bfdb93591ee6c58486e0097890f05f2" - dependencies = [ - "async-compression", -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - "bytes", - "futures-core", - "futures-util", -@@ -8822,7 +9004,7 @@ version = "0.5.1" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" - dependencies = [ -- "crypto-common", -+ "crypto-common 0.1.6", - "subtle", - ] - -@@ -8908,15 +9090,6 @@ version = "1.0.2" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" - --[[package]] --name = "wait-timeout" --version = "0.2.1" --source = "registry+https://github.com/rust-lang/crates.io-index" --checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" --dependencies = [ -- "libc", --] -- - [[package]] - name = "walkdir" - version = "2.5.0" -@@ -9527,7 +9700,7 @@ version = "0.39.0" - source = "registry+https://github.com/rust-lang/crates.io-index" - checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" - dependencies = [ -- "bitflags 2.9.1", -+ "bitflags 2.13.2", - ] - - [[package]] -@@ -9593,6 +9766,20 @@ dependencies = [ - "time", - ] - -+[[package]] -+name = "yamux" -+version = "0.10.2" -+source = "registry+https://github.com/rust-lang/crates.io-index" -+checksum = "e5d9ba232399af1783a58d8eb26f6b5006fbefe2dc9ef36bd283324792d03ea5" -+dependencies = [ -+ "futures", -+ "log", -+ "nohash-hasher", -+ "parking_lot 0.12.4", -+ "rand 0.8.5", -+ "static_assertions", -+] -+ - [[package]] - name = "yansi" - version = "1.0.1" -diff --git a/Cargo.toml b/Cargo.toml -index 5cf402f..0a3e393 100644 ---- a/Cargo.toml -+++ b/Cargo.toml -@@ -54,7 +54,7 @@ alloy = { version = "1.0.37", features = [ - ] } - alloy-trie = { version = "0.9.1", features = ["ethereum"] } - op-alloy-rpc-types = "0.20.0" --revm = { version = "29.0.1", default-features = false, features = [ -+revm = { version = "38.0.0", default-features = false, features = [ - "std", - "serde", - "optional_block_gas_limit", -diff --git a/common/src/fork_schedule.rs b/common/src/fork_schedule.rs -index 21d6aec..ca4b433 100644 ---- a/common/src/fork_schedule.rs -+++ b/common/src/fork_schedule.rs -@@ -34,6 +34,8 @@ pub struct ForkSchedule { - pub holocene_timestamp: u64, - pub isthmus_timestamp: u64, - pub jovian_timestamp: u64, -+ #[serde(default = "disabled_fork")] -+ pub karst_timestamp: u64, - } - - impl Default for ForkSchedule { -@@ -70,6 +72,7 @@ impl Default for ForkSchedule { - holocene_timestamp: u64::MAX, - isthmus_timestamp: u64::MAX, - jovian_timestamp: u64::MAX, -+ karst_timestamp: u64::MAX, - } - } - } -@@ -85,3 +88,7 @@ impl ForkSchedule { - } - } - } -+ -+fn disabled_fork() -> u64 { -+ u64::MAX -+} -diff --git a/ethereum/src/evm.rs b/ethereum/src/evm.rs -index c6c415e..23666d2 100644 ---- a/ethereum/src/evm.rs -+++ b/ethereum/src/evm.rs -@@ -129,6 +129,7 @@ impl> EthereumEvm { - cfg.spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule); - cfg.chain_id = self.chain_id; - cfg.disable_block_gas_limit = !validate_tx; -+ cfg.tx_gas_limit_cap = (!validate_tx).then_some(u64::MAX); - cfg.disable_eip3607 = !validate_tx; - cfg.disable_base_fee = !validate_tx; - cfg.disable_nonce_check = !validate_tx; -@@ -188,6 +189,7 @@ impl> EthereumEvm { - - BlockEnv { - number: U256::from(block.header.number()), -+ slot_num: 0, - beneficiary: block.header.beneficiary(), - timestamp: U256::from(block.header.timestamp()), - gas_limit: block.header.gas_limit(), -diff --git a/opstack/Cargo.toml b/opstack/Cargo.toml -index b80f63a..fc8b310 100644 ---- a/opstack/Cargo.toml -+++ b/opstack/Cargo.toml -@@ -24,7 +24,7 @@ async-trait.workspace = true - alloy.workspace = true - alloy-trie.workspace = true - revm.workspace = true --op-revm = { version = "10.1.0", default-features = false, features = [ -+op-revm = { version = "20.0.0", default-features = false, features = [ - "std", - "serde", - "optional_block_gas_limit", -@@ -56,7 +56,7 @@ helios-revm-utils = { path = "../revm-utils" } - axum = "0.7.6" - clap = { version = "4.5.4", features = ["derive", "env"] } - # networking --libp2p = { version = "0.51.3", features = ["macros", "tokio", "tcp", "mplex", "noise", "gossipsub", "ping"] } -+libp2p = { version = "0.51.3", features = ["macros", "tokio", "tcp", "yamux", "noise", "gossipsub", "ping"] } - discv5 = "0.7.0" - libp2p-identity = { version = "0.1.2", features = ["secp256k1"] } - unsigned-varint = "0.7.1" -@@ -67,3 +67,6 @@ parking_lot = { version = "0.12.2" } - [target.'cfg(target_arch = "wasm32")'.dependencies] - wasm-bindgen-futures = "0.4.37" - getrandom = { version = "0.2.1", features = ["js"] } -+ -+[dev-dependencies] -+serde_json.workspace = true -diff --git a/opstack/src/config.rs b/opstack/src/config.rs -index 74dad94..01c0651 100644 ---- a/opstack/src/config.rs -+++ b/opstack/src/config.rs -@@ -45,6 +45,7 @@ pub struct NetworkConfig { - - #[derive(Copy, Clone, Debug)] - pub enum Network { -+ Phala, - OpMainnet, - Base, - BaseSepolia, -@@ -56,6 +57,7 @@ pub enum Network { - impl Display for Network { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { -+ Self::Phala => f.write_str("phala"), - Self::OpMainnet => f.write_str("op-mainnet"), - Self::Base => f.write_str("base"), - Self::BaseSepolia => f.write_str("base-sepolia"), -@@ -71,6 +73,7 @@ impl FromStr for Network { - - fn from_str(s: &str) -> Result { - match s { -+ "phala" => Ok(Self::Phala), - "op-mainnet" => Ok(Self::OpMainnet), - "base" => Ok(Self::Base), - "base-sepolia" => Ok(Self::BaseSepolia), -@@ -85,6 +88,30 @@ impl FromStr for Network { - impl From for NetworkConfig { - fn from(value: Network) -> Self { - match value { -+ Network::Phala => NetworkConfig { -+ consensus_rpc: Some("http://127.0.0.1:3000".parse().unwrap()), -+ chain: ChainConfig { -+ chain_id: 2035, -+ unsafe_signer: address!("F63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC"), -+ system_config_contract: address!("eBf5859b7646ca9cf8A981613569bF28394F2571"), -+ eth_network: EthNetwork::Mainnet, -+ forks: ForkSchedule { -+ bedrock_timestamp: 0, -+ regolith_timestamp: 0, -+ canyon_timestamp: 0, -+ delta_timestamp: 0, -+ ecotone_timestamp: 0, -+ fjord_timestamp: 0, -+ granite_timestamp: 1736272801, -+ holocene_timestamp: 1736445601, -+ isthmus_timestamp: 1746806401, -+ jovian_timestamp: 1769491801, -+ karst_timestamp: 1787673601, -+ ..Default::default() -+ }, -+ }, -+ verify_unsafe_signer: false, -+ }, - Network::OpMainnet => NetworkConfig { - consensus_rpc: Some( - "https://op-mainnet.operationsolarstorm.org" -diff --git a/opstack/src/consensus.rs b/opstack/src/consensus.rs -index b2532bc..ab6956b 100644 ---- a/opstack/src/consensus.rs -+++ b/opstack/src/consensus.rs -@@ -4,11 +4,11 @@ use std::time::Duration; - - use url::Url; - --use alloy::consensus::proofs::{calculate_transaction_root, calculate_withdrawals_root}; -+use alloy::consensus::proofs::calculate_transaction_root; - use alloy::consensus::transaction::SignerRecoverable; - use alloy::consensus::{Header as ConsensusHeader, Transaction as TxTrait}; - use alloy::eips::eip4895::{Withdrawal, Withdrawals}; --use alloy::primitives::{b256, fixed_bytes, Address, Bloom, BloomInput, B256, U256}; -+use alloy::primitives::{b256, fixed_bytes, Address, Bloom, B256, U256}; - use alloy::rlp::Decodable; - use alloy::rpc::types::{ - Block, EIP1186AccountProofResponse, Header, Transaction as EthTransaction, -@@ -159,7 +159,9 @@ impl Inner { - let age = now.saturating_sub(timestamp); - let number = payload.block_number; - -- if let Ok(block) = payload_to_block(payload) { -+ { -+ let block = -+ payload_to_block(payload, B256::from_slice(&commitment.data[..32]))?; - self.latest_block = Some(block.header.number); - _ = self.block_send.send(block).await; - -@@ -168,8 +170,6 @@ impl Inner { - number, - age.as_secs() - ); -- } else { -- tracing::warn!("invalid block received"); - } - } - } -@@ -271,7 +271,10 @@ fn verify_unsafe_signer(config: Config, signer: Arc>) { - }); - } - --fn payload_to_block(value: ExecutionPayload) -> Result> { -+fn payload_to_block( -+ value: ExecutionPayload, -+ parent_beacon_block_root: B256, -+) -> Result> { - let empty_nonce = fixed_bytes!("0000000000000000"); - let empty_uncle_hash = - b256!("1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"); -@@ -283,7 +286,7 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { - .map(|(i, tx_bytes)| { - let tx_bytes = tx_bytes.to_vec(); - let mut tx_bytes_slice = tx_bytes.as_slice(); -- let tx_envelope = OpTxEnvelope::decode(&mut tx_bytes_slice).unwrap(); -+ let tx_envelope = OpTxEnvelope::decode(&mut tx_bytes_slice)?; - let base_fee = tx_envelope.effective_gas_price(Some(value.base_fee_per_gas.to())); - let recovered = tx_envelope.try_into_recovered()?; - -@@ -323,9 +326,9 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { - let txs_root = calculate_transaction_root(&tx_envelopes); - - let withdrawals: Vec = value.withdrawals.into_iter().map(|w| w.into()).collect(); -- let withdrawals_root = calculate_withdrawals_root(&withdrawals); -+ let withdrawals_root = value.withdrawals_root; - -- let logs_bloom: Bloom = Bloom::from(BloomInput::Raw(&value.logs_bloom)); -+ let logs_bloom: Bloom = Bloom::from_slice(&value.logs_bloom); - - let consensus_header = ConsensusHeader { - parent_hash: value.parent_hash, -@@ -345,12 +348,19 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { - base_fee_per_gas: Some(value.base_fee_per_gas.to::()), - blob_gas_used: Some(value.blob_gas_used), - excess_blob_gas: Some(value.excess_blob_gas), -- parent_beacon_block_root: None, -+ parent_beacon_block_root: Some(parent_beacon_block_root), - extra_data: value.extra_data.to_vec().into(), -- requests_hash: None, -+ requests_hash: Some(b256!( -+ "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" -+ )), - logs_bloom, - }; - -+ eyre::ensure!( -+ consensus_header.hash_slow() == value.block_hash, -+ "payload block hash mismatch" -+ ); -+ - let header = Header { - hash: value.block_hash, - inner: consensus_header, -@@ -361,3 +371,38 @@ fn payload_to_block(value: ExecutionPayload) -> Result> { - Ok(Block::new(header, BlockTransactions::Full(txs)) - .with_withdrawals(Some(Withdrawals::new(withdrawals)))) - } -+ -+#[cfg(test)] -+mod phala_tests { -+ use super::*; -+ use alloy::primitives::address; -+ -+ #[test] -+ fn authentic_phala_payload_reconstructs_header() { -+ let commitment: SequencerCommitment = serde_json::from_str(include_str!( -+ "../tests/fixtures/phala-signed-commitment.json" -+ )) -+ .unwrap(); -+ let signer = address!("F63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC"); -+ commitment.verify(signer, 2035).unwrap(); -+ assert!(commitment.verify(signer, 8453).is_err()); -+ assert!(commitment.verify(Address::ZERO, 2035).is_err()); -+ let payload = ExecutionPayload::try_from(&commitment).unwrap(); -+ let parent_root = B256::from_slice(&commitment.data[..32]); -+ let block = payload_to_block(payload.clone(), parent_root).unwrap(); -+ assert_eq!(block.header.number, 5569808); -+ let truncated = snap::raw::Encoder::new().compress_vec(&[0; 10]).unwrap(); -+ assert!(SequencerCommitment::new(&truncated).is_err()); -+ let mut empty = commitment.clone(); -+ empty.data = Default::default(); -+ assert!(ExecutionPayload::try_from(&empty).is_err()); -+ let mut forged = commitment.clone(); -+ let mut data = forged.data.to_vec(); -+ data[0] ^= 1; -+ forged.data = data.into(); -+ assert!(forged.verify(signer, 2035).is_err()); -+ let mut corrupt = payload; -+ corrupt.state_root = B256::ZERO; -+ assert!(payload_to_block(corrupt, parent_root).is_err()); -+ } -+} -diff --git a/opstack/src/evm.rs b/opstack/src/evm.rs -index c86c5a9..4fb991e 100644 ---- a/opstack/src/evm.rs -+++ b/opstack/src/evm.rs -@@ -132,6 +132,7 @@ impl> OpStackEvm { - cfg.spec = get_spec_id_for_block_timestamp(block.header.timestamp, &self.fork_schedule); - cfg.chain_id = self.chain_id; - cfg.disable_block_gas_limit = !validate_tx; -+ cfg.tx_gas_limit_cap = (!validate_tx).then_some(u64::MAX); - cfg.disable_eip3607 = !validate_tx; - cfg.disable_base_fee = !validate_tx; - cfg.disable_nonce_check = !validate_tx; -@@ -191,6 +192,7 @@ impl> OpStackEvm { - - BlockEnv { - number: U256::from(block.header.number()), -+ slot_num: 0, - beneficiary: block.header.beneficiary(), - timestamp: U256::from(block.header.timestamp()), - gas_limit: block.header.gas_limit(), -@@ -203,7 +205,11 @@ impl> OpStackEvm { - } - - pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSchedule) -> OpSpecId { -- if timestamp >= fork_schedule.isthmus_timestamp { -+ if timestamp >= fork_schedule.karst_timestamp { -+ OpSpecId::KARST -+ } else if timestamp >= fork_schedule.jovian_timestamp { -+ OpSpecId::JOVIAN -+ } else if timestamp >= fork_schedule.isthmus_timestamp { - OpSpecId::ISTHMUS - } else if timestamp >= fork_schedule.holocene_timestamp { - OpSpecId::HOLOCENE -@@ -223,3 +229,30 @@ pub fn get_spec_id_for_block_timestamp(timestamp: u64, fork_schedule: &ForkSched - OpSpecId::default() - } - } -+ -+#[cfg(test)] -+mod phala_fork_tests { -+ use super::*; -+ use crate::config::{Network, NetworkConfig}; -+ -+ #[test] -+ fn selects_phala_forks_at_exact_activation() { -+ let forks = NetworkConfig::from(Network::Phala).chain.forks; -+ assert_eq!( -+ get_spec_id_for_block_timestamp(forks.jovian_timestamp - 1, &forks), -+ OpSpecId::ISTHMUS -+ ); -+ assert_eq!( -+ get_spec_id_for_block_timestamp(forks.jovian_timestamp, &forks), -+ OpSpecId::JOVIAN -+ ); -+ assert_eq!( -+ get_spec_id_for_block_timestamp(forks.karst_timestamp - 1, &forks), -+ OpSpecId::JOVIAN -+ ); -+ assert_eq!( -+ get_spec_id_for_block_timestamp(forks.karst_timestamp, &forks), -+ OpSpecId::KARST -+ ); -+ } -+} -diff --git a/opstack/src/lib.rs b/opstack/src/lib.rs -index d4796d5..044270e 100644 ---- a/opstack/src/lib.rs -+++ b/opstack/src/lib.rs -@@ -30,10 +30,17 @@ pub struct SequencerCommitment { - - impl SequencerCommitment { - pub fn new(data: &[u8]) -> Result { -+ eyre::ensure!( -+ snap::raw::decompress_len(data)? <= 10 * 1024 * 1024, -+ "oversized payload" -+ ); - let mut decoder = snap::raw::Decoder::new(); - let decompressed = decoder.decompress_vec(data)?; - -- let signature = Signature::try_from(&decompressed[..65])?; -+ let signature_bytes = decompressed -+ .get(..65) -+ .ok_or_else(|| eyre::eyre!("truncated signature"))?; -+ let signature = Signature::try_from(signature_bytes)?; - let data = Bytes::from(decompressed[65..].to_vec()); - - Ok(SequencerCommitment { data, signature }) -@@ -56,7 +63,10 @@ impl TryFrom<&SequencerCommitment> for ExecutionPayload { - type Error = eyre::Report; - - fn try_from(value: &SequencerCommitment) -> Result { -- let payload_bytes = &value.data[32..]; -+ let payload_bytes = value -+ .data -+ .get(32..) -+ .ok_or_else(|| eyre::eyre!("truncated payload envelope"))?; - ExecutionPayload::from_ssz_bytes(payload_bytes).map_err(|_| eyre::eyre!("decode failed")) - } - } -diff --git a/opstack/src/server/net/block_handler.rs b/opstack/src/server/net/block_handler.rs -index ef4a825..0dc560c 100644 ---- a/opstack/src/server/net/block_handler.rs -+++ b/opstack/src/server/net/block_handler.rs -@@ -17,7 +17,7 @@ impl BlockHandler { - chain_id, - signer, - commitment_sender: sender, -- blocks_v3_topic: IdentTopic::new(format!("/optimism/{chain_id}/2/blocks")), -+ blocks_v3_topic: IdentTopic::new(format!("/optimism/{chain_id}/3/blocks")), - } - } - -diff --git a/opstack/src/server/net/gossip.rs b/opstack/src/server/net/gossip.rs -index 0a65b63..d4bba8a 100644 ---- a/opstack/src/server/net/gossip.rs -+++ b/opstack/src/server/net/gossip.rs -@@ -7,11 +7,10 @@ use eyre::Result; - use libp2p::{ - futures::StreamExt, - gossipsub::{self, IdentTopic, Message, MessageId}, -- mplex::MplexConfig, - multiaddr::Protocol, - noise, ping, - swarm::{NetworkBehaviour, SwarmBuilder, SwarmEvent}, -- tcp, Multiaddr, PeerId, Swarm, Transport, -+ tcp, yamux, Multiaddr, PeerId, Swarm, Transport, - }; - use libp2p_identity::Keypair; - use sha2::{Digest, Sha256}; -@@ -61,9 +60,28 @@ impl GossipService { - .listen_on(multiaddr) - .map_err(|_| eyre::eyre!("swarm listen failed"))?; - -+ let peers = std::env::var("OP_NODE_P2P_STATIC").unwrap_or_default(); -+ let static_peers = peers -+ .split(',') -+ .filter(|peer| !peer.is_empty()) -+ .map(str::parse::) -+ .collect::, _>>()?; -+ - tokio::spawn(async move { -+ let mut reconnect = tokio::time::interval(Duration::from_secs(5)); - loop { - select! { -+ _ = reconnect.tick() => { -+ for peer in &static_peers { -+ if let Some(Protocol::P2p(id)) = peer.iter().last() { -+ if let Ok(id) = PeerId::from_multihash(id) { -+ swarm.behaviour_mut().gossipsub.add_explicit_peer(&id); -+ if swarm.is_connected(&id) { continue; } -+ } -+ } -+ let _ = swarm.dial(peer.clone()); -+ } -+ }, - peer = peer_recv.recv() => { - if let Some(peer) = peer { - tracing::info!("adding peer"); -@@ -72,8 +90,9 @@ impl GossipService { - } - }, - event = swarm.select_next_some() => { -- if let SwarmEvent::Behaviour(event) = event { -- event.handle(&mut swarm, &self.block_handler); -+ match event { -+ SwarmEvent::Behaviour(event) => event.handle(&mut swarm, &self.block_handler), -+ other => tracing::info!("swarm event: {:?}", other), - } - }, - } -@@ -96,31 +115,17 @@ fn socket_to_multiaddr(socket: SocketAddr) -> Multiaddr { - - /// Computes the message ID of a `gossipsub` message - fn compute_message_id(msg: &Message) -> MessageId { -- let mut decoder = snap::raw::Decoder::new(); -- let id = match decoder.decompress_vec(&msg.data) { -- Ok(data) => { -- let domain_valid_snappy: Vec = vec![0x1, 0x0, 0x0, 0x0]; -- let mut hasher = Sha256::new(); -- hasher.update( -- [domain_valid_snappy.as_slice(), data.as_slice()] -- .concat() -- .as_slice(), -- ); -- hasher.finalize()[..20].to_vec() -- } -- Err(_) => { -- let domain_invalid_snappy: Vec = vec![0x0, 0x0, 0x0, 0x0]; -- let mut hasher = Sha256::new(); -- hasher.update( -- [domain_invalid_snappy.as_slice(), msg.data.as_slice()] -- .concat() -- .as_slice(), -- ); -- hasher.finalize()[..20].to_vec() -- } -- }; -- -- MessageId(id) -+ let decoded = snap::raw::decompress_len(&msg.data) -+ .ok() -+ .filter(|len| *len <= 10 * 1024 * 1024) -+ .and_then(|_| snap::raw::Decoder::new().decompress_vec(&msg.data).ok()); -+ let topic = msg.topic.as_str().as_bytes(); -+ let mut hasher = Sha256::new(); -+ hasher.update([u8::from(decoded.is_some()), 0, 0, 0]); -+ hasher.update((topic.len() as u64).to_le_bytes()); -+ hasher.update(topic); -+ hasher.update(decoded.as_deref().unwrap_or(&msg.data)); -+ MessageId(hasher.finalize()[..20].to_vec()) - } - - /// Creates the libp2p [Swarm] -@@ -128,7 +133,7 @@ fn create_swarm(keypair: Keypair, handler: &BlockHandler) -> Result Date: Tue, 22 Sep 2026 08:20:56 -0700 Subject: [PATCH 03/15] fix(auth-eth): require explicit verified-read policy settings Signed-off-by: Kevin Wang --- dstack/kms/auth-eth/src/main.test.ts | 23 +++++++++++++++++++++++ dstack/kms/auth-eth/src/server.ts | 10 +++++----- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/dstack/kms/auth-eth/src/main.test.ts b/dstack/kms/auth-eth/src/main.test.ts index 842f1e36b..445b27407 100644 --- a/dstack/kms/auth-eth/src/main.test.ts +++ b/dstack/kms/auth-eth/src/main.test.ts @@ -129,3 +129,26 @@ describe('Server', () => { }); }); }); + + +describe('verified-read configuration', () => { + const settings = ['ETH_CHAIN_ID', 'ETH_BLOCK_LAG', 'ETH_MAX_BLOCK_AGE_SECONDS']; + const saved = settings.map(name => process.env[name]); + + afterEach(() => { + settings.forEach((name, index) => { + if (saved[index] === undefined) delete process.env[name]; + else process.env[name] = saved[index]; + }); + }); + + it.each(['ETH_BLOCK_LAG', 'ETH_MAX_BLOCK_AGE_SECONDS'])( + 'requires an explicit %s when verified reads are enabled', async missing => { + Object.assign(process.env, { + ETH_CHAIN_ID: '2035', ETH_BLOCK_LAG: '2', ETH_MAX_BLOCK_AGE_SECONDS: '60', + }); + delete process.env[missing]; + await expect(build()).rejects.toThrow(`invalid ${missing}`); + }, + ); +}); diff --git a/dstack/kms/auth-eth/src/server.ts b/dstack/kms/auth-eth/src/server.ts index ac04c27f3..83eb1fbfe 100644 --- a/dstack/kms/auth-eth/src/server.ts +++ b/dstack/kms/auth-eth/src/server.ts @@ -57,17 +57,17 @@ export async function build(): Promise { const rpcUrl = process.env.ETH_RPC_URL || 'http://localhost:8545'; const kmsContractAddr = process.env.KMS_CONTRACT_ADDR || '0x0000000000000000000000000000000000000000'; const provider = new ethers.JsonRpcProvider(rpcUrl); - const integerSetting = (name: string, fallback: number): number => { - const value = process.env[name] ?? String(fallback); + const integerSetting = (name: string): number => { + const value = process.env[name] ?? ''; if (!/^(0|[1-9][0-9]*)$/.test(value) || !Number.isSafeInteger(Number(value))) { throw new Error(`invalid ${name}`); } return Number(value); }; const readPolicy = process.env.ETH_CHAIN_ID === undefined ? undefined : { - chainId: integerSetting('ETH_CHAIN_ID', 2035), - blockLag: integerSetting('ETH_BLOCK_LAG', 1), - maxAgeSeconds: integerSetting('ETH_MAX_BLOCK_AGE_SECONDS', 60), + chainId: integerSetting('ETH_CHAIN_ID'), + blockLag: integerSetting('ETH_BLOCK_LAG'), + maxAgeSeconds: integerSetting('ETH_MAX_BLOCK_AGE_SECONDS'), }; server.decorate('ethereum', new EthereumBackend(provider, kmsContractAddr, readPolicy)); From c99a719aefa06fa36081d96cab322addacf4443b Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 08:22:10 -0700 Subject: [PATCH 04/15] build(kms): bound verifier build artifacts and isolate preview tags Signed-off-by: Kevin Wang --- .github/workflows/kms-release.yml | 5 +++++ dstack/kms/dstack-app/builder/Dockerfile | 11 +++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/.github/workflows/kms-release.yml b/.github/workflows/kms-release.yml index b883c63fe..ea435cbae 100644 --- a/.github/workflows/kms-release.yml +++ b/.github/workflows/kms-release.yml @@ -31,6 +31,7 @@ jobs: - name: Parse and check version env: DISPATCH_VERSION: ${{ inputs.version }} + PREVIEW: ${{ inputs.preview }} run: | if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ]; then VERSION=$DISPATCH_VERSION @@ -44,6 +45,10 @@ jobs: echo "Version must be a valid Docker tag" >&2 exit 1 fi + if [ "$PREVIEW" = true ] && [[ "$VERSION" != preview-* ]]; then + echo "Preview versions must start with preview-" >&2 + exit 1 + fi echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "RELEASE_TAG=kms-v$VERSION" >> "$GITHUB_ENV" diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index f0d314edb..080696d46 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -31,8 +31,11 @@ WORKDIR /build/helios RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ && git fetch --depth 1 origin 535e59414ff36178b41566af0c7f3e34f71f4fd5 \ && git checkout --detach FETCH_HEAD -RUN cargo test --locked -p helios-opstack --lib \ - && cargo build --locked --release -p helios-cli -p helios-opstack --bins +RUN cargo test --locked --release -p helios-opstack --lib \ + && cargo build --locked --release -p helios-cli -p helios-opstack --bins \ + && install -D target/release/helios /out/helios \ + && install -D target/release/opstack-server /out/opstack-server \ + && rm -rf target FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 AS auth-builder WORKDIR /app @@ -57,8 +60,8 @@ RUN ./pin-packages.sh ./pinned-packages.txt && \ && rm -rf /var/lib/apt/lists/* /var/log/* /var/cache/ldconfig/aux-cache WORKDIR / COPY --from=kms-builder /build/repo/dstack/target/x86_64-unknown-linux-musl/release/dstack-kms /usr/local/bin/dstack-kms -COPY --from=helios-builder /build/helios/target/release/helios /usr/local/bin/helios -COPY --from=helios-builder /build/helios/target/release/opstack-server /usr/local/bin/opstack-server +COPY --from=helios-builder /out/helios /usr/local/bin/helios +COPY --from=helios-builder /out/opstack-server /usr/local/bin/opstack-server COPY --from=auth-builder /usr/local/bin/node /usr/local/bin/node COPY --from=auth-builder /app/dist/ /opt/auth-eth/dist/ COPY --from=auth-builder /app/node_modules/ /opt/auth-eth/node_modules/ From bc3ee124bd79df68dea02876ec934c1c33856bc0 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 08:47:11 -0700 Subject: [PATCH 05/15] chore(kms): pin published Phala preview image for deployment Signed-off-by: Kevin Wang --- .github/workflows/kms-release.yml | 2 +- dstack/kms/README.md | 3 ++- dstack/kms/dstack-app/deploy-to-vmm.sh | 2 +- dstack/kms/dstack-app/docker-compose.yaml | 8 ++++---- dstack/kms/light-client/README.md | 3 ++- 5 files changed, 10 insertions(+), 8 deletions(-) diff --git a/.github/workflows/kms-release.yml b/.github/workflows/kms-release.yml index ea435cbae..1a5ec84e0 100644 --- a/.github/workflows/kms-release.yml +++ b/.github/workflows/kms-release.yml @@ -45,7 +45,7 @@ jobs: echo "Version must be a valid Docker tag" >&2 exit 1 fi - if [ "$PREVIEW" = true ] && [[ "$VERSION" != preview-* ]]; then + if [ "${PREVIEW:-false}" = true ] && [[ "$VERSION" != preview-* ]]; then echo "Preview versions must start with preview-" >&2 exit 1 fi diff --git a/dstack/kms/README.md b/dstack/kms/README.md index 9ededadff..407a57467 100644 --- a/dstack/kms/README.md +++ b/dstack/kms/README.md @@ -106,7 +106,8 @@ The first two components are deployed as an dstack app on dstack in Local-Key-Pr The default [Docker Compose file](dstack-app/docker-compose.yaml) uses the [Phala L2 light client](light-client/README.md). Set `KMS_CONTRACT_ADDR` to a Phala contract compatible with the current auth API, and use a published KMS -image digest; deployment does not build images. Reads use sequencer-authenticated, proof-verified state, +image digest; deployment does not build images. Reads use sequencer-authenticated, +proof-verified state, not L1-settled consensus. See the light-client guide for deployment requirements. The solidity contracts are deployed on an ethereum compatible chain. diff --git a/dstack/kms/dstack-app/deploy-to-vmm.sh b/dstack/kms/dstack-app/deploy-to-vmm.sh index c2880a8c2..2d4e8065b 100755 --- a/dstack/kms/dstack-app/deploy-to-vmm.sh +++ b/dstack/kms/dstack-app/deploy-to-vmm.sh @@ -84,7 +84,7 @@ ADMIN_TOKEN_HASH=$(echo -n "$ADMIN_TOKEN" | sha256sum | cut -d' ' -f1) cp docker-compose.yaml "$COMPOSE_TMP" subvar() { - sed -i "s|\${$1\(:?[^}]*\)\?}|${!1}|g" "$COMPOSE_TMP" + sed -i "s|\${$1\(:[-?][^}]*\)\?}|${!1}|g" "$COMPOSE_TMP" } subvar ETH_MAX_BLOCK_AGE_SECONDS diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index 85b8491c9..59464dd7e 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -4,7 +4,7 @@ services: relay: - image: ${KMS_IMAGE:?Set a published KMS image digest} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} restart: unless-stopped command: - opstack-server @@ -18,7 +18,7 @@ services: security_opt: [no-new-privileges:true] helios: - image: ${KMS_IMAGE:?Set a published KMS image digest} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} restart: unless-stopped command: - helios @@ -33,7 +33,7 @@ services: security_opt: [no-new-privileges:true] auth-api: - image: ${KMS_IMAGE:?Set a published KMS image digest} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} command: [node, /opt/auth-eth/dist/main.js] restart: unless-stopped environment: @@ -49,7 +49,7 @@ services: security_opt: [no-new-privileges:true] kms: - image: ${KMS_IMAGE:?Set a published KMS image digest} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} volumes: - kms-volume:/kms - /var/run/dstack.sock:/var/run/dstack.sock diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 4b4980eea..3da0c0703 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -9,7 +9,8 @@ builder pins the [Helios fork](https://github.com/Dstack-TEE/helios/pull/1) by c Set these variables and render Compose before passing it to dstack: -- `KMS_IMAGE`: the published `ghcr.io/dstack-tee/dstack-kms@sha256:...` digest. +- `KMS_IMAGE`: optional override for the pinned preview image digest. Use a + reviewed release digest for production. - `KMS_CONTRACT_ADDR`: a Phala contract compatible with the current auth API. - `ETH_MAX_BLOCK_AGE_SECONDS`: the maximum accepted age of authenticated state. Choose explicitly based on acceptable revocation delay and observed latency. From 44dde7b1e058bddc8ef4f42188b14516cc6257ad Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 17:41:47 -0700 Subject: [PATCH 06/15] fix(kms): reconnect stalled Phala relay peers Signed-off-by: Kevin Wang --- dstack/kms/dstack-app/builder/Dockerfile | 2 +- dstack/kms/dstack-app/docker-compose.yaml | 1 + dstack/kms/light-client/README.md | 6 ++++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 080696d46..0773c085b 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -29,7 +29,7 @@ RUN echo "${DSTACK_REV}" > /build/.GIT_REV FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS helios-builder WORKDIR /build/helios RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ - && git fetch --depth 1 origin 535e59414ff36178b41566af0c7f3e34f71f4fd5 \ + && git fetch --depth 1 origin 78afc573521ce857d868bdbce6239617fdbc864b \ && git checkout --detach FETCH_HEAD RUN cargo test --locked --release -p helios-opstack --lib \ && cargo build --locked --release -p helios-cli -p helios-opstack --bins \ diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index 59464dd7e..26885a6fa 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -14,6 +14,7 @@ services: - --gossip-address=0.0.0.0:9876 environment: OP_NODE_P2P_STATIC: /ip4/8.229.99.126/tcp/9222/p2p/16Uiu2HAmA7EmxHixJhunY6dZKi7nriyUfBCWP3aBPvGUsQBbigm1 + OP_NODE_P2P_STATIC_IDLE_TIMEOUT: "30" cap_drop: [ALL] security_opt: [no-new-privileges:true] diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 3da0c0703..8783b2222 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -25,8 +25,10 @@ and storage proofs, and code hashes, then executes contract calls locally. The public execution RPC cannot supply an unchecked `eth_call` result. The auth API checks chain ID 2035, uses a two-block proof-availability lag, and rejects stale state, including when a call expires in flight. The lag is not settlement finality -and consumes part of the configured age budget. RPC outages, delayed proofs or -relay stalls fail closed; startup needs enough authenticated blocks in the cache. +and consumes part of the configured age budget. The relay reconnects its static +peer after 30 seconds without a valid signed commitment, before the recommended +60-second freshness limit expires. RPC outages, delayed proofs or relay stalls +still fail closed; startup needs enough authenticated blocks in the cache. ## Trust and compatibility From 01d9c20f8f319b500d6fb19c3d62d43414fff5f1 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 18:06:43 -0700 Subject: [PATCH 07/15] chore(kms): pin relay recovery preview image Signed-off-by: Kevin Wang --- dstack/kms/dstack-app/docker-compose.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index 26885a6fa..0418e63be 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -4,7 +4,7 @@ services: relay: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} restart: unless-stopped command: - opstack-server @@ -19,7 +19,7 @@ services: security_opt: [no-new-privileges:true] helios: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} restart: unless-stopped command: - helios @@ -34,7 +34,7 @@ services: security_opt: [no-new-privileges:true] auth-api: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} command: [node, /opt/auth-eth/dist/main.js] restart: unless-stopped environment: @@ -50,7 +50,7 @@ services: security_opt: [no-new-privileges:true] kms: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:eb1998f99fe87f51c70f1480410f41251f3d2a6c0a4ffef7c75529bcc1e2dc1c} + image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} volumes: - kms-volume:/kms - /var/run/dstack.sock:/var/run/dstack.sock From 0f5f4e3b312f4f5b4c359fb3e7307dbdb57cbf90 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 22:20:03 -0700 Subject: [PATCH 08/15] fix(kms): prove the Phala signer from L1 and drop preview release Pin the Helios fork commit that proves the unsafe signer from Ethereum L1 before accepting blocks and enforces the configured head age. Require an explicit KMS image, drop the preview release workflow and the dev Compose alias, warn when auth-api runs without verified-read settings, and document the secure_time requirement and failure handling. Signed-off-by: Kevin Wang --- .github/workflows/kms-release.yml | 19 +----- dstack/kms/README.md | 6 +- dstack/kms/auth-eth/src/server.ts | 3 + dstack/kms/dstack-app/builder/Dockerfile | 2 +- dstack/kms/dstack-app/builder/README.md | 8 +-- dstack/kms/dstack-app/compose-dev.yaml | 1 - dstack/kms/dstack-app/docker-compose.yaml | 14 ++-- dstack/kms/light-client/README.md | 78 +++++++++++++++-------- 8 files changed, 73 insertions(+), 58 deletions(-) delete mode 120000 dstack/kms/dstack-app/compose-dev.yaml diff --git a/.github/workflows/kms-release.yml b/.github/workflows/kms-release.yml index 1a5ec84e0..eedb5d66a 100644 --- a/.github/workflows/kms-release.yml +++ b/.github/workflows/kms-release.yml @@ -8,13 +8,9 @@ on: workflow_dispatch: inputs: version: - description: Release tag version without kms-v, or a unique preview image tag + description: Existing release tag version without the kms-v prefix required: true type: string - preview: - description: Publish a preview image from the selected ref without a GitHub release - default: false - type: boolean push: tags: - 'kms-v*' @@ -31,7 +27,6 @@ jobs: - name: Parse and check version env: DISPATCH_VERSION: ${{ inputs.version }} - PREVIEW: ${{ inputs.preview }} run: | if [ "$GITHUB_EVENT_NAME" = workflow_dispatch ]; then VERSION=$DISPATCH_VERSION @@ -45,21 +40,16 @@ jobs: echo "Version must be a valid Docker tag" >&2 exit 1 fi - if [ "${PREVIEW:-false}" = true ] && [[ "$VERSION" != preview-* ]]; then - echo "Preview versions must start with preview-" >&2 - exit 1 - fi echo "VERSION=$VERSION" >> "$GITHUB_ENV" echo "RELEASE_TAG=kms-v$VERSION" >> "$GITHUB_ENV" - # Non-preview manual runs rebuild an existing release tag. + # Manual runs rebuild an existing release tag, not the selected branch. - name: Checkout repository uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5 with: - ref: ${{ github.event_name == 'workflow_dispatch' && !inputs.preview && env.RELEASE_TAG || github.sha }} + ref: ${{ github.event_name == 'workflow_dispatch' && env.RELEASE_TAG || github.sha }} - name: Checkout contract dependencies - if: ${{ !inputs.preview }} run: | git submodule update --init --recursive --depth 1 -- \ dstack/kms/auth-eth/lib/forge-std \ @@ -122,17 +112,14 @@ jobs: push-to-registry: true - name: Install Foundry - if: ${{ !inputs.preview }} uses: foundry-rs/foundry-toolchain@908c540300062bd5a7e473851cdb4282204cee09 # v1 - name: Compile contracts with Foundry - if: ${{ !inputs.preview }} run: | cd dstack/kms/auth-eth forge build - name: GitHub Release - if: ${{ !inputs.preview }} uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2 with: tag_name: ${{ env.RELEASE_TAG }} diff --git a/dstack/kms/README.md b/dstack/kms/README.md index 407a57467..aa3f13c91 100644 --- a/dstack/kms/README.md +++ b/dstack/kms/README.md @@ -106,9 +106,9 @@ The first two components are deployed as an dstack app on dstack in Local-Key-Pr The default [Docker Compose file](dstack-app/docker-compose.yaml) uses the [Phala L2 light client](light-client/README.md). Set `KMS_CONTRACT_ADDR` to a Phala contract compatible with the current auth API, and use a published KMS -image digest; deployment does not build images. Reads use sequencer-authenticated, -proof-verified state, -not L1-settled consensus. See the light-client guide for deployment requirements. +image digest; deployment does not build images. Reads use proof-verified state +signed by the L1-registered Phala sequencer, not L1-settled state. See the +light-client guide for deployment requirements. The solidity contracts are deployed on an ethereum compatible chain. diff --git a/dstack/kms/auth-eth/src/server.ts b/dstack/kms/auth-eth/src/server.ts index 83eb1fbfe..290c85e27 100644 --- a/dstack/kms/auth-eth/src/server.ts +++ b/dstack/kms/auth-eth/src/server.ts @@ -69,6 +69,9 @@ export async function build(): Promise { blockLag: integerSetting('ETH_BLOCK_LAG'), maxAgeSeconds: integerSetting('ETH_MAX_BLOCK_AGE_SECONDS'), }; + if (!readPolicy) { + server.log.warn('ETH_CHAIN_ID is unset: authorization reads skip chain ID and freshness checks'); + } server.decorate('ethereum', new EthereumBackend(provider, kmsContractAddr, readPolicy)); const publicRpcEndpoint = (value: string): string => { diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 0773c085b..6c21a3421 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -29,7 +29,7 @@ RUN echo "${DSTACK_REV}" > /build/.GIT_REV FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS helios-builder WORKDIR /build/helios RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ - && git fetch --depth 1 origin 78afc573521ce857d868bdbce6239617fdbc864b \ + && git fetch --depth 1 origin ccf765a076cd696bab31ed61ccdb16f57156c8c3 \ && git checkout --detach FETCH_HEAD RUN cargo test --locked --release -p helios-opstack --lib \ && cargo build --locked --release -p helios-cli -p helios-opstack --bins \ diff --git a/dstack/kms/dstack-app/builder/README.md b/dstack/kms/dstack-app/builder/README.md index e53604264..39815e762 100644 --- a/dstack/kms/dstack-app/builder/README.md +++ b/dstack/kms/dstack-app/builder/README.md @@ -7,8 +7,6 @@ This directory contains the necessary files to build and run the dstack-kms Dock The builder creates a Docker image that includes: - The dstack-kms service compiled from Rust source code - Pure-Rust ACPI measurement support built into the dstack-kms binary -- Helios and its gossip relay from a commit-pinned Dstack-TEE fork -- The auth-eth API and Node runtime, invoked as `node /opt/auth-eth/dist/main.js` ## Prerequisites @@ -42,10 +40,8 @@ Optional environment variables: Publication and OCI export happen only after both package lists pass validation. `NO_CACHE` applies to the validation builds; export then reuses their cached result. -Manual release workflows normally require an existing component release tag. -With `preview=true`, the workflow instead publishes the selected ref under the -provided unique image tag, without creating a GitHub release. Use the resulting -registry digest in Compose; never use a moving preview tag in measured deployment. +Manual release workflows require an existing component release tag and check out +that tag, rather than building the branch selected in the workflow UI. ## Reproducing a released image diff --git a/dstack/kms/dstack-app/compose-dev.yaml b/dstack/kms/dstack-app/compose-dev.yaml deleted file mode 120000 index e736cbabe..000000000 --- a/dstack/kms/dstack-app/compose-dev.yaml +++ /dev/null @@ -1 +0,0 @@ -docker-compose.yaml \ No newline at end of file diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index 0418e63be..d541f94b8 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -2,9 +2,11 @@ # # SPDX-License-Identifier: Apache-2.0 +x-kms-image: &kms-image ${KMS_IMAGE:?Set a published KMS image digest} + services: relay: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} + image: *kms-image restart: unless-stopped command: - opstack-server @@ -19,7 +21,7 @@ services: security_opt: [no-new-privileges:true] helios: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} + image: *kms-image restart: unless-stopped command: - helios @@ -27,6 +29,10 @@ services: - --network=phala - --execution-rpc=https://rpc.phala.network - --consensus-rpc=http://relay:3000 + - --ethereum-consensus-rpc=https://ethereum.operationsolarstorm.org + - --ethereum-execution-rpc=https://eth.drpc.org + - --ethereum-checkpoint=0x3a19ad46aa7391ebfb8fa2690cb8ef56b70d69a2db8e32f4adb63ca7a8697598 + - --max-head-age=${ETH_MAX_BLOCK_AGE_SECONDS:?Set the maximum accepted block age} - --rpc-bind-ip=0.0.0.0 - --rpc-port=8545 depends_on: [relay] @@ -34,7 +40,7 @@ services: security_opt: [no-new-privileges:true] auth-api: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} + image: *kms-image command: [node, /opt/auth-eth/dist/main.js] restart: unless-stopped environment: @@ -50,7 +56,7 @@ services: security_opt: [no-new-privileges:true] kms: - image: ${KMS_IMAGE:-ghcr.io/dstack-tee/dstack-kms@sha256:0bc6c5e7c2a9a7d425d89cf52abdd1e867138ff9178011f57763106c517a1834} + image: *kms-image volumes: - kms-volume:/kms - /var/run/dstack.sock:/var/run/dstack.sock diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 8783b2222..5280bdc00 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -9,37 +9,61 @@ builder pins the [Helios fork](https://github.com/Dstack-TEE/helios/pull/1) by c Set these variables and render Compose before passing it to dstack: -- `KMS_IMAGE`: optional override for the pinned preview image digest. Use a - reviewed release digest for production. +- `KMS_IMAGE`: a reviewed, published KMS image digest. - `KMS_CONTRACT_ADDR`: a Phala contract compatible with the current auth API. -- `ETH_MAX_BLOCK_AGE_SECONDS`: the maximum accepted age of authenticated state. - Choose explicitly based on acceptable revocation delay and observed latency. +- `ETH_MAX_BLOCK_AGE_SECONDS`: the maximum accepted age of authenticated state, + enforced by both Helios and auth-api. It bounds how long revocations can be + missed when the chain feed stalls, e.g. `300` (about 30 Phala blocks). - `IMAGE_DOWNLOAD_URL`: the verified OS image download URL used by KMS. ```sh docker compose -f dstack/kms/dstack-app/docker-compose.yaml config > kms-compose.yaml ``` -Only the KMS port is published. Helios verifies sequencer-signed blocks, account -and storage proofs, and code hashes, then executes contract calls locally. The -public execution RPC cannot supply an unchecked `eth_call` result. The auth API -checks chain ID 2035, uses a two-block proof-availability lag, and rejects stale -state, including when a call expires in flight. The lag is not settlement finality -and consumes part of the configured age budget. The relay reconnects its static -peer after 30 seconds without a valid signed commitment, before the recommended -60-second freshness limit expires. RPC outages, delayed proofs or relay stalls -still fail closed; startup needs enough authenticated blocks in the cache. - -## Trust and compatibility - -This is **sequencer authentication, not L1-settled consensus verification**. -The pinned signer is `0xF63ccBA1929a3eC32248B26c5a22D7C4c9bd3EEC`. -Approve this trust anchor independently; signer rotation requires a reviewed -fork update and image rebuild. The relay is only a data source and cannot forge -signatures. Helios and auth-api must remain inside the measured KMS boundary. - -The observed legacy contract `0xae43c1d4814f665aa4e08ec3589309dff939a049` -permits read-only light-client checks, but lacks `appImplementation()` and uses -older image-measurement semantics. It is deliberately not a production default. -Live light-client contract calls were verified; CVM attestation and key release -are outside that validation scope. +The app must enable `secure_time` (`vmm-cli.py compose --secure-time`, as +`deploy-to-vmm.sh` does). Freshness checks rely on the guest clock; without +NTS-synchronized time, the host can roll the clock back and replay old signed +blocks to hide revocations. `secure_time` is part of `app-compose.json`, so only +approve KMS compose hashes whose `app-compose.json` sets it to `true`. + +Only the KMS port is published. + +## Trust and verification + +Reads use **sequencer-authenticated state, not L1-settled state**: + +1. Helios follows Ethereum mainnet with its sync-committee light client, starting + from the pinned `--ethereum-checkpoint`. +2. Every minute it proves Phala's unsafe signer from the L1 `SystemConfig` + contract (`0xeBf5859b7646ca9cf8A981613569bF28394F2571`) with an account and + storage proof. No Phala block is accepted before the first proof, or once the + latest proof is older than 15 minutes. A signer rotated on L1 therefore + replaces the old key automatically. +3. Each Phala block must carry that signer's signature and a header that hashes to + the signed block hash. Contract calls run locally against account, storage and + code proofs; RPC servers cannot supply an unchecked `eth_call` result. +4. auth-api checks chain ID 2035, reads two blocks behind the head so proofs are + available, pins one block for the whole decision, and rejects state older than + `ETH_MAX_BLOCK_AGE_SECONDS`, including state that expires during the call. + +The remaining trust is in the Phala sequencer key: a sequencer that signs a false +block can forge authorization results, because L1 settlement is not checked. +The relay, Ethereum RPCs and Phala RPC are data sources only. + +## Operations + +Operational failures fail closed with `authorization backend unavailable`: + +- **Feed stalls.** The relay reconnects its static peer after 30 seconds without + a higher signed block. Longer outages exceed the age limit. +- **L1 unavailable.** Without a fresh signer proof, Helios stops accepting blocks. +- **Signer rotation.** Helios follows L1, but the relay filters gossip by the + signer pinned in the fork; update the fork and image. +- **Phala hard forks.** The fork schedule and header layout are pinned. A fork + that changes headers stops Helios with `payload block hash mismatch`; one that + only changes EVM rules can make local calls diverge. Update the fork and image + before every activation. +- **Checkpoint age.** Refresh `--ethereum-checkpoint` (a finalized beacon block + root) with each release; Helios warns once it is older than 14 days. + +Monitor the auth-api `/` endpoint and Helios `failed to advance` logs. From 4de86844b6c68777c013d27c6eed14afd538f57e Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 23:01:47 -0700 Subject: [PATCH 09/15] build(kms): pass SOURCE_DATE_EPOCH to the Helios build Signed-off-by: Kevin Wang --- dstack/kms/dstack-app/builder/Dockerfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 6c21a3421..922bf146e 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -27,6 +27,8 @@ RUN cd repo/dstack && cargo build --release --locked -p dstack-kms --target x86_ RUN echo "${DSTACK_REV}" > /build/.GIT_REV FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506ba959f9685b308a55 AS helios-builder +# Vendored OpenSSL embeds its build date unless SOURCE_DATE_EPOCH is set. +ARG SOURCE_DATE_EPOCH WORKDIR /build/helios RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ && git fetch --depth 1 origin ccf765a076cd696bab31ed61ccdb16f57156c8c3 \ From 1387ed44ba00f65c05cd14ec69f0e78580ca419a Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Tue, 22 Sep 2026 23:34:46 -0700 Subject: [PATCH 10/15] docs(kms): note the light-client startup window Signed-off-by: Kevin Wang --- dstack/kms/light-client/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 5280bdc00..47d89fe23 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -54,6 +54,8 @@ The relay, Ethereum RPCs and Phala RPC are data sources only. Operational failures fail closed with `authorization backend unavailable`: +- **Startup.** Authorization is unavailable until Helios proves the signer and + caches three blocks, typically within a minute of boot. - **Feed stalls.** The relay reconnects its static peer after 30 seconds without a higher signed block. Longer outages exceed the age limit. - **L1 unavailable.** Without a fresh signer proof, Helios stops accepting blocks. From b1d92b167b18f52ec92ff246ecddd42381558bf8 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 24 Sep 2026 02:06:12 -0700 Subject: [PATCH 11/15] style(auth-eth): drop a stray blank line Signed-off-by: Kevin Wang --- dstack/kms/auth-eth/src/main.test.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/dstack/kms/auth-eth/src/main.test.ts b/dstack/kms/auth-eth/src/main.test.ts index 445b27407..5c9b63002 100644 --- a/dstack/kms/auth-eth/src/main.test.ts +++ b/dstack/kms/auth-eth/src/main.test.ts @@ -130,7 +130,6 @@ describe('Server', () => { }); }); - describe('verified-read configuration', () => { const settings = ['ETH_CHAIN_ID', 'ETH_BLOCK_LAG', 'ETH_MAX_BLOCK_AGE_SECONDS']; const saved = settings.map(name => process.env[name]); From 9526dc9d20045ed68105372b70015455d4315888 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 24 Sep 2026 09:46:44 -0700 Subject: [PATCH 12/15] build(kms): pin merged Helios fork and require an NTS-capable guest Pin Dstack-TEE/helios master (645af74), which merges the Phala light client with CI fixes and simplified static-peer reconnects. Default the KMS deploy script to dstack-0.6.0, since secure_time hangs on older guests (#745), and point the on-chain governance guide at the light client settings. Signed-off-by: Kevin Wang --- docs/onchain-governance.md | 6 ++++-- dstack/kms/dstack-app/builder/Dockerfile | 2 +- dstack/kms/dstack-app/deploy-to-vmm.sh | 2 +- dstack/kms/light-client/README.md | 4 +++- 4 files changed, 9 insertions(+), 5 deletions(-) diff --git a/docs/onchain-governance.md b/docs/onchain-governance.md index 777675399..2fca42cb3 100644 --- a/docs/onchain-governance.md +++ b/docs/onchain-governance.md @@ -51,13 +51,15 @@ export RPC_URL="https://eth-sepolia.g.alchemy.com/v2/" ## Configure KMS for On-Chain Auth -The KMS CVM includes an auth-api service that connects to your DstackKms contract. Configure it via environment variables in the KMS CVM: +The KMS CVM includes an auth-api service that connects to your DstackKms contract. The default [KMS Compose](../dstack/kms/dstack-app/docker-compose.yaml) reads Phala L2 through a proof-verifying light client; see the [light-client guide](../dstack/kms/light-client/README.md). Configure it via environment variables in the KMS CVM: ```bash KMS_CONTRACT_ADDR= -ETH_RPC_URL= +ETH_MAX_BLOCK_AGE_SECONDS=300 ``` +For other chains, run auth-api with `ETH_RPC_URL` pointing to your RPC endpoint. + The auth-api validates boot requests against the smart contract. See [Deployment Guide](./deployment.md#2-deploy-kms-as-cvm) for complete setup instructions. ## Whitelist OS Image diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 922bf146e..6bae63174 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -31,7 +31,7 @@ FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506 ARG SOURCE_DATE_EPOCH WORKDIR /build/helios RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ - && git fetch --depth 1 origin ccf765a076cd696bab31ed61ccdb16f57156c8c3 \ + && git fetch --depth 1 origin 645af74b627e167db22d2b52f2ccc4512e4e80d9 \ && git checkout --detach FETCH_HEAD RUN cargo test --locked --release -p helios-opstack --lib \ && cargo build --locked --release -p helios-cli -p helios-opstack --bins \ diff --git a/dstack/kms/dstack-app/deploy-to-vmm.sh b/dstack/kms/dstack-app/deploy-to-vmm.sh index 2d4e8065b..65a5c8887 100755 --- a/dstack/kms/dstack-app/deploy-to-vmm.sh +++ b/dstack/kms/dstack-app/deploy-to-vmm.sh @@ -43,7 +43,7 @@ VERIFY_IMAGE=true # ETH_MAX_BLOCK_AGE_SECONDS= # The dstack OS image name to use for the KMS app -OS_IMAGE=dstack-0.5.5 +OS_IMAGE=dstack-0.6.0 # The dstack KMS image name to use for the KMS app # KMS_IMAGE=ghcr.io/dstack-tee/dstack-kms@sha256: diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 47d89fe23..5a66baf84 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -3,7 +3,7 @@ The default [KMS Compose](../dstack-app/docker-compose.yaml) runs KMS, auth-api, Helios and its gossip relay inside the same CVM. All services use one published KMS image; no deployment-time builds or source downloads are required. The KMS -builder pins the [Helios fork](https://github.com/Dstack-TEE/helios/pull/1) by commit. +builder pins the [Helios fork](https://github.com/Dstack-TEE/helios) by commit. ## Deployment @@ -25,6 +25,8 @@ The app must enable `secure_time` (`vmm-cli.py compose --secure-time`, as NTS-synchronized time, the host can roll the clock back and replay old signed blocks to hide revocations. `secure_time` is part of `app-compose.json`, so only approve KMS compose hashes whose `app-compose.json` sets it to `true`. +Secure time needs a guest image whose chrony supports NTS (dstack 0.6.0 or +later); older images hang at boot ([#745](https://github.com/Dstack-TEE/dstack/issues/745)). Only the KMS port is published. From c851765d059b57826c03046e49817ebe0a3dbd13 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Thu, 24 Sep 2026 09:53:16 -0700 Subject: [PATCH 13/15] feat(kms): read light-client data sources from user config The Phala RPC, Ethereum RPCs and relay peer are untrusted: every response is checked against signatures or proofs. Read them from the unmeasured user config so operators can replace an endpoint without a new compose hash and on-chain approval. Only the user config file is mounted, and only fixed keys are read, so the host cannot inject other variables. The Ethereum checkpoint stays in Compose because it anchors verification. Signed-off-by: Kevin Wang --- dstack/kms/dstack-app/deploy-to-vmm.sh | 7 +++- dstack/kms/dstack-app/docker-compose.yaml | 42 ++++++++++++++--------- dstack/kms/light-client/README.md | 22 ++++++++++++ 3 files changed, 53 insertions(+), 18 deletions(-) diff --git a/dstack/kms/dstack-app/deploy-to-vmm.sh b/dstack/kms/dstack-app/deploy-to-vmm.sh index 65a5c8887..31b500e47 100755 --- a/dstack/kms/dstack-app/deploy-to-vmm.sh +++ b/dstack/kms/dstack-app/deploy-to-vmm.sh @@ -45,6 +45,10 @@ VERIFY_IMAGE=true # The dstack OS image name to use for the KMS app OS_IMAGE=dstack-0.6.0 +# Optional user config overriding the light-client data sources +# (see ../light-client/README.md) +# USER_CONFIG=./kms-user-config + # The dstack KMS image name to use for the KMS app # KMS_IMAGE=ghcr.io/dstack-tee/dstack-kms@sha256: @@ -131,4 +135,5 @@ $CLI deploy \ --port tcp:"$GUEST_AGENT_ADDR":8090 \ --vcpu 8 \ --memory 8G \ - --disk 50G + --disk 50G \ + --user-config "${USER_CONFIG:-<(echo '# KMS light-client data sources')}" diff --git a/dstack/kms/dstack-app/docker-compose.yaml b/dstack/kms/dstack-app/docker-compose.yaml index d541f94b8..5c3a62dcc 100644 --- a/dstack/kms/dstack-app/docker-compose.yaml +++ b/dstack/kms/dstack-app/docker-compose.yaml @@ -3,19 +3,25 @@ # SPDX-License-Identifier: Apache-2.0 x-kms-image: &kms-image ${KMS_IMAGE:?Set a published KMS image digest} +# Only this file is mounted: the rest of host-shared holds the app keys. +x-user-config: &user-config /dstack/.host-shared/.user-config:/etc/user-config:ro services: + # Data sources are untrusted, so the host may override them through the + # unmeasured user config without changing the compose hash. relay: image: *kms-image restart: unless-stopped + volumes: [*user-config] command: - - opstack-server - - --network=phala - - --execution-rpc=https://rpc.phala.network - - --server-address=0.0.0.0:3000 - - --gossip-address=0.0.0.0:9876 + - sh + - -c + - | + cfg() { v=$$(sed -n "s/^$$1=//p" /etc/user-config 2>/dev/null | tail -n 1); echo "$${v:-$$2}"; } + export OP_NODE_P2P_STATIC="$$(cfg OP_NODE_P2P_STATIC /ip4/8.229.99.126/tcp/9222/p2p/16Uiu2HAmA7EmxHixJhunY6dZKi7nriyUfBCWP3aBPvGUsQBbigm1)" + exec opstack-server --network=phala --execution-rpc=https://rpc.phala.network \ + --server-address=0.0.0.0:3000 --gossip-address=0.0.0.0:9876 environment: - OP_NODE_P2P_STATIC: /ip4/8.229.99.126/tcp/9222/p2p/16Uiu2HAmA7EmxHixJhunY6dZKi7nriyUfBCWP3aBPvGUsQBbigm1 OP_NODE_P2P_STATIC_IDLE_TIMEOUT: "30" cap_drop: [ALL] security_opt: [no-new-privileges:true] @@ -23,18 +29,20 @@ services: helios: image: *kms-image restart: unless-stopped + volumes: [*user-config] command: - - helios - - opstack - - --network=phala - - --execution-rpc=https://rpc.phala.network - - --consensus-rpc=http://relay:3000 - - --ethereum-consensus-rpc=https://ethereum.operationsolarstorm.org - - --ethereum-execution-rpc=https://eth.drpc.org - - --ethereum-checkpoint=0x3a19ad46aa7391ebfb8fa2690cb8ef56b70d69a2db8e32f4adb63ca7a8697598 - - --max-head-age=${ETH_MAX_BLOCK_AGE_SECONDS:?Set the maximum accepted block age} - - --rpc-bind-ip=0.0.0.0 - - --rpc-port=8545 + - sh + - -c + - | + cfg() { v=$$(sed -n "s/^$$1=//p" /etc/user-config 2>/dev/null | tail -n 1); echo "$${v:-$$2}"; } + exec helios opstack --network=phala \ + --execution-rpc="$$(cfg PHALA_RPC_URL https://rpc.phala.network)" \ + --consensus-rpc=http://relay:3000 \ + --ethereum-consensus-rpc="$$(cfg ETHEREUM_CONSENSUS_RPC https://ethereum.operationsolarstorm.org)" \ + --ethereum-execution-rpc="$$(cfg ETHEREUM_EXECUTION_RPC https://eth.drpc.org)" \ + --ethereum-checkpoint=0x3a19ad46aa7391ebfb8fa2690cb8ef56b70d69a2db8e32f4adb63ca7a8697598 \ + --max-head-age=${ETH_MAX_BLOCK_AGE_SECONDS:?Set the maximum accepted block age} \ + --rpc-bind-ip=0.0.0.0 --rpc-port=8545 depends_on: [relay] cap_drop: [ALL] security_opt: [no-new-privileges:true] diff --git a/dstack/kms/light-client/README.md b/dstack/kms/light-client/README.md index 5a66baf84..0e7f2d72c 100644 --- a/dstack/kms/light-client/README.md +++ b/dstack/kms/light-client/README.md @@ -30,6 +30,28 @@ later); older images hang at boot ([#745](https://github.com/Dstack-TEE/dstack/i Only the KMS port is published. +### Data sources + +Helios and the relay read their endpoints from the unmeasured +[user config](../../../docs/vmm-cli-user-guide.md), so they can be changed +without a new compose hash. The file holds `KEY=value` lines; other keys are +ignored and unset keys use the defaults in the Compose file: + +| Key | Default | +|-----|---------| +| `PHALA_RPC_URL` | `https://rpc.phala.network` | +| `ETHEREUM_CONSENSUS_RPC` | `https://ethereum.operationsolarstorm.org` | +| `ETHEREUM_EXECUTION_RPC` | `https://eth.drpc.org` | +| `OP_NODE_P2P_STATIC` | Phala's public op-node | + +Set it with `USER_CONFIG` in `deploy-to-vmm.sh`, or later with +`vmm-cli.py update-user-config ` and a restart. Always deploy +with a non-empty user config (a comment is enough): the VMM skips empty ones, +and Docker then mounts a directory in its place. These sources are +untrusted: every response is checked against signatures or proofs, so a +malicious endpoint can only stall authorization. The Ethereum checkpoint stays +in Compose because it anchors that verification. + ## Trust and verification Reads use **sequencer-authenticated state, not L1-settled state**: From e0e4c33b0efe6bc9cce1a0d16edbcde898ba8f0f Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 26 Oct 2026 21:06:55 -0700 Subject: [PATCH 14/15] build(kms): build auth-api on Node 22.23.4 for the emSign distrust The KMS image copies the auth-builder node binary into the runtime, so auth-api ran on Node 20.20.2, which still trusts the compromised eMudhra/emSign intermediates. Use the Node 22.23.4 image that #1466 already moved the compose auth-api to. Signed-off-by: Kevin Wang --- dstack/kms/dstack-app/builder/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 6bae63174..794183bcc 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -39,7 +39,7 @@ RUN cargo test --locked --release -p helios-opstack --lib \ && install -D target/release/opstack-server /out/opstack-server \ && rm -rf target -FROM node:20-bookworm-slim@sha256:2cf067cfed83d5ea958367df9f966191a942351a2df77d6f0193e162b5febfc0 AS auth-builder +FROM node:22.23.4-bookworm-slim@sha256:de170b39439eb0e5a3444ad7d60b015614a381e10fecae2e30ec9e735122f38d AS auth-builder WORKDIR /app COPY --from=kms-builder /build/repo/dstack/kms/auth-eth/package*.json ./ RUN npm ci --ignore-scripts From eb59916bd6d3a993c06ca0af7d395dbc23a69720 Mon Sep 17 00:00:00 2001 From: Kevin Wang Date: Mon, 26 Oct 2026 21:13:36 -0700 Subject: [PATCH 15/15] build(kms): pin the Helios fork with the emSign distrust Dstack-TEE/helios#2 moves the lock to rustls-webpki 0.103.16 and webpki-roots 1.0.10, so Helios rustls clients reject chains through the compromised eMudhra/emSign intermediates. Signed-off-by: Kevin Wang --- dstack/kms/dstack-app/builder/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dstack/kms/dstack-app/builder/Dockerfile b/dstack/kms/dstack-app/builder/Dockerfile index 794183bcc..854e2936b 100644 --- a/dstack/kms/dstack-app/builder/Dockerfile +++ b/dstack/kms/dstack-app/builder/Dockerfile @@ -31,7 +31,7 @@ FROM rust:1.94.1-bookworm@sha256:6ae102bdbf528294bc79ad6e1fae682f6f7c2a6e6621506 ARG SOURCE_DATE_EPOCH WORKDIR /build/helios RUN git init && git remote add origin https://github.com/Dstack-TEE/helios.git \ - && git fetch --depth 1 origin 645af74b627e167db22d2b52f2ccc4512e4e80d9 \ + && git fetch --depth 1 origin ff3820aba2f218c75fae6b542b6067580e9f4ec4 \ && git checkout --detach FETCH_HEAD RUN cargo test --locked --release -p helios-opstack --lib \ && cargo build --locked --release -p helios-cli -p helios-opstack --bins \