Beersy
BRC-167

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.

Ty Everett28 min read

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 (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.

abcservice

The specification

Abstract

This document defines the Chunked, Hashed, Interleaved Resolution Protocol (CHIRP), an immutable Merkle-object layer above the Universal Hash Resolution Protocol () 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 . 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:

  1. the CHIRP URI and its relationship to a UHRP identifier;
  2. canonical root and branch node encodings;
  3. a deterministic fixed-size chunking profile;
  4. complete-host advertisement and HTTP retrieval semantics;
  5. progressive, interleaved, range-aware verification; and
  6. 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 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.

For a CHIRP root, the existing BRC-26 advertisement fields retain their ordinary meanings:

  • hash is the SHA-256 hash of the exact root-node bytes;
  • url is the HTTPS location of those exact root-node bytes;
  • expiryTime is the end of the complete-host ; and
  • contentLength is 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 , 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:

ValueEncoding
0..252one byte containing the value
253..65535fd followed by uint16 little-endian
65536..4294967295fe followed by uint32 little-endian
larger valuesff 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:

FieldEncoding
extensionTypeCompactSize
extensionLengthCompactSize
extensionValueexactly 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 , 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:

FieldSizev1 value
magic5 bytesASCII CHIRP (43 48 49 52 50)
majorVersionuint81
minorVersionuint80
nodeKinduint80 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:

FieldSizeMeaning
childKinduint80 blob, 1 branch
logicalLengthuint64logical bytes covered by this child
objectHash32 bytesSHA-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:

FieldEncodingMeaning
chunkingProfileuint16construction profile identifier
logicalLengthuint64length of the complete logical content
contentHash32 bytesSHA-256 of the complete logical content
childCountCompactSizenumber of child references
childrenchildCount * 41 bytesordered child references
extensionCountCompactSizenumber of extensions
extensionsvariableordered 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:

FieldEncodingMeaning
logicalLengthuint64logical bytes covered by the branch
childCountCompactSizenumber of child references
childrenchildCount * 41 bytesordered child references
extensionCountCompactSizenumber of extensions
extensionsvariableordered 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

TypeNameNodeCriticalityValue
1mediaTyperoot onlyadvisorylower-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:

  1. a unique profile number;
  2. a deterministic boundary algorithm;
  3. minimum and maximum blob sizes;
  4. fanout and tree-shape rules;
  5. behavior for empty and malformed input;
  6. required media parsing, if any;
  7. denial-of-service limits and fallback behavior; and
  8. 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:

  1. Initialize one streaming SHA-256 context for contentHash.
  2. Read logical content in order into blobs of exactly chunkSize bytes, except that the final non-empty blob contains the remaining bytes.
  3. Update contentHash with every source byte in order.
  4. For each blob, compute its object hash and append a blob child reference.
  5. If the reference list contains at most fanout entries, use it as the root child list.
  6. 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.
  7. Repeat step 6 until at most fanout references remain, then use that list as the root children.
  8. Serialize the root with profile 1, the total logical length, the final contentHash, 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: identity or omit Content-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 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 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:

  1. parse and canonicalize the CHIRP URI;
  2. derive the root UHRP URL and query ls_uhrp;
  3. discard expired or structurally invalid advertisements;
  4. obtain the root bytes from a candidate location and verify the root object hash before parsing any node field;
  5. validate the root encoding, understood critical extensions, and generic resource bounds;
  6. traverse verified branch nodes to determine the required blob references;
  7. retrieve each needed object from a candidate complete host;
  8. verify every complete object hash before parsing a node or releasing blob bytes;
  9. concatenate verified blobs in logical order, slicing verified boundary blobs for a requested range; and
  10. for a complete read, verify the final logical length and contentHash.

The verified 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 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:

  1. locate and hash-verify the root node;
  2. parse the graph with the limits in this document;
  3. verify every branch and blob hash;
  4. verify child counts, lengths, closure completeness, root logical length, and root contentHash, plus profile-specific canonical construction when the host understands the declared profile;
  5. establish a lease for every closure object through the promised expiry;
  6. create and submit the ordinary BRC-26 root advertisement; and
  7. 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 , 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, , 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 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, and chunkSize + 1 bytes;
  • 256 and 257 blobs;
  • multiple branch levels;
  • repeated identical blobs;
  • an optional mediaType extension;
  • 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 contentHash mismatch;
  • 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 countLevel widths, including leavesTotal branchesFinal root child logical lengths and object hashes
2562560the 256 input blob references unchanged
257257, 221073741824:65f0e211bb73fbe7c7db0a0433d1626dc1c775821fa658bbf43175e610e44fa2, 4194304:bc57e1fe69cbc6245cace01bbd61591cf81737a162707dea131b63da3f77e74e
6553765537, 257, 2259274877906944: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/chirp package with canonical codecs, streaming upload, lazy tree traversal, bounded interleaved download, range selection, caching, and verification;
  • adapters for browser Blob and ReadableStream<Uint8Array> plus Node.js AsyncIterable<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

Was this helpful?

Search Beersy

Search standards by number, title, author or topic