The artifacts: what each one is, what makes it, what reads it

August 27, 2026 · View on GitHub

Between the commands and the byte formats there is a layer that neither documents: which files exist, which command produces each, which commands can then read it, and what you can skip. This is that map.

Byte layouts are in formats/; the interfaces that read them are in contracts/; the commands themselves, in the order they are run, are in build-and-query.md. Directory names below are placeholders: every one of them is a path you choose on the command line.

What this version emits, and what it still reads

artifactemitsalso reads
graphgraph-v2
headersheaders-v2
revelation archivereveal-archive-v2
nonce censusnonces-v3nonces-v2
nonce witness tablenonces-witness-v1
outpoint indexoutpoint-index-v3outpoint-index-v2
outpoint derivativesoutpoint-derived-v3outpoint-derived-v2
first-spend tablefirstspend-v1
first-reveal tablefirstreveal-v1
block statsblock-stats-v2
price series (external input)price-series-v1
block price (external input, derived)blockprice-v1
address book (input)address-book-v2
check report (output)check-report-v2

One sealed output is deliberately not a row above: derived timeline seals a derived-timeline-v1 meta beside its two CSVs — the same CSV-plus-sealed-meta shape as block stats, but produced by the derivatives module rather than a module of its own, and the table maps modules to the one artifact each emits.

Reading widens; emission never does. Where a previous format is listed, an artifact sealed under it still verifies and still answers questions, so what you downloaded keeps its value. It cannot be extended or rewound: both operations promise the bytes a rebuild would have written, and a fusion across two layouts matches no rebuild. Each refusal says which format it met and why.

This table is not maintained by hand. It is checked against the modules' FORMAT_TAG and READ_TAGS by the test suite, so a format that moves without the documentation moving fails the build rather than misleading a reader.

The flow

One pass over the chain is all you need. It is the only long step that talks to the node, and its co-emission flags feed everything else from it: the archive of revelations, the raw graph the index and the derivatives are built from, and the header chain that lets the pass's own checks be repeated later.

                ┌─ census ─────────────────────────────► census.csv   (context: the set by type and age)
  <snapshot> ──►│
  (dumptxoutset)└─ reuse prepare ──────────────────────► <locks>/     (the current UTXO locks)

   ONE PASS OVER THE CHAIN, over RPC or REST                  │
   archive scan ──────────┬──► <archive>/ ──► archive merge ──┴──► archive derive
     --graph --headers    │      ├ runs/…_keys.bin        (seal +      │
     --nonces             │      ├ runs/…_scripts20.bin   fingerprint) ├─► reuse table
                          │      ├ runs/…_scripts32.bin                └─► curve.csv ──► curve deltas
                          │      └ state.json → manifest.json
                          │            (merged) ──► firstreveal build ──► <firstreveal>/
                          │                                (keys by first-reveal time)

                          ├──► <headers>/ ──► headers fingerprint (seal)
                          │         │
                          │         ├──► headers verify / crosscheck --index
                          │         └──► curve dates            (no node needed)

                          ├──► <nonces>/ ──► nonces merge (seal)
                          │         │
                          │         ├──► nonces groups ──► the repeated points
                          │         ├──► nonces resolve ──► <witness>/  (needs the node)
                          │         │         └──► the resolution on each repeated point
                          │         └──► nonces lookup / verify / rewind

                          └──► <graph>/ ──► graph fingerprint (seal)

                                   ├──► blockstats build ──► block-stats CSV

                                   └──► index build ──► <index>/ ──► derived build ──► <derived>/
                                                                  (a lock's history, fees, co-spends)

   everything above, plugged into:  check ──► per-address answers

--graph is optional: leave it out if you only came for the exposure question and will never want fees, history or co-spends. It costs disk, not time: the pass happens either way, and doing it twice would not.

--nonces is optional on the same terms, with one difference: it costs about 10% of the pass's CPU as well as its disk, because it reads the signatures the other artifacts throw away. It is the one addition here that a later pass could not reconstruct, since nothing kept afterwards holds unlocking data. See nonce-check.md for what it answers, alone and together with the index.

Keep the perimeter identical between scan and derive. --no-faces and --no-cosigners narrow what counts as a revelation; they exist for exploring, but a narrowed scan and a full derive describe different questions, and the comparison refuses rather than quietly mixing them.

What you actually need

Not all of it. Pick by the question you came for.

If you wantBuild
the UTXO set by type and agesnapshot → census
"has this key already been revealed?"snapshot → reuse prepare, then archive scanarchive merge
how much value sits behind revealed keysthe above, then archive derive --locks
reuse over time, as a seriesarchive derive --curvecurve deltas
fees, a lock's history, co-spendsarchive scan --graphindex buildderived build
per-block statisticsarchive scan --graphblockstats build
to repeat the scan's checks later, or to put real dates on a curvearchive scan --headersheaders fingerprint
which keys were first revealed in a height window, as a contiguous readthe archive above, then firstreveal build
whether a signing key ever gave itself away by repeating a noncearchive scan --noncesnonces mergenonces groups
the same, for one of your addressesindex buildderived build, then nonces address (needs a node)
fiat figures, one price per blockindex build, then a publisher's series you fetched → price importprice build (an external input, not an artifact: external-inputs)

The second road, and why it is not in the list

There is another way to count reuse: reuse scan walks the chain comparing every revelation against the locks as it goes, keeping a bitmap of a few megabytes instead of tens of gigabytes, and archive crosscheck then derives the same figure from the sealed archive and puts the two side by side. Different data structures, different order of work, different moment of comparison.

What that comparison covers, precisely: the two extraction pipelines are written separately, and they are what it tests. Both roads then burn the same lock files through the same lookup code, so a broken locks directory would make them agree rather than disagree, which is why the files are verified against the sha256 their manifest recorded at prepare, and why crosscheck --reuse-state refuses a checkpoint made against a different locks manifest.

It is worth knowing that road exists, and the commands are here for anyone who wants to walk it. But it costs a second full pass over the chain, and what it buys is confidence in the method rather than a number you do not already have. Treat it as a result to inherit, not a step to repeat — which is how the numbers published with this project were produced: run once, agreed, reported.

The artifacts

ArtifactFormatWhat it isProduced byRead by
<snapshot>dumptxoutset v2The UTXO set at one block: the pinned root of every count belowbitcoin-cli dumptxoutsetcensus, reuse prepare
census.csvCSVTotals per script type and height band. Aggregates only: no individual coincensusa human
<locks>/locks-v1The current locks: sorted digests of unspent outputs, one file per typereuse preparereuse scan, archive crosscheck
locks_{p2pkh,p2sh,p2wpkh,p2wsh}.binsorted recordsOne lock type per filereuse prepareas above
manifest.jsonlocks-v1Pins the snapshot's base hash (so a scan stops at that height) and each file's record count and sha256, which every reader checks before burning a lockreuse prepareas above
<archive>/reveal-archive-v2Every key and script ever revealed, appendablearchive scanarchive merge/verify/derive/crosscheck/lookup/v1-digests, check
runs/…_keys.binrecordshash160 of public keys revealed in a scriptSig or witnessarchive scanas above
runs/…_scripts20.binrecordshash160 of candidate redeem scriptsarchive scanas above
runs/…_scripts32.binrecordssha256 of candidate witness scriptsarchive scanas above
manifest.jsonreveal-archive-v2The canonical fingerprint, written by mergearchive mergearchive verify
curve.csvCSVOne row per height step: this is the reuse curve over timearchive derive --curvecurve deltas
<headers>/headers-v2The header chain the scan verified, from genesis: 88 B per height plus each coinbase script. Off by default, enabled with --headers (~150 MB)archive scan --headersheaders verify/crosscheck, curve dates
headers.binrecordsThe 80 header bytes verbatim, then the block's size and weightarchive scan --headersas above
coinbase.bin, coinbase_off.binrecordsEach block's coinbase scriptSig, and where it startsarchive scan --headersas above
manifest.jsonheaders-v2Fingerprint and coverage 0..H; no parent, it comes from the blocksheaders fingerprintverification
<nonces>/nonces-v3Every signature nonce point ever published, with the height. Off by default, enabled with --nonces (~55-60 GB): the repeated ones are the candidates for a key recoverable from public data, which a block re-read confirms or rules outarchive scan --noncesnonces groups/lookup/verify/rewind, nonces address (with the index and a node)
nonces_gNNNN.binrecordsOne 16-byte record per signature: point, height, scheme, and the sighash mode it committed toarchive scan --noncesas above
manifest.jsonnonces-v3Fingerprint and coverage; no parent, it comes from the blocksnonces mergenonces verify
<witness>/nonces-witness-v1The evidence that resolves each repeated point: per (nonce point, public key), the signatures that decide whether a key follows. Optional, built after the census (~36 min over the whole chain, a few MB)nonces resolve (needs the node)nonces witness-verify
<graph>/graph-v2The raw transaction graph. Off by default, enabled with --grapharchive scan --graphgraph, blockstats, index build
block-stats CSVblock-stats-v2Per-block series (transactions, inputs, outputs, size, time) derived from the graphblockstats buildblockstats summary, a human
<index>/outpoint-index-v3The chain numbered once: a record per output, its spend already resolvedindex buildindex lookup, derived build, check
outputs.bin, spender_of_gNNNN.bin, spend_extra_gNNNN.binrecordsOutputs in ordinal coordinates; one slot per output naming its spender, with an overflow file for the duplicate-spend anomaly (empty on a consensus-valid chain)index buildas above
txids.bin, txid_index_gNNNN.bin, tx_first_out.bin, blocks.binrecordsThe dictionaries turning txids and heights into ordinals, and backindex buildas above
manifest.jsonoutpoint-index-v3Fingerprint and coverage; the parent graph is declared in buildindex buildverification, derived build
<derived>/outpoint-derived-v3The same facts reordered by lock, by transaction, by co-spendderived buildderived history/fee/cospends, check
history_gNNNN.binrecordsOne row per output carrying both events, receipt and spendderived buildderived history, check
tx_inputs.bin, fees.binrecordsInputs per transaction, and each transaction's feederived buildderived fee/cospends, check
manifest.jsonoutpoint-derived-v3Fingerprint and coverage; the parent index is declared in build, and a stale pairing is refusedderived buildverification
<firstspend>/firstspend-v1The first spend of every lock, ordered by that moment (25 B: spender_tx | lock)firstspend buildfirstspend between
firstspend_gNNNN.bin, manifest.jsonrecords / firstspend-v1One row per lock ever spent from; the parent derivatives are declared in buildfirstspend buildfirstspend between/verify
<firstreveal>/firstreveal-v1The first revelation of every key, ordered by that moment (23 B: first_height | key)firstreveal buildfirstreveal between
firstreveal_gNNNN.bin, manifest.jsonrecords / firstreveal-v1One row per revealed key, a 1:1 restatement of the archive's keys partition; the parent archive is declared in buildfirstreveal buildfirstreveal between/verify
*.lad (inside index, derived, firstspend and firstreveal)laddersSearch caches: one sample every few thousand keys. Outside the fingerprint; without them a search falls back to a blind bisection, slower and with the same answerthe buildersthe readers, when present
<checkpoint>/reuse-scan-v1Second road only. The direct reuse scan's state: a hits_<type>.bin bitmap of which locks history has opened, plus state.json and its own curve.csvreuse scanitself (resume), archive crosscheck

What the scan checks while it reads

The long pass runs for hours or days, unattended, over a connection you may not control end to end. It is worth knowing what it refuses to accept, because none of it is optional and none of it can be turned off:

  1. the bytes must hash to the block that was asked for — the header is re-hashed and compared with the requested hash;
  2. the transactions must be the ones the header commits to — the Merkle root is recomputed from the parsed txids;
  3. the witness bytes must be committed too — recomputing the Merkle root is not enough, because a txid excludes witness data by construction, and the witness is exactly where most revealed keys live. The witness commitment in the coinbase closes that gap, and is verified;
  4. each block must link to the previous one — a prev_hash that does not chain stops the scan rather than producing a plausible number over a reorg or a different node.

Every byte must also be consumed: trailing bytes mean the input was not one well-formed block. So a corrupted or substituted block fails while it is being read, before it can become a number. The scan writes a checkpoint as it goes and resumes from it, so an interruption costs the current interval, not the run.

All four happen in memory and then evaporate, which is what the header archive is for. With --headers on, checks 1, 4 and 2 become repeatable offline forever: headers verify re-derives every block id and every link from the 80 bytes it kept, and headers crosscheck --index recomputes every Merkle root from the index's own txids. Check 3 cannot be repeated from anything kept, because the witness is deliberately not archived, and saying so is part of the claim. See Headers-v2.

Sealed, fingerprinted, and named by their children

Every artifact directory holds a state.json while it is being built and a manifest.json once sealed. The manifest carries the canonical fingerprint — the format tag, the coverage and the data files' digests in a fixed order, ladders excluded — which names what the artifact is, so two honest builds of the same heights agree on it whoever made them. Where it came from is declared separately in build: derivatives name the index, the index names the graph, and a consumer holding both confirms the link, which verify does for them and says when it could not.

verify re-reads every byte against the manifest; stats reports from the manifest alone and is instant; rewind takes a sealed artifact back to a height it already covered, into the bytes a build that had stopped there would have written.

Fingerprints of a run are published where that run is described, not here: this file describes the shapes, and shapes do not have fingerprints. nodsig --version and the release tag say which code produced yours.

Rough sizes

On the 2026 chain through height 957,301, as an order of magnitude for planning. Yours will differ with the height and with how much of the chain you cover.

<index> and <derived> were projected here for a while and are now measured: the v2 pair was 248 and 191 GB, the narrower spend side and u56 satoshi fields predicted 229.1 and 185.8 by arithmetic on the record widths, and the completed v3 build measured 229.6 and 185.3. The projection held to within half a percent, which is what fixed-width records buy.

ArtifactSize
<graph>/~301 GB
<index>/~230 GB
<derived>/~185 GB
<archive>/~98 GB
<nonces>/~60 GB
<firstspend>/~37 GB (optional; 1.48 G locks ever spent from × 25 B)
<firstreveal>/~37 GB (optional; 1.61 G revealed keys × 23 B)
<locks>/, <checkpoint>/, CSVssmall enough not to plan for

Add headroom on top: a fusion writes a new generation before deleting the old.

All of it is shareable

Every artifact here derives from public chain data. None contains an address you looked up, and the census is aggregated by construction, so any of them can be published or handed to someone else without leaking what you were curious about.

The exception is not an artifact: check writes check-results.txt, which lists the addresses you asked about. That one is yours, it is kept out of version control, and it is the only file in this project that should not be shared.