| Internet-Draft | Custody Receipt Block | October 2026 |
| Expires 21 April 2027 | [Page] |
This document defines the Custody Receipt Block, a Bundle Protocol Version 7 (BPv7) extension block that carries a chain of signed custody receipts inside the bundle those receipts describe. Each node that chooses to act as a custodian appends one receipt stating that it held the bundle, which node it received it from, and when it received and forwarded it. Nodes that do not implement the block forward it unchanged and are never required to read the payload or to sign anything. A verifier holding the custodians' public keys can check the chain after the fact and can report, rather than infer, any part of the route for which no receipt exists. The receipts are evidence for accounting and dispute resolution between operators. They are not Bundle Protocol administrative records and they do not replace or modify custody transfer signalling.¶
This note is to be removed before publishing as an RFC.¶
An open reference implementation (Apache-2.0), test vectors and live interoperability results are at https://github.com/BSVKey/dtn-custody-demo. The implementation uses block type 224 from the private and experimental range pending any assignment.¶
This Internet-Draft is submitted in full conformance with the provisions of BCP 78 and BCP 79.¶
Internet-Drafts are working documents of the Internet Engineering Task Force (IETF). Note that other groups may also distribute working documents as Internet-Drafts. The list of current Internet-Drafts is at https://datatracker.ietf.org/drafts/current/.¶
Internet-Drafts are draft documents valid for a maximum of six months and may be updated, replaced, or obsoleted by other documents at any time. It is inappropriate to use Internet-Drafts as reference material or to cite them other than as "work in progress."¶
This Internet-Draft will expire on 21 April 2027.¶
Copyright (c) 2026 IETF Trust and the persons identified as the document authors. All rights reserved.¶
This document is subject to BCP 78 and the IETF Trust's Legal Provisions Relating to IETF Documents (https://trustee.ietf.org/license-info) in effect on the date of publication of this document. Please review these documents carefully, as they describe your rights and restrictions with respect to this document. Code Components extracted from this document must include Revised BSD License text as described in Section 4.e of the Trust Legal Provisions and are provided without warranty as described in the Revised BSD License.¶
In a delay/disruption tolerant network, a bundle may be held for minutes to hours by nodes run by different operators before it reaches its destination. When something goes wrong (a bundle arrives late, is lost, or arrives altered), each operator holds only its own local logs. Those logs are not portable evidence: another party cannot verify them without trusting the operator that wrote them.¶
BPv7 [RFC9171] does not include custody transfer; reliable hop-by-hop transfer is addressed separately [I-D.ietf-dtn-bibect]. Custody transfer answers "who is responsible for retransmission now". This document addresses a different question: "who can prove they held this bundle, and when". The mechanism is a signed receipt that travels with the bundle and can be verified by any party, at any later time, without contacting the nodes that produced it.¶
The design follows the end-to-end argument [SRC84]: forwarding nodes do nothing beyond normal BPv7 block processing. Only nodes whose operators choose to act as custodians produce receipts, and after the first custodian none of them need to parse the payload. A verifier at the destination, or any later auditor, does the checking.¶
This document is published as Experimental so that the format can be evaluated in multi-operator deployments. It reflects version 1 of an implementation that has been run through four independent BPv7 implementations; see Section 7.¶
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 BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals, as shown here.¶
Version 1 receipts are JSON objects [RFC8259]. The canonical form of a receipt is its JSON serialization with the keys of every object sorted recursively by UTF-16 code unit, no insignificant whitespace, and standard JSON string escaping. Implementations MUST reject nesting deeper than 32 levels.¶
The content identifier of a receipt is the string "0x" followed by the lowercase hexadecimal SHA-256 [FIPS180-4] digest of the UTF-8 canonical form of the receipt with the members "claimId", "sig" and "signerPub" removed.¶
| Member | Type | Meaning |
|---|---|---|
| kind | string | "custody/1" |
| payloadId | string | Identifier of the application data unit the bundle belongs to, set by the origin |
| bundleId | string | Subject identifier for this bundle, set by the origin (Section 3.3) |
| prevHop | string | EID of the node this custodian received the bundle from |
| thisHop | string | EID of this custodian |
| receivedAt | integer | Reception time, milliseconds since 1970-01-01T00:00:00Z |
| forwardedAt | integer | Forwarding time, same unit |
| claimId | string | The content identifier (Section 3.1) |
| sig | string | Base64 Ed25519 [RFC8032] signature over the UTF-8 bytes of claimId |
| signerPub | string | Base64 DER SubjectPublicKeyInfo of the signing key [RFC8410] |
The member names are those of the deployed version 1 format and are retained unchanged so that existing receipts remain verifiable. A custodian SHOULD take prevHop from the Previous Node block (Section 4.4.1 of [RFC9171]) when one is present. An encoded receipt is about 530 octets.¶
The origin sets payloadId and bundleId. Every later custodian MUST copy both values from the first receipt in the block and MUST NOT need to parse the payload to do so. This is what allows a custodian to treat the application data unit as opaque, including when the payload is encrypted with a Block Confidentiality Block [RFC9172].¶
A false subject identifier cannot pass verification, because the verifier derives the expected bundleId from the payload it receives (Section 5.3). The derivation is application specific. The reference implementation carries a payload split into chunks under a Merkle root and uses the content identifier of {payloadId, index, leafHex} for the chunk carried by the bundle.¶
The block-type code is TBD1 (Section 10). Implementations of this version use 224, from the private and experimental range, until a code point is assigned.¶
The block-type-specific data is a CBOR [RFC8949] array of text strings. Each text string is the canonical form of one signed receipt, in the order the receipts were appended. Carrying the canonical form as an opaque string means signatures verify over the exact octets carried, with no re-serialization.¶
custody-receipt-block-data = [ + tstr ] ; canonical JSON receipts¶
A bundle MUST NOT contain more than one Custody Receipt Block.¶
The block MUST have "Block must be replicated in every fragment" set, and MUST have "Delete bundle if block can't be processed" and "Discard block if it can't be processed" clear. A node that does not implement this specification therefore retains the block and forwards it unchanged (Section 4.2.4 of [RFC9171]). A CRC-32C is RECOMMENDED.¶
A custodian that holds a bundle and chooses to sign for it:¶
Which bundles a node signs for is local policy (for example, by source, destination, service agreement or the presence of the block). No new naming scheme is needed.¶
A forwarder performs normal BPv7 processing. It does not read the payload, does not sign, and need not implement this specification.¶
A verifier MUST derive bundleId from the payload it received, never from the receipts. It then checks every receipt: the content identifier MUST equal claimId; the signature MUST verify under signerPub; bundleId MUST equal the derived value; and signerPub MUST equal a key the verifier obtained independently for the EID in thisHop. A receipt that fails any of these checks is a verification failure.¶
Two modes are defined:¶
A missing receipt is reported as missing and is never inferred. A receipt that is present but invalid is a failure, not a gap.¶
Because every custodian modifies the Custody Receipt Block, a Block Integrity Block [RFC9172] MUST NOT target it. Receipts carry their own signatures. A BIB that targets the payload or the primary block is unaffected by appending receipts, since appending changes neither. A Block Confidentiality Block MUST NOT target the Custody Receipt Block, because later custodians must read the first receipt.¶
Note to the RFC Editor: please remove this section before publication, as described in [RFC7942].¶
One implementation exists: dtn-custody-demo, Embryo Space Inc., Apache-2.0, JavaScript (Node.js) with no third-party dependencies, https://github.com/BSVKey/dtn-custody-demo. It includes its own BPv7 codec, which was checked byte for byte against the uD3TN project's Python library. In live runs the block was carried byte-identical across a scheduled contact gap by uD3TN, NASA HDTN, JPL ION and Aalyria Hardy, none of which implement it. The BIB-HMAC-SHA2 code [RFC9173] reproduces the RFC 9173 Appendix A vectors byte for byte. A payload BIB was carried through the same four implementations and verified before and after custodians appended receipts. Results and commands are in the repository (transport/bpv7/RESULTS.md).¶
Testing against JPL ION as a BIB acceptor and signer found that ION builds the integrity-protected plaintext for a payload target from the whole serialized block rather than from the block-type-specific data as a byte string (Section 3.7 of [RFC9173]), and omits the SHA variant parameter while signing with HMAC 256/256. The reference implementation interoperates with ION through an optional mode and uses the RFC 9173 construction by default. This is noted because the two constructions cannot verify each other's BIBs.¶
Key distribution. Receipts are only as meaningful as the binding between a custodian EID and its key. Verifiers MUST obtain keys independently of the receipts and MUST NOT trust signerPub on its own.¶
Stripping. Any forwarder can remove the block or truncate the array and recompute the CRC. This cannot be prevented by the block itself; it is detected because the removed custodians appear as gaps. A verifier MUST treat a gap as absence of evidence, not as evidence of absence.¶
Replay. A receipt copied to another bundle fails verification because bundleId is derived from the received payload.¶
False times. A custodian can sign false receivedAt and forwardedAt values. Receipts bind a custodian to its claims; they do not prove the claims are true. Inconsistent times across adjacent receipts can be flagged by the verifier.¶
Denial of service. Verification cost and block size grow linearly with the number of receipts. A verifier SHOULD bound the number of receipts it will process.¶
Privacy. Receipts reveal the custodians on a route and their timing to anyone who can read the bundle.¶
IANA is requested to assign one value (TBD1) in the "Bundle Block Types" registry of the "Bundle Protocol" registry group:¶
| Value | Description | Reference |
|---|---|---|
| TBD1 | Custody Receipt Block | This document |