Rust SDK: offline verification¶
Beta: a Git dependency, not on crates.io, and not yet part of the cross-language interoperability run. Crate:
genesis-mesh-sdk1.7.0.
The Rust SDK (Rust SDK) verifies what the Network Authority (NA) signs
without calling it: decisions, evidence exports, single records, the NA’s key
history, store anchors and audit packs. These modules need no private key and
no network, and they work without the client feature
(default-features = false). They port the Python reference with the same
reason codes, and are tested against vectors the Python core produces. They
are separate code from the same project, so a difference from the Python
reference is a bug in one of them.
The SDK verifies what it is given; it fetches only some of it. It has no
client call for the NA key history (GET /na/key-history, public), store
anchors (GET /admin/evidence/anchors, read tier) or audit packs
(POST /admin/evidence/audit-pack, read tier). Fetch those with
HttpTransport (Rust SDK), or with the genesis-mesh CLI
(na succession history, evidence anchors fetch, audit pack). Evidence
exports come from evidence_store.export_text or export_all (read tier).
Module |
Verifies |
|---|---|
|
Decisions, evidence exports, single records, store anchors, join certificates and CRLs |
|
The NA’s key history: which NA key was valid when |
|
Audit packs, as |
|
The signed forms and digests of every record kind |
Decisions¶
verify::verify_boundary_decision checks a decision’s signature, expiry,
context and bindings. Before acting on a decision (as governed_action
does), judge it as of now:
use genesis_mesh_sdk::verify::{verify_boundary_decision, VerifyDecisionOptions};
use genesis_mesh_sdk::Value;
fn decision_now(decision: &Value, na_public_key: String, policy: Value) -> bool {
let check = verify_boundary_decision(
decision,
&VerifyDecisionOptions {
operator_public_keys: vec![na_public_key],
expected_policies: Some(vec![policy]),
..Default::default()
},
);
if !check.accepted {
eprintln!("not accepted: {}", check.reason);
}
check.accepted && check.authorized
}
accepted says the decision is authentic, unexpired and bound as expected;
authorized says it allows the action. To audit an archived decision, set
at_record_time: true (1.5.0): it is then judged as of its own
decision_made_at, so it still verifies years later. It is off by default,
because a decision about to be acted on must be judged as of now.
Key history¶
The NA’s signing key can change by succession. GET /na/key-history returns
the genesis block and the succession records; key_history::verify_key_history
checks them from the root public key you hold (each succession is signed
by the root key, the new key and the outgoing key) and returns the
NaKeyHistory:
use genesis_mesh_sdk::key_history::{verify_key_history, NaKeyHistory};
use genesis_mesh_sdk::verify::{verify_boundary_decision, VerifyDecisionOptions};
use genesis_mesh_sdk::Value;
fn key_history(view: &Value, root_public_key: &str) -> Option<NaKeyHistory> {
let successions = view["successions"].as_array().cloned().unwrap_or_default();
let checked = verify_key_history(&view["genesis"], &successions, Some(root_public_key));
for failure in &checked.failures {
eprintln!("{failure:?}");
}
checked.history
}
fn decision_archived(decision: &Value, history: NaKeyHistory) -> bool {
verify_boundary_decision(
decision,
&VerifyDecisionOptions {
at_record_time: true,
key_history: Some(history),
..Default::default()
},
)
.accepted
}
With a key history, VerifyDecisionOptions::key_history,
VerifyEvidenceOptions::key_history and VerifyRecordOptions::key_history
accept an NA signature only by the key valid at the record’s own time
(na_key_not_valid_at_time otherwise), with a 300-second hand-over. Without
one, every key given is valid at every time. The keys the history yields
prove nothing unless you got the root key some other way. See
Key Succession and the Root Key.
Evidence exports and records¶
verify::parse_export_lines reads a JSON Lines export, and
verify::verify_evidence_events checks the store chain, envelopes, every
signature, the decision and resource chains, retention checkpoints and every
later entry kind (observations, break-glass, judgements, quarantine,
remediation records, reviews, key successions, archives and grants):
use genesis_mesh_sdk::verify::{parse_export_lines, verify_evidence_events, VerifyEvidenceOptions};
use genesis_mesh_sdk::Value;
fn export(text: &str, na_public_key: String, executor_keys: Vec<Value>) -> genesis_mesh_sdk::Result<bool> {
let events = parse_export_lines(text)?;
let result = verify_evidence_events(
&events,
&VerifyEvidenceOptions::new(vec![na_public_key], executor_keys),
);
for failure in &result.failures {
eprintln!("{} at {:?}: {}", failure.reason, failure.store_sequence, failure.detail);
}
Ok(result.verified)
}
executor_keys is the list GET /admin/evidence/executor-keys returns
(evidence_store.list_executor_keys); retired keys still verify old records,
and an observer key verifies observations only. VerifyEvidenceOptions::new
is for a contiguous export; set contiguous: false for a filtered history,
checkpoint for chains whose early records retention removed, and prior
for a pack’s cited records. Verifying the Python export gives the same result
as the NA’s GET /admin/evidence/verify.
verify::verify_record(kind, record, options) verifies one record as an
export verifies it, with the same reason codes: any entry kind, a policy or
an attestation.
Store anchors¶
verify::parse_anchors reads the anchors you keep outside the NA,
verify::verify_store_anchors checks their signatures, chain and positions,
and verify::check_events_against_anchors ties an export to the anchors held
at both ends. Without anchors you hold, an export proves the integrity of what
it contains, not that nothing was removed. See
Evidence Anchors.
Audit packs¶
audit_pack::verify_audit_pack checks an audit pack as
genesis-mesh audit verify does. It takes each pack file’s path (with /)
and bytes, the root key and the anchors you hold:
use std::collections::BTreeMap;
use std::path::Path;
use genesis_mesh_sdk::audit_pack::{verify_audit_pack, VerifyPackOptions};
use genesis_mesh_sdk::Value;
fn read_pack(root: &Path, dir: &Path, files: &mut BTreeMap<String, Vec<u8>>) -> std::io::Result<()> {
for entry in std::fs::read_dir(dir)? {
let path = entry?.path();
if path.is_dir() {
read_pack(root, &path, files)?;
} else {
let name: Vec<String> = path.strip_prefix(root).unwrap().components()
.map(|c| c.as_os_str().to_string_lossy().into_owned()).collect();
files.insert(name.join("/"), std::fs::read(&path)?);
}
}
Ok(())
}
fn pack(dir: &Path, root_public_key: &str, held_anchors: Vec<Value>) -> std::io::Result<bool> {
let mut files = BTreeMap::new();
read_pack(dir, dir, &mut files)?;
let result = verify_audit_pack(&files, &VerifyPackOptions::new(root_public_key, Some(held_anchors)));
for failure in &result.failures {
eprintln!("{}: {:?}", failure.reason, failure.file);
}
Ok(result.verified)
}
Without held anchors the result warns no_held_anchors. render_report
renders the pack’s report.md byte for byte, differences_from_pack_checks
compares the result with the pack’s own checks.json, and parse_changes
with check_completeness compare the pack with a change log kept outside
Genesis Mesh. See Audit Packs.
The genesis-mesh-verify binary¶
genesis-mesh-verify (from 1.5.0) is a static Linux binary built from these
modules, released from the sdk-rust repository with checksums and Sigstore
bundles, and reproducible from source. An auditor runs it on their own
machines:
genesis-mesh-verify pack pack-2026 --anchor root.pub --known-anchors /audit/anchors
genesis-mesh-verify export export.jsonl --key-history key-history.json --root-key root.pub \
--executor-keys executor-keys.json --known-anchors /audit/anchors --json
genesis-mesh-verify record decision decision.json --key-history key-history.json --root-key root.pub
genesis-mesh-verify key-history key-history.json --root-key root.pub
Exit codes: 0 verified, 1 not verified, 2 usage error, 3 an input could not be read. See The Rust Verifier.