| 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 no node parses the payload: a custodian hashes the bytes it holds. 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. This revision defines version 2 receipts, a CBOR payload signed as a COSE_Sign1 and bound to the BPv7 bundle identity; the version 1 encoding of revision -00 is kept in Appendix A so that receipts already produced remain verifiable. Both have been run through five BPv7 paths built from four independent 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.¶
A version 2 custody receipt is a COSE_Sign1 structure (Section 4.2 of [RFC9052]), untagged, whose payload is a CBOR [RFC8949] map signed by one custodian.¶
The protected header MUST contain the algorithm parameter (label 1). Implementations MUST support EdDSA (value -8) with Ed25519 (Section 2.2 of [RFC9053], [RFC8032]). The unprotected header map is empty in this version, and no external additional authenticated data is used. The signing key is identified by the custodian EID in the payload; key distribution is out of scope (Section 9).¶
| Key | Name | Type | Meaning |
|---|---|---|---|
| 1 | prevHop | tstr | EID of the node the custodian received the bundle from |
| 2 | thisHop | tstr | EID of the custodian |
| 3 | bundle | array | Bundle identity of what was held (Section 3.3) |
| 4 | payloadHash | bstr | SHA-256 [FIPS180-4] of the payload block's block-type-specific data as held |
| 5 | receivedAt | uint | Reception time, DTN time (Section 4.2.6 of [RFC9171]) |
| 6 | forwardedAt | uint | Forwarding time, DTN time |
| 7 | subject | tstr | Optional application identifier |
custody-receipt = COSE_Sign1 ; RFC 9052, untagged
receipt-payload = {
1 => eid-text, ; prevHop
2 => eid-text, ; thisHop
3 => bundle-identity,
4 => bstr .size 32, ; payloadHash, SHA-256
5 => uint, ; receivedAt, DTN time
6 => uint, ; forwardedAt, DTN time
? 7 => tstr, ; subject
}
bundle-identity = [ eid-text, [ uint, uint ] ] ; whole bundle
/ [ eid-text, [ uint, uint ], uint, uint ] ; fragment
eid-text = tstr ; EID in text form
¶
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 156 octets.¶
The bundle identity is the source node ID and creation timestamp of the bundle and, when the custodian holds a fragment, the fragment offset and total application data unit length, all taken from the primary block (Section 4.3.1 of [RFC9171]). A custodian computes payloadHash over the payload block's block-type-specific data without interpreting it. No custodian parses the application data unit, including the first one, and the binding holds when the payload is encrypted by a Block Confidentiality Block [RFC9172], since the custodian hashes the ciphertext it holds.¶
Any verifier can check the binding with no application knowledge. An application that needs its own identifier MAY carry it in subject; verifiers that do not know the application ignore it.¶
The block is replicated in every fragment (Section 4.2). A receipt made before fragmentation names the whole bundle and therefore covers every fragment, but its payloadHash can only be checked after reassembly. A custodian that holds a fragment signs for that fragment: its bundle identity includes the fragment offset and total length, and its payloadHash covers that fragment's payload. A receipt naming a different fragment of the same bundle does not cover the fragment it is found in.¶
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 array of receipts in the order they were appended. In version 2 each element is a COSE_Sign1 array; in version 1 (Appendix A) each element is a text string. A block MUST hold receipts of one version only.¶
custody-receipt-block-data = [ + custody-receipt ] ; version 2
/ [ + tstr ] ; version 1
¶
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 derives the bundle identity and payloadHash from what it holds, signs a receipt, appends it to the array (adding the block if absent) and recomputes the block CRC. It MUST NOT alter or reorder earlier receipts, and MUST NOT append a version 2 receipt to a block of version 1 receipts.¶
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.¶
For each receipt, a verifier: verifies the COSE_Sign1 under a key it obtained independently for the EID in thisHop (failure: signature_invalid, or custodian_not_pinned when it has no key); checks that bundle equals the identity of the bundle received or, when the received bundle is a fragment, the identity of the whole bundle (failure: wrong_bundle); and, when bundle equals the identity received, checks payloadHash against the payload received (failure: payload_hash_mismatch). A whole-bundle receipt found in a fragment is reported as not hash-checked.¶
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. A verifier SHOULD bound the number of receipts it processes.¶
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 every custodian appends to it.¶
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, checked byte for byte against the uD3TN project's Python library, and implements both receipt versions. In live runs the block, with version 1 or version 2 receipts and with a payload BIB beside it, was carried byte-identical across a scheduled contact gap by uD3TN, NASA HDTN, JPL ION and Aalyria Hardy, and along one path through uD3TN and then NASA HDTN; none of them implement it. The audit mode reported the unsigned stretch through each stack that adds a Previous Node block. uD3TN adds one only when built with a compile-time option that is off by default; with it enabled, the uD3TN path was reported the same way. Deployments that rely on the audit need forwarders that emit Previous Node blocks. 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. This has been reported to the ION maintainers. The reference implementation interoperates with ION through an optional mode and uses the RFC 9173 construction by default.¶
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 a key carried with a receipt.¶
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 it names the bundle identity and payload hash of what was held.¶
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 |
Revision -00 of this document defined version 1 receipts, which remain in use. A version 1 receipt is a JSON object [RFC8259] carried in the block as its canonical form (keys sorted recursively by UTF-16 code unit, no insignificant whitespace). Its members are kind ("custody/1"), payloadId, bundleId, prevHop, thisHop, receivedAt and forwardedAt (milliseconds since 1970-01-01T00:00:00Z), claimId ("0x" and the lowercase hexadecimal SHA-256 of the canonical form without claimId, sig and signerPub), sig (base64 Ed25519 signature over the UTF-8 bytes of claimId) and signerPub (base64 DER SubjectPublicKeyInfo [RFC8410]). The origin sets payloadId and bundleId from the application data unit; later custodians copy them from the first receipt. An encoded version 1 receipt is about 530 octets.¶
This section is to be removed before publishing as an RFC.¶