Skip to content

Commit b05fe4a

Browse files
gballetweilzkmfjl
authored
cmd/keeper: add the keeper zkvm guest program (#32543)
Keeper is a zmvm guest program that runs the block transition. It relies on the zkvm maker implementing `getInput`. For now, we only provide a single implementation for the 'ziren' VM. Why keeper? In the _Mass Effect_ lore, the keepers are animals (?) who maintain the citadel. Nothing is known from them, and attempts at tampering with them have failed, as they self-destruct upon inquiry. They have a secret, nefarious purpose that is only revealed later in the game series, don't want any spoilers so I didn't dig deeper. All in all, a good metaphor for zkvms. --------- Co-authored-by: weilzkm <[email protected]> Co-authored-by: Felix Lange <[email protected]>
1 parent 89f364f commit b05fe4a

File tree

12 files changed

+573
-7
lines changed

12 files changed

+573
-7
lines changed

build/ci.go

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -322,9 +322,9 @@ func doTest(cmdline []string) {
322322
gotest.Args = append(gotest.Args, "-short")
323323
}
324324

325-
packages := []string{"./..."}
326-
if len(flag.CommandLine.Args()) > 0 {
327-
packages = flag.CommandLine.Args()
325+
packages := flag.CommandLine.Args()
326+
if len(packages) == 0 {
327+
packages = workspacePackagePatterns()
328328
}
329329
gotest.Args = append(gotest.Args, packages...)
330330
build.MustRun(gotest)
@@ -364,7 +364,7 @@ func doCheckGenerate() {
364364
protocPath = downloadProtoc(*cachedir)
365365
protocGenGoPath = downloadProtocGenGo(*cachedir)
366366
)
367-
c := tc.Go("generate", "./...")
367+
c := tc.Go("generate", workspacePackagePatterns()...)
368368
pathList := []string{filepath.Join(protocPath, "bin"), protocGenGoPath, os.Getenv("PATH")}
369369
c.Env = append(c.Env, "PATH="+strings.Join(pathList, string(os.PathListSeparator)))
370370
build.MustRun(c)
@@ -424,9 +424,16 @@ func doLint(cmdline []string) {
424424
cachedir = flag.String("cachedir", "./build/cache", "directory for caching golangci-lint binary.")
425425
)
426426
flag.CommandLine.Parse(cmdline)
427-
packages := []string{"./..."}
428-
if len(flag.CommandLine.Args()) > 0 {
429-
packages = flag.CommandLine.Args()
427+
428+
packages := flag.CommandLine.Args()
429+
if len(packages) == 0 {
430+
// Get module directories in workspace.
431+
packages = []string{"./..."}
432+
modules := workspaceModules()
433+
for _, m := range modules[1:] {
434+
dir := strings.TrimPrefix(m, modules[0])
435+
packages = append(packages, "."+dir+"/...")
436+
}
430437
}
431438

432439
linter := downloadLinter(*cachedir)
@@ -1169,3 +1176,31 @@ func doSanityCheck() {
11691176
csdb := download.MustLoadChecksums("build/checksums.txt")
11701177
csdb.DownloadAndVerifyAll()
11711178
}
1179+
1180+
// workspaceModules lists the module paths in the current work.
1181+
func workspaceModules() []string {
1182+
listing, err := new(build.GoToolchain).Go("list", "-m").Output()
1183+
if err != nil {
1184+
log.Fatalf("go list failed:", err)
1185+
}
1186+
var modules []string
1187+
for _, m := range bytes.Split(listing, []byte("\n")) {
1188+
m = bytes.TrimSpace(m)
1189+
if len(m) > 0 {
1190+
modules = append(modules, string(m))
1191+
}
1192+
}
1193+
if len(modules) == 0 {
1194+
panic("no modules found")
1195+
}
1196+
return modules
1197+
}
1198+
1199+
func workspacePackagePatterns() []string {
1200+
modules := workspaceModules()
1201+
patterns := make([]string, len(modules))
1202+
for i, m := range modules {
1203+
patterns[i] = m + "/..."
1204+
}
1205+
return patterns
1206+
}

cmd/keeper/1192c3_block.rlp

1.34 KB
Binary file not shown.

cmd/keeper/1192c3_witness.rlp

39.9 KB
Binary file not shown.

cmd/keeper/README.md

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# Keeper - geth as a zkvm guest
2+
3+
Keeper command is a specialized tool for validating stateless execution of Ethereum blocks. It's designed to run as a zkvm guest.
4+
5+
## Overview
6+
7+
The keeper reads an RLP-encoded payload containing:
8+
- A block to execute
9+
- A witness with the necessary state data
10+
- A chainID
11+
12+
It then executes the block statelessly and validates that the computed state root and receipt root match the values in the block header.
13+
14+
## Building Keeper
15+
16+
The keeper uses build tags to compile platform-specific input methods and chain configurations:
17+
18+
### Example Implementation
19+
20+
See `getpayload_example.go` for a complete example with embedded Hoodi block data:
21+
22+
```bash
23+
# Build example with different chain configurations
24+
go build -tags "example" ./cmd/keeper
25+
```
26+
27+
### Ziren zkVM Implementation
28+
29+
Build for the Ziren zkVM platform, which is a MIPS ISA-based zkvm:
30+
31+
```bash
32+
GOOS=linux GOARCH=mipsle GOMIPS=softfloat go build -tags "ziren" ./cmd/keeper
33+
```
34+
35+
As an example runner, refer to https://gist.github.com/gballet/7b669a99eb3ab2b593324e3a76abd23d
36+
37+
## Creating a Custom Platform Implementation
38+
39+
To add support for a new platform (e.g., "myplatform"), create a new file with the appropriate build tag:
40+
41+
### 1. Create `getinput_myplatform.go`
42+
43+
```go
44+
//go:build myplatform
45+
46+
package main
47+
48+
import (
49+
"github.com/ethereum/go-ethereum/params"
50+
// ... other imports as needed
51+
)
52+
53+
// getInput returns the RLP-encoded payload
54+
func getInput() []byte {
55+
// Your platform-specific code to retrieve the RLP-encoded payload
56+
// This might read from:
57+
// - Memory-mapped I/O
58+
// - Hardware registers
59+
// - Serial port
60+
// - Network interface
61+
// - File system
62+
63+
// The payload must be RLP-encoded and contain:
64+
// - Block with transactions
65+
// - Witness with parent headers and state data
66+
67+
return encodedPayload
68+
}
69+
```

cmd/keeper/chainconfig.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
package main
18+
19+
import (
20+
"fmt"
21+
22+
"github.com/ethereum/go-ethereum/params"
23+
)
24+
25+
// getChainConfig returns the appropriate chain configuration based on the chainID.
26+
// Returns an error for unsupported chain IDs.
27+
func getChainConfig(chainID uint64) (*params.ChainConfig, error) {
28+
switch chainID {
29+
case 0, params.MainnetChainConfig.ChainID.Uint64():
30+
return params.MainnetChainConfig, nil
31+
case params.SepoliaChainConfig.ChainID.Uint64():
32+
return params.SepoliaChainConfig, nil
33+
case params.HoodiChainConfig.ChainID.Uint64():
34+
return params.HoodiChainConfig, nil
35+
default:
36+
return nil, fmt.Errorf("unsupported chain ID: %d", chainID)
37+
}
38+
}

cmd/keeper/getpayload_example.go

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
//go:build example
18+
19+
package main
20+
21+
import (
22+
_ "embed"
23+
"fmt"
24+
"os"
25+
26+
"github.com/ethereum/go-ethereum/common/hexutil"
27+
"github.com/ethereum/go-ethereum/core/stateless"
28+
"github.com/ethereum/go-ethereum/core/types"
29+
"github.com/ethereum/go-ethereum/params"
30+
"github.com/ethereum/go-ethereum/rlp"
31+
)
32+
33+
// ExtWitness is a witness RLP encoding for transferring across clients.
34+
// This is taken from PR #32216 until it's merged.
35+
// It contains block headers, contract codes, state nodes, and storage keys
36+
// required for stateless execution verification.
37+
type ExtWitness struct {
38+
Headers []*types.Header `json:"headers"`
39+
Codes []hexutil.Bytes `json:"codes"`
40+
State []hexutil.Bytes `json:"state"`
41+
Keys []hexutil.Bytes `json:"keys"`
42+
}
43+
44+
// This is taken from PR #32216 until it's merged
45+
// fromExtWitness converts the consensus witness format into our internal one.
46+
func fromExtWitness(ext *ExtWitness) (*stateless.Witness, error) {
47+
w := &stateless.Witness{}
48+
w.Headers = ext.Headers
49+
50+
w.Codes = make(map[string]struct{}, len(ext.Codes))
51+
for _, code := range ext.Codes {
52+
w.Codes[string(code)] = struct{}{}
53+
}
54+
w.State = make(map[string]struct{}, len(ext.State))
55+
for _, node := range ext.State {
56+
w.State[string(node)] = struct{}{}
57+
}
58+
return w, nil
59+
}
60+
61+
//go:embed 1192c3_witness.rlp
62+
var witnessRlp []byte
63+
64+
//go:embed 1192c3_block.rlp
65+
var blockRlp []byte
66+
67+
// getInput is a platform-specific function that will recover the input payload
68+
// and returns it as a slice. It is expected to be an RLP-encoded Payload structure
69+
// that contains the witness and the block.
70+
// This is a demo version, that is intended to run on a regular computer, so what
71+
// it does is embed a small Hoodi block, encodes the Payload structure containing
72+
// the block and its witness as RLP, and returns the encoding.
73+
func getInput() []byte {
74+
var block types.Block
75+
err := rlp.DecodeBytes(blockRlp, &block)
76+
if err != nil {
77+
panic(err)
78+
}
79+
80+
var extwitness ExtWitness
81+
err = rlp.DecodeBytes(witnessRlp, &extwitness)
82+
if err != nil {
83+
panic(err)
84+
}
85+
witness, err := fromExtWitness(&extwitness)
86+
if err != nil {
87+
panic(err)
88+
}
89+
90+
payload := Payload{
91+
ChainID: params.HoodiChainConfig.ChainID.Uint64(),
92+
Block: &block,
93+
Witness: witness,
94+
}
95+
96+
encoded, err := rlp.EncodeToBytes(payload)
97+
if err != nil {
98+
fmt.Fprintf(os.Stderr, "failed to encode payload: %v\n", err)
99+
os.Exit(20)
100+
}
101+
return encoded
102+
}

cmd/keeper/getpayload_ziren.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2025 The go-ethereum Authors
2+
// This file is part of the go-ethereum library.
3+
//
4+
// The go-ethereum library is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Lesser General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// The go-ethereum library is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Lesser General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Lesser General Public License
15+
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
16+
17+
//go:build ziren
18+
19+
package main
20+
21+
import (
22+
zkruntime "github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime"
23+
)
24+
25+
// getInput reads the input payload from the zkVM runtime environment.
26+
// The zkVM host provides the RLP-encoded Payload structure containing
27+
// the block and witness data through the runtime's input mechanism.
28+
func getInput() []byte {
29+
input := zkruntime.Read[[]byte]()
30+
return input
31+
}

cmd/keeper/go.mod

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module github.com/ethereum/go-ethereum/cmd/keeper
2+
3+
go 1.24.0
4+
5+
require (
6+
github.com/ethereum/go-ethereum v0.0.0-00010101000000-000000000000
7+
github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime v0.0.0-20250915074013-fbc07aa2c6f5
8+
)
9+
10+
require (
11+
github.com/StackExchange/wmi v1.2.1 // indirect
12+
github.com/VictoriaMetrics/fastcache v1.12.2 // indirect
13+
github.com/bits-and-blooms/bitset v1.20.0 // indirect
14+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
15+
github.com/consensys/gnark-crypto v0.18.0 // indirect
16+
github.com/crate-crypto/go-eth-kzg v1.3.0 // indirect
17+
github.com/crate-crypto/go-ipa v0.0.0-20240724233137-53bbb0ceb27a // indirect
18+
github.com/deckarep/golang-set/v2 v2.6.0 // indirect
19+
github.com/decred/dcrd/dcrec/secp256k1/v4 v4.0.1 // indirect
20+
github.com/emicklei/dot v1.6.2 // indirect
21+
github.com/ethereum/c-kzg-4844/v2 v2.1.0 // indirect
22+
github.com/ethereum/go-verkle v0.2.2 // indirect
23+
github.com/ferranbt/fastssz v0.1.4 // indirect
24+
github.com/go-ole/go-ole v1.3.0 // indirect
25+
github.com/gofrs/flock v0.12.1 // indirect
26+
github.com/golang/snappy v0.0.5-0.20220116011046-fa5810519dcb // indirect
27+
github.com/holiman/bloomfilter/v2 v2.0.3 // indirect
28+
github.com/holiman/uint256 v1.3.2 // indirect
29+
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
30+
github.com/mattn/go-runewidth v0.0.13 // indirect
31+
github.com/minio/sha256-simd v1.0.0 // indirect
32+
github.com/mitchellh/mapstructure v1.4.1 // indirect
33+
github.com/olekukonko/tablewriter v0.0.5 // indirect
34+
github.com/rivo/uniseg v0.2.0 // indirect
35+
github.com/shirou/gopsutil v3.21.4-0.20210419000835-c7a38de76ee5+incompatible // indirect
36+
github.com/supranational/blst v0.3.14 // indirect
37+
github.com/tklauser/go-sysconf v0.3.12 // indirect
38+
github.com/tklauser/numcpus v0.6.1 // indirect
39+
golang.org/x/crypto v0.36.0 // indirect
40+
golang.org/x/sync v0.12.0 // indirect
41+
golang.org/x/sys v0.31.0 // indirect
42+
gopkg.in/yaml.v2 v2.4.0 // indirect
43+
)
44+
45+
replace (
46+
github.com/ethereum/go-ethereum => ../../
47+
github.com/zkMIPS/zkMIPS/crates/go-runtime/zkm_runtime => github.com/weilzkm/zkMIPS/crates/go-runtime/zkvm_runtime v0.0.0-20250915074013-fbc07aa2c6f5
48+
)

0 commit comments

Comments
 (0)