Skip to content
hemilabsPublic

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

zkminer

Multi-zkVM, multi-GPU-vendor ZK proof generator for Hemi's ZK proving marketplace (HemiProve).

zkminer watches the marketplace for open proving jobs, estimates whether each one is worth taking, claims the ones that are, generates the proof on a local GPU, and submits it on-chain. Proving runs in separate worker processes — one per GPU — so a wedged or OOM-killed prover can't take the miner down with it.

Backends: RISC Zero (CUDA and ROCm), SP1, OpenVM.

Requirements

Rust 1.93.0 (pinned in rust-toolchain.toml)
RISC Zero toolchain rzup, see below — required to build, and to prove
CUDA toolkit with nvcc (12.8–13.x tested) for NVIDIA workers
ROCm 6.4+ with hipcc for AMD workers
Build deps build-essential cmake pkg-config protobuf-compiler libclang-dev (+ libhipcub-dev librocthrust-dev for ROCm)
Foundry cast — only for the example job submitters under examples/

RISC Zero toolchain (easy to miss)

Two rzup components are needed, and they fail at different stages:

curl -sSL https://risczero.com/install | bash
export PATH="$HOME/.risc0/bin:$PATH"
rzup install rust            # builds the guest ELFs — without it, cargo build fails
rzup install risc0-groth16   # the Groth16 wrap — without it, every PROOF fails

risc0-groth16 is the nastier of the two: everything compiles and benchmarks fine without it, and only real proofs fail — reported by the host as the unhelpful Worker risc0 process died (EOF).

Build

cargo build --release -p zkminer-cli        # the CLI; no GPU toolchain needed

GPU prover workers are separate binaries, built with the toolchain for their vendor:

# NVIDIA. NVCC/HIPCC must be set explicitly: sppark's build.rs resolves them with
# which::which(), not PATH. Set the other to "off" so it isn't auto-detected.
env NVCC=/usr/local/cuda/bin/nvcc HIPCC=off \
    NVCC_APPEND_FLAGS="--expt-relaxed-constexpr -gencode arch=compute_120,code=sm_120" \
    cargo build --release -p zkminer-prove-risc0 --features cuda \
                --bin zkminer-prove-risc0-cuda --target-dir target/release-cuda

# AMD. Use a SEPARATE --target-dir from the CUDA build to avoid lock contention.
env HIPCC=/opt/rocm/bin/hipcc NVCC=off RISC0_HIP_ARCH="gfx1100" \
    cargo build --release -p zkminer-prove-risc0 --features rocm \
                --bin zkminer-prove-risc0-rocm --target-dir target/release-rocm

Never set RISC0_SKIP_BUILD=1 — it produces empty guest ELF stubs, and every benchmark then silently reports zero work.

make wraps portable Docker builds: make cli | cuda | rocm | sp1 | openvm | all | test.

Install the workers

The miner looks for worker binaries in ~/.zkminer/provers/ under exact names. The directory is hand-managed: a missing binary means that backend is silently skipped, with no error.

~/.zkminer/provers/
├── zkminer-prove-risc0-cuda
├── zkminer-prove-risc0-rocm
├── zkminer-prove-sp1
└── zkminer-prove-openvm

If a worker needs environment the miner's own process won't have (a systemd unit or cron job does not source ~/.bashrc), install a small wrapper script under the expected name that sets it up and then execs the real binary — exec preserves the PID and process group the dispatcher relies on.

Configure

Copy or write ~/.zkminer/config.toml:

[chain]
rpc_url  = "https://testnet.rpc.hemi.network/rpc"
chain_id = 743111                   # Hemi testnet (43111 is mainnet)

[contracts]
hemi_prove          = "0x..."       # router (diamond proxy)
hemi_prove_staking  = "0x..."
hemi_prove_registry = "0x..."
hemi_token          = "0x..."

[wallet]
key_source  = "env"                 # reads $ZKMINER_PRIVATE_KEY
key_env_var = "ZKMINER_PRIVATE_KEY"

[prover]
max_concurrent_proofs = 0           # 0 = one proof per detected GPU
skip_benchmark_gate   = true

zkminer init will scaffold a config, but verify the contract addresses it writes against the deployment you intend to use before running against a live market — a wrong address set produces a miner that starts cleanly, sees no jobs, and has no stake.

Run

export ZKMINER_PRIVATE_KEY=0x...

zkminer status                # balance, stake, collateral, job stats
zkminer benchmark             # measure real per-GPU proving throughput
zkminer benchmark --calibrate # also measure segment size (po2); saves benchmarks.json
zkminer run                   # interactive TUI
zkminer -v run --headless     # headless; -v shows per-job claim/skip reasoning

Verbosity is -v/-vv before the subcommand (RUST_LOG is ignored). Logs go to stderr, so zkminer benchmark --json on stdout stays machine-readable.

Two things worth knowing:

  • Don't run benchmark while the miner is running. Both spawn a worker per GPU and the GPU lock is per-process, so they will double-book VRAM.
  • If the miner sits idle with jobs available, suspect collateral. Available collateral is staked − locked, and a claimed job that passes its deadline locks collateral permanently. This surfaces as a throttled warning at default verbosity.

benchmark --calibrate measures proving time at each segment size and only overrides the SDK's own choice when one size wins by a decisive margin — differences inside measurement noise are discarded rather than acted on.

Layout

crate role
zkminer-cli binary entry point: status, benchmark, run, init
zkminer-chain chain client, auction math, staking, monitor, tx/nonce layer, RPC metering
zkminer-contracts sol!() bindings — the source of truth for the on-chain ABI
zkminer-prover worker dispatcher, ELF registry, benchmarks, guest programs
zkminer-prover-protocol worker↔dispatcher IPC types
zkminer-prove-risc0 / -sp1 / -openvm standalone GPU worker binaries
zkminer-strategy profitability evaluation, cost model, timing prediction
zkminer-tui ratatui interface
zkminer-config TOML config and wallet key loading

RISC Zero and SP1 are patched to the hemilabs forks (GPU + AMD support) via [patch.crates-io], pinned by Cargo.lock. Don't run cargo update — those two are referenced by moving branch heads, so the lockfile is what holds the build on validated commits. (sppark is pinned by immutable rev and is not exposed to that drift.)

Testing

cargo test                                    # unit + integration
ZKVM_ALLOW_SKIP=1 cargo test                  # skip tests needing GPUs/guest ELFs

# comprehensive zkVM suite — dispatches to the installed GPU workers
cargo test -p zkminer-prover --features risc0,sp1 --test comprehensive_zkvm_test \
    -- --test-threads=1 --nocapture

The zkVM suite is fail-closed: a missing worker or guest ELF fails the test rather than skipping, so a half-installed box can't look green. Set ZKVM_ALLOW_SKIP=1 to skip instead. Run it single-threaded to avoid GPU contention, and log to a file rather than piping to tail, which buffers and can hide a hang.

Examples

examples/ holds job submitters for testing against a live market. They read their signing key from the environment (SUBMITTER_KEY, REQUESTER_KEY) and refuse to run without it — never commit a funded key. The forge-based submitters additionally need CONTRACTS_DIR pointing at a HemiProve Foundry checkout; testnet_submitter_cast.sh needs only cast.

License

MIT — see LICENSE.

About

No description, website, or topics provided.

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages