rltp-spec

RLTP Delivery Contract

Real Life Trust Protocol — service contract: Delivery

Abstract

This document specifies how RLTP documents travel between people. The delivery service moves signed, anchor-encrypted, typed documents from one person to another — eventually, at least once, never silently lost — and tells both sides honestly what it knows: the sender its transport state, the receiver nothing the content does not prove itself, and the sender never what the receiver decided.

Messages are Trust Task documents of private, versioned types under https://real-life.org/trust-tasks/ (ToIP DTGWG Trust Tasks framework 0.4, §6.5 private specifications). This casting registers three types — encounter-bundle, delivery-ack, encounter-credential-delivery — the RLTP document profile they share, and the sealed envelope they travel in.

Status of This Document

This is an Editor’s Draft with no standing beyond its own argument. It is developed together with the Encounter Layer through an adversarial convergence process — each casting is reviewed in full by an independent adversarial reviewer and recast, never patched. The current casting is the seventeenth; its review round returned no findings, and the pair was judged blocker-free and compatibly implementable. The document will keep changing as implementation experience accumulates; known open questions are collected in Section 12. Feedback is welcome via the issues of the publication repository (github.com/real-life-org/rltp-spec).

1. Introduction (informative)

1.1 Essence and principles

1.2 The user experience this serves (informative)

After A scans and confirms, A’s app shows a waiting state; the arrival acknowledgement dissolves it (“nothing more to do on your side”), and its absence within ack-wait flips A’s screen to the optical presentation of the sent card — the same enactment on another carrier. When B’s counter-credential later arrives, A sees the relation confirmed; B’s own view becomes mutual only when A’s credential reaches B (Encounter 4.2 — every view is local). Section 8 gives both state machines; a lost acknowledgement after B’s commit reconciles through redelivery and duplicate-known, never through a second enactment (6.3).

2. Conventions and Terminology

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “NOT RECOMMENDED”, “MAY”, and “OPTIONAL” are to be interpreted as described in BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all capitals.

The interim securing profile of Encounter 2.3 applies (did:key anchors, Ed25519/X25519 as Multikeys with decoded multicodec verification, eddsa-jcs-2022 embedded proofs, JCS, SHA-256, multibase, RFC3339-UTC-Z timestamps).

Document — a Trust Task document conforming to the RLTP document profile (Section 3). Document digest — the multibase-encoded multihash (Encounter 2.3: emit u, accept u/z, SHA-256) over JCS(document) of the plaintext document; the document’s identity for idempotency and acknowledgement reference, and format-identical to DTGWG digestMultibase values. Sealed envelope — the encrypted form in which a document travels (Section 5). Disposition — the receiver’s classification of a processed envelope (Section 6).

Term Fragment
Document digest #DocumentDigest
Sealed envelope #SealedEnvelope
Disposition #Disposition
Delivery acknowledgement #DeliveryAck

3. The RLTP Document Profile

RLTP delivery documents are Trust Task documents [TT], target framework version 0.4, under the private task-type rules of TT §6.5. The profile — normative wire form schemas/rltp-delivery-document.schema.json — requires:

Offline schema rule: implementations MUST pre-register every schema of this contract by its $id and MUST NOT resolve any $ref over the network. The shipped schemas/ directory is the complete closure; a validator that cannot resolve a reference from its local registry treats the document as malformed.

Unknown type → the document MUST be rejected with disposition failed(unknown-type); a document that would have an effect is never silently ignored.

4. Registered Task Types

Each type below is a private Trust Task specification with: Type URI, target framework 0.4, payload schema (shipped), proof declaration (per Section 3), and the consistency rules stated here.

4.1 encounter-bundle/0.1

The transmission of the one-scan ceremony (Encounter 5.8): the scanner’s sent card and step credential.

4.2 delivery-ack/0.1

The arrival acknowledgement (DO-1).

4.3 encounter-credential-delivery/0.1

Post-enactment delivery of a step credential: the counter-step of encounter-scan ("counter"), or a standalone credential delivery outside any bundle thread ("deliver").

5. The Sealed Envelope

A document travels sealed to its recipient:

seal = { "rkid":       <recipient key-agreement key, Multikey z6LS…>,
         "epk":        <ephemeral X25519 public key, base64url, 32 bytes>,
         "nonce":      <96-bit nonce, base64url>,
         "ciphertext": <AES-256-GCM ciphertext || 128-bit tag, base64url> }

Normative construction, exactly one way:

A shipped test vector (vectors/seal.json) fixes recipient key, ephemeral key, nonce, plaintext, ciphertext, and document digest; implementations MUST reproduce it byte-for-byte. The vector’s plaintext document is a seal-only sample (type …/seal-vector-sample/0.1, never a wire type): it exercises this section’s construction, not the document profile, and MUST NOT be processed as a delivery document.

No channel authentication is required or assumed: confidentiality comes from the seal, authenticity from proofs and signed payloads bound to anchors by the Layer-1 binding rule.

6. Delivery Promises

6.1 Sender: the status trias

State Meaning
accepted the service durably buffered it; delivery is owed
delivered a valid delivery-ack (4.2 consistency) referencing its digest arrived
failed(reason) the service gave up; reason from the closed set below

Sender failure reasons (closed set): unroutable · oversize · expired-by-adapter-policy (the adapter’s declared give-up bound) · rejected-by-receiver(<receiver reason>) where an adapter conveys one. A late valid acknowledgement after failed transitions the status to delivered; implementations MUST surface the transition. No other states exist — in particular, no acceptance state.

6.2 Receiver: dispositions, in mandatory order

Every received envelope is evaluated in this order, and the first failing stage names the disposition:

  1. size bound (5) — else failed(oversize);
  2. envelope schema, base64url canonicity (lengths mod 4 ≠ 1, zero trailing bits) and rkid known — live or tombstoned (Section 5) — else failed(malformed);
  3. decryption (all-zero check, tag) — else failed(decryption-failed);
  4. document parse + digest computation — a plaintext that does not parse as JSON or defeats JCS/digest computation is failed(malformed); duplicate check against the completed-effect cache — the cache contains ONLY digests whose stage 9 completed successfully; a digest previously rejected at any stage is NOT in it and is re-evaluated in full. Duplicate → duplicate-known: the prior outcome applies idempotently and the stored acknowledgement of the completed effect MUST be re-sent, byte-identical (it exists by construction for every acknowledging type: the ack document is retained inside the effect’s transaction, stage 9; a crash between commit and transmission would otherwise lose it permanently — for a terminal document, 4.2, no acknowledgement exists and nothing is re-sent). Evaluation ends;
  5. document-profile schema + recipient = own anchor — else failed(malformed) / failed(wrong-recipient);
  6. type known — else failed(unknown-type);
  7. payload schema — else failed(malformed);
  8. type consistency rules and pre-lock acceptance checks (Section 4, incl. the issuance window for bundles) — else failed(validation-failed) / failed(stale-issuance); one exception: a bundle whose bound challenge provisionally resolves unknown (the resolution itself has already latched any held aged value — Encounter 5.3) skips the rest of this stage and enters stage 9, where the authoritative resolution decides: still unknown → dispose; any other state → release and re-enter at stage 4 (4.1 check 5);
  9. gates and effect, serialized per lock set: stage 9 is a critical section whose lock set is the document digest and, for bundles, additionally the credential’s bound challenge (the record key). The lock protocol, normatively: the full lock set is acquired atomically, as one acquisition — never one key after the other; an evaluation whose set overlaps a held set holds nothing while waiting; and when the way is free it does not resume — it re-enters at stage 4, rechecking the completed-effect cache and re-selecting its branch on the state actually found. One rule covers both keys; there is no ordering to get wrong and no lock held across the re-entry. The record-key namespace and lifetime are shared with the optical input of Encounter 5.8/5.5 — the same lock, not an equivalent one — so record creation, branch selection, and credential uniqueness are serialized with every competing trigger. The completed-effect cache therefore never holds provisional state: a waiter either finds a completed entry (→ duplicate-known, mandatory re-ack where one is retained — for a terminal document, 4.2, there is nothing to re-send) or finds nothing and proceeds as a fresh evaluation. Inside the critical section: the authoritative resolution and effects of 4.1 — open → record-creating behind the future check (else failed(gate-future)), recorded → the record decides (foreign counterparty: failed(consumed-challenge); else record-aware), unknownfailed(validation-failed) — each effect committed as one durable transaction (record where created, accepted material, cache entry, the acknowledgement document itself where the type acknowledges, retained together with the cache entry per 4.2; 4.1) → unique.

Validate, then consume: no stage before 9 consumes single-use material, and stage 9 consumes only after 1–8 passed in full — for a bundle that includes the issuance window, so nothing that stage 9 records can subsequently fail (the poisoning rule, closed).

incomplete(missing) exists in the taxonomy for types with declared dependencies. No type of this casting declares any; a future type that does MUST define its closed missing vocabulary, its re-evaluation trigger, and its retention bound in its own specification.

6.3 At-least-once and reconciliation

Adapters MAY deliver any envelope multiple times; receivers converge via stage 4 (duplicate-known). A lost acknowledgement is indistinguishable from a lost document to the sender; the sender’s remedy is the carrier switch of Encounter 5.8 — the same enactment continues on the optical leg, and the late bundle is accepted via the record (4.1). A fresh enactment arises only when the optical leg’s boundTo no longer resolves — the gate-expired outcome of Encounter 5.8; the resulting parallel enactments are reconciled by Encounter 0.19, 4.2 and 5.8: both are valid, a late counter-credential to the first is accepted, and enactment multiplicity never multiplies edges (one edge per anchor pair). This contract adds nothing to those rules and relies on them.

7. Timing

Parameter Default Meaning
ack-wait PT30S RECOMMENDED sender-side wait before automatically presenting the optical leg — the carrier switch within the same enactment (Encounter 5.8); presentation is permitted at any moment, and conformance never depends on when the switch happens; cancelled by an arriving acknowledgement or counter-credential
key-retention max(P90D, longest adapter give-up horizon) minimum retention of a key-agreement private key after it last appeared in any card (Section 5)

ack-wait is a UX pacing parameter, not a validity rule: an acknowledgement arriving after it is still valid (6.1 late transition), and the record-aware effect (4.1) makes any switch timing safe. Delivery time itself is unbounded; no rule in this contract references arrival time for validity.

8. State Machines (informative)

Sender (A, one-scan): scanning → confirmed/sent (waiting animation) → delivered ("nothing more to do") → [counter-credential accepted] → relation confirmed — with waiting --ack-wait elapsed--> optical presentation (show the sent card as QR), and failed --late valid ack--> delivered.

Receiver (B, one-scan): envelope → staged evaluation (6.2) → recorded + auto-ack → prompt: "verify A back?" → [human confirms] → counter-step issued → relation confirmed — any rejection before the final stage (6.2 stage 9) sends no ack and consumes nothing; B’s prompt is C4, never automated.

9. Security Considerations

10. Privacy Considerations

11. Conformance

12. Open Issues

Appendix A (informative): mapping to the current implementation

This contract Today (Sync 001/003, wot-core)
Document (Trust Task, profile §3) MessageEnvelope / DIDComm plaintext + MessageType union
threadId thid/pthid
Sealed envelope (5) ECIES body {epk, nonce, ciphertext}, info wot/ecies/v1rltp/v1/seal, plus new rkid
Status trias (6.1) RelayReceipt accepted/delivered/failed
delivery-ack (4.2) attestation-receipt (Häkchen 2) — semantics move to arrival-at-recording, and the ack gains a proof
Dispositions (6.2) K1 InboxAck* taxonomy, now with mandatory order
Transport queue ack relay {type:'ack'} — below the port line, unspecified here

References

[RFC2119] · [RFC8174] BCP 14 · [RFC3339] · [RFC8785] JCS · [RFC5869] HKDF · [TT] ToIP DTGWG Trust Tasks framework specification 0.4 (§4.8.2, §4.11.1, §6.1, §6.3, §6.5, §7.2–7.3) · RLTP Encounter Layer 0.19 (port §11, binding 5.4, ceremony 5.8, state model 5.3, merge rule 4.2) · Sync 001/003 (superseded transport specs, Appendix A).