Skip to content

Verifiable computation & batch jobs

You have computation that is too heavy or too sensitive to trust to one server, and whose results others will rely on: hash chains and commitments, data transformations, simulations, scoring runs. You want several independent machines to compute it, a guarantee they agreed, and a way for anyone to check a result later.

  • A committee of miners, picked by sortition the operator cannot steer, runs every job; validators re-execute a random 10 % and every contested job.
  • Each result is a receipt hash covering module, function, input, output, events and gas: compare it, store it, re-check it with ndsr run years later.
  • Scheduled projects get an unpredictable seed per slot, so jobs can be randomized fairly without anyone controlling the randomness.

The testnet’s reference project (hashchain-lab) does exactly this. Every 60 seconds the Hub schedules a task seeded by the epoch beacon; a committee of 3 miners walks a 64-link Keccak chain from the seed and must agree on the final link and on the best (lowest) link seen. Miners earn 1 NECTA per unit (85 % miner share).

worker/src/lib.rs
//! Hash-chain worker for the testnet example project `hashchain-lab`.
//!
//! Stateless (no storage, no hive.call), so committees of miners can run it. The Hub's
//! schedule task source calls `work` with `{"epoch", "project_id", "seed", "seq"}`; the module walks a keccak
//! chain from the slot seed and reports the final link plus the best (lowest) link seen, a toy
//! proof-of-work whose answer every honest committee member reproduces exactly.
use hivekit::prelude::*;
const DEFAULT_ROUNDS: u64 = 64;
const MAX_ROUNDS: u64 = 4096;
fn leading_zero_bits(h: &str) -> u32 {
let mut bits = 0;
for c in h.trim_start_matches("0x").chars() {
let v = c.to_digit(16).unwrap_or(0);
if v == 0 {
bits += 4;
} else {
bits += v.leading_zeros() - 28;
break;
}
}
bits
}
/// `{"seed": "0x…", "seq": n, "rounds"?: n}` (extra keys ignored) → `{"head", "best", "best_zero_bits", "rounds"}`
#[hive_export]
fn work(input: Value) -> Result<Value, String> {
let seed = input["seed"].as_str().ok_or("`seed` must be a hex string")?;
let seq = input["seq"].as_u64().unwrap_or(0);
let rounds = input["rounds"].as_u64().unwrap_or(DEFAULT_ROUNDS).clamp(1, MAX_ROUNDS);
let mut link = hash(format!("{seed}:{seq}").as_bytes());
let mut best = link.clone();
for _ in 1..rounds {
link = hash(link.as_bytes());
if link < best {
best = link.clone();
}
}
Ok(json!({ "head": link, "best": best, "best_zero_bits": leading_zero_bits(&best), "rounds": rounds }))
}
hive_module!(work);
worker/Cargo.toml
[package]
name = "hashchain-worker"
version = "1.0.0"
edition = "2021"
publish = false
[lib]
crate-type = ["cdylib"]
path = "src/lib.rs"
[dependencies]
hivekit = { path = "../necter-sdk/hivekit-rs" }
[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"
Terminal window
hivec build # → dist/hashchain_worker.hbc
ndsr run dist/hashchain_worker.hbc work --gas 2000000 --input \
'{"epoch":497631,"project_id":"0x84588f60bd2190a6c7929d655010de448e15d22d61ec8021b29ef2c56a553743","seed":"0x1f0b4c6f2d8e3a9b7c5d1e0f2a4b6c8d0e1f3a5b7c9d2e4f6a8b0c1d3e5f7a9b","seq":12}'
{"best":"0x01ba17c8fccc8f2b61755ec8b61883fe1c863b702a9ba48048813f5613ddbbd2","best_zero_bits":7,"head":"0x1829bc42032738828d410524849976d92660b9b21471f722647919f60bfa329c","rounds":64}

gas_used: 89288. Built from the SDK revision deployed on the testnet, the artifact’s address is exactly the live worker’s, 0xa99c85c5c7c08f302256cad48b51d7c363a6fcea09d5d48e570c711eed8e6d90 (GET /v1/modules/{address} reports "stateless": true). A different SDK revision gives a different address but the same outputs.

Terminal window
P=0x84588f60bd2190a6c7929d655010de448e15d22d61ec8021b29ef2c56a553743
curl -s "https://testnet-rpc.necter.network/v1/projects/$P/rounds?limit=5"
curl -s "https://testnet-rpc.necter.network/v1/projects/$P" # economics, stats, miners

Each round shows its committee size, votes, agreeing count, whether it was audited, gas and receipt hash.

  1. Built and uploaded the worker (POST /v1/modules).
  2. Manifest with a schedule task source: {"kind": "schedule", "function": "work", "interval_secs": 60, "gas_limit": 2000000}, committee size 3 (min 3, no backups), round_secs 60, epoch_secs 3600, per-unit rewards of 1 NECTA, daily emission 24,000 NECTA, split 8500/1000/500, min_collateral 10 NECTA, all device classes and engines. The complete document is on The project manifest.
  3. prepare → personal_sign → POST /v1/developers/projects → gasless register.
  4. Operator review → listed.
  5. createVault (developer-paid gas) and fund with 100,000 NECTA.

Swap the hash chain for your computation:

  • Submitted jobs (task_source: {"kind": "api"}): your backend posts each chunk of data as a task input (≤ 1 MiB) and gets back the agreed output. Split large datasets into chunks and combine the per-chunk digests (for example in a Merkle tree) to get one verifiable digest for the whole run.
  • Generated jobs (schedule): derive the job from seed, epoch and seq, for example a simulation run or a random audit sample of a published dataset.
  • Return digests of large results instead of the results themselves when outputs would be big; outputs are limited to 1 MiB.
Hash Chain Lab
Gas per job ~89k → 1 unit per agreeing member
Jobs per day 1,440 (one per minute)
Committee 3
Max miner units per day 4,320 → at 1 NECTA/unit gross, 3,672 NECTA to miners (85 %)
Daily emission cap 24,000 NECTA (not reached at this rate)

Heavier jobs earn more units per round (ceil(gas_used / 1,000,000)), so pricing scales with work done.