diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c3eb29d9..df3ee919 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -75,12 +75,14 @@ jobs: - name: Check workspace run: cargo check --release --workspace - # zk-core must build in a no_std/WASM context. Guard against a consumer or - # feature change that leaks `std` into the runtime build. - - name: Check zk-core no_std feature matrix + # zk-core and zk-verifier must build in a no_std/WASM context. Guard against + # a consumer or feature change that leaks `std` into the runtime build. + - name: Check zk primitives no_std feature matrix run: | cargo check -p orbinum-zk-core --no-default-features cargo check -p orbinum-zk-core --no-default-features --features poseidon-native + cargo check -p orbinum-zk-verifier --no-default-features + cargo check -p orbinum-zk-verifier --no-default-features --features groth16-native # Step 3: Build workspace - compiles once for all subsequent jobs to reuse build: diff --git a/Cargo.lock b/Cargo.lock index 98163772..bfb44c03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10075,7 +10075,7 @@ dependencies = [ [[package]] name = "orbinum-zk-verifier" -version = "3.2.0" +version = "3.3.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", @@ -12929,7 +12929,7 @@ dependencies = [ [[package]] name = "pallet-zk-verifier" -version = "0.17.1" +version = "0.18.0" dependencies = [ "ark-bn254", "ark-ec 0.5.0", diff --git a/frame/zk-verifier/CHANGELOG.md b/frame/zk-verifier/CHANGELOG.md index b2b2285a..b14f8d64 100644 --- a/frame/zk-verifier/CHANGELOG.md +++ b/frame/zk-verifier/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this pallet are documented here. ## [Unreleased] +## [0.18.0] - 2026-10-13 + +### Added + +- Feature `groth16-native`: the pairing runs through the node's + `bn254_groth16_verify` host function instead of in Wasm. Same function, same + results; off by default. + ## [0.17.1] - 2026-10-09 ### Changed diff --git a/frame/zk-verifier/Cargo.toml b/frame/zk-verifier/Cargo.toml index ec669939..e37fe78d 100644 --- a/frame/zk-verifier/Cargo.toml +++ b/frame/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "pallet-zk-verifier" -version = "0.17.1" +version = "0.18.0" description = "Zero-Knowledge proof verification pallet for Orbinum" authors = ["Orbinum Team"] license = "GPL-3.0-or-later" @@ -74,6 +74,7 @@ runtime-benchmarks = [ "dep:ark-groth16", ] skip-proof-verification = [] +groth16-native = ["orbinum-zk-verifier/groth16-native"] try-runtime = [ "frame-support/try-runtime", "frame-system/try-runtime", diff --git a/frame/zk-verifier/src/verifier.rs b/frame/zk-verifier/src/verifier.rs index 73213f3c..375a4cc3 100644 --- a/frame/zk-verifier/src/verifier.rs +++ b/frame/zk-verifier/src/verifier.rs @@ -130,8 +130,9 @@ fn record_stats(circuit_id: CircuitId, version: u32, result: bool) { }); } -/// The pairing, under a key in prepared form: [`verify_prepared`], the same -/// function the node's `bn254_groth16_verify` host function runs. +/// The pairing, under a key in prepared form: [`verify_prepared`], natively +/// through the node's `bn254_groth16_verify` host function under +/// `groth16-native`, in Wasm otherwise. /// /// Always `true` in **test** builds, whose keys deserialize but carry no real /// proofs. Benchmarks run the full pairing, so weights include it. @@ -144,7 +145,16 @@ fn do_verify(prepared: &[u8], proof: &[u8], inputs: &[[u8; 32]]) -> bool { true } - #[cfg(not(test))] + #[cfg(all(not(test), feature = "groth16-native"))] + { + orbinum_zk_verifier::host_interface::groth_16_host_interface::bn254_groth16_verify( + prepared, + proof, + &inputs.concat(), + ) + } + + #[cfg(all(not(test), not(feature = "groth16-native")))] { orbinum_zk_verifier::verify_prepared(prepared, proof, &inputs.concat()) } diff --git a/primitives/zk-verifier/CHANGELOG.md b/primitives/zk-verifier/CHANGELOG.md index 57140afc..e684f473 100644 --- a/primitives/zk-verifier/CHANGELOG.md +++ b/primitives/zk-verifier/CHANGELOG.md @@ -6,6 +6,14 @@ All notable changes to this crate are documented here. ## [Unreleased] +## [3.3.0] - 2026-10-13 + +### Changed + +- `bn254_groth16_verify` drops `register_only` (`#[version(1)]`): runtimes can + import it. Symbol and behaviour unchanged; nodes from 0.4.0 already register + it. + ## [3.2.0] - 2026-10-09 ### Added diff --git a/primitives/zk-verifier/Cargo.toml b/primitives/zk-verifier/Cargo.toml index 0bcf0304..ba1e4dd2 100644 --- a/primitives/zk-verifier/Cargo.toml +++ b/primitives/zk-verifier/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orbinum-zk-verifier" -version = "3.2.0" +version = "3.3.0" authors = ["Orbinum Network "] edition = "2021" publish = false diff --git a/primitives/zk-verifier/src/host_interface.rs b/primitives/zk-verifier/src/host_interface.rs index 72bcd028..2c78f643 100644 --- a/primitives/zk-verifier/src/host_interface.rs +++ b/primitives/zk-verifier/src/host_interface.rs @@ -3,10 +3,8 @@ //! //! - **Frozen.** Executed blocks call version 1 and syncing nodes re-run them: //! any observable change is a new `#[version(N)]`, and version 1 stays. -//! - **`register_only`.** A node without the function cannot instantiate a -//! runtime that imports it, so nodes register it first and no runtime calls -//! it yet. A later runtime drops the flag once every node runs a release -//! with it (minimum author version for authors; RPC and archive by hand). +//! - **Imported from spec 19.** Registered `register_only` since node 0.4.0; +//! a node without it cannot instantiate a runtime that imports it. use alloc::vec::Vec; use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface}; @@ -16,7 +14,7 @@ use sp_runtime_interface::{pass_by::PassFatPointerAndRead, runtime_interface}; pub trait Groth16HostInterface { /// [`crate::verify_prepared`], natively. It must never panic: natively a /// panic takes the node down instead of trapping the runtime. - #[version(1, register_only)] + #[version(1)] fn bn254_groth16_verify( prepared_vk: PassFatPointerAndRead<&[u8]>, proof: PassFatPointerAndRead<&[u8]>, diff --git a/template/runtime/Cargo.toml b/template/runtime/Cargo.toml index da0720ca..76cd20d3 100644 --- a/template/runtime/Cargo.toml +++ b/template/runtime/Cargo.toml @@ -102,7 +102,7 @@ sp-maybe-compressed-blob = "11.1.0" grandpa-verifier-primitives = { workspace = true, features = ["std"] } [features] -default = ["std", "with-rocksdb-weights", "poseidon-native-runtime"] +default = ["std", "with-rocksdb-weights", "poseidon-native-runtime", "groth16-native-runtime"] with-rocksdb-weights = [] with-paritydb-weights = [] @@ -116,6 +116,9 @@ hyperbridge-testnet = [] poseidon-native-runtime = [ "pallet-shielded-pool/poseidon-native", ] +groth16-native-runtime = [ + "pallet-zk-verifier/groth16-native", +] std = [ "ethereum/std", diff --git a/template/runtime/RUNTIME_VERSIONS.md b/template/runtime/RUNTIME_VERSIONS.md index 32ba9d93..81a66796 100644 --- a/template/runtime/RUNTIME_VERSIONS.md +++ b/template/runtime/RUNTIME_VERSIONS.md @@ -28,7 +28,8 @@ every EVM priority tip goes to the validator that authored the block. `transaction_version` moves: pallet index 16 and `shielded_pool` call 18 are gone, and call 19 is renamed. Ships with `orbinum-runtime` / `orbinum-node` 0.5.0, `pallet-shielded-pool` 0.25.0, `pallet-shielded-pool-runtime-api` 0.4.0, -`pallet-evm-precompile-shielded-pool` 0.10.0 and `pallet-validator-set` 0.4.1. +`pallet-evm-precompile-shielded-pool` 0.10.0, `pallet-validator-set` 0.4.1, +`pallet-zk-verifier` 0.18.0 and `orbinum-zk-verifier` 3.3.0. #### 1 · Block author is the session validator @@ -61,6 +62,15 @@ A transaction's priority tip goes to the block author's validator account (`TipToValidator`); `block.coinbase` is the zero address. It used to be bytes 4..24 of the Aura key, whose mapped account no key controls. +#### 4 · Groth16 host function + +The runtime imports `bn254_groth16_verify` (`groth16-native-runtime`): proof +verification runs natively instead of in Wasm (`verify_proof` 2.43 → 0.61 ms). +Same function on both paths, so results do not change. Nodes register it since +0.4.0; an older node cannot instantiate this runtime and stops importing, so +every node (validators, RPC, archive) must run ≥ 0.4.0 before `setCode`. +Weights are unchanged (measured in Wasm, so they overcharge). + #### Migrations - `RelayerFeesToPool` — drains `Relayer::PendingRelayerFees` into diff --git a/template/runtime/build.rs b/template/runtime/build.rs index ccc1f0c2..58390b9a 100644 --- a/template/runtime/build.rs +++ b/template/runtime/build.rs @@ -6,11 +6,13 @@ fn main() { .export_heap_base() .import_memory(); - // Poseidon via host functions (~3× faster). Skipped for try-runtime - // builds: the stock try-runtime CLI executor lacks the custom host - // interface, so those builds fall back to the pure-wasm hasher. + // Poseidon (~3×) and Groth16 (~3.5×) via host functions. Skipped for + // try-runtime builds: the stock try-runtime CLI executor lacks the + // custom host interfaces, so those builds fall back to pure Wasm. if std::env::var("CARGO_FEATURE_TRY_RUNTIME").is_err() { - builder = builder.enable_feature("poseidon-native-runtime"); + builder = builder + .enable_feature("poseidon-native-runtime") + .enable_feature("groth16-native-runtime"); } builder.enable_metadata_hash("ORB", 18).build(); diff --git a/template/runtime/src/runtime_tests.rs b/template/runtime/src/runtime_tests.rs index 13663f72..36273988 100644 --- a/template/runtime/src/runtime_tests.rs +++ b/template/runtime/src/runtime_tests.rs @@ -588,21 +588,18 @@ fn signed_extra_implicit_succeeds_with_check_metadata_hash() { // ── Host functions ──────────────────────────────────────────────────────────── -/// The runtime must not import `bn254_groth16_verify` before every node -/// registers it: a node without it cannot instantiate the runtime and stops -/// importing. Flip when a runtime turns it on. +/// Spec 19 imports `bn254_groth16_verify`, registered since node 0.4.0. A +/// node without it cannot instantiate the runtime and stops importing. #[test] -fn the_runtime_does_not_import_the_groth16_host_function_yet() { +fn the_runtime_imports_the_groth16_host_function() { let Some(blob) = crate::WASM_BINARY else { return; // SKIP_WASM_BUILD }; // Release builds embed the compressed blob. let wasm = sp_maybe_compressed_blob::decompress(blob, 64 << 20).expect("runtime blob"); let imports = |symbol: &[u8]| wasm.windows(symbol.len()).any(|w| w == symbol); - // The search works: Poseidon's host function is imported. - assert!(imports(b"ext_poseidon_host_interface_poseidon_hash_2")); - assert!(!imports( - b"ext_groth_16_host_interface_bn254_groth16_verify" + assert!(imports( + b"ext_groth_16_host_interface_bn254_groth16_verify_version_1" )); }