proving v2: RISC Zero 3.0.6 workspace (guest, pinned image id, host with the SP1 host's CLI and results keys, Metal and CUDA features, pin script)
The same shard statement bytes as the SP1 guest, committed as the journal. RISC Zero's accelerated crates pinned by tag (k256 0.13.4, crypto-bigint 0.5.5, tiny-keccak 2.0.2, sha2 0.10.9 and 0.11.0). Pinned image id 0x9ae0f416ee43e9ea8908c555d424fe23e3592677ffc42a763476623d0eb5cf72 under elf/manifest-r0.json. This Mac has no metal compiler (Command Line Tools, no Xcode) and risc0 3.0.6 selects no Metal HAL in any circuit, so a local risc0-build-kernel patch writes an empty metallib behind RISC0_SKIP_METAL_KERNELS. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
efaa58d0f6
commit
d6e84cb440
23 changed files with 12828 additions and 0 deletions
3
proving/igneum-prove-r0/.gitignore
vendored
Normal file
3
proving/igneum-prove-r0/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
target/
|
||||
*.bin
|
||||
!elf/igneum-prove-r0-guest.bin
|
||||
6583
proving/igneum-prove-r0/Cargo.lock
generated
Normal file
6583
proving/igneum-prove-r0/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
33
proving/igneum-prove-r0/Cargo.toml
Normal file
33
proving/igneum-prove-r0/Cargo.toml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
# Igneum proving, proof system version 2 (6 October 2026): RISC Zero 3.0.6 proving the SAME shard statement as the
|
||||
# SP1 guest in proving/igneum-prove (igneum-prove-core, unchanged, pulled by path), with Metal on Apple silicon and
|
||||
# CUDA on NVIDIA behind cargo features. The guest lives in methods/guest with its own lock file and RISC Zero's
|
||||
# accelerated crate patches; it is a pinned build artefact under elf/ (pin-guest.sh), never built by a normal host
|
||||
# build. Design: docs/analysis/proving-methods.md route D; plan: docs/plans/proving-v2-risc0.md.
|
||||
[workspace]
|
||||
resolver = "2"
|
||||
members = ["host", "methods"]
|
||||
exclude = ["methods/guest"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "ISC"
|
||||
|
||||
[workspace.dependencies]
|
||||
igneum-prove-core = { path = "../igneum-prove/core" }
|
||||
risc0-zkvm = { version = "=3.0.6", default-features = false }
|
||||
risc0-build = { version = "=3.0.6" }
|
||||
alloy-primitives = { version = "=1.7.3", default-features = false, features = ["std", "rlp", "serde", "k256"] }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0"
|
||||
bincode = "=1.3.3"
|
||||
hex = "0.4.3"
|
||||
anyhow = "1.0"
|
||||
sha2 = "0.10"
|
||||
|
||||
# This Mac has the Command Line Tools and no Xcode, so no `metal` shader compiler; risc0-sys compiles the zkp Metal
|
||||
# kernels on EVERY macOS build of the prover (`xcrun metal`) although no circuit in 3.0.6 selects the Metal HAL (the
|
||||
# arm is commented out in rv32im, recursion and keccak). The local copy of risc0-build-kernel 2.0.1 (patches/, one
|
||||
# change in `compile_metal`) writes an empty metallib when RISC0_SKIP_METAL_KERNELS is set. Unset, it is upstream.
|
||||
[patch.crates-io]
|
||||
risc0-build-kernel = { path = "patches/risc0-build-kernel" }
|
||||
34
proving/igneum-prove-r0/bench/mac-2026-10-06.sh
Executable file
34
proving/igneum-prove-r0/bench/mac-2026-10-06.sh
Executable file
|
|
@ -0,0 +1,34 @@
|
|||
#!/usr/bin/env bash
|
||||
# The Mac measurement of proof system 2 (6 October 2026; docs/bench-log.md "6 October 2026, RISC Zero as proof
|
||||
# system 2 on the Mac (Metal)"): for each fixture and po2, one `--mode compressed --shard 0` run of the host under
|
||||
# `/usr/bin/time -l` (wall, user, sys, maximum resident set), then three `--mode verify` runs of its receipt with
|
||||
# RAYON_NUM_THREADS=1 (one core). Run through the MEASURE lock with the miner paused:
|
||||
# /Users/joshm/Projects/igneum/tools/lock/with-lock.sh measure proving/igneum-prove-r0/bench/mac-2026-10-06.sh <host binary> <tag> <po2...>
|
||||
# Fixtures: FIXTURES env (default: the v1 shard fees-v1-shards2.json and the empty shard block-58927-empty-reward.json).
|
||||
set -uo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
HOST="${1:?host binary}"; TAG="${2:?tag, e.g. metal}"; shift 2
|
||||
PO2S="${*:-20}"
|
||||
FIX="$HERE/../../fixtures"
|
||||
FIXTURES="${FIXTURES:-fees-v1-shards2.json block-58927-empty-reward.json}"
|
||||
OUT="$HERE/out-2026-10-06"; mkdir -p "$OUT"
|
||||
for f in $FIXTURES; do
|
||||
name="${f%.json}"
|
||||
for po2 in $PO2S; do
|
||||
run="$name-$TAG-po2$po2"
|
||||
echo "=== $run start $(date -u +%FT%TZ)"
|
||||
/usr/bin/time -l "$HOST" "$FIX/$f" --mode compressed --shard 0 --po2 "$po2" --out "$OUT/$run.json" > "$OUT/$run.log" 2> "$OUT/$run.time"
|
||||
echo "exit $?" >> "$OUT/$run.time"
|
||||
grep -h "RESULT execute\|RESULT compressed" "$OUT/$run.log"; grep -h "real\|maximum resident\|^exit" "$OUT/$run.time"
|
||||
proof=$(python3 -c "import json;print(json.load(open('$OUT/$run.json'))['proof_file'])" 2>/dev/null)
|
||||
stmt=$(python3 -c "import json;print(json.load(open('$OUT/$run.json'))['statement'])" 2>/dev/null)
|
||||
if [ -n "$proof" ]; then
|
||||
for k in 1 2 3; do
|
||||
RAYON_NUM_THREADS=1 /usr/bin/time -l "$HOST" --mode verify --proof "$proof" --statement "$stmt" > "$OUT/$run.verify$k.log" 2> "$OUT/$run.verify$k.time"
|
||||
echo "verify $k exit $?" >> "$OUT/$run.verify$k.time"
|
||||
grep -h "RESULT verify" "$OUT/$run.verify$k.log" | cut -c1-120; grep -h "real\|maximum resident\|^verify" "$OUT/$run.verify$k.time"
|
||||
done
|
||||
fi
|
||||
echo "=== $run end $(date -u +%FT%TZ)"
|
||||
done
|
||||
done
|
||||
BIN
proving/igneum-prove-r0/elf/igneum-prove-r0-guest.bin
Normal file
BIN
proving/igneum-prove-r0/elf/igneum-prove-r0-guest.bin
Normal file
Binary file not shown.
41
proving/igneum-prove-r0/elf/manifest-r0.json
Normal file
41
proving/igneum-prove-r0/elf/manifest-r0.json
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
{
|
||||
"build": {
|
||||
"core_sources": "proving/igneum-prove/core/src/*.rs (sorted, concatenated)",
|
||||
"core_sources_sha256": "0x2bdc089364126f31ff2e26ee55e1c50faa2177d9c0cdbee5be42d00609cb9e5f",
|
||||
"docker": false,
|
||||
"guest_cargo_lock_sha256": "0xc56a9e6a1b4ef697596440ceeb9e0bbe11655fbb63aae7b38c6966612016d6ab",
|
||||
"guest_cargo_toml_sha256": "0x1cb0a3c02fa429725738d0e1ec09442379c6d4c99542b74348fd91d638148886",
|
||||
"guest_crate": "igneum-prove-r0-guest (methods/guest, its own workspace and Cargo.lock)",
|
||||
"guest_sources_sha256": "0xc6c34a98bafdd3c45c1807399657e4bed5843fb9fec14c591df29982bc092a9a",
|
||||
"note": "built with the rzup toolchain on the pinning machine (no docker); a build on another machine must reproduce this image id or the pin is re-done here",
|
||||
"patches": [
|
||||
"k256 = { git = \"https://github.com/risc0/RustCrypto-elliptic-curves\", tag = \"k256/v0.13.4-risczero.1\" }",
|
||||
"crypto-bigint = { git = \"https://github.com/risc0/RustCrypto-crypto-bigint\", tag = \"v0.5.5-risczero.0\" }",
|
||||
"tiny-keccak = { git = \"https://github.com/risc0/tiny-keccak\", tag = \"tiny-keccak/v2.0.2-risczero.0\" }",
|
||||
"sha2 = { git = \"https://github.com/risc0/RustCrypto-hashes\", tag = \"sha2-v0.10.9-risczero.0\" }",
|
||||
"sha2-011 = { git = \"https://github.com/risc0/RustCrypto-hashes\", tag = \"sha2-v0.11.0-risczero.0\", package = \"sha2\" }"
|
||||
],
|
||||
"risc0_build_features": [
|
||||
"unstable"
|
||||
],
|
||||
"risc0_zkvm_guest_features": [
|
||||
"std",
|
||||
"unstable"
|
||||
],
|
||||
"rzup": "Installed components:; cargo-risczero; * 3.0.6; r0vm; * 3.0.6; rust; * 1.97.0; rzup home: /Users/joshm/.risc0"
|
||||
},
|
||||
"format": "igneum-prove-r0-manifest-v1",
|
||||
"guest": {
|
||||
"elf": "igneum-prove-r0-guest.bin",
|
||||
"elf_bytes": 3667564,
|
||||
"elf_kind": "risc0-build ProgramBinary (user ELF + RISC Zero v1compat kernel); the image id is over this binary",
|
||||
"elf_sha256": "0x2be67ce3e5a240104156712a127e460378926e8d4f30f277272314a49d70c98f",
|
||||
"image_id": "0x9ae0f416ee43e9ea8908c555d424fe23e3592677ffc42a763476623d0eb5cf72",
|
||||
"user_elf_bytes": 3635140,
|
||||
"user_elf_sha256": "0x89a84da0e4bbf0e859f92944202ab54569fad79e089eca3228af09e1ab8bbb68"
|
||||
},
|
||||
"pinned_at": "2026-10-06T11:54:47Z",
|
||||
"pinned_on": "Darwin MacBook-Pro.local 25.6.0 Darwin Kernel Version 25.6.0: Fri Jul 31 19:19:08 PDT 2026; root:xnu-12377.161.14~5/RELEASE_ARM64_T6050 arm64",
|
||||
"proof_system": 2,
|
||||
"risc0_crate_version": "3.0.6"
|
||||
}
|
||||
40
proving/igneum-prove-r0/host/Cargo.toml
Normal file
40
proving/igneum-prove-r0/host/Cargo.toml
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
[package]
|
||||
name = "igneum-prove-r0-host"
|
||||
description = "RISC Zero host, proof system version 2: loads a block fixture, cuts and witnesses its shards, runs one shard natively, in the executor (cycles) and proves it to a succinct receipt on the CPU, Metal or CUDA; verifies receipts against the pinned image id; the same CLI shape and results keys as the SP1 host"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[dependencies]
|
||||
igneum-prove-core.workspace = true
|
||||
risc0-zkvm = { workspace = true, features = ["prove", "std"] }
|
||||
alloy-primitives.workspace = true
|
||||
alloy-trie = { version = "=0.9.8", default-features = false, features = ["std"] }
|
||||
alloy-rlp = { version = "=0.3.16", default-features = false }
|
||||
igneum-evm-types = { path = "../../../vendor/igneum-node-exec/igneum/evm-types" }
|
||||
bincode.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
anyhow.workspace = true
|
||||
hex.workspace = true
|
||||
sha2.workspace = true
|
||||
|
||||
[[bin]]
|
||||
name = "igneum-prove-r0-host"
|
||||
path = "src/main.rs"
|
||||
|
||||
[build-dependencies]
|
||||
sha2.workspace = true
|
||||
hex.workspace = true
|
||||
|
||||
[features]
|
||||
default = []
|
||||
# The Metal prover (Apple silicon): the full STARK on the GPU, unified memory.
|
||||
metal = ["risc0-zkvm/metal"]
|
||||
# The CUDA prover (NVIDIA, Linux x86_64 or WSL2).
|
||||
cuda = ["risc0-zkvm/cuda"]
|
||||
|
||||
# Does not embed the pinned files, so it builds before elf/ exists.
|
||||
[[bin]]
|
||||
name = "igneum-prove-r0-pin"
|
||||
path = "src/bin/pin.rs"
|
||||
27
proving/igneum-prove-r0/host/build.rs
Normal file
27
proving/igneum-prove-r0/host/build.rs
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
//! Stamps the host with a hash of the native sources it was built from (`IGNEUM_PROVE_R0_SOURCES`, printed in
|
||||
//! the host's first line; the stale-build class of 5 October 2026): the SP1 core (`../igneum-prove/core/src`)
|
||||
//! and `host/src`, the same recipe as proving/igneum-prove/host/build.rs.
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::Path;
|
||||
|
||||
fn main() {
|
||||
let host = Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
let core = host.join("../../igneum-prove/core/src");
|
||||
let mut files: Vec<std::path::PathBuf> = Vec::new();
|
||||
for dir in [core, host.join("src")] {
|
||||
for entry in std::fs::read_dir(&dir).expect("source dir") {
|
||||
let path = entry.expect("entry").path();
|
||||
if path.extension().map(|e| e == "rs").unwrap_or(false) {
|
||||
files.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
files.sort();
|
||||
let mut h = Sha256::new();
|
||||
for f in &files {
|
||||
println!("cargo:rerun-if-changed={}", f.display());
|
||||
h.update(std::fs::read(f).expect("read source"));
|
||||
}
|
||||
println!("cargo:rerun-if-changed=build.rs");
|
||||
println!("cargo:rustc-env=IGNEUM_PROVE_R0_SOURCES={}", &hex::encode(h.finalize())[..16]);
|
||||
}
|
||||
121
proving/igneum-prove-r0/host/src/bin/pin.rs
Normal file
121
proving/igneum-prove-r0/host/src/bin/pin.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
//! igneum-prove-r0-pin: turns a fresh guest build into the pinned artefacts the host embeds (host/src/pinned.rs).
|
||||
//!
|
||||
//! Usage: igneum-prove-r0-pin [--from <guest program binary>] [--out <elf dir>]
|
||||
//! default --from: the file named igneum-prove-r0-guest.bin under target/riscv-guest (what
|
||||
//! `IGNEUM_BUILD_GUESTS=1 cargo build -p igneum-prove-r0-methods` leaves behind: risc0-build's
|
||||
//! ProgramBinary, the user ELF combined with RISC Zero's v1compat kernel; the image id is computed over
|
||||
//! this binary, not over the bare user ELF next to it), default --out: elf/.
|
||||
//!
|
||||
//! Reads the binary, computes its image id (risc0_zkvm::compute_image_id, the same function the verifier uses),
|
||||
//! writes elf/igneum-prove-r0-guest.bin and elf/manifest-r0.json with the SHA-256, the image id and the build
|
||||
//! inputs (crate version, rzup components, the guest's Cargo.lock and sources, the core sources, the patches).
|
||||
//! This binary does not include the pinned files, so it builds before elf/ exists.
|
||||
|
||||
use anyhow::{Context, Result};
|
||||
use sha2::{Digest, Sha256};
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
fn sha256_hex(bytes: &[u8]) -> String {
|
||||
format!("0x{}", hex::encode(Sha256::digest(bytes)))
|
||||
}
|
||||
|
||||
fn now_utc() -> String {
|
||||
let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
|
||||
let (h, m, s) = ((secs / 3600) % 24, (secs / 60) % 60, secs % 60);
|
||||
let z = (secs / 86400) as i64 + 719468;
|
||||
let era = z.div_euclid(146097);
|
||||
let doe = z - era * 146097;
|
||||
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||
let y = yoe + era * 400;
|
||||
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||
let mp = (5 * doy + 2) / 153;
|
||||
let d = doy - (153 * mp + 2) / 5 + 1;
|
||||
let mo = if mp < 10 { mp + 3 } else { mp - 9 };
|
||||
let y = if mo <= 2 { y + 1 } else { y };
|
||||
format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}Z")
|
||||
}
|
||||
|
||||
fn find_guest(dir: &Path) -> Option<PathBuf> {
|
||||
let mut hit = None;
|
||||
for entry in std::fs::read_dir(dir).ok()?.flatten() {
|
||||
let p = entry.path();
|
||||
if p.is_dir() {
|
||||
if let Some(h) = find_guest(&p) {
|
||||
hit = Some(h);
|
||||
}
|
||||
} else if p.file_name().map(|n| n == "igneum-prove-r0-guest.bin").unwrap_or(false) {
|
||||
hit = Some(p);
|
||||
}
|
||||
}
|
||||
hit
|
||||
}
|
||||
|
||||
/// SHA-256 over the sorted files of a directory with one extension (the IGNEUM_PROVE_SOURCES recipe).
|
||||
fn dir_hash(dir: &Path, ext: &str) -> String {
|
||||
let mut files: Vec<PathBuf> = std::fs::read_dir(dir).map(|r| r.flatten().map(|e| e.path()).filter(|p| p.extension().map(|e| e == ext).unwrap_or(false)).collect()).unwrap_or_default();
|
||||
files.sort();
|
||||
let mut h = Sha256::new();
|
||||
for f in &files {
|
||||
h.update(std::fs::read(f).unwrap_or_default());
|
||||
}
|
||||
format!("0x{}", hex::encode(h.finalize()))
|
||||
}
|
||||
|
||||
fn cmd(program: &str, args: &[&str]) -> String {
|
||||
std::process::Command::new(program).args(args).output().ok().map(|o| String::from_utf8_lossy(&o.stdout).trim().to_string()).unwrap_or_else(|| "unknown".into())
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let arg = |name: &str| args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).cloned();
|
||||
let here = PathBuf::from(env!("CARGO_MANIFEST_DIR")).parent().map(|p| p.to_path_buf()).unwrap_or_default();
|
||||
let from = match arg("--from") {
|
||||
Some(p) => PathBuf::from(p),
|
||||
None => find_guest(&here.join("target/riscv-guest")).context("no igneum-prove-r0-guest.bin under target/riscv-guest (build it first: IGNEUM_BUILD_GUESTS=1 cargo build -p igneum-prove-r0-methods)")?,
|
||||
};
|
||||
let out = arg("--out").map(PathBuf::from).unwrap_or_else(|| here.join("elf"));
|
||||
std::fs::create_dir_all(&out)?;
|
||||
let elf = std::fs::read(&from).with_context(|| format!("read {}", from.display()))?;
|
||||
let id = risc0_zkvm::compute_image_id(&elf).map_err(|e| anyhow::anyhow!("compute_image_id: {e}"))?;
|
||||
let id_hex = format!("0x{}", hex::encode(id.as_bytes()));
|
||||
std::fs::write(out.join("igneum-prove-r0-guest.bin"), &elf)?;
|
||||
let user_elf = std::fs::read(from.with_extension("")).ok();
|
||||
println!("RESULT pin igneum-prove-r0-guest: {} bytes, sha256 {}, image id {id_hex} (from {}; user ELF {} bytes)", elf.len(), sha256_hex(&elf), from.display(), user_elf.as_ref().map(|e| e.len()).unwrap_or(0));
|
||||
let guest_dir = here.join("methods/guest");
|
||||
let patches: Vec<String> = std::fs::read_to_string(guest_dir.join("Cargo.toml")).unwrap_or_default().lines().skip_while(|l| !l.starts_with("[patch.crates-io]")).skip(1).take_while(|l| !l.starts_with('[')).filter(|l| l.contains("git = ")).map(|l| l.trim().to_string()).collect();
|
||||
let rzup_home = std::env::var("HOME").map(|h| format!("{h}/.risc0/bin/rzup")).unwrap_or_else(|_| "rzup".into());
|
||||
let rzup_show = cmd(&rzup_home, &["show"]).lines().map(|l| l.trim().to_string()).filter(|l| !l.is_empty() && !l.starts_with('-')).collect::<Vec<_>>().join("; ");
|
||||
let manifest = serde_json::json!({
|
||||
"format": "igneum-prove-r0-manifest-v1",
|
||||
"proof_system": 2,
|
||||
"risc0_crate_version": "3.0.6",
|
||||
"pinned_at": now_utc(),
|
||||
"pinned_on": cmd("uname", &["-a"]),
|
||||
"guest": {
|
||||
"elf": "igneum-prove-r0-guest.bin",
|
||||
"elf_kind": "risc0-build ProgramBinary (user ELF + RISC Zero v1compat kernel); the image id is over this binary",
|
||||
"user_elf_bytes": user_elf.as_ref().map(|e| e.len()).unwrap_or(0),
|
||||
"user_elf_sha256": user_elf.as_ref().map(|e| sha256_hex(e)).unwrap_or_default(),
|
||||
"elf_bytes": elf.len(),
|
||||
"elf_sha256": sha256_hex(&elf),
|
||||
"image_id": id_hex,
|
||||
},
|
||||
"build": {
|
||||
"guest_crate": "igneum-prove-r0-guest (methods/guest, its own workspace and Cargo.lock)",
|
||||
"guest_cargo_lock_sha256": sha256_hex(&std::fs::read(guest_dir.join("Cargo.lock")).unwrap_or_default()),
|
||||
"guest_cargo_toml_sha256": sha256_hex(&std::fs::read(guest_dir.join("Cargo.toml")).unwrap_or_default()),
|
||||
"guest_sources_sha256": dir_hash(&guest_dir.join("src"), "rs"),
|
||||
"core_sources_sha256": dir_hash(&here.join("../igneum-prove/core/src"), "rs"),
|
||||
"core_sources": "proving/igneum-prove/core/src/*.rs (sorted, concatenated)",
|
||||
"patches": patches,
|
||||
"risc0_zkvm_guest_features": ["std", "unstable"],
|
||||
"risc0_build_features": ["unstable"],
|
||||
"rzup": rzup_show,
|
||||
"docker": false,
|
||||
"note": "built with the rzup toolchain on the pinning machine (no docker); a build on another machine must reproduce this image id or the pin is re-done here",
|
||||
},
|
||||
});
|
||||
std::fs::write(out.join("manifest-r0.json"), format!("{}\n", serde_json::to_string_pretty(&manifest)?))?;
|
||||
println!("RESULT pin: manifest written to {}; rebuild the host so it embeds these files", out.join("manifest-r0.json").display());
|
||||
Ok(())
|
||||
}
|
||||
478
proving/igneum-prove-r0/host/src/main.rs
Normal file
478
proving/igneum-prove-r0/host/src/main.rs
Normal file
|
|
@ -0,0 +1,478 @@
|
|||
//! igneum-prove-r0-host: proof system version 2 (RISC Zero 3.0.6) proving one shard of an Igneum chain block
|
||||
//! fixture, the SAME shard statement bytes as the SP1 host (design 5.1; docs/analysis/proving-methods.md route D).
|
||||
//!
|
||||
//! Usage: igneum-prove-r0-host <fixture.json> [--mode native|execute|compressed] [--shard N] [--po2 N]
|
||||
//! [--budget <test pgas>] [--prover 0x<payout address>] [--out <results.json>]
|
||||
//! igneum-prove-r0-host --mode verify --proof <file> --statement 0x<keccak of the journal>
|
||||
//! igneum-prove-r0-host --mode id
|
||||
//!
|
||||
//! Modes build on each other, as in the SP1 host. `native` cuts the block into shards at `S_p`, builds every
|
||||
//! shard's witness, runs every shard statement natively, checks that the shards chain and sum to the block, and
|
||||
//! shows that a tampered witness fails. `execute` runs every shard guest in the RISC Zero executor (user cycles
|
||||
//! and segments, no proof). `compressed` proves one shard (the `--shard` index, default 0) to a SUCCINCT receipt
|
||||
//! (the constant-size STARK, RISC Zero's analogue of SP1's compressed proof), verified against the pinned image
|
||||
//! id, and writes the receipt and a results JSON with the SP1 host's keys plus `proof_system: 2` and `image_id`.
|
||||
//! `--po2 N` sets the segment limit (2^N cycles a segment; RISC Zero's default is 20). The backend is a build
|
||||
//! feature: `cpu` (none), `metal` (macOS; in risc0 3.0.6 every circuit's Metal arm is commented out, so the
|
||||
//! prover runs its CPU HAL on Apple silicon and the feature only records the intent), `cuda` (NVIDIA).
|
||||
//! Every stage prints a `STAGE ... start` line and one `RESULT` line with a UTC timestamp (ledger P20).
|
||||
//!
|
||||
//! The guest is pinned (`pinned.rs`): the host embeds the ELF and manifest under `elf/`, checks the hash at every
|
||||
//! start, recomputes the image id in the prove and verify modes. `--mode id` prints the pinned id in the SP1
|
||||
//! host's wording (the node's parser). `--mode verify` needs no prover: the receipt is verified against the
|
||||
//! pinned image id and keccak256(journal) is compared with the statement; exit 0 = verified, 3 = not.
|
||||
|
||||
mod pinned;
|
||||
|
||||
use alloy_primitives::{Address, B256};
|
||||
use anyhow::{anyhow, bail, Context, Result};
|
||||
use igneum_prove_core::agg::{self, AggInput};
|
||||
use igneum_prove_core::shard::{build_shards, shard_statement, BuiltShard, ShardInput, ShardOutput};
|
||||
use igneum_prove_core::Fixture;
|
||||
use risc0_zkvm::{default_executor, default_prover, ExecutorEnv, ProverOpts, Receipt};
|
||||
use sha2::Digest as _;
|
||||
use std::time::Instant;
|
||||
|
||||
pub const RISC0_CRATE_VERSION: &str = "3.0.6";
|
||||
pub const PROOF_SYSTEM: u64 = 2;
|
||||
pub const DEFAULT_PO2: u32 = 20;
|
||||
|
||||
fn now() -> String {
|
||||
let secs = std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map(|d| d.as_secs()).unwrap_or(0);
|
||||
let (h, m, s) = ((secs / 3600) % 24, (secs / 60) % 60, secs % 60);
|
||||
let z = (secs / 86400) as i64 + 719468;
|
||||
let era = z.div_euclid(146097);
|
||||
let doe = z - era * 146097;
|
||||
let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
|
||||
let y = yoe + era * 400;
|
||||
let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
|
||||
let mp = (5 * doy + 2) / 153;
|
||||
let d = doy - (153 * mp + 2) / 5 + 1;
|
||||
let mo = if mp < 10 { mp + 3 } else { mp - 9 };
|
||||
let y = if mo <= 2 { y + 1 } else { y };
|
||||
format!("{y:04}-{mo:02}-{d:02}T{h:02}:{m:02}:{s:02}Z")
|
||||
}
|
||||
|
||||
fn stage(name: &str) {
|
||||
println!("STAGE {name} start {}", now());
|
||||
}
|
||||
|
||||
/// The build's backend. `metal` is honest about 3.0.6: no circuit selects the Metal HAL there.
|
||||
fn backend() -> &'static str {
|
||||
if cfg!(feature = "cuda") {
|
||||
"cuda"
|
||||
} else if cfg!(feature = "metal") {
|
||||
"metal (risc0 3.0.6: CPU HAL, the Metal arm is commented out in every circuit)"
|
||||
} else {
|
||||
"cpu"
|
||||
}
|
||||
}
|
||||
|
||||
fn backend_key() -> &'static str {
|
||||
if cfg!(feature = "cuda") {
|
||||
"cuda"
|
||||
} else if cfg!(feature = "metal") {
|
||||
"metal"
|
||||
} else {
|
||||
"cpu"
|
||||
}
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
let arg = |name: &str| args.iter().position(|a| a == name).and_then(|i| args.get(i + 1)).cloned();
|
||||
let mode = arg("--mode").unwrap_or_else(|| "compressed".into());
|
||||
let pinned = pinned::Pinned::load()?;
|
||||
if mode == "id" {
|
||||
println!("RESULT id: {}", pinned.describe());
|
||||
return Ok(());
|
||||
}
|
||||
if mode == "verify" {
|
||||
return run_verify(&pinned, &arg("--proof").context("--proof <file>")?, &arg("--statement").context("--statement 0x<keccak of the journal>")?);
|
||||
}
|
||||
let prover: Address = arg("--prover").map(|s| s.parse()).transpose()?.unwrap_or_else(|| Address::from_slice(&[0x19; 20]));
|
||||
let out_path = arg("--out");
|
||||
let po2: u32 = arg("--po2").map(|s| s.parse()).transpose()?.unwrap_or(DEFAULT_PO2);
|
||||
let path = args.get(1).filter(|a| !a.starts_with("--")).context("usage: igneum-prove-r0-host <fixture.json> [--mode native|execute|compressed] [--shard N] [--po2 N] [--budget <test pgas>] [--prover 0x..] [--out results.json]; --mode verify --proof <file> --statement 0x..; --mode id")?;
|
||||
let shard_index: usize = arg("--shard").map(|s| s.parse()).transpose()?.unwrap_or(0);
|
||||
let test_budget: Option<u64> = arg("--budget").map(|s| s.parse()).transpose()?;
|
||||
let fixture: Fixture = serde_json::from_str(&std::fs::read_to_string(path).with_context(|| format!("read {path}"))?)?;
|
||||
if fixture.format != igneum_prove_core::fixture::FORMAT {
|
||||
bail!("fixture format {} is not {} (regenerate with igneum-prove-export)", fixture.format, igneum_prove_core::fixture::FORMAT);
|
||||
}
|
||||
let block = &fixture.block;
|
||||
let txs: usize = block.blocks.iter().map(|b| b.txs.len()).sum();
|
||||
let fee_set = block.fees.at(block.env.daa_score);
|
||||
if let Some(b) = test_budget {
|
||||
println!("TEST CUT: the block is re-planned at a budget of {b} pgas (the fixture's plan at {} is not compared)", fixture.plan.shard_budget);
|
||||
}
|
||||
if test_budget.is_none() && fixture.plan.consensus && fixture.plan.shard_budget != fee_set.shard_proving_gas_budget {
|
||||
bail!("the fixture's plan says consensus budget {} but the fee set at DAA score {} ({}) has S_p {}; regenerate with igneum-prove-export", fixture.plan.shard_budget, block.env.daa_score, fee_set.name(), fee_set.shard_proving_gas_budget);
|
||||
}
|
||||
println!(
|
||||
"igneum-prove-r0-host sources {}: fixture {path}: chain {} block {} ({}) at DAA score {}, {txs} transactions in {} including blocks, {} accounts in the pre-state, plan {} shard(s) at S_p = {} pgas{}; fee schedule {}: this block meters with {} (intrinsic {} pgas, B_p {}); proof system {PROOF_SYSTEM} (RISC Zero {RISC0_CRATE_VERSION}), backend {}, segment po2 {po2}; prover payout {prover}; {}",
|
||||
env!("IGNEUM_PROVE_R0_SOURCES"),
|
||||
block.chain_id,
|
||||
block.env.number,
|
||||
block.env.hash,
|
||||
block.env.daa_score,
|
||||
block.blocks.len(),
|
||||
block.pre_state.len(),
|
||||
fixture.plan.shards.len(),
|
||||
fixture.plan.shard_budget,
|
||||
if fixture.plan.consensus { "" } else { " (TEST CUT below the consensus budget)" },
|
||||
block.fees.describe(),
|
||||
fee_set.name(),
|
||||
fee_set.pgas.intrinsic_pgas_per_tx,
|
||||
fee_set.block_proving_gas_limit,
|
||||
backend(),
|
||||
now()
|
||||
);
|
||||
let mut results = serde_json::Map::new();
|
||||
results.insert("fixture".into(), path.clone().into());
|
||||
results.insert("block".into(), block.env.number.into());
|
||||
results.insert("prover".into(), backend_key().into());
|
||||
results.insert("backend".into(), backend_key().into());
|
||||
results.insert("proof_system".into(), PROOF_SYSTEM.into());
|
||||
results.insert("risc0_crate_version".into(), RISC0_CRATE_VERSION.into());
|
||||
results.insert("image_id".into(), pinned.image_id.to_string().into());
|
||||
results.insert("po2".into(), po2.into());
|
||||
results.insert("shard_budget".into(), fixture.plan.shard_budget.into());
|
||||
results.insert("consensus_budget".into(), fixture.plan.consensus.into());
|
||||
results.insert("daa_score".into(), block.env.daa_score.into());
|
||||
results.insert("fee_set".into(), fee_set.name().into());
|
||||
results.insert("fees_v1_activation_daa".into(), if block.fees.v1_activation_daa == u64::MAX { serde_json::Value::Null } else { block.fees.v1_activation_daa.into() });
|
||||
|
||||
// 1. Native: the cut, the witnesses, every shard statement, the chain and the sums, against the fixture.
|
||||
stage("native");
|
||||
let t = Instant::now();
|
||||
let budget = test_budget.unwrap_or(fixture.plan.shard_budget);
|
||||
let (outcome, pre_root, shards) = build_shards(block, budget, prover);
|
||||
let native_s = t.elapsed().as_secs_f64();
|
||||
results.insert("budget".into(), budget.into());
|
||||
results.insert("test_cut".into(), test_budget.is_some().into());
|
||||
let e = &fixture.expected;
|
||||
let same = pre_root == e.pre_state_root && outcome.state_root == e.post_state_root && outcome.receipts_root == e.receipts_root && outcome.tx_commitment == e.tx_commitment && outcome.gas_used == e.gas_used && outcome.pgas_used == e.pgas_used;
|
||||
println!(
|
||||
"RESULT native: {:.4} s, pre {} post {} receipts {} gas {} pgas {} executed {} skipped {}: {} at {}",
|
||||
native_s,
|
||||
pre_root,
|
||||
outcome.state_root,
|
||||
outcome.receipts_root,
|
||||
outcome.gas_used,
|
||||
outcome.pgas_used,
|
||||
outcome.executed.len(),
|
||||
outcome.skipped.len(),
|
||||
if same { "MATCHES the fixture's expected values" } else { "DIFFERS from the fixture's expected values" },
|
||||
now()
|
||||
);
|
||||
if !same {
|
||||
bail!("native execution differs from the fixture; regenerate the fixture with igneum-prove-export");
|
||||
}
|
||||
if e.node_state_root != e.post_state_root {
|
||||
bail!("the fixture's node state root differs from its expected post-state root; the exporter must not have produced this file");
|
||||
}
|
||||
if test_budget.is_none() && shards.len() != fixture.plan.shards.len() {
|
||||
bail!("the plan has {} shards here and {} in the fixture", shards.len(), fixture.plan.shards.len());
|
||||
}
|
||||
let (mut sum_gas, mut sum_pgas) = (0u64, 0u64);
|
||||
let mut prev_root = pre_root;
|
||||
let mut prev_link = igneum_prove_core::Carry::default().link();
|
||||
for (i, s) in shards.iter().enumerate() {
|
||||
let o = &s.output;
|
||||
let (accounts, slots, leaves, hashes) = s.input.witness.stats();
|
||||
let bytes = bincode::serialize(&s.input)?.len();
|
||||
if let (None, Some(x)) = (test_budget, fixture.plan.shards.get(i)) {
|
||||
if o.pre_root != x.pre_root || o.post_root != x.post_root || o.receipts_root != x.receipts_root || o.link_in != x.link_in || o.link_out != x.link_out || o.gas_used != x.gas_used || o.pgas_used != x.pgas_used {
|
||||
bail!("shard {}: the native statement differs from the fixture's plan", o.shard_index);
|
||||
}
|
||||
}
|
||||
if o.pre_root != prev_root || o.link_in != prev_link {
|
||||
bail!("shard {}: does not continue the previous shard", o.shard_index);
|
||||
}
|
||||
prev_root = o.post_root;
|
||||
prev_link = o.link_out;
|
||||
sum_gas += o.gas_used;
|
||||
sum_pgas += o.pgas_used;
|
||||
results.entry("shard_witness_bytes").or_insert_with(|| serde_json::Value::Array(Vec::new())).as_array_mut().unwrap().push(serde_json::Value::from(bytes as u64));
|
||||
println!(
|
||||
"RESULT shard {} native: txs {}..{} ({} executed, {} skipped), gas {}, pgas {}{}, witness {accounts} accounts {slots} slots {leaves} leaves {hashes} hashes, input {bytes} bytes, pre {} post {}",
|
||||
o.shard_index,
|
||||
s.spec.tx_start,
|
||||
s.spec.tx_end,
|
||||
o.executed,
|
||||
o.skipped,
|
||||
o.gas_used,
|
||||
o.pgas_used,
|
||||
if s.spec.over_budget { " (ONE TRANSACTION ABOVE S_p)" } else { "" },
|
||||
o.pre_root,
|
||||
o.post_root
|
||||
);
|
||||
}
|
||||
if prev_root != outcome.state_root || sum_gas != outcome.gas_used || sum_pgas != outcome.pgas_used {
|
||||
bail!("the shards do not chain to the block's post-root or do not sum to its gas and pgas");
|
||||
}
|
||||
let native_block = agg::aggregate(&AggInput { shard_vk: [0; 8], shards: shards.iter().map(|s| s.output.to_bytes()).collect(), parent_hash: block.env.parent_hash, prev: None }, &mut |_, _| {});
|
||||
if native_block.post_root != outcome.state_root || native_block.tx_commitment != outcome.tx_commitment || native_block.gas_used != outcome.gas_used {
|
||||
bail!("the native aggregation does not reproduce the block");
|
||||
}
|
||||
println!("RESULT plan: {} shard(s) chain from {} to {} and sum to gas {} pgas {}: the cut equals the block; native aggregation receipts {} provers {}", shards.len(), pre_root, outcome.state_root, sum_gas, sum_pgas, native_block.receipts, native_block.provers);
|
||||
results.insert("native_seconds".into(), native_s.into());
|
||||
results.insert("shards".into(), (shards.len() as u64).into());
|
||||
results.insert("shard_pgas".into(), shards.iter().map(|s| serde_json::Value::from(s.output.pgas_used)).collect::<Vec<_>>().into());
|
||||
results.insert("witness_bytes".into(), shards.iter().map(|s| serde_json::Value::from(bincode::serialize(&s.input).map(|b| b.len() as u64).unwrap_or(0))).collect::<Vec<_>>().into());
|
||||
|
||||
// 2. Tampered witnesses must fail (the same three cases as the SP1 host).
|
||||
tamper_checks(&shards[0])?;
|
||||
if mode == "native" {
|
||||
return finish(results, out_path);
|
||||
}
|
||||
|
||||
// 3. The pinned guest: the image id recomputed from the embedded ELF must be the manifest's.
|
||||
stage("setup");
|
||||
let t = Instant::now();
|
||||
pinned.check_image_id()?;
|
||||
let setup_s = t.elapsed().as_secs_f64();
|
||||
println!("RESULT setup: {setup_s:.3} s (image id recomputed from the embedded guest), proof system {PROOF_SYSTEM} image id {} at {}", pinned.image_id, now());
|
||||
println!("RESULT pinned: setup matches the manifest ({})", pinned.describe());
|
||||
results.insert("setup_seconds".into(), setup_s.into());
|
||||
results.insert("shard_program_id".into(), pinned.image_id.to_string().into());
|
||||
|
||||
match mode.as_str() {
|
||||
"execute" => run_execute(&pinned, &shards, po2, &mut results)?,
|
||||
"compressed" => run_compressed(&pinned, &shards, shard_index, po2, out_path.as_deref(), &mut results)?,
|
||||
other => bail!("unknown mode {other}"),
|
||||
}
|
||||
finish(results, out_path)
|
||||
}
|
||||
|
||||
fn tamper_checks(shard: &BuiltShard) -> Result<()> {
|
||||
let quiet = std::panic::take_hook();
|
||||
std::panic::set_hook(Box::new(|_| {}));
|
||||
let outcome = |name: &str, input: ShardInput, expect_root: B256| -> Result<()> {
|
||||
let r = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| shard_statement(&input)));
|
||||
let verdict = match &r {
|
||||
Err(_) => "REJECTED (the statement cannot be proven)".to_string(),
|
||||
Ok(o) if o.pre_root != expect_root => format!("REJECTED (pre-root {} is not the node's {})", o.pre_root, expect_root),
|
||||
Ok(_) => "ACCEPTED".to_string(),
|
||||
};
|
||||
println!("RESULT tamper {name}: {verdict}");
|
||||
if verdict.starts_with("ACCEPTED") {
|
||||
bail!("a tampered witness ({name}) was accepted");
|
||||
}
|
||||
Ok(())
|
||||
};
|
||||
let expect = shard.output.pre_root;
|
||||
let mut a = shard.input.clone();
|
||||
let addresses: Vec<B256> = a.witness.accounts.iter().map(|acc| alloy_primitives::keccak256(acc.address)).collect();
|
||||
if let Some((_, v)) = a.witness.trie.leaves.iter_mut().find(|(k, _)| addresses.contains(k)) {
|
||||
let mut acc = <alloy_trie::TrieAccount as alloy_rlp::Decodable>::decode(&mut v.as_ref()).expect("leaf decodes");
|
||||
acc.balance += alloy_primitives::U256::from(1);
|
||||
*v = alloy_rlp::encode(acc).into();
|
||||
}
|
||||
outcome("account balance", a, expect)?;
|
||||
let mut b = shard.input.clone();
|
||||
let mut touched = false;
|
||||
for acc in b.witness.accounts.iter_mut() {
|
||||
if let Some((_, v)) = acc.storage.leaves.first_mut() {
|
||||
let mut bytes = v.to_vec();
|
||||
let last = bytes.len() - 1;
|
||||
bytes[last] ^= 0x01;
|
||||
*v = bytes.into();
|
||||
touched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !touched {
|
||||
for acc in b.witness.accounts.iter_mut() {
|
||||
if !acc.code.is_empty() {
|
||||
let mut bytes = acc.code.to_vec();
|
||||
bytes[0] ^= 0x01;
|
||||
acc.code = bytes.into();
|
||||
touched = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if touched {
|
||||
outcome("storage or code", b, expect)?;
|
||||
}
|
||||
let mut c = shard.input.clone();
|
||||
let victim = c.txs.first().and_then(|t| igneum_evm_types::decode_and_check(&t.raw, c.chain_id).ok()).map(|tx| tx.sender);
|
||||
if let Some(sender) = victim {
|
||||
c.witness.accounts.retain(|acc| acc.address != sender);
|
||||
outcome("dropped account", c, expect)?;
|
||||
}
|
||||
std::panic::set_hook(quiet);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn env_for(input: &ShardInput, po2: u32) -> Result<ExecutorEnv<'static>> {
|
||||
let bytes = bincode::serialize(input)?;
|
||||
ExecutorEnv::builder().segment_limit_po2(po2).write_frame(&bytes).build().map_err(|e| anyhow!("executor env: {e}"))
|
||||
}
|
||||
|
||||
fn check_shard_output(out: &ShardOutput, native: &ShardOutput) -> Result<()> {
|
||||
if out != native {
|
||||
bail!("the guest's journal differs from the native run:\n guest {out:?}\n native {native:?}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One executor run: the journal (checked against the native statement), the user cycles, the segments.
|
||||
fn execute_shard(pinned: &pinned::Pinned, s: &BuiltShard, po2: u32) -> Result<(ShardOutput, u64, Vec<(u32, u32)>, f64)> {
|
||||
let env = env_for(&s.input, po2)?;
|
||||
let t = Instant::now();
|
||||
let session = default_executor().execute(env, pinned.elf()).map_err(|e| anyhow!("execute: {e}"))?;
|
||||
let dt = t.elapsed().as_secs_f64();
|
||||
let out = ShardOutput::from_bytes(&session.journal.bytes).context("the journal is not a shard statement")?;
|
||||
check_shard_output(&out, &s.output)?;
|
||||
if session.journal.bytes != s.output.to_bytes() {
|
||||
bail!("the journal bytes are not the native statement bytes");
|
||||
}
|
||||
let segments: Vec<(u32, u32)> = session.segments.iter().map(|g| (g.po2, g.cycles)).collect();
|
||||
Ok((out, session.cycles(), segments, dt))
|
||||
}
|
||||
|
||||
fn run_execute(pinned: &pinned::Pinned, shards: &[BuiltShard], po2: u32, results: &mut serde_json::Map<String, serde_json::Value>) -> Result<()> {
|
||||
let mut cycles_all = Vec::new();
|
||||
for s in shards {
|
||||
stage(&format!("execute shard {}", s.output.shard_index));
|
||||
let (out, cycles, segments, dt) = execute_shard(pinned, s, po2)?;
|
||||
println!("RESULT execute shard {}: {} user cycles in {} segment(s) {:?} (po2, cycles), {:.2} s, {:.0} cycles per EVM gas, {:.0} cycles per pgas at {}", out.shard_index, cycles, segments.len(), segments, dt, cycles as f64 / out.gas_used.max(1) as f64, cycles as f64 / out.pgas_used.max(1) as f64, now());
|
||||
cycles_all.push(serde_json::Value::from(cycles));
|
||||
}
|
||||
results.insert("shard_cycles".into(), cycles_all.into());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The prover's mode (spec 7.7 for version 2): execute (the cycle count), then the succinct receipt of one
|
||||
/// shard, verified against the pinned image id; writes `<out dir>/block-N-shard-i-r0.bin` and records the
|
||||
/// statement (keccak of the journal, what the proof record carries) and the receipt's SHA-256.
|
||||
fn run_compressed(pinned: &pinned::Pinned, shards: &[BuiltShard], index: usize, po2: u32, out_dir: Option<&str>, results: &mut serde_json::Map<String, serde_json::Value>) -> Result<()> {
|
||||
let s = shards.get(index).ok_or_else(|| anyhow!("shard {index} is not in the plan ({} shards)", shards.len()))?;
|
||||
let i = s.output.shard_index;
|
||||
results.insert("shard_index".into(), i.into());
|
||||
stage(&format!("execute shard {i}"));
|
||||
let (_, cycles, segments, dt) = execute_shard(pinned, s, po2)?;
|
||||
println!("RESULT execute shard {i}: {cycles} user cycles in {} segment(s) {:?} (po2, cycles), {dt:.2} s at {}", segments.len(), segments, now());
|
||||
results.insert("cycles".into(), cycles.into());
|
||||
results.insert("execute_seconds".into(), dt.into());
|
||||
results.insert("execute_segments".into(), segments.iter().map(|(p, c)| serde_json::json!({"po2": p, "cycles": c})).collect::<Vec<_>>().into());
|
||||
|
||||
stage(&format!("compressed shard {i} (succinct receipt, po2 {po2})"));
|
||||
let env = env_for(&s.input, po2)?;
|
||||
let t = Instant::now();
|
||||
let info = default_prover().prove_with_opts(env, pinned.elf(), &ProverOpts::succinct()).map_err(|e| anyhow!("prove: {e}"))?;
|
||||
let dt = t.elapsed().as_secs_f64();
|
||||
let receipt = info.receipt;
|
||||
let stats = info.stats;
|
||||
let t = Instant::now();
|
||||
let ok = receipt.verify(pinned.image_digest()).is_ok();
|
||||
let vdt = t.elapsed().as_secs_f64();
|
||||
let journal_ok = receipt.journal.bytes == s.output.to_bytes();
|
||||
let bytes = bincode::serialize(&receipt)?;
|
||||
let statement = alloy_primitives::keccak256(&receipt.journal.bytes);
|
||||
let proof_hash: [u8; 32] = sha2::Sha256::digest(&bytes).into();
|
||||
println!(
|
||||
"RESULT compressed shard {i}: prove {dt:.1} s ({} segment(s), total cycles {}, user cycles {}, paging {}, reserved {}), receipt {} bytes (seal {} bytes), verify {vdt:.3} s, {}; statement {statement} proof sha256 0x{} prover {} at {}",
|
||||
stats.segments,
|
||||
stats.total_cycles,
|
||||
stats.user_cycles,
|
||||
stats.paging_cycles,
|
||||
stats.reserved_cycles,
|
||||
bytes.len(),
|
||||
receipt.seal_size(),
|
||||
if ok && journal_ok { "VERIFIED" } else if ok { "VERIFY FAILED (journal is not the native statement)" } else { "VERIFY FAILED" },
|
||||
hex::encode(proof_hash),
|
||||
s.output.prover,
|
||||
now()
|
||||
);
|
||||
if !ok || !journal_ok {
|
||||
bail!("succinct receipt of shard {i} did not verify");
|
||||
}
|
||||
results.insert("compressed_prove_seconds".into(), dt.into());
|
||||
results.insert("compressed_verify_seconds".into(), vdt.into());
|
||||
results.insert("compressed_proof_bytes".into(), bytes.len().into());
|
||||
results.insert("seal_bytes".into(), receipt.seal_size().into());
|
||||
results.insert("segments".into(), stats.segments.into());
|
||||
results.insert("total_cycles".into(), stats.total_cycles.into());
|
||||
results.insert("user_cycles".into(), stats.user_cycles.into());
|
||||
results.insert("paging_cycles".into(), stats.paging_cycles.into());
|
||||
results.insert("reserved_cycles".into(), stats.reserved_cycles.into());
|
||||
results.insert("statement".into(), statement.to_string().into());
|
||||
results.insert("proof_sha256".into(), format!("0x{}", hex::encode(proof_hash)).into());
|
||||
results.insert("block_hash".into(), s.input.env.hash.to_string().into());
|
||||
results.insert("number".into(), s.input.env.number.into());
|
||||
results.insert("prover".into(), s.output.prover.to_string().into());
|
||||
let dir = out_dir.and_then(|p| std::path::Path::new(p).parent().map(|d| d.to_path_buf())).unwrap_or_else(|| std::path::PathBuf::from("."));
|
||||
let file = dir.join(format!("block-{}-shard-{i}-r0.bin", s.input.env.number));
|
||||
std::fs::write(&file, &bytes)?;
|
||||
results.insert("proof_file".into(), file.display().to_string().into());
|
||||
println!("proof written to {}", file.display());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The image id a receipt claims (the pre-state digest of its claim), printed next to ours so a receipt from a
|
||||
/// host with another guest build is rejected with the reason in the node log.
|
||||
fn claimed_image_id(receipt: &Receipt) -> Option<B256> {
|
||||
use risc0_zkvm::sha::Digestible;
|
||||
let claim = receipt.claim().ok()?;
|
||||
let claim = claim.as_value().ok()?;
|
||||
Some(B256::from_slice(claim.pre.digest().as_bytes()))
|
||||
}
|
||||
|
||||
/// `--mode verify --proof <file> --statement 0x..`: verifies the succinct receipt against the PINNED image id and
|
||||
/// checks that keccak256 of its journal is the statement. Exit 0 = verified, 3 = not verified.
|
||||
fn run_verify(pinned: &pinned::Pinned, proof_path: &str, statement: &str) -> Result<()> {
|
||||
let bytes = std::fs::read(proof_path).with_context(|| format!("read {proof_path}"))?;
|
||||
let want: B256 = statement.parse().context("statement is not 32 bytes of hex")?;
|
||||
stage("setup");
|
||||
let t = Instant::now();
|
||||
pinned.check_image_id()?;
|
||||
println!("RESULT setup: {:.3} s (image id recomputed from the embedded guest, no prover), shard program id {} at {}", t.elapsed().as_secs_f64(), pinned.image_id, now());
|
||||
stage("verify");
|
||||
let t = Instant::now();
|
||||
let receipt: Receipt = match bincode::deserialize(&bytes) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
println!("RESULT verify: NOT VERIFIED in {:.3} s; the file is not a bincode RISC Zero receipt ({e}) at {}", t.elapsed().as_secs_f64(), now());
|
||||
std::process::exit(3)
|
||||
}
|
||||
};
|
||||
let got = alloy_primitives::keccak256(&receipt.journal.bytes);
|
||||
let output = ShardOutput::from_bytes(&receipt.journal.bytes);
|
||||
let claimed = claimed_image_id(&receipt);
|
||||
let same_program = claimed == Some(pinned.image_id);
|
||||
let crypto_ok = same_program && receipt.verify(pinned.image_digest()).is_ok();
|
||||
let ok = crypto_ok && got == want && output.is_some();
|
||||
let dt = t.elapsed().as_secs_f64();
|
||||
let kind = match &receipt.inner {
|
||||
risc0_zkvm::InnerReceipt::Succinct(_) => "succinct",
|
||||
risc0_zkvm::InnerReceipt::Composite(_) => "composite",
|
||||
risc0_zkvm::InnerReceipt::Groth16(_) => "groth16",
|
||||
_ => "other",
|
||||
};
|
||||
let program = match claimed {
|
||||
Some(c) if same_program => format!("image id {c} (ours)"),
|
||||
Some(c) => format!("image id {c} IS NOT OURS {} (the prover runs another guest build)", pinned.image_id),
|
||||
None => "no claim in the receipt".to_string(),
|
||||
};
|
||||
match &output {
|
||||
Some(o) => println!("RESULT verify: {} in {dt:.3} s; block {} shard {} prover {} statement {got} (want {want}) {program} {kind} receipt {} bytes at {}", if ok { "VERIFIED" } else { "NOT VERIFIED" }, o.number, o.shard_index, o.prover, bytes.len(), now()),
|
||||
None => println!("RESULT verify: NOT VERIFIED in {dt:.3} s; the journal is not a shard statement; {program} {kind} receipt at {}", now()),
|
||||
}
|
||||
if ok {
|
||||
Ok(())
|
||||
} else {
|
||||
std::process::exit(3)
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(results: serde_json::Map<String, serde_json::Value>, out_path: Option<String>) -> Result<()> {
|
||||
if let Some(p) = out_path {
|
||||
std::fs::write(&p, serde_json::to_string_pretty(&serde_json::Value::Object(results))?)?;
|
||||
println!("results written to {p}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
88
proving/igneum-prove-r0/host/src/pinned.rs
Normal file
88
proving/igneum-prove-r0/host/src/pinned.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//! The pinned guest (6 October 2026, the rule of proving/igneum-prove/host/src/pinned.rs applied to proof system
|
||||
//! version 2): the host embeds the guest program binary (risc0-build's ProgramBinary: the user ELF with RISC
|
||||
//! Zero's v1compat kernel, what the image id is computed over) and the manifest committed under elf/, checks the ELF's SHA-256 against
|
||||
//! the manifest at every start, and in the prove and verify modes recomputes the image id from the ELF and
|
||||
//! refuses when it is not the manifest's. Changing the guest: pin-guest.sh. Every prover and verifier of version 2
|
||||
//! moves to a new pin together.
|
||||
|
||||
use alloy_primitives::B256;
|
||||
use anyhow::{bail, Context, Result};
|
||||
use risc0_zkvm::Digest;
|
||||
use serde::Deserialize;
|
||||
use sha2::Digest as _;
|
||||
|
||||
pub const GUEST_ELF: &[u8] = include_bytes!("../../elf/igneum-prove-r0-guest.bin");
|
||||
const MANIFEST: &str = include_str!("../../elf/manifest-r0.json");
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct Manifest {
|
||||
pub format: String,
|
||||
pub risc0_crate_version: String,
|
||||
pub pinned_at: String,
|
||||
pub pinned_on: String,
|
||||
pub guest: GuestEntry,
|
||||
#[serde(default)]
|
||||
pub build: serde_json::Value,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct GuestEntry {
|
||||
pub elf: String,
|
||||
pub elf_bytes: usize,
|
||||
pub elf_sha256: String,
|
||||
pub image_id: String,
|
||||
}
|
||||
|
||||
pub struct Pinned {
|
||||
pub image_id: B256,
|
||||
pub manifest: Manifest,
|
||||
}
|
||||
|
||||
impl Pinned {
|
||||
/// Parses the manifest and checks the embedded ELF against it (cheap: one SHA-256).
|
||||
pub fn load() -> Result<Self> {
|
||||
let manifest: Manifest = serde_json::from_str(MANIFEST).context("elf/manifest-r0.json")?;
|
||||
if manifest.format != "igneum-prove-r0-manifest-v1" {
|
||||
bail!("elf/manifest-r0.json has format {}, this host reads igneum-prove-r0-manifest-v1", manifest.format);
|
||||
}
|
||||
let sha = format!("0x{}", hex::encode(sha2::Sha256::digest(GUEST_ELF)));
|
||||
if sha != manifest.guest.elf_sha256 || GUEST_ELF.len() != manifest.guest.elf_bytes {
|
||||
bail!("the embedded guest ({} bytes, sha256 {sha}) is not the manifest's ({} bytes, {}): rebuild the host from a clean elf/", GUEST_ELF.len(), manifest.guest.elf_bytes, manifest.guest.elf_sha256);
|
||||
}
|
||||
let image_id: B256 = manifest.guest.image_id.parse().context("manifest image_id")?;
|
||||
Ok(Self { image_id, manifest })
|
||||
}
|
||||
|
||||
pub fn elf(&self) -> &'static [u8] {
|
||||
GUEST_ELF
|
||||
}
|
||||
|
||||
pub fn image_digest(&self) -> Digest {
|
||||
Digest::from_bytes(self.image_id.0)
|
||||
}
|
||||
|
||||
/// Recomputes the image id from the embedded ELF (the prove and verify modes) and compares.
|
||||
pub fn check_image_id(&self) -> Result<()> {
|
||||
let computed = risc0_zkvm::compute_image_id(GUEST_ELF).map_err(|e| anyhow::anyhow!("compute_image_id: {e}"))?;
|
||||
let computed = B256::from_slice(computed.as_bytes());
|
||||
if computed != self.image_id {
|
||||
bail!("the embedded guest's image id {computed} is not the manifest's {}: this host would make proofs no other node accepts (re-pin with proving/igneum-prove-r0/pin-guest.sh)", self.image_id);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// One line in the SP1 host's wording: the node's `parse_program_ids` reads the words after
|
||||
/// "shard program id " and "aggregator id " (the aggregator id is all zeros until a version 2 aggregator exists).
|
||||
pub fn describe(&self) -> String {
|
||||
format!(
|
||||
"pinned guests: shard program id {} ({} bytes, sha256 {}) aggregator id {} (0 bytes), pinned {} on {}, RISC Zero {} proof system 2",
|
||||
self.image_id,
|
||||
GUEST_ELF.len(),
|
||||
&self.manifest.guest.elf_sha256[..18],
|
||||
B256::ZERO,
|
||||
self.manifest.pinned_at,
|
||||
self.manifest.pinned_on,
|
||||
self.manifest.risc0_crate_version
|
||||
)
|
||||
}
|
||||
}
|
||||
14
proving/igneum-prove-r0/methods/Cargo.toml
Normal file
14
proving/igneum-prove-r0/methods/Cargo.toml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "igneum-prove-r0-methods"
|
||||
description = "Builds the RISC Zero shard guest (methods/guest) when IGNEUM_BUILD_GUESTS=1, for pin-guest.sh; a normal build compiles nothing for the zkVM"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
license.workspace = true
|
||||
|
||||
[build-dependencies]
|
||||
# `unstable`: the keccak precompile the tiny-keccak patch uses is behind this flag in 3.0.x (precompiles page)
|
||||
risc0-build = { workspace = true, features = ["unstable"] }
|
||||
|
||||
# risc0-build reads the guest list here (the directories under methods/).
|
||||
[package.metadata.risc0]
|
||||
methods = ["guest"]
|
||||
16
proving/igneum-prove-r0/methods/build.rs
Normal file
16
proving/igneum-prove-r0/methods/build.rs
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
//! Builds the RISC Zero shard guest (methods/guest) with risc0-build when `IGNEUM_BUILD_GUESTS=1`, for
|
||||
//! pin-guest.sh to turn into the pinned artefacts under elf/ (the same rule as the SP1 workspace: a normal host
|
||||
//! build compiles nothing for the zkVM and needs no RISC Zero toolchain). Without the variable, methods.rs is a
|
||||
//! comment and the host embeds elf/ (host/src/pinned.rs).
|
||||
use std::{collections::HashMap, env, fs, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
println!("cargo:rerun-if-env-changed=IGNEUM_BUILD_GUESTS");
|
||||
let out = PathBuf::from(env::var("OUT_DIR").unwrap()).join("methods.rs");
|
||||
if env::var("IGNEUM_BUILD_GUESTS").map(|v| v == "1").unwrap_or(false) {
|
||||
let opts = risc0_build::GuestOptions::default();
|
||||
risc0_build::embed_methods_with_options(HashMap::from([("igneum-prove-r0-guest", opts)]));
|
||||
} else {
|
||||
fs::write(&out, "// no guest built here: the host embeds the pinned guest under elf/ (IGNEUM_BUILD_GUESTS=1 builds one for pin-guest.sh)\n").unwrap();
|
||||
}
|
||||
}
|
||||
3921
proving/igneum-prove-r0/methods/guest/Cargo.lock
generated
Normal file
3921
proving/igneum-prove-r0/methods/guest/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
33
proving/igneum-prove-r0/methods/guest/Cargo.toml
Normal file
33
proving/igneum-prove-r0/methods/guest/Cargo.toml
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
[package]
|
||||
name = "igneum-prove-r0-guest"
|
||||
description = "RISC Zero guest, proof system version 2: proves one shard of an Igneum chain block (design 5.1), the same statement bytes as the SP1 guest"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
license = "ISC"
|
||||
|
||||
# Its own workspace: built by risc0-build for riscv32im-risc0-zkvm-elf with its own Cargo.lock (committed).
|
||||
[workspace]
|
||||
|
||||
[dependencies]
|
||||
risc0-zkvm = { version = "=3.0.6", default-features = false, features = ["std", "unstable"] }
|
||||
igneum-prove-core = { path = "../../../igneum-prove/core" }
|
||||
bincode = "=1.3.3"
|
||||
# keccak256 through tiny-keccak so RISC Zero's patched tiny-keccak (the keccak circuit) is the backend; outside the
|
||||
# zkVM the same crate computes the same bytes, so the statement is the SP1 guest's byte for byte
|
||||
alloy-primitives = { version = "=1.7.3", default-features = false, features = ["tiny-keccak"] }
|
||||
|
||||
# RISC Zero's accelerated crates (https://dev.risczero.com/api/zkvm/precompiles, release-3.0), pinned by tag:
|
||||
# k256 for the secp256k1 signature recovery (alloy-consensus `k256`), tiny-keccak for keccak256 (alloy-primitives
|
||||
# `tiny-keccak`, the trie hashing), sha2 for the sha256 precompile (revm-precompile) and k256's hashing, both
|
||||
# versions the lock resolves (0.10.9 and 0.11.0).
|
||||
[patch.crates-io]
|
||||
k256 = { git = "https://github.com/risc0/RustCrypto-elliptic-curves", tag = "k256/v0.13.4-risczero.1" }
|
||||
# the k256 patch imports `elliptic_curve::bigint::risc0`, which only the patched crypto-bigint has (the ecdsa example)
|
||||
crypto-bigint = { git = "https://github.com/risc0/RustCrypto-crypto-bigint", tag = "v0.5.5-risczero.0" }
|
||||
tiny-keccak = { git = "https://github.com/risc0/tiny-keccak", tag = "tiny-keccak/v2.0.2-risczero.0" }
|
||||
sha2 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.10.9-risczero.0" }
|
||||
sha2-011 = { git = "https://github.com/risc0/RustCrypto-hashes", tag = "sha2-v0.11.0-risczero.0", package = "sha2" }
|
||||
|
||||
[profile.release]
|
||||
lto = "thin"
|
||||
opt-level = 3
|
||||
14
proving/igneum-prove-r0/methods/guest/src/main.rs
Normal file
14
proving/igneum-prove-r0/methods/guest/src/main.rs
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//! The shard guest, proof system version 2 (RISC Zero). Input: bincode of `ShardInput` (one frame). Output
|
||||
//! (the journal): `ShardOutput` in its fixed byte layout, the same bytes the SP1 guest commits, so a statement is
|
||||
//! keccak256 of the journal in both families. Everything between is `igneum_prove_core::shard::shard_statement`,
|
||||
//! the same code the host runs natively first.
|
||||
|
||||
use igneum_prove_core::shard::{shard_statement, ShardInput};
|
||||
use risc0_zkvm::guest::env;
|
||||
|
||||
fn main() {
|
||||
let input: Vec<u8> = env::read_frame();
|
||||
let input: ShardInput = bincode::deserialize(&input).expect("ShardInput decodes");
|
||||
let out = shard_statement(&input);
|
||||
env::commit_slice(&out.to_bytes());
|
||||
}
|
||||
3
proving/igneum-prove-r0/methods/src/lib.rs
Normal file
3
proving/igneum-prove-r0/methods/src/lib.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
//! The guest built by build.rs when `IGNEUM_BUILD_GUESTS=1` (`IGNEUM_PROVE_R0_GUEST_ELF`, `_ID`, `_PATH`); empty
|
||||
//! otherwise. The host never uses this crate: it embeds the pinned guest under elf/.
|
||||
include!(concat!(env!("OUT_DIR"), "/methods.rs"));
|
||||
471
proving/igneum-prove-r0/patches/risc0-build-kernel/Cargo.lock
generated
Normal file
471
proving/igneum-prove-r0/patches/risc0-build-kernel/Cargo.lock
generated
Normal file
|
|
@ -0,0 +1,471 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 4
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
|
||||
|
||||
[[package]]
|
||||
name = "block-buffer"
|
||||
version = "0.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cc"
|
||||
version = "1.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e3a13707ac958681c13b39b458c073d0d9bc8a22cb1b2f4c8e55eb72c13f362"
|
||||
dependencies = [
|
||||
"jobserver",
|
||||
"libc",
|
||||
"shlex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.9.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crypto-common"
|
||||
version = "0.1.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
|
||||
dependencies = [
|
||||
"generic-array",
|
||||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "digest"
|
||||
version = "0.10.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292"
|
||||
dependencies = [
|
||||
"block-buffer",
|
||||
"crypto-common",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "directories"
|
||||
version = "6.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d"
|
||||
dependencies = [
|
||||
"dirs-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs-sys"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"option-ext",
|
||||
"redox_users",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
|
||||
[[package]]
|
||||
name = "errno"
|
||||
version = "0.3.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastrand"
|
||||
version = "2.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
|
||||
|
||||
[[package]]
|
||||
name = "generic-array"
|
||||
version = "0.14.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
|
||||
dependencies = [
|
||||
"typenum",
|
||||
"version_check",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi 0.11.0+wasi-snapshot-preview1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"r-efi",
|
||||
"wasi 0.14.2+wasi-0.2.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hex"
|
||||
version = "0.4.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
|
||||
|
||||
[[package]]
|
||||
name = "jobserver"
|
||||
version = "0.1.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38f262f097c174adebe41eb73d66ae9c06b2844fb0da69969647bbddd9b0538a"
|
||||
dependencies = [
|
||||
"getrandom 0.3.2",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.172"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.21.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
|
||||
|
||||
[[package]]
|
||||
name = "option-ext"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro2"
|
||||
version = "1.0.95"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778"
|
||||
dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
version = "1.0.40"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "r-efi"
|
||||
version = "5.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5"
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
|
||||
dependencies = [
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.12.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b"
|
||||
dependencies = [
|
||||
"getrandom 0.2.16",
|
||||
"libredox",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "risc0-build-kernel"
|
||||
version = "2.0.1"
|
||||
dependencies = [
|
||||
"cc",
|
||||
"directories",
|
||||
"hex",
|
||||
"rayon",
|
||||
"sha2",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
version = "1.0.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sha2"
|
||||
version = "0.10.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"cpufeatures",
|
||||
"digest",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "shlex"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.100"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tempfile"
|
||||
version = "3.20.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e8a64e3985349f2441a1a9ef0b853f869006c3855f2cda6862a94d26ebb9d6a1"
|
||||
dependencies = [
|
||||
"fastrand",
|
||||
"getrandom 0.3.2",
|
||||
"once_cell",
|
||||
"rustix",
|
||||
"windows-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "2.0.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.14.2+wasi-0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3"
|
||||
dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
||||
|
||||
[[package]]
|
||||
name = "wit-bindgen-rt"
|
||||
version = "0.39.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
]
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
|
||||
#
|
||||
# When uploading crates to the registry Cargo will automatically
|
||||
# "normalize" Cargo.toml files for maximal compatibility
|
||||
# with all versions of Cargo and also rewrite `path` dependencies
|
||||
# to registry (e.g., crates.io) dependencies.
|
||||
#
|
||||
# If you are reading this file be aware that the original Cargo.toml
|
||||
# will likely look very different (and much more reasonable).
|
||||
# See Cargo.toml.orig for the original contents.
|
||||
|
||||
[package]
|
||||
edition = "2021"
|
||||
name = "risc0-build-kernel"
|
||||
version = "2.0.1"
|
||||
build = false
|
||||
autolib = false
|
||||
autobins = false
|
||||
autoexamples = false
|
||||
autotests = false
|
||||
autobenches = false
|
||||
description = "RISC Zero tool for building kernels"
|
||||
homepage = "https://risczero.com/"
|
||||
readme = false
|
||||
license = "Apache-2.0"
|
||||
repository = "https://github.com/risc0/risc0/"
|
||||
|
||||
[lib]
|
||||
name = "risc0_build_kernel"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies.cc]
|
||||
version = "1.2.2"
|
||||
features = ["parallel"]
|
||||
|
||||
[dependencies.directories]
|
||||
version = "6.0.0"
|
||||
|
||||
[dependencies.hex]
|
||||
version = "0.4"
|
||||
|
||||
[dependencies.rayon]
|
||||
version = "1.10"
|
||||
|
||||
[dependencies.sha2]
|
||||
version = "0.10"
|
||||
|
||||
[dependencies.tempfile]
|
||||
version = "3.12"
|
||||
|
|
@ -0,0 +1,225 @@
|
|||
// Copyright 2024 RISC Zero, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \file
|
||||
/// Defines the core finite field data type, Fp, and some free functions on the type.
|
||||
|
||||
#include <metal_stdlib>
|
||||
|
||||
/// The Fp class is an element of the finite field F_p, where P is the prime number 15*2^27 + 1.
|
||||
/// Put another way, Fp is basically integer arithmetic modulo P.
|
||||
///
|
||||
/// The 'Fp' datatype is the core type of all of the operations done within the zero knowledge
|
||||
/// proofs, and is smallest 'addressable' datatype, and the base type of which all composite types
|
||||
/// are built. In many ways, one can imagine it as the word size of a very strange architecture.
|
||||
///
|
||||
/// This specific prime P was chosen to:
|
||||
/// - Be less than 2^31 so that it fits within a 32 bit word and doesn't overflow on addition.
|
||||
/// - Otherwise have as large a power of 2 in the factors of P-1 as possible.
|
||||
///
|
||||
/// This last property is useful for number theoretical transforms (the fast fourier transform
|
||||
/// equivalent on finite fields). See NTT.h for details.
|
||||
///
|
||||
/// The Fp class wraps all the standard arithmetic operations to make the finite field elements look
|
||||
/// basically like ordinary numbers (which they mostly are).
|
||||
class Fp {
|
||||
public:
|
||||
/// The value of P, the modulus of Fp.
|
||||
static constant uint32_t P = 15 * (uint32_t(1) << 27) + 1;
|
||||
static constant uint32_t M = 0x88000001;
|
||||
static constant uint32_t R2 = 1172168163;
|
||||
static constant uint32_t INVALID = 0xfffffffful;
|
||||
|
||||
private:
|
||||
// The actual value, always < P.
|
||||
uint32_t val;
|
||||
|
||||
// We make 'impls' of the core ops which all the other uses call. This is done to allow for
|
||||
// tweaking of the implementation later, for example switching to montgomery representation or
|
||||
// doing inline assembly or some crazy CUDA stuff.
|
||||
|
||||
// Add two numbers
|
||||
static constexpr uint32_t add(uint32_t a, uint32_t b) {
|
||||
uint32_t r = a + b;
|
||||
return (r >= P ? r - P : r);
|
||||
}
|
||||
|
||||
// Subtract two numbers
|
||||
static constexpr uint32_t sub(uint32_t a, uint32_t b) {
|
||||
uint32_t r = a - b;
|
||||
return (r > P ? r + P : r);
|
||||
}
|
||||
|
||||
// Multiply two numbers
|
||||
static constexpr uint32_t mul(uint32_t a, uint32_t b) {
|
||||
uint64_t o64 = uint64_t(a) * uint64_t(b);
|
||||
uint32_t low = -uint32_t(o64);
|
||||
uint32_t red = M * low;
|
||||
o64 += uint64_t(red) * uint64_t(P);
|
||||
uint32_t ret = o64 >> 32;
|
||||
return (ret >= P ? ret - P : ret);
|
||||
}
|
||||
|
||||
// Encode / Decode
|
||||
static constexpr uint32_t encode(uint32_t a) { return mul(R2, a); }
|
||||
|
||||
static constexpr uint32_t decode(uint32_t a) { return mul(1, a); }
|
||||
|
||||
// A private constructor that take the 'internal' form.
|
||||
constexpr Fp(uint32_t val, bool ignore) : val(val) {}
|
||||
|
||||
public:
|
||||
/// Default constructor, sets value to 0.
|
||||
constexpr Fp() : val(0) {}
|
||||
|
||||
/// Construct an FP from a uint32_t, wrap if needed
|
||||
constexpr Fp(uint32_t val) : val(encode(val)) {}
|
||||
|
||||
/// Construct an Fp from an already-encoded raw value
|
||||
static constexpr Fp fromRaw(uint32_t val) { return Fp(val, true); }
|
||||
|
||||
/// Convert to a uint32_t
|
||||
constexpr uint32_t asUInt32() const { return decode(val); }
|
||||
|
||||
constexpr uint32_t asUInt32() device const { return decode(val); }
|
||||
|
||||
/// Return the raw underlying word
|
||||
constexpr uint32_t asRaw() const { return val; }
|
||||
|
||||
/// Get the largest value, basically P - 1.
|
||||
static constexpr Fp maxVal() { return P - 1; }
|
||||
|
||||
/// Get an 'invalid' Fp value
|
||||
static constexpr Fp invalid() { return Fp(INVALID, true); }
|
||||
|
||||
constexpr inline Fp zeroize() {
|
||||
if (val == INVALID) {
|
||||
val = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Implement all the various overloads
|
||||
constexpr void operator=(uint32_t rhs) { val = encode(rhs); }
|
||||
|
||||
constexpr void operator=(uint32_t rhs) device { val = encode(rhs); }
|
||||
|
||||
constexpr Fp operator+(Fp rhs) const { return Fp(add(val, rhs.val), true); }
|
||||
|
||||
constexpr Fp operator-() const { return Fp(sub(0, val), true); }
|
||||
|
||||
constexpr Fp operator-(Fp rhs) const { return Fp(sub(val, rhs.val), true); }
|
||||
|
||||
constexpr Fp operator*(Fp rhs) const { return Fp(mul(val, rhs.val), true); }
|
||||
|
||||
constexpr Fp operator+(Fp rhs) device const { return Fp(add(val, rhs.val), true); }
|
||||
|
||||
constexpr Fp operator-() device const { return Fp(sub(0, val), true); }
|
||||
|
||||
constexpr Fp operator-(Fp rhs) device const { return Fp(sub(val, rhs.val), true); }
|
||||
|
||||
constexpr Fp operator*(Fp rhs) device const { return Fp(mul(val, rhs.val), true); }
|
||||
|
||||
constexpr Fp operator+=(Fp rhs) {
|
||||
val = add(val, rhs.val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Fp operator+=(Fp rhs) device {
|
||||
val = add(val, rhs.val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Fp operator-=(Fp rhs) {
|
||||
val = sub(val, rhs.val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Fp operator*=(Fp rhs) {
|
||||
val = mul(val, rhs.val);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr bool operator==(Fp rhs) const { return val == rhs.val; }
|
||||
|
||||
constexpr bool operator!=(Fp rhs) const { return val != rhs.val; }
|
||||
|
||||
constexpr bool operator<(Fp rhs) const { return decode(val) < decode(rhs.val); }
|
||||
|
||||
constexpr bool operator<=(Fp rhs) const { return decode(val) <= decode(rhs.val); }
|
||||
|
||||
constexpr bool operator>(Fp rhs) const { return decode(val) > decode(rhs.val); }
|
||||
|
||||
constexpr bool operator>=(Fp rhs) const { return decode(val) >= decode(rhs.val); }
|
||||
|
||||
constexpr bool operator==(Fp rhs) device const { return val == rhs.val; }
|
||||
|
||||
constexpr bool operator!=(Fp rhs) device const { return val != rhs.val; }
|
||||
|
||||
constexpr bool operator<(Fp rhs) device const { return decode(val) < decode(rhs.val); }
|
||||
|
||||
constexpr bool operator<=(Fp rhs) device const { return decode(val) <= decode(rhs.val); }
|
||||
|
||||
constexpr bool operator>(Fp rhs) device const { return decode(val) > decode(rhs.val); }
|
||||
|
||||
constexpr bool operator>=(Fp rhs) device const { return decode(val) >= decode(rhs.val); }
|
||||
|
||||
// Post-inc/dec
|
||||
constexpr Fp operator++(int) {
|
||||
Fp r = *this;
|
||||
val = add(val, encode(1));
|
||||
return r;
|
||||
}
|
||||
|
||||
constexpr Fp operator--(int) {
|
||||
Fp r = *this;
|
||||
val = sub(val, encode(1));
|
||||
return r;
|
||||
}
|
||||
|
||||
// Pre-inc/dec
|
||||
constexpr Fp operator++() {
|
||||
val = add(val, encode(1));
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Fp operator--() {
|
||||
val = sub(val, encode(1));
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
/// Raise a value to a power
|
||||
constexpr inline Fp pow(Fp x, size_t n) {
|
||||
Fp tot = 1;
|
||||
while (n != 0) {
|
||||
if (n % 2 == 1) {
|
||||
tot *= x;
|
||||
}
|
||||
n = n / 2;
|
||||
x *= x;
|
||||
}
|
||||
return tot;
|
||||
}
|
||||
|
||||
/// Compute the multiplicative inverse of x, or `1/x` in finite field terms. Since `x^(P-1) == 1
|
||||
/// (mod P)` for any x != 0 (as a consequence of Fermat's little theorem), it follows that `x *
|
||||
/// x^(P-2) == 1 (mod P)` for x != 0. That is, `x^(P-2)` is the multiplicative inverse of x.
|
||||
/// Computed this way, the 'inverse' of zero comes out as zero, which is convenient in many cases, so
|
||||
/// we leave it.
|
||||
constexpr inline Fp inv(Fp x) {
|
||||
return pow(x, Fp::P - 2);
|
||||
}
|
||||
|
|
@ -0,0 +1,224 @@
|
|||
// Copyright 2024 RISC Zero, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once
|
||||
|
||||
/// \file
|
||||
/// Defines FpExt, a finite field F_p^4, based on Fp via the irreducible polynomial x^4 - 11.
|
||||
|
||||
#include <metal_stdlib>
|
||||
|
||||
#include "fp.h"
|
||||
|
||||
// Defines instead of constexpr to appease CUDAs limitations around constants.
|
||||
// undef'd at the end of this file.
|
||||
#define BETA Fp(11)
|
||||
#define NBETA Fp(Fp::P - 11)
|
||||
|
||||
/// Instances of FpExt are element of a finite field F_p^4. They are represented as elements of
|
||||
/// F_p[X] / (X^4 - 11). Basically, this is a 'big' finite field (about 2^128 elements), which is
|
||||
/// used when the security of various operations depends on the size of the field. It has the field
|
||||
/// Fp as a subfield, which means operations by the two are compatible, which is important. The
|
||||
/// irreducible polynomial was chosen to be the simplest possible one, x^4 - B, where 11 is the
|
||||
/// smallest B which makes the polynomial irreducible.
|
||||
struct FpExt {
|
||||
/// The elements of FpExt, elems[0] + elems[1]*X + elems[2]*X^2 + elems[3]*x^4
|
||||
Fp elems[4];
|
||||
|
||||
/// Default constructor makes the zero elements
|
||||
constexpr FpExt() {}
|
||||
|
||||
/// Initialize from uint32_t
|
||||
explicit constexpr FpExt(uint32_t x) {
|
||||
elems[0] = x;
|
||||
elems[1] = 0;
|
||||
elems[2] = 0;
|
||||
elems[3] = 0;
|
||||
}
|
||||
|
||||
/// Convert from Fp to FpExt.
|
||||
explicit constexpr FpExt(Fp x) {
|
||||
elems[0] = x;
|
||||
elems[1] = 0;
|
||||
elems[2] = 0;
|
||||
elems[3] = 0;
|
||||
}
|
||||
|
||||
/// Explicitly construct an FpExt from parts
|
||||
constexpr FpExt(Fp a, Fp b, Fp c, Fp d) {
|
||||
elems[0] = a;
|
||||
elems[1] = b;
|
||||
elems[2] = c;
|
||||
elems[3] = d;
|
||||
}
|
||||
|
||||
constexpr inline FpExt zeroize() {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
elems[i].zeroize();
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Implement the addition/subtraction overloads
|
||||
constexpr FpExt operator+=(FpExt rhs) {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
elems[i] += rhs.elems[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr FpExt operator-=(FpExt rhs) {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
elems[i] -= rhs.elems[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr FpExt operator+(FpExt rhs) const {
|
||||
FpExt result = *this;
|
||||
result += rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr FpExt operator-(FpExt rhs) const {
|
||||
FpExt result = *this;
|
||||
result -= rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
constexpr FpExt operator-() const { return FpExt() - *this; }
|
||||
|
||||
// Implement the simple multiplication case by the subfield Fp
|
||||
// Fp * FpExt is done as a free function due to C++'s operator overloading rules.
|
||||
constexpr FpExt operator*=(Fp rhs) {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
elems[i] *= rhs;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr FpExt operator*(Fp rhs) const {
|
||||
FpExt result = *this;
|
||||
result *= rhs;
|
||||
return result;
|
||||
}
|
||||
|
||||
// Now we get to the interesting case of multiplication. Basically, multiply out the polynomial
|
||||
// representations, and then reduce module x^4 - B, which means powers >= 4 get shifted back 4 and
|
||||
// multiplied by -beta. We could write this as a double loops with some if's and hope it gets
|
||||
// unrolled properly, but it's small enough to just hand write.
|
||||
constexpr FpExt operator*(FpExt rhs) const {
|
||||
// Rename the element arrays to something small for readability
|
||||
#define a elems
|
||||
#define b rhs.elems
|
||||
return FpExt(a[0] * b[0] + NBETA * (a[1] * b[3] + a[2] * b[2] + a[3] * b[1]),
|
||||
a[0] * b[1] + a[1] * b[0] + NBETA * (a[2] * b[3] + a[3] * b[2]),
|
||||
a[0] * b[2] + a[1] * b[1] + a[2] * b[0] + NBETA * (a[3] * b[3]),
|
||||
a[0] * b[3] + a[1] * b[2] + a[2] * b[1] + a[3] * b[0]);
|
||||
#undef a
|
||||
#undef b
|
||||
}
|
||||
|
||||
constexpr FpExt operator*=(FpExt rhs) {
|
||||
*this = *this * rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Equality
|
||||
constexpr bool operator==(FpExt rhs) const {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
if (elems[i] != rhs.elems[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
constexpr bool operator!=(FpExt rhs) const { return !(*this == rhs); }
|
||||
|
||||
constexpr Fp constPart() const { return elems[0]; }
|
||||
|
||||
constexpr FpExt operator+=(FpExt rhs) device {
|
||||
for (uint32_t i = 0; i < 4; i++) {
|
||||
elems[i] += rhs.elems[i];
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr FpExt operator+(FpExt rhs) device const { return FpExt(*this) + rhs; }
|
||||
|
||||
constexpr FpExt operator-(FpExt rhs) device const { return FpExt(*this) - rhs; }
|
||||
|
||||
constexpr FpExt operator-() device const { return -FpExt(*this); }
|
||||
|
||||
constexpr FpExt operator*(FpExt rhs) device const { return FpExt(*this) * rhs; }
|
||||
|
||||
constexpr FpExt operator*(Fp rhs) device const { return FpExt(*this) * rhs; }
|
||||
|
||||
constexpr bool operator==(FpExt rhs) device const { return FpExt(*this) == rhs; }
|
||||
|
||||
constexpr bool operator!=(FpExt rhs) device const { return FpExt(*this) != rhs; }
|
||||
|
||||
constexpr Fp constPart() device const { return FpExt(*this).constPart(); }
|
||||
};
|
||||
|
||||
/// Overload for case where LHS is Fp (RHS case is handled as a method)
|
||||
constexpr inline FpExt operator*(Fp a, FpExt b) {
|
||||
return b * a;
|
||||
}
|
||||
|
||||
/// Raise an FpExt to a power
|
||||
constexpr inline FpExt pow(FpExt x, size_t n) {
|
||||
FpExt tot(1);
|
||||
while (n != 0) {
|
||||
if (n % 2 == 1) {
|
||||
tot *= x;
|
||||
}
|
||||
n = n / 2;
|
||||
x *= x;
|
||||
}
|
||||
return tot;
|
||||
}
|
||||
|
||||
/// Compute the multiplicative inverse of an FpExt.
|
||||
constexpr inline FpExt inv(FpExt in) {
|
||||
#define a in.elems
|
||||
// Compute the multiplicative inverse by basically looking at FpExt as a composite field and using
|
||||
// the same basic methods used to invert complex numbers. We imagine that initially we have a
|
||||
// numerator of 1, and a denominator of a. i.e out = 1 / a; We set a' to be a with the first and
|
||||
// third components negated. We then multiply the numerator and the denominator by a', producing
|
||||
// out = a' / (a * a'). By construction (a * a') has 0's in it's first and third elements. We
|
||||
// call this number, 'b' and compute it as follows.
|
||||
Fp b0 = a[0] * a[0] + BETA * (a[1] * (a[3] + a[3]) - a[2] * a[2]);
|
||||
Fp b2 = a[0] * (a[2] + a[2]) - a[1] * a[1] + BETA * (a[3] * a[3]);
|
||||
// Now, we make b' by inverting b2. When we multiply both sizes by b', we get out = (a' * b') /
|
||||
// (b * b'). But by construction b * b' is in fact an element of Fp, call it c.
|
||||
Fp c = b0 * b0 + BETA * b2 * b2;
|
||||
// But we can now invert C directly, and multiply by a'*b', out = a'*b'*inv(c)
|
||||
Fp ic = inv(c);
|
||||
// Note: if c == 0 (really should only happen if in == 0), our 'safe' version of inverse results
|
||||
// in ic == 0, and thus out = 0, so we have the same 'safe' behavior for FpExt. Oh, and since we
|
||||
// want to multiply everything by ic, it's slightly faster to premultiply the two parts of b by ic
|
||||
// (2 multiplies instead of 4)
|
||||
b0 *= ic;
|
||||
b2 *= ic;
|
||||
return FpExt(a[0] * b0 + BETA * a[2] * b2,
|
||||
-a[1] * b0 + NBETA * a[3] * b2,
|
||||
-a[0] * b2 + a[2] * b0,
|
||||
a[1] * b2 - a[3] * b0);
|
||||
#undef a
|
||||
}
|
||||
|
||||
#undef BETA
|
||||
#undef NBETA
|
||||
388
proving/igneum-prove-r0/patches/risc0-build-kernel/src/lib.rs
Normal file
388
proving/igneum-prove-r0/patches/risc0-build-kernel/src/lib.rs
Normal file
|
|
@ -0,0 +1,388 @@
|
|||
// Copyright 2025 RISC Zero, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
use std::{
|
||||
env, fs,
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
};
|
||||
|
||||
use rayon::prelude::*;
|
||||
use sha2::{Digest, Sha256};
|
||||
use tempfile::tempdir_in;
|
||||
|
||||
const METAL_INCS: &[(&str, &str)] = &[
|
||||
("fp.h", include_str!("../kernels/metal/fp.h")),
|
||||
("fpext.h", include_str!("../kernels/metal/fpext.h")),
|
||||
];
|
||||
|
||||
#[derive(Eq, PartialEq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub enum KernelType {
|
||||
Cpp,
|
||||
Cuda,
|
||||
Metal,
|
||||
}
|
||||
|
||||
pub struct KernelBuild {
|
||||
kernel_type: KernelType,
|
||||
flags: Vec<String>,
|
||||
files: Vec<PathBuf>,
|
||||
inc_dirs: Vec<PathBuf>,
|
||||
deps: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl KernelBuild {
|
||||
pub fn new(kernel_type: KernelType) -> Self {
|
||||
Self {
|
||||
kernel_type,
|
||||
flags: Vec::new(),
|
||||
files: Vec::new(),
|
||||
inc_dirs: Vec::new(),
|
||||
deps: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Add a directory to the `-I` or include path for headers
|
||||
pub fn include<P: AsRef<Path>>(&mut self, dir: P) -> &mut KernelBuild {
|
||||
self.inc_dirs.push(dir.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add an arbitrary flag to the invocation of the compiler
|
||||
pub fn flag(&mut self, flag: &str) -> &mut KernelBuild {
|
||||
self.flags.push(flag.to_string());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a file which will be compiled
|
||||
pub fn file<P: AsRef<Path>>(&mut self, p: P) -> &mut KernelBuild {
|
||||
self.files.push(p.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add files which will be compiled
|
||||
pub fn files<P>(&mut self, p: P) -> &mut KernelBuild
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: AsRef<Path>,
|
||||
{
|
||||
for file in p.into_iter() {
|
||||
self.file(file);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a file which will be compiled
|
||||
pub fn file_opt<P: AsRef<Path>>(&mut self, _p: P, _opt: usize) -> &mut KernelBuild {
|
||||
self
|
||||
}
|
||||
|
||||
/// Add files which will be compiled
|
||||
pub fn files_opt<P>(&mut self, _p: P, _opt: usize) -> &mut KernelBuild
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: AsRef<Path>,
|
||||
{
|
||||
self
|
||||
}
|
||||
|
||||
/// Add a dependency
|
||||
pub fn dep<P: AsRef<Path>>(&mut self, p: P) -> &mut KernelBuild {
|
||||
self.deps.push(p.as_ref().to_path_buf());
|
||||
self
|
||||
}
|
||||
|
||||
/// Add dependencies
|
||||
pub fn deps<P>(&mut self, p: P) -> &mut KernelBuild
|
||||
where
|
||||
P: IntoIterator,
|
||||
P::Item: AsRef<Path>,
|
||||
{
|
||||
for file in p.into_iter() {
|
||||
self.dep(file);
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn compile(&mut self, output: &str) {
|
||||
println!("cargo:rerun-if-env-changed=RISC0_SKIP_BUILD_KERNELS");
|
||||
for src in self.files.iter() {
|
||||
rerun_if_changed(src);
|
||||
}
|
||||
for dep in self.deps.iter() {
|
||||
rerun_if_changed(dep);
|
||||
}
|
||||
match &self.kernel_type {
|
||||
KernelType::Cpp => self.compile_cpp(output),
|
||||
KernelType::Cuda => self.compile_cuda(output),
|
||||
KernelType::Metal => self.compile_metal(output),
|
||||
}
|
||||
}
|
||||
|
||||
fn compile_cpp(&mut self, output: &str) {
|
||||
if env::var("RISC0_SKIP_BUILD_KERNELS").is_ok() {
|
||||
return;
|
||||
}
|
||||
|
||||
// It's *highly* recommended to install `sccache` and use this combined with
|
||||
// `RUSTC_WRAPPER=/path/to/sccache` to speed up rebuilds of C++ kernels
|
||||
cc::Build::new()
|
||||
.cpp(true)
|
||||
.debug(false)
|
||||
.files(&self.files)
|
||||
.includes(&self.inc_dirs)
|
||||
.flag_if_supported("/std:c++17")
|
||||
.flag_if_supported("-std=c++17")
|
||||
.flag_if_supported("-fno-var-tracking")
|
||||
.flag_if_supported("-fno-var-tracking-assignments")
|
||||
.flag_if_supported("-g0")
|
||||
.compile(output);
|
||||
}
|
||||
|
||||
fn compile_cuda(&mut self, output: &str) {
|
||||
println!("cargo:rerun-if-env-changed=NVCC_APPEND_FLAGS");
|
||||
println!("cargo:rerun-if-env-changed=NVCC_PREPEND_FLAGS");
|
||||
println!("cargo:rerun-if-env-changed=RISC0_CUDART_LINKAGE");
|
||||
println!("cargo:rerun-if-env-changed=NVCC_CCBIN");
|
||||
|
||||
for inc_dir in self.inc_dirs.iter() {
|
||||
rerun_if_changed(inc_dir);
|
||||
}
|
||||
|
||||
if env::var("RISC0_SKIP_BUILD_KERNELS").is_ok() {
|
||||
let out_dir = env::var("OUT_DIR").map(PathBuf::from).unwrap();
|
||||
let out_path = out_dir.join(format!("lib{output}-skip.a"));
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&out_path)
|
||||
.unwrap();
|
||||
println!("cargo:{}={}", output, out_path.display());
|
||||
return;
|
||||
}
|
||||
|
||||
let mut build = cc::Build::new();
|
||||
|
||||
for file in self.files.iter() {
|
||||
build.file(file);
|
||||
}
|
||||
|
||||
for inc in self.inc_dirs.iter() {
|
||||
build.include(inc);
|
||||
}
|
||||
|
||||
for flag in self.flags.iter() {
|
||||
build.flag(flag);
|
||||
}
|
||||
|
||||
if env::var_os("NVCC_PREPEND_FLAGS").is_none() && env::var_os("NVCC_APPEND_FLAGS").is_none()
|
||||
{
|
||||
build.flag("-arch=native");
|
||||
}
|
||||
|
||||
let cudart = env::var("RISC0_CUDART_LINKAGE").unwrap_or("static".to_string());
|
||||
|
||||
build
|
||||
.cuda(true)
|
||||
.cudart(&cudart)
|
||||
.debug(false)
|
||||
.ccbin(env::var("NVCC_CCBIN").is_err())
|
||||
.flag("-diag-suppress=177")
|
||||
.flag("-diag-suppress=2922")
|
||||
.flag("-Xcudafe")
|
||||
.flag("--display_error_number")
|
||||
.flag("-Xcompiler")
|
||||
.flag("-Wno-missing-braces,-Wno-unused-function")
|
||||
.compile(output);
|
||||
}
|
||||
|
||||
fn compile_metal(&mut self, output: &str) {
|
||||
// Igneum (6 October 2026): this Mac has the Command Line Tools and no Xcode, so no `metal` compiler.
|
||||
// In risc0 3.0.6 no circuit selects the Metal HAL (rv32im, recursion and keccak all have the Metal
|
||||
// arm commented out in their `cfg_if`), yet risc0-sys builds the zkp Metal kernels on every macOS
|
||||
// build and risc0-zkp `include_bytes!`s the result. With RISC0_SKIP_METAL_KERNELS set, an EMPTY
|
||||
// metallib is written instead, so the CPU prover builds; a Metal HAL would fail to load it.
|
||||
println!("cargo:rerun-if-env-changed=RISC0_SKIP_METAL_KERNELS");
|
||||
if env::var("RISC0_SKIP_METAL_KERNELS").is_ok() {
|
||||
let out_dir = env::var("OUT_DIR").map(PathBuf::from).unwrap();
|
||||
let out_path = out_dir.join(format!("skip-metal-{output}")).with_extension("metallib");
|
||||
fs::write(&out_path, []).unwrap();
|
||||
println!("cargo:warning=RISC0_SKIP_METAL_KERNELS: {output} is an empty metallib (no Metal compiler on this machine; the Metal HAL is unused in risc0 3.0.6)");
|
||||
println!("cargo:{}={}", output, out_path.display());
|
||||
return;
|
||||
}
|
||||
let target = env::var("TARGET").unwrap();
|
||||
let sdk_name = if target.ends_with("ios") {
|
||||
"iphoneos"
|
||||
} else if target.ends_with("ios-sim") {
|
||||
"iphonesimulator"
|
||||
} else if target.ends_with("darwin") {
|
||||
"macosx"
|
||||
} else {
|
||||
panic!("unsupported target: {target}")
|
||||
};
|
||||
|
||||
self.cached_compile(
|
||||
output,
|
||||
"metallib",
|
||||
METAL_INCS,
|
||||
&[],
|
||||
&[sdk_name.to_string()],
|
||||
|out_dir, out_path, sys_inc_dir, _flags| {
|
||||
let files: Vec<_> = self.files.iter().map(|x| x.as_path()).collect();
|
||||
|
||||
let air_paths: Vec<_> = files
|
||||
.into_par_iter()
|
||||
.map(|src| {
|
||||
let air_path = out_dir.join(src).with_extension("").with_extension("air");
|
||||
if let Some(parent) = air_path.parent() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
let mut cmd = Command::new("xcrun");
|
||||
cmd.args(["--sdk", sdk_name]);
|
||||
cmd.arg("metal");
|
||||
cmd.arg("-o").arg(&air_path);
|
||||
cmd.arg("-c").arg(src);
|
||||
cmd.arg("-I").arg(sys_inc_dir);
|
||||
cmd.arg("-Wno-unused-variable");
|
||||
for inc_dir in self.inc_dirs.iter() {
|
||||
cmd.arg("-I").arg(inc_dir);
|
||||
}
|
||||
println!("Running: {cmd:?}");
|
||||
let status = cmd.status().unwrap();
|
||||
if !status.success() {
|
||||
panic!("Could not build metal kernels");
|
||||
}
|
||||
air_path
|
||||
})
|
||||
.collect();
|
||||
|
||||
let result = Command::new("xcrun")
|
||||
.args(["--sdk", sdk_name])
|
||||
.arg("metallib")
|
||||
.args(air_paths)
|
||||
.arg("-o")
|
||||
.arg(out_path)
|
||||
.status()
|
||||
.unwrap();
|
||||
if !result.success() {
|
||||
panic!("Could not build metal kernels");
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
fn cached_compile<F: Fn(&Path, &Path, &Path, &[String])>(
|
||||
&self,
|
||||
output: &str,
|
||||
extension: &str,
|
||||
assets: &[(&str, &str)],
|
||||
flags: &[String],
|
||||
tags: &[String],
|
||||
inner: F,
|
||||
) {
|
||||
let out_dir = env::var("OUT_DIR").map(PathBuf::from).unwrap();
|
||||
if env::var("RISC0_SKIP_BUILD_KERNELS").is_ok() {
|
||||
let out_path = out_dir
|
||||
.join("skip-".to_string() + output)
|
||||
.with_extension(extension);
|
||||
fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.open(&out_path)
|
||||
.unwrap();
|
||||
println!("cargo:{}={}", output, out_path.display());
|
||||
return;
|
||||
}
|
||||
|
||||
let out_path = out_dir.join(output).with_extension(extension);
|
||||
let sys_inc_dir = out_dir.join("_sys_");
|
||||
|
||||
let cache_dir = risc0_cache();
|
||||
if !cache_dir.is_dir() {
|
||||
fs::create_dir_all(&cache_dir).unwrap();
|
||||
}
|
||||
|
||||
let temp_dir = tempdir_in(&cache_dir).unwrap();
|
||||
let mut hasher = Hasher::new();
|
||||
for flag in flags {
|
||||
hasher.add_flag(flag);
|
||||
}
|
||||
for tag in tags {
|
||||
hasher.add_flag(tag);
|
||||
}
|
||||
for src in self.files.iter() {
|
||||
hasher.add_file(src);
|
||||
}
|
||||
for (name, contents) in assets {
|
||||
let path = sys_inc_dir.join(name);
|
||||
if let Some(parent) = path.parent() {
|
||||
fs::create_dir_all(parent).unwrap();
|
||||
}
|
||||
fs::write(&path, contents).unwrap();
|
||||
hasher.add_file(path);
|
||||
}
|
||||
for dep in self.deps.iter() {
|
||||
hasher.add_file(dep);
|
||||
}
|
||||
let digest = hasher.finalize();
|
||||
let cache_path = cache_dir.join(digest).with_extension(extension);
|
||||
if !cache_path.is_file() {
|
||||
let tmp_dir = temp_dir.path();
|
||||
let tmp_path = tmp_dir.join(output).with_extension(extension);
|
||||
inner(tmp_dir, &tmp_path, &sys_inc_dir, flags);
|
||||
fs::rename(tmp_path, &cache_path).unwrap();
|
||||
}
|
||||
fs::copy(cache_path, &out_path).unwrap();
|
||||
|
||||
println!("cargo:{}={}", output, out_path.display());
|
||||
}
|
||||
}
|
||||
|
||||
fn risc0_cache() -> PathBuf {
|
||||
directories::ProjectDirs::from("com.risczero", "RISC Zero", "risc0")
|
||||
.unwrap()
|
||||
.cache_dir()
|
||||
.into()
|
||||
}
|
||||
|
||||
struct Hasher {
|
||||
sha: Sha256,
|
||||
}
|
||||
|
||||
impl Hasher {
|
||||
pub fn new() -> Self {
|
||||
Self { sha: Sha256::new() }
|
||||
}
|
||||
|
||||
pub fn add_flag(&mut self, flag: &str) {
|
||||
self.sha.update(flag);
|
||||
}
|
||||
|
||||
pub fn add_file<P: AsRef<Path>>(&mut self, path: P) {
|
||||
let bytes = fs::read(path).unwrap();
|
||||
self.sha.update(bytes);
|
||||
}
|
||||
|
||||
pub fn finalize(self) -> String {
|
||||
hex::encode(self.sha.finalize())
|
||||
}
|
||||
}
|
||||
|
||||
fn rerun_if_changed<P: AsRef<Path>>(path: P) {
|
||||
println!("cargo:rerun-if-changed={}", path.as_ref().display());
|
||||
}
|
||||
22
proving/igneum-prove-r0/pin-guest.sh
Executable file
22
proving/igneum-prove-r0/pin-guest.sh
Executable file
|
|
@ -0,0 +1,22 @@
|
|||
#!/usr/bin/env bash
|
||||
# Re-pins the RISC Zero shard guest (proof system version 2): builds the guest from methods/guest with risc0-build
|
||||
# (the rzup toolchain), computes its image id, writes proving/igneum-prove-r0/elf/ (the ELF and manifest-r0.json),
|
||||
# rebuilds the host so it embeds them, prints `--mode id`. Every version 2 prover and verifier must then move to a
|
||||
# host built from the new elf/ together. Run on the Mac through the build lock. Usage: proving/igneum-prove-r0/pin-guest.sh
|
||||
# Host features: on a Mac `metal`; set HOST_FEATURES to override (e.g. cuda on PC 2).
|
||||
set -euo pipefail
|
||||
HERE="$(cd "$(dirname "$0")" && pwd)"
|
||||
LOCK="$HERE/../../tools/lock/with-lock.sh"; [ -x "$LOCK" ] || LOCK=/Users/joshm/Projects/igneum/tools/lock/with-lock.sh
|
||||
export PATH="$HOME/.cargo/bin:$HOME/.risc0/bin:$PATH"
|
||||
cd "$HERE"
|
||||
# No `metal` compiler (Command Line Tools only): the host build writes an empty metallib (patches/risc0-build-kernel).
|
||||
if [ "$(uname)" = Darwin ] && ! xcrun --sdk macosx --find metal >/dev/null 2>&1; then export RISC0_SKIP_METAL_KERNELS=1; fi
|
||||
FEATURES="${HOST_FEATURES:-}"; if [ -z "$FEATURES" ] && [ "$(uname)" = Darwin ]; then FEATURES=metal; fi
|
||||
echo "1/4 building the guest (IGNEUM_BUILD_GUESTS=1, risc0-build) and the pin tool"
|
||||
"$LOCK" build env IGNEUM_BUILD_GUESTS=1 nice -n 19 cargo build --release -j 4 -p igneum-prove-r0-methods -p igneum-prove-r0-host --bin igneum-prove-r0-pin
|
||||
echo "2/4 pinning"
|
||||
./target/release/igneum-prove-r0-pin
|
||||
echo "3/4 rebuilding the host with the pinned files embedded (features: ${FEATURES:-none})"
|
||||
"$LOCK" build env nice -n 19 cargo build --release -j 4 -p igneum-prove-r0-host --bin igneum-prove-r0-host ${FEATURES:+--features "$FEATURES"}
|
||||
./target/release/igneum-prove-r0-host --mode id
|
||||
echo "4/4 done: commit proving/igneum-prove-r0/elf/ and methods/guest/Cargo.lock with the host change; every version 2 prover and verifier moves together"
|
||||
Loading…
Reference in a new issue