PCOC · Docs · Specification

Satoshi Hike v1.35

Change note, 2026-10-06. In-place amend. Part A (Rules A1–A5) is locked by Christian (relayed by Chief of Staff), 2026-10-06. No version bump. This file stays Satoshi Hike v1.35. The object kind remains satoshi-hike-v1.35-object. There is no parallel specification path. Part B (Rules B0–B8) is candidate admission-service policy and is not locked.

The 2026-10-04 in-place amend stays in force where this note does not replace it. Section 14 records measurements and does not judge. Section 15 is admission-service policy and is the only judgment. Former section 15 Conformance is section 16. Former section 16 Closed is section 17. Section 13 stays produce: it writes what happened and does not judge. Whether a live run meets a later acceptance check is not in this file.

Wire. The CBOR key on stage 0 stays waypoint. Prose in this amend calls that field the seed value (SHA-256(seed)). It is not a Rule A1 waypoint. Stage 0 has no waypoint of its own (Principle 6). This amend does not rename the key. Docs confirms this prose.

Rule IDs. A1, A1.1–A1.5, A2, A2.1–A2.4, A3, A3.1–A3.4, A4, A4.1–A4.11, A5, and B0–B8 are the normative headings below. They are stable test keys. The index in section 1 maps each ID to its heading. Section 1 classifies Part A. Where a heading has more than one class, the rows are ID.n. Unnumbered Part A sentences use P<section>.<n>.

Conformance note, 2026-10-06 (S1). Section 1 classifies every Part A rule. Section 16 states what a conforming producer, verifier, and admission service do. Conformance means meeting the published vectors under conformance/v1.35/. Profile conformance-small is the test-only parameter set. Section 15 is a Part B draft: inputs, outputs, two-phase async, friendly list, and named parameters. Everything in this proposal, including the section 15.1 challenge encoding, the Sign-in origin, and the issuing routes, is proposed until Christian locks it. Rules B0–B8 remain candidates for their thresholds, floors, the letter k, and the budgets. Rule B9 is normative. No version bump.

Proposed amendment. This text is a proposal. It governs only once Christian locks it. Until then, Part A as locked at 546d24f governs. Everything in this proposal, including the section 15.1 challenge encoding, the Sign-in origin, and the issuing routes, is proposed until Christian locks it. Rules B0–B8 remain candidates for their thresholds, floors, the letter k, and the budgets. Rule B9 is normative.

Status: Locked for Part A as of 546d24f. This proposal does not replace that lock until Christian locks it.

Normative changes from 546d24f. Proposed. Christian locks each one.

MVP decisions, 2026-10-06 22:23. Not law. These may change in a later policy version.

Candidates (Game Theory rev 7). Not locked. Only the default leg_rate_floor above is locked.

Normative changes, S1 fold.

Custody candidate for Christian's lock. Not part of the 22:23 not-law block.

Locked, Christian 2026-10-07 00:15.

Open questions for Christian.

This file is the specification. A hike, a checkpoint, a waypoint, a seed value, and a leg result are defined here.

Sections marked Normative are the construction. Sections marked Informative explain that construction and do not add a second one.

1. Status

The status prose in this section is informative. The rule index is the ID-to-heading map. The subsection “Part A rule classes” is the normative classification.

This document’s Part A is Locked as of 2026-10-06. Part B thresholds, floors, the letter k, and the budgets in section 15 remain candidate policy, except a value a later sentence marks locked. This file is an in-place amend of the locked cut. The 2026-10-04 amend remains the authority for the split between measurement (section 14) and judgment (section 15). GATE.md orders the later evidence. Merge to main waits on measured produce and verify evidence.

Rule index

Normative for the ID-to-heading map. The heading text in the section named is the rule. A test named for an ID tests that heading. This table does not add a second rule. Classes for these headings, and for unnumbered Part A sentences, are in “Part A rule classes” below.

ID Heading Section
A1 Rule A1. Event order at every stage boundary 13
A1.1 Rule A1.1 Legs, then the tip 13
A1.2 Rule A1.2 The interrupt ends the stage 13
A1.3 Rule A1.3 Waypoint byte order 13
A1.4 Rule A1.4 Post 13
A1.5 Rule A1.5 Pivot 13
A2 Rule A2. Stages and where leg 0 starts 12.1
A2.1 Rule A2.1 Eighteen stages 12.1
A2.2 Rule A2.2 Leg 0 12.1
A2.3 Rule A2.3 Checkpoint 12.1
A2.4 Rule A2.4 First post 12.1
A3 Rule A3. What the object stores 8
A3.1 Rule A3.1 Headers 8
A3.2 Rule A3.2 Per stage 8
A3.3 Rule A3.3 Paths 8
A3.4 Rule A3.4 Produce stderr 13.3
A4 Rule A4. What verify measures against the portable index 14
A4.1 Rule A4.1 Shape 14.1
A4.2 Rule A4.2 Headers 14.2
A4.3 Rule A4.3 Chain 14.3
A4.4 Rule A4.4 Six-leg check 14.6
A4.5 Rule A4.5 Paths 14.3
A4.6 Rule A4.6 Bookends 14.4
A4.7 Rule A4.7 Bookend legs 14.4
A4.8 Rule A4.8 Bookend span 14.4
A4.9 Rule A4.9 Bookend leg rate 14.4
A4.10 Rule A4.10 Analysis 14.4
A4.11 Rule A4.11 Fewer than two anchored and fetched 14.4
A5 Rule A5. Earlier objects 14.7
B0 Rule B0. Which rate the admission service reads 15
B1 Rule B1. Integrity 15
B2 Rule B2. Leg-rate floor 15
B3 Rule B3. Span sanity 15
B4 Rule B4. Paths minimum 15
B5 Rule B5. Plant gap 15
B6 Rule B6. Fixed start 15
B7 Rule B7. Resubmits 15
B8 Rule B8. Leg count 15
B9 Rule B9. Invariant refuses 15.2

Part A rule classes

Normative.

This subsection is the normative classification of Part A. It does not add a second construction. Each row is one rule and one class. Informative prose, section 5, section 7.4, section 13.4, the expected-case list in section 13.1, and sections 17 and 18 are not rows. A glossary sentence that only renames a row is not a second row. The Normative and Informative marks on the sections a row cites stay where those sections put them.

Rule ID scheme.

Rule ID Class One-line rule SPEC locus
P2.1 RETIRED Eighteen stages. The live rule is A2.1. This ID is not reused. §2, §12.1
P2.2 INVARIANT Produce and verify perform no signature check. The admission service checks the signature (section 15). §2, §15
P3.1 INVARIANT Ranges are half-open; 0^n is n zero bytes; ‖ is raw concatenation; H is FIPS 180-4 SHA-256. §3
P3.2 RANGE le64 and u64_le are 8 little-endian bytes of an unsigned integer below 2^64. §3
P3.3 INVARIANT mod is the non-negative remainder; mod 2^64 keeps the low 64 bits. §3
P4.1 RANGE Outcome tokens are the closed lowercase set. There is no token not obtained. §4
P4.2 INVARIANT Part A does not pass, fail, reject, deny, or admit. §4
P6.1 CONSTANT SLOT_BYTES is 16384. §6
P6.2 CONSTANT WORDS_PER_SLOT is 2048. §6
P6.3 CONSTANT TERRAIN_BYTES is 268435456. §6
P6.4 CONSTANT N_SLOTS is 16384. §6
P6.5 CONSTANT SAMPLE_BYTES is 1048576. §6
P6.6 CONSTANT OFFSET_MODULUS is 267386881. §6
P6.7 CONSTANT STEPS_PER_LEG is 1000000. §6
P6.8 CONSTANT STAGES is 18. §6
P6.9 CONSTANT HASH_BYTES is 32. §6
P6.10 CONSTANT LEG_RESULT_BYTES is 8. §6
P6.11 CONSTANT PUBLIC_KEY_BYTES is 32. §6
P6.12 CONSTANT BEACON_BYTES is 32. §6
P6.13 CONSTANT SEAL_DOMAIN is the twelve bytes pcoc-walk-v1. §6
P6.14 CONSTANT SEED_DOMAIN is PSEED64 followed by a NUL byte. §6
P6.15 CONSTANT KIND is satoshi-hike-v1.35-object. §6
P6.16 CONSTANT FILL is dag2. §6
P6.17 CONSTANT MIX_ADD is 0x9E3779B97F4A7C15. §6
P6.18 CONSTANT MIX_MUL1 is 0xBF58476D1CE4E5B9. §6
P6.19 CONSTANT MIX_MUL2 is 0x94D049BB133111EB. §6
P6.20 CONSTANT WORD_XOR is 0xC0FFEE. §6
P6.21 CONSTANT PARENT_MUL is 0xD1B54A32D192ED03. §6
P6.22 CONSTANT SLOT_MUL is 0xA24BAED4963EE407. §6
P6.23 INVARIANT WORDS_PER_SLOT = SLOT_BYTES / 8, and the division is exact. §6
P6.24 INVARIANT N_SLOTS = TERRAIN_BYTES / SLOT_BYTES, and the division is exact. §6
P6.25 INVARIANT OFFSET_MODULUS = TERRAIN_BYTES − SAMPLE_BYTES + 1. §6
P6.26 INVARIANT This version has no AUDIT_K. The six-leg draw count is not a section 6 constant. §6
P7.1 INVARIANT The first eight bytes of a digest are b[0:8]. u64_le does not reverse a digest a second time. §7.1
P7.2 RANGE Hex uses 0-9, a-f, and A-F with no spaces. A 64-digit spelling is 32 bytes, byte 0 first. §7.2
P7.3 INVARIANT Produce emits one CBOR map in core deterministic encoding. §7.3
P7.4 INVARIANT Top-level and per-stage key orders are the orders section 7.3 lists. Stage 0 has no path key. §7.3
P7.5 RANGE Text is definite-length UTF-8. Byte fields, unsigned integers, and definite arrays are as section 7.3 states. §7.3
P7.6 RANGE Verify decodes any definite-length encoding of the same values, including a non-shortest unsigned integer. Key order is not a shape mismatch. §7.3
P7.7 INVARIANT Malformed CBOR is shape mismatch, and shape_item includes malformed cbor. Later measurements are still recorded. §7.3
A3 INVARIANT The hike object has exactly the top-level fields, types, and lengths in the section 8 table. §8 Rule A3
P8.1 INVARIANT Evidence is the hash-bound set in section 8. Convenience signposts are checked against the evidence or the index and never relied on. §8, §14
P8.2 INVARIANT Stored heights are convenience. d_g and delta_h subtract index-confirmed heights only. §8, §14.3, §14.4
A3.1 INVARIANT The object stores seed_beacon and all 18 interrupts, with strictly later heights and differing hashes, and stores no block time. §8 Rule A3.1
A3.2 INVARIANT Each stage map has exactly the listed fields. Array lengths equal leg_count. leg_count is at least 1. Stage 0’s waypoint is SHA-256(seed). §8 Rule A3.2
A3.3.1 INVARIANT Stage 0 omits path. An empty byte string is not a stage-0 path. “not planted by design” is not a token. §8 Rule A3.3
A3.3.2 INVARIANT stages[17].path is always empty. The record is not fetched by design. §8 Rule A3.3
A3.3.3 CONSTANT Sixteen waypoints, W_1 through W_16, are fetchable. W_17 is not. §8 Rule A3.3
A3.3.4 INVARIANT The object has no terrain field. A stored leg result is le64 of the section 11 u64. §8
A3.4 INVARIANT path_skip <g> and not fetched by design 17 are the exact stderr lines, in that order, after the cut-off. Empty bytes are not a silent skip. §13.3 Rule A3.4
P9.1 RANGE {tip-url}, {index-url}, and {bari-url} are absolute http or https URLs with no trailing slash. §9, §13
P9.2 INVARIANT A clock read is one GET, does not follow a redirect, and is successful only on status 200. §9
P9.3 RANGE A successful body is one UTF-8 JSON object, with no BOM, no second value, and no duplicate member name. §9
P9.4 RANGE header.hash is 64 hex digits in Bitcoin display order. The natural beacon reverses those 32 bytes. §9
P9.5 RANGE header.height is a JSON number in 0 … 2^53 − 1, not a string and not a non-integer JSON number. An index height above that bound is an unsuccessful read: not read. An object-byte height above that bound is section 14 shape_item height_range. §9, §14
P9.6 RANGE header.time is a JSON integer in 0 … 2^32 − 1 Unix seconds, not a string and not a non-integer JSON number. §9
P9.7 INVARIANT On mainnet, header.time equals the nTime of the block whose hash is header.hash. Section 14 measures the portable index. A direct Bitcoin read is outside that procedure. §9, §14
P9.8 CONSTANT With no named gossip peer, the starting list is the copied Bitcoin Core mainnet DNS seeds, in the listed order, port 8333. §9
P9.9 INVARIANT tip.pcoc.app is retired. It is not a dial. §9
P9.10 INVARIANT Produce Observe reads the plane tip sequence and does not GET an HTTP tip or the portable index. §9
P9.11 INVARIANT The interrupt is the first successful post-checkpoint Observe whose hash differs and whose height is strictly greater. There is no rebinding. §9, Rule A1
P9.12 INVARIANT An unsuccessful seed Observe, or an unsuccessful post-checkpoint Observe that looks for the interrupt, emits no object. An unsuccessful Hashcash, POST, or path-ask Observe is a miss, and produce continues. §9
P9.13 INVARIANT Verify does not Observe, does not GET an HTTP tip, and does not GET a Bari path. It reads the portable index at each stored height. §9, §14
P9.14 INVARIANT The seed beacon is the natural beacon from the first successful Observe, before any leg. §9
P9.15 INVARIANT A height is not an input to SHA-256 in this construction. §9
P10.1 INVARIANT production_seed is public_key ‖ seed_beacon, 64 bytes, read as eight little-endian lanes. §10
P10.2 INVARIANT mix64, the fill pin, the parent indices, and the dag2 fill are the section 10 construction. Terrain is filled once. The step does not write it. §10
P11.1 INVARIANT A step folds every word of the selected slot and sets the next state from SHA-256(SEAL_DOMAIN ‖ le64(S_folded)). §11.1
P11.2 INVARIANT The leg result is STEPS_PER_LEG steps from the first eight bytes of the previous checkpoint, stored as le64. §11.2
P11.3 INVARIANT The terrain sample is SAMPLE_BYTES at the offset taken from the previous checkpoint. It is not the waypoint slot sample. §11.3
A2.1 CONSTANT seed_beacon plus the 18 interrupts gives stages numbered 0–17. §12.1 Rule A2.1
A2.2 INVARIANT The seed value is SHA-256(seed), stored at stages[0].waypoint, never posted, and not one of W_1 … W_17. Leg 0 starts from it or from W_g. §12.1 Rule A2.2
A2.3 INVARIANT The checkpoint preimage is the previous checkpoint, the stage limb, the terrain sample, and le64(leg_result), in that order. §11.4, §12.1 Rule A2.3
A2.4 INVARIANT The first Bari post is W_1, after I_0. Stage 0 has no plant and no path. §12.1 Rule A2.4
A1.3 INVARIANT W_{g+1} is SHA-256 of I_g, its slot sample, stages[g].waypoint, and the fold of that stage’s checkpoints. slot_index is u64_le of the first eight bytes of I_g, modulo N_SLOTS. No waypoint is built from I_17. §12.2, §13 Rule A1.3
A1 INVARIANT At each stage boundary the order is legs, then the tip, then the interrupt, then the waypoint bytes, then the post, then the pivot. There is no rebinding. §13 Rule A1
A1.1 INVARIANT Legs in a stage run back to back. The tip is read after each checkpoint. A leg already in progress finishes. §13 Rule A1.1
A1.2 INVARIANT When the tip read shows interrupt I_g, leg production in stage g stops. §13 Rule A1.2
A1.4 INVARIANT The producer posts W_{g+1} to Bari with the Hashcash fee. §13 Rule A1.4
A1.5 INVARIANT W_{g+1} is the pivot. Stage g+1 leg 0 starts from it. §13 Rule A1.5
P13.1 INVARIANT Produce sets no wall-clock bound, no steps-per-second quota, and no leg-count quota. leg_count ≥ 1 is the drive loop, not an admission floor. §13, §13.3
P13.2 CONSTANT A plant posted at height H is asked only at H+2, H+3, and H+4. The path is gone at H+5. §13.1
P13.3 INVARIANT Produce does not pause the drive before the first leg of a stage. A stage that ends at one leg, when the next header is the interrupt, is a world event the producer recorded. §13.1
P13.4 INVARIANT The Hashcash challenge and digest are the section 13.1 byte strings. Leading zero bits are counted from the first digest byte. §13.1
P13.5 CONSTANT The Hashcash difficulty is 21 leading zero bits, the one value for produce and for Bari. §13.1
P13.6 INVARIANT The nonce search starts at 0. Produce sends one waypoint POST and retries only when the tips differ by a header or the request did not connect. Status 204 stores plant height H. Any other miss does not withhold the object. There is no fee bump. §13.1
P13.7 RANGE The leaf is 64 lowercase hex digits. Before each due ask, produce waits from 15 seconds through 45 seconds. §13.1
P13.8 INVARIANT A checked path is a status 200 canonical path body that validates for that waypoint and whose block is H+2, H+3, or H+4. Every other ask result is a miss. §13.1
P13.9 INVARIANT The cut-off is last_interrupt_height. The cut-off rule determines which asks are made. No ask is made above the cut-off. §13.1
P13.10 INVARIANT leg_count is a convenience copy of len(checkpoints). Produce does not invent path bytes. No metric reads leg_count. §13.3
P13.11 INVARIANT The live event log is not the hike object and is not an input to a leg, a checkpoint, a leg result, or a waypoint. §13.5
P13.12 RANGE A public-key input is 32 bytes, or 64 hex digits with an optional leading 0x. The object stores the 32 bytes. §13
P13.13 INVARIANT A waypoint POST is retried only when the tips differ by a header or the request did not connect. §13.1
P13.14 INVARIANT Plant height H is the height of the Hashcash tip on the POST that returned 204. §13.1
P13.15 INVARIANT The live event log uses the five record types in section 13.5, in the key order that section lists. §13.5
P13.16 INVARIANT The waypoint POST is POST {bari-url}/waypoint. The body is the 32 waypoint bytes. Content-Type is application/octet-stream. The header is X-Bari-Hashcash-Nonce. §13.1
A4 INVARIANT Verify records measurements against the object and the portable index and does not judge. Section 15 is not applied in section 14. §14 Rule A4
P14.1 INVARIANT Anyone may read Bitcoin directly. That read is outside the section 14 procedure. Section 14 measures the portable index (P9.7). P9.7 is not a conformance-vector check. A LAN match is not a mainnet fact. §14
P14.2 RANGE The verify record is the JSON object section 14 defines: required keys, no JSON null, decimal integers, and the stated array lengths. §14 verify record
P14.3 RANGE shape_item uses only the 14 strings in that table, in that order. Malformed CBOR is exactly malformed cbor. height_range is an object-byte height above 2^53 − 1. §14 verify record
A4.1.1 INVARIANT shape is match only when every shape condition holds. Otherwise it is mismatch, and later measurements are still recorded. §14.1 Rule A4.1
A4.1.2 INVARIANT kind, fill, and the size fields equal the profile in force, or shape_item includes constant. The profile in force is section 6, unless the verifier runs conformance-small. §14.1, §16.1
A4.1.3 INVARIANT Shape records the field set, widths, stage-0 path key, empty W_17 path, stages length, leg_count, seed_beacon, and header order. §14.1
A4.2.1 INVARIANT Verify reads every stored header from the portable index, in order, including last_interrupt. One header does not skip a later header. §14.2 Rule A4.2
A4.2.2 INVARIANT index_comparison is match, mismatch, or not read, from hash and height together. Time is not part of that token. §14.2
A4.2.3 INVARIANT T is header.time only when index_comparison is match. On mismatch, T is not read. §9, §14.2
A4.3.1 INVARIANT Verify recomputes each checkpoint digest and the seed value and records match or mismatch. A missing leg result is mismatch. This check does not run a leg. §14.3 Rule A4.3
A4.3.2 INVARIANT Verify recomputes W_1 … W_17 from stored bytes under section 12.2. No waypoint is recomputed from last_interrupt. §14.3
A4.5.1 INVARIANT Empty path bytes on W_1 … W_16 are skip and plant n/a. Any other bytes are present. Empty W_17 is not fetched by design. §14.3 Rule A4.5
A4.5.2 INVARIANT Anchored and fetched means present and plant match among W_1 … W_16. anchored_total is that count and is not a threshold. §14.3, §14.5
A4.5.3 INVARIANT d_g subtracts index-confirmed heights only, and only when plant is match and the index comparison of I_{g−1} is match. Otherwise d_g is not defined. §4, §14.3
P14.4 INVARIANT A present path’s plant is compared with the portable index. A read that does not return a document is not read. A document that was read with no plant, or with no matching plant, is mismatch. Verify does not GET Bari. §14.3
P14.5 INVARIANT On mainnet a read document whose network is not main is plant mismatch. Two plants may share a block. tree_size is not compared. §14.3
P14.6 INVARIANT A present path that does not decode, or whose validate_for does not succeed, has plant mismatch. A hash-verified path body with a height above 2^53 − 1 is plant mismatch and claimed_block not defined. The hike object still decodes. §14.3
P14.7 INVARIANT An index GET spells the height in the shortest base-10 digits. §14.2
A4.6 INVARIANT Bookends are the lowest and highest anchored-and-fetched indices among W_1 … W_16. §14.4 Rule A4.6
A4.7 INVARIANT N is the checkpoint count over stages a … b−1. It does not read leg_count. §14.4 Rule A4.7
A4.8.1 INVARIANT S is T(P_b) − T(I_{a−1}) only when both T values were taken on match. Otherwise S is not read. §14.4 Rule A4.8
A4.8.2 INVARIANT delta_h subtracts index-confirmed heights only. Otherwise delta_h is not defined. §14.4 Rule A4.8
A4.9 INVARIANT The verify record holds integers N and S and does not hold a floating-point rate. The display is not a judgment. §14.4 Rule A4.9
A4.10 INVARIANT Analysis records the whole-hike count and span, each per-stage span, and checkpoints outside the bookends. It is not the bookend pair. §14.4 Rule A4.10
A4.11 INVARIANT Fewer than two present paths, or two or more present paths with every read returned and fewer than two plants match, leaves the bookend fields and fixed_start not defined. A short or missing dependent read leaves those fields not read. That count is not a threshold. §14.4 Rule A4.11
A4.4.1 CONSTANT The six-leg draw count is 6. It is not STAGES and it is not AUDIT_K. §14.6 Rule A4.4
A4.4.2 INVARIANT The draw is six distinct global checkpoint indices by the unbiased draw in section 14.6. Live verify reads the operating-system CSPRNG. A conformance-small vector may supply draw bytes (section 16.1). Fewer than six legs is not defined. A short byte source is not read. When that draw is not read, six_leg_runs is not read. A short-byte six-leg draw stays re-readable. It is not not defined. An 8-byte draw at or above the limit is consumed. §14.6, §16.1
A4.4.3 INVARIANT Each chosen index is recorded match or mismatch in draw order. §14.6
A4.4.4 INVARIANT The index draw finishes before any leg result is recomputed. A verifier may recompute the six leg results concurrently, and may run Rule A4.3 checkpoint comparisons concurrently. Elements of six_leg_runs may execute in parallel. This file does not require a thread pool and does not set a pool size. §14.6
A5 INVARIANT An earlier object is measured as it is. There is no legacy rule, and nothing is deleted. §14.7 Rule A5
B9 INVARIANT Rule B9 names the section 15.2 refuses that do not wait on a locked threshold: decode, waypoint rehash, shape mismatch including height_range, seed-value mismatch, a confirmed plant or header mismatch, a checkpoint-digest mismatch, and a six-leg mismatch. A cap hit is Rule B7. This row is a section 15 judgment. It is not a Part A measurement. §15.2 Rule B9

Sentences reviewed for POLICY THRESHOLD. None of the rows above is a POLICY THRESHOLD. The sentences a reader might take for one are:

  1. leg_count ≥ 1 (A3.2, A4.1.3, P13.1). It is the section 13.2 loop, recorded under shape. It is not an admission floor. The candidate admission statement is Rule B8, and it sets no leg-count threshold.
  2. Hashcash difficulty 21 (P13.5). It is a produce and Bari constant. It is not an admission threshold.
  3. The wait from 15 seconds through 45 seconds (P13.7). It is a produce range. It is not an admission threshold.
  4. Section 9, “mean about 60 seconds.” That phrase describes the synth sequencer. It is not a section 6 constant, not a conformance-small parameter, and not an admission threshold.
  5. Section 13.1, a stage that ends at leg_count = 1 “is conforming.” That sentence means the producer followed section 13. Judgment of the record is section 15.
  6. Rule A4.9, the pair (N, S) as the rate input a later admission service may read. Section 14 records the integers. It does not apply leg_rate_floor. The comparison is Rule B2.
  7. The informative note under Rule A4.11 about one to four blocks, and “Rule B3 is the candidate lever.” Those sentences are not a Part A number. The lever, if one is locked later, is a named parameter in section 15.

No Part A sentence is moved into section 15 by this classification. Where section 14 says an admission service may judge a recorded token, the judgment stays in section 15.

2. Scope

Normative.

This document specifies Satoshi Hike v1.35.

A hike has exactly eighteen stages (Rule A2.1).

Section 13 produces the object. Section 14 measures it. Section 15 is the admission service. Produce and verify perform no signature check. The admission service checks the signature (section 15.1).

3. Interpretation

Normative.

“Must” and “must not” apply to a conforming producer, a conforming verifier, and a conforming admission service.

Byte ranges are half-open. b[i:j] is bytes i through j-1 of b, starting at index 0.

0^n is the byte string of n zero bytes.

‖ is raw concatenation of byte strings, in the written order. A concatenation has no length prefix, no tag, and no separator.

le64(x) is the 8-byte little-endian encoding of the unsigned integer x with 0 ≤ x < 2^64.

u64_le(b) is the unsigned integer represented by the 8-byte string b in little-endian order.

mod is the non-negative remainder. For a ≥ 0 and m > 0, a mod m is the integer r with 0 ≤ r < m and a = qm + r for some integer q.

mod 2^64 on a u64 quantity is ordinary wrapping arithmetic on a 64-bit unsigned integer. The result is the low 64 bits, and overflow is discarded. That is this mod with modulus 2^64. It is the rule for the fill pin, the parent indices, the slot mix, and the step’s u64 state. A mod with any other modulus stays the remainder above.

SHA-256 is the function specified by FIPS 180-4. Its output is 32 bytes. In this document, H means SHA-256.

4. Glossary

Normative.

Term Meaning
Satoshi Hike v1.35 This construction.
Individual The hiker. The individual is identified by a 32-byte BIP-340 x-only public key on secp256k1.
Seed public_key ‖ seed_beacon. It is 64 bytes: the 32-byte public key, then the 32-byte seed_beacon in natural byte order. This is production_seed (section 10).
Seed beacon The tip read at the start of produce. The object stores it as seed_beacon.
Seed value SHA-256(seed). Stage 0’s leg 0 starts from it. The object stores it at the wire key stages[0].waypoint. That field is the seed value. It is not a Rule A1 waypoint. It is never built from an interrupt and never posted. Stage 0 has no waypoint of its own.
Interrupt I_g, g = 0 … 17. The tip as read at the first post-checkpoint tip check in stage g whose hash differs from I_{g−1} (from seed_beacon, on stage 0) and whose height is strictly greater. If the tip moved by more than one block before that check, I_g is the tip as read. There are 18 interrupts.
Waypoint W_{g+1}, g = 0 … 16: the 32-byte value Rule A1.3 builds after I_g and produce posts to Bari. The waypoints are W_1 … W_17. No waypoint is built after I_17. Stage 0 has no waypoint of its own.
Pivot What a waypoint does. W_{g+1} ends stage g’s record and starts stage g+1.
Path The canonical path body of a waypoint, showing that waypoint’s plant in a block (section 13.1). Only W_1 … W_17 have paths. Stage 0 has no path.
Anchored and fetched The waypoint’s path is present, and plant is match. A present path whose plant is mismatch or not read is recorded and is not anchored and fetched.
Skip Empty path bytes on W_1 … W_16. A skip is a normal produce outcome and is recorded. How skips count is admission-service policy (Rule B4).
P_g The block named in W_g’s path, where its plant was mined. Verify records P_g as authenticated only when that path is present and the plant comparison is match.
d_g A signed integer only when plant is match and the index comparison of I_{g−1} is match: index-confirmed height(P_g) minus index-confirmed height(I_{g−1}). Otherwise not defined. Not taken from a producer-stored height alone. Computed by verify; a verify-record field, not object content.
Bookends W_a and W_b: the anchored-and-fetched waypoints with the lowest index and the highest index.
Bookend legs N: the number of checkpoints over stages a … b−1. An integer when bookends exist. Computed by verify; a verify-record field, not object content.
Bookend span S = T(P_b) − T(I_{a−1}) when both T values are integers under Rule A4.2, so each index_comparison is match. Otherwise S is not read. Seconds, a signed integer when recorded. Computed by verify; a verify-record field, not object content.
Bookend leg rate Display only. N÷S when S > 0. Otherwise the display is not defined. The verify record holds the integers N and S. It does not hold a floating-point rate. Section 15 may read the pair (N, S).
Δh Record key delta_h: index-confirmed height(P_b) minus index-confirmed height(I_{a−1}), only when both header comparisons are match. Otherwise not defined. Not taken from a producer-stored height alone. A signed integer in the bookend block when it is defined. Rule B3 may read it. It is not an analysis value and it is not policy. Computed by verify; a verify-record field, not object content.
Analysis The whole-hike checkpoint count and span, each per-stage span and its rate display, and checkpoints outside the bookends. Rule B0 does not read analysis for the floor. Computed by verify; a verify-record field, not object content.
Portable index What verify measures against. At each height it holds the header hash and the height, and optionally waypoint anchoring. A row that carries Bitcoin nTime stores it as header.time. A row with no header.time is measured as T not read. This file does not name a cut height and does not backfill a row. The index is faithful to Bitcoin. Anyone may bypass the index and read Bitcoin directly (see §14). That direct read is not the section 14 procedure.
T(x) The Bitcoin nTime of block x, from the portable index member header.time, only when the index comparison for that header is match. Otherwise the measurement is not read. Unix seconds when it is an integer.
Block time That nTime: an unsigned 32-bit count of Unix seconds, read as it is.
Clock The plane's tip sequence. Produce Observe reads that sequence and returns the natural beacon and its height. The tip sets the order of events. It is not money, not a confirmation count, and not an archive of the chain. Block time is a measured input to section 14. It does not replace tip advances as that order. Wall-clock time is not an input. The verifier's wall clock is not an input.
Terrain The deterministic room derived from the individual’s public key and the seed beacon.
Hike The whole proof and produce object: one individual’s recorded journey over terrain under the clock.
Stage Legs from the seed value (stage 0) or from waypoint W_g (stage g ≥ 1) until interrupt I_g. Stage 17 ends at I_17. No waypoint is built from I_17.
Leg One leg of exactly STEPS_PER_LEG steps, from a previous checkpoint to a leg result and a checkpoint.
Step One fold+seal over one slot of SLOT_BYTES bytes.
Leg result The u64 end state of a leg.
Checkpoint The 32-byte SHA-256 digest defined in section 11.
not fetched by design The path record for W_17 only, lowercase, with spaces. stages[17].path is the empty byte string. Verify identifies it by position.
Leg-rate floor Rule B2 policy parameter, not a measurement. The default profile 1/2 leg/s is locked (MVP, not law); other profiles are candidates.
Index-confirmed height header.height from an index document whose comparison is match (stored hash agrees and stored height agrees). For P_b that comparison is index_comparison_P_b: the header hash agrees and header.height equals P_b.block_height. A stored height alone is not this height.
Policy profile See section 15. Not a hike-object field.
Re-verify See section 15.
Marks as fast See section 15.
Friendly list See section 15.
Paths minimum An admission-service policy candidate (Rule B4). Not a measurement.
Outcome token Lowercase, with spaces: match, mismatch, present, skip, not defined, not read, not fetched by design, and n/a. Not not_defined. Not title case. There is no measurement token not obtained; that wording is retired below. n/a means a plant comparison does not apply because path_state is skip. n/a is not not defined. not read is an index document that is missing or unreadable, a header whose comparison is not match so T is not taken, a T that is missing or unusable on a match, an unreadable header, or anything short or missing from a network read. Short path bytes for a six-leg run, and a short-byte six-leg draw, are not read. A document that was read and has no plant for that block is plant mismatch. A path body that does not yield claimed_block is not defined. Fewer than six legs is not defined. Those not defined values are settled by the object's own bytes.

The public key is the 32-byte string used below. Produce and verify do not test a signature on it.

Retired wording. Use the term in the table.

Retired Use instead
opening header; the header that opened stage g; open header; close header seed_beacon on stage 0; I_{g−1} on stage g ≥ 1
a header arrives during the waypoint post there is no rebinding; the next interrupt is the later tip check (Rule A1)
deciding legs; deciding span; deciding leg rate bookend legs N; bookend span S; bookend leg rate (display)
opening value seed value, or W_g
stage span bookend span S; a per-stage span is analysis (Rule A4.10) and is not an admission input
leg floor leg-rate floor
seed block seed_beacon
anchored and included anchored and fetched
not planted by design; any path field on stage 0 stage 0 has no path key (Rule A3.3)
walk or re-walk, for a leg leg, or recomputed leg result
not obtained, for an unsuccessful index read or a missing T not read
roll-forward cut A row with no header.time records T as not read. This file does not name a cut height.

SEAL_DOMAIN pcoc-walk-v1 is an exempt wire constant. It is not retired wording.

Part A (sections 8–14) does not say that verify or the object passes, fails, rejects, denies, or admits, and it does not say accept, refuse, revoke, or held. Those outcomes are section 15. Do not say “minimum legs per stage.” A per-stage rate is analysis. It is not an admission test.

5. Overview

Informative.

One individual contributes a public key. The clock contributes a seed beacon and, later, eighteen interrupts. The public key and the seed beacon determine the terrain. The hike then records eighteen stages, numbered 0–17. Stage 0 stores the seed value, seed_beacon, and one or more legs. It has no waypoint of its own and no path. Stages 1–17 each store a waypoint, the interrupt the stage started from, the path of that waypoint, and one or more legs. The interrupt that ends stage 17 is stored as last_interrupt. Each leg is STEPS_PER_LEG steps and ends in a leg result and a checkpoint.

public key  +  seed beacon
        │
        ▼
     terrain          (recomputed by a verifier; not stored)
     seed value       SHA-256(public key ‖ seed beacon); stored at stages[0].waypoint

hike
  stage 0
    seed value, seed beacon, beacon height
    leg 0 … leg (leg_count − 1)
      STEPS_PER_LEG steps → leg result
      checkpoint
  stage 1 … stage 17
    waypoint, interrupt it started from, beacon height, path
    leg 0 … leg (leg_count − 1)
      STEPS_PER_LEG steps → leg result
      checkpoint
  last_interrupt, last_interrupt_height

The procedures in the normative sections are the construction.

6. Constants

Normative.

Name Value
SLOT_BYTES 16384 (16 KiB)
WORDS_PER_SLOT 2048
TERRAIN_BYTES 268435456 (256 MiB)
N_SLOTS 16384
SAMPLE_BYTES 1048576 (1 MiB)
OFFSET_MODULUS 267386881
STEPS_PER_LEG 1000000 (1_000_000)
STAGES 18
HASH_BYTES 32
LEG_RESULT_BYTES 8
PUBLIC_KEY_BYTES 32
BEACON_BYTES 32
SEAL_DOMAIN the 12 bytes pcoc-walk-v1
SEED_DOMAIN the 8 bytes PSEED64 followed by a NUL byte
KIND satoshi-hike-v1.35-object
FILL dag2
MIX_ADD 0x9E3779B97F4A7C15
MIX_MUL1 0xBF58476D1CE4E5B9
MIX_MUL2 0x94D049BB133111EB
WORD_XOR 0xC0FFEE
PARENT_MUL 0xD1B54A32D192ED03
SLOT_MUL 0xA24BAED4963EE407

There is no AUDIT_K. This draw does not use AUDIT_K = 18 and does not use AUDIT_K = STAGES. The six-leg check draws 6 legs. That count is not a constant in this table.

WORDS_PER_SLOT = SLOT_BYTES / 8. The divisor is the width of one little-endian u64.

N_SLOTS = TERRAIN_BYTES / SLOT_BYTES, and the division is exact: TERRAIN_BYTES = N_SLOTS × SLOT_BYTES.

OFFSET_MODULUS = TERRAIN_BYTES − SAMPLE_BYTES + 1.

The section 6 numbers for WORDS_PER_SLOT, N_SLOTS, and OFFSET_MODULUS are that production evaluation. They are not a second free constant. Under conformance-small the same formulas apply to that profile’s SLOT_BYTES, TERRAIN_BYTES, and SAMPLE_BYTES.

SEAL_DOMAIN is the twelve ASCII bytes 70 63 6f 63 2d 77 61 6c 6b 2d 76 31. It is exactly those twelve bytes.

SEED_DOMAIN is the eight bytes 50 53 45 45 44 36 34 00. Section 10 uses that string as a little-endian integer.

7. Encodings

Normative.

7.1 Little-endian integers

le64 and u64_le are defined in section 3.

The first eight bytes of a 32-byte string b are b[0:8]. Those bytes are SHA-256 output order when b is a digest: byte 0 is the first byte of the digest. u64_le does not reverse a digest a second time.

7.2 Hexadecimal

A hexadecimal spelling uses the digits 0-9, a-f, and A-F. Letter case does not change the value. The spelling contains no spaces.

A 64-digit spelling denotes 32 bytes. The first two digits are byte 0.

7.3 CBOR

The hike object is one CBOR data item, as defined by RFC 8949. The item is a map.

Produce emits the core deterministic encoding:

Every field name in this object is at most 23 bytes, so the first byte of an encoded key is 0x60 plus the name length. For the fields in section 8, that order is:

Top-level map. Keys sort by encoded length, then bytewise. steps_per_leg is 13 bytes. last_interrupt is 14 bytes. last_interrupt_height is 21 bytes.

  1. fill
  2. kind
  3. stages
  4. n_slots
  5. public_key
  6. slot_bytes
  7. seed_beacon
  8. sample_bytes
  9. steps_per_leg
  10. last_interrupt
  11. last_interrupt_height

Stage 0 has no path key (Rule A3.3). Its map:

  1. beacon
  2. waypoint
  3. leg_count
  4. checkpoints
  5. leg_results
  6. beacon_height

Each stage map for stages 1–17:

  1. path
  2. beacon
  3. waypoint
  4. leg_count
  5. checkpoints
  6. leg_results
  7. beacon_height

Text values and map keys are definite-length UTF-8 text strings (CBOR major type 3). A length from 0 through 23 is the additional information in the first byte. A longer string uses additional information 24, 25, 26, or 27 and the length bytes that follow, then the UTF-8 bytes. Indefinite-length text is not a text string in this object. KIND is longer than 23 bytes, and the kind value is still one definite-length text string. Byte fields are byte strings (major type 2). Counts and heights are unsigned integers (major type 0). Arrays are definite arrays in the order defined in section 8.

Verification decodes one definite-length CBOR data item and records section 14. These are decoding rules. A definite-length encoding of the section 8 values decodes. A non-shortest definite-length unsigned integer decodes when the numeric value is the same. Map key order is not a shape mismatch. Indefinite lengths, tags, floating-point values, simple values, negative integers, a map key that is not a text string, a duplicate key, and any byte after that one data item are a malformed CBOR value. A malformed CBOR value is shape mismatch, and shape_item includes malformed cbor. Later measurements are still recorded. Produce is what fixes the canonical bytes.

7.4 Checks

Informative.

le64(1) = 01 00 00 00 00 00 00 00.

le64(0x0102030405060708) = 08 07 06 05 04 03 02 01.

FIPS 180-4’s SHA-256 example for the three bytes abc is ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad.

SHA-256(SEAL_DOMAIN ‖ le64(0)) is the 32-byte digest whose hexadecimal spelling, byte 0 first, is 4816bdaf23240476ca57e168301eceee8eb8170ec5446863855e4ceebb55b533. The first eight digest bytes, in that index order, are 48 16 bd af 23 24 04 76. Their spelling in index order is 4816bdaf23240476. When S_folded = 0, section 11 sets S' = u64_le(D[0:8]), the little-endian integer of those eight bytes. That integer is 0x76042423afbd1648. The spelling 4816bdaf23240476 is the digest bytes in index order. It is not the hexadecimal spelling of the integer S'.

A production_seed of 32 bytes of 0x11 followed by 32 bytes of 0x22 has fill pin 0x7df0aff879fb1171 under section 10.

8. Hike object

Normative.

Rule A3. What the object stores

The object has exactly these top-level fields:

Field CBOR type Value
kind text KIND
fill text FILL
slot_bytes unsigned SLOT_BYTES
n_slots unsigned N_SLOTS
sample_bytes unsigned SAMPLE_BYTES
steps_per_leg unsigned STEPS_PER_LEG
public_key byte string, length 32 the individual’s public key
seed_beacon byte string, length 32 the seed beacon in natural order
stages array, length STAGES stage maps, stage 0 first
last_interrupt byte string, length 32 I_17, the interrupt that ended stage 17, in natural order
last_interrupt_height unsigned the height of I_17

Evidence and convenience. Evidence, the hash-bound values, is the seed (public_key, seed_beacon), the checkpoints, the leg results, the waypoints W_1 … W_17, the Bitcoin headers (stages[g].beacon for g ≥ 1, and last_interrupt), the path’s leaf, Merkle path and root, which bind the path to the waypoint, stages[0].waypoint (= SHA-256(seed), the seed value), and any calculated field whose hash dependencies enforce order and cost. Convenience signposts are the path locators (txid, vout, block_hash, block_height), checked against the index, leg_count, beacon_height, last_interrupt_height, the section 6 constants, and stages[0].beacon (= seed_beacon). Verify records leg_count under shape: shape_item includes leg_count when it differs from len(checkpoints) (Rule A4.1). Section 6 constants are recorded as constant. Stored heights are recorded as index_comparison (Rule A4.2). They are convenience. d_g and delta_h do not subtract a stored height unless the index comparison that confirms it is match (Rules A4.5 and A4.8). Verify checks a convenience signpost against the evidence or the index and never relies on it. A broken signpost is a recorded mismatch. Section 15 is where an admission service may judge that recorded mismatch.

Rule A3.1 Headers

The field name beacon is kept.

stages[g].beacon for g ≥ 1 is I_{g−1}, the interrupt stage g started from. It is not a second header read after the waypoint post.

For 0 ≤ g < 17, stages[g+1].beacon_height > stages[g].beacon_height, and stages[g+1].beacon differs from stages[g].beacon.

last_interrupt_height > stages[17].beacon_height, and last_interrupt differs from stages[17].beacon.

The object therefore carries seed_beacon and all 18 interrupts. The object carries no block time. Section 14 reads each block time from the portable index (sections 9 and 14.2).

Rule A3.2 Per stage

Stage 0’s map has exactly these fields. It has no path key.

Field CBOR type Value
beacon byte string, length 32 seed_beacon in natural order
beacon_height unsigned the height of seed_beacon
leg_count unsigned, at least 1 number of legs in the stage
waypoint byte string, length 32 the seed value, SHA-256(public_key ‖ seed_beacon). Wire key waypoint. Prose name: seed value. Not a Rule A1 waypoint. Never posted.
checkpoints array of byte strings one 32-byte checkpoint per leg, leg 0 first
leg_results array of byte strings one 8-byte little-endian leg result per leg, leg 0 first

Stages 1–17 each have exactly these fields:

Field CBOR type Value
beacon byte string, length 32 I_{g−1} in natural order
beacon_height unsigned the height of I_{g−1}
leg_count unsigned, at least 1 number of legs in the stage
waypoint byte string, length 32 W_g, the waypoint stage g started from
path byte string the canonical path body for W_g, or the empty byte string section 13.3 stores for a skip or for W_17
checkpoints array of byte strings one 32-byte checkpoint per leg, leg 0 first
leg_results array of byte strings one 8-byte little-endian leg result per leg, leg 0 first

len(checkpoints) = len(leg_results) = leg_count.

An object-byte height above 2^53 − 1, including beacon_height and last_interrupt_height, is not a usable unsigned value on the verify record. Section 14 records shape mismatch, shape_item includes height_range, and the value is not defined. It is not not read. Do not clamp the value and do not record it.

checkpoints[j] and leg_results[j] belong to the same leg.

stages[0].beacon = seed_beacon.

stages[0].waypoint = SHA-256(public_key ‖ seed_beacon).

Rule A3.3 Paths

Stage 0 has no path. The stage-0 map omits the path key. Omission is the encoding. An empty byte string is not a stage-0 path. The words “not planted by design” are not a token in this file.

Stages 1–17 each carry the path of their waypoint W_g, or empty bytes for a skip.

stages[17].path (W_17) is always the empty byte string. Its asks fall after the cut-off (section 13.1). The record is not fetched by design.

Sixteen waypoints, W_1 … W_16, are fetchable. W_17 is not one of them.

A path key on stage 0 is a shape mismatch (Rule A4.1). Those bytes are not a path. Verify does not give them path semantics.

The object has no terrain field. A verifier recomputes terrain from public_key and seed_beacon.

A leg result on the object is le64 of the u64 defined in section 11, stored as an 8-byte byte string.

9. Clock

Normative.

{tip-url} and {index-url} are absolute URLs with scheme http or https (ASCII case-insensitive) and no trailing slash.

A clock read is one GET of a URL below. The reader does not follow a redirect. A status other than 200 is not a successful read. The reader does not require a particular Content-Type.

A successful body is UTF-8 JSON: one JSON object and no second value. A byte order mark (U+FEFF, the bytes EF BB BF) is not a successful read and is not stripped. Bytes that are not UTF-8 are not a successful read.

The top-level value is a JSON object with a member header whose value is a JSON object. header contains hash, height, and time. No other member, at the top level or nested, affects the hike. If the same member name occurs more than once in any JSON object in the document, including a nested object, the read is not successful. The reader does not keep one of the repeated values.

header.hash is a JSON string of exactly 64 hexadecimal digits under section 7.2, so the digits are 0-9, a-f, and A-F, and letter case does not change the value. The string has no 0x prefix. The digits are Bitcoin display order. Let display be the 32 bytes of that spelling under section 7.2. The natural beacon is the 32-byte string natural with

natural[i] = display[31 − i]    for i = 0 … 31

header.height is a JSON number in the range 0 … 2^53 − 1. It is not a JSON string. A JSON number written with a fractional part or an exponent is not a height. That bound is what this file states for a height. A height above 2^53 − 1 that an index document reports makes that read unsuccessful. Record not read. Do not clamp the value and do not record the oversized value. An integer above 2^53 − 1 in the object's own bytes, including stored_height and P_b when those bytes are the object's and that height is not inside a hash-verified path body, is recorded in section 14: shape mismatch, shape_item includes height_range, and the value is not defined. It is not not read. A hash-verified path body with a height above 2^53 − 1 is recorded in section 14.3: plant mismatch and claimed_block not defined (P14.6). The hike object still decodes. not read is only for a read that is not hash-bound or whose hash does not agree.

header.time is the Bitcoin header nTime of the header at that height. It is a JSON integer in the range 0 … 2^32 − 1, in Unix seconds. It is not a JSON string. A JSON number written with a fractional part or an exponent is not a time. On the mainnet path, header.time equals the nTime of the block whose hash is header.hash. The portable index is faithful to Bitcoin: header hash, height, and nTime. Anyone may bypass the index and read Bitcoin directly (see §14). Section 14 measures the portable index. It does not read Bitcoin as its procedure. T(x) reads header.time from the portable index for block x only when Rule A4.2 records index_comparison match. Produce does not read header.time. Bari’s observed time (header.timestamp_unix_ms, when a document carries it) is a different member. Section 14 does not use it as T.

A read whose header has no time, or whose time breaks this rule, is still a successful read for hash and height when those members follow the rules above. T for that header is then not read. Section 14.2 records that token. A read that is not successful leaves both the index comparison and T as not read. T is taken only when section 14.2 records index_comparison match for that header. match requires the stored hash to agree with the index. A successful read whose hash does not agree is mismatch, and T is not read. Do not take that document’s time. A height disagreement is also mismatch, and T is not read on any mismatch. Previously a successful read with a well-formed time recorded T even when the hash did not agree. That reading is withdrawn.

The body rules above are the portable index. header.hash and header.height follow those rules for every index read. header.time follows them on a row that carries it. A row with no header.time records T as not read. This file does not name a cut height and does not backfill a row. Produce Observe does not use that GET.

Produce Observe returns (natural, height) from the plane's one tip sequence. The default profile is the test plane. Its sequence is the synth sequencer (the synth tip sequence). Wire magic is regtest headers (header network magic for gossip). That magic is not a Bitcoin Core plant and not a tip clock. One cadence, mean about 60 seconds. “Mean about 60 seconds” describes the synth sequencer. It is not a section 6 constant, not a conformance-small parameter, and not an admission threshold. The natural beacon is that header's hash in natural byte order. Test Observe is that gossip peer. It does not GET an HTTP tip.

The live profile is explicit. Its sequence is the Bitcoin best chain, the chain with the most work, read by gossip. Live Observe does not GET an HTTP tip.

When no gossip peer is named, the starting list is a copy of Bitcoin Core's current mainnet DNS seeds (CMainParams::vSeeds in src/kernel/chainparams.cpp). The list is Core's own bootstrap. This specification does not invent a host. The copy is what lets a producer join gossip without naming one peer and without depending on any Grounding Lab node. A lookup of one seed returns node addresses. The producer connects to those nodes and asks them for more peers. An address that does not answer is dropped. That is how a producer stays independent of any single gossip peer. The copied list is checked again against Bitcoin Core's mainnet chainparams when a producer release is cut.

The copy, in Core's order, is dnsseed.bluematt.me, seed.bitcoin.jonasschnelli.ch, seed.btc.petertodd.net, seed.bitcoin.sprovoost.nl, dnsseed.emzy.de, seed.bitcoin.wiz.biz, seed.mainnet.achownodes.xyz. The port is 8333. A named peer is still host:port when the producer supplies one. This specification does not name that host or port. tip.pcoc.app is retired. It is not a dial. Do not recreate it. The test profile does not use this list. Its Observe remains the synth peer.

Produce does not read {index-url}. Produce does not read an HTTP tip, including {tip-url}/index and {tip-url}/index/{height}.

Observe is that plane sequence and returns (natural, height).

The seed beacon is the natural beacon from the first successful Observe, taken at the start of produce, before any leg. It fixes the terrain. It is the header stage 0 starts from. After the terrain fill, stage 0’s legs begin from the seed value. There is no Bari post before those legs.

A stage ends at its interrupt. The interrupt is the first successful Observe, taken after a checkpoint has been recorded, that returns a natural beacon different from the header the stage started from and a height strictly greater than that header’s height. The header stage 0 started from is seed_beacon. The header stage g ≥ 1 started from is I_{g−1}. Both the hash condition and the height condition are required. If the tip moved by more than one block before that check, the interrupt is the tip as read. It is not an earlier header the producer did not read. The interrupt is a Bitcoin header interrupting the drive.

The interrupt that ends stage g, for g from 0 through 16, is stored as stages[g+1].beacon. The waypoint W_{g+1} is built from that interrupt after it is read (Rule A1.3, section 12.2). The interrupt that ends stage 17 is last_interrupt. No second Observe replaces stages[g].beacon after the waypoint post. There is no rebinding.

A header newer than I_g is the next interrupt candidate. Produce reads it at the Observe after stage g+1’s first checkpoint, the same as any other header.

An Observe taken for Hashcash, a waypoint POST, or a path ask does not end a stage and is not I_g. After a post, produce returns to the drive. It does not pause the drive to wait for a further header before the first leg, and it does not pause on a wall clock before the first leg.

Heights are stored on stages. A height is not an input to SHA-256 in this construction.

If the seed Observe, or a post-checkpoint Observe that looks for the interrupt, is not successful, produce emits no object. An Observe for Hashcash, a waypoint POST, or a path ask that does not succeed is a miss. Rule A3.4 records it and produce continues.

Verification does not Observe the tip sequence and does not GET an HTTP tip. Section 14.2 reads GET {index-url}/index/{height} under the body rules in this section, once for each stored header: each stage’s beacon_height, then last_interrupt_height. It records index_comparison and T for each. That read is part of the header measurement in section 14.2. It is not the only request verify may send: section 14.3 may read the same index for a present path’s plant facts. Verify does not GET a Bari path. The index is that plane's index. The default profile does not fill https://index.pcoc.app.

10. Terrain

Normative.

production_seed = public_key ‖ seed_beacon

production_seed is 64 bytes. It has no separator. Lane i, for i = 0 … 7, is

lane[i] = u64_le(production_seed[8i : 8i+8])

Every integer step below is a u64. A sum or product written mod 2^64 wraps as section 3 defines: the low 64 bits are kept. The fill pin is one such integer. The terrain is TERRAIN_BYTES bytes, filled below, and then left unchanged. Later beacons do not change it. Slot i, for 0 ≤ i < N_SLOTS, is

terrain[i × SLOT_BYTES : (i+1) × SLOT_BYTES]

Every load of slot i returns those same SLOT_BYTES bytes. The step does not write terrain.

10.1 mix64

mix64(x):
    y = (x + MIX_ADD) mod 2^64
    y = ((y XOR (y >> 30)) × MIX_MUL1) mod 2^64
    y = ((y XOR (y >> 27)) × MIX_MUL2) mod 2^64
    return y XOR (y >> 31)

10.2 Fill pin

SEED_DOMAIN as a little-endian integer is u64_le(SEED_DOMAIN) = 0x0034364445455350.

x_0 = u64_le(SEED_DOMAIN)
x_{i+1} = mix64( x_i XOR lane[i] XOR ((i × MIX_ADD) mod 2^64) )    for i = 0 … 7
fill_pin = x_8

10.3 Word rotation and parent indices

rotl(w, r) is w rotated left by r bits on a 64-bit word. Bits that leave the top re-enter at the bottom. rotl(w, 0) = w. In this section r is in 0 … 63.

floor_log2(n), for an integer n ≥ 1, is the greatest integer b with 2^b ≤ n.

reverse32(v) treats v as 32 bits, high bits zero when v < 2^32. Bit k of the result, with bit 0 the least significant bit, equals bit 31 − k of v.

For a slot index i with 1 ≤ i < N_SLOTS:

bits = max(floor_log2(i), 1)
bitrev_parent(i) = (reverse32(i) >> (32 − bits)) mod i

h = (i × MIX_ADD) mod 2^64
mul_parent(i) = floor(h / 2^32) mod i

Both parent values are integers in 0 … i − 1.

10.4 dag2 fill

Start from TERRAIN_BYTES zero bytes. For i from 0 through N_SLOTS − 1, build WORDS_PER_SLOT words and then store the slot.

w[j] = mix64( fill_pin XOR (i << 32) XOR j XOR WORD_XOR )    for j = 0 … WORDS_PER_SLOT − 1

i << 32 is the slot index shifted left 32 bits. j is the word index.

Slot 0 keeps those words. It has no parent lanes and no slot mix.

When i > 0, take the three parents in this order, even if two of them are the same index:

  1. i − 1
  2. bitrev_parent(i)
  3. mul_parent(i)

For parent number k in 1, 2, 3, and for each j in 0 … WORDS_PER_SLOT − 1, let p be that parent index and let pw be the little-endian word already stored at word j of slot p:

rot = (j + k) mod 64
w[j] = mix64( (w[j] XOR rotl(pw, rot)) XOR ((k × PARENT_MUL) mod 2^64) )

After those three parents:

slot_mix = (i × SLOT_MUL) mod 2^64
w[j] = mix64(w[j] XOR slot_mix)    for j = 0 … WORDS_PER_SLOT − 1

For every slot, including slot 0, write le64(w[j]) into word j of slot i.

Terrain is computed once, from the public key and the seed beacon, before the first leg.

11. Leg

Normative.

A leg is STEPS_PER_LEG steps. It starts from the previous checkpoint defined in section 12. The leg result is the u64 end state of the leg. The checkpoint is the digest below. Every leg in a stage uses the same 32-byte second limb: the seed value on stage 0, and waypoint W_g on stage g ≥ 1.

11.1 Step

Let S be the current u64 state. One step computes the next state S'.

i = S mod N_SLOTS
slot = terrain[i × SLOT_BYTES : (i+1) × SLOT_BYTES]

slot is SLOT_BYTES long and contains WORDS_PER_SLOT little-endian words. Word w, for w = 0 … WORDS_PER_SLOT − 1, is u64_le(slot[8w : 8w+8]). The word index is the integer w.

S_folded = S XOR (XOR over w = 0 … WORDS_PER_SLOT − 1 of (word(w) XOR w))
D = SHA-256(SEAL_DOMAIN ‖ le64(S_folded))
S' = u64_le(D[0:8])

SEAL_DOMAIN ‖ le64(S_folded) is 20 bytes. w is XORed as a u64. The loop is every word in the slot. Under section 6, WORDS_PER_SLOT is 2048, so the production bound is w = 0 … 2047. Profile conformance-small (section 16.1) uses that profile’s WORDS_PER_SLOT in this same sentence. It does not change the production constant.

D[0:8] is the first eight bytes of the digest in index order, byte 0 first. S' = u64_le(D[0:8]) is the little-endian integer of those bytes. It is not the integer whose big-endian hexadecimal spelling is the same eight bytes. S and S' are 64-bit unsigned integers under the wrapping rule in section 3. The step does not keep a wider integer. Section 7.4 records the S_folded = 0 check: those eight digest bytes spell 4816bdaf23240476 in index order, and the integer S' is 0x76042423afbd1648.

The step reads terrain and does not write it.

11.2 Leg result

S_0 = u64_le(previous_checkpoint[0:8])
S_{t+1} = Step(S_t)    for t = 0 … STEPS_PER_LEG − 1
leg_result = S_{STEPS_PER_LEG}

leg_result is a u64. On the hike object it is the byte string le64(leg_result).

11.3 Terrain sample

The terrain sample at a previous checkpoint is a contiguous window of SAMPLE_BYTES bytes. It is not aligned to a slot boundary.

offset = u64_le(previous_checkpoint[0:8]) mod OFFSET_MODULUS
terrain_sample = terrain[offset : offset + SAMPLE_BYTES]

The offset is taken from the first eight bytes of the previous checkpoint. The window itself is the sample.

This window is not the waypoint’s slot sample. The slot sample is defined in section 12.

11.4 Checkpoint

Rule A2.3. The checkpoint bind, in order, is

H(previous checkpoint ‖ stage limb ‖ terrain sample(at previous checkpoint) ‖ leg result)

H is SHA-256. The stage limb is the seed value on stage 0 and W_g on stage g ≥ 1. The four limbs are raw bytes:

Order Limb Bytes
1 previous checkpoint 32
2 seed value on stage 0; W_g on stage g ≥ 1 32
3 terrain sample at the previous checkpoint SAMPLE_BYTES
4 leg result le64(leg_result), 8 bytes

The preimage is 32 + 32 + SAMPLE_BYTES + 8 bytes. Under section 6 that is 32 + 32 + 1048576 + 8 = 1048648 bytes. The layout is unchanged from the previous cut of this file. On stage 0 the second limb is the seed value, where that cut used 0^32.

The stage path is not a limb of this preimage. Stage 0 has no path.

The checkpoint is the 32-byte digest. The hike object stores that digest and the matching leg_results limb. The first limb is the full 32-byte previous checkpoint. The eight bytes that select the terrain sample are not a substitute for that limb. The leg result appears only as the fourth limb.

12. Stage and waypoint

Normative.

Stages are stages[0] through stages[17].

A stage contains one or more legs. The count is how many legs the drive in section 13 recorded before interrupt I_g. Every leg in stage 0 uses the seed value as its second checkpoint limb. Every leg in stage g ≥ 1 uses waypoint W_g.

12.1 Previous checkpoint

Rule A2. Stages and where leg 0 starts

Rule A2.1 Eighteen stages

seed_beacon plus the 18 interrupts gives 18 stages, numbered 0–17.

Rule A2.2 Leg 0

The seed value is

stages[0].waypoint = SHA-256(public_key ‖ seed_beacon)   # SHA-256(seed); SHA-256(production_seed); 64-byte preimage

That field is the seed value. It is not a Rule A1 waypoint. It is stored on the object. It is the stage limb of stage 0’s checkpoint preimages (section 11.4). It is the third limb of W_1’s preimage (section 12.2). It is never posted to Bari. Stage 0 has no waypoint of its own. The seed value is not one of W_1 … W_17.

Leg 0 of stage 0 starts from the seed value. Leg 0 of stage g ≥ 1 starts from W_g:

stage 0:           previous = stages[0].waypoint    # the seed value
stage g, g ≥ 1:    previous = stages[g].waypoint    # W_g

Leg j for j ≥ 1 uses previous checkpoint checkpoints[j − 1] of that same stage.

The public key reaches every later checkpoint, fold, and waypoint through the seed value.

A stage does not carry the last checkpoint of the previous stage forward. The previous stage’s checkpoints reach the next stage through the fold in its waypoint (section 12.2).

Rule A2.3 Checkpoint

The checkpoint preimage is section 11.4. The stage limb is the seed value on stage 0 and W_g on stage g ≥ 1. Leg 0’s previous checkpoint is that same value. Leg j ≥ 1 uses checkpoints[j − 1] of that stage.

Rule A2.4 First post

The first Bari post is W_1, after I_0. Stage 0 has no plant and no path.

12.2 Waypoint

Waypoint preimage

Rule A1.3. For g from 0 through 16, after stage g has ended at interrupt I_g, produce computes W_{g+1} as follows. B is I_g, in natural order. It is stages[g+1].beacon. B is not stages[g].beacon. W is stages[g].waypoint: the seed value when g = 0, and W_g when g ≥ 1. C[0] … C[n−1] are stages[g].checkpoints in leg order. n ≥ 1. fold_g is the fold below.

F_0 = 0^32
F_{j+1} = SHA-256(F_j ‖ C[j])    for j = 0 … n − 1
fold = F_n                        # fold_g

slot_index = u64_le(B[0:8]) mod N_SLOTS
slot_sample = terrain[slot_index × SLOT_BYTES : (slot_index + 1) × SLOT_BYTES]

W_{g+1} = SHA-256(B ‖ slot_sample ‖ W ‖ fold)

That concatenation order is normative. It is the v1.35 preimage order. Only the first limb changes: B is the interrupt I_g, where the previous cut of this file used stages[g].beacon. slot_sample is the terrain slot indexed by I_g’s hash. The preimage is 32 + SLOT_BYTES + 32 + 32 bytes. Under section 6 that is 32 + 16384 + 32 + 32 = 16480 bytes.

The stage path is not an input to this preimage.

F_0 is the start value of the fold. It is not itself hashed. The first hash in the fold is SHA-256(0^32 ‖ C[0]).

W_{g+1} is the pivot. It ends stage g and starts stage g+1. Produce posts it to Bari with the Hashcash fee (section 13.1). Leg 0 of stage g+1 starts from it.

W_1 is built after I_0 from that interrupt, its slot sample, the seed value, and the fold of stage 0’s checkpoints. It is the first waypoint posted to Bari.

Stage 17 ends at I_17. No waypoint is computed from stage 17 or from last_interrupt. Stage 17’s checkpoints are not folded into a waypoint.

12.3 What a stage stores

When stage g ends at I_g, the object records the header the stage started from (beacon and beacon_height), the seed value or the waypoint the stage’s legs used, leg_count, checkpoints, and leg_results.

For g from 0 through 16, I_g is stored as stages[g+1].beacon and stages[g+1].beacon_height. I_17 is stored as last_interrupt and last_interrupt_height.

Stage 0 has no plant and no path key. No ask is made for stage 0.

The path ask window is section 13.1. No ask is made above the cut-off. A waypoint W_1 … W_16 with no checked path stores empty bytes and produce writes path_skip <g> (Rule A3.4). W_17 stores empty bytes and produce writes not fetched by design 17.

13. Produce

Normative.

Inputs are the 32-byte public key and one plane profile: the tip sequence and {bari-url}. The default profile is the test plane. The live profile is explicit. A missing parameter is not filled from the other plane.

{bari-url} is an absolute URL with scheme http or https (ASCII case-insensitive) and no trailing slash, the same rule section 9 states for a URL. Produce does not read {index-url}.

A public-key input may be those 32 bytes, or a hexadecimal spelling of them under section 7.2 with an optional leading 0x and exactly 64 digits. The object stores the 32 bytes.

Output is one hike object in the encoding of section 7.3, or no object if the drive’s clock read does not succeed. A waypoint POST that does not succeed, and an ask that does not complete, do not withhold the object. Rule A3.4 stores empty path bytes and produce continues. On the live profile, produce also appends the event log in section 13.5 while the drive runs. That log is not the hike object. Only the live profile writes it.

Rule A1. Event order at every stage boundary

Produce is a continuous drive from the seed beacon through STAGES. A Bitcoin header interrupts that drive. The interrupt is a world event, unknown in time and value. Each stage boundary is one event, in this order. There is no rebinding. A later header is the next interrupt, read after the next stage’s next checkpoint. Never say a header arrives during the waypoint post.

Rule A1.1 Legs, then the tip

Stage g’s legs run back to back. After each checkpoint the producer reads the tip. A leg already in progress always finishes.

Rule A1.2 The interrupt ends the stage

When that read shows a new interrupt I_g, leg production in stage g stops.

Rule A1.3 Waypoint byte order

The producer builds W_{g+1} by section 12.2:

W_{g+1} = SHA-256(I_g ‖ slot_sample(I_g) ‖ stages[g].waypoint ‖ fold_g)

The third limb is the seed value on g = 0 and W_g on g ≥ 1. The seed value is not called W_g.

Rule A1.4 Post

The producer posts W_{g+1} to Bari with the Hashcash fee (section 13.1).

Rule A1.5 Pivot

W_{g+1} is the pivot. Stage g+1’s leg 0 starts from it.

Stage 0 has no boundary before it. After the seed Observe and the terrain fill, produce enters stage 0’s legs from the seed value. There is no Bari post. The first Bari post is W_1, after I_0. The interrupt that ends stage 17 is last_interrupt. Produce stores it and builds no waypoint from it.

Between interrupts the drive runs legs of STEPS_PER_LEG steps and records a checkpoint after each leg. The checkpoint binds the leg result, the seed value or the stage waypoint, the previous checkpoint, and the terrain sample. That record is what the drive did. leg_count is how many of those legs were recorded before the interrupt. Several legs in one stage are the consequence of an uninterrupted stretch. This specification does not bound the wall-clock time of a leg. It does not set a steps-per-second quota. It does not set a leg quota. Principle 9: there is no minimum or maximum leg count. leg_count ≥ 1 follows from Rule A1.1, because the repeat in section 13.2 runs at least once, and section 14.1 records it under shape.

Observe performs the read in section 9 and returns (natural, height).

13.1 Stage path

For a stage g ≥ 1, when its waypoint W_g is known, and before any leg of that stage, produce posts that waypoint. It is the pivot built after I_{g−1}. The seed value is never posted. Stage 0 has no plant and no path ask.

A plant posted at height H is asked at H+2. If the path is served, it is stored and later headers are not asked. If it is not served, the producer asks again at H+3, and if it is still not served, once more at H+4. After H+4 a miss stores an empty path and the stage does not keep waiting. Nothing is asked before H+2 or after H+4. The ask is not made before the post. Test and live use this one rule.

The live tip for Hashcash is one Observe of that same tip sequence, taken at this moment. Its natural beacon is tip_hash. This Observe is not the interrupt I_g, and it does not end a stage. If it does not succeed, the POST does not succeed. Produce continues. The miss is kept until the stderr step after the cut-off. No path_skip line is written at this moment. Hashcash is that Observe. It is not a second read and it is not an HTTP tip.

After the post, produce returns to the drive. It must not pause the drive to wait for further headers before the first leg. It must not pause on a wall clock before the first leg. There is no one-minute stagger. A header newer than I_{g−1} is read at the Observe after this stage’s first checkpoint. If it meets the interrupt rule of section 9, it ends this stage at leg_count = 1. That is a world event, and it is conforming. Waiting for such a header before the first leg is the non-conforming pause.

Hashcash is the rule in services/bari/src/hashcash.rs. Produce does not use a second puzzle.

The challenge is SHA-256 of the ten bytes bari-hc-v1 followed by tip_hash. A nonce is a u64. Its digest is SHA-256(challenge ‖ waypoint ‖ le64(nonce)). Leading zero bits are counted from the first digest byte. Inside a byte the high bit is first. A zero byte contributes eight bits and the count continues. The first non-zero byte contributes that byte’s leading zeros and the count stops.

The difficulty is 21 leading zero bits. That is the one Hashcash difficulty value in this specification. Produce and Bari both use it. Two environment variables that can disagree are not allowed.

The nonce is the first u64 in the order 0, 1, 2, … whose digest meets the difficulty. If that search exhausts every u64, the POST does not succeed. Produce continues. The miss is kept until the stderr step after the cut-off. No path_skip line is written at this moment. The request header is X-Bari-Hashcash-Nonce. Its value is that nonce in decimal.

Produce sends one POST {bari-url}/waypoint and does not follow a redirect. The body is the 32 waypoint bytes. Content-Type is application/octet-stream. The request carries the Hashcash header. Status 204 means the waypoint was appended. If Bari’s tip and the producer’s tip differ by a header, or the request did not connect, produce tries again. If the POST does not succeed, including when a status is not 204 and is not one of those two retries, and including when those retries end, produce continues. The miss is kept until the stderr step after the cut-off (Rule A3.4). No path_skip line is written at this moment. Produce does not send a second POST for that stage, except to try again in those two cases. There is no fee bump, RBF, replacement, or repair of a missed plant. A missed POST does not stop the hike and does not withhold the object.

The plant is posted at height H, the height of the tip for Hashcash in the POST that returned 204.

The serve window is fixed. A plant posted at height H can be included at H+2, H+3, and H+4. The path is served through H+4. It is gone at H+5. An operator setting does not change this.

On the live profile, Bari's plant is a Bitcoin transaction. The anchor is the block that includes that transaction. Bari serves the waypoint path for the window stated above, from H+2 through H+4. The producer posts the waypoint to Bari and asks Bari for the path. This specification does not define a path that anchors by talking to Bitcoin and skipping Bari.

The leaf is the waypoint’s 64 lowercase hexadecimal digits, byte 0 first. An ask sends GET {bari-url}/path/{leaf} and does not follow a redirect. After each ask header, H+2, H+3, and H+4, the producer waits a random duration from 15 seconds through 45 seconds before the GET, so the producers do not ask Bari at the same moment. Produce must make the ask at every due header. Watching only the newest tip is not enough.

An ask that does not complete, or returns anything other than a checked 200, is a miss. After the last ask due at or below the cut-off, a waypoint W_1 … W_16 with no checked path stores empty bytes. The path_skip <g> line is written in the stderr step after the cut-off (Rule A3.4), not when the ask returns. Produce must make the ask at every due header. A miss does not stop the hike and does not withhold the object.

There is no silent skip. A silent skip stores path bytes that were not fetched and leaves no record. That is forbidden. “No silent skip” is not a reason to pause the drive for further headers. That pause does not satisfy it. Section 13.3 separates that rule from path_skip.

The path bytes are not an input to section 11.4 or section 12.2.

Cut-off. The cut-off is last_interrupt_height, the height of I_17. An ask due at a height at or below the cut-off is made under the rule above, including an ask due at the cut-off header itself, after its 15-to-45-second delay. An ask due at a height above the cut-off is not made. Produce does not wait for a header above the cut-off. When every ask due at or below the cut-off has happened, produce encodes the object.

Informative. Plants are W_1 … W_17. There is no plant for the seed value. When each interrupt is one header above the last, and the plant for W_g (g ≥ 1) is posted at the height of I_{g−1}:

Rule A3.3: sixteen waypoints, W_1 … W_16, are fetchable. W_17 is not. When heights jump or a plant is posted above that height, the cut-off rule determines which asks are made. These indices are the expected case, not a second rule. Under this expected case the last bookend is usually W_14, W_15, or W_16 (Rule A4.6).

13.2 Procedure

(seed_beacon, height_0) ← Observe()
seed ← public_key ‖ seed_beacon                 # production_seed, section 10
seed_value ← SHA-256(seed)                      # stages[0].waypoint; not a Rule A1 waypoint
fill_pin ← section 10.2
terrain ← section 10.4

waypoint ← seed_value
(beacon, beacon_height) ← (seed_beacon, height_0)

for g in 0 … 17:
    if g ≥ 1:
        post waypoint                # W_g; section 13.1; Hashcash; does not end a stage
    previous ← waypoint              # leg 0: seed value on stage 0, W_g on stage g ≥ 1
    checkpoints ← []
    leg_results ← []

    repeat:
        leg_result ← LegResult(terrain, previous)     # section 11.2, STEPS_PER_LEG steps
        sample ← terrain sample at previous           # section 11.3, SAMPLE_BYTES
        checkpoint ← SHA-256(
            previous ‖ waypoint ‖ sample ‖ le64(leg_result))
        append checkpoint to checkpoints
        append le64(leg_result) to leg_results
        previous ← checkpoint
        (tip, tip_height) ← Observe()                 # Rule A1.1; after the checkpoint
        if tip ≠ beacon and tip_height > beacon_height:
            (interrupt, interrupt_height) ← (tip, tip_height)    # I_g; Rule A1.2
            stop the repeat

    record stages[g] with
        beacon, beacon_height, waypoint,
        leg_count = len(checkpoints),
        checkpoints, leg_results
        # stage 0: no path key

    if g < 17:
        waypoint ← SHA-256(interrupt ‖ slot_sample(interrupt) ‖ stages[g].waypoint ‖ fold(checkpoints))
                                                      # Rule A1.3; the pivot W_{g+1}
        (beacon, beacon_height) ← (interrupt, interrupt_height)  # I_g stored as stages[g+1].beacon
    else:
        (last_interrupt, last_interrupt_height) ← (interrupt, interrupt_height)
                                                      # I_17; no waypoint is built

cut_off ← last_interrupt_height
make every path ask due at a height ≤ cut_off that has not happened yet   # section 13.1
for g in 1 … 17:
    stages[g].path ← stage g's checked path body, or empty bytes
                                                      # final stderr step: path_skip <g> ascending g, then not fetched by design 17
encode the object                                     # section 7.3; stage 0 has no path key

The repeat body always runs at least once, so leg_count ≥ 1. Each iteration is one leg of the drive, exactly STEPS_PER_LEG steps, then one checkpoint record. The checkpoint preimage is the previous checkpoint, the seed value or the stage waypoint, the terrain sample at that previous checkpoint (SAMPLE_BYTES), and le64(leg_result), as section 11.4 defines. Observe runs after the checkpoint is recorded, not in the middle of a leg. That Observe is how the drive notices the interrupt. The section 13.1 Hashcash Observe is not an interrupt read.

Stage 0 starts with no post. Its legs begin after the terrain fill, from the seed value. The interrupt comes first. The waypoint of the next stage is built from it and posted after it. That waypoint is the pivot. I_g is stored as the next stage’s beacon. It is not replaced.

Stage 0 has no plant and no ask. For W_1 … W_17, the ask window is section 13.1. Those asks happen during whichever stage the drive is in by then, or at the cut-off header. No ask is made above the cut-off. An ask does not end a stage. A miss does not stop the hike.

When stage 17’s repeat stops at I_17, produce records stage 17, stores last_interrupt and its height, and builds no waypoint. It makes the asks due at or below the cut-off and then encodes the object. It does not wait for a header above the cut-off.

13.3 Drive, path_skip, and not fetched by design

Normative.

leg_count is how many legs the section 13.2 drive recorded before the interrupt. The count is that stretch. It is a convenience copy of len(checkpoints). Verify records it under shape: shape_item includes leg_count when it differs from len(checkpoints) (Rule A4.1). No metric reads leg_count. Section 14.1 records leg_count ≥ 1 as shape, because the repeat runs at least once. Principle 9: there is no minimum or maximum leg count beyond that. Produce does not enforce a rate.

The stage-loop rig in QA/stage-loop/TEST-SPEC.md checks one production stage of this drive. A result there checks that one stage. It does not check two headers.

Produce does not invent path bytes. A checked 200 is stored. Anything else is a miss, and the miss is recorded. That is “no silent skip”. A miss does not stop the hike.

If the POST for W_g does not succeed, no path ask is due for that waypoint, because there is no plant height. Produce continues. The miss is kept for the final stderr step. If the POST returns 204, produce makes every ask that is due at or below the cut-off. After the cut-off, produce writes every stderr line in one step: path_skip <g> for each empty path among W_1 … W_16, in ascending g, then not fetched by design 17. No stderr line for those paths is written before that step.

Rule A3.4 Produce stderr

The stderr line for an empty path on W_g, with g in 1 … 16, is exactly one line:

path_skip <g>

<g> is the decimal waypoint index with no leading zeros. It is the same g as in W_g and stages[g]. The line is the ASCII bytes of path_skip, one ASCII space (U+0020), the decimal digits of g, and one LF (U+000A). No carriage return and no second space. This includes a POST that does not succeed, a miss on an ask due at or below the cut-off, and a cut-off miss. Produce stores empty path bytes and continues. Empty bytes are not a canonical path body. path_skip is not a silent skip. It does not authorize pausing the drive to wait for further headers. Test and live use this one line. No other spelling is this record.

That final step writes the lines in ascending g. Within that step, a line for a smaller g is not written after a line for a larger g. not fetched by design 17 is last.

The stderr line for W_17 is exactly the ASCII bytes of not fetched by design, one ASCII space (U+0020), 17, and one LF (U+000A):

not fetched by design 17

That line is the last line of the final stderr step. It is not path_skip. W_17 is never asked. Every ask would fall above the cut-off by position. Produce stores empty path bytes for stage 17 and continues to encode the object. It does not authorize waiting for a header above the cut-off. Produce does not write not fetched by design for W_15, W_16, or any other waypoint. Produce writes nothing on stderr for stage 0. Stage 0 has no path.

13.4 Bari restart

Informative.

Replacing a live Bari during a running produce is normal. A waypoint already in the pool is retained across a quick restart. A skip is the worst case, not a fee bump.

13.5 Live event log

Normative.

The live profile writes one append-only event log while produce runs, so a reader can see progress before the hike object exists. Only the live profile writes this log. The log is not the hike object, it is not a field of that object, and it is not an input to a leg, a checkpoint, a leg result, or a waypoint. The producer does not write the log from inside a leg. It does not write one line per checkpoint. It does not write one line per leg result. It does not write the log on the path that places the hike object.

The file path is the hike object path with the suffix .events. At the start of a live run the producer creates that file, replacing a file already at that path, and then only appends. Each record is one compact JSON object with no insignificant whitespace, keys in the order listed for that record, then one newline (U+000A). The producer appends the record after the work that record names, and those bytes are visible to a reader before produce continues. The hike object is still written only when the hike returns. If the log is not created or a record is not appended, the hike, the path ask, and path_skip are unchanged.

A tip in this log is the shared public tick: the natural hash and the height. It is not a count of confirmations, not a fee, and not an archive. This section does not add a wait for a tip.

The records are:

  1. Block. After the live producer reads a new header from the tip sequence. A header it ignores is not a record. event is hike.produce.block. height is that header’s height. tip is 64 lowercase hexadecimal digits, byte 0 first, the natural hash.

  2. Waypoint. When the live producer takes waypoint W_g for stage g ≥ 1 to post, after that waypoint is known and before the POST. There is no waypoint record for stage 0. The seed value is not posted. W_g is the pivot from section 12.2. The record is not written inside that calculation. event is hike.produce.waypoint. stage is g, from 1 through 17. waypoint is 64 lowercase hexadecimal digits, byte 0 first.

  3. Posted. After that stage’s waypoint POST has settled, once. A retry is not its own record. event is hike.produce.posted. stage and waypoint are the same as the waypoint record. posted is true when Bari answered the POST, and false when it did not. succeeded is true only when that post returned status 204. Status 204 means the waypoint was appended. status is that HTTP status when Bari answered, and is omitted when it did not. The waypoint is on this record. A missed plant is not fee-bumped, replaced, or repaired.

  4. Checked. After a status 200 body is checked under section 13.1, and only then. A miss is not this check. event is hike.produce.checked. waypoint is the asked waypoint, 64 lowercase hexadecimal digits, byte 0 first. checked is true only when the body is a path for that waypoint and the path’s block is one of H+2, H+3, or H+4. Otherwise checked is false.

  5. Fetch. After GET {bari-url}/path/{leaf} has returned and the body has been read. The record is not written when the request is sent. event is hike.produce.fetch. waypoint is the asked waypoint, 64 lowercase hexadecimal digits, byte 0 first. worked is true only when this fetch is the successful fetch section 13.1 stores. status is the HTTP status when the producer has one, and is omitted when it does not. block is the path body’s block hash, 64 lowercase hexadecimal digits, byte 0 first, and block_height is that block’s height. Those two keys are present only when the body names that block. miss is present only when the body is one of these phrases: still in the pool, sealed but not in a block yet, not included, already archived after it was served, unknown. A miss stays a miss. not included is not a successful fetch. Stderr path_skip remains the record that an empty path was stored. This fetch record does not replace it.

The log does not choose a Bari URL. {bari-url} stays the URL section 13 already uses. On the live profile the built-in default stays https://bari.pcoc.app. This section does not define a path that skips Bari.

14. Verify

Normative.

Rule A4. What verify measures against the portable index

Input is one hike object and {index-url}.

The profile in force is the section 6 constants, unless the verifier is running conformance-small. That run may differ only in SAMPLE_BYTES, SLOT_BYTES, TERRAIN_BYTES, and STEPS_PER_LEG. The profile name and those four integers are verifier inputs. The hike object gains no new field. The verify record must state the profile it ran under, and, when that profile is conformance-small, SAMPLE_BYTES, SLOT_BYTES, TERRAIN_BYTES, and STEPS_PER_LEG. That statement is measurement provenance, not a producer claim. Injected draw bytes are rig-only, for a deterministic Rule A4.4 vector. They are never a production input. Production verify reads the operating-system CSPRNG.

Section 14 records measurements against the object and the portable index. It does not judge them. It does not read Bitcoin as its procedure. Outcome tokens, lowercase with spaces, are match, mismatch, present, skip, not defined, not read, not fetched by design, and n/a. There is no yes/no token. This section does not pass, fail, reject, deny, or admit.

Evidence and convenience. Evidence, the hash-bound values, is the seed (public_key, seed_beacon), the checkpoints, the leg results, the waypoints W_1 … W_17, the Bitcoin headers (stages[g].beacon for g ≥ 1, and last_interrupt), the path’s leaf, Merkle path and root, which bind the path to the waypoint, stages[0].waypoint (= SHA-256(seed), the seed value), and any calculated field whose hash dependencies enforce order and cost. Convenience signposts are the path locators (txid, vout, block_hash, block_height), checked against the index, leg_count, beacon_height, last_interrupt_height, the section 6 constants, and stages[0].beacon (= seed_beacon). Verify records leg_count under shape: shape_item includes leg_count when it differs from len(checkpoints) (Rule A4.1). Section 6 constants are recorded as constant. Stored heights are recorded as index_comparison (Rule A4.2). They are convenience. d_g and delta_h do not subtract a stored height unless the index comparison that confirms it is match (Rules A4.5 and A4.8). Verify checks a convenience signpost against the evidence or the index and never relies on it. A broken signpost is a recorded mismatch. Section 15 is where an admission service may judge that recorded mismatch. N, S, delta_h, d_g, a, b, spans, and rates are computed by verify from that evidence and the portable index, are never producer claims, and are fields of the verify record.

The measurements are recorded in section order: Rule A4.1 (section 14.1), Rule A4.2 (section 14.2), Rule A4.3 then Rule A4.5 (section 14.3), Rules A4.6 through A4.11 (section 14.4), Rule A4.4 (section 14.6). Later measurements are still recorded. Section 15 is not applied inside this section.

Verification does not GET an HTTP tip. Verification does not GET a Bari path. Verification does not Observe, so it cannot recover an unstored header by assuming the next height.

Informative. By design, any verifier may instead read a Bitcoin node directly (block headers with nTime, and the waypoint plants' OP_RETURNs) and build its own index. This specification does not require it.

Verify record

Normative. The verify record is one JSON object. It is not the hike object. The hike object gains no new field. The profile name and, under conformance-small, the four integers SAMPLE_BYTES, SLOT_BYTES, TERRAIN_BYTES, and STEPS_PER_LEG are verifier inputs. They appear on this record as measurement provenance. profile is always on the record: conformance-small, or production for a run under the section 6 constants. The profile is not absent from the record. When profile is conformance-small, the record also has sample_bytes, slot_bytes, terrain_bytes, and steps_per_leg. Those four integer keys are present only for conformance-small. When profile is production, those four integer keys are absent. They are not a producer claim. The keys of this full record include profile, shape, shape_item, headers, seed_value, checkpoints, waypoints, paths, anchored_total, skip_list, a, b, N, S, delta_h, fixed_start, P_b, height_P_b, index_comparison_P_b, T_P_b, height_I_a_minus_1, T_I_a_minus_1, six_leg, six_leg_runs, and analysis. On this full record, every key listed above is present. The fast record in section 15.3 omits checkpoints[*].legs[*].result, six_leg, and six_leg_runs. The Rule A4.3 checkpoint digest is thorough-only in every profile. The fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2 from stored checkpoints. Every other section 14 key stays, including checkpoints[*].g, checkpoints[*].legs[*].j, waypoints[*].result, and seed_value. It has no placeholders. It does not fill an omitted key with not defined. There is no JSON null. Tokens are JSON strings. Top-level key order is not significant for the values. The bytes hashed as verify_record_sha256 are this object encoded with RFC 8785 (JSON Canonicalization Scheme). That encoding is the default for this cut. If a future profile stores the record as CBOR instead, that profile must name deterministic CBOR (RFC 8949 §4.2.1) explicitly. This file does not define a third encoding. Arrays are JSON arrays. n/a is used only as plant when path_state is skip: the plant comparison does not apply. n/a is not not defined. A field Rule A4.11 leaves undefined stores not defined. Display rounding is not under test. The rate quotient is not a key of the verify record.

Normative. Part A. The JSON type of each numeric key on this verify record is fixed. It does not depend on the value. Heights, counts, Unix seconds, and leg indices are JSON numbers. Their stated bound is ±(2^53−1). A non-negative key is 0 … 2^53 − 1. Signed differences of those values, including S, delta_h, d_g, and the analysis spans, stay inside ±(2^53−1) and are JSON numbers. Decimal-string keys: none. Integers are plain decimal JSON numbers. No exponent. No leading zeros. No spelling -0. A negative integer uses a leading - and then digits, and the value is not zero. A measurement token is a JSON string. six_leg_runs is a JSON array of draw objects when the draws are recorded, and the JSON string not read when the six-leg draw is not read. That string is the token. It is not an array and it is not [].

When a shape mismatch leaves a key or an array element without a usable value from the object, that value is not defined, and an index read or T that cannot be made is not read. Array lengths stay as stated: headers has 19 elements, checkpoints has 18, waypoints has 17, paths has 17, and analysis.per_stage has 18 elements. When that stage’s checkpoints are unusable, that stage’s legs is not defined. The length of legs is the number of checkpoints. It does not read leg_count. The one exception is a checkpoint with no leg result: its result is mismatch (Rule A4.3).

shape is match or mismatch.

shape_item is a JSON array of strings from this closed set, and from no other string:

Order String Violated when
1 malformed cbor The input is not one definite-length CBOR data item under section 7.3
2 field set A map does not have exactly the section 8 fields, types, and lengths
3 stage-0 path key Stage 0 has a path key
4 W_17 path stages[17].path is not empty
5 constant kind, fill, slot_bytes, n_slots, sample_bytes, or steps_per_leg differs from the profile in force
6 stages length stages does not have length STAGES
7 leg_count leg_count differs from len(checkpoints), leg_count < 1, or an array length differs
8 checkpoint width A checkpoint is not 32 bytes
9 leg result width A leg result is not 8 bytes
10 seed beacon stages[0].beacon is not seed_beacon
11 last_interrupt last_interrupt is not 32 bytes, or it does not differ from stages[17].beacon, or its height is not strictly greater
12 header height order A stored height is not strictly greater than the header before it
13 header hash differ A stored header hash does not differ from the header before it
14 height_range An integer above 2^53 − 1 is in the object's own bytes, including stored_height and P_b when those bytes are the object's. A height inside a hash-verified path body is not this string

On match, shape_item is []. On mismatch, the array contains every string whose condition is violated, each string once, in the order 1…14. The table order is normative for shape_item. A condition that is violated is not dropped because another condition is also violated. A condition that tests a field’s value is violated when that field is absent. stage-0 path key is violated only when the key is present.

A malformed CBOR value is the one exception to that absent-field rule. shape is mismatch and shape_item is exactly ["malformed cbor"]. No other string is added.

When the object decodes, last_interrupt and last_interrupt_height are both absent, and every other Rule A3 condition that uses only present fields is met, shape_item is exactly ["field set", "last_interrupt", "header height order", "header hash differ"]. That is the earlier-object case. headers still has 19 elements. headers[18].stored_hash and headers[18].stored_height are not defined. headers[18].index_comparison is not read, and headers[18].T is not read. If only the last_interrupt hash is absent and last_interrupt_height is present and strictly greater than stages[17].beacon_height, header height order is not violated.

headers is an array of 19 objects, index 0 through 18, in stored-header order. Index 0 is stages[0].beacon (seed_beacon). Index k for k from 1 through 17 is stages[k].beacon (I_{k−1}). Index 18 is last_interrupt (I_17). Each object has exactly stored_hash, stored_height, index_comparison, and T. stored_hash is 64 lowercase hexadecimal digits, natural byte order, byte 0 first, the hash stored for that header, or not defined when that hash is absent. stored_height is the unsigned height stored for that header, or not defined when that height is absent. An integer above 2^53 − 1 in that object byte is not recorded as that unsigned value: shape is mismatch, shape_item includes height_range, and the value is not defined. It is not not read. index_comparison is match, mismatch, or not read. When stored_hash is not defined, index_comparison is not read. T is an integer number of Unix seconds only when index_comparison is match and header.time follows section 9, and not read otherwise.

seed_value is match if stages[0].waypoint equals SHA-256(public_key ‖ seed_beacon), and mismatch otherwise.

checkpoints is an array of 18 objects, index g from 0 through 17, one per stage. Each object has g and legs. legs is an array in leg order, j from 0 through one less than the number of checkpoints in that stage. legs runs over the checkpoints. Each leg object has j and result. result is match or mismatch. A checkpoint with no leg result has result mismatch.

waypoints is an array of 17 objects for the Rule A4.3 recomputes of W_1 … W_17, in ascending g from 1 through 17. Each object has g and result (match or mismatch).

paths is an array of 17 objects in ascending g from 1 through 17. Array position g − 1 is W_g.

For g from 1 through 16, each object has g, path_state, plant, P_g, height_P_g, d_g, and claimed_block. path_state is present or skip. plant is match, mismatch, not read, or n/a. plant is n/a only when path_state is skip. Then P_g, height_P_g, d_g, and claimed_block are not defined.

For g = 17, the object has g, w17_path, and claimed_block. When stages[17].path is empty, w17_path is not fetched by design and claimed_block is not defined. When stages[17].path is not empty, w17_path is not defined, shape_item includes W_17 path, and claimed_block is the block encoding below when both fields can be read, otherwise not defined. Those bytes are not present and are not anchored and fetched.

P_g, P_b, and claimed_block, when they are objects, have exactly block_hash and block_height. block_hash is 64 lowercase hexadecimal digits in Bitcoin display order, the same presentation as the path body’s block_hash. block_height is an integer. root in an index plant comparison stays natural-order hex. That comparison is not these keys.

When plant is match, P_g is that object and height_P_g is the index-confirmed block_height (the plant the index recorded). Otherwise P_g and height_P_g are not defined. d_g is the signed integer height_P_g − height(I_{g−1}) only when plant is match and the index comparison of I_{g−1} (headers index g) is match. height(I_{g−1}) in that subtraction is header.height from that matching index document. Otherwise d_g is not defined. match for that header requires the stored hash to agree and the stored height to equal header.height. Do not subtract stages[g].beacon_height when that comparison is not match. Previously d_g subtracted the stored height whenever plant was match. That reading is withdrawn. On a present path, claimed_block is that object when both fields can be read from the path body, and not defined when they cannot. That gap is not an index read.

anchored_total is the count of g in 1…16 whose path_state is present and whose plant is match. skip_list is a JSON array of integers, ascending, each a g in 1…16 whose path_state is skip. It is [] when there is none.

a and b are JSON numbers in 0 … 2^53 − 1, or not defined. N is a JSON number in 0 … 2^53 − 1, or not defined. S is a JSON number of seconds in ±(2^53−1), or not read, or not defined. delta_h is a JSON number in ±(2^53−1), or not defined. P_b is the block encoding for W_b when bookends exist, and not defined otherwise.

The raw endpoints are present as their own keys. The read for P_b is GET {index-url}/index/{P_b.block_height}. index_comparison_P_b is match, mismatch, or not read. index_comparison_P_b is not read when that read does not return a document, mismatch when the document’s 32-byte hash does not equal the 32 bytes of P_b.block_hash or header.height is not P_b.block_height, and match when the hash agrees and header.height equals P_b.block_height. header.hash may use uppercase digits (section 7.2). Letter case does not change the 32-byte value. The comparison is those 32 bytes, not the hex spelling. height_P_b is that document’s header.height when index_comparison_P_b is match, and not defined otherwise. T_P_b is the integer header.time only when index_comparison_P_b is match and header.time follows section 9. A time that does not follow section 9 is not read. Otherwise T_P_b is not read. height_I_a_minus_1 is header.height from the index document for I_{a−1} when that header’s index_comparison is match, and not defined otherwise. It is not the stored stages[a].beacon_height taken alone. match for that header requires the stored hash to agree and the stored height to equal header.height. T_I_a_minus_1 is T(I_{a−1}) under Rule A4.2, an integer only when that index_comparison is match, and not read otherwise. When Rule A4.11 applies because the object's own bytes leave fewer than two present paths, a, b, N, S, delta_h, P_b, height_P_b, index_comparison_P_b, T_P_b, height_I_a_minus_1, and T_I_a_minus_1 are not defined. When Rule A4.11 applies because two or more paths are present, every read returned, and fewer than two plants are match, those same fields are not defined. That case is index-settled. When Rule A4.11 applies because a dependent read is short or missing, these fields are not read: a, b, N, S, delta_h, P_b, height_P_b, index_comparison_P_b, T_P_b, height_I_a_minus_1, T_I_a_minus_1, fixed_start.N, fixed_start.S, fixed_start.delta_h, and analysis.legs_outside.

fixed_start is a verify-computed key. Verify computes it from keys this record already holds. The producer does not claim it. The hike object does not carry fixed_start. When bookends are defined, fixed_start is an object with N, S, and delta_h. fixed_start.N is the checkpoint count over stages 1 through b−1. It does not read leg_count. fixed_start.S is T_P_b − headers[1].T. If either time is not read, fixed_start.S is not read. fixed_start.delta_h is the index-confirmed height(P_b) minus the index-confirmed header.height of the header at headers[1] when headers[1].index_comparison is match and the P_b height read is match. headers[1].stored_height is not that height. fixed_start.delta_h is not read when headers[1].index_comparison is not read or the P_b height read is not read. A not read on either of those reads stays not read. fixed_start.delta_h is not defined only when the object's own bytes leave no bookends, when two or more paths are present, every read returned, and fewer than two plants are match, or when both of those reads returned and headers[1].index_comparison is mismatch or the P_b height read is mismatch. The two-or-more present paths case is index-settled. When the object's own bytes leave no bookends, fixed_start.N, fixed_start.S, and fixed_start.delta_h are not defined.

six_leg is an object { "indices": [q0, q1, q2, q3, q4, q5], "results": [token, token, token, token, token, token] } in draw order when six indices are drawn. Each result is match or mismatch. When fewer than six legs exist, six_leg is not defined. That count is settled by the object's own bytes. When the byte source cannot supply the next 8 bytes, six_leg is not read. A short-byte six-leg draw stays re-readable. It is not not defined. Short path bytes for a six-leg run are that not read. six_leg_runs is an array of those objects, one Rule A4.4 draw per element, in the order the runs were requested. The recorded order is that request order. Whether the runs execute together is Rule A4.4.4. six_leg is six_leg_runs[0] when six_leg_runs is a non-empty array. When the six-leg draw is not read, six_leg_runs is not read and six_leg is not read. That array is not [].

analysis is an object with whole_hike_legs, whole_hike_span, per_stage, and legs_outside. whole_hike_legs is the number of checkpoints over stages 1–17, a JSON number in 0 … 2^53 − 1. whole_hike_span is the JSON number T(I_17) − T(I_0) in ±(2^53−1), or not read. per_stage is an array of 18 objects, index g from 0 through 17, each with g, checkpoint_count, and span. checkpoint_count is the number of checkpoints in the stage. It is not a read of the object’s leg_count. span is the JSON number of the per-stage span in ±(2^53−1), or not read. legs_outside is the number of checkpoints over stages outside a … b−1, a JSON number in 0 … 2^53 − 1, or not defined when bookends are not defined. analysis has no rate key.

14.1 Shape

Normative.

Rule A4.1 Shape

Shape compares the object with Rule A3. shape is match when every condition below is met. Otherwise shape is mismatch, and shape_item names each condition that is violated. Later measurements are still recorded. A malformed CBOR value is shape mismatch and shape_item is exactly ["malformed cbor"]. A condition that tests a field’s value is violated when that field is absent. stage-0 path key is violated only when the key is present. When a shape mismatch leaves a key or an array element without a usable value from the object, that value is not defined, and an index read or T that cannot be made is not read. Array lengths stay as stated in the verify record.

Shape does not decode a path on stages 1–17 and does not record skip or present. Rule A4.5 does. This measurement does not GET a path.

14.2 Headers

Normative.

Rule A4.2 Headers

{index-url} is the absolute URL from section 9. For each stage, in order from stage 0, GET {index-url}/index/{beacon_height}. Then GET {index-url}/index/{last_interrupt_height}. Each height is the shortest base-10 digits of the integer.

Each GET is a clock read under section 9. For each stored header, record its stored height, its stored hash, its index comparison, and its T.

Previously this rule took T from a successful read whose time followed section 9 even when the stored hash did not agree. That reading is withdrawn.

On the mainnet path, a successful read’s header.hash, header.height, and header.time are the portable index’s record of that Bitcoin block. index_comparison uses hash and height only. T is recorded separately.

Read every stored header, including last_interrupt. A mismatch or a not read does not skip a later header. Later measurements are still recorded.

The LAN test plane has its own index and its own synthetic network. A synthetic hike can still record match against that index. On the LAN test plane, the comparison is against that plane’s own synthetic block. A header that matches that synthetic block is not a mismatch for not being a Bitcoin block. A match against that index is not a mainnet fact.

This header read does not run a leg and does not recompute terrain. It does not GET an HTTP tip. It is not Observe.

14.3 Waypoints and paths

Normative.

Rule A4.3 Chain

Recompute terrain as in section 10.

Checkpoint comparisons, for each stage in order and each leg j in order:

This digest record does not run a leg. It checks every stored checkpoint that has a leg result against that limb and the recomputed sample. A checkpoint with no leg result is mismatch. A mismatch does not skip a later leg.

Record match if stages[0].waypoint equals SHA-256(public_key ‖ seed_beacon). Record mismatch otherwise. That comparison does not call the field a Rule A1 waypoint.

Informative. Stage 0’s checkpoint comparisons use the computed seed value. The W_1 recompute uses the stored stages[0].waypoint. So the stored field is measured by this seed-value comparison and through W_1.

For g from 0 through 16, recompute W_{g+1} under section 12.2. Use as B the interrupt I_g, which is the stored stages[g+1].beacon. Use the stored stages[g].waypoint and the stored checkpoints of stage g. On g = 0 that stored field is the seed value, not a member of W_1 … W_17. Record match if the digest equals stages[g+1].waypoint. Record mismatch otherwise. This uses the stored checkpoint bytes. It does not run a leg. No waypoint is recomputed from stage 17 or from last_interrupt. The waypoints recomputed here are W_1 … W_17.

Rule A4.5 Paths

For each of W_1 … W_16 (stages[1].path … stages[16].path):

When stages[17].path is the empty byte string, record not fetched by design. It is not skip and it is not present. When those bytes are not empty, shape_item includes W_17 path. Do not record those bytes as present. Do not authenticate a plant. They are not anchored and fetched. If those bytes still yield block_hash and block_height, record them as claimed_block. If they do not, claimed_block for that waypoint is not defined.

A present path is measured by the procedure below.

A path that does not decode as a canonical path body, or whose validate_for does not succeed for that stage’s waypoint, has plant mismatch when the bytes were read, because the path facts disagree with a canonical body. That is not an unsuccessful index read. A hash-verified path body with a height above 2^53 − 1 is plant mismatch and claimed_block not defined (P14.6). The hike object still decodes. That height is not shape_item height_range. Do not clamp that height and do not record it.

anchored_total is the count of W_1 … W_16 whose path_state is present and whose plant is match. Only that pair is anchored and fetched. skip_list is the JSON array of skipped waypoint indices among W_1 … W_16, ascending.

A present path whose plant is mismatch or not read is in neither anchored_total nor skip_list. It is recorded, and it is never a bookend.

Present path plant measured against the index

Normative.

Purpose and authority

On the mainnet path, this measurement compares each present path and that stage’s waypoint with the plant facts the portable index records. It is the measurement “present path plant measured against the index,” placed here with the waypoint measurement.

The measurement records plant match, mismatch, or not read for the claimed path and its waypoint anchor. Fake or invented locator metadata, once read, is plant mismatch.

Bitcoin is the ground truth for those facts. The portable index stands in for it faithfully. This measurement reads them from the portable index. On the mainnet path the index document’s network is main. When the document was read, any other network is plant mismatch.

Bari is the produce-side path-fetch and plant helper. Verify does not re-GET Bari for this measurement.

When the read returns a document and that document records no plant for that block, plant is mismatch. When plants are present and none matches, plant is mismatch. When the read does not return a document, plant is not read. There is no silent fallback from a missing document.

This measurement is separate from the header index comparison in section 14.2. Section 14.2 records, for each stored header, match, mismatch, or not read, and T or not read. This measurement records whether a present path’s locators and Merkle-committed root match the plant the portable index records. A header index comparison of match does not mean the plant comparison is match. A plant comparison of match does not mean the header index comparison is match.

What the proof already carries

For a present path that decodes as CanonicalPathBody, the hike object stores path as the canonical path-body bytes defined by that type in services/bari/src/proof.rs (the same type section 13.1 names). The hike SPEC inlines the field list and roles below for this measurement; exact byte layout and encode/decode remain by-reference to that Bari type. A present path’s bytes include, when this measurement has decoded them:

Field Role in this measurement
leaf Equal to that stage’s waypoint when this measurement’s validate_for succeeds
Merkle path (leaf_index, tree_size, siblings) Commits leaf to root when this measurement’s validate_for succeeds
root The Merkle root the on-chain waypoint anchor must commit
txid Transaction locator
vout Output index locator
block_hash Block locator
block_height Height locator

This measurement does not add a new hike-object field and does not change the path wire version. It binds the locators and root already carried in path.

Shape, section 14.1, does not decode path. For a present path on W_1 … W_16, this measurement decodes CanonicalPathBody and runs validate_for(waypoint) (leaf + Merkle to root only). A present path for which that decode or validate_for does not succeed has plant mismatch. A skip is not decoded here. Stage 0 has no path. W_17 is not fetched by design and is not decoded here.

Normative verify procedure

Inputs. One hike object, and {index-url}. Plant facts are the plant records that the index document holds for the block: root, txid, vout, block_hash, and block_height. They are not a new hike-object field. Errors on this measurement, not section 15 decisions: a read that does not return a document is plant not read; a document that was read and has no plant for that block, or no matching plant, is plant mismatch; a path body that does not yield claimed_block is not defined.

This bind is recorded for every present path among W_1 … W_16, in order, inside this waypoint measurement, after section 14.1 and section 14.2 have been recorded. It does not apply section 15, and it does not stop a later stage or a later measurement. If the read does not return a document, plant is not read. If the document was read and it has no plant for that block, or the plants do not match, plant is mismatch. claimed_block is always recorded (Rule A4.5).

For each present path that decodes as CanonicalPathBody and whose validate_for succeeds for that stage’s waypoint, let body be that decoded body and let waypoint be that stage’s stored waypoint.

  1. Leaf already bound. body.leaf equals waypoint and the Merkle path verifies to body.root, the decode and validate_for just above. This measurement does not repeat a weaker check. A present path that did not succeed there has plant mismatch.

  2. Obtain plant facts for the locators. On the mainnet path, the verifier reads GET {index-url}/index/{body.block_height}. The plant facts for (body.txid, body.vout, body.block_hash, body.block_height, body.root) come from that portable-index document. Bari is not queried on this measurement. If that read does not return a document, plant is not read and steps 4–5 are not a mismatch. A document that was read and records no plant for that block is mismatch.

  3. What the index attests. Informative. The portable index records a plant for a block only after checking against Bitcoin that txid is in block_hash at block_height on the best chain, that output vout has value zero, and that its scriptPubKey is exactly Bari commitment_script(root). Verify does not repeat those checks. It compares the body with the plant the index records (step 4).

  4. Portable index. The verifier reads GET {index-url}/index/{body.block_height}. The document’s network must be main. Any other network is plant mismatch. The index must be able to record two plants in the same block. A plant the document records for that block must equal this body on root, txid, vout, block_hash, and block_height. root is natural-order hex. txid and block_hash are Bitcoin display hex, the same presentation as IndexSnapshot::to_stored. A plant’s tree_size is not part of the comparison. This step compares plant facts that were read. A read that does not return a document is plant not read. A document that was read and records no plant for that block, a read document whose network is other than main, or a read plant that disagrees on those locator and root fields is plant mismatch. There is no silent fallback. A read that does not return a document is not recorded as mismatch.

  5. No invented metadata. Locators that do not equal a plant the index records (step 4) are plant mismatch.

plant is match only when the path decodes and validates (step 1), the index document was read (step 2), its network is main, and a plant it records for that block equals body on root, txid, vout, block_hash and block_height (step 4). That match is what authenticates P_g: body.block_hash at body.block_height. Rule A4.5 then records height_P_g. It records d_g only when the index comparison of I_{g−1} is also match. Section 15 is where an admission service may judge a plant mismatch. This section only records it.

LAN test plane

The LAN test plane has its own index and its own synthetic network. On that plane, this measurement binds each present path among W_1 … W_16 to that plane’s own index and its synthetic facts. That index asserts that plane’s synthetic facts. Against that index, network equal to synthetic is the network this measurement reads, and a synthetic document is not a plant mismatch for being synthetic. A synthetic hike can still record match against that index.

On that plane, step 2 obtains that plane’s synthetic facts from its own index. The verifier reads GET {index-url}/index/{body.block_height} on that index, and a plant that index records for that block must equal body on root, txid, vout, block_hash, and block_height. On that plane, step 4 uses that index and its synthetic facts. A synthetic path whose plant comparison is match against that index is not a plant mismatch for not being mainnet, and step 5 does not record plant mismatch for that reason. On the LAN test plane, the index check is against that plane’s own synthetic block. A header that matches that synthetic block is not a mismatch for not being a Bitcoin block.

A match against that index is not a mainnet fact.

Explicit separation from the header index comparison

Rule A4.2, section 14.2 Rule A4.5, this section
Object Each stored header’s hash and height, including last_interrupt Path locators and root on a present W_1 … W_16
Read GET {index-url}/index/{height} → header, including header.time GET {index-url}/index/{block_height} with a plant locator comparison
Recorded index_comparison match, mismatch, or not read; T or not read plant match, mismatch, or not read; on match, P_g, height_P_g, and d_g
Does not Decode path Re-check the stage header against its stored height

What this measurement does not claim

14.4 Bookends

Normative.

The verifier’s wall clock is not an input. Verify does not Observe, so it cannot recover an unstored header by assuming the next height. Block times are T from Rule A4.2, read as they are, and a T is recorded only when that header’s index_comparison is match. No clamp, no median-time-past rule, no reordering, and no other adjustment is applied. delta_h (Δh) is recorded in this bookend block when both heights are index-confirmed. Rule B3 may read it. It is a raw measurement. It is not analysis and it is not policy.

Rule A4.6 Bookends

W_a and W_b are the anchored-and-fetched waypoints with the lowest index and the highest index, wherever they sit. “First” and “last” mean those indices, not the order in which asks completed. Only a present path whose plant comparison is match is anchored and fetched. A present path whose plant comparison is mismatch is never a bookend.

a and b are those indices. Both are in 1 … 16. W_17 is not fetched by design, so it is not a bookend. Stage 0 has no path, so a is not 0.

Rule A4.7 Bookend legs

N is the number of checkpoints over stages a … b−1, inclusive. These are the checkpoints of the legs that start from W_a and are folded into W_b. N does not read leg_count. N is a JSON number in 0 … 2^53 − 1.

Rule A4.8 Bookend span

Record the endpoints from the index, not from stored heights alone.

P_b is the authenticated block named in W_b’s path. The header read for that block is GET {index-url}/index/{P_b.block_height}. index_comparison_P_b and T_P_b are that read, as the verify record states. height_P_b is header.height from that document only when index_comparison_P_b is match. Otherwise height_P_b is not defined.

I_{a−1} is the interrupt W_a was built from, stored as stages[a].beacon. The stored height stages[a].beacon_height is convenience. height_I_a_minus_1 is header.height from the index read of that header only when that header’s index_comparison is match. Otherwise height_I_a_minus_1 is not defined. T(I_{a−1}) is T from Rule A4.2, so it is an integer only on that same match, and not read otherwise.

S = T(P_b) − T(I_{a−1})
Δh = height_P_b − height_I_a_minus_1

S is seconds, a JSON number in ±(2^53−1), recorded when both T values are integers, including zero or a negative value. If either T is not read, record S as not read. Do not invent an integer S. Do not subtract a time taken from a header whose index_comparison is not match.

delta_h is a JSON number in ±(2^53−1) only when both height_P_b and height_I_a_minus_1 are index-confirmed integers, including zero or a negative value. Otherwise delta_h is not defined. Do not compute it from stages[a].beacon_height or from any other producer-stored height. match for index_comparison_P_b requires the header hash to agree and header.height to equal P_b.block_height. match for I_{a−1} requires the stored hash to agree and the stored height to equal header.height. The subtraction uses those header.height values.

Previously Δh used stages[a].beacon_height whenever the plant height was known. That reading is withdrawn. Δh does not require T. When T is not read and both heights are index-confirmed, S is not read and delta_h is still that integer. N is still the integer from Rule A4.7 when bookends exist. Both S and delta_h sit in the bookend block. Rule B3 may read delta_h. It is not analysis and it is not policy.

Rule A4.9 Bookend leg rate

The verify record holds the integer N and the integer S only. It does not hold a floating-point rate. It does not hold any other encoding of the quotient.

The canonical rate input for the admission service is the pair (N, S), when both are integers. Rule A4.11 records N and S as not defined when the object's own bytes leave fewer than two present paths among W_1 … W_16. When a present path is not anchored because a read is not read, Rule A4.11 records the fields that depend on that read as not read. This rule does not then store integers.

The rate display is N÷S when S > 0. Otherwise the rate display is not defined. N and S are still shown. not defined here is a display token. It is not a floating-point value, and it is not a judgment. Display rounding is not under test. The quotient is not in the verify record.

When S is not read, the rate display is not defined, and there is no numeric pair.

Rule A4.10 Analysis

Record these beside the bookend block. They are analysis. Rule B0 does not read them for the leg-rate floor. Each span below uses T from Rule A4.2, so each endpoint time exists only when that header’s index_comparison is match. The span is a signed integer of seconds when both endpoint times were read, and not read when either endpoint T is not read. Each rate below is display only, on the same rule as Rule A4.9: the quotient when the span is an integer greater than 0, otherwise not defined. Display rounding is not under test. The verify record’s analysis holds the integers. It does not hold a float. delta_h is not an analysis value.

delta_h is not in this list. It stays in the bookend block (Rule A4.8).

Rule A4.11 Fewer than two anchored and fetched

If fewer than two waypoints among W_1 … W_16 are anchored and fetched, there are no bookends. When the object's own bytes leave fewer than two present paths among those waypoints, record a, b, N, S, delta_h, P_b, height_P_b, index_comparison_P_b, T_P_b, height_I_a_minus_1, T_I_a_minus_1, fixed_start.N, fixed_start.S, fixed_start.delta_h, and analysis.legs_outside as not defined. When two or more paths are present, every read returned, and fewer than two plants are match, those same fields are not defined. That case is index-settled. When a dependent read is short or missing, including an unreadable header, record these fields as not read: a, b, N, S, delta_h, P_b, height_P_b, index_comparison_P_b, T_P_b, height_I_a_minus_1, T_I_a_minus_1, fixed_start.N, fixed_start.S, fixed_start.delta_h, and analysis.legs_outside. The rate display is not defined. anchored_total and skip_list from Rule A4.5 are still recorded.

Informative. The legs in N start from W_a, which hashes I_{a−1}, so they could not begin before I_{a−1} existed. They are folded into W_b, whose plant is in P_b, so they existed by then. Naming an older interrupt only makes S longer. The interrupts in between, and any skipped waypoints, do not change N÷S, apart from nTime effects at the two ends of S. An honest S also includes the one to four blocks between I_{b−1} and P_b. Miner nTime can move either end. Rule B3 is the candidate lever. That remark is not a Part A number. A stored interrupt shows that the header exists. It does not show that produce ran live. Only anchored and fetched shows that the fold existed by the plant’s block.

14.5 Anchored and fetched

Normative.

anchored_total is Rule A4.5: path_state present and plant match among W_1 … W_16. A skip is not in that count. A present path with plant mismatch or not read is not in that count. W_17 is not in that count. The count is not a threshold. This section does not judge it. How many waypoints are fetchable is Rule A3.3.

14.6 Six-leg check

Normative.

Rule A4.4 Six-leg check

Six legs are drawn. The draw count is 6. It is not STAGES. It is not AUDIT_K. N in this file is bookend legs (Rule A4.7). This section does not use N for the hike’s leg total.

A global leg index addresses one leg of the hike. Let L_g be the number of checkpoints in stage g, len(stages[g].checkpoints). The draw does not read leg_count. start_0 = 0, and start_{g+1} = start_g + L_g. Let leg_total = start_{STAGES}. The index q with start_g ≤ q < start_{g+1} is leg j = q − start_g of stage g. When shape is match, leg_total ≥ STAGES. STAGES is 18 and the draw count is 6, so at least 6 indices exist. There is no AUDIT_K.

Before the draw, if there are fewer than 6 legs, six_leg is not defined and the draw is not made.

Each verification draws its own 6 distinct global indices, uniformly, from the operating-system CSPRNG. The getrandom interface is one conforming way to read that CSPRNG. The bytes are fresh for that verification. Another verification draws again. The two draws are not required to be the same indices.

One unbiased draw from 0 … leg_total−1 uses mathematical integers:

M = 2^64
limit = M − (M mod leg_total)
repeat:
    read 8 bytes from the CSPRNG
    u = u64_le(those bytes)          # 0 ≤ u < M
    if u < limit:
        return u mod leg_total

When leg_total is a power of two, limit = M and the first draw is used.

The measurement repeats that draw, discarding an index already chosen, until it holds 6 distinct indices.

An 8-byte draw with u >= limit is consumed. Those bytes are not read again. If the byte source cannot supply the next 8 bytes before 6 distinct indices have been obtained, record the draw as not read. A short-byte six-leg draw is that not read. It is not not defined. Short path bytes for that run are the same not read. When the six-leg draw is not read, six_leg_runs is not read and six_leg is not read. That array is not []. A later verification may draw again. Indices drawn before that are not the six leg results.

The index draw in this section runs on the calling thread. It finishes before any leg result is recomputed. A verifier may recompute the six leg results concurrently, and may run Rule A4.3 checkpoint comparisons concurrently. A single-threaded recompute follows this rule. Elements of six_leg_runs may execute in parallel. This file does not require a thread pool and does not set a pool size.

For each chosen index, recompute that leg from section 11.2. The previous checkpoint is that stage’s leg-0 start when j = 0 (Rule A2.2): SHA-256(public_key ‖ seed_beacon) on stage 0, and the stored waypoint on stage g ≥ 1. Otherwise it is the stored checkpoints[j − 1] of that stage. The draw count is 6 either way.

The order of the chosen indices is the order drawn. The order in which the leg computations finish does not change that order. For each chosen index, record match if the recomputed leg result equals u64_le(leg_results[j]). Record mismatch otherwise.

14.7 Earlier objects

Normative.

Rule A5. Earlier objects

Objects from before this amend still reach this verifier. The object kind is unchanged. Verify completes and records what it finds. Examples are a seed-value comparison of mismatch, a shape mismatch because last_interrupt and last_interrupt_height are both absent (shape_item exactly ["field set", "last_interrupt", "header height order", "header hash differ"] when every other present-field condition is met) or because stage 0 carries a path key (shape_item includes stage-0 path key), and recomputed waypoints of mismatch. There is no legacy rule. Nothing is deleted. Kept examples, including the 2026-10-04 live objects, stay as they are.

15. Admission service policy

Normative for the admission service only. Part B. Proposed; takes effect on Christian's lock. Everything in this section, including the section 15.1 challenge encoding, the Sign-in origin, and the issuing routes, is proposed and takes effect on Christian's lock. This file does not say those three are locked by the Chief of Staff. Rules B0–B8 remain candidates for their thresholds, floors, the letter k, and the budgets. Rule B9 is normative. Numeric thresholds in section 15.5 are candidates. They are not locked.

Part A records and measures. This section is the only judgment. The producer in section 13 records evidence and does not judge. The verifier in section 14 outputs a verify record of measurements and does not judge. The admission service applies this section.

The admission service is the actor in this section. The Grounding Lab Admission Gate (Gate) is the deployment name of that service. It is not a second specification. The Sign-in door is the HTTP door: POST /v1/sign-in/challenge and POST /v1/sign-in. The grant is the HTTP-only session handle that door gives only to the browser session that minted the challenge. The grant is not in the QR, not a website session, and not a field of the hike object or the verify record. A deployment of the admission service may spell refuse as deny on its wire. The word in this file is refuse.

A threshold here can change without changing section 13 and without changing the measurements in section 14. No order among B0–B8 is locked. Section 15.5 lists the numeric candidates. It does not lock them.

The Gate decision log is the companion specification Gate decision log, outside this file. Gate Bot owns that companion. This file does not write that log and does not write log JSON. Thorough outcomes are: thorough decides accept and the accept stands; revoke; refuse; or held. held is a thorough not read or a retry. It is not a negative settle. A negative thorough outcome, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. provisional then held then a negative thorough outcome is revoke. A later revoke cites a new policy version or a new measurement. The HTTP response outcomes are provisional, accept, refuse, revoke, and held. held is a response outcome. It is not a decision-log event.

15.1 Inputs

Normative. The admission service runs its own conforming section 14 verifier on the hike object it receives. It never takes a verify record supplied by the submitter. The record it produces is bound to the object hash: SHA-256 of the exact object bytes.

The inputs are:

  1. The hike object, its bytes.
  2. The object’s public_key.
  3. The object hash, SHA-256 of those bytes.
  4. The challenge defined in this section, issued by the admission service.
  5. A BIP-340 signature over that challenge, checked against public_key. Produce and verify do not check a signature (section 2). The signature check is not a section 14 measurement.
  6. A policy profile (section 15.4). The profile may name a friendly list. The friendly list is not a field of the hike object and not a field of the verify record.

Challenge

The challenge is a hard cut from a signature over a display-order index header hash, and from the Option B layout (domain pcoc-admit-challenge-b-v1, a 16-byte id, little-endian milliseconds, and no object hash). The Sign-in door does not accept either of those. The cut is deliberate.

Domain. The domain is the 19 ASCII bytes pcoc-gate-signin-v1. There is no trailing NUL in the tag.

Origin. origin is a UTF-8 web origin in the WebAuthn spelling: lowercase scheme and host, a port only when it is not the default for the scheme, and no trailing slash. Length n is 1 through 255 bytes. The production Sign-in origin for this product is https://pcoc.app. The signature binds to that origin. Each integrating site uses its own origin. gate.pcoc.app is the event-log site. It is not the Sign-in origin.

Other fields. nonce is 32 bytes from the admission service’s CSPRNG at issue time. expiry_unix_s is a u64 count of Unix seconds, not milliseconds. The challenge is valid while now_unix_s < expiry_unix_s. object_sha256 is the 32-byte SHA-256 of the exact object bytes that are submitted. The preimage does not carry an IP address, a Cloudflare token, a session cookie, a user-agent, or a second copy of public_key.

The lifetime is service configuration. It is not part of the hash formula. A candidate lifetime is 120 seconds (expiry_unix_s = issue_unix_s + 120). That candidate is not locked.

Bytes. Integers are big-endian.

challenge_bytes =
    ASCII "pcoc-gate-signin-v1"
  ‖ 0x00
  ‖ uint8(n)
  ‖ origin_utf8[0 .. n)
  ‖ nonce[32]
  ‖ uint64_be(expiry_unix_s)
  ‖ object_sha256[32]

The length is 93 + n bytes. For https://pcoc.app, n = 16 and the length is 109.

BIP-340 message.

bip340_message_32 = SHA-256(challenge_bytes)

That 32-byte digest is the BIP-340 message. The signer must not hash it again. The signature is 64 bytes under the object’s x-only public_key. Verify is bip340_verify(public_key, bip340_message_32, signature). A BIP-340 failure is an error named bad_signature. It is not a section 15 decision and it is not a fast final refuse. There is no decision line. An append-only hash-chained log entry may record it. v1 has no signed head and no signed receipt. Gate Bot owns that log. This file does not write the entry.

submission_sha256 = SHA-256(
    object_sha256 ‖ public_key ‖ bip340_message_32 ‖ signature)

That preimage is 32 ‖ 32 ‖ 32 ‖ 64 = 160 bytes. The Gate decision log names how a submission is logged. Gate Bot owns that companion. This file does not write its JSON. A negative thorough outcome in that log, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. A later revoke cites a new policy version or a new measurement.

Issuing. Challenge issue is a sub-step of the Sign-in door, behind the same Cloudflare protection. The Sign-in door is those two routes. It is not the grant, and it is not a website session.

  1. POST /v1/sign-in/challenge mints the challenge. The caller sends object_sha256. origin comes from the admission service’s configured origin list. It is never taken from the caller.
  2. POST /v1/sign-in submits the object and the BIP-340 signature over bip340_message_32. Whoever posts that request receives only the HTTP response outcome: provisional, accept, refuse, revoke, or held. held is a response outcome. It is not a decision-log event.

The site Sign-in function fetches the challenge and embeds it in the QR or the page. The grant goes only to the browser session that minted the challenge. That handle is not in the QR. The signing app shows origin before the user signs. /v1/challenge and /v1/admit stay 404. verify_cap_per_door_session counts verifies at this door. It is not a cap on a website session. provisional_action_cap is fixed at 0. No irreversible action is allowed while provisional, in every profile. That value is fixed in this file. It is not a profile knob. A provisional grant is short, read-only access until thorough settles or async_catch_budget is hit. The friendly-list entry is added only at a thorough accept.

Single-use. The service stores a row keyed by nonce: origin, expiry_unix_s, object_sha256, and used. Where that row lives for more than one process is outside this section.

Errors. These names are not section 15 decisions and they are not a log record:

The service looks up submission_sha256 before the nonce check. A resubmit of the same submission_sha256 returns the original outcome and does not mint a grant. A used nonce on a new submission, a different submission_sha256, is reused. On the first cryptographic use of the signature, set used, including when the later policy decision is refuse. A refuse burns the nonce. The set is an atomic compare-and-set: the row moves from not used to used for at most one submission. Two racing POST /v1/sign-in requests must not both pass a separate read of used and then both set it. The storage backend is outside this section. The atomicity is not. The nonce compare-and-set is for single-use nonces only. It does not admit the provisional grant.

A candidate grace after expiry is 1 hour, so a late replay is expired or reused rather than unknown_challenge. That candidate is not locked. A restart that does not keep the row forgets an outstanding challenge. The client asks again.

Informative. EXAMPLE, not production. Origin https://pcoc.app, nonce 0123456789abcdef repeated to 32 bytes, expiry_unix_s 1893456000, object_sha256 32 bytes of 0xaa. challenge_bytes hex:

70636f632d676174652d7369676e696e2d7631001068747470733a2f2f70636f632e6170700123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef0000000070dbd880aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa

bip340_message_32 hex: 69f340e811a370b22200bc7be89e8fd844548156bddcae55f449cb599470733a. An earlier EXAMPLE that used origin https://gate.pcoc.app is not this vector.

Friendly list

A friendly-list entry is added only at a thorough accept. A friendly list maps a public_key to an accepted object_sha256 and a policy version. A listed key may sign in again only for that same already-accepted object hash. While a later-policy re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Only a re-verify that refuses, other than a B7 cap refuse, ends that sign-in as a revoke, and it removes the friendly entry only when this object is the key's current friendly entry. That sign-in does not run section 14 again. A new object hash is a new section 15.1 submission. When the policy version changes, the Gate re-verifies the accepted object. The Gate keeps those object bytes. The decision-log companion publishes every decided object. If those bytes are missing when a re-verify is needed, that is not not read. The next sign-in requires a full section 15.1 submission of the object. It is not a signature-only return treated as not read. One entry per key, and that entry is one accepted object. Custody candidate for Christian's lock: only a thorough accept of a newer object replaces the entry. Newer is the decision-log seq of that thorough accept. A higher seq is newer. It is not the submission time and it is not a hike Bitcoin time. A refuse or a revoke of any other object does not change the entry. A re-verify of an older object does not displace the current entry. A re-verify accept keeps the entry only when this object is already the key's current friendly entry. If a newer accept B later loses its accept, A is not restored. The replaced pair's logged decision stays unchanged. A logged manual operator strike of the key is the other removal. A revoke in the object's own admission cycle, from provisional or from held, when thorough falls short of policy, ends that pair's grant and any signature-only sign-in for the object. Signature-only sign-in requires state accepted. Who edits the list by hand is a deployment concern. The list has no time expiry.

Normative. Locked, Christian 2026-10-07 00:15. Re-verify is Christian's re-check (2026-10-07 00:15). An accept stands. A policy-version change means the Gate re-verifies. If that re-verify refuses, other than a B7 cap refuse, it is a revoke. A B7 cap refuse is never a re-verify refuse, never a revoke, and never removes a friendly entry. The key's friendly entry is removed only when this object is the key's current friendly entry, as with any revoke. The removal is a compare-and-remove on object_sha256: the Gate removes the entry only if the current entry's object hash still matches this pair. A not read never removes the entry and never changes sign-in state. Before either reverify_max_attempts or max_held_blocks is hit, the Gate retries within the existing reverify budget. After either budget is hit on a later-policy re-verify not read, the hit is not a revoke, does not remove the friendly entry, and does not change sign-in state. After the hit, the Gate keeps retrying on each later {index-url} tip advance: at most one retry per strict increase in that tip height. A jump of several blocks is one retry. An equal or lower tip, including a reorg, is not a retry. These post-budget retries do not restart the spent budget and do not re-arm held_bound. The number of such retries is unbounded.

Binding

The fast decision and a later revoke bind the same submission: the object bytes, the object hash, the challenge, and the signature. This file does not mint a website session and does not add a session field to the hike object or the verify record. The grant defined above is the only session handle this section names. A provisional grant is short, read-only access until thorough settles or async_catch_budget is hit. The friendly-list entry is added only at a thorough accept.

15.2 Outputs and states

The HTTP response outcomes are provisional, accept, refuse, revoke, and held. States are received, provisional, accepted, refused, revoked, and held. accept is the thorough outcome whose state is accepted. The fast decision token is provisional. A fast-phase accept is invalid. The fast phase never issues accept. It may issue a final refuse when a fast rule fails on the object's own bytes: decode (the object bytes fail to decode as a hike object), waypoint rehash, shape mismatch, including shape_item height_range, seed-value mismatch, a plant or header mismatch confirmed by a fresh read from {index-url} that bypasses the local cache, an object-settled refuse-at-once, or a cap on a signed submission. That refuse does not start a thorough run and does not issue a grant. Otherwise the fast phase gives provisional. A fast-phase not read is held. Only the thorough phase issues accept. refuse is the response whose state is refused. revoke is the response whose state is revoked. held is both a response outcome and a state. held is not a decision-log event. Thorough outcomes are: thorough decides accept and the accept stands; revoke; refuse; or held. held is a thorough not read or a retry. It is not a negative settle. A negative thorough outcome, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. provisional then held then a negative thorough outcome is revoke. A later revoke cites a new policy version or a new measurement. Thorough decides accept; the accept stands. Sections 8–14 do not use these words as outcomes. This section does not say that a hike object passes or fails. This file does not write the decision-log JSON. Gate Bot owns that companion.

A client cannot trigger revoke. revoke_scope is the pair (public_key, object_sha256). A revoke binds to (public_key, object_sha256). It does not end another object's grant. A revoke in that object's own admission cycle ends that pair's grant and any signature-only sign-in for the object. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A not read does not. The Gate invalidates the pair's grant before it returns or logs a revoke. Every grant use or signature-only sign-in checks the pair's current state. How relying parties hear of a revoke stays a deployment concern.

An admission cycle runs from received for a (public_key, object_sha256) under a policy_version until a thorough accept, or a refuse or revoke of the pair (including reason held_bound). A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. A later policy-version re-verify is not a new admission cycle and never issues provisional.

The service keeps an atomic per-cycle record keyed on (public_key, object_sha256, policy_version). That record is not the nonce row. The nonce compare-and-set stays for single-use nonces only. The record admits at most one provisional grant in the cycle. Concurrent submissions of the same pair cannot each receive one. A new submission while the cycle is open returns the current state, with no grant and no new run. A new submission while the cycle is closed refused or revoked under this policy_version returns that outcome and does not open a new cycle. While the pair is accepted and this object is the key's current friendly entry, friendly-list and signature-only rules apply. The replaced-object case is the custody-candidate row that follows. A client submission of a pair that is accepted, under a new policy_version, is a re-verify under the locked re-verify rule. Custody candidate: a pair that is refused or revoked under an old policy_version, submitted under a new policy_version, starts a new admission cycle under the new version and may issue provisional under the same rules as any fresh cycle.

Row order. The new-submission rows are checked before the cap rows. A cap row applies to the submission the per-cycle record has admitted. It does not apply to a new submission of a pair whose cycle is already open. A cap hit stays in received. The submission response is refuse. It does not end the cycle.

From When To
received A new submission of this (public_key, object_sha256) under this policy_version while the admission cycle is already open in received. The atomic per-cycle record is keyed on (public_key, object_sha256, policy_version). It is not the nonce row. Return the current state. No grant. No new run received
provisional A new submission of this pair under this policy_version while the admission cycle is open. Return the current state. No grant. No new run provisional
held A new submission of this pair under this policy_version while the admission cycle is open. Return the current state. No grant. No new run held
refused A new submission of this pair under this policy_version. The cycle is closed refused. Return that outcome. No new cycle refused
revoked A new submission of this pair under this policy_version. The cycle is closed revoked. Return that outcome. No new cycle revoked
refused Custody candidate. This pair is refused under an old policy_version. A client submits it under a new policy_version. The submission starts a new admission cycle under the new version. That cycle may issue provisional under the same rules as any fresh cycle. The pair may be accepted received
revoked Custody candidate. This pair is revoked under an old policy_version. A client submits it under a new policy_version. The submission starts a new admission cycle under the new version. That cycle may issue provisional under the same rules as any fresh cycle. The pair may be accepted received
accepted Custody candidate. A new submission of this pair under this policy_version, and a newer accept has replaced this object as the key's friendly entry. Return the logged accepted decision. State that this object is not the key's friendly entry. How that statement is carried is a decision-log companion concern. This file does not name a field for it. No grant. No new run. No signature-only sign-in through this object. This submission does not displace the current friendly entry accepted
accepted A new submission of this same pair under this policy_version, and this object is the key's current friendly entry. Friendly-list and signature-only rules apply. This is not a new admission cycle and it never issues provisional accepted
accepted A client submission of a pair that is accepted under any policy_version. It never issues provisional. Under a new policy_version it is a re-verify of that pair: straight to thorough, subject to verify_cap_per_object on (public_key, object_sha256, policy_version). The result is logged under the new version. A refuse is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256. A not read keeps the entry and sign-in state. Signature-only sign-in continues. After either reverify budget is hit on a not read, the Gate keeps retrying on each later {index-url} tip advance: at most one retry per strict increase in that tip height. A jump of several blocks is one retry. An equal or lower tip, including a reorg, is not a retry. These post-budget retries do not restart the spent budget and do not re-arm held_bound. The number of such retries is unbounded. accepted
received provisional is on, no grant was issued in that admission cycle, the fast measurements and the signature meet the profile, and every fast measurement the profile needs for provisional is present as an integer or a token other than not read or not defined. At most one provisional grant is issued in that cycle provisional
received The profile sets a figure, and on a valid signature and a fresh challenge verify_cap_per_object is hit. A verify_cap_per_object refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. The pair's state does not move to refused. The submission response is refuse. Service-started re-verifies and held retries are not in the count. An unauthenticated rate limit is not this row. A transport rate limit is not this row received
received The profile sets a figure, and on a valid signature and a fresh challenge verify_cap_per_door_session is hit. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. The pair's state does not move to refused. The submission response is refuse. Candidate figures are 3, 3, and 5 (strict, default, lenient). Not locked. Held retries do not count. An unauthenticated rate limit is not this row. A transport rate limit is not this row received
received A plant or header mismatch is read from the local cache and is not yet confirmed by a fresh read from {index-url} that bypasses the local cache held
received That same plant or header mismatch is confirmed by a fresh read from {index-url} that bypasses the local cache. Invariant fast final refuse. No thorough run. No grant. phase fast refused
received The object bytes fail to decode as a hike object. Invariant fast final refuse. No thorough run. No grant refused
received A fast not read other than a six-leg read, including an unreadable header or a height or plant the mirror does not hold, when the profile needs that measurement for provisional held
received Settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored, or N is not defined from the object's own bytes, or fewer than six legs. If a re-read could bring that waypoint count to two or more, the measurement is not read and the state is held, and this row does not apply. A short-byte six-leg draw is not this row. S not read is not this row refused
received S is not read. The state enters held and the read is retried held
received Shape mismatch, including shape_item height_range, seed-value mismatch, or waypoint rehash mismatch is measured from the object. Waypoint rehash is the recomputed waypoint compared with the stored waypoint. Invariant fast final refuse. No thorough run. No grant. Not gated on a lock refused
provisional A thorough not read, including a short or missing six-leg read, a short-byte six-leg draw, or short path bytes for a six-leg run. When the six-leg draw is not read, six_leg_runs is not read. The service re-verifies held
received provisional: off. A thorough not read, including a short or missing six-leg read, a short-byte six-leg draw, or short path bytes for a six-leg run. When the six-leg draw is not read, six_leg_runs is not read. The service re-verifies held
received provisional is on, and the fast phase has not left received within fast_accept_bound. No grant. The state enters held. This timeout hold does not return to provisional held
received provisional: off. Thorough decides accept; the accept stands accepted
received provisional: off. Thorough negative. No grant was issued refused
provisional async_catch_budget is hit before thorough settles. The move from provisional to held ends the grant. It does not resume. No grant remains. The state enters held. This hold does not return to provisional. A later thorough refuse is revoke, because a grant was issued in the cycle held
provisional Thorough decides accept; the accept stands accepted
provisional Thorough negative. A grant was issued (provisional or accept, including one already ended by the move from provisional to held). phase thorough. The revoke cites fast_seq revoked
held provisional is on, no grant was issued in that admission cycle, and this hold was not entered from fast_accept_bound or from async_catch_budget. The fast inputs are read on retry provisional
held This hold was entered from fast_accept_bound or from async_catch_budget. Thorough decides accept; the accept stands. This hold does not return to provisional accepted
held This hold was entered from fast_accept_bound or from async_catch_budget. Thorough negative, and no grant was issued in that cycle. phase thorough. fast_seq is omitted. This hold does not return to provisional refused
held This hold was entered from fast_accept_bound or from async_catch_budget. Thorough negative, and a grant was issued in the cycle. A later thorough refuse is revoke. phase thorough. The revoke cites fast_seq. This hold does not return to provisional revoked
held This hold was entered from fast_accept_bound or from async_catch_budget. reverify_max_attempts or max_held_blocks is hit, whichever comes first. Same budgets, no reset. When both are hit on the same check, the detail is reverify_max_attempts. Reason held_bound. No grant was issued in that cycle. phase is the phase running when the budget ran out. This hold does not return to provisional refused
held This hold was entered from fast_accept_bound or from async_catch_budget. reverify_max_attempts or max_held_blocks is hit, whichever comes first. Same budgets, no reset. When both are hit on the same check, the detail is reverify_max_attempts. Reason held_bound. A grant was issued in that cycle. phase thorough. The revoke cites fast_seq. This hold does not return to provisional revoked
held Thorough decides accept; the accept stands accepted
held Thorough negative, and a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). phase thorough. The revoke cites fast_seq revoked
held Thorough negative, and no grant was issued refused
held A thorough not read, including a short or missing six-leg read, a short-byte six-leg draw, or short path bytes for a six-leg run. When the six-leg draw is not read, six_leg_runs is not read. Neither reverify_max_attempts nor max_held_blocks is hit held
held reverify_max_attempts or max_held_blocks is hit, whichever comes first, and a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). Reason held_bound. Detail names the budget. When both budgets are hit on the same check, the detail is reverify_max_attempts. phase thorough. The revoke cites fast_seq revoked
held No grant was issued, and the budget ran out while the fast phase was still running. Reason held_bound. Detail names the budget. When both budgets are hit on the same check, the detail is reverify_max_attempts. phase fast. No thorough line. The line carries the fast record. fast_seq is omitted refused
held No grant was issued, and the budget ran out while the thorough phase was running. That includes provisional: off, and a fast-entered hold that later ran thorough when the budget hit. Reason held_bound. Detail names the budget. When both budgets are hit on the same check, the detail is reverify_max_attempts. phase thorough. fast_seq is omitted. It is never encoded as JSON null. The line carries the thorough record refused
held A cache-sourced plant or header mismatch is confirmed by a fresh read from {index-url} that bypasses the local cache, and no grant was ever issued. Invariant fast final refuse. phase fast. No thorough run. No grant refused
held A cache-sourced plant or header mismatch is confirmed by a fresh read from {index-url} that bypasses the local cache, and a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held). Invariant. phase thorough. The revoke cites fast_seq. The reason is the confirmed plant or header mismatch revoked
revoked Any later check of that same submission revoked
accepted The policy version changes. Re-verify the stored object under the new policy version. While that re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Thorough decides accept; the accept stands. The friendly entry is kept only when this object is already the key's current friendly entry. If a newer accept B later loses its accept, A is not restored. A re-verify of an older object does not displace a newer entry. This re-verify is not a new admission cycle and never issues provisional accepted
accepted That re-verify refuses under the new policy version. The refuse is a measured mismatch or any B-rule outcome other than a B7 cap refuse. A not read is not this row. The outcome is revoke. phase thorough. The revoke cites the new policy version and fast_seq. The Gate invalidates the pair's grant before it returns or logs the revoke. When this object is the key's current friendly entry, the revoke removes that entry by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A re-verify of an older object does not displace a newer entry. If a newer accept B later loses its accept, A is not restored revoked
accepted That re-verify is not read, and neither reverify_max_attempts nor max_held_blocks counted from the first not read on that re-verify is hit. The earlier accept stands. No suspend. No held_bound. Friendly returns continue. The re-verify is retried later accepted
accepted That re-verify is still not read, and reverify_max_attempts or max_held_blocks counted from the first not read on that re-verify is hit, whichever comes first. The hit is not a revoke, does not remove the friendly entry, and does not change sign-in state. Signature-only sign-in continues on the earlier accept. After the hit, the Gate keeps retrying on each later {index-url} tip advance: at most one retry per strict increase in that tip height. A jump of several blocks is one retry. An equal or lower tip, including a reorg, is not a retry. These post-budget retries do not restart the spent budget and do not re-arm held_bound. The number of such retries is unbounded accepted
accepted The state is accepted. A listed key signs in again for the same already-accepted object hash. While a later-policy re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Only a re-verify that refuses, other than a B7 cap refuse, ends that sign-in as a revoke, and it removes the friendly entry only when this object is the key's current friendly entry. Section 14 does not run again. Signature-only sign-in requires state accepted accepted
refused Any later check of that same submission refused

Normative. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored. If a re-read could bring that count to two or more, the measurement is not read and the state is held. N not defined from the object's own bytes. Fewer than six legs is not defined from the object's own bytes and is that same refuse-at-once. S not read stays held. A fast not read, including an unreadable header, is held. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are thorough-only. They are not read. When the six-leg draw is not read, six_leg_runs is not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. The service re-verifies. A short-byte six-leg draw stays re-readable.

A move from provisional to held ends the grant. It does not resume. No action is allowed while held. held leaves to provisional only when provisional is on, no grant was issued in that admission cycle, the hold was not entered from fast_accept_bound or from async_catch_budget, and the fast inputs are read on retry. A hold entered from fast_accept_bound or from async_catch_budget does not return to provisional. It leaves only to a thorough accept, to refuse, to revoke, or to refuse/revoke with reason held_bound when reverify_max_attempts or max_held_blocks runs out. Those are the same budgets, with no reset. At most one provisional grant is issued in that admission cycle. held leaves to accepted when thorough decides accept, to revoked on a negative thorough outcome when a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), or to refused on a negative thorough outcome when no grant was issued. provisional then held then a negative thorough outcome is revoke. While neither reverify_max_attempts nor max_held_blocks is hit, a thorough not read is retried and the state is held. Exhausting either budget is refuse/revoke with reason held_bound: revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. It does not stay held. That held is not a negative settle. held for a missing measurement is only for not read. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored. If a re-read could bring that count to two or more, the measurement is not read and the state is held. N not defined from the object's own bytes. Fewer than six legs is not defined from the object's own bytes and is that same refuse-at-once. S not read stays held. That not defined does not go to held. A fast not read, including an unreadable header, is held. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are thorough-only. They are not read. When the six-leg draw is not read, six_leg_runs is not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. A short-byte six-leg draw stays re-readable. A Rule A4.3 checkpoint digest that has not been recomputed does not put an honest object in held.

A height or plant the mirror does not hold is not read, and the state is held. A plant or header mismatch read from that mirror is held until a fresh read from {index-url} that bypasses the local cache confirms the same comparison. {index-url} is the upstream. The local mirror is a cache of {index-url} responses. An operator's own Bitcoin-built index counts as its {index-url}. Anyone may still check that index against Bitcoin. Only a mismatch confirmed by that fresh read can refuse.

MVP only, not law. A refuse or a revoke binds to (public_key, object_sha256). Under that policy version the object stays refused or revoked. The wording is refused under policy version X. There is no ban and no flag. The same submission_sha256 does not become accepted under that policy version. A resubmit of the same submission_sha256 returns the original outcome. v1 does not issue a signed receipt. It does not mint a grant. A new submission needs a new challenge. The same key may submit a new object hash at any time. The same object submitted again under a new policy version is verified again and may be accepted. held is not a sticky refuse and it is not accept. “Not sticky” means a new object hash is allowed. It does not mean this object can leave refused under the same policy version. The decision-log companion publishes the refuse. This file does not write that log.

Signature-only sign-in requires state accepted. A listed key may sign in again only for that same already-accepted object hash. While a later-policy re-verify is not read, including after either reverify budget is hit, signature-only sign-in continues on the earlier accept. Only a re-verify that refuses, other than a B7 cap refuse, ends that sign-in as a revoke, and it removes the friendly entry only when this object is the key's current friendly entry. That sign-in does not run section 14 again. A new object hash is a new section 15.1 submission. When the policy version changes, a re-verify that refuses under the new policy version revokes an accepted object. That refuse is a measured mismatch or any B-rule outcome other than a B7 cap refuse. A not read is not a refuse and does not revoke. That revoke cites the new policy version. While that re-verify is not read and neither reverify_max_attempts nor max_held_blocks counted from the first not read on that re-verify is hit, the earlier accept stands. It does not suspend the accept, it does not apply held_bound, and friendly returns continue. The re-verify is retried later. Both reverify_max_attempts and max_held_blocks for that re-verify are counted from the first not read on that re-verify. max_held_blocks starts at the Gate’s {index-url} tip height at that first not read. It is hit when a retry finds that tip ≥ start + max_held_blocks and the re-verify is still not read. reverify_max_attempts is hit when that many retries of this re-verify, counted from that first not read, are still not read. Before either budget is hit, the Gate retries within the existing reverify budget. After either budget is hit, the hit is not a revoke, does not remove the friendly entry, and does not change sign-in state. Signature-only sign-in continues on the earlier accept. After the hit, the Gate keeps retrying on each later {index-url} tip advance: at most one retry per strict increase in that tip height. A jump of several blocks is one retry. An equal or lower tip, including a reorg, is not a retry. These post-budget retries do not restart the spent budget and do not re-arm held_bound. The number of such retries is unbounded. A re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry. When that re-verify decides accept, the entry is kept only when this object is already the key's current friendly entry. If a newer accept B later loses its accept, A is not restored. A re-verify of an older object does not displace a newer entry. Only a thorough accept of a newer object replaces the entry. Newer is the decision-log seq of that thorough accept. A higher seq is newer. The replaced pair's logged decision stays unchanged. Gate Bot owns the companion log. This file does not write that log. An index outage does not revoke that accepted object. Thorough decides accept and the accept stands.

Rule B9. Invariant refuses

Normative. These outcomes are not Rule B0–B8 candidates. They do not wait on a locked threshold. A revoke in this list is phase thorough. A grant means the fast phase already finished. fast_seq is omitted when the line does not cite one. It is never encoded as JSON null.

15.3 Two-phase async

  1. Fast phase. The fast list is the section 14.1–14.5 measurements other than the Rule A4.3 checkpoint digest recompute: shape, the seed value, waypoint and fold recompute from stored checkpoint bytes, path and plant comparison, bookends, and the anchored count, plus the signature. Header and plant facts come from the service's local mirror of the portable index. {index-url} is the upstream. The local mirror is a cache of {index-url} responses and serves the section 9 body rules. The mirror holds only {index-url} responses. It is filled from the portable index only. A height or plant the mirror does not hold is not read, and the state is held. The fast phase does no other network read. A plant or header mismatch read from that mirror is held until a fresh read from {index-url} that bypasses the local cache confirms the same comparison. An operator's own Bitcoin-built index counts as its {index-url}. Anyone may still check that index against Bitcoin. Only a mismatch confirmed by that fresh read can refuse. The fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2 from stored checkpoints. The Rule A4.3 checkpoint digest is thorough-only in every profile. The fast phase never recomputes a leg. Leg recompute stays thorough-only. The fast phase does not turn a partial view into the finished verify record. Branch on phase. There is no new token for the phase. The fast record omits checkpoints[*].legs[*].result, six_leg, and six_leg_runs. The Rule A4.3 checkpoint digest is thorough-only in every profile. Every other section 14 key stays, including checkpoints[*].g, checkpoints[*].legs[*].j, waypoints[*].result, and seed_value. The keys it does carry are shape, headers, the seed value, waypoint and fold comparisons from stored checkpoints, paths and plants, bookends, the anchored count, and profile. The full section 14 record, after thorough, has every key present, and six_leg_runs holds the thorough draws. Both records are encoded with RFC 8785. The fast record has no placeholders. It does not fill an omitted key with not defined. It is not the finished record. The fast decision token is provisional. A fast-phase accept is invalid. The fast phase never issues accept. It may issue a final refuse when a fast rule fails on the object's own bytes: decode (the object bytes fail to decode as a hike object), waypoint rehash, shape mismatch, including shape_item height_range, seed-value mismatch, a plant or header mismatch confirmed by a fresh read from {index-url} that bypasses the local cache, an object-settled refuse-at-once, or a cap on a signed submission. That refuse does not start a thorough run and does not issue a grant. Otherwise the fast phase gives provisional. A fast-phase not read is held. When profile mode provisional is on, it returns within fast_accept_bound. That bound is a profile parameter. No duration is locked. When provisional is off, the fast phase does not grant provisional. provisional: off is that mode. It is not a value of fast_accept_bound.
  2. Held. held for a missing measurement is only for not read. When a fast measurement the active profile needs for provisional is not read, the fast phase does not issue provisional and does not record a sticky refuse. The state is held. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored. If a re-read could bring that count to two or more, the measurement is not read and the state is held. N not defined from the object's own bytes. Fewer than six legs is not defined from the object's own bytes and is that same refuse-at-once. S not read stays held. That not defined does not go to held. A fast not read, including an unreadable header, is held. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are thorough-only. They are not read. When the six-leg draw is not read, six_leg_runs is not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. A short-byte six-leg draw stays re-readable. A missing Rule A4.3 checkpoint digest is not that not defined case: the digest is not yet run. A height or plant the mirror does not hold is held, as section 15.2 states. A cache-sourced plant or header mismatch is held until a fresh read from {index-url} that bypasses the local cache confirms it. {index-url} is the upstream. The local mirror is a cache of {index-url} responses. An operator's own Bitcoin-built index counts as its {index-url}. Anyone may still check that index against Bitcoin. The fast phase must not issue provisional on a floor that is not read or not defined. A fast-phase not read is held. A plant mismatch confirmed by a fresh read from {index-url} that bypasses the local cache is a fast final refuse: no thorough run and no grant.
  3. Thorough phase. Rule A4.3 checkpoint digest recompute is thorough-only. Leg recompute (section 14.6 and any repeated six-leg runs the profile names) is thorough-only. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are that thorough not read. When the six-leg draw is not read, six_leg_runs is not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. Only the thorough phase issues accept. The fast phase never issues accept. The thorough phase issues refuse or revoke by the grant-ever-issued rule. A fast final refuse is the case named in the fast phase. Thorough outcomes are: thorough decides accept and the accept stands (accepted); revoke; refuse; or held. held is a thorough not read or a retry. It is not a negative settle. A negative thorough outcome, or a failed policy-version re-verify, is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. provisional then held then a negative thorough outcome is revoke. A later revoke cites a new policy version or a new measurement. held is an HTTP response outcome. It is not a decision-log event. If the object was provisional, a move to held ends the grant. It does not resume. A grant was issued (provisional or accept, including one already ended by the move from provisional to held). A later negative thorough outcome is revoke. It is refuse only when no grant was issued.
  4. Provisional. The fast-phase outcome is provisional, a reversible grant, until thorough settles. It is never accept. provisional_action_cap is fixed at 0. No irreversible action is allowed while provisional, in every profile. That value is fixed in this file. It is not a profile knob. A provisional grant is short, read-only access until thorough settles or async_catch_budget is hit. The friendly-list entry is added only at a thorough accept. The window before thorough settles is the attacker surface of a provisional grant. A provisional grant is not a finished admission. The residual risk is QR relay phishing: a victim scans a QR that an attacker relayed. Informative. With provisional_action_cap fixed at 0, a forged object can still reach provisional for free: the fast phase has no leg, and the plant gap is record-only. That grant is short, read-only access until thorough settles or async_catch_budget is hit. verify_cap_per_door_session is the main limit.

Held bound. Normative. reverify_max_attempts and max_held_blocks are profile inputs. Candidates are 3 and 6. The numbers are not locked. max_held_blocks applies to every hold, whatever the cause: a fast not read, a thorough not read, fast_accept_bound, or async_catch_budget. It starts at the Gate’s {index-url} tip height at the first entry into held in the cycle. It does not reset. That tip height is the normative clock. An append-only hash-chained log entry may record that tip height as bitcoin_tip_height. That name is the log entry's name for the tip. The entry is unsigned. It is not a signed head. v1 has no signed head and no signed receipt. This file does not define a second clock. It is hit when a check finds that tip ≥ start + max_held_blocks, even when no not read has occurred. reverify_max_attempts counts not read retries only, fast or thorough. A hold with no not read, including fast_accept_bound and async_catch_budget, does not count toward it. It is hit when that many retries are still not read. Whichever budget is hit first, the outcome is refuse/revoke with reason held_bound. The detail names the budget that hit, reverify_max_attempts or max_held_blocks. This file names that detail. It does not write the decision-log JSON. Exhausting either budget is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. It does not stay held. phase is the phase running when the budget ran out, and the line carries that phase's record. No grant was issued and the fast phase was still running: the outcome is refuse, reason held_bound, the detail names the budget that hit, phase fast, no thorough line, and the line carries the fast record. No grant was issued and the thorough phase was running, including provisional: off and a fast-entered hold that later ran thorough: the outcome is refuse, reason held_bound, the detail names the budget that hit, phase thorough, fast_seq is omitted, and the line carries the thorough record. fast_seq is omitted. It is never encoded as JSON null. A grant was ever issued (provisional or accept, including one already ended by the move from provisional to held): the outcome is revoke, phase thorough, and that revoke cites fast_seq. Every revoke is phase thorough. When both reverify_max_attempts and max_held_blocks are hit on the same check, the detail is reverify_max_attempts. Gate Bot owns that companion. This file does not write the line. After a grant was issued (provisional or accept, including one already ended by the move from provisional to held), a held_bound outcome is revoke, not refuse. That revoke binds to (public_key, object_sha256). It does not end another object's grant. A revoke in that object's own admission cycle ends that pair's grant and any signature-only sign-in for the object. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A not read does not.

Which named checks are fast and which are thorough is the profile schema in section 15.4. The fast list does not include a leg.

The hit rule, the {index-url} tip clock, and the phase of a held_bound line are the Held bound paragraph above. An append-only hash-chained log entry may record that tip height as bitcoin_tip_height. That name is the log entry's name for the tip. The entry is unsigned. It is not a signed head. v1 has no signed head and no signed receipt. That held is not a negative settle. The fast phase itself does not refuse for a not read gap, or for a height or plant the mirror does not hold. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored. If a re-read could bring that count to two or more, the measurement is not read and the state is held. N not defined from the object's own bytes. Fewer than six legs is not defined from the object's own bytes and is that same refuse-at-once. S not read stays held. That not defined does not go to held. A short-byte six-leg draw stays re-readable. The state is held. The service re-verifies.

15.4 Named parameters and profile schema

A policy profile names parameters and lists its fast checks and its thorough checks. The fast list is shape, header match, the seed value, waypoint and fold recompute from stored checkpoints, path and plant comparison, bookends, the anchored count, and the signature. The thorough list is the Rule A4.3 checkpoint digest and section 14.6. The fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2 from stored checkpoints. The Rule A4.3 checkpoint digest is thorough-only in every profile. The fast list never recomputes a leg. A profile supplies values later. This section does not lock them, except the default profile leg_rate_floor of 1/2, which is locked. MVP only, not law. provisional_action_cap is fixed at 0. No irreversible action is allowed while provisional, in every profile. That value is not a profile knob. revoke_scope is the pair (public_key, object_sha256). fast_checkpoint_sample is moot: the fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2, and the Rule A4.3 checkpoint digest is thorough-only.

Name Rule What the name stands for
leg_rate_floor B2 Default profile floor locked at 1/2 leg per second. MVP only, not law. Compared in integers on the bookend pair (N, S). The strict-profile figure in section 15.5 stays a candidate.
span_sanity_bound B3 Candidate integer seconds. Earlier spelling S_SANITY_BOUND.
span_scale_seconds B3 Candidate integer c in S_eff = max(S, delta_h × c).
span_endpoint_mode B3 Candidate raw or neighbour.
fixed_start B6 Candidate switch. When on, the admission service reads fixed_start.N, fixed_start.S, and fixed_start.delta_h. It does not derive them.
min_anchored_paths B4 Candidate integer m in anchored_total >= m. Candidates are 8, 7, and 7 (strict, default, lenient). 8 is 50% of 16. 7 is 43.75% of 16. Both sit inside 40–50%. Not locked.
min_last_bookend B4 Candidate integer floor on bookend index b. Candidates are 14, 14, and 13. Not locked.
plant_gap_ceiling B5 Record-only candidate. Not a fast final refuse. A locked threshold on this ceiling is not the plant-mismatch fast refuse.
plant_gap_floor B5 Candidate integer. The comparison is d_g >= plant_gap_floor. The candidate value is in section 15.5.
thorough_six_leg_runs B7 Candidate number of Rule A4.4 runs on the thorough path, per profile. The leg count is six_leg_runs = 6 times that number. This file does not lock one count for every profile. Each run is one element of the six_leg_runs array, recorded in order. Concurrency is Rule A4.4.4.
verify_cap_per_object B7 Candidate compute limit of 1 per (public_key, object_sha256, policy_version). Not locked. Not a ban. A hit's refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. Service-started re-verifies and held retries are not in the count. The same object under a new policy version may be verified again. When the profile sets this figure, a hit on a valid signature and a fresh challenge is a fast final refuse of that attempt.
verify_cap_per_door_session B7 Candidate 3, 3, and 5 (strict, default, lenient). Not locked. Counts client submissions in the session. Held retries do not count. Bounds the fast verifies and the thorough runs those submissions trigger. Not a cap on a website session. A transport rate limit stays outside this policy. A hit stays in received. The submission response is refuse. A cap refuse does not end the cycle.
provisional_action_cap 15.3 Fixed at 0. No irreversible action while provisional, in every profile. Not a profile knob.
revoke_scope 15.2 Fixed (public_key, object_sha256). A revoke binds to that pair. It does not end another object's grant. A revoke in that object's own admission cycle ends that pair's grant and any signature-only sign-in for the object. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A not read does not.
async_catch_budget 15.3 Thorough-phase budget. No number is locked.
provisional 15.3 Profile mode on or off. off means no provisional state. It is not a value of fast_accept_bound.
fast_accept_bound 15.3 Fast-phase budget when provisional is on. No number is locked. Unused when provisional is off.
reverify_max_attempts 15.3 Profile input. Counts not read retries only, fast or thorough. A hold with no not read, including fast_accept_bound and async_catch_budget, does not count toward it. It is hit when that many retries are still not read. Whichever of this budget and max_held_blocks is hit first is refuse/revoke with reason held_bound. The detail names the budget that hit. When both budgets are hit on the same check, the detail is reverify_max_attempts. Exhausting it is revoke, phase thorough, if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. After a grant was issued the outcome is revoke, not refuse. Candidate 3. No number is locked.
max_held_blocks 15.3 Profile input. Applies to every hold, whatever the cause: a fast not read, a thorough not read, fast_accept_bound, or async_catch_budget. Starts at the Gate’s {index-url} tip height at the first entry into held in the cycle. It does not reset. An append-only hash-chained log entry may record that tip as bitcoin_tip_height. That name is the log entry's name for the tip. The entry is unsigned. It is not a signed head. v1 has no signed head and no signed receipt. Hit when a check finds that tip ≥ start + max_held_blocks, even when no not read has occurred. Whichever of this budget and reverify_max_attempts is hit first is refuse/revoke with reason held_bound. The detail names the budget that hit. Exhausting it is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. Candidate 6. No number is locked.
no_pair_outcome B0 Binds to (public_key, object_sha256). When (N, S) is not a numeric pair because a time is not read, the state enters held from received, from provisional, or from held, and the read is retried. When N is not defined from the object's own bytes, or fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored, the outcome is refuse at once. That result does not go to held. If a re-read could bring that count to two or more, the measurement is not read and the outcome is held. S not read stays held. A short or missing network read is not read, the outcome is held, and the service re-verifies.

verify_cap_per_object is a candidate of 1, keyed on (public_key, object_sha256, policy_version). It is a compute limit, not a ban. A hit's refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. Service-started re-verifies and held retries are not in the count. A new policy version is a new count. The figure is not locked.

Rule B0. Which rate the admission service reads

Candidate, not locked. The admission service reads the bookend pair (N, S) from Rule A4.9. That pair is the candidate admission rate. The whole-hike value stays analysis (Rule A4.10). When S is not read, there is no numeric pair. The state is held and the read is retried (no_pair_outcome). S not read stays held. When N is not defined from the object's own bytes, or fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored, a re-read cannot change that result. Policy refuses it at once. It does not go to held. If a re-read could bring that count to two or more, the measurement is not read and the state is held. A short or missing network read is not read. The state is held. The service re-verifies.

Rule B1. Integrity

Candidate, not locked. The bullets below are the remaining candidates: thresholds, floors, and the policy counts. Those refuses are Rule B9. They are not these bullets. not read is a technical outcome of an index document that is missing or unreadable, or of a header T that is not taken. It is not an automatic refuse. On the fast path it is held when the profile needs the measurement for provisional. A height or plant the mirror does not hold is that not read. On the thorough path a not read is held and is retried. When reverify_max_attempts or max_held_blocks is hit, whichever comes first, the outcome is revoke, phase thorough, if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. The reason token is held_bound. The detail names the budget that hit. When both budgets are hit on the same check, the detail is reverify_max_attempts. Policy refuses at once only when the waypoint count is settled by bytes already in hand: fewer than two waypoints among W_1 … W_16 would be anchored even if every not read plant or path read among them came back anchored. If a re-read could bring that count to two or more, the measurement is not read and the state is held. N not defined from the object's own bytes. Fewer than six legs is not defined from the object's own bytes and is that same refuse-at-once. S not read stays held. That not defined does not go to held. A fast not read, including an unreadable header, is held. A short or missing six-leg read, a short-byte six-leg draw, and short path bytes for a six-leg run are thorough-only. They are not read. When the six-leg draw is not read, six_leg_runs is not read. The state is held from received when provisional is off, from provisional, or from held. They are not a fast received to held row. A short-byte six-leg draw stays re-readable. A cache-sourced plant or header mismatch is held until a fresh read from {index-url} that bypasses the local cache confirms it. {index-url} is the upstream. The local mirror is a cache of {index-url} responses. An operator's own Bitcoin-built index counts as its {index-url}. Anyone may still check that index against Bitcoin. A plant or header mismatch confirmed by that fresh read is an invariant in section 15.2. No grant was ever issued: the outcome is refuse, phase fast. A grant was ever issued (provisional or accept, including one already ended by the move from provisional to held): the outcome is revoke, phase thorough, and the revoke cites fast_seq. not defined is not not read.

Rule B2. Leg-rate floor

The default profile value is locked. MVP only, not law. leg_rate_floor for the default profile is 1/2 leg per second. Other profile figures for this rule stay candidates. The comparison is integer-only on the bookend pair: a ratio p/q meets the floor when q·N ≥ p·S_eff. The profile supplies p and q. When Rule B3 is not applied, S_eff = S. The comparison does not round. There is no comparison when (N, S) is not a numeric pair. The fast phase does not issue provisional on that gap. An object-settled pair is a fast final refuse: no thorough run and no grant. S not read is held. A pair that is not defined because the object's own bytes settle it is refuse at once. A short or missing network read stays not read and the state is held.

Rule B3. Span sanity

Candidate, not locked. One candidate is S_eff = max(S, delta_h × span_scale_seconds). span_endpoint_mode raw uses T_P_b and T_I_a_minus_1. neighbour uses max(T_P_b, headers[b].T) − min(T_I_a_minus_1, headers[a+1].T). Another candidate: if S < span_sanity_bound, the floor is not met. No value is locked. A delta_h of not defined, and an S of not read, are not numbers for this rule.

Rule B4. Paths minimum

Candidate, not locked. One candidate is anchored_total >= min_anchored_paths. The candidates are 8, 7, and 7 (strict, default, lenient). 8 is 50% of 16. 7 is 43.75% of 16. Both sit inside 40–50%. The target is about 1% honest refusal. Another candidate is b >= min_last_bookend. Those candidates are 14, 14, and 13. Not locked. The anchored count is among W_1 … W_16. W_17 is not in the 16.

Rule B5. Plant gap

Candidate, not locked. plant_gap_ceiling stays record-only. d_g is recorded. This rule does not refuse in the fast phase. A plant mismatch from a fresh read from {index-url} that bypasses the local cache is a fast final refuse. This plant-gap ceiling is not that refuse. The fast phase does not recompute a leg. The named lower bound is plant_gap_floor: d_g >= plant_gap_floor. The candidate value of plant_gap_floor is in section 15.5. No value is locked.

Rule B6. Fixed start

Candidate, not locked. The named parameter is fixed_start. The window is Rule B6. It is not a section 15.4 number. When bookends are defined, section 14 records fixed_start.N, fixed_start.S, and fixed_start.delta_h. When the switch is on, the admission service reads those three. It does not derive them. Part A still records the actual a, b, N, and S. This rule does not replace those measurements. When a fixed-start value the profile needs is not read, the fast phase is held if it needs that value for provisional. When that value is not defined and the object's own bytes settle it, policy refuses it at once. It does not go to held. A short or missing network read stays not read.

Rule B7. Resubmits

Candidate, not locked. verify_cap_per_object is a candidate of 1 per (public_key, object_sha256, policy_version). Not locked. It is a compute limit, not a ban. A hit's refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. Service-started re-verifies and held retries are not in the count. The same object under a new policy version may be verified again. Candidate verify_cap_per_door_session is 3, 3, and 5 (strict, default, lenient). Not locked. It counts client submissions in the session, so it bounds the fast verifies and the thorough runs those submissions trigger. Held retries are Gate-internal and do not count. It is not a cap on a website session. A transport rate limit stays outside this policy. Only the thorough phase recomputes legs. thorough_six_leg_runs is how many Rule A4.4 runs are recorded. The leg count six_leg_runs is 6 times that number. Each run is one element of the six_leg_runs array, in request order. Concurrency is Rule A4.4.4. The draw count of each run stays 6. thorough_six_leg_runs is a per-profile candidate. This file does not lock one six_leg_runs count for every profile. A candidate, not locked: these counts are not shared across Gates in v1.

Normative. When a profile sets a figure, a hit on verify_cap_per_object or verify_cap_per_door_session, on a valid signature and a fresh challenge, is a fast final refuse of that attempt. A verify_cap_per_object refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. A verify_cap_per_door_session hit stays in received. The submission response is refuse. Candidate verify_cap_per_door_session is 3, 3, and 5 (strict, default, lenient). Not locked. It counts client submissions in the session, so it bounds the fast verifies and the thorough runs those submissions trigger. Held retries are Gate-internal and do not count. These are compute limits. They do not ban the key. The hit does not start a thorough run and does not issue a grant. An unauthenticated rate limit is not this rule. A transport rate limit stays outside this policy.

Rule B8. Leg count

Candidate, not locked. There is no minimum or maximum leg count as policy. leg_count ≥ 1 per stage follows from Rule A1.1 and is recorded under shape (Rule A4.1). It is not an admission threshold.

15.5 Candidate profiles

Informative except the default profile leg_rate_floor, which is locked. Three profile names: strict, default, lenient. Earlier candidate figures, superseded and not in force: leg-rate ratios 0.9 and 1.0; min_anchored_paths 12; plant_gap_ceiling 5; span_scale_seconds around 300. Decimal spellings 0.5 and 0.7 were old forms only. The locked default stays 1/2. The strict candidate stays 7/10. The current min_anchored_paths candidates are 8, 7, and 7. The table is the later candidate set from the 2026-10-06 admission policy candidates note, with the default floor and the anchoring target below. Only the default profile leg_rate_floor of 1/2 is locked. MVP only, not law. Every other figure in the table is a candidate for Christian's lock.

Parameter strict default lenient
leg_rate_floor 7/10. Candidate. About 2–3% honest refusal with a funded Bari; up to about 11% if Bari inclusion drops to 89% 1/2. Locked. MVP only, not law 1/2. Candidate
span_scale_seconds 360 300 300
span_sanity_bound unused unused unused
span_endpoint_mode neighbour raw raw
fixed_start on on on
min_anchored_paths 8. Candidate. 50% of 16 7. Candidate. 43.75% of 16 7. Candidate. 43.75% of 16
min_last_bookend 14. Candidate 14. Candidate 13. Candidate
plant_gap_ceiling record d_g; no integer record d_g; no integer record d_g; no integer
plant_gap_floor 1 1 1
thorough_six_leg_runs 15 (six_leg_runs = 90) 8 (six_leg_runs = 48) 8 (six_leg_runs = 48)
provisional off on on
fast_accept_bound unused 2 seconds 2 seconds
async_catch_budget 40.11 seconds 22.05 seconds 22.05 seconds
reverify_max_attempts 3 3 3
max_held_blocks 6 6 6
verify_cap_per_object 1 per (public_key, object_sha256, policy_version). Candidate 1 per (public_key, object_sha256, policy_version). Candidate 1 per (public_key, object_sha256, policy_version). Candidate
verify_cap_per_door_session 3. Candidate. Not locked 3. Candidate. Not locked 5. Candidate. Not locked

The candidates note also names fast_six_leg_runs and fast_checkpoint_sample. Leg recompute stays thorough, so fast_six_leg_runs is not a fast check. fast_checkpoint_sample is moot: the fast checkpoint-binding checks are the fold and the waypoint rehash under section 12.2, and the Rule A4.3 checkpoint digest is thorough-only. The leg count six_leg_runs is 6 times thorough_six_leg_runs. Each run is one element of the six_leg_runs array. The three thorough_six_leg_runs figures above are candidates. Lenient is 8 runs (six_leg_runs = 48). This file does not lock one thorough count for every profile. That count is a candidate for the Chief of Staff and for Christian. The async_catch_budget candidates, in table order strict, default, lenient, are 40.11 seconds, 22.05 seconds, and 22.05 seconds, derived with parallel Rule A4.3. They are not locked. fast_accept_bound candidates are unused, 2 seconds, and 2 seconds. reverify_max_attempts and max_held_blocks are profile inputs. Candidates are 3 and 6. reverify_max_attempts counts not read retries only, fast or thorough. A hold with no not read, including fast_accept_bound and async_catch_budget, does not count toward it. It is hit when that many retries are still not read. max_held_blocks applies to every hold, whatever the cause: a fast not read, a thorough not read, fast_accept_bound, or async_catch_budget. It starts at the Gate’s {index-url} tip height at the first entry into held in the cycle and does not reset. held_bound fires when a check finds that tip ≥ start + max_held_blocks, even when no not read has occurred. Whichever budget is hit first, the outcome is refuse/revoke with reason held_bound. The detail names the budget that hit. Exhausting either budget is revoke if a grant was ever issued (provisional or accept, including one already ended by the move from provisional to held), and refuse if none was. After a grant was issued (provisional or accept, including one already ended by the move from provisional to held) that outcome is revoke, not refuse. A held_bound revoke binds to (public_key, object_sha256). It does not end another object's grant. A revoke in that object's own admission cycle ends that pair's grant and any signature-only sign-in for the object. A later-policy re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry, by a compare-and-remove on object_sha256, only if the current entry's object hash still matches this pair. A not read does not. verify_cap_per_object is a candidate of 1 per (public_key, object_sha256, policy_version). It is a compute limit, not a ban. A hit's refuse is final for that submission_sha256 only. It does not change the pair's state or friendly entry. It is not the pair's thorough decision. A cap refuse (verify_cap_per_object or verify_cap_per_door_session) does not end the cycle. Service-started re-verifies and held retries are not in the count. Candidate verify_cap_per_door_session is 3, 3, and 5 (strict, default, lenient). Not locked. It counts client submissions in the session, so it bounds the fast verifies and the thorough runs those submissions trigger. Held retries are Gate-internal and do not count. A transport rate limit stays outside this policy. Only the default profile leg_rate_floor of 1/2 leg per second is locked. MVP only, not law. Every other figure in this table is a candidate for Christian's lock. The strict column's 7/10 stays a candidate. min_anchored_paths candidates are 8, 7, and 7. 8 is 50% of 16. 7 is 43.75% of 16. Both sit inside 40–50%. min_last_bookend candidates are 14, 14, and 13. The target is about 1% honest refusal. An append-only hash-chained log entry may record the {index-url} tip height as bitcoin_tip_height. That name is the log entry's name for the tip. The entry is unsigned. It is not a signed head. v1 has no signed head and no signed receipt. These other counts are not shared across Gates in v1. One entry per key. Only a thorough accept of a newer object replaces it. Newer is the decision-log seq of that thorough accept. A higher seq is newer. A refuse or a revoke of any other object does not change the entry. A re-verify of an older object does not displace the current entry. A re-verify that refuses, other than a B7 cap refuse, is a revoke and removes the friendly entry only when this object is the key's current friendly entry. A not read does not remove the entry. A logged manual operator strike of the key is the other removal. Signature-only sign-in requires state accepted. The list has no time expiry. provisional_action_cap is fixed at 0. A provisional grant is short, read-only access until thorough settles or async_catch_budget is hit. revoke_scope is the pair (public_key, object_sha256).

16. Conformance

Normative.

Conformance is stated separately for three layers. A layer does not take on another layer’s job.

  1. A conforming producer follows section 13 and emits the deterministic encoding of section 7.3, or emits no object when the drive’s clock read does not succeed. It records evidence. It does not emit a verify record. It does not say accept, refuse, revoke, or held. A producer that pauses the drive to wait for a further header, or pauses on a wall clock, before the first leg of a stage does not follow section 13. Stage 0 has no path key in that encoding.
  2. A conforming verifier follows section 14 and outputs the verify record: measurements as integers and the section 4 tokens. The profile name and, under conformance-small, SAMPLE_BYTES, SLOT_BYTES, TERRAIN_BYTES, and STEPS_PER_LEG are verifier inputs. They appear on the section 14 verify record as measurement provenance. The profile is not absent from that record. The hike object gains no new field. It does not judge those measurements. It does not say accept, refuse, revoke, or held. It does not post a waypoint and it does not ask Bari for a path.
  3. A conforming admission service applies section 15. Its inputs are the hike object, public_key, the object hash, the challenge, the BIP-340 signature, and a policy profile. It runs section 14 itself. It does not take a submitter-supplied verify record. It does not rewrite the hike object.

Sections 8–14 do not use pass, fail, reject, deny, or admit for the object or for a measurement. Those outcomes are not verifier outputs. The decisions accept, refuse, revoke, and held, and the states in section 15.2, are section 15 only.

Vectors. Conformance to a layer means the implementation’s outputs for that layer equal the published vectors under conformance/v1.35/. The vectors are language-agnostic. The pack is published later. This section is the contract. It does not ship the vector files. A vector compares bytes, integers, and tokens. It does not grade a hike object as pass or fail. An admission vector, under a named policy profile, compares the section 15 states. This file governs. This file stays the specification. The vectors under conformance/v1.35/ are normative worked examples of it. Where a published vector and a sentence of this file conflict, the conflict is a defect in this specification. It blocks release until a specification change settles which side is right. An implementer reports the conflict and does not pick a side. A published vector does not settle the conflict. The conformance runner may mark that vector known-defect until the specification change settles it.

When two implementations follow this file at the production constants in section 6, they agree on the terrain bytes, each leg result, each checkpoint, the seed value, and each waypoint W_1 … W_17. Given the same path-body bytes and the same headers, they agree on the deterministic encoding of the object.

Section 10 defines production_seed, the fill pin, and every terrain byte. The checkpoint preimage and the waypoint preimage are defined in sections 11 and 12. The leg is defined in section 11. Path bytes on stages 1–17 are the checked body of the successful fetch in section 13.1, or the empty path bytes section 13.3 stores on path_skip or on not fetched by design. A verifier does not fetch those bytes again. Section 14.3 records skip, present, plant match or mismatch or not read or n/a, and d_g. Each verification draws its own six indices, as section 14.6 states, and records each recomputed leg result against the stored leg result.

16.1 Profile conformance-small

The profile in force is the section 6 constants, unless the verifier is running conformance-small. That profile may differ only in SAMPLE_BYTES, SLOT_BYTES, TERRAIN_BYTES, and STEPS_PER_LEG. The profile name and those four integers are verifier inputs. The hike object gains no new field. The verify record must state the profile, and must state SAMPLE_BYTES, SLOT_BYTES, TERRAIN_BYTES, and STEPS_PER_LEG when the profile is conformance-small. A live object still carries the section 6 size fields.

The profile may set only these section 6 parameters:

Parameter Rule ID Why it may shrink
SAMPLE_BYTES P6.5 It is the terrain-sample limb of every checkpoint preimage.
SLOT_BYTES P6.1 It is the slot the step reads and the slot-sample limb of every waypoint preimage.
TERRAIN_BYTES P6.3 It is the terrain fill.
STEPS_PER_LEG P6.7 It is the number of steps in one leg.

WORDS_PER_SLOT, N_SLOTS, and OFFSET_MODULUS are not free integers under the profile. They stay the section 6 formulas (P6.23, P6.24, P6.25) applied to the profile values. SLOT_BYTES is at least 8, a multiple of 8, and at most the section 6 value. STEPS_PER_LEG is at least 1 and at most the section 6 value. SAMPLE_BYTES is at least 1 and at most the section 6 value. TERRAIN_BYTES is at most the section 6 value, an exact multiple of SLOT_BYTES, and at least SAMPLE_BYTES, so OFFSET_MODULUS stays at least 1. The step XORs every word of the slot (w = 0 … WORDS_PER_SLOT − 1). The checkpoint preimage stays 32 + 32 + SAMPLE_BYTES + 8 bytes. The waypoint preimage stays 32 + SLOT_BYTES + 32 + 32 bytes. Hash order does not change.

The profile does not set STAGES, HASH_BYTES, LEG_RESULT_BYTES, PUBLIC_KEY_BYTES, BEACON_BYTES, SEAL_DOMAIN, SEED_DOMAIN, KIND, FILL, or the mix constants P6.17–P6.22. It does not set the Hashcash difficulty (P13.5), the ask window (P13.2), or the six-leg draw count (A4.4.1). A thread pool is not required (A4.4.4). Eighteen stages stay eighteen. Waypoint indices, W_17, and the cut-off stay where section 13 puts them.

Legs. legs_per_stage is vector fixture metadata, not a section 6 constant. It is a list of 18 integers, one per stage, each at least 1. Producer KATs use it: the rig’s tip schedule follows that list, and the vector’s object stores those counts as leg_count. Production produce does not read legs_per_stage. Principle 9 still holds: there is no maximum leg count on a live object. A vector that exercises Rule A4.4 needs at least 6 legs in the hike, because fewer than 6 leaves six_leg not defined. The draw count stays 6.

Draw bytes. Injected draw bytes are rig-only, for a deterministic Rule A4.4 vector. The rig supplies them in order. They are never a production input. Production verify reads the operating-system CSPRNG. The draw count stays 6.

Wire. The hike object gains no new field. Its size fields stay slot_bytes, n_slots, sample_bytes, and steps_per_leg. Under conformance-small those fields hold the verifier’s four integers, and n_slots is the formula. shape_item includes constant when a field differs from the profile in force (A4.1.2). A live producer emits section 6 only.

Rig checks. Whether the six recomputes run together is Rule A4.4.4. A rig does not add a required pool. A rig may also check the producer rules in this file: no pause before the first leg (section 13.1), and the wait from 15 seconds through 45 seconds (P13.7). Rig-only inputs, not specification parameters, are now_unix_s, the issuing nonce, phase timing, the local mirror contents, a Bari mock, injected draw bytes, and the block-height (tip) clock.

The same profile integers and the same inputs produce the same bytes. The profile does not relax an INVARIANT. QA chooses the integers in the published vectors. This section does not lock a second set of production numbers.

17. Closed

Informative.

The items below were open. They are now part of the construction in the sections named here. This section does not add a second construction.

17.1 Waypoint path fetch

Closed. Section 13.1 posts W_1 … W_17. It does not post the seed value. The ask window is section 13.1. No ask is made above the cut-off. Test and live use that one rule. On stages 1–17 the field is path, a CBOR byte string, in the key order of section 7.3. Stage 0 has no path key. Those bytes are not an input to section 11.4 or section 12.2. A POST that does not succeed, and an ask that is not a checked 200, are a miss. Produce stores empty bytes and writes path_skip <g> for W_1 … W_16, and continues. It does not withhold the object. W_17 writes not fetched by design 17. A skip is a measurement (Rule A4.5). Rule B4 is where a paths minimum would judge it, and that rule is not locked. A present path is measured in section 14.3, including CanonicalPathBody validate_for and the chain bind. Verify does not GET the path again.

17.2 Portable-index height fetch during verify

Closed. Verify reads the portable index. For each stored header, including last_interrupt, it GETs {index-url}/index/{height} and records index_comparison and T under Rule A4.2. T is header.time, Bitcoin nTime, Unix seconds, only when index_comparison is match. Otherwise T is not read. On the mainnet path, header.time equals the nTime of the block whose hash is header.hash. index_comparison uses hash and height only. An unsuccessful read is not read. A hash or height that differs, when the read returned them, is mismatch, and T is not read. That record is part of Rule A4.2. The admission service applies section 15 to the record. Verify does not GET an HTTP tip. Verify does not Observe.

The LAN test plane has its own index and its own synthetic network. A synthetic hike can still record match against that index. On the LAN test plane, the index check is against that plane’s own synthetic block. A header that matches that synthetic block is not a mismatch for not being a Bitcoin block. A match against that index is not a mainnet fact.

17.3 Path and chain bind

Closed. On the mainnet path, section 14.3 records plant match, mismatch, or not read for each present path among W_1 … W_16. not read means the read does not return a document. A document that was read and has no plant for that block, or whose plants do not match, is mismatch. Only match is anchored and fetched. On mismatch and on not read, d_g is not defined. claimed_block is the path body’s block_hash and block_height when those fields can be read, otherwise not defined. The measurement is distinct from Rule A4.2. A skip is not a plant comparison. Verify does not GET the path from Bari again.

The LAN test plane has its own index and its own synthetic network. On that plane, section 14.3 records the plant comparison against that plane’s own index and its synthetic facts. A synthetic hike can still record match against that index. A match against that index is not a mainnet fact.

18. Follow-up wording

Informative for items 1–8. Item 9 is locked.

Items 1–8 are Docs follow-ups. This file does not edit the files named there. Docs applies the replacements an item still names, in a later change. Item 1 remains Informative. Its closing sentences lock the public verify page’s website rows as valid and invalid and do not ask Docs to retoken those rows. The proposals follow section 4. Item 9 is locked law for the verify CLI.

  1. Verify page. In apps/www/verify/verify.js and apps/www/verify/README.md, replace “One broken checkpoint breaks the proof” with “One checkpoint recorded as mismatch is a measurement. The Gate judges it.” Replace “An empty path does not fail” with “An empty path on W_1 through W_16 is recorded as skip. The Gate’s paths minimum, if one is locked, is what judges skips.” The public verify page’s website rows stay valid and invalid. This item must not require those website rows, including the lines for shape, checkpoint_mismatch, and successor_waypoint_differs, to become the section 14 match and mismatch tokens. Apps (CLI, Desktop, and Android) keep the section 14 measurement vocabulary (match, mismatch, and the other section 14 tokens).

  2. Ops. In docs/ops/live-environment.md and docs/ops/test-environment.md, replace “A verify pass is a separate result” with “A verify measurement record is separate from the Sign-in door. Verify records match, mismatch, present, skip, not defined, not read, not fetched by design, and n/a. The Sign-in door is the HTTP door of the Grounding Lab Admission Gate (section 15).” A Bari POST miss or an incomplete ask is path_skip and produce continues. Those pages must not say a missed plant stops the hike.

  3. Gate /v1/verify. Replace the double-negative fields checkpoint_mismatch: false and successor_waypoint_differs: false with positive tokens, for example checkpoints: match and waypoints: match, using match and mismatch. The same response’s present_path_does_not_match_plant and opening_waypoint_is_zero follow the same change: plant comparison match or mismatch, and the seed-value comparison match or mismatch. opening_waypoint_is_zero does not describe the seed value.

  4. Leg names. Where a measurement name or a test name says “re-walk” or “re-walked leg,” say “leg” or “recomputed leg result.” Section 14.6 in this file already uses that wording. Other files still say “re-walk,” including reference/hike/src/verify.rs, services/admission-gate/src/jobs.rs, and apps/window/pcoc-window/tests/acceptance.rs. Those stay for a later change. The constant SEAL_DOMAIN (pcoc-walk-v1) is a byte string. It is not leg wording, and it stays.

  5. tip.pcoc.app. docs/ops/test-environment.md lists tip.pcoc.app under “Live names” (the sentence that begins “Live names (bari.pcoc.app…” and the row “Live hosts on this profile”). That name is retired. Remove it from the live-name lists. Add a note: do not recreate tip.pcoc.app. Christian’s lock is: do not recreate tip.pcoc.app. Section 9 of this file already says it is retired and is not a dial.

  6. Record keys. A later verify response uses the keys pinned in the section 14 verify record: profile (and, when that profile is conformance-small, sample_bytes, slot_bytes, terrain_bytes, and steps_per_leg), shape, shape_item, headers (stored_hash, stored_height, index_comparison, T), seed_value, checkpoints, waypoints, paths (path_state, plant, P_g, height_P_g, d_g, claimed_block, and for g = 17 w17_path), anchored_total, skip_list, a, b, N, S, delta_h, fixed_start (N, S, delta_h), P_b, height_P_b, index_comparison_P_b, T_P_b, height_I_a_minus_1, T_I_a_minus_1, six_leg, six_leg_runs, and analysis (per_stage carries checkpoint_count, not leg_count). Tokens stay lowercase with spaces, including n/a. Do not add a rate field. Admission outcomes for those keys are section 15.

  7. Bari status 204. Call it a successful POST. Do not call it an admit outside section 15.

  8. Produce event log. In section 13.5 the keys posted, succeeded, checked, and worked stay JSON true or false on that log. They are not section 14 tokens. A later wording pass may describe those log fields without using them as verify outcomes.

  9. Verify CLI. Locked. CoS GO, Techie align audit 2026-10-07. The verify CLI emits only section 14 record tokens. Exit code 0 means the record was produced, whatever it measured, including all not read or mismatch. Exit code 1 means the object file cannot be read or decoded. That is the object file, not the not read token. Exit code 2 means a usage or invocation error. No other exit code. Stdout and stderr carry no pass, fail, accept, refuse, or admission wording. A named --profile is production or conformance-small. An omitted --profile, test, and live still measure with the section 6 constants. --profile live selects the live index only. Admission never runs inside verify. --profile is not a Gate policy profile. Profile judgement lives only in the policy library and the Gate.