@bsvkey/dtn-custody
v0.5.0
Published
Verifiable data custody and relay payment for delay/disruption-tolerant networks (BPv7), settled on BSV.
Maintainers
Readme
dtn-custody-demo

The demo above is a real run (npm test, npm run demo, make spike, make r2, make live), recorded by
demo/record.mjs and rendered by demo/render.mjs. The
closing card reads the anchor transaction live from the chain and recomputes the payload's root.
At scale, on mainnet: watch the narrated walkthrough (under six
minutes, demo/walkthrough.mp4). 4,032 satellites on real Starlink orbits, 30,502 paid custody receipts on Bitcoin BSV
mainnet, 0 rejected, all mined, and 100 of 100 sampled bundles proven from the chain alone. Every
txid is in constellation/RUNS.md.
A reference implementation of a verifiable data-custody and relay-payment layer for delay/disruption-tolerant networking (DTN), with settlement on BSV. A chunked payload crosses a multi-hop, occultation-interrupted link where every chunk is verified against a Merkle root, each relay hop leaves a signed custody receipt, the blackout produces a documented gap object, and delivery is bound to a real on-chain BSV settlement, with the manifest root anchored on chain for provenance.
It rides standard BPv7 (RFC 9171) for transport (µD3TN) and adds the custody,
verification, and payment work as an optional agent beside the bundle protocol agent,
using the same
content-addressed receipt route as @bsvkey/x402-bsv-client
and the ASM capacity-attest work. Out of scope,
by design: the physical RF layer (modulation, coding, pointing, FEC).
This is published as an open reference implementation for the BSV ecosystem: the protocol, agent library, DTN adapters, and on-chain verifiers are here under Apache-2.0 so anyone can build verifiable data-custody and relay settlement on BSV. See Open core vs. hosted service.
New here? Start with docs/QUICKSTART.md: install, the four calls, and a
runnable example (npm run quickstart) in five minutes.
Full design, milestones, and acceptance criteria: docs/BUILD-PLAN.md.
One-line scope
chunk → Merkle → BPv7 bundles → multi-hop store-and-forward with a scripted
occultation → out-of-order Merkle verification → per-hop custody receipts → gap object
across the blackout → delivery bound to a real BSV settlement → provenance anchor.
Layout
index.mjs public entry point: the whole agent API in one import
examples/ quickstart.mjs, the smallest end-to-end integration
agent/ the application agent
lib/ canonical hashing, Merkle tree, keys, receipts, gap object
source.mjs chunk + Merkle + signed manifest
relay.mjs per-hop custody receipts
dest.mjs out-of-order verify, reassemble, chain check, settlement binding
transport/
sim.mjs in-process DTN simulator (delay, reorder, occultation)
bpv7-adapter.md the real BPv7/uD3TN + netem seam (same shape as sim.mjs)
bpv7/ Custody Receipt Block: the custody chain inside the BPv7 bundle (block 224)
relief/ disaster relief: clinic reports by courier and satellite, with proof
kit/ field pilot kit: provisioning, node daemon, hub dashboard, payouts, Docker
realnet/ the scenario over real network namespaces with a real blackout
scale/ 480 clinics, 48 couriers, 16 relays, six attacks
radio/ pay over LoRa radio: offline-signed payment, gateway receipt back
ground/ ground-side pilot kit: station-signed ledgers from received data, no flight change
flight/ onboard signing in C (byte-identical to the test vectors)
constellation/ thousands of satellites: batch receipts, changing routes, attacks
mars/ JPL ION over LTP at Mars light time
voyager/ Voyager 1 and 2 at a light-day: signed DSN schedule, 160/16 bps, bit errors
ion/, mix/, hardy/ JPL ION, mixed uD3TN/HDTN and Aalyria Hardy interop runs
bench/ performance and overhead measurements
test/ the acceptance-criteria suite (node --test)
contact-plans/ per-tier link schedules with occultation windows
docs/ BUILD-PLAN.md (the full plan)
docker-compose.yml the 4-node BPv7 + netem testbed (stub)
Makefile one-command entry points
run-demo.mjs the end-to-end demo (npm run demo)Status
The custody agent is implemented and green, and it has been proven over real infrastructure:
npm testpasses the acceptance criteria;npm run demoruns the full pipeline offline (zero dependencies).M0 (
make spike/make spike-local) carries the payload over a real transport through an occultation:spike-localover real sockets,make spikeover real veths withtc netemand a real 100%-loss L2 blackout.make spike-moonrepeats it at real Earth-Moon light time (1.3 s one way on the Earth link) with a 20 s blackout. Seem0/RESULTS.md.R2 (
make r2) carries the same custody chain over real µD3TN BPv7, with the bundles held in µD3TN storage across a scheduled contact gap (real bundle custody, not TCP retransmit). Seer2/RESULTS.md.Live settlement (
make live) binds the delivery receipt to a real, on-chain- verified BSV settlement (WhatsOnChain): the receipt names the txid the verifier paid, and that txid is confirmed on chain to pay the seller. Verified against a real mainnet settlement (5942 sats, 2351+ conf). Seelive/.Provenance anchor is on chain: the manifest root of
live/anchored-payload.txtis committed in an OP_RETURN (tx733dcc21…b51207), andmake liveverifies it by default.buildAnchorTxbuilds (never broadcasts) the anchor for a new root; broadcasting is the operator's step. Seelive/RESULTS.md.Disaster relief (
relief/): with the cell network down, two field clinics send encrypted reports by motorbike courier and a solar-powered satellite terminal to a district hub. Every chunk and handoff verifies, the 70-minute power cut is a gap record, relay operators are paid only for custody they can prove, and an impostor relay is refused.node relief/run.mjs, seerelief/README.md. It is also a deployable field pilot kit (relief/kit/): key provisioning, a node daemon for clinic tablets, courier phones and satellite relays over real µD3TN, a hub dashboard and BSV relay payouts, in onedocker compose up. Over five Linux network namespaces with real loss, rate limits, delay and a real 100%-loss satellite blackout, every file arrived verified and byte-identical in all five runs (relief/realnet/). At regional scale, 480 clinics, 48 couriers and 16 relays over three days, every delivered bundle verified, payouts follow proven handoffs only, and six attacks are each contained to exactly the bundles they touch (relief/scale/).Pay over radio (
radio/): a BSV payment signed with no internet crosses a LoRa (Meshtastic) link in frames of 200 bytes or less, missing frames are asked for again, and the gateway checks it offline, broadcasts only when told to, and radios back a signed receipt. A payment is 2 frames, about 3 s on air on LongFast.node radio/run-demo.mjsover a simulated lossy link;radio/cli.mjsfor real radios; passed through real Meshtastic firmware in Docker (radio/meshtasticd/run-sim.sh), not yet on two physical radios. With no radio at all, a phone page hands the payment over by QR code or sound (radio/handoff/index.html), and an SMS gateway takes it by text and replies with the signed receipt (radio/sms/). The phone page has carried a real payment by sound: 1,000 sat signed in one wallet's page, played to a second wallet's page, then broadcast and mined in block 971842 on Bitcoin BSV mainnet (radio/RESULTS.md). A bearer faucet has also run on mainnet: coins announced as sound, heard by an iPhone, and claimed and broadcast from the phone. Seeradio/README.md.Ground-side pilot kit (
ground/) signs what a ground station received, reports every scheduled pass (a silent pass becomes a gap record), seals the ledger under one root, and corroborates two stations. Runs on existing received files; no flight change.node ground/demo.mjs.Constellation scale (
constellation/): 4,032 satellites with routes that change while bundles are in flight, one batch receipt per satellite per contact, and verification that accepts any authorized, linked path. 10,000 to 200,000 bundles all verify; under harsh outages (most cross-links failing, ground stations offline) 9,040 bundles wait in storage and still verify; five attacks are each caught for exactly the bundles they touch.node constellation/run.mjs, seeconstellation/RESULTS.md. The same runs over real orbits: public Starlink elements propagated with SGP4, links and ground visibility from geometry each minute. At 4,032 and at 9,732 satellites (the whole active fleet in the snapshot) every bundle verifies, with thousands rerouted mid-transfer.node constellation/orbits-run.mjs, seeconstellation/ORBITS-RESULTS.md. On a real BSV node: every batch receipt from the 4,032-satellite real-orbit run went through Teranode (private regtest chain) as a transaction that pays the relaying satellite and commits that receipt: 30,502 transactions, all confirmed, and 100 of 100 sampled bundles proven custodied and paid at every hop from the chain alone. Seeconstellation/TERANODE-RESULTS.md. On teratestnet, BSV's public Teranode test network, through Arcade: the same 30,502 paid receipts all mined, every merkle proof matching its block header, and 100 of 100 bundles proven (constellation/TTN-RESULTS.md). On Bitcoin BSV mainnet, through ARC: all 30,502 paid receipts and 11 anchors mined, every proof matching a header whose proof of work was verified, 100 of 100 bundles proven (constellation/MAIN-RESULTS.md). Every run, with its transaction records, is listed inconstellation/RUNS.md. With--batch=100one transaction pays 100 satellites (output i for receipt i, the receipt ids listed in one OP_RETURN): 306 transactions instead of 30,502, submitted at about 750 payments per second instead of 80, with each payment to a fresh payout address.--confirmrebuilds every block header it relies on and checks its proof of work before trusting its merkle root, andconstellation/sweep.mjscollects the satellites' payments back.Mars light time (
mars/): JPL ION over its LTP convergence layer at 4, 12.5 and 22 minutes each way (close approach, average and near-maximum Mars distance), every file arriving exactly one light time later; and a rover to relay-orbiter to Earth chain with 5% packet loss each way on a 4-minute link, repaired by LTP.make mars, seemars/RESULTS.md.Offline key management (
spec/KEY-RECORDS.md): the key-management gap BPSec leaves out of scope, closed for custody receipts. Signed key and revocation records chain to operator root keys and travel inside bundles (block 225); a verifier needs only the root keys and its own clock. Rotation, self-revocation, issuer compromise, and witnessed time, so a stolen key cannot backdate a receipt past an honest custodian. Live through uD3TN, HDTN, ION and Hardy (make bpv7-keyring-all), and onboard in C (flight/keyring.c, about 3 KB on a Cortex-M4).Voyager 1 and 2 (
voyager/): custody both ways at 23.8 and 20-hour light times, 160 bps down and 16 bps up (simulated, virtual time). Link windows come only from a DSN schedule signed as adtn.contact-plan/1record; the spacecraft transmits a light time before each track; bundles that miss a window are held and recorded in gap records checked against the signed plan. Voyager 2 can be commanded only from DSS-43, which shows: 112 h from command ready to its signed receipt at JPL, against 88 h for Voyager 1. A bit error sweep with NACK-driven resends shows each lost bundle costs about two days.make voyager,node voyager/sweep.mjs, seevoyager/RESULTS.md. Real JPL ION over LTP also passes at Voyager's data rates, 160 bps down and 16 bps up, with a 240 s light time (netem-capped):make ion-voyager, seevoyager/ION-RESULTS.md.Onboard signing in C (
flight/): the same records produced in portable C99 with TweetNaCl Ed25519, byte-identical to the published test vectors (signatures included), about 12 KB of code on an ARM Cortex-M4.make flight. The same signer also ships as a NASA cFS application that self-tests at boot and signs receipts on command over the software bus (make cfs, seeflight/cfs/).Performance (
bench/): about 30,000 custody receipts signed per second on one desktop core; custody adds about 2% at 64 KiB chunks. Seebench/RESULTS.md.
Mission-latency contact plans (Moon, Sun-Earth L1, Mars, Uranus) ship in contact-plans/
and compile to the formats NASA's DTN software loads (verified end to end with NASA HDTN: our custody payload was stored by the HDTN router across a scheduled gap and verified at the receiver, see hdtn/RESULTS.md): HDTN (NASA Glenn) JSON and JPL ION
contact and range commands (node contact-plans/compile.mjs <plan>), also verified end to end with JPL ION: ION's contact graph routing held our payload across a scheduled 20 s gap and every chunk and custody handoff verified on arrival (make ion, see ion/RESULTS.md). A mixed chain also passes: uD3TN hands the payload to NASA HDTN over TCPCLv3, HDTN stores it across a scheduled gap, and delivers it to a second uD3TN node, where every chunk and handoff verifies (make mix, see mix/RESULTS.md). It also runs on Aalyria's Hardy: our agent attaches through Hardy's gRPC application and convergence-layer APIs, Hardy's time-variant routing agent (fed a contact plan from our compiler, --hardy) holds the payload across a scheduled gap, and every chunk verifies (make hardy, see hardy/RESULTS.md). The demo runs any of
them: node run-demo.mjs --plan contact-plans/mars-relay.json.
Run it:
npm test # acceptance criteria, offline, deterministic
npm run demo # the end-to-end pipeline with readable output
npm run quickstart # the 40-line integration example (docs/QUICKSTART.md)Standards and heritage
This layer sits on top of a deployed, standardized transport; it does not reinvent DTN.
- Standard. Bundle Protocol v7 is an IETF standard: RFC 9171, with BPSec (RFC 9172), default security contexts (RFC 9173), and the TCP convergence layer (RFC 9174), updated by RFC 9713 (2025). BPv6 was RFC 5050; the architecture is RFC 4838.
- Deployed. DTN runs operationally on the International Space Station (the DTNME engine), first flew in space on UK-DMC (2008), was demonstrated deep-space by NASA JPL's DINET (Deep Impact / EPOXI), and is used by NASA's PACE mission. This reference uses µD3TN, a space-tested BPv7 implementation.
- Heritage. "Delay-tolerant networking" was coined by Kevin Fall (2002), out of Vint Cerf's Interplanetary Internet work, with ARPA/DARPA lineage.
- The gap this fills. BPv7 standardizes how bundles move, but defines no native payment, incentive, or economic model, and only limited trust verification. It does not say who paid for a delivery, or how to prove the custody chain offline. That settlement and verifiable-custody layer is what this adds, on top of the standard, not instead of it.
- Our Internet-Draft. The Custody Receipt Block is written up as an individual draft, draft-bsvkey-dtn-custody-receipt-block, with a discussion draft on accountability between administrative domains. A one-page summary with the interoperability evidence and the questions for the DTN Working Group is at dtn.bsvkey.com/ietf.
Record format specification
spec/CUSTODY-RECORDS.md specifies the record formats (canonical JSON,
content identifiers, signatures, Merkle proofs, custody, gap and delivery records, plus the
satellite-downlink and mirror-corroborated records of the sibling projects), with test
vectors that the test suite regenerates from the code on every run.
Receipts can also travel inside the BPv7 bundle itself, as the Custody Receipt Block
(extension block 224, spec section 2.1): each custodian appends its signed receipt, and
BPv7 nodes that do not know the block forward it unchanged. Plain forwarders never read
the payload or sign; only the origin reads the payload, later custodians copy the ids
from the block. Where a route crosses forwarders that did not sign, the destination
audit (auditBundleCustody) reports each unsigned stretch as an uncustodied transit gap
instead of passing or failing the whole chain. Checked against uD3TN's own
BPv7 library byte for byte, and live through all four BPv7 stacks used here, each holding
the bundle across a scheduled gap and none of them knowing the block: uD3TN, NASA HDTN,
JPL ION and Aalyria Hardy (make bpv7-block-all, see transport/bpv7/RESULTS.md). A BPSec
payload signature (RFC 9172/9173 BIB) survives every receipt appended, through all four
stacks (make bpv7-bpsec-all), and JPL ION built with real crypto accepts our BIB and
signs one our code verifies (make bpv7-bpsec-ion, bpv7-bpsec-ion-source).
Acceptance criteria (what "done" means)
- Out-of-order chunk integrity against a Merkle root; a tampered chunk is rejected.
- A custody chain across the relays verifies (three links, two relay custody hops); a forged or missing hop is detected.
- A gap object documents the occultation and reconciles on link reopen.
- Delivery is bound to a settlement (matching txid passes, unbound refuses).
- The manifest root is anchored on-chain and re-derives from the received payload.
- Runs headless in CI on every push (GitHub Actions, offline suite) plus a
--livemode with one real anchor.
Open core vs. hosted service
This repository is the open core under Apache-2.0: the protocol, the agent library
(agent/), the DTN transport adapters (transport/, m0/, r2/), the verifiers, and
the live on-chain checks (live/). Use it, fork it, build on it.
What is not in this repository and is not covered by this license: a hosted BSV settlement facilitator and relay marketplace, managed key custody, enterprise and compliance features, and the BSVKey brand. Those are operated separately. The open core settles on BSV; running a production relay-payment service on top of it is where the hosted offering lives.
Contributing and security
See CONTRIBUTING.md and SECURITY.md. The keys and
funds in any live path are yours; this reference implementation never holds them.
Copyright 2026 Embryo Space Inc. (DBA BSVKey). Licensed under the Apache License 2.0.
See LICENSE and NOTICE.

