| Internet-Draft | SADP | September 2026 |
| Atakora | Expires 24 March 2027 | [Page] |
This document specifies the Secure Agent Delegation Protocol (SADP), an experimental end-to-end encrypted communication layer for user-to-agent and agent-to-agent workflows. SADP defines task capsules: signed, encrypted, individually routable protocol objects that carry agent tasks, tool invocations, and results across untrusted brokers, queues, and orchestration infrastructure. SADP provides asynchronous session establishment using signed prekey bundles, per-message forward secrecy and post-compromise recovery through a Double-Ratchet-style message ratchet, an optional hybrid post-quantum key-agreement profile based on ML-KEM-768, and an authenticated opaque-broker profile with replay protection. SADP is transport agnostic and is designed to be carried over HTTP, message queues, and existing agent protocols such as A2A and MCP without requiring those systems to be trusted with plaintext task content. Capability-based delegation semantics and scoped context disclosure are specified in a companion document.¶
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 24 March 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.¶
Agentic AI systems increasingly move sensitive data and authority through long chains of intermediaries: a user's device talks to a personal agent, which delegates to specialist agents, which invoke tools, all mediated by orchestration servers, brokers, queues, model gateways, and logging layers. In deployed systems today, each of those intermediaries typically sees task plaintext. Transport security protects individual hops, but the orchestration layer itself operates on cleartext prompts, documents, tool arguments, and results. That layer includes the broker that stores and forwards messages, the gateway that logs requests, and the framework that schedules agents.¶
That default is a poor fit for legal, medical, financial, government, and enterprise workflows, and it is an increasingly poor fit for personal assistants that hold long-lived user memory. The current generation of agent interoperability protocols reflects this gap: the A2A protocol [A2A] secures hops with TLS and delegates authorization to server-side policy, and the Model Context Protocol [MCP] adopts an OAuth 2.1 resource-server model for tool access. Neither provides end-to-end confidentiality of task content across an untrusted intermediary, and neither binds a task's authorization to the encrypted object that carries it.¶
SADP addresses the confidentiality half of this problem. It defines an encrypted, signed, individually routable protocol object, the task capsule, together with the session, ratchet, and broker machinery needed to deliver capsules asynchronously between principals that may be offline, may run on multiple runtimes, and may never share a direct network path. The authorization half consists of capability grants, attenuated delegation chains, and cryptographically scoped context disclosure. It is specified in the companion document [I-D.atakora-wimse-sadp-delegation].¶
This revision is intended for experimentation and review. Neither this specification nor its reference implementation has received independent external cryptographic review, and the wire format is expected to change as interoperability tests and formal analysis identify necessary repairs. Deployments MUST treat the v0.2 profiles as experimental.¶
The acronym SADP has appeared previously in IETF work for the unrelated Scoped Address Discovery Protocol [I-D.ietf-mboned-sadp]. This document uses SADP for the Secure Agent Delegation Protocol; the two protocols are unrelated. A future publication path may choose a less ambiguous short name if that avoids confusion in IETF registries or discussion.¶
SADP is designed to provide:¶
Confidentiality of task payloads, tool arguments, context, and results from brokers, queues, gateways, logs, and unrelated runtimes.¶
Integrity and authenticity of capsule headers, payloads, and registry material.¶
Asynchronous, offline-capable session establishment using signed prekey bundles, following the design pioneered by X3DH [X3DH].¶
Per-message forward secrecy and post-compromise recovery through a message ratchet derived from the Double Ratchet algorithm [DOUBLERATCHET].¶
An optional hybrid post-quantum key-agreement profile combining X25519 [RFC7748] with ML-KEM-768 [FIPS203], following the hybrid approach of PQXDH [PQXDH].¶
Multi-runtime agent identity, treating each runtime of an agent like a device in multi-device secure messaging [SESAME].¶
An authenticated broker profile in which the broker routes and stores opaque capsule bytes, authenticates requests by digital signature, and enforces mailbox ownership without access to plaintext.¶
Transport agnosticism: SADP objects are self-contained and can be carried over HTTP, WebSocket, message queues, or as opaque payloads inside A2A messages and MCP tool calls.¶
SADP explicitly does not attempt to provide anonymous or metadata-resistant routing, protection against a compromised endpoint after decryption, model-behavior safety, or a global identity governance framework. Section 14 discusses these boundaries.¶
SADP is a layer, not a replacement. A2A [A2A] defines agent discovery, agent cards, and task exchange between agent servers; MCP [MCP] defines tool and resource access for model applications. Both can carry SADP capsules as opaque payloads. When they do, the A2A server or MCP host retains its routing and lifecycle role but loses visibility into task content; only the intended recipient principal can decrypt. Similarly, an ordinary message queue or mailbox service can act as an SADP broker with no knowledge of the protocol beyond byte storage, and a conforming SADP broker (Section 10) adds authenticated mailboxes, registry services, and receipts.¶
SADP deliberately composes established cryptographic constructions rather than inventing new ones, and this document credits those constructions at the point of use. The asynchronous prekey handshake derives from X3DH [X3DH]; the message ratchet derives from the Double Ratchet [DOUBLERATCHET]; the hybrid post-quantum profile follows PQXDH [PQXDH] and the subsequent post-quantum ratchet work [SPQR]; the multi-runtime model follows Sesame [SESAME]. The capability lineage in the companion document builds on Macaroons [MACAROONS], Biscuit [BISCUIT], and UCAN [UCAN], and its on-behalf-of semantics relate to OAuth 2.0 Token Exchange [RFC8693] and GNAP [RFC9635].¶
Adjacent protocol work exists and predates this document. [I-D.chapman-a2a-mls] applies MLS [RFC9420] to A2A messages with purpose binding; the AGNTCY SLIM project [SLIM] provides an MLS-based encrypted session layer as an agent transport; [I-D.asor-wimse-agent-delegation-chain] defines verifiable attenuated delegation tokens for agent chains without an encryption layer; [I-D.hamr-oauth-agent-delegation] defines an HTTP header profile for attenuated cross-domain agent delegation; [I-D.liu-oauth-chain-delegation] defines OAuth-based chained delegation tokens for agent identities; [I-D.li-oauth-delegated-authorization] defines constrained client-issued delegated authorization tokens; [I-D.singla-agent-identity-protocol] defines decentralized identity and capability delegation for agents; [I-D.vandoulas-aidp] defines a control-plane model for intent, authority, execution, and observation; [I-D.nelson-agent-delegation-receipts] defines user-signed, log-anchored delegation receipts; [I-D.sharif-attp] specifies an agent trust transport framework for trust scoring and action-limit enforcement; and [I-D.sharif-agent-transport-protocol] specifies an asynchronous store-and-forward agent transport. Academic proposals address authenticated delegation [SOUTH2025] and multi-agent governance [SAGA2025].¶
What SADP contributes relative to that work is object-level rather than channel-level end-to-end encryption. The unit of protection is an individually signed, individually routable task capsule that survives store-and-forward delivery through arbitrary intermediaries, composed with capability-bound authorization and payload-blind audit commitments in one protocol. In the v0.2 profile, task and context payloads are encrypted end-to-end, while capability grants and audit records are authenticated outer capsule fields that may reveal delegation metadata to brokers that store the full capsule. A future private-delegation-evidence profile is expected to move grants and detailed audit records inside an encrypted inner body while leaving only public commitments in the outer capsule. Group-channel approaches such as MLS excel when a stable group of participants shares a conversation; SADP targets the delegation topology, where each hop is a distinct pairwise relationship with distinct authority, and where a task object must remain verifiable and confidential as it is queued, retried, and re-routed.¶
This document specifies the SADP data model (Section 5, Section 6), session establishment (Section 7), the message ratchet (Section 8), single-capsule payload profiles (Section 9), the opaque broker profile (Section 10), metadata privacy (Section 11), and transport carriage (Section 12). Capability grants, delegation chains, caveats, scoped context disclosure, and the audit hash chain are specified in [I-D.atakora-wimse-sadp-delegation]; this document defines only the capsule fields that carry them.¶
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.¶
An entity that participates in SADP with a stable identifier and cryptographic key material. Principals include users, agents, runtimes, tools, workspaces, and brokers.¶
An executing instance of an agent or tool, analogous to a device in multi-device secure messaging [SESAME]. An agent identity is stable across its runtimes; each runtime has its own key material and registration.¶
The SADP protocol object: a JSON document with a cleartext routing header, protocol metadata, authenticated capability grants and audit records, and an encrypted task or context payload. In the v0.2 profile, capability and audit fields are outer authenticated fields rather than encrypted inner fields.¶
An untrusted store-and-forward service that accepts, stores, and delivers opaque capsule bytes to recipient mailboxes and hosts the runtime registry. A broker is trusted for availability, never for confidentiality.¶
The per-principal delivery queue a broker maintains.¶
The shared cryptographic state between two principals on one communication edge, established as described in Section 7 and advanced by the message ratchet.¶
Signed public key material a runtime publishes to the registry so that senders can establish sessions while the runtime is offline, following the design of [X3DH].¶
A prekey published for single use and consumed at selection time.¶
An ordered (local principal, remote principal, thread) tuple over which one session is maintained.¶
The broker-hosted directory of runtime records, signing keys, encryption keys, and prekey bundles.¶
A versioned ASCII string naming a cryptographic profile, for example "sadp-session-init-x25519-hkdf-sha256-v0.2". Every signed or encrypted SADP structure names its scheme explicitly.¶
SADP layers as follows:¶
+--------------------------------------------------------------+ | Application layer: agent tasks, tool calls, results, memory | +--------------------------------------------------------------+ | Delegation layer: capabilities, delegation chains, audit | | (specified in the companion document) | +--------------------------------------------------------------+ | Capsule layer: signed, encrypted task capsules (this doc) | +--------------------------------------------------------------+ | Session layer: prekey handshake, ratchet, AEAD (this doc) | +--------------------------------------------------------------+ | Broker/transport: opaque mailboxes over HTTP, A2A, MCP, | | queues (this doc, sections 10 and 12) | +--------------------------------------------------------------+¶
A typical exchange proceeds as follows:¶
Each runtime generates an Ed25519 [RFC8032] signing keypair, an X25519 [RFC7748] encryption keypair, optionally an ML-KEM-768 [FIPS203] keypair, and registers with a broker, proving possession of its signing key (Section 10.4).¶
A sender fetches the recipient's prekey bundle from the registry, verifies its signature and expiry, and derives a session root key using the signed session-initiation handshake (Section 7.3).¶
The sender builds a task capsule: cleartext routing header, authenticated capability grants and audit commitments as needed, and a task or context payload encrypted under the session's message ratchet (Section 8).¶
The broker authenticates the sender's request by signature (Section 10.2), performs mailbox-level authorization, stores the opaque capsule, and emits delivery events.¶
The recipient fetches the capsule, performs the validation sequence of Section 6.4, verifies capabilities and audit linkage per [I-D.atakora-wimse-sadp-delegation], decrypts the encrypted payload, and only then releases plaintext to the application.¶
Replies and delegated subtasks flow the same way on their own edges, advancing or establishing sessions as needed.¶
A principal identifier is a sequence of one or more segments separated
by "/" (U+002F). Each segment has the form kind:name, where kind
is one of:¶
user | device | agent | runtime | tool | workspace | broker | memory | context¶
and name is a nonempty token of ASCII letters, digits, "-", "_", and
".". Examples:¶
user:alice/device:phone agent:contract-reviewer/runtime:prod-a tool:pdf-parser/runtime:prod-b workspace:acme¶
The identifier without its trailing runtime:* or device:* segment
is the principal's "subject" and groups the runtimes or devices of a
single actor. Implementations MUST reject identifiers with unknown
segment kinds, empty segments, or characters outside the token
alphabet.¶
SADP structures are JSON [RFC8259] documents. Wherever this document requires canonical bytes, such as signing inputs and hash inputs, the canonical form is defined as:¶
UTF-8 encoding;¶
object member names sorted lexicographically by Unicode code point at every nesting level;¶
no duplicate member names (parsers MUST reject duplicates);¶
no insignificant whitespace;¶
array order preserved;¶
numbers restricted to integers within the IEEE-754 exact range.¶
This canonical form is a deliberately restricted profile compatible with the JSON Canonicalization Scheme [RFC8785]; implementations MAY use an RFC 8785 canonicalizer, provided the restrictions above are enforced on input.¶
Binary values are encoded as base64url without padding. Timestamps in human-facing document fields use RFC 3339 [RFC3339]; timestamps inside signing inputs and validation APIs use integer seconds since the Unix epoch, to keep canonical bytes unambiguous.¶
All hashes in this document are SHA-256 [RFC6234] and are encoded as base64url without padding unless stated otherwise.¶
Capsule headers carry a critical array naming top-level fields whose
semantics a receiver must understand. A receiver MUST reject a capsule
that lists a critical field the receiver does not implement, and MUST
ignore unknown fields that are not listed as critical. This provides
forward compatibility without silent security downgrade.¶
Every capsule carries the following cleartext header fields:¶
{
"version": "sadp/0.1",
"type": "task_create",
"capsule_id": "cap_01J00000000000000000000000",
"task_id": "task_01J00000000000000000000000",
"thread_id": "thread_01J00000000000000000000000",
"turn_id": "turn_01J00000000000000000000000",
"from": "user:alice/device:phone",
"to": "agent:contract-reviewer/runtime:prod-a",
"created_at": "2026-09-19T12:00:00Z",
"expires_at": "2026-09-19T13:00:00Z",
"critical": ["policy", "capabilities", "audit"]
}
¶
version identifies the capsule envelope format. The envelope version
is deliberately decoupled from cryptographic profile versions: the
security profiles defined in this document carry "-v0.2" scheme
identifiers inside an envelope whose format is unchanged from
"sadp/0.1". capsule_id MUST be unique per sender; receivers and
brokers use it for replay rejection and idempotency. expires_at is
mandatory; receivers MUST reject expired capsules.¶
The initial message-type registry (Section 15) contains:¶
session_init, session_repair, runtime_added, runtime_removed, task_create, task_update, task_cancel, task_result, delegate_create, delegate_result, tool_call, tool_result, memory_read_request, memory_read_result, memory_write_request, memory_write_result, receipt, policy_violation¶
Beyond the header, a capsule MAY carry:¶
policy:Cleartext delegation policy hints (allowed delegatees, allowed and forbidden tools, retention). Policy fields are advisory routing metadata; enforcement derives from capability grants ([I-D.atakora-wimse-sadp-delegation]).¶
capabilities:An array of signed capability grants, possibly chained. In v0.2 this field is an outer authenticated capsule field and may be visible to brokers that store the full capsule. Format and verification rules are specified in [I-D.atakora-wimse-sadp-delegation].¶
context_refs:References to encrypted context blobs, carrying a resource identifier, content type, and an encrypted key reference; the key material is released only against valid capabilities.¶
audit (and, for delegation, parent_audit):Hash-chain audit records binding the capsule to its delegation lineage. In v0.2 these records are outer authenticated capsule fields and may reveal lineage shape to brokers. They are specified in [I-D.atakora-wimse-sadp-delegation].¶
session_init:A session-initiation envelope (Section 7.3), present on the first capsule of an edge.¶
encrypted_payload:The encrypted task content: a ratchet message (Section 8) or a single-capsule AEAD payload (Section 9).¶
key_agreement:Present with the pre-ratchet payload profile only (Section 9).¶
Receivers MUST validate in the following order, and MUST NOT release encrypted payload plaintext to application code before all applicable steps pass:¶
Parse with duplicate-key rejection.¶
Reject unsupported envelope versions.¶
Reject malformed principal identifiers.¶
Reject unknown critical fields.¶
Reject expired capsules.¶
Reject replayed capsule identifiers and, for ratchet messages, replayed message counters.¶
Verify signatures and authentication tags.¶
Verify capability chains: holder, resource, action, task binding, expiry, delegation depth, and caveats ([I-D.atakora-wimse-sadp-delegation]).¶
Verify audit parent linkage ([I-D.atakora-wimse-sadp-delegation]).¶
Decrypt the encrypted payload.¶
Release plaintext to the application.¶
Debug or inspection modes that bypass any step MUST be impossible to confuse with production validation (for example, distinct function names and distinct result types).¶
Each registered runtime publishes a signed prekey bundle to the registry:¶
{
"principal": "agent:contract-reviewer/runtime:prod-a",
"subject": "agent:contract-reviewer",
"runtime_id": "prod-a",
"signing_public_key": "<base64url Ed25519>",
"encryption_public_key": "<base64url X25519>",
"ml_kem768_encapsulation_key": "<base64url ML-KEM-768 ek>",
"prekey_id": "prekey_agent_a_1",
"prekey_kind": "runtime_static",
"scheme": "sadp-prekey-ed25519-v0.1",
"created_at_epoch_seconds": 1789819200,
"expires_at_epoch_seconds": 1789905600,
"signature": "<base64url Ed25519>"
}
¶
The signature is computed with the runtime's Ed25519 signing key over
a domain-separated canonical encoding that binds every field above
except signature. Registries MUST verify prekey signatures and
expiry at publication and at lookup, and MUST reject tampered or
expired bundles. The ml_kem768_encapsulation_key field is OPTIONAL
and enables the hybrid profile of Section 7.6.¶
Runtimes SHOULD publish batches of signed one-time prekeys. Prekey selection prefers an unconsumed one-time prekey and falls back to the runtime-static prekey; a one-time prekey is consumed atomically at selection. Because consumption is a state change that an attacker could abuse to exhaust the supply, prekey lookup requires an authenticated request from a registered principal (Section 10.2). Runtimes can monitor inventory through the registry's inventory interface and MUST tolerate static-prekey fallback, as in [X3DH].¶
The v0.2 session-initiation profile is identified by:¶
sadp-session-init-x25519-hkdf-sha256-v0.2¶
The initiator:¶
Fetches and verifies the responder's prekey bundle.¶
Generates a fresh ephemeral X25519 keypair.¶
Computes the Diffie-Hellman shared secret between its ephemeral private key and the responder's prekey.¶
If the bundle carries an ML-KEM-768 encapsulation key and local policy selects the hybrid suite, encapsulates to it (Section 7.6).¶
Builds the session transcript (Section 7.4).¶
Signs the transcript with its Ed25519 identity signing key.¶
Derives the session root key: HKDF-SHA256 [RFC5869] with the transcript as salt, the key-agreement output as input keying material, and the fixed info string "SADP session root v0.1". (Derivation labels are versioned independently of scheme identifiers and are unchanged from the prior profile revision.)¶
The session-initiation envelope is:¶
{
"scheme": "sadp-session-init-x25519-hkdf-sha256-v0.2",
"session_id": "<base64url SHA-256 of root key>",
"initiator": "user:alice/device:phone",
"responder": "agent:contract-reviewer/runtime:prod-a",
"selected_prekey_id": "prekey_agent_a_1",
"selected_prekey_kind": "runtime_static",
"initiator_ephemeral_public_key": "<base64url X25519>",
"responder_identity_public_key": "<base64url X25519>",
"initiator_signing_public_key": "<base64url Ed25519>",
"initiator_signature": "<base64url Ed25519>",
"transcript_hash": "<base64url SHA-256>",
"algorithm_suite": "x25519-hkdf-sha256+ml-kem-768",
"pq_hybrid_scheme": "sadp-ml-kem-768-v0.1",
"pq_kem_ciphertext": "<base64url ML-KEM-768 ct>",
"repair_for_session_id": "<base64url prior session id>",
"repair_epoch": 1
}
¶
initiator_signing_public_key and initiator_signature are REQUIRED.
The PQ fields and repair fields are OPTIONAL.¶
The transcript is a text string: the line
SADP session transcript v0.2, followed by one name:value line per
field, in exactly this order:¶
scheme, initiator, responder, selected_prekey_id, selected_prekey_kind, initiator_ephemeral_public_key, responder_identity_public_key, initiator_signing_public_key, algorithm_suite, pq_hybrid_scheme, pq_kem_ciphertext, repair_for_session_id, repair_epoch¶
Absent optional fields contribute the literal value null.
transcript_hash is the SHA-256 of the transcript string.¶
Because the algorithm suite and the ML-KEM ciphertext are inside the transcript, and the transcript is signed, an attacker who strips or alters the post-quantum component, substitutes a prekey, or replaces the ephemeral key invalidates the signature. This is the downgrade binding: no shared-secret material ever appears on the wire, and no commitment to shared-secret bytes is published. (An earlier profile revision published a hash commitment to the PQ shared secret; it has been removed because it added no binding beyond the signed ciphertext and turned ML-KEM implicit rejection into an observable decapsulation oracle.)¶
initiator_signature is an Ed25519 signature over the byte string:¶
"SADP session init signature v0.2\n" || transcript¶
A responder MUST, before deriving any key material:¶
Recompute the transcript from the envelope fields and verify
transcript_hash.¶
Verify initiator_signature against
initiator_signing_public_key.¶
Bind the key to the claimed identity: a responder using a registry
MUST resolve the registered signing key for the initiator
principal and reject the envelope if it differs from
initiator_signing_public_key. Deployments without a registry
MUST surface the observed key to the application for explicit
trust establishment; silent trust-on-first-use is NOT permitted as
a library default.¶
The responder authenticates implicitly: only the holder of the private key matching the selected prekey (and, in the hybrid suite, the ML-KEM decapsulation key) can derive the root key and therefore read or produce ratchet traffic. This asymmetric design, with signature-based explicit authentication of the initiator and key-possession-based implicit authentication of the responder, parallels the deniability tradeoff discussion in [X3DH]; SADP chooses explicit initiator authentication because agent workflows require attributable task submission, and deniability is a non-goal.¶
The hybrid suite x25519-hkdf-sha256+ml-kem-768 follows the
construction pattern of PQXDH [PQXDH]: the initiator encapsulates to
the responder's ML-KEM-768 [FIPS203] encapsulation key, carries the
ciphertext in pq_kem_ciphertext, and the root-key input keying
material becomes the labeled concatenation:¶
"x25519:" || DH_shared_secret || ";pq:" || ML-KEM_shared_secret¶
Both components are fixed length, so the labeled concatenation is injective. The security goal is that an adversary must break both X25519 and ML-KEM-768 to recover the session root ("harvest-now-decrypt-later" resistance for recorded traffic). Post-quantum protection of the ratchet itself (as in [SPQR]) is out of scope for this revision and identified as future work.¶
Three session-start profiles exist: x25519 (no PQ fields) and
x25519+ml-kem-768, plus a test-only profile that injects an external
PQ secret on both sides for interoperability harnesses and MUST NOT be
enabled in production. Rules:¶
A sender MUST prefer the hybrid suite when the selected prekey bundle carries an ML-KEM encapsulation key, and MUST NOT silently downgrade; downgrade requires explicit local policy.¶
The chosen algorithm_suite is bound in the signed transcript;
responders MUST reject a suite inconsistent with the envelope's
fields.¶
Brokers are not negotiators: they store and serve prekey-bundle fields opaquely after signature validation.¶
When a runtime rotates keys or loses state, the initiator starts a
replacement session whose envelope carries repair_for_session_id
and repair_epoch, binding the replacement to the prior session
inside the signed transcript. Responders SHOULD treat repair
envelopes for unknown prior sessions as fresh sessions and MUST NOT
allow a repair to resurrect a revoked runtime.¶
The v0.2 message ratchet profile is identified by:¶
sadp-ratchet-hkdf-chacha20poly1305-v0.2¶
It is a Double-Ratchet-style construction [DOUBLERATCHET] with per-direction symmetric chains and X25519 DH ratchet steps.¶
Session state is local-only and MUST NOT be transmitted through a broker. It comprises the role (initiator or responder), send and receive chain keys, per-direction chain anchors (the chain key at counter zero of the current DH epoch), send and receive counters, DH epoch counters for both directions, the local DH keypair, the last observed remote DH public key, and a bounded skipped-message-key cache.¶
All derivations are HKDF-SHA256 [RFC5869], written below as HKDF(salt, IKM, info). BE64(x) is the 64-bit big-endian encoding of x. For a chain key CK, message counter n, and DH epoch e:¶
message_key(n) = HKDF(CK, BE64(n),
"SADP ratchet message key v0.1" || BE64(n))
next_chain_key = HKDF(CK, "next", "SADP ratchet next chain v0.1")
nonce(n) = HKDF(message_key(n), BE64(n),
"SADP ratchet nonce v0.2") (12 bytes)
¶
A DH ratchet step at epoch e derives the new chain key from the current epoch's chain anchor CA (not the position-dependent current chain key, so both peers derive the same value regardless of how many messages each has processed) and the fresh X25519 shared secret ss:¶
chain_key(e) = HKDF("SADP DH ratchet step",
CA || ss || BE64(e),
"SADP DH ratchet chain v0.1")
¶
The new chain key also becomes the new epoch's chain anchor. The AEAD is ChaCha20-Poly1305 [RFC8439]; because each message key is used for exactly one encryption, nonce uniqueness per key is structural. Implementations MUST use HKDF and HMAC from audited library implementations rather than reimplementations, and MUST zeroize chain keys, chain anchors, message keys, and DH private keys when they are discarded.¶
{
"scheme": "sadp-ratchet-hkdf-chacha20poly1305-v0.2",
"session_id": "<base64url>",
"dh_epoch": 1,
"dh_public_key": "<base64url X25519>",
"counter": 0,
"nonce": "<base64url 12 bytes>",
"ciphertext": "<base64url ciphertext || tag>"
}
¶
The AEAD associated data is:¶
caller_aad || "\n" || "sadp-ratchet-header-v0.2:" || session_id || ":" || dh_epoch || ":" || dh_public_key || ":" || counter¶
where caller_aad binds capsule metadata (at minimum thread, turn,
from, and to), and an absent dh_public_key contributes the literal
string null. Binding the ratchet header into the AAD makes header
tampering detectable at AEAD verification rather than producing
undefined state transitions.¶
A receiver MUST process an incoming ratchet message against a working copy of session state and commit that state only after the AEAD opens successfully. A message that fails authentication MUST leave session state exactly as it was, including DH-epoch state and counters. This rule exists because an earlier revision applied the DH ratchet step before verification, allowing a single forged header to permanently desynchronize a session through an unauthenticated denial of service.¶
Receivers MUST reject replayed (dh_epoch, counter) pairs.¶
Responders perform a send-side DH ratchet on their first reply. When a
receiver observes a new remote dh_public_key, it first derives and
caches message keys for the remaining expected counters of the current
receive chain, so that legitimately delayed messages from the prior
epoch remain decryptable, and then advances the receive chain to the
new epoch. Skipped keys are cached keyed by (dh_epoch, counter), with
a global bound of 64 entries; on overflow the oldest epoch is evicted
first. Cached skipped keys MUST be zeroized on use or eviction.¶
Two payload profiles exist for capsules outside a ratcheted session; both are intended for development, fixtures, and first-contact messages, and ratcheted sessions SHOULD be used for all multi-message exchanges.¶
sadp-aead-chacha20poly1305-v0.1:Direct AEAD with a caller-provisioned 32-byte key, 12-byte nonce,
and associated data binding
"sadp-capsule-v0.1:" || capsule_id || ":" || from || ":" || to.¶
sadp-pre-ratchet-x25519-hkdf-sha256-v0.1:A fresh sender-ephemeral X25519 key against the recipient's static encryption key; the AEAD key is HKDF-SHA256 over the shared secret with the capsule-metadata associated data as salt. This profile provides confidentiality from the broker but neither sender authentication at the cryptographic layer nor post-compromise recovery, and MUST NOT be used where those properties are required.¶
A conforming broker MAY route capsule bytes by recipient mailbox, store them durably, return them in insertion order, delete by capsule identifier, and inspect the minimal envelope metadata needed for mailbox behavior. A broker MUST NOT decrypt payloads, mutate capsule bytes, log payload bytes, or require access to payload keys. The mailbox interface is:¶
POST /mailboxes/{recipient}/capsules
GET /mailboxes/{recipient}/capsules
DELETE /mailboxes/{recipient}/capsules/{capsule_id}
GET /mailboxes/{recipient}/receipts
GET /mailboxes/{recipient}/events
POST /mailboxes/{recipient}/policy/send
GET /mailboxes/{recipient}/policy/send
POST /mailboxes/{recipient}/compact
¶
together with registry endpoints for runtime registration, prekey lookup, one-time prekey publication and inventory, issuer-key lookup, and revocation.¶
Authenticated broker requests carry:¶
X-SADP-Principal: agent:contract-reviewer/runtime:prod-a X-SADP-Request-Id: req_<base64url of 16 random bytes> X-SADP-Timestamp: <integer epoch seconds> X-SADP-Audience: <audience the client signed> X-SADP-Signature: <base64url Ed25519>¶
The signature scheme is sadp-request-ed25519-v0.2. The signing input
is:¶
"SADP broker request v0.2\n" || canonical_json({
"scheme": "sadp-request-ed25519-v0.2",
"principal": principal,
"method": method,
"path": path,
"request_id": request_id,
"body_sha256": SHA-256(body),
"timestamp_epoch_seconds": timestamp,
"audience": audience
})
¶
audience is the broker identifier as the client targets it: the
lowercased scheme, host, and port of the request URL with no trailing
slash. The client declares the audience it signed in the
X-SADP-Audience field; a verifier MAY instead derive candidate
audience values from the request URL it received. The broker verifies
the signature against the registered signing key of the claimed
principal and MUST reject requests whose audience does not match its
own identity (configured explicitly or derived from the request URL).
Binding the audience prevents a signed request captured at one broker
from being replayed at another.¶
This design is deliberately similar in spirit to HTTP message signatures [RFC9421]; SADP uses its own canonical-JSON signing input so that identical logic can run over non-HTTP carriage (Section 12).¶
Brokers MUST reject a request whose timestamp is more than 300 seconds
from server time in either direction, and MUST reject a request whose
request_id has been seen from the same principal within the
retention window. The replay cache MUST retain identifiers for at
least twice the timestamp window. request_id MUST be generated from
at least 16 bytes of cryptographically secure randomness; derivation
from request content is forbidden, because deterministic identifiers
turn the replay cache into a false-negative generator for legitimately
repeated operations and a false-positive oracle otherwise.¶
Runtime registration MUST prove possession of the submitted signing key: the registration record carries a prekey bundle whose signature is verified against the submitted signing key. A broker MUST apply the following state rules:¶
Creating a new principal record requires proof of possession, and,
where deployed, transport-level identity: with mutual TLS, the
verified client-certificate leaf MUST carry a URI SAN of the form
sadp-principal:<runtime-principal> matching the submitted
principal.¶
Updating an existing record, including publishing new prekeys or rotating keys, requires an authenticated request under the currently registered signing key, mutual-TLS identity, or an administrator signature. Unauthenticated re-registration of an existing principal MUST be rejected; a registry upsert that replaces a principal's keys is an account takeover.¶
A revoked runtime MUST NOT be re-registerable except by an administrator.¶
Anonymous registration modes are development conveniences and MUST be explicit opt-in configuration, never a default.¶
Recipients own an allow/deny policy over senders, optionally scoped by message type, thread, delegated-resource prefix, delegation status, and fanout count. The v0.2 default remains allow for compatibility; deployments handling sensitive workloads SHOULD configure default deny.¶
Brokers persist delivery receipts and emit mailbox events as an event stream with durable cursors, supporting both snapshot reads and live streaming. Replay-cache compaction is available to the mailbox owner but MUST enforce a minimum retained-identifier floor; a compaction request below the floor is rejected, because trimming the replay cache below recent traffic re-opens replay.¶
SADP v0.2 is an opaque-payload routing profile, not an anonymity system. A broker observes sender and recipient principals, capsule identifiers, capability grant metadata, audit records, sizes, timing, and registration metadata. It does not observe encrypted task payload or context plaintext. Deployments for which relationship, grant, resource-name, or timing metadata is sensitive should apply the staged hardening profiles, which are informative here:¶
M1 (minimization): random opaque capsule identifiers, padded payload buckets, redacted structured logs, retention limits.¶
M2 (sealed batch routing): recipient hints moved out of URL paths, batched multi-recipient delivery, timing jitter, queue-depth hiding.¶
M3 (pairwise routing identifiers): opaque per-edge mailbox identifiers replacing stable principal names on routing operations.¶
M4 (sender-hidden delivery): token-based delivery in which the sender's stable identity appears only inside the encrypted capsule.¶
M5 (traffic-analysis resistance): cover traffic, fixed-size frames, batched delivery windows, and mix-style relays.¶
Future wire revisions reserve fields for opaque routing identifiers, routing epochs, padding policy identifiers, and delivery-token commitments, authenticated as associated data so endpoints can detect broker substitution.¶
SADP objects are self-contained JSON documents; carriage requires only that bytes arrive intact.¶
The broker profile of Section 10 is itself an HTTP [RFC9110] API and is the reference transport.¶
An SADP capsule is carried as an opaque payload part inside an A2A message or task [A2A]. The A2A server routes and manages task lifecycle without payload visibility. Agent cards MAY advertise SADP support and registry location.¶
An encrypted tool invocation is carried as an SADP tool_call
capsule inside an MCP [MCP] tool call. The tool runtime, as a
distinct SADP principal, decrypts arguments and encrypts results;
the MCP host sees only capsule bytes. This requires the tool
runtime, not the host, to hold the recipient keys.¶
Any at-least-once byte transport can carry capsules; receiver-side replay rejection (Section 6.4) makes redelivery safe.¶
Protocol errors are surfaced as typed conditions, and implementations MUST distinguish at minimum: validation failures (malformed, expired, replayed, unknown-critical), authentication failures (signature, transcript, AEAD), authorization failures (mailbox ownership, send policy, capability), and availability failures (rate limit, storage). Brokers MUST NOT reflect payload bytes in error responses. Receivers MUST treat all AEAD failures identically and without detailed reason codes on the wire, to avoid oracle behavior.¶
Against a passive or active broker, queue, gateway, or logging layer, SADP provides: task and context payload confidentiality (only intended recipients hold message keys); integrity and authenticity of capsule payloads and authenticated metadata (signatures, audit commitments, and AEAD); replay rejection (capsule identifiers, ratchet counters, request nonces); expiry enforcement; forward secrecy per message and post-compromise recovery across DH ratchet steps ([DOUBLERATCHET]); downgrade resistance for the negotiated suite (signed transcript, Section 7.4); and mailbox-level authorization with replay-protected request authentication (Section 10.2).¶
SADP does not protect against: a compromised recipient after decryption; malicious model behavior over legitimately received plaintext; prompt injection contained in legitimately decrypted content; collusion among intended recipients; traffic analysis and relationship, capability, audit, and resource-name metadata in the v0.2 outer capsule fields (see Section 11); or revocation of data already disclosed. Deployments MUST NOT represent SADP as an anonymity system.¶
The v0.2 handshake authenticates the initiator explicitly by signature and the responder implicitly by key possession (Section 7.3). The binding between a signing key and a principal name is only as strong as the registry's registration proofing (Section 10.4); deployments that accept anonymous registration have no principal authentication at all, which is why anonymous modes are confined to explicit development configuration. Cross-registry identity, attestation of runtimes, and richer identity proofing are out of scope and are natural integration points for workload-identity systems such as SPIFFE [SPIFFE] and the WIMSE architecture work.¶
The v0.2 profiles use conservative, widely deployed primitives: X25519 [RFC7748], Ed25519 [RFC8032], ChaCha20-Poly1305 [RFC8439], HKDF-SHA256 [RFC5869], SHA-256 [RFC6234], and ML-KEM-768 [FIPS203]. Two cautions are in order. First, the reference implementation's ML-KEM dependency tracks a library that predates final FIPS 203 test-vector alignment in some releases; deployments MUST pin a library version with FIPS 203 final vectors. Second, neither this specification nor its reference implementation has yet received independent external cryptographic review; until such review completes, deployments should treat the protocol as experimental. The construction deliberately reuses the analyzed shapes of X3DH and the Double Ratchet to maximize the applicability of existing analysis, but reuse of shape is not a substitute for review of this composition.¶
Signed requests with timestamps and nonces bound to a broker audience limit replay-based abuse; authenticated prekey fetch prevents anonymous one-time-prekey exhaustion; idempotent capsule posting bounds duplicate-delivery amplification; and the verify-before-commit rule (Section 8) prevents forged headers from desynchronizing sessions. Brokers remain subject to ordinary volumetric denial of service and SHOULD deploy standard rate limiting per authenticated principal.¶
Compromise of a runtime's signing key allows impersonation of that runtime for new sessions and broker requests until revocation propagates; it does not retroactively decrypt recorded traffic (message confidentiality rests on the DH/KEM secrets and ratchet chains, not the signing key). Compromise of session state exposes current chain keys; the DH ratchet restores confidentiality after the next uncompromised ratchet step. Implementations MUST zeroize retired key material and SHOULD encrypt session stores at rest.¶
This document, if published, would request:¶
Registration of the media type application/sadp+json for SADP
capsules.¶
Creation of an "SADP Scheme Identifiers" registry (Specification Required) with initial entries for the scheme identifiers defined in this document and in [I-D.atakora-wimse-sadp-delegation].¶
Creation of an "SADP Message Types" registry (Specification Required) with the initial entries listed in Section 6.¶
Registration of the X-SADP-Principal, X-SADP-Request-Id,
X-SADP-Timestamp, X-SADP-Audience, and X-SADP-Signature HTTP
field names.¶
This document makes no request of IANA at Internet-Draft stage.¶
The following abbreviated flow shows a user tasking an agent through an untrusted broker. Keys and ciphertexts are truncated.¶
Runtime registration (authenticated; proof of possession via the signed prekey bundle):¶
POST /registry/runtimes
X-SADP-Principal: agent:contract-reviewer/runtime:prod-a
X-SADP-Request-Id: req_kf93hHW0aQx7Zp2Lw1VtBg
X-SADP-Timestamp: 1789819200
X-SADP-Audience: https://broker.example.com:443
X-SADP-Signature: rV8kQx3Tn...
{ "principal": "agent:contract-reviewer/runtime:prod-a",
"signing_public_key": "hzT4...",
"encryption_public_key": "9aK2...",
"ml_kem768_encapsulation_key": "pQ77...",
"prekey_id": "prekey_agent_a_1", ... , "signature": "yGd1..." }
¶
Sender fetches a prekey bundle (authenticated), initiates a session, and posts the first capsule:¶
POST /mailboxes/agent:contract-reviewer%2Fruntime:prod-a/capsules
X-SADP-Principal: user:alice/device:phone
...
{ "version": "sadp/0.1",
"type": "task_create",
"capsule_id": "cap_01J...",
"task_id": "task_01J...",
"thread_id": "thread_01J...",
"from": "user:alice/device:phone",
"to": "agent:contract-reviewer/runtime:prod-a",
"created_at": "2026-09-19T12:00:00Z",
"expires_at": "2026-09-19T13:00:00Z",
"critical": ["capabilities", "audit"],
"capabilities": [ { ...signed grant, see companion document... } ],
"audit": { ...hash-chain record, see companion document... },
"session_init": {
"scheme": "sadp-session-init-x25519-hkdf-sha256-v0.2",
"initiator": "user:alice/device:phone",
"responder": "agent:contract-reviewer/runtime:prod-a",
"initiator_ephemeral_public_key": "b7Qe...",
"initiator_signing_public_key": "Nc2w...",
"initiator_signature": "8fWq...",
"algorithm_suite": "x25519-hkdf-sha256+ml-kem-768",
"pq_kem_ciphertext": "R2m8...", ... },
"encrypted_payload": {
"scheme": "sadp-ratchet-hkdf-chacha20poly1305-v0.2",
"session_id": "tPq1...", "dh_epoch": 0,
"dh_public_key": "aW3k...", "counter": 0,
"nonce": "kQ9v...", "ciphertext": "..." } }
¶
The recipient authenticates its mailbox read, validates per
Section 6.4 (including capability-chain and audit checks
from the companion document), verifies the signed session init,
derives the root key, decrypts, executes or delegates the task, and
replies with a task_result capsule on the same thread, advancing
the ratchet.¶
| Purpose | Scheme identifier | Primitives |
|---|---|---|
| Session init | sadp-session-init-x25519-hkdf-sha256-v0.2 | X25519, HKDF-SHA256, Ed25519, SHA-256 |
| PQ hybrid | sadp-ml-kem-768-v0.1 | ML-KEM-768 |
| Ratchet | sadp-ratchet-hkdf-chacha20poly1305-v0.2 | HKDF-SHA256, ChaCha20-Poly1305, X25519 |
| Single-capsule AEAD | sadp-aead-chacha20poly1305-v0.1 | ChaCha20-Poly1305 |
| Pre-ratchet payload | sadp-pre-ratchet-x25519-hkdf-sha256-v0.1 | X25519, HKDF-SHA256, ChaCha20-Poly1305 |
| Capability grants | sadp-cap-ed25519-v0.2 | Ed25519, SHA-256 (companion document) |
| Prekey bundles | sadp-prekey-ed25519-v0.1 | Ed25519 |
| Broker requests | sadp-request-ed25519-v0.2 | Ed25519, SHA-256 |
The session and ratchet design follows the public Signal protocol specifications [X3DH] [DOUBLERATCHET] [SESAME] [PQXDH], and this document's threat framing benefited from prior work on encrypted agent messaging [I-D.chapman-a2a-mls] [SLIM] and agent delegation [I-D.asor-wimse-agent-delegation-chain] [SOUTH2025] [SAGA2025].¶