Chunked, Hashed, Interleaved Resolution Protocol (CHIRP)
Right now, if you publish a big file for others to fetch and verify by hash, you have to wait until the whole file finishes before you can even name it, and anyone downloading it has to fetch the entire thing and check it as one giant blob before trusting any of it. That means a dropped connection near the end wastes the whole transfer, and there is no safe way to grab just a slice of a huge file and know it really belongs to that file. This makes large media, datasets, and archives clunky to publish and painful to stream or resume.
Reference for an AI
Everything an assistant needs to answer questions about BRC-167 accurately, including what it depends on.
Summary
- Why
- Whole-file hashing forces publishers to wait for the entire file before naming it and forces consumers to download and verify everything at once, which is slow, memory-heavy, and unfriendly to retries or partial reads.
- What
- BRC-167 (CHIRP) is a Merkle-tree layer on top of the Universal Hash Resolution Protocol (UHRP) that identifies a large ordered byte stream by a small root hash built from hashed chunks.
- How
- A publisher splits the source into bounded chunks, hashes them (and any branch nodes) into a Merkle tree, uploads chunks as they become available, and a resolver fetches and verifies chunks independently, in parallel from multiple hosts, retrying or resuming only the pieces it still needs.
What this lets you do
- Start uploading a large file before it finishes generating
- Verify each chunk independently instead of the whole file at once
- Fetch chunks concurrently from multiple hosts
- Retry or resume just the failed or missing chunk, not the whole transfer
- Request a logical byte range and prove it belongs to the full file
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
The specification
Abstract
This document defines the Chunked, Hashed, Interleaved Resolution Protocol (CHIRP), an immutable Merkle-object layer above the Universal Hash Resolution Protocol (UHRP) defined by BRC-26. UHRP identifies and discovers a single byte string by its SHA-256 hash. CHIRP identifies an ordered byte stream by a small root node that commits to hashed data chunks and, when necessary, hashed branch nodes.
CHIRP allows a publisher to hash and upload bounded chunks while the source is still arriving. A resolver can retrieve chunks concurrently from multiple UHRP hosts, verify each complete chunk before releasing it, retry an individual chunk without restarting the transfer, resume interrupted work, and select only the chunks needed for a logical byte range. The final CHIRP identifier is necessarily known only after the source ends, but publication no longer requires retaining or uploading the entire source as one object.
CHIRP reuses the existing tm_uhrp topic and ls_uhrp lookup service. A CHIRP
root is itself an ordinary UHRP object, and a UHRP host advertising that root
commits to serve the root's complete transitive object closure until the
advertisement expires. This design adds chunk-aware resolution without
creating a parallel discovery network or changing existing UHRP tokens.
Motivation
The UHRP content-addressing model provides strong whole-object integrity and location independence. Its simplest client contract nevertheless treats a file as one object: the publisher derives the identifier from the whole file, and the consumer accepts the result only after the whole response matches that identifier. For large files this creates avoidable latency, memory, retry, and resilience costs:
- publication cannot name the final object before the source reaches EOF;
- a failed upload or download may require retrying the whole file;
- a consumer cannot safely use an early portion merely because it arrived;
- ordinary range requests do not prove that a returned range belongs to the requested whole-object hash;
- replicas are selected at whole-file granularity rather than per unit of transfer; and
- one UHRP advertisement per independently resolved chunk would make very large files operationally expensive.
CHIRP retains UHRP as the trust and discovery base while making the transfer unit bounded and independently verifiable. It uses one advertised root per complete host replica, not one advertisement per chunk. The Merkle structure also creates stable extension points for future content-defined chunking, media-aware alignment, compact inclusion proofs, partial hosting, erasure coding, and collections without changing the meaning of a v1 identifier.
Conformance Language
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, NOT RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119 and RFC 8174 when, and only when, they appear in all capitals.
Scope
CHIRP v1 describes one immutable, ordered byte stream. It defines:
- the CHIRP URI and its relationship to a UHRP identifier;
- canonical root and branch node encodings;
- a deterministic fixed-size chunking profile;
- complete-host advertisement and HTTP retrieval semantics;
- progressive, interleaved, range-aware verification; and
- an interoperable baseline upload-session contract.
The following are deliberately outside the v1 interoperability core:
- mutable or append-only streams;
- named directory or collection trees;
- encryption and key distribution;
- compression or transcoding semantics;
- time-based media seeking;
- erasure-coded reconstruction;
- partial-host discovery and coverage aggregation; and
- a compact serialized Merkle inclusion-proof format.
These features can be specified later using the version, profile, node-kind, child-kind, and extension registries defined below. A future specification MUST NOT redefine bytes or semantics assigned by this document.
Terminology
- Logical content: The ordered byte stream represented by a CHIRP root after concatenating its leaf blobs from left to right.
- Object: A byte string stored by a CHIRP host and identified by
SHA-256(objectBytes). An object is either a raw blob, a branch node, or the root node. - Blob: A leaf object whose bytes are logical content bytes without a CHIRP wrapper.
- Node: A canonically serialized CHIRP root or branch object.
- Object hash: The 32-byte SHA-256 digest of the exact stored object bytes.
- Object identifier: An object hash encoded with the existing BRC-26 UHRP
Base58Check prefix
ce00. - Root identifier: The object identifier of the canonical root-node bytes.
- Closure: The root plus every branch and blob reachable from it by recursively following child references.
- Complete host: A host that advertises a root through UHRP and serves every object in that root's closure through the CHIRP object route for the advertisement's lifetime.
- Chunking profile: A deterministic algorithm for dividing logical content into blobs and constructing a canonical tree. The graph remains self-describing after it has been constructed, subject to the fixed safety ceilings of its node major version.
Relationship to UHRP
Root addressing
The canonical CHIRP URI is:
chirp://<rootIdentifier>
rootIdentifier MUST be a valid BRC-26 UHRP object identifier: Base58Check of
the two prefix bytes ce00 followed by the 32-byte SHA-256 root hash. The
Base58Check checksum is not an integrity substitute; implementations MUST
still verify the SHA-256 digest of downloaded bytes.
The URI scheme is case-insensitive under ordinary URI rules, but producers
MUST emit lower-case chirp. The Base58Check identifier is case-sensitive.
The canonical URI MUST NOT contain user information, a port, path segments,
query parameters, or a fragment. Parsers MAY accept the equivalent compact
form chirp:<rootIdentifier>, but MUST canonicalize it to the double-slash
form. A bare identifier remains a UHRP identifier and MUST NOT be inferred to
be CHIRP.
The UHRP URL used to discover the root is derived without hashing again:
chirp://<rootIdentifier> -> uhrp://<rootIdentifier>
A resolver queries BRC-24 service ls_uhrp with that UHRP URL, follows the
current, unexpired BRC-26 advertisements, and treats each conforming advertised
location as a candidate complete host. CHIRP v1 defines no tm_chirp topic and
no ls_chirp lookup service.
Blob compatibility
Raw CHIRP blobs use exactly the same SHA-256 object identity as ordinary UHRP content. A CHIRP implementation MAY therefore ingest an already hosted UHRP blob without changing its bytes or hash, subject to the CHIRP host committing that blob into a root closure.
A CHIRP tree containing one blob is valid. Applications SHOULD continue to use
plain UHRP for small, indivisible content when progressive publication, range
selection, or tree composition provides no benefit. A unified application API
MAY accept both uhrp: and chirp: references, but the two schemes retain
their distinct interpretation.
Advertisement meaning
For a CHIRP root, the existing BRC-26 advertisement fields retain their ordinary meanings:
hashis the SHA-256 hash of the exact root-node bytes;urlis the HTTPS location of those exact root-node bytes;expiryTimeis the end of the complete-host commitment; andcontentLengthis the serialized root-node byte length, not the logical content length.
The logical content length and logical content SHA-256 are committed inside the root node.
A host MUST NOT publish the root advertisement until it possesses and has validated the complete closure. Before broadcasting a renewed root advertisement, a host MUST durably extend the lease of the complete closure through the new advertised expiry. If broadcast has an ambiguous outcome, the host MUST retain the extended lease and reconcile or safely retry the advertisement; it MUST NOT roll the closure lease back to an earlier expiry. Spending, expiry, or eviction of the advertisement ends the UHRP availability claim but does not change the immutable CHIRP identifier.
Cryptographic Construction
CHIRP v1 uses SHA-256 for all object hashes:
objectHash = SHA-256(exactObjectBytes)
Blob object bytes are the logical content bytes themselves. Node object bytes
begin with the CHIRP magic value and contain ordered child hashes. Therefore
the root-node hash recursively commits to every branch, blob, blob boundary,
blob length, and blob position. The magic value, node kind, and childKind
context distinguish node interpretation from blob interpretation. If raw blob
bytes happen to equal valid serialized node bytes, their identical object hash
is intentional; the parent reference still determines whether those bytes are
content or a branch.
The root also contains:
contentHash = SHA-256(concatenatedLogicalContentBytes)
contentHash provides a conventional whole-stream identity independent of
the chunking profile and root metadata. It is redundant with the verified
Merkle graph for integrity, but it allows applications to compare two CHIRP
representations of the same bytes and to derive the conventional UHRP
identifier those bytes would have. That derived UHRP identifier is an identity
only; its availability is not implied unless a host separately advertises the
assembled byte stream through UHRP.
No double-SHA-256 operation is used for object or content hashes. Ordinary Base58Check checksum rules still apply when encoding an object identifier. Digest bytes are stored in the natural left-to-right SHA-256 output order and MUST NOT be reversed as transaction identifiers sometimes are.
Integer and Extension Encoding
Fixed-width integers
uint8, uint16, and uint64 fields are unsigned integers encoded in
big-endian byte order. A uint64 value can range from 0 through 2^64 - 1.
An implementation unable to represent a valid value exactly MUST fail cleanly
rather than round or truncate it.
CompactSize
Variable counts and lengths use the canonical Bitcoin CompactSize encoding:
| Value | Encoding |
|---|---|
0..252 | one byte containing the value |
253..65535 | fd followed by uint16 little-endian |
65536..4294967295 | fe followed by uint32 little-endian |
| larger values | ff followed by uint64 little-endian |
The shortest possible representation MUST be used. A decoder MUST reject a non-minimal CompactSize value.
Extensions
Both node kinds end with an extension vector. Each entry is:
| Field | Encoding |
|---|---|
extensionType | CompactSize |
extensionLength | CompactSize |
extensionValue | exactly extensionLength bytes |
Extension entries MUST be strictly ordered by ascending extensionType and
MUST NOT repeat a type. Type 0 is reserved and MUST NOT appear.
Even-numbered types are critical. A consumer that does not understand a critical extension MUST reject the node. Odd-numbered types are advisory. A consumer MAY ignore an unknown advisory extension after enforcing its length and the general resource limits. Ignoring an advisory extension never changes the logical byte stream.
Future specifications assigning an extension MUST define the node kinds on which it is valid, its canonical encoding, security bounds, and whether it can affect logical-content interpretation. An extension that changes logical byte interpretation MUST be critical. A known extension appearing on a node kind for which it is not registered MUST be rejected.
Node Serialization
Common prefix
Every node begins with:
| Field | Size | v1 value |
|---|---|---|
magic | 5 bytes | ASCII CHIRP (43 48 49 52 50) |
majorVersion | uint8 | 1 |
minorVersion | uint8 | 0 |
nodeKind | uint8 | 0 root, 1 branch |
An unknown major version MUST be rejected. Within major version 1, a consumer MAY accept a higher minor version when the core layout is unchanged and every encountered critical extension is understood. Unknown node kinds MUST be rejected. Future minor versions under major version 1 MUST preserve the common prefix and kind-specific core layouts; they may assign profiles and extensions under the registries in this document.
Child reference
Each child reference is exactly 41 bytes:
| Field | Size | Meaning |
|---|---|---|
childKind | uint8 | 0 blob, 1 branch |
logicalLength | uint64 | logical bytes covered by this child |
objectHash | 32 bytes | SHA-256 of the exact child object bytes |
Unknown child kinds MUST be rejected. Child order is significant. Offsets are
not encoded; the logical offset of a child is the sum of the preceding child
lengths. A child with kind 0 is always interpreted as raw content bytes and
is never parsed as a node. A child with kind 1 MUST decode as a branch node;
the decoded branch logicalLength MUST match the reference length.
Root node
After the common prefix, a root node contains:
| Field | Encoding | Meaning |
|---|---|---|
chunkingProfile | uint16 | construction profile identifier |
logicalLength | uint64 | length of the complete logical content |
contentHash | 32 bytes | SHA-256 of the complete logical content |
childCount | CompactSize | number of child references |
children | childCount * 41 bytes | ordered child references |
extensionCount | CompactSize | number of extensions |
extensions | variable | ordered extension vector |
The sum of child logicalLength values MUST equal the root logicalLength.
An empty stream MUST have logicalLength = 0, childCount = 0, and
contentHash = SHA-256(empty).
Branch node
After the common prefix, a branch node contains:
| Field | Encoding | Meaning |
|---|---|---|
logicalLength | uint64 | logical bytes covered by the branch |
childCount | CompactSize | number of child references |
children | childCount * 41 bytes | ordered child references |
extensionCount | CompactSize | number of extensions |
extensions | variable | ordered extension vector |
A branch MUST contain between 1 and 256 children. The 256-child ceiling is a major-version-1 serialization and safety rule, independent of the root's chunking profile, because a branch does not carry a profile field. A profile MAY require a lower construction fanout but cannot make a branch with more than 256 children valid. The sum of child lengths MUST equal the branch length. A v1 branch MUST NOT use the root node kind as a child.
v1 extension registry
| Type | Name | Node | Criticality | Value |
|---|---|---|---|---|
1 | mediaType | root only | advisory | lower-case ASCII media-type essence |
The mediaType value, when present, MUST be an IANA-style type and subtype
such as application/pdf, audio/mpeg, or video/mp4. It MUST contain one
/, MUST NOT contain parameters or optional whitespace, and MUST be between 3
and 127 bytes. It is descriptive metadata, not authority to execute or render
content. Consumers MUST apply their own content-security and media policies.
Because extensions are part of the root bytes, changing mediaType changes the
CHIRP root identifier. It does not change contentHash. Applications that
need byte-only equivalence MUST compare contentHash, not root identifiers.
Chunking Profiles
Registry and forward compatibility
Chunking profile 0 is reserved. This document assigns profile 1.
A profile defines deterministic publication, including blob boundaries, construction fanout at or below the v1 ceiling, and canonical tree shape. The serialized graph explicitly contains all actual lengths and hashes, so a resolver that understands the node and child kinds MAY safely retrieve an unknown profile. Such a resolver MUST report that it could not validate profile-specific canonical construction and MUST still enforce all generic hash, length, extension, 256-child, and resource rules.
A publisher MUST implement a profile before emitting its identifier. A future profile specification MUST provide:
- a unique profile number;
- a deterministic boundary algorithm;
- minimum and maximum blob sizes;
- fanout and tree-shape rules;
- behavior for empty and malformed input;
- required media parsing, if any;
- denial-of-service limits and fallback behavior; and
- golden test vectors.
Profiles MUST NOT change the v1 node serialization, hash algorithm, or 256-child ceiling. A design requiring a larger fanout, different logical semantics, child kinds, or hash algorithms needs a compatible critical extension where possible or a new major version.
A complete host MAY store and advertise an otherwise valid root whose
chunking profile it does not understand, because closure, object hashes,
generic lengths, and contentHash remain independently verifiable. It MUST
NOT claim that the unknown profile's canonical construction has been checked.
Likewise, a resolver or host MUST report profile-specific construction as
canonical only after it has traversed and validated all construction rules for
that profile; a root declaration alone is not evidence of canonicality.
Profile 1: fixed 4 MiB, fanout 256
Profile 1 uses:
chunkSize = 4,194,304 bytes
fanout = 256 child references
The publisher MUST construct the tree as follows:
- Initialize one streaming SHA-256 context for
contentHash. - Read logical content in order into blobs of exactly
chunkSizebytes, except that the final non-empty blob contains the remaining bytes. - Update
contentHashwith every source byte in order. - For each blob, compute its object hash and append a blob child reference.
- If the reference list contains at most
fanoutentries, use it as the root child list. - Otherwise, partition the whole list left-to-right into consecutive groups
of at most
fanout, serialize one branch for each group, and replace each group with the corresponding branch reference. - Repeat step 6 until at most
fanoutreferences remain, then use that list as the root children. - Serialize the root with profile
1, the total logical length, the finalcontentHash, and the chosen extensions.
Every non-final blob MUST be exactly chunkSize bytes. At each constructed
level, every group except the final group MUST contain exactly fanout
children. The final group may contain one through fanout children. All root
children MUST be of the same child kind, and all leaves in a non-empty profile
1 tree have the same depth. An alternative grouping of the same blobs is not a
canonical profile 1 representation.
The 4 MiB boundary limits the amount a strict resolver must buffer before it can release verified data. A fanout of 256 keeps a node small while allowing a shallow tree for very large content.
Future media-aware profiles
Profile 1 is media-agnostic and may split a container box, compressed frame, record, archive entry, or independently decodable segment. This does not harm byte integrity, but it does not provide time-based or semantic random access.
Future profiles MAY align boundaries to structures such as ISO Base Media File Format boxes, WebM clusters, audio frames, image tiles, archive members, database pages, or application records. Such a profile SHOULD preserve a bounded maximum blob size and MUST specify deterministic behavior when the content is malformed, encrypted, unrecognized, or contains an oversized logical unit. A media-aligned profile MAY use advisory root extensions that reference a separate hashed random-access index. A time or semantic range MUST ultimately resolve to logical byte ranges before ordinary CHIRP retrieval.
Media metadata and alignment are intentionally separate. The mediaType
extension does not select a chunking profile, and a profile does not assert a
media type.
Complete-Host HTTP Retrieval Profile
Advertised root location
The HTTPS URL placed in the UHRP root advertisement MUST have this suffix:
/chirp/v1/<rootIdentifier>/objects/<rootIdentifier>
A provider MAY place deployment-specific path segments before /chirp/v1/.
The URL MUST NOT contain a query or fragment. Both occurrences of
rootIdentifier MUST exactly match the requested CHIRP root identifier.
The object URL for any descendant is derived by replacing only the final path segment:
/chirp/v1/<rootIdentifier>/objects/<objectIdentifier>
This derivation retains the scheme, authority, port, and preceding path from the UHRP-advertised root URL. Nodes never contain provider URLs. A resolver MUST NOT derive a child location from an unverified node field or from a redirect target.
Object response
An object request uses GET. A successful response MUST:
- return status
200; - return the exact object bytes whose SHA-256 matches
objectIdentifier; - use
Content-Encoding: identityor omitContent-Encoding; - remain available until the root advertisement expires.
A successful response SHOULD include an accurate Content-Length. A missing
Content-Length does not make an otherwise valid identity-encoded response
unusable, including when an HTTP/2 origin or intermediary streams the body.
When the header is present, it MUST be a canonical non-negative decimal length
and MUST equal the received byte length.
A resolver MUST enforce the exact expected byte length already committed by a
blob child reference whether or not Content-Length is present. Root and
branch responses MUST remain bounded by the v1 node-size ceiling. For a
profile it does not understand, a resolver MUST additionally apply a finite
local per-object byte ceiling before buffering the response.
A host SHOULD return application/vnd.bsv.chirp-node for root and branch
nodes and application/octet-stream for blobs. Response media types are
advisory and are not hashed. A host SHOULD expose credential-free cross-origin
GET, HEAD, and preflight access appropriate for public UHRP content.
A host MUST serve every closure object under the root namespace. It MAY return
404 for an object not in that closure even when the same bytes are stored for
another root. A host MAY deduplicate physical storage across roots, but lease
and garbage-collection accounting MUST keep a shared object while any
unexpired advertised root references it.
Redirects on derived object routes are NOT RECOMMENDED. If an implementation follows one, it MUST require HTTPS, enforce its normal redirect limit, verify the final bytes against the requested object identifier, and continue deriving other object URLs from the original UHRP-advertised root location.
Range requests
A CHIRP resolver performs logical byte-range selection by traversing only branches whose cumulative logical ranges overlap the request, fetching each overlapping blob in full, verifying the full blob hash, and then returning the requested subrange from the verified boundary blobs.
HTTP byte ranges over individual blob objects MAY be supported by a host, but
a v1 client MUST NOT label a partial blob response verified merely because the
server honored Range. A future proof-carrying sub-blob profile can relax this
rule without changing v1 behavior.
Resolution and Verification
A conforming resolver MUST:
- parse and canonicalize the CHIRP URI;
- derive the root UHRP URL and query
ls_uhrp; - discard expired or structurally invalid advertisements;
- obtain the root bytes from a candidate location and verify the root object hash before parsing any node field;
- validate the root encoding, understood critical extensions, and generic resource bounds;
- traverse verified branch nodes to determine the required blob references;
- retrieve each needed object from a candidate complete host;
- verify every complete object hash before parsing a node or releasing blob bytes;
- concatenate verified blobs in logical order, slicing verified boundary blobs for a requested range; and
- for a complete read, verify the final logical length and
contentHash.
The verified Merkle path commits every released blob to its position and
length before release. A complete-read contentHash mismatch still makes the
root malformed. A streaming API MAY already have released Merkle-verified
chunks when that final semantic check fails, so it MUST surface a terminal
error. Applications requiring all-or-nothing delivery MUST use an atomic sink
or buffer until completion.
Branch objects MAY be fetched lazily. Repeated references to the same object MAY be fetched once and reused, while still contributing bytes at every referenced logical position.
Interleaving and retry
When more than one complete host advertises a root, a resolver SHOULD interleave concurrent object requests across distinct hosts rather than using one host until failure. The exact scheduler, concurrency, throughput weighting, and cache policy are implementation choices and do not affect content identity.
A resolver MUST bound total and per-host concurrency. On a timeout, transport failure, non-success response, incorrect length, or hash mismatch, it SHOULD retry the same object at a different complete host before failing the logical read. Bytes from a failed or mismatched response MUST NOT be released. A resolver MAY lower the priority of a host after failures or integrity violations. A node that matches its requested object hash but fails canonical parsing or graph validation is an intrinsic error in the immutable CHIRP root; the resolver MUST fail rather than expect another host to supply different bytes for the same hash.
Cancellation MUST stop unnecessary requests. Resume state and caches MUST be keyed by verified object hash, not by an unverified provider URL. A cache MUST verify newly inserted bytes and MAY trust a previously verified immutable entry subject to local storage-integrity policy.
Merkle inclusion verification
A proof that a blob occurs at a logical position can consist of:
- the blob bytes or blob hash;
- the child index at each level; and
- the exact serialized ancestor nodes from the blob's parent through the root.
The verifier hashes the blob, checks the selected child reference, hashes each ancestor node, checks the next reference, and finally compares the root hash with the CHIRP URI. The accumulated lengths of preceding siblings determine the logical offset. This document defines the verification procedure but not a compact proof serialization; a later BRC may define one.
Baseline Upload-Session Profile
Discovery and reading are the protocol core. Hosts can apply different authentication, payment, quota, and contract mechanisms to publication. Nevertheless, the following baseline routes allow a reference uploader to stream objects before the final root exists.
Create a staging session
POST /chirp/v1/uploads
Content-Type: application/json
Request fields:
{
"retentionSeconds": "2592000",
"logicalLength": null
}
retentionSeconds is a positive decimal string. logicalLength is either a
decimal uint64 string when known or null for an open-ended input stream.
The host responds with status 201 and:
{
"uploadId": "provider-opaque-value",
"stagingExpiresAt": "1787616000"
}
The upload ID is an opaque capability within the authenticated session and
MUST NOT be interpreted by the client. stagingExpiresAt is a canonical
decimal uint64 string containing UNIX epoch seconds. A host MUST bound
staging lifetime and garbage-collect abandoned sessions.
Upload an object
PUT /chirp/v1/uploads/<uploadId>/objects/<objectIdentifier>
Content-Type: application/octet-stream
Content-Encoding: identity
The request body contains the exact object bytes. The host MUST stream-hash
the body and reject a digest mismatch. It responds 201 when newly staged or
204 when identical bytes were already available to the session. Provider
size, rate, and resource limits remain applicable.
A host MAY expose HEAD on the same URL so an uploader can skip an object the
session already references. A successful HEAD is only an upload
optimization; the host remains responsible for validation at commit.
Commit and advertise
POST /chirp/v1/uploads/<uploadId>/commit
Content-Type: application/json
{"rootIdentifier":"<rootIdentifier>"}
Before responding successfully, the host MUST:
- locate and hash-verify the root node;
- parse the graph with the limits in this document;
- verify every branch and blob hash;
- verify child counts, lengths, closure completeness, root logical length,
and root
contentHash, plus profile-specific canonical construction when the host understands the declared profile; - establish a lease for every closure object through the promised expiry;
- create and submit the ordinary BRC-26 root advertisement; and
- ensure the advertised URL follows the complete-host retrieval profile.
The host responds 201 with:
{
"chirpURL": "chirp://<rootIdentifier>",
"uhrpURL": "uhrp://<rootIdentifier>",
"hostedFileLocation": "https://host.example/chirp/v1/<rootIdentifier>/objects/<rootIdentifier>",
"expiryTime": 1787616000
}
Failure MUST NOT create an advertisement. Retrying a successful commit for the same session and root SHOULD be idempotent.
Closure validation MUST use bounded aggregate memory. In particular, a host
MUST NOT retain every unique blob body merely to validate contentHash; it
SHOULD stream or re-read one bounded blob at a time while separately retaining
only bounded graph metadata. Public object authorization SHOULD use a bounded
cached or indexed closure-membership representation rather than re-parsing and
linearly scanning an entire commit record for every request.
Authentication may use BRC-103 and BRC-104. Pricing and payment may use BRC-105, BRC-121, or another contract selected by the host. CHIRP does not change those protocols.
Partial Hosting and Future Coverage Protocols
The Merkle graph naturally permits a host to retain selected subtrees, blobs, or logical ranges. CHIRP v1 intentionally does not advertise partial coverage, because silently weakening a root advertisement would make existing clients believe that one host can complete a read when it cannot.
A partial host therefore MUST NOT make the ordinary v1 root advertisement unless it also retains the complete closure. A future coverage protocol SHOULD identify coverage using one or more of:
- complete subtree object hashes;
- ordered leaf intervals;
- logical byte ranges aligned to child boundaries; or
- a compact bitmap tied to a specific canonical chunking profile.
The coverage claim SHOULD be signed or carried by a spendable, expiring token, MUST bind the root identifier and coverage representation, and MUST distinguish integrity from availability. A future resolver may union independently hosted coverage and prove that the union closes the requested range or whole root.
Such a protocol may define a new BRC-87-compliant topic and lookup service, for
example a coverage-specific tm_* / ls_* pair, without changing
tm_uhrp / ls_uhrp or the v1 complete-host promise. Complete v1 hosts remain
valid fallback providers for future resolvers.
Erasure-coded shards require reconstruction semantics beyond child kind 0
and 1. They MUST use a new understood child kind plus a critical extension,
a new node major version, or a separate BRC that defines equivalent fail-closed
behavior. A v1 resolver will reject rather than misinterpret them.
Future Collections and Structured Content
The v1 root represents one byte stream, not a filename, directory, or mutable dataset. A future collection node may map normalized names to independently hashed CHIRP or UHRP objects, but must define path normalization, duplicate handling, ordering, metadata limits, link cycles, and traversal security. It must use a new node kind or major version, so v1 readers fail explicitly.
Applications can meanwhile serialize an archive, database snapshot, media container, or other dataset into one logical byte stream. CHIRP verifies the bytes; the enclosing format defines its own semantics.
Security and Privacy Considerations
Integrity is not authenticity
CHIRP proves that retrieved bytes match an immutable identifier. It does not identify the author, establish copyright or safety, certify a media type, or authorize execution. Applications needing provenance MUST authenticate the CHIRP URI through a signature, transaction, certificate, trusted application record, or other external mechanism.
Resource exhaustion
Before allocating or scheduling from untrusted node values, a v1 implementation MUST enforce at least these limits:
- no more than 256 children per root or branch for every v1 profile;
- no node larger than 65,536 bytes;
- no more than 16,384 total extension-value bytes per node;
- no extension type or length above local exact-integer capacity;
- a traversal depth no greater than 16;
- bounded unique-object count, total traversed-reference count, logical length, fetch concurrency, retries, redirect count, response size, and wall-clock time; and
- exact agreement between declared lengths, received lengths, child sums, and local application limits.
Implementations MUST track the active ancestry to reject cycles. They MAY memoize bounded graph metadata or verified identifiers, but MUST NOT rely on memoization as the traversal bound and need not retain object bodies. Reusing a verified object at multiple legitimate logical positions remains valid, with each occurrence counting toward the total traversed-reference limit.
Network safety
Child nodes contain no URLs. A resolver MUST derive object URLs only from an HTTPS location returned by the UHRP root lookup. It MUST apply SSRF protections appropriate to its environment, including redirect, address-range, DNS rebinding, port, and response-size policies. Browser implementations inherit the browser's network boundary but still need response and concurrency limits.
Early consumption
A chunk is Merkle-verified before release, but a full stream is not declared
complete until its length and contentHash are checked. Media players and
other progressive consumers MUST handle a terminal error after earlier valid
chunks. Executable, scriptable, or security-sensitive formats SHOULD use an
atomic fully verified sink unless their own incremental validation model makes
early execution safe.
Metadata and content handling
The optional media type is attacker-controlled advisory metadata. Consumers SHOULD disable MIME sniffing where applicable and MUST apply their own active content, sandbox, filename, and download policies. No filename is carried in v1, avoiding path traversal and normalization ambiguity.
Confidentiality and equality leakage
CHIRP is public content addressing. Hashes reveal equality of identical blobs, tree shape reveals approximate size and repetition, and advertisements reveal hosting relationships. Applications requiring confidentiality SHOULD encrypt and pad content before CHIRP chunking. The CHIRP hashes then authenticate the ciphertext. Key distribution and padding policy are outside this document.
Availability
Hash verification detects corruption but does not create availability. A publisher SHOULD obtain independent complete-host advertisements according to its resilience needs. Resolver interleaving improves fault tolerance but cannot recover an object absent from every complete host. Future partial and erasure-coded profiles can add availability strategies without weakening v1 integrity.
Conformance and Test Vectors
An implementation claiming CHIRP v1 publication conformance MUST produce the canonical profile 1 root for identical logical bytes and identical root extensions. A resolution-only implementation MAY support additional profiles subject to the fail-closed rules above.
Portable test vectors SHOULD include exact node bytes, SHA-256 object hashes, Base58Check identifiers, CHIRP URIs, content hashes, and expected parse or verification results for at least:
- empty content;
- one byte;
chunkSize - 1,chunkSize, andchunkSize + 1bytes;- 256 and 257 blobs;
- multiple branch levels;
- repeated identical blobs;
- an optional
mediaTypeextension; - a complete and a logical-range read;
- one unavailable host and one corrupt host;
- non-minimal CompactSize values;
- unsorted, duplicate, unknown advisory, and unknown critical extensions;
- invalid node, child, profile, and version values;
- child-length and root-length mismatches;
- root
contentHashmismatch; - excessive depth, object size, extension size, and fanout; and
- an attempted partial host represented as a complete v1 host.
The reference vectors SHOULD be independent artifacts that TypeScript and other language implementations can consume without executing one another's code.
Compact tree-shape vectors
Implementations MAY represent large tree-shape vectors as deterministic child
references instead of embedding the corresponding blob bodies. The v1
reference corpus uses logicalLength = 4194304 for every synthetic leaf and:
objectHash[i] = SHA-256(UTF8("BRC-167 canonical tree leaf:" + decimal(i)))
where i starts at zero with no leading zeroes. Canonical construction over
those references MUST produce:
| Leaf count | Level widths, including leaves | Total branches | Final root child logical lengths and object hashes |
|---|---|---|---|
| 256 | 256 | 0 | the 256 input blob references unchanged |
| 257 | 257, 2 | 2 | 1073741824:65f0e211bb73fbe7c7db0a0433d1626dc1c775821fa658bbf43175e610e44fa2, 4194304:bc57e1fe69cbc6245cace01bbd61591cf81737a162707dea131b63da3f77e74e |
| 65537 | 65537, 257, 2 | 259 | 274877906944:9b196daab8cd5ef832093f9fadab3656e3cc2fd2256857e91956f4baacd0a0e8, 4194304:cfee311fdfa2f73242c729513a301e0b670b3bd6511bce9c2f65018a2da97c53 |
Each final entry is logicalLength:objectHash for a branch child reference.
Matching these hashes commits to the exact serialized intermediate branches
and therefore detects a different grouping rule without requiring the vector
artifact to repeat every branch byte.
Minimal golden vectors
The following vectors use profile 1 with no extensions. Hex strings contain
the exact serialized root bytes without whitespace.
Empty logical content
contentHash:
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
rootBytes (52 bytes):
434849525001000000010000000000000000e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b8550000
rootHash:
0403640d635fd27b6c719d2b81db853483ff8d0fb46f7b90ce9f9d7e9a2729ee
rootIdentifier:
XUSvYkywHxEMvs7oiYYMV8bJ1sJjHq2mHgZvu8jSLyLhbNRVjG8E
chirpURL:
chirp://XUSvYkywHxEMvs7oiYYMV8bJ1sJjHq2mHgZvu8jSLyLhbNRVjG8E
UTF-8 hello
The one blob contains bytes 68 65 6c 6c 6f.
contentHash and blobHash:
2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
blobIdentifier:
XUTEaLuvEhPySbAMiJxYEhBBGb258URNoqgnaf3Ym4b2wg683ZKp
rootBytes (93 bytes):
4348495250010000000100000000000000052cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824010000000000000000052cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982400
rootHash:
1731ac8562f744fdd7990a5ef69b36bc140761db5b0e2936e896021d03b276a9
rootIdentifier:
XUT4zhwjYd9NLrcGUudTnMiQ7WpA3SeEa4T7ZwrcNn85qmW1XucC
chirpURL:
chirp://XUT4zhwjYd9NLrcGUudTnMiQ7WpA3SeEa4T7ZwrcNn85qmW1XucC
UTF-8 hello with mediaType = text/plain
This vector uses the same blob and content hash as the preceding vector. Its
extension vector is 01 01 0a 746578742f706c61696e: one extension, type 1,
length 10, value text/plain.
rootBytes (105 bytes):
4348495250010000000100000000000000052cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824010000000000000000052cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b982401010a746578742f706c61696e
rootHash:
5493c139e9366f7c3facf9b3f28d5e0da6514fec975e70f59f5b2c3a40cd2c85
rootIdentifier:
XUTY2f2HxHyj7RDPgsSngBETiwZj58oYfjyGgfgFLsCE2y3mgrGv
chirpURL:
chirp://XUTY2f2HxHyj7RDPgsSngBETiwZj58oYfjyGgfgFLsCE2y3mgrGv
Implementation Plan
The initial reference implementation is intended for the
bsv-blockchain/ts-stack and is
expected to include:
- a compositional
@bsv/chirppackage with canonical codecs, streaming upload, lazy tree traversal, bounded interleaved download, range selection, caching, and verification; - adapters for browser
BlobandReadableStream<Uint8Array>plus Node.jsAsyncIterable<Uint8Array>sources; - a CLI supporting publication, retrieval, verification, and logical ranges;
- the baseline upload and complete-host routes in the basic filesystem UHRP server;
- equivalent cloud-bucket support after the local contract is proven;
- an OpenAPI description of the HTTP routes;
- portable conformance vectors; and
- a two-host integration proof covering progressive upload, first-chunk delivery before full completion, interleaving, retry, corruption rejection, cancellation, resume, expiry, and bounded memory.
The implementation is informative. The byte-level and behavioral requirements in this BRC are authoritative for interoperability.
References
- BRC-22: Overlay Network Data Synchronization
- BRC-24: Overlay Network Lookup Services
- BRC-26: Universal Hash Resolution Protocol
- BRC-87: Standardized Naming Conventions for BRC-22 Topic Managers and BRC-24 Lookup Services
- BRC-103: Peer-to-Peer Mutual Authentication and Certificate Exchange Protocol
- BRC-104: HTTP Transport for BRC-103 Mutual Authentication
- BRC-105: HTTP Service Monetization Framework
- BRC-121: Simple 402 Payments
- RFC 2119: Key words for use in RFCs to Indicate Requirement Levels
- RFC 8174: Ambiguity of Uppercase vs Lowercase in RFC 2119 Key Words
- RFC 6838: Media Type Specifications and Registration Procedures
- RFC 9110: HTTP Semantics
- BitTorrent Enhancement Proposal 52: The BitTorrent Protocol Specification v2
- IPFS UnixFS specification