Parse and cryptographically verify Hedera record stream files — the signed consensus output every mainnet node publishes — both eras: v6 record files (mid-2022 onward) and HIP-1056 block streams. A dependency-light, no-GC Rust crate and a zero-runtime CLI.
Why trust another parser? The network is the reference. Correctness here is not "our tests pass" — every claim is anchored to something the network itself signed or an independent implementation computed:
- The network attests the parse. For v6, the consensus nodes'
signed metadata hashes reproduce from this parser's own extracted
fields — if the parse were wrong, the RSA signatures wouldn't
verify. (
verify,attest, and the offline tests all run this.) - Window-exhaustive mirror differential. Every transaction in every committed mainnet fixture window is compared field-for-field against the mirror node's independent decoding of the same files — this caught a real post-release response code (527) the vendored protos didn't know yet.
- Check-for-check proof-path agreement — across independent crypto
stacks. Block-era proof verification (hinTS BLS threshold,
aggregate Schnorr, WRAPS Groth16+KZG) is differentially tested
against
hiero-block-verifier-jsoverhiero-block-nodefixtures — every individual check, not just the verdict, must agree (tests/block_proof_differential.rs). The divergence is deliberate: the JS verifier is built on pure-JS@noble/curves, this crate on arkworks — two unrelated curve implementations in two languages deriving identical pairings, transcripts, and verdicts over the same bytes. Agreement between shared-dependency implementations would prove far less. - Continuity is proven, not assumed. The v6 running-hash chain and
the block-era root chain (recomputed merkle root == next footer's
claim) are asserted across every consecutive fixture pair and during
every
etl --verify-chainrun. - Committed snapshots pin the output contract so the canonical JSON shape cannot drift silently.
The CLI:
hiero-streams etl --dir downloaded/ --out data --transfers --verify-chain
# threaded backfill: record files → day-partitioned Parquet with a
# stable, contract-tested schema.
# --verify-chain asserts the running-hash chain within and across
# day boundaries as it transforms — the backfill is provably
# gapless and un-reordered by construction, or it exits non-zero
# naming the exact file where the chain breaks
hiero-streams parse file.rcd.gz # or file.blk.gz — format auto-detected
hiero-streams verify file.rcd.gz file.rcd_sig \
--address-book book.bin --node 0.0.3 # exit 0 only when valid
hiero-streams verify file.blk.gz --bootstrap genesis.blk.gz
# block era (build with --features block-proofs): verifies the
# in-band proof — recomputed merkle root, hinTS threshold
# signature, Schnorr/WRAPS suffix — no signature file to fetch.
# --bootstrap is the genesis block carrying the ledger-ID
# publication; genesis verifies with no flag
hiero-streams block-activity file.blk.gz
# per-block node liveness: which consensus nodes authored gossip
# events in the block — a signal the record era never exposed
hiero-streams attest file.rcd.gz --project <gcp-project>
# fetches every node's signature + the live address book;
# exit 0 only when >= 1/3 of the network signed these bytes
use hiero_streams::{parse_record_file, verify_record_file, parse_address_book};
let file = parse_record_file(&bytes)?; // .rcd.gz or .rcd, as-is
for tx in &file.transactions {
// consensus_timestamp, payer, tx_type, result, charged_fee_tinybar,
// transfers (every HBAR leg), token_transfers
}
let book = parse_address_book(&address_book_bytes)?;
let result = verify_record_file(&bytes, &signatures, &book)?;
result.attested; // >= 1/3 of the address book signed this exact fileThe record streams are the network's raw, signed output — mirror nodes, explorers, and dashboards are all derived from them. Reach for this crate when you need that ground truth directly:
- Indexers & analytics at bulk-history scale. The public mirror REST API serves ~100 rows per page behind rate limits; the streams are the firehose. Parse a full mainnet day (~43k files, ~300k transactions) in under a second and feed your own store — no API keys, no rate limits, no intermediary.
- Auditors, exchanges, custodians. "Prove this transaction
happened" should not mean "an API said so."
verify_record_fileturns a record file + signature files + the address book into a cryptographic attestation that ≥ ⅓ of the network signed these exact bytes. The CLI makes that a single static binary an auditor can run with zero runtime installed — for both eras:verifychecks a block stream's in-band proof the same way it checks a record file's node signature. - Checking derived data against ground truth. Mirror nodes can be wrong (we have found real discrepancies). When a balance, fee total, or supply figure looks off, the streams are what you reconcile against.
- The block-stream future, already here. Hedera is cutting over from record files to block streams (HIP-1056); at the network's 10k-TPS design target, a no-GC parser stops being a nicety. This crate already parses and proof-verifies the new format behind the same era-detecting API, validated against the mainnet preview.
- The check on the block-node era itself. HIP-1081 introduces a
new class of intermediaries — independent (including commercial)
block-node operators — and its trust model says consumers should
trust proofs, not node reputation. That sentence only means
something if independent proof verifiers exist: today there are
exactly two,
hiero-block-verifier-jsand this crate, and they are differentially tested against each other. More hands between the network and you strengthens the case for verifying, not weakens it. And the v6 era never migrates: four years of history exist only as record files (even served as HIP-1193 wrapped blocks, the payload is v6 bytes) — auditing it requires a maintained v6 verifier, forever.
Not the right tool for ordinary application reads — use the mirror REST API or a typed client for "get this account's balance." This is infrastructure for infrastructure-builders.
-
Throughput where it matters — measured honestly. A full real mainnet day (2026-07-10, node 0.0.3: 43,140 record files, 307,991 transactions; files preloaded, so this measures parsing, not disk; 8-core machine, best of 5 runs):
Configuration Full day Throughput Rust, single thread 2.61 s 118k tx/s Rust, 8 threads 0.37 s 834k tx/s Rust via Node binding (JSON round-trip) 6.31 s 49k tx/s A full day of consensus output — data that took 24 hours to produce — parses in about a third of a second. The structural edge is parallelism (7× here, near-linear on 8 cores — decisive for a multi-year backfill), no GC for long-running tails, and headroom for block-stream-era files carrying orders of magnitude more transactions each. The Node binding exists for identical-output portability, not speed — its JSON crossing costs more than the parse itself (reproduce with
cargo run --release --example parse_dir -- <dir>). -
A zero-runtime distributable.
hiero-streams verifyis a single static binary. -
One implementation, many languages. Rust exposes C FFI, so Python/Go/JVM bindings can wrap this one audited core.
-
Provably memory-safe on untrusted input. The library is
#![forbid(unsafe_code)]— a compile error for anyunsafeanywhere in the crate, generated protobuf code included. For a tool whose whole job is decoding attacker-controlled bytes, "the parse/verify core contains no memory-unsafety" is a guarantee the compiler enforces, not a claim in a README.
From Rust — use the crate directly (git dependency until the crates.io release):
[dependencies]
hiero-streams = { git = "https://github.com/hiero-hackers/hiero-streams-rs" }From any other language — shell out to the CLI; the contract is JSON on stdout and the exit code. No library linkage needed:
// Node.js
import { execFileSync } from "node:child_process";
const parsed = JSON.parse(
execFileSync("hiero-streams", ["parse", "file.rcd.gz"]),
);# Python
import json, subprocess
r = subprocess.run(["hiero-streams", "verify", rcd, sig,
"--address-book", book, "--node", "0.0.3"],
capture_output=True, text=True)
attested_by_node = r.returncode == 0
verdict = json.loads(r.stdout)The JSON shapes are stable — pinned by committed snapshot tests — so consumers can build against them without tracking this crate's internals.
Native Node binding — bindings/node (napi-rs) exposes
parseRecordFileJson, parseBlockJson, verifyBlockProofJson,
recordFileHashHex, and verifyNodeSignature, returning exactly the
golden-shape JSON every other surface produces — the binding's test
asserts its block-proof output is identical to the CLI's, both built
from one library serializer. Use it for one-audited-core portability;
for raw speed use the library natively (see the table above). PyO3 and
WASM remain on the roadmap.
The library and offline verification need only Rust 1.86+ (the
rust-version in Cargo.toml, enforced by CI) and protoc
(brew install protobuf). Anything touching the public
stream buckets (attest, downloads for etl) additionally needs a
Google Cloud project, because the buckets are requester-pays —
Google bills your project for reads; that is how the raw data stays
public without Hedera funding unbounded egress.
One-time setup (~10 minutes):
-
console.cloud.google.com → sign in → New project (any name; note the project ID).
-
Billing → attach a payment method. New accounts get ~$300 free credits, which comfortably cover everything in the cost table below.
-
Install and authenticate the CLI:
brew install --cask google-cloud-sdk gcloud auth login gcloud config set project YOUR_PROJECT_ID
That's all — attest shells out to gcloud auth print-access-token
(or set GCS_OAUTH_TOKEN), and downloads use
gcloud storage cp --billing-project=YOUR_PROJECT_ID.
Two drivers scale differently, and conflating them is how estimates go wrong (ours included, twice):
- File count is constant: one file per
2 s, 43,200/day,$26 for the whole v6 era**) and the latency-bound floor of download time.64 M for the v6 era — regardless of traffic. This drives GET-operation cost ($0.0004/1,000 → ** - File size scales with transactions: measured
600 bytes per transaction compressed. This drives egress ($0.12/GB → ~$0.07 per million transactions) and the bandwidth-bound part of download time.
Our measured anchor (mainnet hour 2026-07-04T00: 1,800 files, 8 MB, 3.8 tx/s) is a quiet-era floor. The v6 era includes the 2022–2024 high-TPS period (atma.io and other HCS-heavy applications pushed sustained hundreds-to-thousands of tx/s), whose days are 10–100× larger than today's. Cost therefore depends on total transactions, not days: at tens of billions of v6-era transactions, expect roughly 10–30 TB and $1,200–3,600 of egress for the full era — plus the fixed ~$26 of ops. Bounded windows stay cheap: a modern day ~$0.04, a month ~$1; a 2023 peak day up to a few dollars.
Time estimates (measured on this machine, one process; the quiet-era numbers are from a real 7-consecutive-day run, 2026-07-05 → 07-11, 302,211 files):
| Step | Measured | Extrapolation |
|---|---|---|
| Download, quiet day (43,200 files, ~0.2 GB) | ~220 files/s sustained (latency-bound; 3–3.5 min/day over 7 straight days) | ~3.5 min |
| Download, heavy 2023 day (43,200 files, tens of GB) | bandwidth-bound | ~30–60 min at 30 MB/s |
| Download, full v6 era | both regimes | ~4 days–2 weeks single machine (≈3.5-day latency floor at 220 files/s + the bandwidth-bound heavy era); parallelize by date range to cut linearly |
Parse/transform (etl), per day |
full week in 32.6 s → 4.7 s/day, disk reads + Parquet writes included | ~2 h of file overhead for the era's 64 M files, plus parse time on heavy days (parse core sustains 834k tx/s) |
Parsing is never the bottleneck; the wall-clock cost of a full backfill is download, and the dollar cost is egress on the heavy era. GET-ops and quiet-era egress are rounding errors. Fetching signature files from all ~29 nodes multiplies the op count ×29 — attest selectively, don't attest-per-file during a backfill.
Expect some nodes to be missing from the buckets: attestation needs only ⅓ of the address book, and individual nodes can go dark for extended periods while still listed (observed on mainnet: one node silent for months, making 28/29 the healthy baseline). The network's canonical daily activity record is the end-of-day payout from account 0.0.802 — a node absent from that transfer list was inactive the previous day:
/api/v1/transactions?transactiontype=CRYPTOTRANSFER&result=success&type=debit&account.id=0.0.802
hiero-streams etl is the fast path for bulk history: download record
files (gcloud storage cp, parallel + resumable), then run the
threaded pipeline. On a full mainnet week (2026-07-05 → 07-11:
302,211 files, 3,259,764 transactions) it transforms in 33 s on the
same 8-core machine — disk reads included — with --verify-chain
proving the entire week gapless and un-reordered: the running-hash
chain holds within every day and across all six midnight boundaries.
The dataset reconciles exactly: an independent re-parse of all 302k
files matches the Parquet dataset's per-day row counts and
sum(fee_tinybar) to the tinybar (week total 81,138.9769 ℏ). Query the
result from a laptop:
SELECT day, type, sum(fee_tinybar) / 1e8 AS fees_hbar
FROM read_parquet('data/transactions/*/*.parquet', hive_partitioning=1)
GROUP BY ALL ORDER BY day, fees_hbar DESC;Correctness is anchored to the network itself — its signatures and its own independent decoder — not to this crate agreeing with itself:
- The consensus nodes cryptographically attest the parse of every
file-level field. The signed metadata hash in each
.rcd_sigcovers version, HAPI version, both running hashes, and the block number — all fields this parser extracts. The test suite recomputes that hash from parsed fields and verifies it under the signing node's real RSA key: if the parse of those fields were wrong, the network's own signature would reject it. - The mirror node — the network team's own implementation — agrees
field-for-field.
tests/record_mirror_differential.rsparses committed mainnet record files and asserts set-equality of transactions plus per-transaction agreement on type, payer, result, fee, and complete transfer lists against mirror-node data fetched for exactly that consensus window (fixtures committed; the test runs offline). Because record files are the network's complete output, this is an exhaustive window comparison, not spot checks. - The chain proves extraction at scale: 1,800 consecutive mainnet files (13,587 transactions) chain-verify with zero breaks — every file's parsed running hashes and block number reconciling against its neighbors' (parsed in ~0.5 s).
- Signature verification is tested with real signed fixtures (from the hiero-mirror-node repo) and the address book that actually signed them: the signing node's key verifies, sibling keys reject, and a single flipped byte fails.
- Committed output snapshots pin the exact canonical-JSON shape for
the signed fixtures (
tests/record_snapshot.rs), so refactors cannot silently change what consumers receive.
The empirically-settled format facts are preserved bit-for-bit: the signed hash domain is the entire uncompressed file (version header included), and the RSA-3072 signature (SHA384withRSA) is over the 48-byte hash itself.
Documented here because it is specified nowhere else: HIP-435 defines
SignatureFile.metadata_signature but not the bytes the metadata hash
covers — the layout lives only in SignatureWriterV6.writeSignatureFile
(hiero-consensus-node). The preimage is the big-endian concatenation
int32(recordFileVersion = 6) | int32(hapi major) | int32(minor)
| int32(patch) | startObjectRunningHash.hash (48 raw bytes)
| endObjectRunningHash.hash (48 raw bytes) | int64(blockNumber)
hashed with SHA-384 and signed SHA384withRSA like the file signature.
Confirmed against real mainnet .rcd_sig files: the reconstructed
digest verifies under the signing node's RSA-3072 key (and any field
omitted, reordered, or byte-swapped fails). This matters for cheap
historical audits: metadata signatures let you verify chain continuity
(running hashes + block numbers) from ~1 KB sig files instead of
downloading the full record files — roughly a 300× data reduction over
the v6 era.
docs/ARCHITECTURE.md— how the crate works: data flow, module map, every dependency and why, the trust model, and what depends on this repo (the published JSON contract, Parquet schema parity).docs/MIGRATION.md— the HIP-1056 block migration: what changes and what provably doesn't, the proof-verification port plan (withhiero-block-verifier-jsas the reference implementation), tripwires to watch, and the cutover checklist.docs/CODE-TOUR.md— how to dig into the source: the module map, byte-trace paths, the design decisions you'd otherwise trip over, which test pins which contract, and recipes for common changes.ROADMAP.md— what's next, what was deliberately deferred, and the spikes behind the bigger dependency decisions.
- v6 record files (mainnet mid-2022 onward); earlier v2/v5 formats are rejected loudly.
- Block streams (HIP-1056) — parsing shipped, validated against the
mainnet preview. Proof verification shipped behind the
off-by-default
block-proofsfeature: all three in-band proof paths (aggregate Schnorr on BabyJubjub, hinTS BLS threshold on BLS12-381, WRAPS Groth16+KZG on BN254) plus block-merkle-root recomputation, differentially tested check-for-check againsthiero-block-verifier-jsover fixtures fromhiero-block-node. ETL shipped:etlera-detects its input and writes the same Parquet dataset contract from.blk.gzfiles, with--verify-chainasserting block-number gaplessness and root-chain continuity. GA labeling waits on the HIP-1193 cutover formalizing (wrapped record blocks — proposed in HIP PR #1427 — bridge the transition). - Full v6 verification: file + metadata signatures, ⅓-of-address-book
attestation (with the
attestfetcher), and the running-hash chain across consecutive files. - No I/O in the library: callers bring bytes (bucket clients, files), the crate parses and verifies. High-throughput backfill/ETL binaries are candidates for a companion crate.
Runnable illustrations under examples/ (cargo run --release --example <name> [-- args]):
| Example | Shows | Needs |
|---|---|---|
verify_offline |
full v6 verification (hash, file + metadata signatures) against bundled genuinely-signed fixtures | nothing |
verify_block |
block-era in-band proof (merkle root, hinTS, Schnorr + WRAPS) against bundled fixtures — --features block-proofs |
nothing |
parse_one |
one file → readable transaction lines | nothing (bundled fixture) |
chain_check -- <dir> |
running-hash chain over a directory — proves a sequence is gapless and un-reordered | downloaded files |
fee_report -- <dir> |
mini analytics: top fee payers + fees by type | downloaded files |
parse_dir -- <dir> |
throughput benchmark, sequential vs threaded | downloaded files |
Sanity anchor: on the bulk corpus, chain_check verified all 1,800
files chain-intact (blocks 97200914..=97202713) and fee_report's
total matches the Parquet dataset's sum(fee_tinybar) exactly.
proto/, proto-hapi/ vendored protobuf definitions (record era / block era)
build.rs prost codegen (two compile units, one per proto tree)
src/record/ v6 record files: parsing + trust (signatures, chain, attestation)
src/block/ HIP-1056 block streams: parsing + in-band proof verification
src/transaction.rs the shared output vocabulary both eras produce
src/cli/ the CLI (bin-only): parse / verify / block-activity / attest / etl
examples/ runnable illustrations (see Examples)
bindings/node/ the N-API binding
tests/ the contract pins — snapshots, differentials, real-crypto
tests/fixtures/ real signed stream files from dev-net, mainnet, and test networks
The golden files are committed snapshots of the parser's canonical
output for the fixtures. Regenerate one only on an intentional output
change (hiero-streams parse <fixture>), and review the diff as the
contract change it is. New fixtures are validated against mirror-node
data for their consensus window (see tests/record_mirror_differential.rs)
before their snapshots are committed.
CONTRIBUTING.md— how to build, the test matrix, coding conventions, and the PR/DCO process.CODE_OF_CONDUCT.md— Contributor Covenant 2.1.SECURITY.md— how to report a vulnerability privately. This crate verifies untrusted input, so parsing panics and verification-soundness issues are both in scope.
Apache-2.0 — see LICENSE. Not affiliated with or endorsed
by Hedera, Hashgraph, the Hiero project, or LF Decentralized Trust.