Agent Federation Protocol (AFP) Specification
Verifiable, metadata-private federation between tenants
Abstract
The Agent Federation Protocol (AFP) is the protocol layer for verifiable interaction between tenants. It defines how two tenants coordinate work across organizational boundaries, how an instance proves operational health to an operator without disclosing its work, and how an instance's identity survives a change of deployment topology or operator.
AFP v0.3.0 is a deliberate narrowing of AFP v0.2.0. The v0.2.0 draft was scoped as an open-marketplace federation protocol — third-party agent registration, capability-discovery feeds, task delegation as a central wire feature, observation contribution, reputation accumulation, and a federated-to-native trust ladder. ADAMAS's product scope has since narrowed to a platform for life-sciences work run by named domain entities that are themselves ADAMAS multi-agent systems. There are no third-party agents to integrate and no marketplace to police. Most of v0.2.0's protocol surface addressed problems that no longer exist; v0.3.0 retires it.
What remains is smaller, sharper, and built on three foundations that did not exist in v0.2.0: KERI-aligned self-certifying identity, ZKA and ZKC as substrate (with ZKM as an optional authorization peer), and a structural operator-blindness guarantee — the operator of any AFP transport is cryptographically and topologically incapable of observing tenant work content, and incapable of reconstructing the graph of which tenants coordinate with which.
This specification targets ZKA v0.9.0-draft, ZKC v0.4.1-draft, and AFP-KDC v1.0.2. ZKA resolves the AFP §12.7 bundle dependency by carrying ZKA/Coord work-attestation and profile-aware ZKC proofs inside zka:bundle:v1. ZKC v0.4.1-draft supports anonymous, scoped, and explicitly-consented linkable presentation profiles; AFP selects among them per flow and binds every accepted proof to the KERI-authenticated presenter, credential-subject AID, APR signer, and AFP session context (§5.9). AFP-KDC v1.0.2 retains the v1.0.1 field-domain correction and additionally pins the complete Poseidon2 permutation, sponge, constants digest, and shared ZKA/ZKC/ZKM conformance vectors without changing any derived value (Appendix A). AFP inherits the six Freedom Safeguards defined normatively in ZKA §1.6 — including Safeguard 6 (Verifier Accountability), which is load-bearing for unknown-counterparty federation (ADR-0004).
The machine-checked composition boundary is epoch zka:protocol-family:2026-08-15.2 in ZKA's canonical protocol-family/ package and migration note. AFP vendors a commit-locked snapshot and rejects unknown, unmatched, or ambiguous version/profile combinations.
Version 0.3.0 Changes (relative to v0.2.0-draft):
- Scope narrowed from open-marketplace federation to instance-level federation. The agent-manifest schema, capability-discovery feed, task-delegation wire protocol, external observation contribution, individual-agent reputation, the federated→integrated→native trust ladder, and the HTTP bridge are all removed (§1.4).
- KERI-aligned self-certifying identity adopted as load-bearing; hash-derived identifiers and the flat
nkey_historymodel are retired (§4). - ZKA and ZKC reframed as substrate: tenant ↔︎ tenant rides on ZKA, tenant ↔︎ operator rides on ZKC (§5, §6).
- Operator-blindness elevated to a structural protocol invariant (§3, §7). The SaaS deployment model of v0.2.0 — operator-run transport with transport-observed per-tenant telemetry — is retired; AFP v0.3.0 assumes a source-available ADAMAS with no operator on the coordination path.
- Telemetry redesigned: no load-bearing dependency on per-tenant transport observation; all per-tenant operator knowledge is ZKC-proved (§7).
- Metadata privacy added as an explicit protocol surface (§3.4, §8): the coordination relationship graph is protected, not merely the payloads. §8 specifies the AFP Federation Mixnet — a self-contained, consortium-operated Loopix-family mix network with mandatory cover traffic — as the concrete mechanism delivering OB-2 against a global passive link observer.
- Key Derivation Core (KDC) v1.0.0 incorporated as Appendix A: the master-seed derivation, Poseidon2/BN254 domain-tag scheme, domain-tag registry, and per-entity HKDF sub-derivation, previously inline in ZKA §2.2 and ZKC §2.1, are consolidated into a single self-contained, independently versioned module that ZKA, ZKC, and AFP all normatively reference (§2.2, §4.3, Appendix A).
Version 0.3.1-draft Changes (relative to v0.3.0), per ADR-0003:
- Bilateral Session Core consolidated into AFP (§12). The session machinery that PRP v0.2 and VXP v0.2 each independently re-derived — bilateral session establishment, lifecycle and timeout, identity binding, the NATS subject tree, the dispute/arbiter model, the bilateral commitment + optional-anchor primitive, the session-envelope wire format, and the application of the Freedom Safeguards at the session layer — is lifted into a single AFP-owned section group. Profiles reference it rather than re-specify it.
- The Profile concept defined (§13). An AFP profile is a named behavior built over the Bilateral Session Core. PRP (pairwise reconciliation) and VXP (value exchange) are named as the two v0.2 profiles; AFP's own §5 tenant↔︎tenant work exchange is the base behavior. A lightweight profile-registration mechanism is specified. This realizes ADR-0003's "3 open protocols + 2 AFP profiles" framing: PRP and VXP are not peer protocols but profiles over AFP.
- NATS namespace ownership resolved (§12.4). AFP owns the subject tree; every profile lives under
afp.{tenant}.{profile}.*(e.g.afp.{tenant}.prp.*,afp.{tenant}.vxp.*). This resolves the flag VXP v0.2 raised:vxp.*is a profile subtree under AFP, not a standalone namespace. - Federation bundle / CoordBundle home resolved (§12.7). ZKA owns the cryptographic bundle (
zka:bundle:v1, carrying ZKA/Coord work-attestation and ZKC compliance proofs); AFP owns the session envelope (afp:envelope:v1) that carries a ZKA bundle across a tenant boundary within a session and binds it to the session context. VXP'svxp:coordbundle:v1and any PRP bundle wrapper are replaced by AFP-envelope + ZKA-bundle. - A companion
PROFILE_MIGRATION_NOTE.mdmaps, per profile, which PRP/VXP v0.2 sections are now superseded by a reference to a §12/§13 core section, so the follow-up trim of PRP/VXP is mechanical. This revision does not edit the PRP or VXP specs.
Version 0.3.5-draft Changes (relative to v0.3.4-draft):
- Canonical filename established. The live normative AFP specification is now
docs/afp_specification.md; versioning lives in this header and the Document History table, not the filename. - ZKA-Aztec v5 status clarified. ZKA-Aztec is v5/AZUP-2 migrated and sandbox-accepted in ZKA.dev, but AFP's anchor conformance baseline remains Ethereum L2 via APL unless a deployment explicitly integrates Aztec as an APL rail. No AFP session, envelope, identity, routing, or safeguard semantics change.
Version 0.3.6-draft Changes (relative to v0.3.5-draft):
- ZKM/KDC and §14 composition alignment. ZKM is added as an optional
principal-issued authorization peer and as a consumer of the KDC seed model.
AFP §14 bilateral envelopes and ZKM unilateral mandates are independent
constraints: neither subsumes or mutates the other. The existing mismatch
between KDC §A.4's
encodeTagrequirement and deployed ZKA/ZKM operational separator derivation is disclosed as unresolved rather than silently normalized. No AFP envelope, session, profile, KDC constant, or test vector changes. - USD-obligation/WETH-settlement profile added (§15). The experimental
payv1.0 profile lets two principals bilaterally authenticate a USD-minor-unit obligation and an exact WETH-wei settlement amount for an immutable Base ZKA pool. AFP owns quote, expiry, replay, and receipt semantics; ZKA owns private settlement and pool state; APL owns route execution and finality observation. - Settlement boundaries made explicit. The launch profile is Base WETH9 in a separately deployed immutable single-asset pool with
zka:asset-risk:s3-hard:v1. Any additional asset requires a separate pool and explicit risk profile. A native stable unit is an independent future protocol, not an AFPpayor ZKA/Pay prerequisite. - Safeguard inheritance corrected. The stale §12.8/§14.8 five-safeguard mappings now include all six safeguards, including Safeguard 6 (Verifier Accountability).
Version 0.3.7-draft Changes (relative to v0.3.6-draft):
- ZKC v0.4.0 presentation profiles adopted (§5.9). Telemetry and known-counterparty bilateral flows default to anonymous presentations and permit a scoped→anonymous downgrade when continuity is unnecessary. Introduction and open-federation flows require scoped continuity; linkable v1 remains available only with explicit holder consent. Every accepted presentation binds the KERI presenter AID, ZKC credential-subject AID, authenticated APR signer, AFP session context, session-policy-derived scope, and active profile.
- Rotation and receipts minimize handles (§4.6, §5.9). Anonymous artifacts expose no holder-derived handle; scoped artifacts expose only
presentationPseudonymandscopeId; linkable artifacts expose only the consented legacyholderCommitment. ZKC v2 rebind is used for scoped continuity, while the frozen v1 path remains the explicitly linkable compatibility profile. ZKA bundle carriage remains explicitlyzka:bundle:v1. - AFP-KDC v1.0.1 reconciles domain tags (Appendix A). Circuit field tags now unambiguously derive as
LEInteger(Blake2b-512("ZKA-Domain-" || tag)) mod BN254.Fr; fixed 32-byte tags such as the HKDF salt remain a separate byte-encoding class. KDC owns only the six derivation tag strings; ZKA, ZKC, and ZKM own their operational tag strings.docs/kdc_v1_0_1_domain_registry.jsonKAT-pins and collision-checks the complete 27-tag family. This is a compatibility-only patch: no existing constant or cryptographic artifact changes.
Version 0.3.8-draft Changes (relative to v0.3.7-draft):
- Pluggable instance-attestation roots fixed (§6.3.1).
afp.telemetry.instance_attestation.v2replaces v1's hard-wired publisher AID with a closed root-class taxonomy and verifier-selected root authority:root:build-provenance,root:tee-attestation, orroot:publisher-manifest. The v2 public statement, private witness, circuit branches, cadence, failure signals, and root-specific verification obligations are normative rather than Draft. - Publisher manifests reclassified without changing v1. A platform publisher signing its own build manifest is
root:publisher-manifestand self-attested at the platform layer. Such a v2 proof carries a mandatory §6.4 public-sample binding and the §3.5 honest-disclosure duty. The completeafp.telemetry.instance_attestation.v1catalog row and its wire/circuit semantics remain byte-identical for compatibility. - Counterparty use moved to v2 (§14.2, §14.9). Introduction policy now names accepted root classes and root authorities and consumes the vendor-neutral v2 predicate. AFP references the separately versioned ZKC machine build/runtime credential schema; it does not redefine that credential or expose raw provenance bundles or TEE quotes.
Version 0.3.9-draft Changes (relative to v0.3.8-draft):
- AFP-KDC v1.0.2 parameterization (§A.3). Pins BN254
Fr, Poseidon2 width 4, rate 3, capacity 1,x^5, 8 full and 56 partial rounds, both matrices, all round constants, the length IV, exact absorption/trailing-permutation rule, and output lane. The canonical 8,320-byte constant table is SHA-256 committed. - One conformance authority.
docs/kdc_v1_0_2_conformance_vectors.jsoncovers raw sponge edge arities plus boundary and asymmetric 32-byte seeds across every KDC branch and defines the required execution contract for consumer gates. Standard Noir, Rust, ZKC, ZKM, Aztec Nargo, and the applicable Solidity H2 surface execute the shared values in CI. - Cross-repository drift gate. A scheduled and change-triggered workflow compares copied parameter tables, runs sibling runtimes, verifies the upstream Poseidon source identity, and invokes circuit/vector/artifact binding validators so parameter, vector, circuit-ID, and VK drift fails closed. No derived field, proof, VK, hash, or prior vector changes.
Version 0.3.9-draft (ADR-0005 tenant definition) Changes (relative to v0.3.9-draft), per ADR-0005:
- Tenant definition generalized (§1.6, §2.1). A
tenantis a runtime that federates as a principal under its own tenant root AID and MUST satisfy the tenant conformance profile — no longer "an ADAMAS instance." ADAMAS becomes the reference implementation, holding no protocol privilege. - Tenant conformance profile consolidated (new §1.5.1). Seven tenant-side obligations — KERI identity custody (§4), operator blindness and topology (§3, §7.1, §7.3), routing participation including constant-rate emission (§8), the bilateral session core (§12), operator telemetry (§6), the six Freedom Safeguards (§9), and the tenant-internal obligations (§5.6, §5.7) — gathered in one place. Every item restates a requirement already normative elsewhere; the profile introduces none and adds no wire surface.
- "ADAMAS-internal" is now "tenant-internal" (§1.1, §1.2, §5.2, §5.6, §5.7). Where the prior text implicitly delegated a duty to ADAMAS, the duty is restated as a normative tenant obligation — §5.7's containment, capability-scoping, and observability of a tenant's own entities most notably.
- Trust level reads verified conforming instance (§1.2, §12.1, §12.5, §13.1). Naming an attestation root authority is tracked separately and is not in this revision.
- §7.1 generalized to "Auditable implementation." OB-relevant behavior of any conforming implementation MUST be auditable; an implementation that cannot support inspection MUST NOT claim OB and falls under §3.5 honest disclosure. Source availability is the canonical means, and the reference implementation's Business Source License is the worked example rather than the definition.
- Retirement guard restated (§1.2). Generalizing who may implement a tenant confers no authority to reopen third-party agent registration, discovery or marketplace surface, portable positive reputation, a trust ladder, or the HTTP bridge. The §14 introduction tier's opt-in willingness-to-federate directory and the §14.7 negative-history record are expressly unaffected.
- New open item (§10). The formal multi-vendor conformance program — tenant-profile vectors as a normative gate, governance, certification — is deferred until a second serious implementer commits; until then conformance is asserted against the spec text and judged bilaterally.
- No wire change: no new or renamed message class, predicate ID, public input, envelope revision, or NATS subject.
afp.telemetry.instance_attestation.v1remains frozen.
Version 0.3.9-draft (ADR-0005 authority naming) Changes (relative to v0.3.9-draft (ADR-0005 tenant definition)), per ADR-0005 item 4:
- Trust level qualified (§1.2). The single trust level reads verified conforming instance under a verifier-accepted attestation authority (§6.3.1). §1.2 is the canonical statement; §12.1, §12.5, and §13.1 cross-reference it rather than restate it, and none of them makes instance attestation a precondition of a session (§1.5.1).
- An organizational attestation root authority is the accountable party, not a product (§6.3.1). For an organizational authority — the case
root:publisher-manifestpresents —root_authority_identifierMUST resolve to the party accountable for the attestation, its organizational root AID, never to a product, brand, build line, or distribution.root:tee-attestationandroot:build-provenancekeep the branch-specific meanings already fixed for their identifiers (a hardware vendor's product root policy; a transparency log's checkpoint signer). - Product-scoped signing MAY be delegated and KEL-anchored (§6.3.1). An organizational authority MAY sign manifests under a delegated AID whose inception is anchored in the authority's KEL, exactly as an entity is delegated from a tenant root (§4.2);
root_material_digestcommits to that delegated signing-key state, and rotation, compromise handling, and succession run as published events in the signing AID's KEL, each anchored by a seal in the authority's KEL (§4.2, §4.4, §4.6). - Delegation resolution is a verifier duty, not a circuit obligation (§6.3.1). The verifier resolves the delegation chain from the signing AID to the named authority root AID out of circuit, before acceptance, mirroring the existing rule for a delegated verifier AID, and independently recomputes
root_material_digestfrom the resolved key state. The circuit continues to bind only the leaf signing-key state committed byroot_material_digest; no branch obligation and no verification key changes. The attest-own-builds-only property rests on that recomputation, not on the circuit alone, and the text says so. - Stale or revoked authority is dischargeable without a vendor document (§6.3.1). For a KERI-rooted authority the determination is made from that authority's own KEL and the KELs it anchors (§4.2, §4.4, §4.6);
root:build-provenanceandroot:tee-attestationkeep their existing verifier-pinned log-policy and vendor-root material. Authority-KEL events are checked against a witness set and threshold pinned in the accepted trust material (§4.5). In no class is an authority-published registry or feed on the normative path. root:publisher-manifestgains an explicit scope and a split disclosure duty (§6.3.1). An authority under this class attests only manifests it itself signs; the deployment's §3.5 disclosure duty is unchanged, and the authority's half is stated as a verifier acceptance condition — a verifier MUST NOT accept an authority under this class unless the trust material it accepted characterizes the attestation as platform-layer self-attestation — so no MUST is placed on a party that is neither a principal nor a conformance subject (§1.5, §2.1, §3.5).- Operational-status disclosure is path-independent and normative (§6.3.1). Where a named authority's KEL and trust material do not yet exist, a deployment selecting it MUST disclose the path as spec-available but not operational under §3.5, on the telemetry, bilateral, and open-federation paths alike; §14.9 applies additionally where the introduction tier runs. The duty lives in the normative body, not in the non-normative note.
- A new artifact
kindfixes an existing preimage (§6.3.1).afp:publisher-key-state:v1names the JCS-encoded signing-key-state record whose digestroot_material_digestcarries forroot:publisher-manifest, with every field encoding pinned: KEL SAIDs, keys, and pre-rotation commitments verbatim as the KEL lists them (never re-encoded under thesha256:*_digestrule), andkt/ntcopied verbatim, including KERI's nested weighted-threshold form. This determines the preimage of an existing public input; it adds no public input, no wire field, and no new public statement. - The set of authorities is open (§1.2, §5.5, §5.8, §6.3.1, §9, §10, §12.8). Extending the root-class taxonomy requires a revision to §6.3.1; standing up a new root authority does not. Any organization MAY stand as an authority on identical terms, by publishing trust material a verifier can accept — never by registration, permission, notification, grant, or amendment to this specification. AFP publishes no authority registry, list, or feed and defines no procedure for admitting, ranking, suspending, or removing an authority.
- An attestation root authority is not a principal (§2.1). AFP still recognizes exactly three kinds of principal. An authority is an AID whose trust material a verifier may elect to accept; it addresses no AFP message, opens no session, and holds no protocol privilege. Where the same legal entity is also an operator (§8.6), the two roles are independent.
- Shipped defaults are product configuration, not spec defaults (§6.3.1, §9, §12.8, §14.4). The obligation now has a normative home in §6.3.1: an implementation MAY ship a default verification policy naming authorities it recommends, and such an entry MUST be visible to, editable by, and removable by the deployment operating it, MUST NOT be required for conformance, and MUST NOT be hardcoded beyond removal. It is not a default of this specification. A pair applies to an interaction only when signed into that interaction's policy (§5.5, §14.4). The §9 and §12.8 rows remain the cross-references they claim to be.
- Pyramidal Inc. named non-normatively as the first authority (§6.3.1). The Pyramidal/ADAMAS instantiation — an organizational root AID with a delegated, product-scoped ADAMAS publisher AID beneath it — is recorded as a clearly-marked non-normative example, written throughout in the intended voice and headed by the not-yet-operational disclosure. No AID value is asserted; the key ceremony has not run. The operational-status duty binds from §6.3.1's normative body on every path, with §14.9 applying additionally on the introduction path.
afp.telemetry.instance_attestation.v1remains frozen, semantically as well as textually (§6.3, §6.3.1). Implementations MUST NOT map v1'spublisher AIDonto the organizational-root/delegated-publisher construction. The delegated-publisher construction is v2-and-forward only.- New open items (§10). The transparency-log upgrade path for the ADAMAS product line, and the out-of-band authority binding material an authority publishes outside this specification.
- No wire change: no new or renamed message class, predicate ID, public input, envelope revision, or NATS subject.
AfpInstanceAttestationPublicInputsV2andAfpIntroductionPolicy.accepted_instance_attestation_rootsare unchanged.
Version 0.3.9-draft (ADR-0006 interaction-context fold) Changes (relative to v0.3.9-draft (ADR-0005 authority naming)), per ADR-0006:
- §15.4 rewritten: the
payinteraction context is the ZKA/Pay Interaction-Context Binding Profile v1 Poseidon2 fold. The SHA-256/canonical-JSON derivation, itsafp:payment-context:v1kind discriminator, and its published conformance vector are retired (no external consumer recomputed them; verified across ZKA.dev and ADAMAS at ratification). §15.4 now restates the fold normatively and self-containedly — fixed-byte tag, field-count prefix, three-call expansion, input order — pinned to ZKA.devdb39afdwith the KAT fixture SHA-256; an upstream v2 binds AFP only via a superseding ADR. - Field mapping pinned on both legs. Fold inputs are the 32-byte digest payloads of
quote_hashand the session context (code-agnostic qb64 extraction), reduced plain big-endian modr, zero rejected fail-closed at quote-construction time; the wire carries the unreduced digest, and recomputing parties MUST reduce first. Thepayprofile normatively requires the session context to be a qb64-encoded 32-byte digest (base-spec formalization: new §10 open item). - Conformance re-anchored (§15.4, §15.8). All five upstream KAT vectors (vendored verbatim, hash-pinned) plus a new AFP-native end-to-end vector — complete quote content through
quote_hash, both field reductions (both digests exceedr), and the fold output — replace the retired vector as the "MUST reproduce exactly" anchor. - §15.5 checklist item 6 extended with the terminal fail-closed zero-reduction condition at dispatch.
- Appendix A reconciled (§2.2 note, §A.3, §A.4). New §A.4 fixed-byte fold-tag carve-out for ZKA's
zka/pay/*/zka/coord/*families, mirroring the existing ZKC-literals exemption; §A.3's field-separator prohibition scoped to KDC key derivation; §A.4's blanket same-derivation claim corrected to thederiveFieldTagclass. The tag is registered in no family registry today; ZKA-side registration is a tracked follow-up. - Reference implementation:
derive_interaction_contextkeeps its(str, str) -> strsignature over the new fold;canonical_nonzero_bn254(the retired1 + (x mod (r-1))mapping) andPAYMENT_CONTEXT_KINDare removed from the public API, replaced byfield_from_digest(plain mod-r, reject-zero),fold_interaction_context, andINTERACTION_CONTEXT_TAG. - No wire change: no new or renamed message class, quote field, envelope revision, or NATS subject; the two merged downstream carriers (ZKA holder-adapter, ADAMAS APL settlement request) remain wire-compatible — only the derived value they carry opaquely changes.
Version 0.3.4-draft Changes (relative to v0.3.3-draft), per ADR-0004:
- Open-federation trust basis (the normative change ADR-0004 records). AFP's trust basis is generalized from "a pre-existing bilateral federation agreement is the sole precondition for a session" to: a session MAY be opened on either (a) a bilateral federation agreement (the existing known-counterparty path, unchanged) or (b) per-interaction ZKC compliance attestation that satisfies each party's own acceptance policy (the new open-federation / unknown-counterparty path). Revised surface: the Abstract; §1.2 (trust-ladder row), §1.3 (relationship 1 admits unknown counterparties); §5.1 (model no longer assumes a known counterparty), §5.2 (open-federation session context), §5.5 (the agreement is now one of two trust bases); a new §5.8 "Open federation — trust by ZKC attestation"; §8.8 (route resolution reconciled with the no-prior-agreement path); §12.1 and §12.3 (the session is trust-anchored on an agreement or an attestation, and identity binding admits attested AIDs).
- Path (b) is OPTIONAL to support, but constrained when supported. A conforming deployment MAY support only the bilateral path; if it supports open federation, the §5.8 requirements (authenticated proof request, holder-retained receipt, own-policy acceptance, full safeguard inheritance) are MUST. Safeguard 6 is what makes path (b) safe: the requesting tenant is a verifier and MUST authenticate its demand under a stable identity (no signature ⇒ no proof), and the responder retains a portable Disclosure Receipt — so "prove you are compliant to transact with me" between strangers cannot degrade into unaccountable coercion.
- Discovery / rendezvous stays out of AFP. Only trust establishment is generalized (attestation, fully compatible with operator-blindness); how two strangers find each other and route first contact is not moved into AFP — a queryable tenant directory is the coordination graph OB-2 (§3, §8) exists to prevent. The residual stranger-rendezvous-under-OB-2 problem is surfaced as a new §10 open item, not hidden inside an over-broad "support unknown counterparties" mandate. Path (b) is usable today only where first contact is established out-of-band.
- Freedom Safeguards, settlement, and ZKC/ZKA semantics unchanged. Both paths inherit all six safeguards identically; compliance attestation remains an interaction-boundary concern (Safeguard 5: a declined/failed attestation means the counterparty declines the interaction — never that funds or notes are gated, frozen, redirected, or seized; exit stays unconditional). ZKA and ZKC need no substantive change — only the cross-reference/safeguard-count harmonization already done in v0.3.3.
- Completes the §1.4 version harmonization. The §1.4 related-protocols table still carried the v0.3.2 substrate labels (ZKA v0.6.2 / ZKC v0.2.4) after the v0.3.3 Abstract/diagram bump; the two row labels are corrected to ZKA v0.7 / ZKC v0.3.0. (The v0.3.2 changelog and Document-History rows retain v0.6.2/v0.2.4 as the accurate record of what that revision targeted.)
- Reconciled with the §14 Introduction Tier (merged in parallel). §14 (added independently) admits a stranger session by minting a provisional §5.5 agreement; §5.8 admits one with no agreement (per-interaction attestation). They are complementary, not competing. §12.1 is reframed to enumerate three trust bases — (a) pre-existing agreement, (b) §14 provisional agreement, (c) §5.8 attestation/no-agreement — and §14.2's "sole session type permitted to open without a pre-existing agreement" is scoped to "sole session type permitted to open by minting a provisional agreement," so §5.8 (no agreement) is not a counter-exception. §5.8 gains a "Relationship to the Introduction Tier" note; the §10 rendezvous open item now covers both paths.
Version 0.3.3-draft Changes (relative to v0.3.2-draft):
- Co-publication cross-reference harmonization. AFP now targets ZKA v0.7 and ZKC v0.3.0 — the co-published revisions of the sibling specs (updated in the Abstract, the §1.4 related-protocols diagram, and the Appendix A KDC consumer list). Inline ZKA/ZKC section citations elsewhere in the body are swept to the same versions in this revision.
- Inherited Freedom Safeguard count corrected to SIX. AFP previously inherited "five" Freedom Safeguards (a ZKA v0.6.0 count); ZKA v0.7 §1.6 and ZKC v0.3.0 §1.4 both now define six, adding Safeguard 6 — Verifier Accountability, the first safeguard to constrain the counterparty rather than the operator. Corrected in the Abstract, §1.1, §1.5, §9, and §12.8. Safeguard 6 is load-bearing for unknown-counterparty federation (ADR-0004): a tenant requesting a compliance proof from a stranger is a verifier, and SG6 (authenticated request + holder-retained receipt) is what keeps "prove you are compliant to transact with me" from becoming silent, unaccountable coercion.
- No change to AFP session, envelope, routing, identity, telemetry, or profile mechanics. This revision aligns cross-references and the inherited-safeguard count for simultaneous separate publication of ZKA, ZKC, and AFP; the open-federation trust-model change itself is recorded in ADR-0004 and lands in a subsequent revision.
Version 0.3.2-draft Changes (relative to v0.3.1-draft):
- Substrate targets updated (§1.4, §12.7). AFP now targets ZKA v0.6.2 and ZKC v0.2.4. ZKA v0.6.2 adds optional
coord_proofcarriage tozka:bundle:v1and permits pure work-attestation bundles without a payment leg, satisfying the ZKA-side dependency previously flagged by AFP §12.7. ZKC v0.2.4 adds the ZKC-Core credential rebind circuit (zkc:proof:credential_rebind:v1), which AFP §4.6 sequences with KEL rotation while ZKA note migration remains Draft. Issuer-specific migration tokens may optimize integrations but do not satisfy AFP rotation presentation without the ZKC-Core rebind proof. - Open-item status refreshed (§10). The §12.7 ZKA bundle dependency is no longer an open AFP item. The remaining rotation seam is narrowed to ZKA note migration; ZKC credential rebinding is now available as the credential-side primitive. Witness bootstrapping, public-sample consistency, the operator telemetry predicate catalog, AFP rotation sequencing, KDC vectors, and the AFP Federation Mixnet implementation profile are now specified.
1. Overview
1.1 Design Goals
- Tenant Federation: Two tenants coordinate work across organizational boundaries with neither side revealing more than the work requires.
- Operator Blindness: Whoever operates an AFP transport can verify the platform is healthy without learning what tenants do, who they coordinate with, or what moves between them. This is structural, not policy.
- Identity Continuity: A tenant's identity, reputation relationships, and attestations survive a change of deployment topology or operator.
- Substrate Reuse: AFP defines how ZK-mediated interaction is initiated, authenticated, sequenced, and recorded. It does not reinvent the cryptography of ZKA, ZKC, or ZKM.
- Freedom by Default: The six Freedom Safeguards (ZKA v0.9.0-draft §1.6) hold across every AFP interaction.
- Minimality: v0.3.0 specifies the smallest protocol surface that delivers the three relationships of §1.3. Anything expressible as tenant-internal behavior is not in the protocol.
1.2 What AFP Is Not
A clean inventory of what v0.3.0 removes, so conforming implementations do not carry retired surface:
| v0.2.0 concern | v0.3.0 status |
|---|---|
| Agent manifest schema for third-party agents | Removed. Entities have manifests inside their instance; nothing of that shape crosses the protocol boundary. |
| Capability discovery as a wire protocol / marketplace feed | Removed as a feed. A narrower case survives — "what does this tenant expose to a federated counterparty" — under §5 tenant ↔︎ tenant, negotiated bilaterally, not advertised. |
| Task delegation as a central wire protocol | Removed as a central feature. Intra-tenant routing is tenant-internal; cross-tenant work requests are a specific message class under §5. |
| Observation contribution from external agents | Removed. Observations originate only from tenant-internal entities. |
| Individual-agent reputation accumulation | Removed. Instance-level trust is handled by bilateral federation agreements (§5.5). |
| Trust-level escalation ladder (federated → integrated → native) | Removed. There is one trust level — verified conforming instance under a verifier-accepted attestation authority (§6.3.1) — plus whatever trust two instances either negotiate bilaterally (a §5.5 agreement) or establish per-interaction by ZKC attestation (open federation, §5.8). Neither is a portable score, and neither the root class nor the root authority is a rank: an authority is accepted or not accepted by a given verifier, and acceptance confers no tier. Whether a party requires instance attestation at all is that party's own policy (§1.5.1, §6.3.1). |
| HTTP bridge | Removed. Cross-instance traffic is between tenants on a common transport; no HTTP-only third-party use case remains. |
| Operator-run SaaS transport with transport-observed telemetry | Removed. See §3, §7. |
Where any retired concern survives, it survives as tenant-internal implementation, never as AFP wire protocol.
The retirements above are restated, not reopened. Generalizing who may implement a tenant (§1.5.1, §1.6, §2.1) changes the class of software that may sit at each end of a session; it restores no row of the table. A conforming implementation MUST NOT read that generalization as authority to:
- Register third-party agents. An entity is a principal only by delegation from the tenant that runs it: its AID is a delegated AID anchored in that tenant's KEL (§2.1, §4.2), and no other tenant can confer, register, or admit it. No AFP message registers, enrolls, or admits an agent across a tenant boundary; observations still originate only from tenant-internal entities; and intra-tenant dispatch remains tenant-internal (§5.6), never a wire delegation feature. A second tenant implementation is a tenant, never a registered agent.
- Publish discovery, marketplace, or capability-registry surface. What a tenant exposes remains negotiated bilaterally under §5 and is never advertised as a capability- or work-discovery feed — not to counterparties, not to operators, not to a DGP gateway (§1.4.1). The OPTIONAL, opt-in willingness-to-federate directory of §14.1 is the sole permitted published record and is unaffected: it carries a tenant's consent to be approached and its negotiating ceiling, never a work descriptor or an open call for work. Conformance itself is not discoverable: it is asserted and verified per §6.3.1 under the verifier's own policy. Nor is the set of attestation authorities discoverable: AFP publishes no authority registry, list, or feed. A verifier's accepted (root class, root authority) pairs live in its own authenticated verification policy (§6.3.1) and in the bilateral agreement or introduction policy that scopes an interaction (§5.5, §14.4); they are not advertised.
- Carry portable reputation. Positive trust signals remain per-agreement and bilateral (§5.5, §14.6): no score, rating, or conformance mark accumulates globally or substitutes for a bilateral agreement. The signed negative-history record of §14.7 is unaffected — it is a per-event signed statement of a specific default, not an accumulated score.
- Reintroduce a trust ladder. There remains exactly one protocol-level trust level. AFP defines no escalating ladder of counterparty classes and no privileged class of implementer; the reference implementation holds no protocol privilege, and a named attestation authority (§6.3.1) holds none either. Naming an authority in this specification, or shipping it as a default entry in an implementation's verification policy, confers no protocol privilege on that authority, imposes no obligation on any verifier to accept it, and creates no standing that AFP could grant or withhold. A conformance shortfall is handled by the honest-disclosure regime of §3.5 and §8.9 — the deployment discloses the limitation and MUST NOT claim the property — never by creating a new principal class or a lesser tier of tenant.
- Reinstate the HTTP bridge. All AFP traffic remains tenant ↔︎ tenant over the common transport (§2.3).
Surface with any of these effects, other than the constructs §14 expressly permits, is retired v0.2.0 surface under a new name and is outside AFP regardless of the name it carries.
1.3 The Three Relationships
AFP v0.3.0 is the protocol layer for exactly three relationships, all between identifiable, cryptographically verifiable principals:
- Tenant ↔︎ tenant — two tenants, each running their own entities, collaborating across organizational lines. The protocol makes the interaction safe, verifiable, and minimally disclosing. This is the only meaning of "federation" in v0.3.0: two instances talking, never a third-party agent integrated into an instance. The two instances may be known to each other — joined by a pre-existing bilateral federation agreement (§5.5) — or mutually unknown, establishing trust per-interaction by ZKC compliance attestation (open federation, §5.8). Both are tenant↔︎tenant federation; they differ only in how trust is established, never in who the principals are.
- Tenant ↔︎ operator — the operator of a transport needs to know the platform is healthy without learning what tenants do. The protocol enforces that asymmetry structurally.
- Identity continuity — a tenant's identity and accumulated relationships survive a move between deployment topologies (managed transport ↔︎ self-hosted appliance) and a change of operator.
1.4 Related Protocols
| Protocol | Relationship |
|---|---|
| ZKA v0.7 | Settlement and coordination substrate. Tenant ↔︎ tenant work artifacts, attestations, and value transfers are ZKA notes and ZKA/Coord proofs. AFP defines the interaction envelope around them. ZKA in turn normatively references the Key Derivation Core (Appendix A) for its key hierarchy and, as of v0.6.2, carries ZKA/Coord work-attestation proofs inside zka:bundle:v1 (§12.7). The AFP↔︎ZKA dependency remains acyclic: ZKA depends only on Appendix A, which depends on nothing else in AFP (§2.2). |
| ZKM v0.1.0-draft | Optional principal-issued spend-authorization layer. An AFP tenant's agent MAY exercise a ZKM mandate inside an AFP session, but AFP owns bilateral session policy while ZKM owns unilateral mandate state. Both constraints must pass independently; neither mutates or replaces the other. ZKM references KDC v1.0.2 for its seed/key-derivation model and owns its additional zkm/* tags. |
| ZKC v0.4.0 | Compliance substrate. Tenant ↔︎ operator telemetry and tenant ↔︎ tenant compliance claims are profile-aware ZKC proofs. AFP defines which claims are proved, their cadence, identity/session binding, accepted presentation profiles, downgrade rules, and failure semantics (§5.9). ZKC references AFP-KDC v1.0.2 for credential binding and owns the v1/v2 proof, rebind, APR, scope, pseudonym, and receipt cryptography. |
| KERI | Identity substrate. AFP identities are KERI Autonomic Identifiers (AIDs) with Key Event Logs (KELs). See §4. KERI machinery sits above the Key Derivation Core: KDC produces seeds and keys; AFP §4 wraps a KERI AID around them. |
| DGP | Out of AFP scope except for the boundary rule in §1.4.1. A tenant may expose a DGP gateway to a third-party tool, but that gateway is not an AFP endpoint, does not receive an AFP discovery feed, and does not expand the AFP trust boundary. |
| PRP v0.2 · VXP v0.2 | AFP profiles, not peer protocols (ADR-0003). PRP (pairwise reconciliation) and VXP (value exchange) are named behaviors over AFP's Bilateral Session Core (§12); each adds its own atom kinds and state machine but reuses the core's session, identity binding, NATS namespace, dispute/arbiter model, commitment + optional-anchor primitive, session-envelope bundle, and Freedom-Safeguards application. See §13 and PROFILE_MIGRATION_NOTE.md. |
1.4.1 DGP Gateway Boundary
A tenant may expose a DGP gateway to a third-party tool, internal integration, or adjacent tenant-internal workflow. That gateway is a DGP surface controlled by the tenant; it is not an AFP endpoint and does not become part of AFP federation.
The boundary is:
- No AFP discovery feed. AFP does not publish tenant, counterparty, capability, route, profile, or telemetry discovery data to a DGP gateway. If a tenant wants a DGP tool to know about an AFP artifact, the tenant exports a tenant-scoped reference explicitly.
- No coordination-traffic visibility. A DGP gateway MUST NOT receive AFP coordination traffic, mixnet route material, rendezvous handles, counterparty pairing data, session traffic timing, or any other data that would weaken OB-1 or OB-2 (§3).
- No trust-boundary expansion. Trust in a DGP gateway is never transitive into AFP. A gateway credential, policy decision, or audit event does not authorize AFP session establishment, telemetry acceptance, KEL rotation, note migration, or profile commitment unless the AFP principal independently signs the AFP artifact required by this specification.
- Opaque provenance only. A DGP gateway MAY carry opaque references to AFP session envelopes, work artifacts, PRP evidence, or VXP lifecycle atoms when the tenant intentionally exports them for provenance or audit. The reference MUST NOT include bilateral agreement contents, counterparty AID pairings, route state, telemetry private inputs, or mixnet metadata unless those fields are already visible to the tenant principal controlling the gateway.
This rule keeps DGP gateways useful at tenant integration edges without recreating the retired AFP v0.2 discovery, observation, or trust-score surfaces.
1.5 Conformance Terminology
The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, MAY, and OPTIONAL are to be interpreted as in RFC 2119 / RFC 8174.
A conforming AFP v0.3.0 implementation satisfies all normative requirements of §3–§8 and all six Freedom Safeguards (ZKA v0.9.0-draft §1.6). Sections or subsections marked Draft state a normative requirement whose concrete mechanism is not yet finalized; a conforming implementation MUST satisfy the requirement and MAY choose any mechanism that does, pending a future revision that fixes the mechanism.
1.5.1 The tenant conformance profile
A conforming AFP tenant satisfies the tenant conformance profile stated below. The two conformance subjects are distinct: a conforming implementation is a codebase that satisfies the normative requirements of §3–§8 and the six Freedom Safeguards (§1.5); a conforming tenant is a deployed runtime, addressed by its tenant root AID (§1.6, §2.1), that federates as a principal (§2.1) and therefore additionally carries the tenant-side obligations enumerated here. The profile is a consolidation of requirements this specification already imposes across §3–§12, stated as one list rather than left to be inferred from the identity of the implementer; with one exception it introduces no requirement that is not already normative elsewhere in this document, and it introduces no wire surface. The exception is item 7: the duty it states was previously implicit — discharged by the identity of the implementer rather than stated as an obligation — and this revision makes it explicit here and in §5.7 simultaneously. Making an implicit duty explicit is not the same as imposing a new one, but the profile does not claim §5.7 as prior authority for it. ADAMAS, published by Pyramidal Inc., is the reference implementation of this profile; that attribution is a statement of fact about one implementation, not a conformance gate, and publishing an implementation is unrelated to standing as an attestation authority (§6.3.1).
A conforming tenant MUST satisfy all of the following:
- KERI identity custody (§4). The tenant MUST be identified by a KERI tenant root AID governed by a KEL, with each entity a delegated AID whose inception is anchored in that KEL (§4.2). Pre-rotation is REQUIRED on every KEL event, and the tenant MUST reject a KEL whose rotation events do not honor prior pre-rotation commitments (§4.4). The tenant MUST publish its witness set and threshold as part of its KEL, MUST check that a counterparty's KEL events carry receipts from at least that counterparty's declared witness threshold, and MUST carry its witness configuration across a topology change (§4.5, §4.5.1, §4.5.2). Rotation, recovery, and succession run as KEL events, with guardian designation itself a published KEL event (§4.6); the tenant MUST NOT treat a KEL rotation alone as sufficient while notes or credentials derived from the old seed remain in use, and MUST publish the §4.6.1 rotation presentation when a rotation affects ZKA or ZKC material, under the sequencing and verifier rules of §4.6.2–§4.6.3.
- Operator blindness and deployment topology (§3, §7.1, §7.3). The tenant MUST deliver both OB-1 (content) and OB-2 (coordination graph); OB-1 alone does not satisfy the invariant (§3.1). OB is a capability standard, not a conduct standard: the operator must be unable to obtain the protected information (§3.2). The tenant MUST NOT route tenant↔︎tenant coordination traffic through a transport whose operator can correlate the endpoints (§3.3), and MUST NOT deploy in the topology §7.3 excludes — one in which an operator runs the transport that tenant↔︎tenant coordination traffic transits. Its OB-relevant behavior MUST be auditable (§7.1). Where OB-2 is not structurally satisfied, the tenant MUST NOT represent it as satisfied and MUST disclose the limitation (§3.5).
- Routing participation, including cover traffic (§8). The tenant MUST route coordination traffic through a layer that satisfies the §8.2 conformance properties, which are the conformance test (§8.1, §8.2). Constant-rate emission is REQUIRED, not optional: the tenant MUST emit coordination-plane traffic into its routing layer at a constant, scheduled rate at all times while federated, whether or not it has real coordination traffic to send, with real
afp.*messages (§5.3) slotted into that stream (§8.5). Where the deployment uses the AFP Federation Mixnet — the construction v0.3.0 adopts — §8.4–§8.5 fix the packet form, the indistinguishability of cover from real packets, and a schedule derived from the active directory epoch and the tenant's local scheduler rather than from the presence of application work; missed cover slots are an OB-2 health failure and MUST be disclosed in operator telemetry without identifying counterparties (§8.5). An implementation MAY satisfy §8.2 by another construction, but it MUST satisfy §8.2, including an equivalent constant-emission property (§8). A tenant that does not emit at a fixed rate is not OB-2-conforming and MUST disclose this per §3.5. - The bilateral session core (§12). Every AFP-mediated interaction the tenant takes part in MUST run inside a bilateral session (§12.1): exactly two principals, mutually authenticated against the declared witness threshold, end-to-end encrypted, metadata-private under §8, anchored to one of the three trust bases of §12.1, and context-bound. The tenant MUST carry the core's establishment, lifecycle, and timeout rules (§12.2), identity binding (§12.3), namespace ownership (§12.4), dispute and arbiter model (§12.5), commitment and optional-anchor primitive (§12.6), and session-envelope wire format (§12.7). Any profile the tenant runs MUST run over that core and MUST NOT re-derive or weaken it (§13.4).
- Operator telemetry (§6). Toward any operator to which the tenant stands in the §6 tenant↔︎operator relationship, the tenant MUST supply operational assurance only as claims it proves — never by permitting observation of tenant work or the coordination graph (§6.1) — pushed by the tenant on a telemetry subject at the tenant's own timing (§6.5). Every such proof MUST use a registered predicate identifier and supported version from the §6.3 catalog at that predicate's stated cadence; a proof outside the catalog is not AFP-conforming. Every operator-facing catalog proof MUST bind the tenant AID, operator AID, predicate ID, predicate version, telemetry subject, telemetry window, and active ZKC presentation profile into the proof transcript (§6.3), and every self-attested predicate MUST carry the public-sample binding of §6.4. This surface is identical in every deployment topology (§6.7).
- The six Freedom Safeguards (§9). The tenant MUST satisfy all six Freedom Safeguards (ZKA v0.9.0-draft §1.6) as §9 applies them to AFP — predicate pluralism, minimal disclosure, no revocability, open-source predicates, agent exit rights, and verifier accountability — and as §12.8 applies them to every bilateral session. A profile the tenant runs inherits the §12.8 application and MUST NOT weaken any safeguard (§12.8, §13.4).
- Tenant-internal obligations (§5.6, §5.7). The tenant MUST contain, capability-scope, and observe its own entities; the mechanism is tenant-internal and outside AFP scope (§5.7). Routing an inbound work request to the correct entity within the tenant is likewise tenant-internal, and the tenant MUST keep that boundary: no AFP wire message carries a tenant's internal routing topology (§5.6). These are tenant-side duties discharged behind the tenant boundary; nothing in this item crosses the wire.
Where a provision cited above is itself OPTIONAL or conditional — open federation (§5.8), or §8's allowance for an alternative construction satisfying §8.2 — the profile inherits that condition unchanged and does not strengthen it. The profile confers no discovery, registry, reputation, or capability-publication surface of any kind (§1.2); it states what a tenant must satisfy, not what a tenant may advertise. A tenant that does not meet an OB-relevant obligation of this profile MUST NOT claim the corresponding property and MUST disclose the limitation under §3.5.
A shortfall does not by itself end tenanthood or bar a session. Where §1.2, §12.1, §12.5, §13.1, and §5.8 speak of a verified conforming instance, of a verified conforming instance under a verifier-accepted attestation authority, or of conforming tenants, they state the standard each party is entitled to expect and to verify — not a protocol-level registry check, and not a requirement that every session carry an instance attestation. Whether a party requires instance attestation at all, and which (root class, root authority) pairs it accepts if it does, is that party's own policy (§6.3.1, Safeguard 1); the §12.1 trust bases include one — a pre-existing §5.5 agreement — that may require no attestation whatever. Acceptance remains each counterparty's own policy (§5.5, §5.8, §6.3.1, Safeguard 1): a tenant that has disclosed a shortfall under §3.5 or §8.9 remains a principal (§2.1), and whether a given counterparty opens or continues a session with it is that counterparty's decision. AFP defines no gate that excludes a non-conforming tenant from principalhood, and none that compels a counterparty to transact with one.
Conformance to this profile is asserted and bilaterally verified, not certified: there is no conformance registry, mark, or published implementer list (§1.2), and a counterparty's acceptance of an implementation is its own policy decision (§5.5, §5.8, §6.3.1). An attestation root authority (§6.3.1) is not a conformance certifier: a root authority attests what build a machine subject runs, never that a tenant satisfies this profile. Accepting an authority is not accepting a conformance claim, and no authority — named in this specification or otherwise — certifies conformance. The formal multi-vendor conformance program — published tenant-profile vectors as a normative gate — is deferred (§10).
1.6 Notation
| Symbol | Meaning |
|---|---|
H(domain, x...) |
The exact AFP-KDC v1.0.2 Poseidon2 sponge over BN254 with a field domain tag derived per Appendix A |
AID |
KERI Autonomic Identifier |
KEL |
Key Event Log |
M |
Tenant master seed (256 bits) |
M' |
Per-entity sub-seed, M' = HKDF(M, "entity/{name}") |
π |
A zero-knowledge proof (UltraHonk / BN254 unless stated otherwise) |
dip |
KERI delegated-inception event |
tenant |
A runtime that federates as a principal, addressed by its own tenant root AID; a tenant MUST satisfy the tenant conformance profile (§1.5.1) |
entity |
A named domain multi-agent system within a tenant, addressed by a delegated AID |
2. Architecture
2.1 Principals
AFP recognizes exactly three kinds of principal. All three are KERI AIDs (§4); they differ only in role, never in identity machinery.
- Tenant. A runtime that federates as a principal, identified by its own tenant root AID; a tenant MUST satisfy the tenant conformance profile (§1.5.1). The unit of federation. A tenant runs one or more entities. Tenanthood is a structural fact about what a runtime is, not a certificate of conformance: a tenant that falls short of an obligation is a non-conforming tenant, subject to the honest-disclosure regime of §3.5 and §8.9, never a non-principal. ADAMAS, published by Pyramidal Inc., is the reference implementation; the protocol names no implementer. §6.3.1 names a first attestation authority, which is a different thing: an authority is a party whose trust material a verifier may elect to accept, never a party entitled to implement, admit, or approve a tenant.
- Entity. A named domain multi-agent system within a tenant (e.g. Viksana, Alochana). Identified by a delegated AID whose inception is anchored in the tenant's KEL. Entities are the principals that actually perform and attribute cross-tenant work; an AFP message that crosses a tenant boundary on behalf of an entity carries the entity's delegated AID.
- Operator. Whoever runs an AFP transport. Identified by its own root AID, structurally identical to a tenant's. Operator status is an operational fact about who runs infrastructure; it confers no protocol-level privilege and no data path into tenant state (§3).
HyperTalk sessions are not AFP principals. A HyperTalk session is a tenant-facing surface; when work must cross a tenant boundary, the entity is the principal, never the session. Within-tenant user identity is internal access control and outside AFP scope.
A §6.3.1 attestation root authority is likewise not an AFP principal, and the enumeration above remains closed at three. An authority is an AID whose published trust material a verifier may elect to accept into its own verification policy; it addresses no AFP message, opens no session, holds no role under this section, and holds no protocol privilege. Its AID is ordinary KERI machinery (§4) used outside the principal roles, exactly as the AIDs of a mix node, a witness, or an external digest source are. Standing as an authority is neither a fourth principal class nor a tier of any of the three; where the same legal entity is also an operator, the roles are independent (§8.6).
2.2 Layering
+-------------------------------------------------------------+
| AFP v0.3.9 |
| - relationship envelopes (tenant<->tenant, tenant<->op) |
| - identity continuity (KERI KEL exchange, witnesses) |
| - metadata-privacy routing requirements |
+-------------------------------------------------------------+
| | |
v v v
+---------------+ +----------------+ +----------------------+
| ZKA v0.9 | | ZKC v0.4.1 | | KERI |
| notes, coord, | | predicates, | | AIDs, KELs, |
| bundles | | proofs | | witnesses, rotation |
+---------------+ +----------------+ +----------------------+
| | |
+------------------+-------------------+
|
v
+-------------------------------+
| Key Derivation Core (KDC) |
| v1.0.2 -- AFP Appendix A: |
| master-seed derivation, |
| Poseidon2/BN254 domain tags, |
| per-entity HKDF sub-seeds |
+-------------------------------+
|
v
+-------------------------------+
| Transport (NATS / leaf |
| nodes) + metadata-privacy |
| routing layer (§8) |
+-------------------------------+
AFP sits above ZKA, ZKC, and KERI and below nothing — it is the top protocol of the federation stack. ZKM is a composition peer rather than a new AFP layer: it can constrain an agent's spend inside a session without replacing AFP policy or ZKA settlement. AFP never reimplements a primitive that ZKA, ZKC, ZKM, or KERI already defines; where this specification needs such a primitive it cites the owning specification.
Note on the Key Derivation Core. Master-seed derivation, the Poseidon2/BN254 field-tag scheme, and the per-entity HKDF sub-derivation are depended on by ZKA, ZKC, ZKM, and AFP alike. They are specified once as the self-contained AFP-KDC v1.0.2 module in Appendix A. v1.0.1 made the deployed Blake2b512 field-tag derivation explicit; v1.0.2 pins the already-deployed Poseidon2 parameters and shared conformance vectors. Neither patch changes a derived value from v1.0.0. KDC owns the six derivation tag strings. Each referencing protocol owns its operational tag strings and MUST derive their field constants with §A.3, save for the ZKA pool-adjacent fixed-byte fold tags carved out in §A.4. A dependent pins
AFP-KDC v1.0.2without depending on any other AFP section, so the dependency graph remains acyclic.
2.3 Transport
AFP messages travel over a NATS-based transport (subjects, JetStream for durable streams, leaf nodes for cross-instance links). Transport selection is otherwise unconstrained. The transport is assumed untrusted for confidentiality purposes: §3 and §8 specify what the transport operator may and may not learn, and those guarantees MUST hold regardless of who runs the transport.
The v0.2.0 HTTP bridge is removed. All AFP traffic is between tenants speaking AFP over the common transport.
3. The Operator-Blindness Invariant
This section is normative and central. It states the property that AFP is taken to promise: a tenant need not trust the operator, because the operator is structurally incapable of compromising tenant confidentiality.
3.1 Statement
Invariant OB (Operator Blindness). No operator of an AFP transport shall be able to learn, from operating that transport:
- the content of any tenant's work — work artifacts, payment amounts, counterparty identities, coordination payloads; or
- the coordination graph — which tenant is coordinating with which.
OB-1 (content) and OB-2 (graph) are distinct properties with distinct mechanisms. A conforming implementation MUST deliver both. Delivering OB-1 while leaving OB-2 unmet — encrypted payloads over an operator-observable relationship graph — does not satisfy this invariant.
3.2 Capability, not conduct
OB is a statement about capability, not conduct. An implementation satisfies OB only if the operator cannot obtain the protected information, not if the operator chooses not to. "The operator does not look" is not conformance. A design in which the operator could reconstruct the coordination graph by correlation — even if no operator ever does — fails OB.
This follows the same discipline as ZKA Safeguard 3 (No Revocability): the absence of a capability, verifiable by inspection, rather than a promise of good behavior.
3.3 Consequence for deployment topology
OB has a hard consequence. Whoever runs a transport can observe that transport's traffic graph — connection metadata, timing, correlation of endpoints. Therefore, an operator who runs the transport that tenant↔︎tenant traffic transits cannot, by §3.2, satisfy OB-2 for that traffic. Payload encryption (OB-1) is achievable on an operator-run transport; relationship-graph privacy (OB-2) is not.
AFP v0.3.0 resolves this by requiring that the operator is not on the coordination path:
- OB-1 (content) is delivered in every deployment by end-to-end encryption of all AFP payloads (§5.2) plus the substrate properties of ZKA and ZKC. The operator sees ciphertext envelopes, never contents.
- OB-2 (graph) is delivered by the metadata-privacy routing layer of §8, under which no single transport operator — including the entity that publishes a tenant's implementation — observes an unobscured tenant↔︎tenant relationship.
A conforming AFP deployment MUST NOT route tenant↔︎tenant coordination traffic through a transport whose operator can correlate the endpoints. Where the §8 routing layer is not yet available in a given deployment, that deployment MUST NOT claim OB-2 conformance and MUST disclose the limitation to its tenants (§3.5).
3.4 Metadata is in scope
OB-2 exists because encrypting content while leaking metadata is a known and unacceptable failure mode for a confidentiality platform. AFP treats the following as protected metadata, not merely the payloads they carry: the existence of a coordination session between two named tenants; the pairing of tenant AIDs in a session; the timing and volume correlation that would reveal such a pairing; and stable identifiers (NKeys, subject names) that would let an observer link a tenant across sessions. §8 specifies the routing requirements that protect these.
3.5 Honest disclosure
A deployment that delivers OB-1 but not OB-2 — for example, an interim deployment before the §8 routing layer is operational — is a valid lesser tier, but it MUST be labeled as such. An implementation MUST NOT represent OB-2 as satisfied unless it is structurally satisfied per §3.2. Claiming appliance-grade or routing-layer-grade confidentiality for a deployment that does not have it is a conformance violation, independent of any cryptographic correctness.
The duty stated in this subsection is a duty of the party making the claim. Two of its addressees are AFP conformance subjects (§1.5): a deployment discloses the tier it actually delivers, as above, and an implementation that cannot support inspection of its OB-relevant behavior discloses that limitation rather than claiming OB (§7.1). A §6.3.1 attestation root authority is named here as a third addressee, but on a different footing, because an authority is not an AFP principal and not an AFP conformance subject (§1.5, §2.1): the character of its attestation is fixed by §6.3.1's class rules, and the duty to characterize it truthfully in published trust material is given normative effect as a verifier acceptance condition in §6.3.1 rather than as a conformance obligation this subsection could enforce. An authority that wants to be accepted discloses accordingly; nothing here binds a party AFP does not otherwise bind. This subsection is otherwise scoped to OB-1/OB-2 claims and neither imposes an OB obligation on an authority nor converts an attestation into an OB claim.
4. Identity
AFP v0.3.0 adopts KERI-aligned self-certifying identity. This section is normative. It supersedes the hash-derived identifier model and the flat nkey_history array of the v0.2.0 Identity Addendum.
4.1 Why KERI
A self-certifying identifier can be verified from the identifier and its Key Event Log alone, with no registry lookup and no dependency on any operator. This is the only identifier class that survives a change of operator and a move to a self-hosted appliance — the v0.2.0 hash-derived identifier could not, because verifying it required the issuing operator's registry. Identity continuity (§1.3 relationship 3) therefore requires KERI alignment; it is not optional.
4.2 Identifier hierarchy
AFP identity is a two-level KERI delegation tree per tenant, with a master-seed derivation beneath it.
- Tenant root. Each tenant has one master seed
M. The tenant root is a KERI AID governed by a KEL.Mis the tenant's root secret; the tenant root AID is the canonical identifier of the tenant across the federation. - Entity (delegated AID). Each entity within the tenant has a sub-seed
M' = HKDF(M, "entity/{name}"). The entity is a KERI delegated AID: its KEL opens with a delegated-inception (dip) event anchored in the tenant's KEL. The tenant can rotate, revoke, or replace any entity AID by events in its own KEL. - Operator. An operator has its own root AID with its own master seed, structurally identical to a tenant. Operator privileges are operational only; per §3 they confer no protocol-level data path.
4.3 Relationship to ZKA and ZKC keys
The master-seed derivation tree extends below the KERI AID, and ZKA/ZKC keys derive from the per-entity sub-seed, not from the tenant master directly:
Tenant master seed M
|
| KERI tenant root AID (KEL governed by witnesses)
|
+------------+------------+
| |
M' = HKDF(M, M' = HKDF(M,
"entity/viksana") "entity/alochana") ...
| |
KERI delegated AID KERI delegated AID
(dip anchored in (dip anchored in
tenant KEL) tenant KEL)
| |
ZKA/ZKC key hierarchy ZKA/ZKC key hierarchy
from M' per Appendix A from M' per Appendix A
(KDC v1.0.2): (KDC v1.0.2):
sk = H("zka/spending", M')
vk = H("zka/viewing", sk)
pk = H("zka/proof", sk)
cbk = H("zkc/credential", M')
For each entity, the entity's sub-seed M' is the master-seed input to AFP-KDC v1.0.2 (Appendix A) — specifically to deriveKeys (the ZKA key hierarchy) and the credential-binding derivation (the ZKC cbk and private/global holderCommitment). The HKDF sub-derivation M' = HKDF(M, "entity/{name}") is itself defined by KDC §A.5. Consequently:
- An entity's ZKA address and its ZKC
holderCommitmentderive along separate paths fromM'and are unlinkable withoutM', exactly as Appendix A requires — the §4.2 hierarchy does not weaken that property, it scopes it per entity. - Compromise of one entity's
M'does not expose sibling entities: eachM'is an independent HKDF output ofM(Appendix A §A.5). - Cross-tenant work performed by an entity is attributable to that entity's delegated AID and signable with a key derived from
M'.
4.4 Key Event Log
Each tenant root and each entity AID has a KEL — an append-only, self-certifying log of inception, rotation, delegation, and interaction events. The KEL replaces the v0.2.0 flat nkey_history array.
Pre-rotation is REQUIRED, not optional. Every KEL event commits to the digest of the next key set (next_key_digest); a rotation event reveals the pre-committed next key and commits to the one after. An AFP implementation MUST reject a KEL whose rotation events do not honor prior pre-rotation commitments.
4.5 Witnesses
Each tenant chooses a set of KERI witnesses that receipt its KEL events. Witnesses are the mechanism by which a counterparty — including an appliance with no access to the tenant's original operator — can verify that the tenant's key history is consistent and has not forked.
- A tenant MUST publish its witness set and threshold as part of its KEL.
- A verifying counterparty MUST check that KEL events carry receipts from at least the tenant's declared witness threshold.
- Witness configuration travels with the tenant across a topology change: an appliance migration (§4.7) carries the witness set.
Witnesses receipt KEL events only; they never see AFP work content and are not a data path under §3. A witness learns that a tenant rotated a key, not what the tenant does.
4.5.1 Witness bootstrap record
A new tenant establishes its initial witness configuration with a witness bootstrap record:
interface AfpWitnessBootstrap {
kind: 'afp:witness-bootstrap:v1';
tenant_aid: string; // tenant root AID being bootstrapped
kel_inception_dig: string; // digest of the tenant root inception event
witness_set: Array<{
witness_aid: string;
witness_endpoint: string; // transport endpoint for KEL receipts only
witness_kel_dig: string; // latest witnessed event for the witness AID
}>;
threshold: number; // minimum receipts required for KEL acceptance
bootstrap_mode: 'managed-bootstrap' | 'bring-your-own';
bootstrap_issuer_aid?: string; // optional candidate-set publisher, never a trust root
issued_at: datetime;
expires_at: datetime;
tenant_signature: string; // tenant signs all fields above
witness_receipts: Array<{
witness_aid: string;
kel_event_dig: string; // MUST equal kel_inception_dig
receipt: string; // KERI receipt over the inception event
}>;
bootstrap_issuer_signature?: string; // optional signature over the offered candidate set
}The tenant root inception event MUST commit to the same witness_set and threshold carried in the bootstrap record. The bootstrap record is a convenience envelope for discovery and verification; the KEL remains authoritative. A conforming implementation MUST reject a bootstrap record whose signed fields do not match the tenant root inception event.
bootstrap_issuer_aid is optional and advisory. In a managed deployment, an operator or consortium MAY publish a signed candidate set to help a tenant find witnesses, but the tenant chooses the witness set and signs the bootstrap record. The operator's signature is never a trust root and never substitutes for witness receipts.
4.5.2 Managed and appliance bootstrap paths
Two bootstrap paths are conforming:
- Managed bootstrap. The tenant selects witnesses from a managed or consortium-published candidate set, signs
afp:witness-bootstrap:v1, obtains receipts from at leastthresholdwitnesses over the tenant root inception event, and publishes the record with the KEL. - Bring-your-own witness bootstrap. An appliance tenant supplies its own witness AIDs and endpoints, signs
afp:witness-bootstrap:v1, obtains the same threshold receipts, and publishes the record with the KEL. An appliance MUST be able to complete this flow without contacting the original managed operator.
Both paths produce the same verifier-facing artifact. A counterparty MUST NOT distinguish their trust semantics: the only acceptance criteria are the tenant signature, the KEL/inception match, the declared threshold, and valid KERI receipts from the declared witnesses.
4.5.3 Verification and failure semantics
A verifier that receives a tenant's initial KEL state MUST:
- verify the tenant root AID from the inception event;
- verify the
tenant_signatureover the bootstrap record; - verify that
kel_inception_dig,witness_set, andthresholdmatch the inception event; - verify that at least
thresholdreceipts inwitness_receiptsare valid KERI receipts from AIDs inwitness_setoverkel_inception_dig; - verify each witness AID against its own KEL state up to
witness_kel_dig; and - reject expired bootstrap records unless the same witness configuration is already established by later KEL events.
Bootstrap failure has exactly one protocol consequence: the verifier treats the tenant identity as not yet established and declines AFP session establishment with that tenant. It MUST NOT trigger operator intervention, key revocation, note invalidation, or any change to tenant state.
After bootstrap, witness changes are ordinary KEL events: the tenant rotates the witness configuration by publishing the new set and threshold in the KEL and obtaining receipts under the old and new threshold policy according to KERI witness-change rules. Appliance migration (§4.7) carries the current KEL and witness configuration; it is not a re-bootstrap unless the tenant intentionally changes its witness set.
4.6 Rotation, recovery, succession
Identity-lifecycle operations are re-specified on the KEL:
- Rotation is a standard KEL rotation event honoring the prior pre-rotation commitment.
- Recovery (key loss) uses KERI's delegated-recovery / guardian mechanism: a pre-designated guardian set can author a recovery event. Guardian designation MUST itself be a published KEL event.
- Succession (a tenant or entity handing control to a new principal) is a delegation or rotation event, depending on whether the AID identity is preserved.
A master-seed rotation re-derives the entity's ZKA/ZKC keys, which orphans the credential binding (cbk) and any on-chain ZKA notes bound to the old keys. ZKC v0.4.0 owns both credential-side continuity primitives: scoped zkc:proof:rebind:v2 exposes only old/new presentation pseudonyms plus their shared scopeId; the frozen zkc:proof:credential_rebind:v1 exposes old/new global holder commitments and is therefore the explicitly-consented linkable profile. There is no anonymous rebind, because a rebind is a continuity claim. Issuer migration tokens MAY optimize integrations but do not replace the ZKC-Core proof. ZKA §5.7 owns note migration.
AFP owns the upper-layer sequencing and presentation of those facts to counterparties. An AFP implementation MUST NOT treat the KEL rotation alone as sufficient when notes or credentials derived from the old seed remain in use.
4.6.1 Rotation presentation artifact
When a tenant or delegated entity rotates a seed that affects ZKA or ZKC material, it MUST publish a rotation presentation:
interface AfpRotationPresentation {
kind: 'afp:rotation-presentation:v1';
tenant_aid: string;
entity_aid?: string; // absent for tenant-root rotation
old_kel_event_dig: string; // last accepted event before rotation
rotation_event_dig: string; // KEL rotation event being presented
new_kel_event_dig: string; // latest witnessed event after rotation
affected_material: Array<'kel' | 'zkc-credential-binding' | 'zka-notes'>;
presentation_profile: 'zkc:presentation:anonymous:v1' | 'zkc:presentation:scoped:v1' | 'zkc:presentation:linkable:v1';
zkc_credential_rebind?: {
proof_kind: 'zkc:proof:rebind:v2' | 'zkc:proof:credential_rebind:v1';
proof: string;
public_inputs: {
// scoped v2: the only holder-derived fields permitted
old_presentation_pseudonym?: string;
new_presentation_pseudonym?: string;
scope_id?: string;
// linkable v1: present only with explicit holder consent
old_holder_commitment?: string;
new_holder_commitment?: string;
presentation_profile: string;
issuer_id: string;
credential_schema_id: string;
context: string; // binds AID, KEL rotation, session, APR, profile, and active handles
timestamp: datetime;
};
proof_public_inputs_hash: string; // hash of the ZKC-owned public statement above
issuer_migration_token?: string; // optional optimization; not a substitute for the proof
};
zka_note_migration?: {
status: 'not-applicable' | 'complete' | 'pending-zka-draft';
proof_kind?: string; // ZKA-owned when status is complete
proof?: string;
migrated_note_root?: string;
};
presented_at: datetime;
tenant_signature: string;
}The presentation artifact is an AFP envelope over KEL state plus lower-layer proofs. It does not define either ZKC circuit or the ZKA note-migration proof. Its handle surface is profile-exclusive: scoped v2 MUST omit both holder commitments; linkable v1 MUST omit scoped pseudonyms and scope_id; an anonymous flow that does not require continuity omits zkc_credential_rebind entirely and re-establishes the rotated credential as a fresh anonymous presentation.
4.6.2 Sequencing rules
A conforming implementation MUST present rotation in this order:
- Pre-rotation commitment. The prior KEL event MUST already commit to the next key set (
next_key_digest, §4.4). If the pre-rotation commitment is missing or not honored, the rotation is invalid. - KEL rotation. The tenant publishes the KEL rotation event, obtains the declared witness-threshold receipts, and makes the witnessed KEL state available to counterparties.
- Credential rebinding. If
affected_materialincludeszkc-credential-bindingand the consuming flow requires continuity, the tenant MUST include scopedzkc:proof:rebind:v2, or linkablezkc:proof:credential_rebind:v1after explicit consent. The proof context MUST bind the entity AID, KEL rotation, AFP session, authenticated APR signer, active presentation profile, and exactly the active profile's handles. An issuer migration token MAY accompany the proof but MUST NOT replace it. Anonymous flows that do not require continuity MAY present the rotated credential as fresh and MUST NOT publish an old↔︎new link. - ZKA note migration. If
affected_materialincludeszka-notes, the tenant MUST include azka_note_migrationblock. While ZKA §5.7 remains Draft, the only conforming unresolved status ispending-zka-draft; counterparties MUST treat notes bound to the old seed as not yet migrated for new AFP work, but MUST NOT invalidate, freeze, or redirect them. When ZKA finalizes the note-migration proof,status: 'complete'MUST carry the ZKA-owned proof and migrated note root. - Presentation binding. The tenant signs the complete
afp:rotation-presentation:v1artifact and presents it on the relevant tenant↔︎tenant or tenant↔︎operator channel before relying on the rotated material in a new AFP session, telemetry proof, or work bundle.
4.6.3 Verifier behavior
A verifier MUST:
- verify the tenant or entity AID against the KEL chain through
new_kel_event_dig; - verify witness receipts for
rotation_event_digandnew_kel_event_digunder §4.5; - verify that
old_kel_event_digimmediately precedes the rotation being presented; - verify the tenant signature over the presentation artifact;
- when ZKC credential material is affected, require either (a) a valid profile-specific credential-rebind proof when the consuming flow requires continuity, or (b) the anonymous profile with no
zkc_credential_rebindwhen continuity is unnecessary, treating the rotated credential as a fresh presentation and accepting no old↔︎new linkage; and - require either no affected ZKA notes,
zka_note_migration.status == 'not-applicable', or a ZKA-owned completion proof before accepting rotated ZKA notes for new AFP work.
Rotation-presentation failure has the same consequence as other AFP verification failures: the verifier declines the affected session, telemetry proof, or work bundle and emits an alert where applicable. It MUST NOT trigger operator intervention, credential revocation, note invalidation, or any mutation of tenant state.
4.7 Appliance migration
A tenant may move between deployment topologies — for example, from a managed transport to a self-hosted appliance. Because all entity sub-seeds derive from the tenant master seed M, migration is one move: relocating M re-derives every M' and therefore every entity's ZKA/ZKC key hierarchy. The KEL state and witness configuration move with the tenant. No counterparty relationship, attestation, or identifier changes as a result of the move — which is the point of choosing self-certifying identity in §4.1.
5. Tenant ↔︎ Tenant Federation
This section specifies the first relationship: two tenants coordinating work across organizational boundaries.
Relationship to the Bilateral Session Core (§12), added in v0.3.1. The session machinery introduced informally in this section — establishment (§5.2), bilateral federation agreements (§5.5), and the work-artifact envelope (§5.4) — is generalized and made AFP-owned in §12 (Bilateral Session Core). §12 is the canonical, normative home of the session/identity/dispute/commitment/bundle/safeguards machinery that the profiles of §13 (PRP, VXP) reuse. This §5 remains the specification of AFP's base behavior: direct tenant↔︎tenant work exchange, which §13.3 names as the base profile over the same core. Read §5 for the work-exchange behavior; read §12 for the shared substrate that PRP and VXP also build on. Where §5 and §12 describe the same construct (the session of §5.2, the agreement of §5.5), §12 is the consolidated statement and §5 the work-exchange application of it.
5.1 Model
A tenant↔︎tenant interaction is a bilateral, ZKA-mediated work exchange between two entities, one in each tenant. It is not a marketplace transaction: there is no discovery feed, no open call, no solver. The protocol's job is to make the exchange verifiable and minimally disclosing once the two counterparties are in contact.
Trust between the two tenants rests on one of two bases (generalized in v0.3.4, per ADR-0004):
- Known counterparty (bilateral path). The two tenants hold, or establish, a bilateral federation relationship (§5.5). This is the original v0.3.0 path, unchanged.
- Unknown counterparty (open-federation path, §5.8). The two tenants have no prior agreement and establish trust per-interaction by mutual ZKC compliance attestation. Each party answers the other's Authenticated Proof Request with a ZKC compliance bundle; acceptance is each party's own policy.
In both cases the counterparty is a specific, identified principal by the time a session is established — open federation removes the prior-agreement assumption, not the identified-principal one. What AFP does not provide in either case is discovery: how two strangers find each other and route first contact is out of scope (§8.8), because a queryable directory of tenants would itself be the coordination graph (§3).
A typical exchange: an entity in tenant A (e.g. Viksana) needs an analysis run by an entity in tenant B; B performs the work; the result returns to A. Neither side reveals more than the work requires.
5.2 Session establishment
A tenant↔︎tenant session is established between two entity AIDs:
- Address resolution. The initiating entity resolves the counterparty tenant AID (not the entity AID) to a transport route via the §8 routing layer. The protocol addresses tenants; routing a request to the correct entity inside the receiving tenant is tenant-internal (§5.6).
- Mutual authentication. Each side verifies the other's KEL against its declared witness threshold (§4.5). Authentication is of the entity delegated AID, with its delegation chain verified up to the tenant root AID.
- Channel encryption. The session establishes an end-to-end encrypted channel between the two entities. All subsequent AFP payloads on the session are ciphertext to any transport operator (OB-1, §3.3). Channel key agreement uses keys derived from the entities'
M'hierarchies. - Context binding. The session has a context
H(initiatorEntityAID, responderEntityAID, sessionId, timestamp)that binds every proof and message in the session and prevents replay to another session or counterparty. This context is the value supplied to the ZKA and ZKCcontextpublic inputs (§5.4, §6).
Open-federation variant (§5.8). For an unknown-counterparty session the entity AIDs are not drawn from a pre-existing agreement; they are the attested identities established at first interaction (the ZKC verifier/holder identities each party authenticates under §5.8). Steps 2–4 are otherwise unchanged: the same KEL verification (step 2), the same channel encryption (step 3), and the same context-binding hash (step 4) over those attested AIDs — additionally binding the shared ZKC proof context so the session is anchored to the specific attestation that opened it. The binding shape is identical; only the provenance of the two AIDs differs (attested-at-first-contact vs. carried-in-agreement).
5.3 Message classes
AFP defines a deliberately small set of message classes for the tenant↔︎tenant session. All are carried inside the encrypted channel of §5.2. These are the message classes of the base work-exchange behavior; under the consolidated NATS namespace of §12.4 they are addressed as leaves of the base profile's subtree, afp.{tenant}.work.* (with afp.{tenant}.session.* for the lifecycle classes). The bare names below are retained for readability; §12.4 is normative for their full tenant-scoped addressing.
| Class | Purpose |
|---|---|
afp.session.open / afp.session.close |
Establish / tear down a session per §5.2 |
afp.work.request |
An entity requests cross-tenant work. Carries a work descriptor and the requester's bilateral-agreement reference (§5.5). |
afp.work.result |
The performing entity returns a result, carried as ZKA note(s) and/or a ZKA/Coord proof (§5.4). |
afp.disclose.request / afp.disclose.response |
One side requests, and the other provides, a selective disclosure or compliance bundle (§5.4, §6.1). |
afp.attest |
An entity contributes a coordination attestation (ZKA/Coord proof) for a completed multi-step workflow. |
There is no afp.register, no afp.discover, no afp.delegate as a marketplace primitive. Work requests are a message inside an already-established bilateral relationship, not an open call.
5.4 Work artifacts ride on ZKA
Cross-tenant work artifacts, attestations, and value transfers are ZKA constructs; AFP carries them and defines their interaction envelope but does not redefine them.
- Value transfer between tenants (e.g. payment for work performed) is a ZKA/Pay transfer (ZKA v0.9.0-draft §3): the payment is a ZKA note, amounts and parties hidden from observers. When principals price an obligation in USD but settle it in WETH, the AFP
payprofile (§15) binds the exact signed quote to the ZKA payment; ZKA does not interpret USD or choose a price. - Verifiable multi-step work is attested with ZKA/Coord recursive coordination proofs (ZKA v0.9.0-draft §4): an
afp.attestmessage carries aCoordinationProofproving that an N-step workflow executed correctly without revealing the step inputs. AFP'scontext(§5.2) is bound into the proof'scontextpublic input. - Selective disclosure — proving a property of a work artifact or balance without revealing it — uses ZKA/Pay selective-disclosure circuits (ZKA v0.9.0-draft §6). Each disclosure is atomic (one property per proof) per Safeguard 2.
- Compliance — where one tenant requires the other to prove a compliance property as a condition of the interaction — uses a ZKA Compliance Bundle (
zka:bundle:v1, ZKA v0.9.0-draft §5.4), which wraps atomic ZKC v0.4 proofs:zkc:proof:v2for anonymous/scoped presentations or frozenzkc:proof:v1for an explicitly-consented linkable presentation. AFP applies the flow policy and AID/session binding of §5.9. Anafp.disclose.responseMAY carry a Compliance Bundle.
A tenant↔︎tenant compliance requirement is an interaction-boundary concern (Safeguard 5): it may cause the requesting tenant to decline the interaction, and nothing more. It MUST NOT gate either tenant's access to its own funds or notes, and a failed bundle verification has exactly one permitted consequence — the counterparty declines — per ZKA v0.9.0-draft §5.4 and Safeguard 3.
5.5 Bilateral federation agreements
v0.3.0 does not define individual-agent reputation or a portable reputation score. Instead, trust between two tenants is a bilateral federation agreement: an explicit, mutually signed record that two tenant AIDs have agreed to federate, optionally scoped (which entities, which work classes, which compliance requirements apply).
- A federation agreement is signed by both tenant root AIDs and referenced by
afp.work.requestmessages. - Agreement scope is bilateral and private to the two tenants; it is not published or discoverable.
- Whether any instance-level reputation signal beyond the bilateral agreement is needed is left open (§10); the working assumption of v0.3.0 is that bilateral agreements supersede portable scoring entirely.
- Requiring instance attestation on this path is OPTIONAL. Where an agreement's compliance requirements include
afp.telemetry.instance_attestation.v2, the agreement MUST name the (root class, root authority) pairs each party accepts, with the semantics and verifier obligations of §6.3.1; the §14.4accepted_instance_attestation_rootsshape is the reference encoding. An agreement that does not require the predicate names no authority, and the absence of a named authority never selects one by default. Like every other term of the agreement, the accepted pairs are private to the two tenants and are neither published nor discoverable (§1.2).
The bilateral federation agreement is the trust basis for the known-counterparty path. As of v0.3.4 (ADR-0004) it is one of two trust bases: two tenants with no prior agreement may instead establish trust per-interaction by ZKC compliance attestation — the open-federation path of §5.8. Both are bilateral and private; neither is a portable reputation score. Where this section says "the agreement," an open-federation session substitutes the per-interaction attestation and its policy block.
5.6 Internal routing boundary
When an afp.work.request arrives addressed to a tenant, routing that request to the correct entity within the receiving tenant is tenant-internal and outside AFP scope. The protocol sees tenant AIDs as session endpoints and entity AIDs as the principals named inside the encrypted channel; it does not see, and does not specify, how a tenant dispatches an inbound request across its own entities. A conforming implementation MUST keep this boundary: no AFP wire message carries a tenant's internal routing topology.
5.7 Rogue-agent threats are out of scope
A compromised or misbehaving entity inside a tenant operates with that tenant's legitimate keys and legitimate access. Protecting a tenant against its own rogue entity is a containment, capability-scoping, and observability problem handled within the tenant: a conforming tenant MUST contain, capability-scope, and observe its own entities (§1.5.1 item 7), and the mechanism is tenant-internal. ADAMAS, the reference implementation, discharges this through policy enforcement, per-entity capability scoping, and the Drasta observability entity acting on the tenant's behalf. It is explicitly not an AFP concern. AFP's guarantees — operator blindness, federation verifiability, identity continuity — MUST NOT be read as covering an internal rogue agent. This boundary is stated so that conforming implementations do not conflate the two.
5.8 Open federation — trust by ZKC attestation
Added in v0.3.4 (ADR-0004). Supporting open federation is OPTIONAL; a conforming deployment MAY support only the bilateral path of §5.5. Where a deployment supports open federation, the requirements in this subsection are normative (MUST/MUST NOT as stated).
Open federation is a tenant↔︎tenant session (§5.1, §5.2) between two conforming tenants that hold no prior bilateral federation agreement. In place of the agreement, trust is established per-interaction by mutual ZKC compliance attestation. This is the second of the two trust bases of §5.1; everything else about the session — its message classes (§5.3), its work-on-ZKA carriage (§5.4), its routing (§8), its session core (§12), and all six Freedom Safeguards (§9) — is unchanged.
Trust establishment. To open an open-federation session, each party in turn:
- Authenticates a scoped proof request. The requesting tenant issues a ZKC v0.4 Authenticated Proof Request declaring the predicates, purpose/proof context,
presentation.profile = zkc:presentation:scoped:v1, andscopePolicy = protocol:afp:<profile_policy_hash>. The APR is signed by the current KEL-anchored key of the requesting entity AID. An unauthenticated request is rejected — no signature ⇒ no proof. - Answers with a subject-bound ZKC compliance bundle. The responder answers with
zkc:proof:v2inside ZKA v0.9.0-draftzka:bundle:v1, wrapped inafp:envelope:v1, or declines. Every proof is atomic and its verified credential-subject AID, scoped pseudonym, and proof context MUST satisfy §5.9. A borrowed anonymous proof is not an open-federation trust basis. - Retains a profile-minimized Disclosure Receipt. Each party retains a holder-custodied ZKC Disclosure Receipt containing the scoped pseudonym and
scopeId, neverholderCommitment, unless the holder separately and explicitly consented to the linkable v1 profile.
Because each side both requests and answers, trust is mutual: neither party is a pure verifier nor a pure holder, and each ends the exchange holding a receipt of the other's demand.
Acceptance is each party's own policy. There is no global registry of who-may-transact-with-whom, no canonical predicate set, and no canonical or required attestation authority. Each party decides, under its own acceptance policy, whether the bundle it received satisfies it (Predicate Pluralism, Safeguard 1; verifier flexibility, ZKC §1.1). Where a party's acceptance policy includes afp.telemetry.instance_attestation.v2, that policy names the (root class, root authority) pairs it accepts per §6.3.1 and the counterparty proves under one accepted pair, exactly as §14.2 requires on the introduction path; a party MAY accept an authority the other party rejects, and neither outcome is a conformance judgment about the other. A satisfied policy opens the session; an unsatisfied one declines it — and declining is the only permitted consequence of a failed attestation, of a rejected authority, or of an authority found stale or revoked (Safeguard 5; see below).
Identity binding. The session principals are the attested entity AIDs from step 1–2. For each direction the KERI-authenticated presenter AID, ZKC-verified credential-subject AID, and responder AID MUST be identical; the APR signer key MUST resolve through the verifier AID's current KEL; and the scope and proof context MUST recompute under §5.9. This is the proof-borrowing guard: a proof produced for another holder/AID, verifier, policy, profile, or session is rejected even if its predicate result is otherwise true.
Safeguard 6 is what makes this safe, and is why the path is viable. The party demanding "prove you are compliant to transact with me" is a verifier in the ZKC sense; Safeguard 6 requires it to authenticate the demand under a stable identity and leaves the responder with portable evidence of the demand. ZKC v0.4 scoped presentation further prevents the accountability record from forcing global holder linkability.
Interaction-boundary only (Safeguard 5). A declined or failed attestation means the counterparty declines the interaction, and nothing more. It MUST NOT gate, freeze, redirect, or seize either party's funds or notes; it MUST NOT touch ZKA settlement or ZKC credential validity; and exit remains unconditional. Open federation changes who may open a session with whom and how that trust is shown — it changes nothing about value.
What open federation does NOT provide — discovery and rendezvous (§8.8). This subsection covers trust establishment once the two parties are in contact. It does not specify how two strangers find each other or how first contact is routed. A queryable directory mapping tenant AIDs to routes would itself be the coordination graph that operator-blindness (OB-2, §3, §8) exists to prevent. Until a dedicated discovery/rendezvous layer with its own privacy analysis exists (a §10 open item), open federation is usable only where first contact is established out-of-band. AFP deliberately solves the trust half and leaves the discovery half explicitly out of scope, rather than admitting a directory that would weaken OB-2.
Relationship to the Introduction Tier (§14). Open federation and the introduction tier (§14) are the two ways AFP admits a session between tenants with no pre-existing agreement; a deployment MAY offer either or both. They differ in what they leave behind: §5.8 establishes trust per interaction with no agreement (lightest; suited to one-off or low-stakes exchange), whereas §14 runs an introduction session that mints a signed provisional §5.5 agreement and grows a progressive, ZK-provable reputation envelope (heavier; suited to an ongoing relationship). Both inherit all six safeguards, are interaction-boundary-only (Safeguard 5), and share the same unresolved stranger-rendezvous problem (§10). The three trust bases are enumerated together in §12.1.
5.9 ZKC v0.4 presentation profiles and AFP identity continuity
AFP consumes ZKC v0.4.0 presentations through zka:bundle:v1; ZKC remains authoritative for the v1/v2 envelopes, circuits, APR signatures, pseudonym/scope derivation, and proof verification. AFP is authoritative for selecting the privacy profile a flow may consume and binding the verified ZKC result to AFP identity and session state.
| AFP flow | Accepted profiles | Default | Downgrade rule |
|---|---|---|---|
| Tenant→operator telemetry (§6) | anonymous, scoped, linkable | anonymous | scoped→anonymous MAY be accepted when the predicate and policy window require no cross-presentation continuity |
| Known-counterparty bilateral (§5.5 / §12) | anonymous, scoped, linkable | anonymous | scoped→anonymous MAY be accepted when KERI/session authentication already supplies all required continuity |
| Introduction (§14) | scoped, linkable | scoped | no anonymous downgrade; the presentation becomes the credential basis of an ongoing provisional agreement |
| Open federation (§5.8) | scoped, linkable | scoped | no anonymous downgrade; the presentation is the per-interaction trust basis that opens the session |
In every row, zkc:presentation:linkable:v1 is the frozen v1 proof path and requires an explicit, recorded holder consent for that presentation. It is never selected by fallback, downgrade, verifier preference, or an absent APR presentation block.
Scoped policy derivation. AFP scoped requests use the signed ZKC scopePolicy string protocol:afp:<profile_policy_hash>, where profile_policy_hash is the exact session, telemetry, or introduction policy hash authenticated for the interaction. ZKC derives scopeId from the authenticated APR signer public key, that scope-policy string, and its declared rotation epoch (ZKC v0.4.0 §5.9.1). The holder derives the identifier itself; AFP MUST reject a verifier-supplied literal scope. The APR signing key MUST be the current KEL-anchored key for apr_verifier_aid; swapping either the AID or key fails verification.
AID, credential subject, and context binding. A proof accepted for an AFP responder MUST cryptographically prove the credential's subject-AID claim equals that responder's KERI entity AID. AFP then requires the KERI-authenticated envelope presenter to be the same AID. The ZKC proof context is the canonical digest of:
interface AfpZkcPresentationContextV1 {
kind: 'afp:zkc-presentation-context:v1';
credential_subject_aid: string; // == KERI-authenticated presenter entity AID
session_context: string; // exact §5.2 / §12.3 context
apr_verifier_aid: string; // APR signer key resolves here through its KEL
presentation_profile: string; // active, not merely requested, profile
scope_policy: string | null; // protocol:afp:<profile_policy_hash> iff scoped
}The credential issuer signature binds the subject-AID claim to the credential and private holder commitment; the ZKC proof binds that credential subject and the active profile handle to this context; the AFP envelope signature binds the same result to the presenter's KERI AID. Consequently a proof borrowed from an unrelated holder, AID, APR signer, scope, profile, or session cannot be repackaged: at least one issuer claim, ZKC public input, KEL key binding, or AFP context comparison fails. A boolean envelope label that merely says “verified” is insufficient; a conforming AFP implementation MUST invoke a ZKC v0.4 verifier and consume its verified public statement.
Version and handle exclusivity. Anonymous and scoped presentations use zkc:proof:v2; linkable presentations use the frozen zkc:proof:v1. Every one is carried in ZKA v0.9.0-draft zka:bundle:v1. AFP receipts, rotation presentations, and other AFP-authored artifacts expose no holder-derived handle beyond the active profile:
| Active profile | Permitted holder-derived fields |
|---|---|
| anonymous | none in AFP-authored artifacts; presentationPseudonym == 0 and scopeId == 0 occur only as sentinels in the ZKC v2 proof envelope |
| scoped | presentationPseudonym, scopeId |
| linkable | holderCommitment only |
Mixing handle families is a conformance failure. In particular, a scoped receipt or rotation artifact MUST NOT carry holderCommitment. An anonymous ZKC v2 proof envelope MUST carry the two zero sentinels required by ZKC, while an AFP-authored receipt or rotation artifact MUST omit all three holder-derived fields; implementations MUST NOT serialize the proof-envelope sentinels into those AFP artifacts.
Conformance. Implementations MUST include negative cases for a swapped presenter AID, credential subject/holder, scope, APR signer key or AID, session/proof context, and presentation profile. They MUST also demonstrate that anonymous presentations remain accepted in telemetry and known-counterparty bilateral flows where continuity is unnecessary, that an anonymous rotation presents the credential as fresh with no rebind or artifact handles, that scoped continuity requires zkc:proof:rebind:v2, and that introduction/open-federation anonymous downgrades and unconsented linkable presentations are rejected.
6. Tenant ↔︎ Operator Telemetry
This section specifies the second relationship: an operator learning that the platform is healthy without learning what tenants do. It rides on ZKC.
6.1 Model
The operator needs operational assurance — that instances are running supported, attested builds and behaving within agreed bounds. Under the operator-blindness invariant (§3), the operator obtains this assurance only through claims a tenant proves, never through observation of tenant work or the coordination graph.
v0.3.0 retires the v0.2.0 two-layer telemetry model in which "Layer 1" was transport-observed per-tenant metadata. Under §3.2, per-tenant transport observation is not a capability the operator is permitted to have. Therefore all per-tenant operator knowledge is ZKC-proved. Nothing in the operator-health story may be load-bearing on the operator watching individual tenant traffic.
Telemetry uses the §5.9 flow policy. zkc:presentation:anonymous:v1 is the default because the authenticated tenant→operator channel and the predicate's public tenant/operator/window inputs already provide interaction identity. An operator MAY request scoped continuity across one declared telemetry policy window; the tenant MAY downgrade that request to anonymous when the predicate does not require cross-presentation continuity. Linkable v1 is accepted only after explicit holder consent and MUST NOT be required as the ambient telemetry mode.
6.2 What the operator may learn
The operator's per-tenant knowledge is exactly the set of claims in the telemetry predicate catalog (§6.3), each delivered as a ZKC proof. Separately, the operator MAY compute aggregate operational metrics across the whole tenant base (for example, total compute consumed, total error count) provided the aggregate is computed so that no per-tenant value is recoverable from it — by aggregation with a sufficient tenant count, or by differential privacy. Aggregate metrics MUST NOT be derived in a way that reveals or narrows any individual tenant's value, and MUST NOT reveal the coordination graph.
6.3 Telemetry predicate catalog
Operator-facing claims are ZKC proofs. Each claim is one atomic predicate (Safeguard 2). AFP defines the starting predicate catalog below; each predicate circuit MUST be open-source, versioned, and published with a public review period before it is accepted by an operator (§9 Safeguard 4). A telemetry proof that uses an unregistered predicate identifier, an unsupported predicate version, or a predicate outside this catalog is not AFP-conforming.
| Predicate ID | Claim | Issuer model | Public inputs | Private witness | Cadence | Failure signal |
|---|---|---|---|---|---|---|
afp.telemetry.instance_attestation.v1 |
"I am a verified ADAMAS instance, build hash X" | Third-party-issued: the ADAMAS publisher signs a build manifest; the tenant proves possession. | Tenant AID, entity AID or instance AID, build manifest digest, publisher AID, predicate version, telemetry window, operator AID. | Publisher-issued ZKC credential, holder binding key, instance signing key. | At bootstrap, after build change, and at least once per operator policy window. | instance_attestation_missing or instance_attestation_invalid. |
afp.telemetry.instance_attestation.v2 |
"This machine subject runs the attested manifest under verifier-accepted root class R and authority A." | Pluggable, as classified by §6.3.1: third-party-verifiable build provenance or TEE attestation, or a self-attested publisher manifest. | Tenant AID; entity AID or instance AID; attested manifest digest; root class; root-authority identifier; root-material digest; attestation-evidence digest; verification-policy digest; public-sample-binding digest (the zero sentinel only where §6.3.1 permits it); predicate ID and version; attestation window; verifier AID; active presentation profile. | ZKC machine build/runtime credential and holder binding key; instance signing key; attested manifest; root-specific provenance, inclusion, quote, endorsement, or publisher-signature evidence and openings; public-sample-binding witness when required. | At bootstrap, after any manifest, root authority, root material, measurement, endorsement/collateral, or verification-policy change, and at least once per verifier policy window. | instance_attestation_missing, instance_attestation_invalid, instance_attestation_unsupported_root, instance_attestation_untrusted_authority, instance_attestation_provenance_not_in_log, instance_attestation_measurement_mismatch, or instance_attestation_sample_binding_missing. |
afp.telemetry.build_currency.v1 |
"I run a supported build" | Third-party-issued: the publisher signs the supported-build set; the tenant proves set membership. | Tenant AID, build manifest digest, supported-build-set root, publisher AID, support-policy version, telemetry window, operator AID. | Membership path for the build digest and publisher-issued support credential. | At bootstrap, after build change, and whenever the supported-build-set root changes. | build_currency_missing, build_currency_expired, or unsupported_build. |
afp.telemetry.compute_quota.v1 |
"My consumption is within agreed bounds" | Self-attested: the tenant signs a usage-log root and proves the bound. | Tenant AID, usage-log root, quota policy digest, committed usage upper bound, telemetry window, operator AID, afp:telemetry-sample-binding:v1 digest. |
Usage-log inclusion/range witnesses, tenant usage signing key, and sample-binding witness from §6.4. | Once per quota window and after any policy-window close. | compute_quota_missing, compute_quota_invalid, or compute_quota_over_bound. |
afp.telemetry.audit_log_integrity.v1 |
"My audit log has not been truncated" | Self-attested: the tenant signs the current append-only audit-log head. | Tenant AID, previous accepted log head, current log head, append-only accumulator root, telemetry window, operator AID, afp:telemetry-sample-binding:v1 digest. |
Append-only proof from previous to current head, tenant audit signing key, and sample-binding witness from §6.4. | Every audit window and after recovery from a missed window. | audit_log_missing, audit_log_invalid, or audit_log_truncated. |
Every operator-facing catalog proof MUST bind the tenant AID, operator AID, predicate ID, predicate version, telemetry subject, telemetry window, and active ZKC presentation profile into the proof transcript. For v2, these values map to verifier_aid = operator AID and attestation_window = telemetry window under §6.3.1. Scoped proofs additionally bind protocol:afp:<telemetry-policy-hash>, the APR signer, pseudonym, and scopeId per §5.9. This prevents replay across verifier, subject, window, policy, profile, or scope. Predicate versions are monotonic: a verifier MAY accept multiple versions during a migration window, but it MUST publish the accepted version set and MUST NOT reinterpret a v1 predicate or linkable presentation under later semantics.
Third-party-issued predicates prove what a tenant is. Self-attested predicates prove how a tenant behaves and MUST carry the public-sample binding of §6.4. build_currency, compute_quota, and audit_log_integrity retain those classifications. Instance attestation is versioned separately: v1 retains its frozen publisher-issued classification and semantics, while v2 uses the root-class taxonomy and binding rules below. Both versions are usable operator-facing; new counterparty-facing policy in §14 uses v2 so it can select a vendor-neutral root. The delegated, product-scoped publisher construction of §6.3.1 is v2-and-forward only: v1's publisher AID is a frozen, hard-wired field and acquires no delegation, organizational-root, or authority semantics from it.
6.3.1 Instance-attestation root classes
afp.telemetry.instance_attestation.v1 is frozen. Its public inputs, circuit, private witness, cadence, failure signals, and catalog row above remain unchanged; implementations MUST NOT reinterpret its publisher AID as a generic authority field, as an organizational root AID, or as the delegated product-scoped publisher AID of the v2 construction below, and MUST NOT attach v2 semantics to a v1 proof. A v1 publisher AID denotes exactly what it denoted when v1 was frozen. The freeze is semantic as well as textual: a v1 proof reinterpreted under the delegated-publisher construction produces no wire-visible difference, so the prohibition, not a wire check, is what preserves the frozen meaning.
afp.telemetry.instance_attestation.v2 fixes the generic statement. Its wire-level public statement is:
interface AfpInstanceAttestationPublicInputsV2 {
kind: 'afp:instance-attestation-public-inputs:v2';
tenant_aid: string;
subject_aid: string; // entity AID or instance AID
attested_manifest_digest: string; // canonical digest, defined below
root_class: 'root:build-provenance' | 'root:tee-attestation' | 'root:publisher-manifest';
root_authority_identifier: string;
root_material_digest: string;
attestation_evidence_digest: string;
verification_policy_digest: string;
public_sample_binding_digest: string; // canonical digest or exact zero sentinel
predicate_id: 'afp.telemetry.instance_attestation.v2';
predicate_version: '2';
attestation_window: { starts_at: datetime; ends_at: datetime };
verifier_aid: string;
presentation_profile: string; // active §5.9 profile
}For tenant→operator telemetry, verifier_aid MUST equal the telemetry operator AID and attestation_window MUST equal the telemetry window. For §14 counterparty use, verifier_aid MUST equal the APR's apr_verifier_aid; the APR signing key MUST resolve through that AID's current KEL, and a delegated verifier AID MUST resolve through its delegation chain to the counterparty tenant AID. attestation_window MUST equal the explicit attestation_window on afp:introduction-request:v1 and MUST be bounded by the session expiry. The proof context additionally binds the APR signer, session context, scope policy, and active presentation profile. A proof made for one flow, verifier, or window MUST NOT verify in the other.
Symmetrically, where an authority delegates product-scoped signing (below), two obligations apply and they are discharged differently. The delegated signing AID's delegation chain MUST resolve to the AID named by root_authority_identifier, and the verifier performs that resolution out of circuit, before acceptance, exactly as it resolves a delegated verifier AID above — that resolution reads only published KEL events, which the verifier holds. The manifest signing key MUST be the current signing-key state of that delegated AID, and that obligation is discharged indirectly, because the signed manifest and its signature stay in the private witness by design and the verifier never sees them: the verifier recomputes root_material_digest from the signing-key state it resolves (below), and the circuit proves the hidden signature verifies under exactly that committed state. The circuit continues to bind only the leaf signing-key state committed by root_material_digest, and no branch obligation below is changed by the presence of a delegation. A verifier that cannot resolve the chain to the named authority root MUST reject the proof with instance_attestation_untrusted_authority.
Canonical encoding (normative). Every non-sentinel *_digest is the ASCII string sha256: followed by exactly 64 lowercase hexadecimal characters. AFP-authored JSON artifacts use RFC 8785 JSON Canonicalization Scheme (JCS) bytes and MUST contain a kind discriminator; AFP's datetime strings first normalize to whole-second UTC YYYY-MM-DDTHH:MM:SSZ. External provenance statements, transparency-log entries/checkpoints, hardware reports/collateral, and publisher-signed manifests MUST NOT be re-encoded as AFP JSON. Their digest is SHA-256 over ASCII("AFP-External-Artifact-v2\0") || u32be(len(kind)) || UTF8(kind) || u32be(len(scheme)) || UTF8(scheme) || u64be(len(signed_bytes)) || signed_bytes, where signed_bytes are the exact scheme-native signed bytes and kind and scheme are nonempty ASCII identifiers fixed by the accepted verification policy. This wrapper domain-separates artifact types without changing the signed payload.
Publisher signing-key state (normative). For root:publisher-manifest, root_material_digest commits to the publisher's signing-key state. That state is neither an external provenance artifact nor a transparency-log checkpoint nor hardware collateral, so it is AFP-authored and therefore carries a kind under the rule above. Its preimage is the RFC 8785 JCS bytes of exactly:
interface AfpPublisherKeyState {
kind: 'afp:publisher-key-state:v1';
authority_aid: string; // == root_authority_identifier
signing_aid: string; // delegated signing AID; == authority_aid when signing is not delegated
delegation_anchor_dig: string | null; // said of the authority-KEL event anchoring the delegated inception; null iff signing_aid == authority_aid
key_state_event_dig: string; // said of the establishment event, in the signing AID's KEL, fixing the state below
key_state_sequence_number: number; // that event's sequence number in the signing AID's KEL
signing_public_keys: string[]; // ordered current signing keys, exactly as the establishment event lists them
signing_threshold: string | string[] | string[][]; // that event's `kt`, copied verbatim from the establishment event
next_key_digests: string[]; // ordered pre-rotation commitments from the same event, verbatim
next_threshold: string | string[] | string[][]; // that event's `nt`, copied verbatim from the establishment event
}Field encodings are KERI-native and are never re-encoded. delegation_anchor_dig and key_state_event_dig carry the KERI SAIDs of those events exactly as the KEL lists them (CESR-qualified), and each element of signing_public_keys and next_key_digests carries the corresponding key or pre-rotation commitment exactly as the establishment event lists it. signing_threshold and next_threshold carry the establishment event's kt and nt values verbatim — a hex string for a simple threshold, or the nested list of fraction strings KERI uses for a weighted multisig threshold, which is why the type is a union. These fields are KERI-native identifiers and values, they follow the _dig convention this specification already uses for KEL event digests (§4.5.1, §4.6.1, §12.2.1), and they are NOT re-encoded under the sha256:-prefixed *_digest rule above. RFC 8785 JCS then canonicalizes the record — including a nested threshold — deterministically.
root_material_digest is sha256: followed by the lowercase hex SHA-256 of those JCS bytes. A verifier MUST recompute this digest from the key state it independently resolves out of the signing AID's KEL, reached through the delegation anchors in the authority's KEL (§4.2, §4.4), and MUST reject a mismatch with instance_attestation_untrusted_authority. Where signing is not delegated the signing AID is the authority AID and the two KELs coincide. Every field is derived from published KEL events, so the record is a re-encoding of state the verifier already holds; it is never transmitted as an AFP message, adds no public input, and adds no wire field. For root:publisher-manifest, the same record MUST also appear in the circuit's private witness: the circuit reconstructs its RFC 8785 JCS bytes, opens root_material_digest as their SHA-256 digest, and uses that record's signing_public_keys and signing_threshold for publisher-signature verification. The circuit binding and the verifier's independent recomputation are both mandatory and prove different facts: the former binds the hidden signature to the public digest, while the latter binds that digest to authority-anchored public KEL state. For root:build-provenance and root:tee-attestation, root_material_digest retains the external-artifact meaning fixed in their branches below and MUST NOT use this record.
The exact absent-sample sentinel is sha256:0000000000000000000000000000000000000000000000000000000000000000, which MUST NOT be accepted as an artifact digest. The circuit decodes each real digest to 32 bytes and exposes it as two unsigned 128-bit big-endian limbs (high limb, then low limb). It maps root_class to 1, 2, or 3 in the order shown above. Each string public input maps separately to two limbs as SHA-256 over u32be(byte_length) || UTF8(NFC(string)). The attestation_window object maps to two limbs as SHA-256 over its RFC 8785 JCS bytes and exposes starts_at and ends_at additionally as unsigned 64-bit Unix seconds. Verifiers MUST recompute every mapping and reject noncanonical strings, timestamps, digests, sentinels, or limb values. This construction uses SHA-256, not H(domain, x...), and therefore adds no AFP-KDC field-domain tag.
The root class MUST be one of the three literal values above. The root-authority identifier MUST resolve, under the verifier's authenticated verification-policy digest, to trust material the verifier accepted before receiving the proof. It is not a prover-selected display name.
For an organizational attestation root authority — the case root:publisher-manifest presents, and the case any KERI-rooted authority presents under any class — the authority is the accountable party that stands behind the attestation, identified by its organizational root AID, never a product, brand, build line, or software distribution. For such an authority, a root-authority identifier naming a product rather than the party accountable for it MUST NOT be accepted. This does not disturb the other two classes: for root:tee-attestation and root:build-provenance the root-authority identifier retains the branch-specific meaning fixed below — a hardware vendor's product root policy resolved from the verifier's own pinned vendor-root configuration, and a transparency log's checkpoint signer under the verifier's accepted log policy — and this paragraph neither restates nor overrides it.
An organizational authority MAY delegate product-scoped signing: the signing AID is then a delegated AID whose inception is anchored in the authority's KEL, exactly as an entity is delegated from a tenant root (§4.2), and root-material digest MUST commit to that delegated signing-key state. Rotation, compromise handling, and succession of a delegated signing key MUST run as explicit, published events — the delegated inception and each delegated rotation as events in the signing AID's KEL, each anchored by a seal in the authority's KEL (§4.2, §4.4, §4.6). It is through those anchors that the authority remains the authoritative source for the signing key's state and for the stale/revoked determination below, and this holds for any authority identified by a KERI AID without reference to any registry, advisory feed, or vendor document. For root:build-provenance and root:tee-attestation the equivalent determination is made against the log-policy and pinned vendor-root material fixed in those branches. What holds for every class, without exception, is that no authority-published feed is ever on the normative path. The binding between an authority AID and the legal entity it denotes is established out-of-band, at the moment a verifier accepts that authority's trust material into its verification policy; AFP defines no on-wire representation of that binding and no protocol step that establishes it.
root-material digest commits to the exact accepted transparency-log checkpoint, hardware endorsement/collateral set, or publisher signing-key state used for this proof. attestation-evidence digest has the branch-specific meaning fixed below and commits to evidence without exposing the raw provenance bundle, TEE quote, or signed manifest. The separately versioned ZKC machine build/runtime credential schema defines the provider-neutral claim consumed here; AFP binds that claim to these public inputs and does not redefine the credential schema or issuer-adapter stages.
The v2 circuit MUST prove all common properties: the prover holds an authentic, unexpired machine build/runtime credential and its holder binding; the credential's machine subject equals the KERI-authenticated entity or instance AID; the instance signing key is controlled by that subject; the hidden manifest opens to attested manifest digest; the hidden root evidence opens to attestation-evidence digest; the credential claim, root evidence, and manifest agree on the root class, root authority, machine subject, and manifest; and the proof transcript binds every v2 public input plus the active §5.9 presentation profile. It MUST then enforce the branch below for the declared root class. A verifier MUST reject an unknown class, a class/authority mismatch, a stale or revoked authority, a branch proof produced for another class, or a nonzero/zero public-sample-binding digest that violates that class's rule. It MUST also reject a root-material digest absent from its accepted policy for root:build-provenance and root:tee-attestation, where that digest names an externally pinned checkpoint or vendor-collateral set. For root:publisher-manifest the accepted policy binds the authority AID, not a key-state digest: root_material_digest is a per-key-state value that changes at every published rotation, and it is validated by recomputation from the resolved KEL state under the publisher-key-state rule above rather than by presence in a pre-signed list. A verifier's policy therefore does not have to be re-signed on every publisher rotation, and a policy that does additionally pin a specific publisher key-state digest pins that key state and nothing more.
Two clarifications govern that list where signing is delegated. Agreement on the root authority is agreement on the identifier value: the credential claim, root evidence, and manifest MUST agree on the root-authority identifier the proof declares, and the circuit is not required to prove the delegation chain behind it — the verifier resolves that chain out of circuit before acceptance, as stated above. For a KERI-rooted authority, staleness and revocation are resolved from its KEL: where the root-authority identifier is a KERI AID — the organizational case, and the only case root:publisher-manifest admits — a verifier MUST determine that authority's current key state, revocation, and succession from the authority's KEL and the KELs it anchors, under §4.2, §4.4, and §4.6, and MUST reject a proof whose committed signing-key state is superseded by a published rotation, whose delegated signing AID's delegation has been revoked, or whose authority root key state the verifier can no longer resolve. This parallels the kel_keystate_stale rejection of §12.3. For root:build-provenance and root:tee-attestation — whose authorities are a transparency log's checkpoint signer and a hardware vendor's product root, neither of which is required to be a KERI AID with a KEL — the same determination is made instead against the checkpoint-signer key and rotation state under the verifier's accepted log policy, and against the verifier's pinned vendor root configuration with its collateral freshness and revocation state, exactly as those branches already fix below. In every class the material is either self-certifying KERI state or material the verifier itself pinned; in no class is an authority-published feed on the normative path.
Authority-KEL integrity is not assumed. An authority is not a tenant (§2.1), so the §4.5 tenant-scoped witness duties do not attach to it of their own force. A verifier MUST therefore verify authority-KEL events — and the KEL events of any AID that authority anchors — against a witness set and threshold pinned in the authority trust material it accepted into its verification policy, applying the receipt-threshold rule of §4.5. A KEL that does not meet the pinned threshold, or that forks against events the verifier previously accepted, is treated as unresolvable authority key state and routes to exactly the consequence stated below, never to any other.
An authority MAY additionally publish an advisory or status feed covering its own key state and revocation status as a convenience; such a feed MUST NOT enumerate, rank, endorse, or report on any other authority (§1.2). No verifier is required to consult one, and no such feed is a normative path for discharging this obligation.
The consequence of any authority-related failure — an authority the verifier does not accept, an authority found stale or revoked, an unresolvable delegation, or unresolvable authority key state — is exactly the consequence of any other failure in this section and nothing more: in tenant→operator telemetry an alert under §6.6, and in every counterparty-facing use — a §5.4/§5.5 bilateral interaction, open federation (§5.8), and the introduction tier (§14.3) — declining the interaction and nothing beyond it. A rejected, stale, or revoked authority MUST NOT gate, quarantine, freeze, or otherwise act upon tenant state, a tenant's funds or notes, or any prior session, and MUST NOT force a downgrade of tenant state (Safeguards 3 and 5). A §14.7 contraction of the go-forward envelope is a form of declining to extend further credit and is unaffected by this sentence; it touches no prior session and no tenant state.
root:build-provenance.attestation_evidence_digestMUST be the external-artifact digest of the exact signed provenance statement. The private witness MUST include that statement, builder identity/signature evidence, artifact/subject mapping toattested_manifest_digest, the accepted scheme-specific log entry and leaf bytes, and the Merkle inclusion path. The verifier MUST authenticate the checkpoint signer named byroot_authority_identifier, recompute the checkpoint committed byroot_material_digest, apply its split-view/consistency policy against its last accepted checkpoint, and verify the ZKC circuit whose public statement names the exact statement digest and checkpoint. The circuit proves leaf inclusion: it MUST verify the builder signature and policy, exact manifest subject, that the accepted log's leaf-construction algorithm commits the scheme-specific log entry toattestation_evidence_digest, and that the resulting leaf, Merkle path, tree size, and root match the authenticated checkpoint. Together these checks are the required transparency-log inclusion verification; the log entry and path remain hidden. A signature without an authenticated checkpoint and successful inclusion branch is insufficient and MUST fail withinstance_attestation_provenance_not_in_log. A §6.4 public-sample binding is OPTIONAL because the independently obtained log checkpoint is its external root; the exact zero sentinel denotes absence. If nonzero, the binding MUST verify under §6.4 and commit to the same manifest, verifier, and attestation window.root:tee-attestation.attestation_evidence_digestMUST be the canonical digest of the exact hardware attestation report. The private witness MUST include that report, endorsement chain, freshness material, measurement, and manifest-to-measurement mapping accepted by policy. Its report-data field MUST equal SHA-256 over the canonical bytes of{kind:'afp:instance-attestation-report-data:v2', attested_manifest_digest, subject_aid, attestation_window, verifier_aid}. The circuit MUST prove this measurement-to-manifest binding, report signature/endorsement chain, allowed measurement underverification_policy_digest, and freshness forattestation_window.root_authority_identifierMUST name the hardware vendor/product root policy;root_material_digestMUST commit to the exact verifier-accepted vendor certificate roots, revocation status, and endorsement/collateral version. The verifier MUST resolve those values from its own pinned hardware-vendor root configuration, validate collateral freshness and revocation, and MUST NOT trust a vendor root supplied only inside the quote. A §6.4 public-sample binding is OPTIONAL because the independently authenticated report is the external root; the exact zero sentinel denotes absence, and a nonzero binding MUST verify against the same manifest, verifier, and window.root:publisher-manifest.attestation_evidence_digestMUST be the canonical digest of the exact publisher-signed manifest. Beyond the common witness above, the private witness for this branch MUST include exactly: that manifest, the publisher signature over it, and theafp:publisher-key-state:v1JCS bytes for the committed signing-key state. Beyond the common constraints above, this branch adds exactly four: that the hidden manifest opens toattested_manifest_digest; that the circuit reconstructs the publisher-key-state JCS bytes from the witnessed record and requires their SHA-256 digest to equalroot_material_digest; that the hidden signature verifies over the manifest under that record'ssigning_public_keysat itssigning_threshold; and that the record'sauthority_aidequalsroot_authority_identifier. These are the pre-existing branch obligation to bind signing-key state to both public values, made byte-exact by the record above; delegation does not add another circuit obligation. Establishing that the witnessed record is in fact the current state of the namedsigning_aid, and that the signing AID's delegation chain reachesroot_authority_identifier, is the verifier's independent out-of-circuit work stated above — recomputation from resolved KEL state, before acceptance.root_material_digestfor this class is theafp:publisher-key-state:v1digest of the (possibly delegated) signing-key state. Because a platform publisher is attesting its own build, this class is SELF-ATTESTED at the platform layer, not third-party-issued. Itspublic_sample_binding_digestMUST NOT be the zero sentinel and MUST identify a valid §6.4 public-sample binding over the same manifest, predicate version, attestation window, verifier, and assertion commitment; absence fails withinstance_attestation_sample_binding_missing. A deployment relying on this class alone MUST make the §3.5 honest disclosure that its instance authenticity rests on publisher self-attestation and a public consistency sample, not an independent build-provenance log or hardware root. It MUST NOT describe the result as third-party attestation.An authority under this class attests only manifests it itself signs. It makes no statement, warranty, or attestation about any build it did not produce — including forks, rebuilds, and derivative distributions — and a proof MUST NOT be read as one. This guarantee is not purely in-circuit, and an implementer MUST NOT treat it as if it were. The circuit proves that the hidden signature verifies under the exact key state committed by
root_material_digestand that the witnessed record names the declared authority; it does not prove that the record'ssigning_aidis actually delegated from that authority or that the record reflects current KEL state. Those facts rest on the verifier's mandatory out-of-circuit recomputation ofroot_material_digestfrom the KEL state it resolves through the authority's delegation anchors, together with the policy match on the accepted authority. Absent that recomputation, a party could construct a key-state record naming another organization's root AID asauthority_aid, commit its own keys in that record, sign its own manifest with them, and satisfy every in-circuit constraint — so a verifier that omits the recomputation forfeits the property and accepts forged attribution. A fork or derivative distribution stands up its own publisher authority under the identical rules of this section — it may attest its own builds on exactly the same terms — but it does not inherit, and cannot borrow, the authority of the upstream publisher.The disclosure duty is split and both halves apply. The deployment's §3.5 duty stated immediately above is unchanged and binds a conformance subject (§1.5). The authority's half is stated as a verifier acceptance condition, because an authority is not an AFP principal and not an AFP conformance subject (§1.5, §2.1): a verifier MUST NOT accept an authority under this class unless the trust material it accepted characterizes the attestation as platform-layer self-attestation rather than as third-party attestation. An authority that wishes to be accepted under this class therefore characterizes its own attestation truthfully in the trust material it publishes (§3.5) — that is where the duty is discharged, and it is not discharged in, and does not re-home, the §6.4 public-sample binding, which is produced and signed by the tenant. This is one of exactly two acceptance conditions this section states over accepted trust material — the other is the pinned witness set and threshold above — and together they are the whole of the normative standing AFP grants an authority's published material (§10).
Operational-status disclosure, on every path. Where a named authority's KEL and published trust material do not yet exist, a deployment selecting that authority MUST disclose the path as spec-available but not operational under §3.5 and MUST NOT claim an operational attestation path — on the tenant→operator telemetry path, on a §5.4/§5.5 bilateral path, and on the §5.8 open-federation path alike. Where the deployment also runs the §14 introduction tier, §14.9 applies additionally and in its own terms.
The operator-facing predicate catalog remains closed. Safeguard 1 permits each bilateral verifier to choose which of the three registered root classes and which authorities it accepts; it does not permit an implementation to invent a fourth class or accept an unregistered telemetry predicate. Extending the root-class taxonomy requires a revision to this section.
Standing up a new root authority does not. The set of authorities is open and unenumerated: any organization MAY stand as an authority under any of the three classes, on the identical terms this section states, and does so by publishing trust material a verifier can accept — not by registration with, permission from, notification to, or a grant by any party, and not by an amendment to this specification. Whether a given verifier accepts a given authority is that verifier's policy decision alone (Safeguard 1, §9). AFP maintains no authority registry and defines no procedure for admitting, ranking, suspending, or removing an authority. Naming an authority in this specification records an instance of the construction; it neither privileges that authority nor closes the set.
Shipped defaults are product configuration (normative). An implementation MAY ship a default verification policy naming authorities it recommends. Such an entry MUST be visible to, editable by, and removable by the deployment operating it; it MUST NOT be required for conformance, and an implementation MUST NOT hardcode an authority entry a deployment cannot remove. It is product configuration and not a default of this specification: this specification defines no default authority and no default (root class, root authority) pair. A shipped entry applies to an interaction only once the party operating the deployment signs it into that interaction's policy (§5.5, §14.4). The §9 Safeguard 1 and §12.8 rows state how this bears on the safeguards; this paragraph is where the obligation itself lives.
Non-normative — the first named authority. None of the following is yet operational. Pyramidal Inc.'s organizational root AID has not been incepted, and no AID value is asserted here or anywhere else in this specification. What follows records the intended instantiation of the construction above so that the normative text can stay authority-agnostic, exactly as ADAMAS is cited as the reference implementation without becoming the definition (§1.5.1, §2.1). Nothing in this note is a requirement of this specification; every requirement it alludes to is stated in the normative text above and binds there, not here.
- Pyramidal Inc. is the first named attestation root authority. For proofs it stands behind,
root_authority_identifieris intended to resolve to Pyramidal Inc.'s organizational root AID — the accountable legal entity, not the ADAMAS product.- ADAMAS build manifests are to be signed by a delegated, product-scoped ADAMAS publisher AID whose inception is to be anchored in Pyramidal's KEL, mirroring the tenant→entity delegation of §4.2.
root_material_digestwould then commit to that delegated signing-key state asafp:publisher-key-state:v1above, with rotation, compromise handling, and succession of the publisher key running as explicit events in the publisher AID's KEL, each anchored by a seal in Pyramidal's organizational KEL.- The ADAMAS product line attests under
root:publisher-manifestand is therefore self-attested at the platform layer, with the mandatory §6.4 public-sample binding and both halves of the disclosure duty above. Pyramidal attests only ADAMAS builds it produces; a fork or third-party distribution stands up its own authority under the same rules.- Once incepted, Pyramidal Inc.'s organizational root AID is intended to ship as the default entry in ADAMAS verification policies. That would be a shipped product configuration — visible, editable, and removable by the deployment operating it, per the normative paragraph above — never a default of this specification and never a requirement of it (§9, §12.8, §14.4).
- The AID↔︎legal-entity binding would be established out-of-band when a verifier accepts Pyramidal's trust material, per the rule above.
- Because the authority's KEL and published trust material do not yet exist, the operational-status disclosure stated normatively above applies today to any deployment selecting this root path, and §14.9 applies additionally where that deployment runs the introduction tier.
- Pyramidal Inc. is also an AFP operator and is constrained as one by §8.6. The two roles are independent in both directions; neither transfers trust to the other.
6.4 Public-sample consistency
A self-attested claim is only as good as its resistance to a lying tenant. AFP requires that each self-attested predicate be paired with a public sample the verifier can check the signed assertion against — for example, an aggregate count or a digest the verifier independently holds — and that the proof demonstrate cryptographic consistency between the signed assertion and the sample.
For a root:publisher-manifest proof (§6.3.1), the mandatory public-sample binding is produced and signed by the tenant proving its own instance, as this section's artifact requires; it is not produced or signed by the attestation authority, and the authority's separate duty to characterize its own attestation (§6.3.1) is discharged in that authority's published trust material rather than here.
The consistency artifact is a ZKC proof over a tenant-signed sample binding:
interface AfpTelemetrySampleBinding {
kind: 'afp:telemetry-sample-binding:v1';
tenant_aid: string;
predicate_id: string; // ZKC predicate identifier from §6.3
predicate_version: string;
telemetry_window: {
starts_at: datetime;
ends_at: datetime;
};
assertion_commitment: string; // commitment to the private self-attested value
sample_kind: 'aggregate' | 'selective-reveal' | 'external-digest';
sample_commitment: string; // commitment to the verifier-checkable sample
sample_policy: {
min_population?: number; // REQUIRED for aggregate samples
differential_privacy?: {
epsilon: number;
delta: number;
};
revealed_fields?: string[]; // REQUIRED for selective-reveal samples
external_source_aid?: string; // REQUIRED for external-digest samples
};
sample_context_hash: string; // hash of cadence, operator, and predicate context
issued_at: datetime;
tenant_signature: string;
zkc_proof: string; // proves consistency without revealing hidden values
}The zkc_proof MUST prove all of the following:
- the prover controls
tenant_aidand signed the binding; assertion_commitmentcommits to the same private value used by the self-attested telemetry predicate;sample_commitmentcommits to a verifier-checkable sample whose declaredsample_kindandsample_policyare satisfied;- the assertion is included in, bounded by, or equal to the public sample according to the predicate-specific rule; and
- the proof binds
predicate_id,predicate_version,telemetry_window,sample_context_hash, and the operator-facing telemetry subject so the sample cannot be replayed across predicates, operators, or windows.
v2 counterparty mapping (normative). The afp:telemetry-sample-binding:v1 field names remain frozen for telemetry compatibility. For v2 sample bindings, including mandatory root:publisher-manifest bindings used in §14, telemetry_window MUST equal the v2 attestation_window; the operator component inside sample_context_hash MUST be replaced by verifier_aid; and the operator-facing telemetry subject component MUST be replaced by the exact AFP introduction session context. Requirement 5 above is evaluated with those substitutions, so the binding cannot replay across counterparty verifier, APR, session, or window. The binding's tenant signature and proof context MUST also bind afp.telemetry.instance_attestation.v2, its active §5.9 profile, and the selected root class/authority. These mappings do not change the v1 artifact's wire fields and MUST NOT be applied to frozen instance_attestation.v1.
The allowed sample forms are intentionally narrow:
- Aggregate sample. The tenant proves that its committed value is included in an aggregate whose population is at least
min_population, or that the aggregate is differentially private under the declared(epsilon, delta). The aggregate MUST NOT expose any individual tenant value or coordination relationship. - Selective-reveal sample. The tenant reveals the minimum fields required for the verifier to recompute the predicate-specific rule and proves that the unrevealed fields remain bound to the same assertion commitment.
- External-digest sample. A non-operator source, named by
external_source_aid, publishes a signed digest or Merkle root the verifier independently obtains. The tenant proves consistency with that digest without giving the operator access to the underlying per-tenant log.
Under §3, the operator has no per-tenant transport observation to use as a sample. A public sample for a self-attested predicate MUST therefore be one of the forms above; it MUST NOT be derived from operator surveillance of tenant traffic, message timing, counterparty identity, coordination volume, or any other coordination-plane observation.
If the sample binding is missing, expired, inconsistent with the predicate, or uses a sample form outside this section, the proof fails. In tenant→operator telemetry, §6.6 applies: the result is an alert only, never operator intervention in tenant state. In §14 counterparty use, the verifier declines the introduction interaction under §14.3; it MUST NOT form the provisional agreement, gate tenant state, or impose any other consequence.
6.5 Push model and cadence
Telemetry is tenant-push. A tenant publishes telemetry proofs on a telemetry subject the operator subscribes to; the tenant controls timing. The operator monitors cadence — a missing proof within an expected window is itself a meaningful operational signal — but cadence is established by the presence or absence of pushed proofs, not by transport observation of the tenant's other traffic.
6.6 Failure semantics
A failed or missing telemetry proof MUST NOT trigger any automated operator intervention in tenant state. Per Safeguard 3 and the operator-blindness invariant, the operator has no such capability and the protocol grants none. A telemetry-proof failure propagates as an alert on a separate operator-monitored subject; human investigation follows. This is the structural difference between AFP operator telemetry and a ZKC v0.4 compliance gate: a gate is allow-or-deny; AFP telemetry is observe-and-alert. AFP defines no gate over tenant state for any party.
6.7 Topology independence
The tenant↔︎operator protocol surface is identical whether the tenant runs on a managed transport or a self-hosted appliance. In every topology, the operator learns only what the tenant proves. There is no deployment in which the operator learns more by observation; §3 forbids it. The appliance case is therefore not a privileged tier for telemetry — it is the same protocol — and the managed-transport case is held to the same structural standard.
7. Deployment Topologies
7.1 Auditable implementation
Every topology in this section is conformance-relevant only to the extent that the software realizing it can be checked: operator blindness is a claim about capability (§3.2), and a capability claim is verifiable only against an implementation open to inspection. This subsection states that requirement; it applies to §7.2 and §7.3 alike. Two consequences are normative:
- Auditability of OB. All OB-relevant behavior of a conforming tenant or transport implementation MUST be auditable: a tenant, or a third party, MUST be able to verify by inspection that no operator data path into tenant state exists. This is what makes OB a capability claim (§3.2) rather than a promise. Source availability is the canonical way to satisfy this requirement; ADAMAS, the reference implementation published by Pyramidal Inc., satisfies it under a Business Source License with a scheduled conversion to Apache 2.0. The same party publishes the ADAMAS build manifests attested under §6.3.1; auditability under this subsection and attestation under §6.3.1 are independent obligations, and neither substitutes for the other — an attested build is not thereby auditable, and an auditable implementation is not thereby attested.
- No privileged operator build. A conforming AFP implementation MUST NOT depend on a closed or operator-only build for any OB-relevant behavior. Anything load-bearing for §3 MUST be within the material an auditor can inspect under the first bullet; where that material is source, it MUST be the published source.
An implementation that cannot support inspection of its OB-relevant behavior MUST NOT claim OB as a capability (§3.2). Such an implementation falls under the honest-disclosure duty of §3.5: it MUST disclose the limitation to its tenants rather than represent OB as satisfied. Inability to support inspection is a limitation of the implementation, never a lesser standard of conformance — §3.2 admits no substitute for structural verifiability.
7.2 Appliance
An appliance is a self-hosted deployment of a conforming tenant that does not connect to an operator-run transport — not a distinct protocol target. For the reference implementation, it is offered as a hardware recommendation plus a licensed ADAMAS image and support plan. An appliance tenant runs the same AFP v0.3.0 as a managed-transport tenant. The appliance reaches the rest of the federation through the §8 routing layer; it does not connect to an operator-run transport that could observe it. Identity migration to or from an appliance is the one-move operation of §4.7.
7.3 No operator-run coordination transport
v0.3.0 does not define a deployment in which an operator runs the transport that tenant↔︎tenant coordination traffic transits. Such a deployment cannot satisfy OB-2 (§3.3) and is therefore not a conforming AFP v0.3.0 topology. Operators may run telemetry-subject infrastructure (§6.5) and may participate in the §8 routing layer as one relay among several, but no operator may occupy a position from which the coordination graph is observable.
8. Metadata-Privacy Routing
This section delivers OB-2 — the coordination-graph privacy of §3. It states the normative requirement (§8.1), the conformance properties any routing layer must satisfy (§8.2), the AFP Federation Mixnet — the concrete routing design v0.3.0 adopts (§8.3–§8.7) — private route resolution (§8.8), the relationship to honest disclosure (§8.9), and residual limitations (§8.10). The §8.2 properties are the conformance test; the §8.3–§8.8 design is the means by which v0.3.0 meets them. An implementation MAY satisfy §8.2 by another construction, but it MUST satisfy §8.2.
The threat model is explicit and was decided deliberately: the OB-2 adversary includes a passive observer of the network links between relays — a transit provider, an infrastructure operator, or a national-scale link observer — not merely a curious party running one piece of infrastructure. AFP's flagship use case is cross-jurisdictional collaboration between identifiable institutions; the existence and intensity of a coordination relationship is itself commercially sensitive, sometimes materially so, and the platform cannot ask a tenant to predict in advance which relationships are sensitive. The routing layer is therefore designed to defeat statistical traffic analysis by a global passive adversary, not only single-relay correlation.
8.1 Requirement
Tenant↔︎tenant coordination traffic MUST be routed such that no single transport operator, no single relay, no colluding minority of relays, and no passive observer of the links between relays can learn the pairing (tenant A AID, tenant B AID) for any coordination session, nor reconstruct it from timing or volume correlation.
8.2 Properties the routing layer must provide (conformance test)
A conforming routing layer MUST provide:
- Endpoint unlinkability. A relay forwarding a message sees, at most, one hop in each direction — never both the originating and terminating tenant of a session.
- No cleartext relationship in addressing. Routing headers and any identifier visible to a relay MUST NOT encode the tenant pair, and MUST NOT be stable across sessions in a way that lets a relay link a tenant over time.
- Correlation resistance. Timing and volume patterns MUST be treated so that a relay, a colluding minority of relays, or a passive observer of inter-relay links cannot reconstruct the pairing by correlation. This is the discriminating property and the reason a mixnet, not a simpler relay, is required.
- No single trusted relay. The layer MUST NOT contain a relay whose compromise reveals the graph. This is the §3.2 capability standard applied to the routing layer itself: decentralization of the relay set is required, not optional.
8.3 The AFP Federation Mixnet
v0.3.0 satisfies §8.2 with a self-contained, consortium-operated mix network of the Loopix family. "Self-contained" means the mixnet is operated by and for the federation; it is not a dependency on any public, token-incentivized, third-party anonymity network. "Loopix family" means the design class characterized by the Sphinx packet format, a stratified mix topology, continuous-time (exponential-delay) mixing, and mandatory cover traffic — the class with formal, published anonymity bounds against a global passive adversary.
Reference implementation. The preliminary reference implementation is Katzenpost (katzenpost/katzenpost, AGPLv3) — a Loopix-family mixnet using client-selected exponential mixing delays, a stratified topology, a configurable-geometry Sphinx, post-quantum cryptographic agility, and a decentralized directory-authority system. AFP v0.3.0 adopts Katzenpost as the reference implementation for development, not as a frozen normative dependency: Katzenpost is itself mid-evolution toward a next-generation protocol (the Echomix design) and has no confirmed large-scale production deployment as of this writing. Accordingly, §8 binds to the Loopix-family architecture (§8.2 properties), not to a specific Katzenpost version. An implementation tracks a named Katzenpost release; the AFP conformance test remains §8.2.
Reference maturity rule. A deployment that uses Katzenpost MUST publish the exact release, commit, configuration profile, and any local patches in its AFP conformance statement. Echomix is tracked as the expected next-generation Katzenpost protocol, but it is not a normative AFP dependency until AFP publishes a successor mixnet profile. A deployment MAY pilot Echomix-derived behavior only if the deployed behavior still satisfies §8.2 and the conformance statement identifies the deviation from afp.mixnet.profile.ob2-baseline.v1.
Why a mixnet and not a lighter relay. Simpler designs — VPNs with rotating exits, onion-routed relays without mixing, or queue-relay systems without cover traffic — defeat single-relay and IP-level correlation but do not defeat §8.2 property 3 against a link observer: a burst of real traffic leaving tenant A still correlates, by timing and volume, with the burst arriving at tenant B, regardless of how the source address is obscured. Only mixing delay plus cover traffic breaks that correlation. This is the property that selects the Loopix family.
8.4 Topology and packet format
The AFP baseline implementation profile is afp.mixnet.profile.ob2-baseline.v1. A deployment MAY define a stricter profile, but it MUST disclose any deviation and still satisfy §8.2.
| Parameter | ob2-baseline-v1 value |
Conformance rule |
|---|---|---|
| Packet format | Sphinx, fixed-size packets | Every coordination packet has identical observable size and form. |
| Hop geometry | 3 mix hops, one hop per layer | A deployment MUST NOT use fewer than 3 hops for OB-2 claims. |
| Layers | 3 stratified layers | No operator may control all nodes in any layer or enough adjacent-layer positions to observe a full path. |
| Payload class | 4 KiB encrypted AFP payload fragment per packet | Larger AFP messages are fragmented and padded into indistinguishable packets. |
| Per-hop delay | Exponential delay, mean 30 s | The delay is independently sampled per hop by the sender. |
| Maximum packet TTL | 15 min | Expired packets are dropped without revealing whether they were real or cover packets. |
| Tenant cover rate | At least 1 packet every 10 s per federated tenant | Real traffic is slotted into the same schedule; deployments may raise but not lower the rate. |
| Mix-node loop cover | At least 1 loop packet every 10 s per mix node per adjacent layer | Loop cover is independent of tenant activity. |
| Directory epoch | At most 24 h | Tenants MUST reject expired directories unless an emergency extension is KERI-witnessed. |
- Packet format. All coordination traffic is carried as fixed-size Sphinx packets. Every packet is identical in size and form; a mix node decrypts exactly one layer and learns only the immediately preceding and following hop. This delivers §8.2 properties 1 and 2 at the packet layer.
ob2-baseline-v1fixes the hop geometry and payload class above so implementers do not pick weaker deployment defaults. - Stratified topology. Mix nodes are arranged in ordered layers; a packet traverses one node per layer. Three layers are REQUIRED for
ob2-baseline-v1, so no single mix node sees both endpoints and the property is retained against a colluding pair that does not span all three layers. - Continuous-time mixing. Each mix node delays each packet by an independent, exponentially distributed random delay (client-selected per hop). This decouples the timing of a packet's arrival from its departure and is the core of §8.2 property 3. AFP coordination is not latency-critical (§5 work requests tolerate delay), so the baseline accepts multi-hop latency in exchange for correlation resistance.
8.5 Mandatory cover traffic
Cover traffic is REQUIRED, not optional, and is the property on which OB-2 against a link observer depends.
- Every tenant MUST emit coordination-plane traffic into the mixnet at a constant, scheduled rate at all times while federated, whether or not it has real coordination traffic to send. Real
afp.*messages (§5.3) are slotted into this constant stream; when there is no real message, a cover (loop) message is sent instead. Cover and real packets are indistinguishable to any relay or observer (identical Sphinx form, §8.4). - Mix nodes MUST additionally emit loop cover traffic, so that link-level traffic between mix nodes is constant independent of tenant activity.
- The cover schedule MUST be derived from the active mixnet directory epoch and the tenant's local scheduler, not from the presence of application work. Missed cover slots are an OB-2 health failure and MUST be disclosed in operator telemetry without identifying counterparties.
- Exploiting the bounded tenant set. Because the federation membership is bounded and enumerable (unlike an open consumer network), AFP can mandate constant emission from every participant. The observable consequence is the OB-2 guarantee in its strongest form: every tenant appears, to any link observer, to be emitting a constant stream at all times — an observation identical whether the tenant is coordinating with another tenant or with no one. The adversary's observation is the same in every world; the coordination graph is therefore not present in the observable traffic. A conforming deployment MUST run cover traffic at a fixed rate at every tenant and every mix node; a deployment that does not is not OB-2-conforming and MUST disclose this per §3.5.
8.6 Relay operation — no single trusted relay
- Mix nodes are operated by mutually distrusting parties — federated tenants, designated independent operators, or both — such that no single operator controls enough of any path to correlate endpoints.
- The operator (e.g. Pyramidal) MUST NOT occupy a position on the mix path from which the coordination graph is observable. Consistent with §7.3, the operator publishes the (source-available) mixnet software and MAY run a directory authority or witness, but MUST NOT be the sole operator of any mix layer, and MUST NOT run a mix node in a topological position that, alone, reveals a pairing. This is the §3.2 capability standard applied to the operator's own infrastructure.
- The operator role and the §6.3.1 attestation-authority role are independent. Where the same legal entity is both an AFP operator (§2.1) and a §6.3.1 attestation root authority — as Pyramidal Inc. is — neither role confers anything on the other: standing as an attestation authority confers no operator privilege and no relaxation of the constraint above or of §3, and operating a transport confers no standing as an authority and no claim on any verifier's policy. A verifier's acceptance of an authority is not a judgment about that party's infrastructure, and §3's operator-blindness invariant is a capability standard that no attestation reputation can substitute for.
- The mixnet uses no token and no cryptocurrency. Relay operation is a federation membership obligation or a flat contractual arrangement, not a speculative incentive. The federation is a bounded set of identified institutions, not an open population of anonymous relay operators; the token-and-incentive layer that public mixnets require to recruit untrusted relays is therefore unnecessary, and is deliberately excluded.
8.7 Mix-node directory
The set of mix nodes, their layer assignment, and their public keys MUST be published as a directory that every tenant can verify without trusting any single party.
- The directory MUST be a tamper-evident structure verifiable against the federation's existing KERI substrate (§4): directory documents are signed by a quorum of consortium witnesses, so a tenant — including an appliance with no operator contact — verifies the mix-node set the same way it verifies a peer's KEL. AFP introduces no new trust authority for the mix-node directory; it reuses §4. (The §6.3.1 attestation root authority is unrelated to this section: it is a verifier-elective trust root for instance attestation, plays no part in directory verification, and confers nothing here.)
- A reference Loopix-family implementation supplies its own decentralized directory-authority mechanism (a quorum-voting protocol). A conforming deployment MAY use that mechanism provided its trust model meets §8.2 property 4 — no single directory authority can forge the mix-node set — and provided the directory is bound into the consortium's KERI-witnessed structure so directory trust and federation identity share one root.
The directory document has the following verifier-facing shape:
interface AfpMixnetDirectory {
kind: 'afp:mixnet-directory:v1';
profile: 'afp.mixnet.profile.ob2-baseline.v1';
epoch: number;
valid_from: datetime;
valid_until: datetime;
keri_anchor_event_dig: string;
sphinx: {
packet_payload_bytes: 4096;
hop_count: 3;
};
mixing: {
delay_distribution: 'exponential';
mean_delay_seconds: 30;
packet_ttl_seconds: 900;
};
cover: {
tenant_packet_interval_seconds: 10;
mix_loop_interval_seconds: 10;
};
layers: Array<{
layer_index: number;
nodes: Array<{
mix_node_aid: string;
operator_aid: string;
endpoint: string;
sphinx_public_key: string;
directory_signing_key: string;
software_ref: string;
}>;
}>;
quorum_signatures: Array<{
witness_aid: string;
signature: string;
}>;
}A tenant MUST reject a directory if the KERI anchor is absent, the witness quorum is below the federation threshold, the profile parameters are weaker than ob2-baseline-v1, any layer is empty, or the operator-control constraints of §8.6 are violated.
8.8 Private route resolution
§5.2 step 1 requires an initiating entity to resolve a counterparty tenant AID to a transport route. This subsection specifies how, without any party learning the pairing.
- No directory of tenants. AFP defines no global, queryable directory mapping tenant AIDs to routes — such a directory would itself be the coordination graph (the same reason §5 defines no capability-discovery feed). This holds for both trust paths: open federation (§5.8) generalizes trust establishment, not discovery, and adds no directory. Mixnet route resolution is possible only between tenants that already share rendezvous data — bootstrapped from a bilateral federation agreement (§5.5) for the known-counterparty path, or established out-of-band-then-in-protocol for the open-federation path (see the last bullet).
- Rendezvous data is carried in the federation agreement. When two tenants establish a §5.5 agreement, that agreement carries — or bootstraps — the rendezvous information each needs to reach the other through the mixnet: mixnet ingress points and a per-relationship rendezvous handle. Because the agreement is held only by the two tenant principals it concerns, the artifact that encodes the A↔︎B pairing exists nowhere except with A and B. No relay, no directory, and no operator holds it.
- Rendezvous handles MUST NOT be stable cleartext identifiers of the tenant pair (§8.2 property 2). A handle is per-relationship and is used only inside Sphinx-wrapped packets; it is never exposed to a relay in the clear.
- Finding a counterparty that you cannot yet reach is out of AFP scope — and this is unchanged by open federation. ADR-0004 generalized trust establishment (§5.8: two strangers can establish trust by ZKC attestation with no prior agreement) but deliberately did not move discovery / rendezvous into AFP. The reason is precise: a queryable directory of who-can-reach-whom is exactly the coordination graph OB-2 (§3.1) exists to deny. So:
- For the known-counterparty path, rendezvous data is carried in the §5.5 agreement (above); route resolution begins once that agreement exists.
- For the open-federation path (§5.8), the two tenants have no agreement to carry rendezvous data. First contact must be established out-of-band — a real-world decision, optionally assisted by a separate tenant-discovery layer outside this specification — after which the parties establish a per-relationship rendezvous handle and proceed exactly as the known-counterparty path does. AFP provides the trust-by-attestation and the session; it does not provide the find-each-other step.
- The genuine unresolved tension is routing first contact through the mixnet for parties who have no agreement from which to bootstrap rendezvous data. This is tracked as an open item (§10) and is the one place where open federation is not yet self-contained.
- Any discovery layer, if one exists, MUST NOT have visibility into coordination traffic and is not part of the AFP trust boundary.
8.9 Relationship to OB and to honest disclosure
§8.3–§8.8 are the mechanism by which OB-2 (§3.1) is satisfied. OB-1 (content) does not depend on this section — it is delivered by §5.2 channel encryption in every topology. A deployment whose mixnet is not yet operational, or which cannot yet run mandatory cover traffic across a sufficient mix-node set, does not satisfy OB-2 and MUST disclose this per §3.5: such a deployment claims OB-1 and states OB-2 as not-yet-met. Because OB-2 has little value at very small federation sizes (with few tenants there is almost no coordination graph to hide) and accrues value as the federation grows, an honest-disclosure interim during early deployment is consistent with this specification.
8.10 Residual limitations
Two limitations MUST be disclosed honestly rather than overclaimed:
- Membership is not hidden. The mixnet hides who coordinates with whom; it does not hide that a tenant is a federation participant — a tenant's constant cover-traffic emission to the mixnet is itself observable. For a bounded consortium this is generally acceptable (membership may be public), but it is a real boundary of the guarantee.
- Long-horizon statistical disclosure. Constant cover traffic makes any point-in-time observation uninformative, but an adversary observing a small network over a long period can mount statistical-disclosure attacks that mixing and cover traffic mitigate but do not eliminate. Mandatory cover traffic from every tenant (§8.5) is the principal mitigation; the residual is smaller for larger federations. This limitation is shared by all Loopix-family systems and is inherent to the design class.
9. Freedom Safeguards
AFP v0.3.0 inherits the six Freedom Safeguards defined normatively in ZKA v0.9.0-draft §1.6. They are not re-declared here; this section states only how each bears on AFP.
| # | Safeguard | Bearing on AFP |
|---|---|---|
| 1 | Predicate Pluralism | AFP carries ZKC compliance bundles (§5.4) but privileges no issuer or predicate set. A tenant's compliance requirements in a federation agreement (§5.5) are that tenant's bilateral choice; AFP defines no canonical or default set — of issuers, of predicates, of root classes, or of root authorities (§6.3.1). An implementation MAY ship a default verification policy naming authorities it recommends; that is implementation-side product configuration, and §6.3.1 states normatively that such an entry must be visible to, editable by, and removable by the deployment operating it and is not a default of this specification. Naming an authority in this specification privileges it in no way. |
| 2 | Minimal Disclosure | Every selective disclosure and every telemetry claim (§6.3) is atomic — one property per proof. AFP message classes (§5.3) carry single-predicate proofs; composite disclosure requires explicit per-predicate consent. |
| 3 | No Revocability | AFP defines no gate over tenant state for any party. Operator telemetry failure is observe-and-alert, never intervene (§6.6). No AFP message can invalidate, freeze, or redirect a ZKA note. |
| 4 | Open-Source Predicates | Telemetry predicate circuits (§6.3) MUST be open-source, versioned, and published with a public review period, exactly as ZKC §1.4 requires of any predicate. |
| 5 | Agent Exit Rights | A tenant's withdrawal of its assets to a base layer is a ZKA operation gated by nothing in AFP. Leaving a federation, leaving a managed transport for an appliance (§4.7), and changing operator are all unconditional. AFP adds no exit gate. |
| 6 | Verifier Accountability | The first safeguard to constrain the counterparty rather than the operator, and load-bearing for open federation (§5.8). A tenant that requests a compliance proof is a verifier: it MUST authenticate the request under a stable KERI identity, and the holder retains a profile-minimized Disclosure Receipt of what was demanded, by whom, and for what purpose (ZKC v0.4.0 §5.5–§5.9; AFP §5.9). Anonymous demands are rejected; global linkability requires explicit consent. |
Operator blindness (§3) is an AFP-level invariant in the same spirit as the Safeguards: a structural guarantee, verifiable by inspection (§7.1), not a policy promise.
10. Open Items
Carried forward for the v0.3.x spec series; surfaced here so they are not lost.
ZKA note migration (§4.6). AFP rotation sequencing and ZKC credential rebinding are specified. The remaining lower-layer Draft seam is the ZKA-owned note-migration proof under ZKA §5.7 (ZKA v0.9.0-draft).
Instance-level reputation (§5.5). Whether any signal beyond the bilateral federation agreement is needed. Resolved in v0.3.x (§14) — see the note below.
Stranger rendezvous under operator-blindness (§5.8, §8.8, §14). Two tenants with no pre-existing agreement can now establish a trust basis two ways — per-interaction ZKC attestation with no agreement (§5.8, open federation) or a provisional agreement negotiated by an introduction session (§14) — but both still require the parties to be in contact first. The known-counterparty path bootstraps mixnet rendezvous data from the §5.5 agreement, which strangers lack. Routing first contact through the mixnet for parties with no agreement, without introducing a queryable directory that would itself be the coordination graph (the exact thing OB-2 denies), is unresolved. It needs a dedicated discovery/rendezvous layer with its own privacy analysis, or an out-of-band first-contact bootstrap; it is out of AFP scope and MUST NOT be solved by relaxing §8 OB-2. Until then, §5.8 and §14 are usable only where first contact is established out-of-band.
Operator ↔︎ operator federation. Deferred until there are at least two independent operators running conforming tenant infrastructure in production and a concrete need for cross-operator tenant portability, relay governance, directory governance, billing settlement, or incident coordination. This is future multi-operator scope, not AFP v0.3.2 conformance.
A future operator↔︎operator design MUST NOT create an operator-visible coordination graph, an operator trust bridge for tenant↔︎tenant session establishment, a shared discovery feed, or any telemetry-sharing path that reveals tenant work. It MUST preserve §3 operator blindness, §4 tenant-controlled identity continuity, §6 tenant-pushed telemetry, and §8 OB-2 routing. Until that design exists, AFP deployments remain conforming only for tenant↔︎tenant, tenant↔︎operator, and identity-continuity relationships (§1.3); any operator↔︎operator operational agreement is out of AFP scope and cannot relax AFP requirements for tenant sessions.
Base-spec
session_contextencoding (§5.2, §12.3, §15.4). The base spec defines the session context only asH(initiatorEntityAID, responderEntityAID, sessionId, timestamp)— no digest algorithm, output width, or input canonicalization is pinned, and the reference implementation's timestamp serialization is not cross-runtime reproducible. §15.4 pins apay-profile payload contract (a qb64-encoded 32-byte digest) as the de facto reading; the base-spec formalization must land and be reconciled against it. Tracked:req_afp_formalize_sessioncontext_encoding_in_ab9e1b6c(ADR-0006).Multi-vendor conformance program (§1.5.1). A formal conformance program for the tenant profile — published tenant-profile test vectors as a normative gate, cross-runtime CI that fails closed on drift, program governance, and certification — is deferred until a second serious implementer commits. Until that milestone, §1.5.1 states the tenant conformance profile normatively but does not gate it on vectors: conformance is asserted against the spec text and judged bilaterally by counterparties and verifiers (§5.5, §5.8, §6.3.1), exactly as it was when the reference implementation was the only implementation. An implementation's operator-blindness claims are therefore held to §3.5 honest disclosure and checked by the §7.1 auditability requirement, not by a vector suite. The AFP-KDC v1.0.2 conformance vectors (Appendix A) are the shape such a suite would take.
When the trigger fires, the program MUST NOT become a gate on who may federate. Vectors test the §1.5.1 obligations and nothing more; acceptance of a counterparty remains bilateral verifier policy (§6.3.1) rather than a certification grant; and no conformance mark, implementer registry, or published conformance feed may be introduced — that surface is retired (§1.2). The same holds for attestation authorities: standing as a §6.3.1 root authority is not a certification grant, is not gated on this program, and creates no implementer registry. A second implementer's publisher stands as an authority on the same terms as the first, before and after this program exists.
Transparency-log upgrade for the ADAMAS product line (§6.3.1). Moving ADAMAS builds into a transparency log would upgrade that product line from
root:publisher-manifesttoroot:build-provenance— a strictly stronger, third-party-verifiable position inside the same taxonomy and under the same authority root, requiring no revision to this specification and no change to any verifier's accepted authority. Until then the product line attests as self-attested at the platform layer, with the mandatory §6.4 public-sample binding and the §3.5 disclosure duties of §6.3.1. The same upgrade path is open to any authority on identical terms.Out-of-band authority binding material (§6.3.1). The AID↔︎legal-entity binding of §6.3.1 is established out-of-band. An authority's key ceremony, its published trust material, and any practice statement or relying-party terms it publishes are companion work owned by that authority; AFP defines neither their form nor their content and requires none of them of any verifier. It grants them normative standing in exactly one respect and no other: §6.3.1 states verifier acceptance conditions over the trust material a verifier elects to accept — the pinned witness set and threshold for the authority's KEL, and, under
root:publisher-manifest, the self-attestation characterization. A verifier's obligations under §6.3.1 — including the stale/revoked determination — are dischargeable for a KERI-rooted authority from that authority's own KEL and the KELs it anchors (§4.2, §4.4, §4.6); theroot:build-provenanceandroot:tee-attestationclasses keep their existing verifier-pinned log-policy and vendor-root material. In no class is an authority-published advisory or status feed on the normative path.
Generalized in v0.3.9-draft (ADR-0005 tenant definition). A
tenantis no longer "an ADAMAS instance": it is a runtime that federates as a principal under its own tenant root AID and MUST satisfy the tenant conformance profile (§1.5.1) (§1.6, §2.1), with ADAMAS the reference implementation and the worked example rather than the definition. Tenanthood is structural, not a certificate: a runtime that falls short of an obligation is a non-conforming tenant under the honest-disclosure regime of §3.5 and §8.9, never a non-principal. Boundaries the spec drew as ADAMAS-internal are tenant-internal, and the duties they implicitly delegated are now stated as normative tenant obligations (§5.6, §5.7); the single trust level reads verified conforming instance (§1.2, §12.1, §12.5, §13.1), realized entirely by the existing §6.3.1 root-class and verifier-policy machinery; and §7.1's auditability requirement binds any conforming implementation rather than one vendor's licensing posture. This generalizes who may implement a tenant, never what the protocol does — no new message class, public input, or envelope revision, and every §1.2 retirement intact (restated there). The formal multi-vendor conformance program is deferred to the second-implementation milestone (the new open item above).
Completed in v0.3.9-draft (ADR-0005 authority naming) — the attestation authority. The note above deferred one half of ADR-0005; this revision lands it. The single trust level now reads verified conforming instance under a verifier-accepted attestation authority (§1.2, cross-referenced from §12.1, §12.5, and §13.1), still realized entirely by the existing §6.3.1 root-class and verifier-policy machinery and still with no wire change. §6.3.1 fixes what an authority is — the accountable party, for an organizational authority its organizational root AID, never a product — permits KEL-anchored product-scoped delegated signing on the §4.2 shape, assigns delegation resolution to the verifier rather than the circuit, and makes the stale/revoked determination dischargeable, for a KERI-rooted authority, from that authority's own KEL and the KELs it anchors (§4.2, §4.4, §4.6) — with the transparency-log and hardware classes keeping their existing verifier-pinned material, and no authority-published feed on the normative path in any class. The anti-gatekeeping guard is the point of the change: extending the root-class taxonomy requires a revision to §6.3.1, but standing up a new authority does not — the set of authorities is open, entered by publishing acceptable trust material rather than by registration, grant, or amendment, and acceptance is always the verifier's own policy (§1.2, §5.5, §5.8, §9, §12.8). Pyramidal Inc. is named as the first such authority non-normatively (§6.3.1), with no AID value asserted and the authority not yet operational (§14.9); an authority named here or shipped as an implementation's default holds no protocol privilege, is not a principal (§2.1), and certifies no conformance (§1.5.1). Requiring instance attestation at all remains each party's own policy: nothing in this revision makes an attestation a precondition of a session.
Generalized in v0.3.4 — open-federation trust basis (ADR-0004). AFP's trust basis is no longer "a bilateral federation agreement is the sole precondition for a session." A session MAY be opened on either a §5.5 agreement (known counterparty) or per-interaction ZKC compliance attestation (§5.8, open federation). This unlocks ADAMAS-to-ADAMAS transactions between unknown counterparties with no new protocol and no weakening of operator-blindness — only trust establishment is generalized (attestation, compatible with OB-2); discovery/rendezvous stays out (the new open item above). Safeguard 6 (Verifier Accountability) is what keeps stranger-attestation accountable, and all six safeguards are inherited identically by both paths.
Resolved in v0.3.0; reconciled in v0.3.7 and fully pinned in v0.3.9 — shared cryptographic core. AFP-KDC v1.0.2 (Appendix A) is the self-contained shared derivation module referenced by ZKA, ZKC, and ZKM. It retains every v1.0.0 value while making field-tag derivation and operational-tag ownership unambiguous and fully parameterizing the deployed Poseidon2 sponge.
Resolved in v0.3.1 — shared bilateral-session core (ADR-0003). The session machinery that PRP v0.2 and VXP v0.2 each re-derived is consolidated into AFP as the Bilateral Session Core (§12), and the Profile concept (§13) names PRP and VXP as the two v0.2 profiles. Two seams the profiles flagged are resolved here: (a) NATS namespace — profiles live under
afp.{tenant}.{profile}.*;vxp.*is a subtree under AFP, not standalone (§12.4); (b) the federation bundle home — ZKA owns the cryptographic bundle (zka:bundle:v1, incl. the ZKA/Coord proof), AFP owns the session envelope (afp:envelope:v1), andvxp:coordbundle:v1/ any PRP wrapper are replaced by envelope-over-bundle (§12.7). ZKA v0.7 now supplies the required bundle member, so this is no longer an open cross-protocol item. The mechanical trim of PRP/VXP to reference §12/§13 is tracked inPROFILE_MIGRATION_NOTE.md.
Resolved in v0.3.2 — ZKA bundle dependency. The bundle dependency recorded by §12.7 is resolved by ZKA v0.7 §5.4:
zka:bundle:v1now carries an optionalcoord_proofmember and supports pure work-attestation bundles. AFP'safp:envelope:v1therefore wraps a single ZKA-owned bundle for payment and non-payment work-attestation flows.
Resolved in v0.3.2 — DGP gateway boundary. The DGP seam named in earlier drafts is resolved by §1.4.1: tenant-exposed DGP gateways are non-AFP integration surfaces. AFP provides no discovery feed to them, grants them no coordination-traffic visibility, and does not expand the AFP trust boundary around them.
Resolved in v0.3.2 — witness bootstrapping. §4.5 now defines
afp:witness-bootstrap:v1, managed and bring-your-own witness bootstrap paths, verifier checks, failure semantics, and migration behavior.
Resolved in v0.3.2 — public-sample consistency. §6.4 now defines
afp:telemetry-sample-binding:v1, the allowed public-sample forms, proof obligations, anti-replay context binding, and failure semantics for self-attested telemetry predicates.
Resolved in v0.3.2 — operator telemetry predicate catalog. §6.3 now defines AFP predicate IDs, issuer models, public inputs, private witnesses, cadence, and failure signals for instance attestation, build currency, compute-quota compliance, and audit-log integrity.
Resolved in v0.3.2 — AFP rotation sequencing. §4.6 now defines
afp:rotation-presentation:v1, the required ordering from KEL rotation through ZKC credential rebinding and ZKA note-migration status, verifier behavior, and failure semantics. ZKA note migration itself remains a ZKA-owned Draft seam.
Resolved in v0.3.2 — metadata-private routing parameters. §8 now defines
afp.mixnet.profile.ob2-baseline.v1, including Sphinx geometry, mixing delay, cover-traffic floors, directory epoch rules, theafp:mixnet-directory:v1KERI-bound directory artifact, and Katzenpost/Echomix reference-maturity disclosure.
Resolved in v0.3.x — instance-level reputation / stranger onboarding. §14 (Introduction Tier) defines how two instances with no prior relationship reach a first §5.5 agreement: a credential + ZKC-proof handshake auto-negotiates a scoped, collateral-priced provisional agreement, and limits then grow with a ZK-provable, non-replayable reputation signal bound to that agreement. This supersedes the §5.5 "working assumption is no" for the stranger case — the reputation signal is progressive, ZK-provable, and bilateral (per-agreement), not a portable global score, and it grows the envelope rather than gating any tenant state (§14.6, Safeguards 3/5). The reputation circuit and the bonded-escrow rail are honestly-disclosed companion-requirement seams (§14.9).
11. Relationship to Prior AFP Documents
| Document | Status under v0.3.0 |
|---|---|
Agent_Federation_Protocol_AFP.md (v0.2.0-draft) |
Superseded. Retain as historical reference; do not implement against it. NATS subject and JetStream patterns are partially reusable as implementation reference; the semantic model is replaced. |
AFP_AGENT_IDENTITY_ADDENDUM.md (v0.1.1-draft) |
Superseded. Identity-lifecycle concepts survive in KERI-aligned form (§4); the hash-derived identifier and flat nkey_history model are retired. |
keri_afp_mapping.md (v0.1.0-draft) |
Promoted to canonical: the basis of §4. |
afp_v0_3_scope_note.md (v0.3-scope) |
Realized by this specification. |
afp_v0_3_scope_addendum.md (v0.3-scope) |
Realized by this specification: Decision 1 (identity) is §4; Decision 2 (telemetry) is §6, revised per the operator-blindness invariant of §3 to remove the transport-observed telemetry layer. |
Revision note. The scope addendum's Decision 2 specified a two-layer telemetry model whose Layer 1 was transport-observed per-tenant metadata. This specification supersedes that: under the operator-blindness invariant (§3), per-tenant transport observation is not a capability the operator is permitted to have, so all per-tenant operator knowledge is ZKC-proved (§6.1). The addendum's Layer 2 survives as §6.3; its Layer 1 is removed. This change follows the decision, taken after the addendum was written, to make ADAMAS source-available and to keep the operator off the coordination path entirely.
12. Bilateral Session Core
This section group is the consolidation mandated by ADR-0003. It is normative, added in v0.3.1. Two tenant-level behaviors specified separately — PRP pairwise reconciliation and VXP value exchange — each independently re-derived the same bilateral-session substrate: two verified instances open a session over AFP, bind it to KERI identities, exchange signed atoms under bilateral policy, resolve disputes bilaterally with an optional pre-agreed arbiter, commit with both signatures and an optional DLT anchor, and inherit the Freedom Safeguards. AFP v0.3.1 lifts that substrate here so it is defined once. AFP's own §5 tenant↔︎tenant work exchange is an instance of it; PRP and VXP are profiles (§13) over it.
The core does not replace §4 (identity), §3 (operator blindness), §8 (routing), or §9 (safeguards); it composes them into the session abstraction that profiles consume. Where a §12 subsection restates a construct already introduced in §5, §12 is the consolidated, profile-facing statement and §5 is its work-exchange application.
12.1 The bilateral session
A bilateral session is the unit of all AFP-mediated interaction between two verified conforming instances (§1.2). Where — and only where — a party requires instance attestation, "verified" is verified under a root class and root authority that party accepts (§6.3.1); trust basis (a) below imposes no attestation requirement of itself — whether the agreement carries one is bilateral (§5.5) — and this section imposes none. It is, by construction:
- bilateral — exactly two principals, one entity in each of two tenants. N-party interaction is composed from bilateral sessions (a star or mesh of them, profile-defined; §13.3), never run as a single N-way protocol;
- mutually authenticated — each side verifies the other's delegated AID and its delegation chain to the counterparty tenant root AID against the declared witness threshold (§4.5, §5.2 step 2);
- end-to-end encrypted — every payload is ciphertext to any transport operator (OB-1, §3.3);
- metadata-private — routed through the §8 mixnet so no operator or link observer learns the tenant pairing (OB-2);
- trust-anchored — every session is anchored to a trust basis, of which there are three (§5.1, §5.8, §14): (a) a pre-existing bilateral federation agreement (§5.5), whose policy block the session runs under — the original known-counterparty path; (b) a provisional agreement that an introduction session (§14,
afp.introduction.v1) negotiates and both parties sign before any value interaction — the one session type that opens by minting an agreement rather than presupposing one; or (c) per-interaction ZKC compliance attestation with no agreement (§5.8, open federation, ADR-0004), whose accepted attestation + declared purpose plays the policy-block role. (Through v0.3.3 the core admitted only (a); §14 added (b); v0.3.4 / ADR-0004 added (c).) Cases (b) and (c) are the two ways to interact without a pre-existing agreement — (b) ends in a signed provisional agreement, (c) in none. Every other property in this list — mutual authentication, end-to-end encryption, OB-2 routing, context binding — is unchanged and REQUIRED for all three. A profile MAY restrict itself to the agreement path — PRP and VXP currently do; - context-bound — every signed atom and every proof in the session binds the session context of §5.2 step 4.
A profile (§13) does not define a new session; it runs inside a bilateral session, contributing its own atom kinds and state machine while the session establishment, identity, transport, dispute, commitment, envelope, and safeguard machinery are the core's. The introduction tier (§14) is the one place a profile is permitted to drive session opening itself, and only because closing the stranger-onboarding gap requires it; the carve-out is bounded to that profile and stated normatively in §14.2.
12.2 Session establishment, lifecycle, and timeout
12.2.1 The session atom
A bilateral session is durable, queryable AKG state, represented by a core SessionAtom (afp:session:v1). A profile MAY specialize/extend this atom (PRP's prp:session:v2 and a VXP transaction context are profile specializations), but every profile session carries the core fields:
interface AfpSessionAtom {
kind: 'afp:session:v1';
id: string; // session id; component of the session context (§5.2 step 4)
// Principals — KERI-aligned (§4). The session is between two ENTITY delegated AIDs,
// each verified up to its TENANT root AID.
initiator: AfpPartyRef;
responder: AfpPartyRef;
// Governing bilateral federation agreement (§5.5) and the profile this session runs.
agreement_ref: string; // AFP bilateral federation agreement id
agreement_version: string; // pinned at session open
profile: string; // registered profile name (§13.2), e.g. 'prp' | 'vxp' | 'work'
profile_policy_hash: string; // hash of the profile policy block exchanged at open
// Lifecycle (§12.2.2)
status: AfpSessionStatus;
// Timing / timeout (§12.2.3)
opened_at: datetime;
expires_at: datetime; // hard session timeout; REQUIRED
closed_at?: datetime;
// Binding
session_context: string; // H(initiatorEntityAID, responderEntityAID, sessionId, timestamp)
}
interface AfpPartyRef { // the canonical session identity binding (§12.3)
tenant_aid: string; // §4 tenant root AID (self-certifying)
entity_aid: string; // §4 entity delegated AID — the acting principal
kel_event_dig: string; // KEL event the signing key is pinned to (key-state pinning)
signature: string; // KEL-anchored signature over the atom content hash
}
enum AfpSessionStatus {
PROPOSED = 'proposed', // open requested, not yet accepted
OPEN = 'open', // mutually authenticated, channel established
ACTIVE = 'active', // profile exchange in progress
CLOSING = 'closing', // graceful teardown initiated
CLOSED = 'closed', // completed normally
EXPIRED = 'expired', // expires_at reached before close
ABORTED = 'aborted' // torn down on error or by either party's exit (§12.8, Safeguard 5)
}The profile field is recorded in the session so the transport layer and the counterparty know which atom kinds and state machine govern the exchange. afp:session:v1 carries no profile-specific fields; those live in the profile's own atoms.
12.2.2 Lifecycle
The core session lifecycle is the part of every profile's state machine that concerns the session itself, distinct from the profile's exchange state machine (which the profile defines over ACTIVE):
propose ──▶ PROPOSED ──accept──▶ OPEN ──begin──▶ ACTIVE ──close──▶ CLOSING ──▶ CLOSED
│ │ ▲
reject/timeout either-party exit (§12.8 SG-5) │
│ or error │
▼ ▼ profile reports done
ABORTED ABORTED ──────────────────────┘
(EXPIRED reachable from any non-terminal state when expires_at passes)
- Propose / accept. The initiator proposes a session under a named profile and a pinned federation-agreement version; the responder accepts (→
OPEN) or rejects (→ABORTED). Acceptance completes the mutual authentication and channel establishment of §5.2. - Activate. On the first profile exchange message the session moves to
ACTIVE. The profile's own state machine (PRP Compare→…→Commit; VXP Intent→…→Settlement) runs entirely withinACTIVE. - Close. When the profile reports its exchange complete, the session moves
CLOSING → CLOSED. A core CommitmentAtom (§12.6), where the profile produces one, is the artifact that justifies a clean close. - Abort / expire. Either party MAY exit at any time (§12.8, Safeguard 5) — the session moves to
ABORTED, leaving whatever signed atoms exist as the durable record.expires_atelapsing moves any non-terminal session toEXPIRED. No core transition can be forced by an operator (§12.8, Safeguard 3).
A profile MAY name additional terminal annotations for its exchange (e.g. PRP ESCALATED); those describe a session that closes or aborts at the core level — the core recognizes only the states above.
12.2.3 Timeout
Every session MUST carry an expires_at. A session that has not reached a terminal state by expires_at is EXPIRED; a fresh session must be proposed to continue. Profiles MAY set finer-grained per-phase deadlines (PRP evidence_deadline_seconds, VXP offer valid_until) within the federation agreement's policy block, but the core session timeout is the outer bound and is non-optional. Timeout is observe-and-resolve, never operator intervention: an expired session simply ends; nothing in tenant state is frozen or rolled back (§12.8, Safeguard 3).
12.3 Identity binding for sessions
Session identity binding is §4 applied to the session, stated once here so profiles cite it rather than re-derive it:
- The two session principals are entity delegated AIDs (§4.2). Each is authenticated by verifying its KEL to its tenant root AID against the tenant's declared witness threshold (§4.5), exactly as §5.2 step 2 specifies. This verification is identical for both trust paths: in a bilateral session (§5.5) the two AIDs are carried in the agreement; in an open-federation session (§5.8) they are the AIDs attested at first interaction. The KEL-to-root verification, and everything below, is the same in either case — open federation changes the provenance of the two AIDs, not how they are authenticated or bound.
- Every signed session atom carries an
AfpPartyRef(§12.2.1): the tenant root AID, the acting entity delegated AID, thekel_event_digpinning the key state the signature is bound to, and a KEL-anchored signature over the atom's content hash. A signature bound to a superseded KEL event MUST be rejected (thekel_keystate_stalecondition the profiles surface). - The session context
H(initiatorEntityAID, responderEntityAID, sessionId, timestamp)(§5.2 step 4) is the value bound into every in-session proof'scontextpublic input — the same value ZKA/Coord and ZKC proofs bind — preventing replay to another session or counterparty. Profiles MUST bind it and MUST NOT define a parallel context. - A ZKC v0.4 presentation additionally satisfies §5.9: the verified credential-subject AID equals the presenting
AfpPartyRef.entity_aid, the APR signer key resolves through the requesting party's current KEL, and the ZKC proof context incorporates this session context, active presentation profile, and any AFP scope policy. Known-counterparty bilateral flows default to anonymous and may accept scoped→anonymous downgrade when continuity is unnecessary; they never gain linkability without explicit consent. - Cross-tenant work is attributed to the entity delegated AID and signed with a key derived from that entity's sub-seed
M'(§4.3, Appendix A). The session never addresses HyperTalk sessions or internal users (§2.1); the entity is always the principal that crosses the boundary.
A profile's party reference (PRP PartyRef; VXP created_by: EntityAID + the §1.3 derivation) is a restatement of AfpPartyRef; under v0.3.1 it is AfpPartyRef, and the profile carries no additional identity machinery.
12.4 NATS namespace ownership
AFP owns the federation subject tree. No profile defines a standalone namespace. The canonical tree is:
afp.{tenant}.{profile}.*
where {tenant} scopes the subtree to a tenant and {profile} is a registered profile name (§13.2). Concretely:
afp.{tenant}.work.* # base work-exchange behavior (the §5.3 message classes)
afp.{tenant}.session.* # core session lifecycle (propose / accept / close / status)
afp.{tenant}.prp.* # PRP profile subtree
afp.{tenant}.vxp.* # VXP profile subtree
afp.{tenant}.telemetry.* # tenant↔operator telemetry (§6.5) — operator-facing, not a bilateral profile
Normative rules:
- A profile's subjects MUST live under
afp.{tenant}.{profile}.*and nowhere else. A standalone top-level namespace (a barevxp.*, or the retiredprp.{org}.*) is non-conforming. - A profile name occupies exactly one
{profile}label and MUST be registered (§13.2) before its subtree is used. - The subjects MUST remain metadata-private under §8: the
{tenant}label and any subject string visible to a relay is carried only inside Sphinx-wrapped packets and MUST NOT be a stable cleartext identifier of the tenant pair (§8.2 property 2). The tree above is the logical namespace; its on-the-wire form is the routing layer's concern.
Resolves the VXP flag. VXP v0.2 §1.4 / §16 flagged that its
vxp.exchange.*/vxp.channel.*subjects sat in a standalonevxp.*namespace pending an AFP decision on ownership. The decision is:vxp.*is a profile subtree under AFP, addressedafp.{tenant}.vxp.*. VXP'svxp.exchange.intentbecomesafp.{tenant}.vxp.exchange.intent, and so on. PRP v0.2 already adoptedafp.{tenant}.prp.*; v0.3.1 makes that ownership normatively AFP's, not a PRP-side assumption.
12.5 Bilateral dispute and arbiter model
PRP §8 and VXP §9 specify the same dispute model in different words. It is consolidated here; both profiles reference this section.
Primary — bilateral-only, no operator mediation. A dispute within a bilateral session is resolved bilaterally, between the two principals, or not in-protocol at all. There is no operator-mediated dispute: the operator of any AFP transport cannot adjudicate, freeze, reverse, or force any outcome. This is Safeguard 3 (No Revocability) made concrete at the session layer (§12.8): a core CommitmentAtom (§12.6) is valid only with both parties' KEL-anchored signatures; no admin, governance, or operator key can produce or invalidate one. When the two parties cannot agree in-protocol, the session aborts/escalates with a signed divergence record (the profile's dispute atoms) as the durable artifact, and the parties resolve off-protocol — through their own entities, then the federation agreement's governance process — and, failing that, sever the relationship. Exit is unconditional (§12.8, Safeguard 5).
Optional — a pre-agreed third-party arbiter. A federation agreement (§5.5) MAY name, in advance, a single arbiter — a third entity AID, itself a verified conforming instance (§1.2) — to break a tie. The two parties decide bilaterally what verification they require of the arbiter, exactly as for any counterparty; this section imposes no instance-attestation requirement on an arbiter and gives no attestation authority any standing over a dispute. The arbiter:
- is a specific, pre-agreed AID named in the agreement (e.g. a multi-site trial's coordinating center, a consortium secretariat), never selected at dispute time, never discovered from a marketplace, never TVF-ranked;
- receives the evidence the two parties already exchanged, over its own bilateral sessions with each;
- issues a signed decision — but cannot force a commitment. Even an arbiter's decision requires both parties' counter-signatures on the resulting CommitmentAtom (or the session aborts). The arbiter's authority is exactly and only what the two parties granted it in writing in the agreement.
For a monetary leg (a VXP concern), an arbiter MAY act as a co-signer on a pre-agreed 2-of-3 escrow release — able to break a tie only within the funds the parties escrowed, never against base-layer custody (Safeguard 5). This is the one arbiter capability that touches value, and it is bounded by what the parties escrowed in advance.
There is no global arbiter registry, no min_tvf_level, no capability query, no random-from-pool selection — those are marketplace mechanisms, retired with the v0.2.0 marketplace (§1.2). A profile MAY add profile-specific escalation steps (PRP's entities→governance ladder, VXP's per-leg breach conditions) on top of this model, but the bilateral-only-primary + optional-pre-agreed-arbiter shape, and the no-forced-outcome guarantee, are the core's.
12.6 Commitment and optional-anchor primitive
Both profiles end an interaction with a bilaterally-signed commitment to an agreed outcome, chained for replay-resistance, with an optional DLT anchor. Consolidated:
12.6.1 Bilateral commitment
A core CommitmentAtom (afp:commitment:v1) is the record that two principals agreed to an outcome:
interface AfpCommitmentAtom {
kind: 'afp:commitment:v1';
id: string;
session_ref: string; // the afp:session:v1 this commits
outcome_root: string; // Poseidon2 commitment to the agreed outcome
// (profile-defined: PRP reconciled-state root; VXP settled-legs root)
profile: string; // which profile produced this outcome
// Bilateral signatures — BOTH required (§12.5, Safeguard 3)
initiator_signature: AfpCommitSignature;
responder_signature: AfpCommitSignature;
// Chaining — replay-resistant ordered history per AID-pair
previous_commitment_ref?: string;
sequence_number: number; // monotonically increasing per AID-pair
anchor_ref?: string; // afp:anchor:v1, if anchored (§12.6.2)
// Optional pre-agreed arbiter co-signature (§12.5), present only if an arbiter acted
arbiter_signature?: AfpCommitSignature;
}
interface AfpCommitSignature {
signed_by: string; // tenant/entity AID
kel_event_dig: string; // pins key state at signing (§12.3)
signature: string; // Ed25519, KEL-anchored
signed_at: datetime; // observational metadata; not in signature payload
}Normative properties:
- Both signatures are REQUIRED. A CommitmentAtom with one signature is not a commitment. No operator or arbiter signature substitutes for a party signature (§12.5).
- Chaining.
previous_commitment_ref+ a per-AID-pair monotonicsequence_numberform a signed, ordered, replay-resistant history between the two AIDs. A gap or non-monotonic sequence is a detectable error (the profiles'sequence_mismatch). - Outcome opacity. The atom commits to an
outcome_root(a Poseidon2 commitment), not to the cleartext outcome — minimal disclosure (§12.8, Safeguard 2). What the root commits to is profile-defined.
A profile's commitment atom (PRP prp:commitment:v2; a VXP SettlementAtom) is a specialization of this primitive: it MAY add profile-specific fields (PRP bilateral_metrics, VXP legs[]) but MUST carry the bilateral-signature, chaining, and outcome-root machinery defined here.
12.6.2 Optional anchor
Anchoring is optional. The protocol is fully functional with bilateral KEL-anchored signatures alone, which already give a signed, ordered, replay-resistant history. For records whose disputes might later be adjudicated by a party outside the bilateral relationship (a regulator, an auditor), a commitment MAY be anchored to a DLT for an independent, tamper-evident timestamp:
interface AfpAnchorAtom {
kind: 'afp:anchor:v1';
id: string;
commitment_ref: string;
chain: string; // e.g. 'ethereum:arbitrum' | 'ethereum:base' | 'ethereum:optimism'
via: 'apl'; // ADAMAS Payment Layer
transaction_id: string;
block_number: number;
block_timestamp: datetime;
anchored_data: { // hashes only — never atom content
commitment_hash: string;
outcome_root: string;
parties: string[]; // AIDs, hashed for privacy
sequence_number: number;
};
inclusion_proof: string; // either party verifies independently
}- Ethereum L2 (Arbitrum / Base / Optimism) via APL is the mandatory baseline — a conforming deployment that anchors MUST support at least one Ethereum L2 through APL. ZKA-Aztec is v5/AZUP-2 migrated and sandbox-accepted in ZKA.dev, but Aztec, Namada, and Penumbra remain non-baseline anchor targets for AFP profiles until an APL deployment explicitly wires the rail and claims it.
- Only hashes are anchored — the commitment hash, outcome root, hashed party AIDs, and sequence number. Never atom content. The anchor is a tamper-evident external timestamp, nothing more.
- No-anchor is conformant and is the default for low-stakes state.
A profile's anchor policy (PRP policy.anchor.mode, VXP channel settle_to) selects when to anchor; the anchor atom and the Ethereum-L2-baseline rule are the core's.
12.7 The federation bundle wire format — session envelope over ZKA bundle
This subsection resolves the bundle-home ambiguity that VXP v0.2 (its vxp:coordbundle:v1, §11/§16) and PRP v0.2 (its direct zka:bundle:v1 usage) both surfaced. Decision, stated explicitly:
ZKA owns the cryptographic bundle; AFP owns the session envelope.
- The ZKA Compliance Bundle
zka:bundle:v1(ZKA v0.9.0-draft §5.4) is the cryptographic bundle: it carries the ZKA/Pay transaction proof (where present), ZKC v0.4zkc:proof:v2anonymous/scoped proofs or explicitly-consented linkablezkc:proof:v1proofs, the applicable identity/presentation binding proof, and the ZKA/Coord recursive work-attestation proof. AFP does not introduce a replacement bundle version.- AFP owns the session envelope
afp:envelope:v1: the construct that carries azka:bundle:v1across a tenant boundary within a bilateral session, binding it to the session context (§12.3) and routing it on the namespace of §12.4. The envelope is session-level framing; the bundle is cryptographic payload.- Therefore VXP's
vxp:coordbundle:v1and any PRP bundle wrapper are REPLACED by AFP envelope + ZKA bundle. A profile never authors a bundle wire format; it places a ZKA bundle inside an AFP envelope.
interface AfpEnvelope {
kind: 'afp:envelope:v1';
session_ref: string; // the bilateral session (§12.2) this crosses within
session_context: string; // H(initiatorEntityAID, responderEntityAID, sessionId, timestamp) (§12.3)
profile: string; // profile placing the envelope (e.g. 'vxp' | 'prp' | 'work')
bundle: ZkaBundle; // zka:bundle:v1 (ZKA §5.4) — cryptographic payload, carried verbatim
bundle_version: 'zka:bundle:v1';
message_class: string; // which AFP message class / subject this rides (§5.3, §12.4)
generated_at: datetime;
expires_at: datetime;
}Verification is two-layer and unambiguous: AFP verifies the envelope (its session_context matches the live session, the envelope has not expired, the profile/message_class are valid for the session); ZKA verifies the bundle (every proof inside zka:bundle:v1 — transaction, compliance, binding, coordination — verifies, and its required context bindings are valid). The bundle metadata.context MUST equal the envelope's session_context in every AFP flow, including pay. The pay profile additionally derives a distinct interaction_context from that session context and the §15.3 quote hash — computed over signature-free quote content, before either signature (§15.4); the ZKA payment proof/request for that quote MUST bind the derived value, without changing the bundle/envelope equality, changing afp:envelope:v1, or overloading its session_context. No proof system lives in the AFP layer; AFP frames and binds, ZKA proves.
Resolved interface. ZKA v0.9.0-draft §5.4 satisfies this requirement by adding an optional
coord_proofmember tozka:bundle:v1and by permitting pure work-attestation bundles wherecoord_proofis present andtransactionis absent. The bundle'smetadata.contextis the shared replay/context binding and always equals the AFP envelope'ssession_context. Forpay, §15.4 defines the additional quote-bound interaction context supplied to the ZKA payment proof/request — whose binding the pool and proof enforce; the derivation itself is a producer obligation, checked by any party that recomputes the §15.4 fold. AFP therefore has no transitional profile-authored bundle: a profile carries an AFP envelope overzka:bundle:v1, and ZKA verifies the transaction, compliance, binding, and coordination proofs that are present.
12.8 Freedom Safeguards at the session layer
The six Freedom Safeguards are defined normatively in ZKA v0.9.0-draft §1.6 and inherited by AFP at the protocol level (§9). This subsection maps them onto the bilateral session so that every profile inherits them by building on the core — PRP and VXP need not, and under v0.3.1 do not, re-derive a safeguards mapping. (PRP §14 and VXP §14 become references to this subsection; see PROFILE_MIGRATION_NOTE.md.)
| # | Safeguard (ZKA §1.6) | Application to an AFP bilateral session — inherited by every profile |
|---|---|---|
| 1 | Predicate Pluralism | A session privileges no issuer or predicate set. Compliance requirements are the bilateral choice recorded in the federation agreement (§5.5); the core defines no canonical or default predicate set and no canonical or default root authority (§6.3.1), and a profile MUST NOT introduce one. A default entry an implementation ships in its verification policy is product configuration, not a core or profile default; it is visible, editable, and removable by the deployment operating it (the duty is normative in §6.3.1), and it applies to an interaction only when signed into that interaction's policy (§9, §14.4). |
| 2 | Minimal Disclosure | Every in-session proof is atomic — one predicate per proof (§5.4). A core CommitmentAtom commits to an outcome_root, not cleartext (§12.6.1). The session reveals only what the exchange requires; profiles enforce this structurally (PRP's Merkle-subtree pruning, VXP's value commitments) but inherit the default from the core. |
| 3 | No Revocability | No operator, arbiter, or platform can force, freeze, reverse, or invalidate a session outcome. A CommitmentAtom requires both parties' KEL-anchored signatures (§12.6.1); dispute is bilateral-only with at most a pre-agreed arbiter that still needs both signatures (§12.5); timeout/expiry is observe-and-resolve, never intervention (§12.2.3). This is the session-layer statement of the §3 operator-blindness invariant. |
| 4 | Open-Source Predicates | Any predicate a session carries MUST be open-source, versioned, and published with a public review period, exactly as ZKC §1.4 / ZKA §1.6 require. A profile adds no opaque predicate. |
| 5 | Agent Exit Rights | Either party MAY exit a session, and the relationship, at any time (§12.2.2 → ABORTED); exit is unconditional and carries no protocol penalty. Leaving a federation, leaving a managed transport for an appliance (§4.7), and changing operator are all unconditional. A compliance-proof rejection blocks the interaction (the session declines), never exit. A profile's recourse for abandoning a confirmed commitment is only the bilaterally-agreed terms in the agreement, never an operator-enforced lock. |
| 6 | Verifier Accountability | A proof or attestation request MUST be authenticated under the requesting party's stable KERI identity and the holder MUST retain a portable receipt of what was requested, for which declared purpose, and by whom (§5.8). Anonymous or unreceipted demands are rejected. A profile MUST preserve this accountability for every proof it requests. |
A profile inherits this table by running over the core. A profile MAY note how it realizes a safeguard structurally (as PRP's Localize algorithm realizes Safeguard 2), but it MUST NOT weaken any safeguard, and it does not re-establish them — they hold because the session does.
13. Profiles
13.1 What an AFP profile is
An AFP profile is a named behavior over the Bilateral Session Core (§12). ADR-0003 establishes the category: PRP and VXP are not peer protocols (a protocol is a wire contract between independent, mutually-distrusting implementations — strangers implement ZKA/ZKC/AFP), because both endpoints of PRP and VXP are always verified conforming instances (§1.2) — that is, AFP tenants. They are also not purely-internal components. They are profiles: standardized behaviors layered on AFP, shipped within AFP rather than as independent standards with their own governance.
A profile:
- has its own atom kinds and its own exchange state machine (PRP: SessionAtom/ComparisonAtom/…/CommitmentAtom over Compare→Localize→Evidence→Resolve→Commit; VXP: IntentAtom/…/SettlementAtom over Intent→Negotiation→Commitment→Fulfillment→Settlement);
- reuses, and MUST NOT re-specify, the core's:
- bilateral session establishment, lifecycle, and timeout (§12.2),
- identity binding (§12.3),
- NATS namespace, under
afp.{tenant}.{profile}.*(§12.4), - bilateral dispute / optional-arbiter model (§12.5),
- commitment + optional-anchor primitive (§12.6),
- session-envelope bundle wire format (§12.7),
- Freedom-Safeguards application (§12.8);
- runs inside a bilateral session whose
profilefield (§12.2.1) names it, between two entities whose federation agreement (§5.5) carries the profile's policy block.
The test for profile vs. protocol: if both endpoints are always AFP tenants (§1.5.1) and the behavior is a named way of using a bilateral session, it is a profile. ADR-0005's generalization of the tenant definition does not fire this test: a second, non-ADAMAS tenant implementation is still an AFP tenant, so PRP and VXP remain profiles and their trust posture is unchanged. Nor does the naming of an attestation authority (§6.3.1) fire it: a tenant attesting under any accepted authority, or under none, is still an AFP tenant. Consistent with ADR-0005's own out-of-scope clause for PRP/VXP, this sentence carries the unqualified phrase deliberately — the endpoint class it names is AFP tenants, and narrowing it to attested endpoints would change the profiles' scope, which ADR-0005 disclaims. If a behavior is later opened to counterparties that are not AFP tenants, promote that profile to a standalone standard then — promotion is cheap; premature standardization is expensive (ADR-0003).
13.2 Profile registration and extension
Profiles are registered lightly — AFP deliberately avoids a heavyweight registry (that would re-introduce marketplace machinery, §1.2). A profile is defined by a small, declarative profile descriptor:
interface AfpProfileDescriptor {
name: string; // the {profile} namespace label (§12.4); unique; lowercase; e.g. 'prp'
version: string; // profile spec version, e.g. '0.2'
spec_ref: string; // URI / citation of the profile specification
atom_kinds: string[]; // the profile's atom-kind discriminators, e.g. ['prp:session:v2', …]
policy_schema_ref: string; // the policy block this profile adds to the federation agreement (§5.5)
status: 'reference' | 'experimental' | 'deprecated';
}Registration rules:
- A profile
nameoccupies exactly one{profile}label in the §12.4 tree and MUST be unique across registered profiles. - The registry is the set of descriptors published with the AFP specification plus any a federation agrees bilaterally to recognize. There is no global, queryable, marketplace-style profile directory (§8.8's reasoning applies: such a directory would itself leak the coordination graph). Two tenants recognize a profile by naming it in their federation agreement; the descriptor is how they agree on its atom kinds and policy schema.
- A profile MUST register its atom-kind discriminators so they do not collide with another profile's or with the core's
afp:*:v1kinds. - Extension is by versioning the profile (a new descriptor
version), not by mutating the core. A profile MUST NOT add to or alter the core's §12 machinery; if a behavior needs a core change, that is an AFP-spec change, not a profile change. This keeps the core stable and the dependency acyclic: profiles depend on the core; the core depends on no profile. - A profile descriptor's
statusisreference(specified with AFP, the recommended set),experimental(recognized bilaterally, not yet a reference profile), ordeprecated.
13.3 Published profiles
AFP publishes the three v0.2 reference behaviors and two experimental profiles: the v0.3.x afp.introduction profile (§14), which remains experimental until its lower layers are operational (§14.9), and the pay v1.0 profile (§15), which remains experimental pending independent production-audit evidence for its Base settlement dependencies.
Profile (name) |
Behavior | Atom kinds | Spec | Status |
|---|---|---|---|---|
work (base) |
AFP's own tenant↔︎tenant work exchange — §5: a bilateral cross-tenant work request and a ZKA-carried result / attestation. The base behavior of the core, not a separate document. | afp:session:v1, afp:commitment:v1 + the §5.3 message classes |
This spec, §5 + §12 | reference |
prp |
Pairwise Reconciliation Protocol — bilateral state reconciliation: Compare → Localize → Evidence → Resolve → Commit (+ optional Anchor). | prp:session:v2, prp:comparison:v2, prp:divergence:v2, prp:evidence:v2, prp:resolution:v2, prp:commitment:v2, prp:anchor:v2 |
PRP v0.2 | reference |
vxp |
Value eXchange Protocol — bilateral value exchange: Intent → Negotiation → Commitment → Fulfillment → Settlement, across reciprocal / compute / data / capability / money domains. | vxp:* discriminators over IntentAtom, NegotiationAtom, CommitmentAtom, FulfillmentAtom, SettlementAtom, DisputeAtom, ChannelAtom, SubscriptionAtom |
VXP v0.2 | reference |
afp.introduction |
Introduction Tier — stranger onboarding: a credential + ZKC-proof handshake that auto-negotiates a scoped, collateral-priced provisional §5.5 agreement, with reputation-driven limit growth, graduation, and safeguard-compatible downgrade/termination. | afp:introduction-offer:v1, afp:introduction-request:v1, afp:credential-presentation:v1, afp:provisional-agreement:v1, afp:limit-adjustment:v1, afp:downgrade:v1, afp:termination:v1 |
This spec, §14 | experimental |
pay |
USD-obligation/WETH-settlement — a bilaterally authenticated USD commercial obligation and exact WETH-wei quote bound to a Base ZKA-native pool, with finality-aware receipt evidence. | afp:payment-quote:v1, afp:payment-receipt:v1 |
AFP v0.3.6-draft, §15 | experimental |
Each of PRP and VXP keeps its own behavior-specific machinery — PRP's O(D·log N) Merkle localization, named scope filters, star / mesh multi-party composition; VXP's value-domain taxonomy, exchange channels, subscriptions, per-domain breach conditions. Those are the profiles' value and stay in the profile specs. What they stop specifying is the shared core: session, identity, namespace, dispute / arbiter, commitment + anchor, bundle, safeguards — all now §12, referenced. PROFILE_MIGRATION_NOTE.md maps the specific PRP / VXP v0.2 sections that become references.
Multi-party note. PRP composes N-party reconciliation from bilateral sessions in a star (a coordinating center runs one session per site) or a small mesh. This is a profile composition of core bilateral sessions, not a core N-way construct — the core is bilateral by definition (§12.1), and any multi-party behavior is a profile arranging bilateral sessions. The optional pre-agreed arbiter of §12.5 (e.g. a trial coordinating center) and PRP's star hub are frequently the same entity, but the roles are distinct: arbiter (tie-break authority granted in the agreement) vs. hub (the party that holds the converged snapshot).
13.4 Profile conformance
A conforming AFP profile:
- runs every interaction inside a bilateral session (§12.1) whose
profilefield names it; - carries the core session, identity, commitment, and envelope fields (§12.2–§12.7) on its atoms, adding only profile-specific fields;
- addresses its subjects under
afp.{tenant}.{profile}.*(§12.4) and nowhere else; - resolves disputes under the §12.5 model (bilateral-only primary; at most a pre-agreed arbiter; no operator mediation; no forced outcome);
- inherits the §12.8 Freedom-Safeguards application and weakens no safeguard;
- registers a profile descriptor (§13.2) whose atom kinds do not collide.
A "profile" that defines its own session establishment, its own identity binding, its own standalone namespace, its own dispute model, or its own bundle wire format is not a conforming AFP profile under v0.3.1 — it is duplicating the core, which is exactly what this consolidation removes.
14. Introduction Tier
This section is normative, added in v0.3.x. It specifies how two tenants with no prior relationship bootstrap a working bilateral relationship automatically — a credential + ZKC-proof handshake that auto-negotiates a scoped, collateral-priced provisional federation agreement (§5.5), grows the envelope with provable history, graduates to a standard agreement, and degrades or terminates safely — without weakening operator blindness (§3), OB-2 routing (§8), or the Freedom Safeguards (§9 / §12.8).
It closes a gap three earlier sections leave open. §5.1 assumes "the counterparty is already known … there is no discovery feed, no open call, no solver." §12.1 opens a session "only between two tenants that already hold a bilateral federation agreement." §10 lists instance-level reputation as open, "working assumption is no." The introduction tier is the specified path from strangers to a first agreement, expressed as an afp.introduction.v1 profile (§13) over the Bilateral Session Core (§12) plus the narrow §12.1 bootstrap exception. The progressive-trust model has four steps: (1) open discovery + credential handshake → a scoped small-limit provisional agreement; (2) first value interactions settle trustless on the ZKA-direct rail or under collateral-anchored small-limit channels; (3) limits grow with ZK-provable interaction history; (4) graduation to a standard §5.5 agreement and fast APL channels.
14.1 The willingness-to-federate directory
Discovery is opt-in. An instance that wishes to be approachable by strangers MAY publish an offer record to a KERI-anchored, witness-replicated directory, structurally the same object as the §8.7 mix-node directory:
interface AfpIntroductionOffer { // afp:introduction-offer:v1
kind: 'afp:introduction-offer:v1';
tenant_aid: string; // who is open to being approached
accepted_credential_classes: string[]; // e.g. ['zkc:proof:jurisdiction:v2',
// 'zkc:proof:kyc:v2',
// 'afp.telemetry.instance_attestation.v2']
entertained_envelope: { // the CEILING it will negotiate within — not an offer of credit
assets: string[];
max_per_interaction: string; // decimal string
max_cumulative: string;
collateral_required: boolean;
};
route_handle: string; // §8.8 private-route resolution handle (not a clear address)
directory_epoch: number; // binds to the directory epoch (anti-replay), as §8.7
}The directory is KERI-anchored and verified exactly as §8.7's mix-node directory: a tenant MUST reject an offer whose KERI anchor is absent, whose witness quorum is below the federation threshold (§4.5), or whose directory_epoch does not match the directory's current epoch exactly (a stale epoch is replay; a future epoch cannot yet be validated).
Reconciliation with §5.1 and §8 (normative). This directory does not reintroduce the v0.2.0 marketplace (§1.2) and does not weaken OB-2 (§3, §8):
- It carries no work descriptor and no open call for work — only a tenant's consent to be approached for an introduction. §5.1 bans a work-discovery feed; a willingness directory is a different object and is permitted.
- It reveals which tenants are open, never which tenants have paired. First contact is routed over the §8 mixnet, so the actual tenant pairing remains OB-2-private. The directory is no more graph-revealing than §8.7's published list of mix nodes.
- Publishing is OPTIONAL. A non-publishing instance remains reachable by an out-of-band-provided tenant AID; the directory is a convenience for open instances, not a precondition for the handshake.
The directory's hosting, epoch rotation, and gossip operation are a federation-operational concern outside AFP scope, not a tenant-internal one — the record is witness-replicated and verifiable without trusting any single party, structurally like the §8.7 mix-node directory, so no single tenant discharges it (a companion requirement); this section specifies the record shape, the OB reconciliation, and the conformance rules only.
14.2 The afp.introduction.v1 profile and the bootstrap exception
The introduction tier is an AFP profile (§13), registered with the descriptor:
{
name: 'afp.introduction',
version: '1.0',
spec_ref: 'AFP v0.3.x §14',
atom_kinds: [
'afp:introduction-offer:v1',
'afp:introduction-request:v1',
'afp:credential-presentation:v1',
'afp:provisional-agreement:v1',
'afp:limit-adjustment:v1',
'afp:downgrade:v1',
'afp:termination:v1',
],
policy_schema_ref: 'afp:introduction-policy:v1',
status: 'experimental', // promoted to 'reference' once the lower layers are operational federation-wide (§14.9)
}The bootstrap exception is a core change, not a profile-local one. §13.2 rule 4 forbids a profile from altering the core; a behavior that needs a core change is an AFP-spec change. The introduction tier needs exactly one: §12.1's agreement-scoped precondition must permit the introduction session to open under a provisional agreement. That amendment is stated in §12.1 and is bounded to this profile: the introduction session is the sole session type permitted to open by minting a provisional agreement in place of a pre-existing one, and every other session guarantee (mutual authentication §5.2 step 2, end-to-end encryption, OB-2 routing §8, context binding §5.2 step 4) is unchanged and still REQUIRED. (One other trust basis admits a session with no pre-existing agreement — open federation (§5.8, ADR-0004) — but it is not an exception to this profile carve-out: it opens with no agreement at all, via per-interaction ZKC attestation, rather than minting a provisional one, and is base behavior of §5, not a profile driving session opening. §12.1 enumerates all three trust bases.)
The instance-attestation credential named below is §6.3 afp.telemetry.instance_attestation.v2. Each side's introduction policy MUST name the root classes and root-authority identifiers it accepts; the proving side selects one accepted pair and proves the v2 predicate under that branch. The verifier performs the matching §6.3.1 root-specific obligations in addition to ordinary ZKC v0.4 verification, including out-of-circuit resolution of any delegated publisher signing AID to the named authority root. Neither side is required to accept any particular authority; a policy naming no authority declines v2 attestation rather than defaulting to one, and an authority an implementation ships as a default entry has this standing only once that party signs it into the policy (§14.4). A v1 proof does not satisfy a v2 request and MUST NOT be reinterpreted as carrying a generic root class.
14.3 The handshake
The handshake runs inside one §12 bilateral session (profile: 'afp.introduction') and drives it from zero trust to a signed provisional agreement:
PROPOSED ──▶ CREDENTIALING ──▶ NEGOTIATING ──▶ BONDING ──▶ ACTIVE
│ │ │ │ │
└─▶ABORTED └─▶DECLINED └─▶DECLINED └─▶ABORTED ├─▶ DOWNGRADED ──▶ (ACTIVE)
/ABORTED /ABORTED ├─▶ TERMINATED
└─▶ GRADUATED
The opener fixes the credential-validity interval used by any v2 instance-attestation APR:
interface AfpIntroductionRequest { // afp:introduction-request:v1
kind: 'afp:introduction-request:v1';
initiator_tenant_aid: string;
responder_tenant_aid: string;
offered_credential_classes: string[];
required_credential_classes: string[];
proposed_envelope: object; // the §14.4 envelope fields
attestation_window: { starts_at: datetime; ends_at: datetime };
}Every introduction request MUST carry attestation_window; starts_at MUST precede ends_at, and both endpoints MUST lie within the introduction session lifetime. If v2 is requested, its APR and proof MUST copy this exact window. Requiring the field universally keeps the v1 request shape deterministic even when a request does not ask for instance attestation.
Relationship to ZKM mandates. The provisional agreement produced here is a bilateral AFP policy envelope; a ZKM mandate is a unilateral, principal-issued authorization consumed by an agent. An interaction MAY be subject to both. The AFP per-interaction/cumulative limits and the ZKM cap, expiry, policy, and revocation checks are evaluated independently, and both must accept. Forming, downgrading, terminating, or graduating an AFP agreement does not issue, widen, revoke, or consume a ZKM mandate. Conversely, ZKM revocation does not mutate the AFP agreement or any underlying ZKA note.
- PROPOSED. The initiator opens the session to the responder tenant AID (resolved via §14.1 or out-of-band) carrying an
afp:introduction-request:v1: the credential classes it offers, the classes it requires, the explicit v2attestation_window, and a proposed envelope (assets, limits, collateral, rotation policy, expiries). - CREDENTIALING. Each side presents an
afp:credential-presentation:v1— ZKA v0.9.0-draftzka:bundle:v1(§12.7) wrapping atomic, scoped ZKC v0.4zkc:proof:v2proofs (jurisdictionzkc:proof:jurisdiction:v2, KYC-where-requiredzkc:proof:kyc:v2, instance-attestationafp.telemetry.instance_attestation.v2). The APR requestszkc:presentation:scoped:v1withscopePolicy = protocol:afp:<introduction-policy-hash>and the accepted instance-attestation root classes and root authorities; the proof satisfies the AID/APR/session binding of §5.9 and the selected §6.3.1 branch. Anonymous downgrade is forbidden because these presentations become the credential basis of the provisional agreement. Linkable v1 is permitted only after explicit holder consent. Each side verifies the other against its own policy. Failure only declines the interaction; it never gates tenant state. - NEGOTIATING. The two sides converge on a scoped envelope — the intersection of offered/required credentials plus a mutually acceptable {permitted assets, per-interaction limit, cumulative-outstanding limit, collateral requirement, rotation policy, credential validity expiries, agreement expiry}. Either side MAY counter; either side MAY abort unconditionally (Safeguard 5).
- BONDING. Collateral is posted against exposure, which may be asymmetric. Whichever side(s) the negotiated envelope extends credit to — the side that could be left owing if a non-atomic settlement fails — posts the required bond into the pre-agreed escrow (the §12.5 2-of-3 / arbiter-co-signed escrow). A symmetric envelope bonds both sides; an asymmetric one bonds only the credit-taking side. A trustless-ZKA-direct-only envelope skips bonding entirely: settlement is atomic, so neither side is exposed and no credit is extended.
- ACTIVE. Both parties sign an
afp:provisional-agreement:v1with the §12.6.1 bilateral, KEL-anchored signature machinery. The provisional agreement is now a valid, narrowly-scoped §5.5 federation agreement carrying anafp:introduction-policy:v1block. Subsequent value interactions run as ordinary work (§5) / VXP sessions scoped by this agreement, each bounded by the envelope and the posted bond.
Terminal states: DECLINED (credential or negotiation failure; no agreement is formed), ABORTED (unconditional exit, §12.8 Safeguard 5), and from ACTIVE: DOWNGRADED (the envelope contracts, then the session returns to ACTIVE under the new terms; §14.7), TERMINATED (§14.7), and GRADUATED (replaced by a standard §5.5 agreement; §14.6).
14.4 The provisional agreement and the introduction policy block
interface AfpProvisionalAgreement { // afp:provisional-agreement:v1
kind: 'afp:provisional-agreement:v1';
tier: 'introduction'; // distinguishes it from a standard §5.5 agreement
// §5.5 agreement core: mutually signed by both tenant root (or delegated) AIDs,
// using the §12.6.1 bilateral-signature shape.
initiator_signature: AfpCommitSignature; // §12.6.1: signed_by, kel_event_dig, signature, signed_at
responder_signature: AfpCommitSignature;
policy: AfpIntroductionPolicy; // afp:introduction-policy:v1
reputation_state: { // the running, signed basis for §14.6 growth
settled_interactions: number;
cumulative_settled_value: string;
slash_events: number;
last_commitment_ref?: string; // chains into the §12.6.1 commitment history
};
}
interface AfpIntroductionPolicy { // afp:introduction-policy:v1
permitted_entities: string[];
permitted_assets: string[];
max_per_interaction: string;
max_cumulative_outstanding: string;
bond_ref?: string; // escrow reference; absent only for a trustless-ZKA-direct-only envelope
accepted_credentials: { class: string; valid_until: string }[];
accepted_instance_attestation_roots: {
root_class: 'root:build-provenance' | 'root:tee-attestation' | 'root:publisher-manifest';
root_authority_identifier: string;
}[]; // signed v2 verifier policy; empty only when v2 is not required
rotation_policy: string; // KEL rotation expectations (§4.6)
growth_curve: object; // negotiated step / cap / decay (§14.6) — not protocol-fixed
agreement_expires_at: string;
}A provisional agreement is a specialization of the §5.5 federation agreement, distinguished by tier: 'introduction' and a reputation state — and, for a bonded envelope, a bond reference (bond_ref is absent for a trustless-ZKA-direct-only envelope, §14.5). When accepted_credentials contains afp.telemetry.instance_attestation.v2, accepted_instance_attestation_roots MUST contain at least one pair, and the verifier MUST accept only a proof whose public root_class and root_authority_identifier exactly match one signed pair. If v2 is not required the array MUST be empty; it MUST NOT silently authorize an attestation predicate. A pair that a party's implementation ships as a default entry has no different status here: it is signed into the agreement by that party or it does not apply. An empty array means the predicate is not required — never that a shipped default applies. Like every §5.5 agreement the complete policy is private to the two tenants, bilaterally signed, not published, and not discoverable. It is referenced by the afp.work.request (§5.3) / VXP intent messages of the value interactions it scopes.
14.5 Settlement safety floor
Every value interaction under an introduction-tier agreement MUST be either:
- (a) trustless ZKA-direct — settled atomically on the ZKA rail with no credit extended (the safe default), or
- (b) bonded — outstanding exposure MUST NOT exceed the collateral posted in the pre-agreed escrow.
A conforming implementation MUST NOT allow uncollateralized credit at the introduction tier. Credentials gate which counterparties and assets are eligible; the bond is what bounds loss. (Instance identity is not sybil resistance; collateral prices the risk.)
Enforcement location. The floor is enforced at the agreement / channel-policy layer: the provisional agreement's envelope populates the channel policy block (the APL channel policy block for bonded and graduated channels), and the layer that authorizes or opens a value channel enforces "ZKA-direct, or bonded ≤ collateral" before any interaction. This section defines the conformance property; the channel-policy layer implements it. A value profile (e.g. VXP) is not special-cased to carry introduction-tier validation — cross-cutting policy lives at the core/agreement layer (§13.2 rule 4).
14.6 Progressive limit growth and graduation
Mechanism (normative). A party MAY present an afp:reputation-proof — a zero-knowledge proof of N settled interactions and cumulative settled value with zero slash/default events, bound to this agreement and counterparty and non-replayable (bound to the agreement id and a monotonic counter, mirroring the §12.6.1 chaining). On verification, the presenter proposes an afp:limit-adjustment:v1 raising the envelope; both parties sign it with the §12.6.1 machinery. A rejected, stale, or slash-bearing proof yields no adjustment — never a freeze.
Curve (policy, not protocol). The growth curve — step size, caps, decay on inactivity — is a negotiated parameter in the afp:introduction-policy:v1 block. AFP fixes the predicate and the adjustment handshake; the numbers are the bilateral choice. This mirrors the house rule that the mechanism is normative and the parameters are negotiated (cf. §8.2).
Graduation. When the parties choose, they replace the provisional agreement with a standard §5.5 agreement — no introduction tier, no mandatory bond, eligible for fast APL channels. Graduation is itself a bilateral signed act; the §12.6.1 chained-commitment history carries forward as the relationship's provable basis. The reputation proof and the on-chain escrow are lower-layer constructs (§14.9); this section owns the predicate's interface and the adjustment/graduation handshake, not the circuit.
14.7 Downgrade and termination
Both are safeguard-compatible: nothing here freezes, claws back, or redirects tenant state (Safeguard 3), and exit is unconditional (Safeguard 5).
Downgrade. The go-forward envelope contracts — lower limits, higher collateral, narrower assets. Triggers include a lapsed credential validity (the policy-block expiry), a late or missed settlement, a signed dispute-divergence record (§12.5), partial collateral consumption, or reputation decay. A downgrade is realized either as a re-negotiation both parties sign (an afp:downgrade:v1 followed by a new afp:limit-adjustment:v1) or as a unilateral protective contraction: a party MAY always decline to extend further credit or offer a smaller envelope (declining an interaction is always permitted, §5.4 / Safeguard 5). A party can never unilaterally compel the counterparty to act.
Termination. Recorded as an afp:termination:v1; either party MAY terminate unconditionally and with no protocol penalty (Safeguard 5). Residual obligations settle against the pre-posted escrow only — the §12.5 arbiter-co-signed release, bounded by what was escrowed, never against base-layer custody (Safeguard 5). Any unsettled dispute leaves a signed divergence record (§12.5).
Default consequence. A default during the relationship additionally yields a portable negative-history record — a signed statement of the default, usable as anti-reputation input to the counterparty's future introductions. It is the only persistent consequence beyond loss of the bond, and it is itself only a signed record: not a freeze, not a clawback (Safeguard 3).
14.8 Composition and Freedom Safeguards
- OB-2 (§8). The introduction handshake and every subsequent interaction are routed through the §8 mixnet. The willingness directory (§14.1) reveals openness, never pairing; the coordination graph is never operator-visible.
- APL channel policy block. The envelope's {permitted assets, limits, collateral} populate the channel policy block under which bonded and graduated channels run (§14.5).
- Freedom Safeguards (§12.8). The introduction profile inherits all six by running over the core and weakens none:
- Predicate Pluralism (1) — AFP privileges no credential issuer or set; the required credentials are each tenant's bilateral choice recorded in the agreement.
- Minimal Disclosure (2) — credentials are atomic ZKC proofs, one predicate per proof.
- No Revocability (3) — bond slashing is bounded to pre-escrowed funds with the pre-agreed arbiter co-signature (§12.5); it never touches tenant state or a ZKA note.
- Open-Source Predicates (4) — the credential and reputation circuits MUST be open-source, versioned, and published with a public review period (ZKC §1.4).
- Agent Exit Rights (5) — decline, abort, and terminate are all unconditional and penalty-free.
- Verifier Accountability (6) — every credential or reputation-proof request is authenticated under the requester's stable identity and leaves the holder a portable receipt (§5.8); no anonymous proof demand is accepted.
14.9 Open seams and honest disclosure
The introduction tier is the AFP-owned protocol contract; several lower layers it composes are owned and tracked elsewhere. AFP owns the handshake, the provisional-agreement object, the introduction policy block, and the conformance properties of this section. It references — and does not redefine — the following:
| Construct | Owner / status |
|---|---|
| Trustless ZKA-direct settlement rail | ZKA (transfer + unconditional withdrawal); operational. |
zka:bundle:v1 carrying the credential presentation |
ZKA §5.4 (credential-only bundles are supported; no payment leg required). |
| ZK reputation-proof circuit (§14.6) | ZKA/Reputation — companion requirement; not yet operational. |
| On-chain bilateral escrow with mutually-chosen arbiter (§14.5, §14.7) | Companion requirement (ADAMAS.Network / ZKA escrow contract); not yet operational. |
| Instance-attestation predicate (§14.2) | §6.3 afp.telemetry.instance_attestation.v2; AFP fixes the public statement and root-specific verifier obligations. The separately versioned ZKC machine build/runtime credential schema and issuer-adapter registry entry are companion work; raw provenance bundles and TEE quotes remain provider-side. An authority's key ceremony, published trust material, and the out-of-band AID↔︎legal-entity binding (§6.3.1) are likewise companion work owned by that authority rather than by AFP; for the ADAMAS product line these are Pyramidal Inc.'s and are not yet operational. |
Honest disclosure. A deployment whose selected §6.3.1 v2 root path, escrow, or ZK-reputation lower layer is not yet operational MUST disclose the introduction tier as spec-available but not fully operational rather than claim it, exactly as §3.5 and §8.9 require for OB-1/OB-2. The profile descriptor's status: experimental (§14.2) reflects this until the lower layers are operational federation-wide. Claiming a fully operational introduction tier without a realized v2 credential/circuit/verifier path, the reputation circuit, and the bonded-escrow rail in place is a conformance violation, independent of the correctness of any one available component.
15. USD Obligation / WETH Settlement Profile (pay)
This section is normative, added in v0.3.6-draft. It defines the AFP
pay v1.0 profile: two principals agree on a commercial obligation denominated
in USD minor units and an exact settlement amount denominated in WETH wei, then
bind that bilateral quote to a private ZKA/Pay transfer. The profile is additive:
afp:session:v1, afp:envelope:v1, and zka:bundle:v1 are unchanged; AFP-KDC
v1.0.2 preserves the v1.0.1 domain-tag clarification while pinning the already
deployed Poseidon2 parameters, and therefore leaves every payment derivation
and cryptographic artifact unchanged.
15.1 Ownership boundaries and registration
AFP owns the principal-readable USD obligation; the exact WETH-wei quote; its
source, time, expiry, slippage bound, nonce, and bilateral authentication; its
binding to a live AFP session and a specific immutable ZKA pool; and the
observational settlement receipt. ZKA owns notes, nullifiers, roots, proofs,
conservation, pool state, pool_context, the payment proof's
interaction_context, unconditional spend/withdrawal semantics, and each
pool's asset-risk profile. APL owns selection of the configured :zka_direct
route, construction and submission of the ZKA SDK request, finality observation,
and route evidence returned to AFP.
AFP does not verify a ZKA proof, query an exchange-rate oracle, decide whether a price is fair, custody value, define a multi-asset pool, or reproduce ZKA calldata or circuit schemas. ZKA does not interpret USD, choose a pricing source, or enforce quote fairness. APL does not alter a signed quote.
The published descriptor is:
{
name: 'pay',
version: '1.0',
spec_ref: 'AFP v0.3.6-draft §15',
atom_kinds: [
'afp:payment-quote:v1',
'afp:payment-receipt:v1',
],
policy_schema_ref: 'afp:payment-policy:v1',
status: 'experimental'
}experimental reflects the absence of the independent-audit evidence required
to claim the launch settlement dependencies as production-ready. It does not
weaken this wire contract or any conformance requirement below.
The federation agreement names the profile through this policy block:
interface AfpPaymentPolicyV1 {
kind: 'afp:payment-policy:v1';
deployment: {
chain_id: 8453;
asset: '0x4200000000000000000000000000000000000006';
pool: string;
pool_context: string;
asset_risk_profile: 'zka:asset-risk:s3-hard:v1';
};
accepted_rate_sources: string[];
max_quote_lifetime_seconds: number;
max_slippage_bps: number;
required_finality: 'safe' | 'finalized';
}accepted_rate_sources MUST be non-empty and contain unique, non-empty names.
max_quote_lifetime_seconds MUST be a positive integer;
max_slippage_bps MUST be an integer in 0..10000. The deployment fields use
the canonical encodings of §15.2 and identify one immutable pool. An accepted
quote's deployment and rate source MUST match this policy exactly, its lifetime
MUST NOT exceed the policy maximum, and its slippage bound MUST NOT exceed the
policy maximum. Receipt finality MUST reach required_finality before the
application represents the interaction as settled. The Bilateral Session Core's
profile_policy_hash MUST equal
qb64("D", SHA-256(canonical_json(AfpPaymentPolicyV1))), and a pay quote MUST
be accepted only inside a live session whose profile is exactly pay and
whose policy hash matches the policy used for validation.
15.2 Canonical payment quote
interface AfpPaymentQuoteV1 {
kind: 'afp:payment-quote:v1';
quote_id: string;
session_ref: string;
session_context: string;
obligation: {
currency: 'USD';
amount_minor: string;
};
settlement: {
chain_id: 8453;
asset: '0x4200000000000000000000000000000000000006';
pool: string;
amount_wei: string;
pool_context: string;
asset_risk_profile: 'zka:asset-risk:s3-hard:v1';
};
rate: {
source: string;
usd_minor: string; // settlement-rate numerator
weth_wei: string; // settlement-rate denominator
reference_usd_minor: string; // signed source-rate numerator
reference_weth_wei: string; // signed source-rate denominator
};
quoted_at: string;
expires_at: string;
max_slippage_bps: number;
quote_nonce: string;
initiator_signature?: AfpCommitSignature;
responder_signature?: AfpCommitSignature;
}obligation.amount_minor, settlement.amount_wei, and all four rate integers
MUST be positive canonical unsigned base-10 integer strings:
digits only, with no sign, whitespace, decimal point, exponent, or leading zero.
USD is represented in ISO 4217 minor units (cents for USD) and WETH in wei. A
wire implementation MUST NOT use a binary floating-point value for any monetary
or rate component. The Base launch profile additionally requires
settlement.amount_wei <= 2^120 - 1, matching ZKA's frozen
MAX_NOTE_VALUE_WEI; quote construction and validation MUST reject a larger
amount before signing or routing it.
The rate is the exact positive rational usd_minor / weth_wei. Quote
construction and validation MUST use arbitrary-precision integer arithmetic
without overflow or truncation and the deterministic
ceiling rule:
settlement.amount_wei = ceil(
obligation.amount_minor * rate.weth_wei / rate.usd_minor
)
Equivalently, implementations MAY compute
(amount_minor * weth_wei + usd_minor - 1) // usd_minor; any different result
MUST be rejected, and a result above the frozen ZKA note-value bound MUST be
rejected. max_slippage_bps MUST be an integer in 0..10000. The
reference rational is reference_usd_minor / reference_weth_wei; the
settlement rational is usd_minor / weth_wei. Validation MUST also enforce,
with arbitrary-precision integer arithmetic:
abs(usd_minor * reference_weth_wei
- reference_usd_minor * weth_wei) * 10000
<= max_slippage_bps * weth_wei * reference_usd_minor
This is the absolute settlement-price deviation divided by the signed reference
price. It constrains pre-signing quote construction, never permits execution
above the signed settlement.amount_wei, and no post-signing repricing is
allowed. AFP fixes no global rate source; rate.source names the bilateral
source or policy whose signed reference observation the parties accepted.
Every address in canonical quote content MUST be normalized before hashing to
lowercase 0x followed by exactly 40 hexadecimal digits. Mixed-case,
checksum-case, short, overlong, or otherwise non-canonical encodings MUST be
rejected, not silently normalized during verification. The Base launch profile
fixes chain_id to 8453 and settlement.asset to the lowercase Base WETH9
address shown above. settlement.pool, settlement.pool_context, and
settlement.asset_risk_profile MUST exactly match the configured immutable
deployment. pool_context MUST use the ZKA SDK's canonical nonzero BN254 field
encoding: lowercase 0x followed by exactly 64 hexadecimal digits whose
big-endian integer is in 1..r-1, for the modulus r in §15.4.
session_context MUST be a qb64-encoded 32-byte digest (§15.4); any other
form is out of profile and MUST be rejected.
quoted_at and expires_at MUST have exactly the whole-second UTC form
YYYY-MM-DDTHH:MM:SSZ; fractional seconds and numeric UTC offsets are invalid.
Both timestamps MUST represent valid instants, and expires_at MUST be strictly
later than quoted_at. The quote is expired when the verifier's current time is
greater than or equal to expires_at.
quote_nonce MUST be lowercase 0x followed by exactly 64 hexadecimal digits
(32 bytes). A signing party MUST NOT sign two different quote contents with the
same nonce. quote_id is a caller-supplied, non-empty stable identifier. An
acceptor MUST atomically and durably reserve the exact
(quote_id, quote_hash, quote_nonce, acceptance_record_hash) acceptance tuple
when accepting a quote.
This durable acceptance ledger is the authority consumed by route and receipt
tracking code. A repeated accepted quote is a duplicate and MUST NOT execute again; the
same ID with different canonical content or the same nonce with a different
quote is a terminal validation failure. A rejected quote MUST NOT consume either
replay key. A restart or process crash MUST NOT make an accepted replay key
available again.
15.3 Quote hash and bilateral authentication
quote_content is the quote object with
initiator_signature and responder_signature absent (not null). AFP uses
the canonical JSON serialization already used by the Bilateral Session Core:
UTF-8, lexicographically sorted object keys, and compact separators. Hashes use
SHA-256 and AFP's D qb64 digest convention:
quote_hash = qb64("D", SHA-256(canonical_json(quote_content)))
Both principals MUST sign this exact quote_hash using the
AfpCommitSignature shape of §12.6.1. Both signatures are required. Each
signature MUST be genuine under the signer's KEL-current Ed25519 key identified
by kel_event_dig; a stale, unknown, malformed, or mismatched key state MUST be
rejected. AfpCommitSignature.signed_at is untrusted observational metadata: it
is not covered by the Ed25519 payload, MUST NOT be used for freshness or
authority, and changing it neither invalidates nor strengthens quote
authentication. Freshness derives only from the signed quoted_at and
expires_at fields compared with the verifier's current time. The initiator and
responder AIDs MUST be the two principals of the live session,
and session_ref and session_context MUST exactly match that session. The
session context MUST recompute from the signed §12.2.1 session fields, and both
session AfpPartyRef signatures MUST verify against KEL-current delegated
identity and the declared witness thresholds before quote acceptance. An
operator, arbiter, application administrator, or one party alone cannot
substitute for either signature.
Signatures authenticate the exact settlement amount and all quote controls, including the rate source, pool, pool context, risk profile, timestamps, slippage bound, ID, and nonce. Any mutation after either signature was made invalidates authentication.
After both signatures verify, the acceptor MUST compute
acceptance_record_hash = qb64("D", SHA-256(canonical_json(full_quote))), where
full_quote includes both complete signature objects. This is distinct from
the content-only quote_hash. The exact signed-record hash is stored in the
durable acceptance tuple, retained by AcceptedPaymentQuote, and recomputed
from its sealed canonical wire on every use. Removing, replacing, or changing
either signature therefore cannot reuse a content-only acceptance reservation.
15.4 Interaction-context derivation
The ZKA payment for an accepted quote MUST bind the canonical nonzero BN254
field value derived below. The derivation is the ZKA/Pay Interaction-Context
Binding Profile v1 fold — restated here normatively and in full, so this
specification stays auditable standalone, and pinned to its frozen
upstream definition: ZKA docs/sdd/sdd_pay_interaction_binding.md §2 at
ZKA.dev db39afd, KAT fixture SHA-256
e0d7fd0442b3cfd14c21c949ae13bcf9fba4250b2067300f729aaad2d38a2a8d. On any
divergence the pinned v1 governs. A future upstream v2 does not bind AFP;
adopting one requires a new revision of this specification and a superseding
decision record (ADR-0006).
Both fold inputs are 32-byte digests mapped into the scalar field by plain big-endian reduction:
r = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
afp_quote_hash_field = big_endian_uint256(quote_digest) mod r
session_context_field = big_endian_uint256(session_digest) mod r
Here r is the BN254 scalar-field modulus. quote_digest is the 32 SHA-256
digest bytes the §15.3 quote_hash encodes — its qb64 payload: the digest
itself, never its string encoding.
session_digest is the digest payload of the §12.3 session context: for this
profile the session context MUST be a qb64-encoded 32-byte digest, whatever
its derivation code, and a session context that is not one is out of profile
and MUST be rejected fail-closed, never mapped by a fallback. This mapping is
a reading of the same §5.2/§12.3 session context into field form — not a
parallel context, and the §12 envelope/bundle context equality is unaffected.
The wire stays unreduced: the quote digest carried to ZKA or an APL
adapter is the raw 32 bytes, and any party recomputing the fold MUST apply
the reduction above first — roughly four of five uniformly distributed
digests exceed r, so skipping it is the common-case error. Truncation is
non-conforming. A reduction yielding zero MUST be rejected fail-closed
(probability 6/2²⁵⁶ ≈ 2⁻²⁵³; never substituted). Both inputs exist before
either quote signature, so the producer MUST perform the full derivation at
quote-construction time, before signature exchange and nonce reservation — a
zero reduction there costs only a reissue with a fresh quote_nonce; at
dispatch it is a terminal §15.5 fail-closed condition.
The derivation is the field-count-prefixed Poseidon2 fold over the §A.3
instance's two-field hash P(a, b):
INTERACTION_CONTEXT_TAG =
0x00007a6b612f7061792f696e746572616374696f6e2d636f6e746578742f7631
// the UTF-8 bytes "zka/pay/interaction-context/v1",
// right-aligned in a 32-byte word — a fixed byte tag (§A.4 carve-out)
state = P(INTERACTION_CONTEXT_TAG, Field(2))
state = P(state, afp_quote_hash_field)
state = P(state, session_context_field)
interaction_context = "0x" || lowercase_hex_64(state)
Field(2) is the field element two — the count of folded input fields,
absorbed as an ordinary second sponge input to the first call, not an arity
annotation. The input order is normative — quote hash first; order sensitivity is proven
by the upstream asymmetry-pair vector. A zero fold output MUST be rejected
fail-closed, matching the ZKA circuit's interaction_context != 0 assertion.
The tag is a raw fixed byte tag consumed directly as a field element
under the §A.4 carve-out for ZKA pool-adjacent fold tags; it is not a §A.3
deriveFieldTag domain, and it is currently registered in no family registry
— its defining authorities are the pinned SDD §2 and the ZKA SDK's
INTERACTION_CONTEXT_TAG_HEX constant. Registration in ZKA's
operational-domains registry is tracked as
req_zka_register_zkapayinteractioncontextv1__923e6c97, with AFP-side
vendoring and drift-gate coverage as
req_afp_vendor_zka_operationaldomains_regist_c22a7062.
The application or APL adapter MUST provide the 32-byte lowercase hex field
value, not a digest or a qb64 encoding, as the ZKA payment request/proof's
interaction_context; the pool and proof MUST enforce the corresponding
binding. The envelope retains the original session_context. Implementations
MUST NOT replace, concatenate, reinterpret, or overload that session field, and
MUST NOT use a raw quote hash or raw session context in place of the derived
field.
Conformance vectors. An implementation MUST reproduce, byte-identically,
all five vectors of the pinned upstream KAT fixture — small-ints,
one-one, max-canonical, keccak-derived-quote, asymmetry-pair
(published verbatim as the normative artifact
docs/pay_interaction_context_kat_v1.json) — and the
AFP-native end-to-end vector below, which pins the qb64→field seam the
upstream vectors cannot exercise. Both of its digests exceed r, so the
reduction leg is vector-pinned rather than prose-pinned. For the §15.2
canonical quote content
{"expires_at":"2026-07-11T12:05:00Z","kind":"afp:payment-quote:v1","max_slippage_bps":100,"obligation":{"amount_minor":"2500","currency":"USD"},"quote_id":"quote-2026-08-16-0002","quote_nonce":"0xabababababababababababababababababababababababababababababababab","quoted_at":"2026-07-11T12:00:00Z","rate":{"reference_usd_minor":"250000","reference_weth_wei":"1000000000000000000","source":"bilateral:base-weth-usd:v1","usd_minor":"250000","weth_wei":"1000000000000000000"},"session_context":"X_eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee","session_ref":"session-0001","settlement":{"amount_wei":"10000000000000000","asset":"0x4200000000000000000000000000000000000006","asset_risk_profile":"zka:asset-risk:s3-hard:v1","chain_id":8453,"pool":"0x1234567890abcdef1234567890abcdef12345678","pool_context":"0x0101010101010101010101010101010101010101010101010101010101010101"}}the derived values are:
quote_hash = D_f5637ad946fbcc4ca76a7bb137a1796c1eb60c24590d773231d36f4759e8462d
afp_quote_hash_field = 0x036df29ae103ab7c0dd91f20b01abf9a55b282b9f86e445bde69a363a9e84628
session_context_field = 0x2d5db5236a286e480dadd814e8e98d7a4e1f4dcd08092ca9df67189f2eeeeeea
interaction_context = 0x26232f5bff7eb274b601df155b7b47abf504fc5778bdc2b35b961d14e5140d6d
Implementations MUST reproduce this vector exactly.
15.5 Acceptance and route execution
Before dispatching a route, an implementation MUST fail closed unless all of the following hold:
- the quote has the exact kind and canonical field encodings of §15.2;
- the amount satisfies the deterministic ceiling-rate calculation;
- the caller presents an immutable live-session capability minted by the AFP
session core only after its owning instance completed the normal
propose/accept-or-confirm/activate lifecycle; the detached session snapshot,
identifiers, both
AfpPartyRefsignatures, and both quote signatures all verify KEL-current; - the quote is unexpired and its ID and nonce pass atomic replay checks;
- chain, WETH asset, immutable pool address,
pool_context, andzka:asset-risk:s3-hard:v1match configured Base launch policy; and - the derived
interaction_contextis supplied unchanged to the ZKA request, and no §15.4 field reduction of either fold input yielded zero — a terminal fail-closed condition at dispatch, reachable only if the mandatory quote-construction-time derivation (§15.4) was skipped.
The live-session capability is owner-bound and non-serializable. Every access MUST revalidate the exact current FSM, normal activation provenance, and expiry against the owning session manager's clock. Closing, aborting, expiring, or replacing that FSM invalidates every previously minted handle. A restarted manager MUST rehydrate the durable session/FSM state, re-establish its lifecycle provenance, and mint a new handle; it MUST NOT persist or restore the handle as standalone authority.
Unknown or unsupported chain, asset, pool, context, risk profile, signature, or
finality state MUST be rejected. APL MUST submit exactly the signed
settlement.amount_wei; it MUST NOT reprice, substitute an asset or pool, or
route through a mutable/multi-asset settlement contract. A failed proof or
submission leaves the interaction unsettled.
Successful acceptance MUST produce an immutable, non-wire
AcceptedPaymentQuote capability minted only by the acceptance boundary after
all checks above succeed. Its constructor MUST NOT be public, and it MUST NOT
accept a caller-supplied quote hash. The minting path MUST canonically
reconstruct the detached quote and recompute and verify quote_hash before
retaining the quote ID, nonce, interaction context, and canonical quote wire.
Receipt or route code MUST require this capability and a store-issued limited
acceptance/route authority. It MUST fail closed unless the ledger contains the exact
capability (quote_id, quote_hash, quote_nonce, acceptance_record_hash) tuple;
verifier absence, false, or error is rejection. The tracker MUST recheck this reservation at
initialization. A copied capability for the same sealed wire and exact durable
tuple is equivalent; an unreserved capability is not accepted authority.
The deployment composition root MUST construct one
PaymentReceiptVerificationContext that binds a store-issued limited
acceptance/route authority and the deployment's finality-evidence verifier. The
writer/replay store itself, an arbitrary structurally similar object, and an
untrusted request-selected verifier MUST be rejected. Route evidence creation,
route decoding, and tracker creation MUST use only this preconfigured context;
their public request-facing calls MUST NOT accept a verifier parameter. A
durable SQLite authority uses a separate connection exposing only exact-tuple
verification and atomic route claim, never quote reservation; the in-memory
test authority exposes the same limited operations under the store lock. This boundary prevents request
data from substituting a fresh attacker ledger while leaving deployment owners
free to choose their trusted composition root.
Before returning route evidence, the context MUST atomically claim one
transaction_id against the exact accepted four-field tuple. The first claim
wins, the same transaction is idempotent, and every different transaction for
that quote MUST fail. Route decoding and tracker initialization MUST verify the
exact claimed transaction. The supported reference API registers store-issued
authority instances through a closure-held issued-instance set and rejects
direct construction, nominal object construction, and structurally similar
fakes. This is API misuse hardening inside a trusted implementation, not a
sandbox boundary against arbitrary code already executing in the same
interpreter.
The reference capability is process-local and MUST carry an HMAC-SHA-256 seal
over its canonical accepted wire under an unpredictable key held only by the
acceptance closure. Every capability use MUST compare the seal in constant time,
reparse the sealed wire, recompute quote_hash and interaction_context, and
match the retained quote ID, nonce, hash, and context before returning authority.
This seal provides process-local tamper detection only; it is not
authorization. The durable acceptance-ledger reservation above is the
authorization source for untrusted wire/request inputs when accessed through
the deployment-bound context.
In-process trust boundary. AFP does not claim to sandbox arbitrary code that already executes inside the trusted composition-root process. Such code can introspect Python closures, monkey-patch implementations, or access storage credentials and therefore can subvert any in-process reference guard. A deployment MUST keep request data, tenant code, plugins, and other untrusted extensions outside that authority boundary. If same-process code itself is in the adversary model, the acceptance/route authority MUST move behind a separate process, KMS/HSM-backed signer, or equivalently isolated service boundary; the in-process reference objects are not sufficient for that threat model. The capability MUST NOT serialize across a process boundary. Restart does not clear the durable replay reservation or permit the same quote to execute again: normal acceptance requires a new quote, while any implementation-specific recovery path must verify a durable accepted record without weakening replay. Long-running receipt observation should persist its route/receipt state rather than persist this capability.
15.6 Payment receipt and finality
interface AfpPaymentReceiptV1 {
kind: 'afp:payment-receipt:v1';
quote_ref: string;
quote_hash: string;
interaction_context: string;
chain_id: number;
pool: string;
asset: string;
amount_wei: string;
transaction_id: string;
observed_at: string;
finality: 'pending' | 'safe' | 'finalized';
finality_evidence: object;
}A receipt is observational evidence, not settlement authority. quote_ref
MUST equal the accepted quote's quote_id; its quote hash, interaction context,
chain, pool, asset, and amount MUST exactly match the accepted quote and the
immutable route evidence. transaction_id
MUST be non-empty and MUST identify the submitted transaction. observed_at
uses the exact whole-second UTC format of §15.2. finality_evidence MUST be a
non-empty object appropriate to the deployment's documented finality policy;
unknown or empty evidence is insufficient.
Receipt tracking MUST be configured with a fail-closed, deployment-specific
finality-evidence verifier. For every observation, that verifier MUST validate
the evidence against the configured chain and immutable deployment, the exact
submitted transaction, and the claimed pending, safe, or finalized level.
Verifier absence, failure, an unknown evidence shape, or evidence for another
transaction, deployment, or level leaves the interaction unsettled. Retained
evidence MUST be recursively immutable; serialization returns a detached JSON
copy and cannot expose mutable nested aliases.
For one quote and transaction, receipt state MAY advance monotonically
pending → safe → finalized. It MUST NOT regress, skip to an unknown
state, change the transaction, or rewrite any quote/route binding. pending
MUST NOT be represented as final settlement. A deployment MAY require safe or
finalized for application completion; until that threshold is supported by
matching evidence, the AFP interaction remains unsettled.
Concurrent observations for one tracker MUST serialize the finality comparison
and state update atomically; a lower or equal-rank observation cannot overwrite
a higher state, regardless of verifier completion order.
15.7 Asset-risk and safeguard requirements
The flagship profile is WETH in one separately deployed immutable,
single-asset ZKA pool declaring zka:asset-risk:s3-hard:v1. Settlement
contracts MUST be single-asset and immutable. Every additional asset requires a
separate pool address, pool_context, note/nullifier/root state, escrow,
certification record, and explicit asset-risk profile. An application MUST NOT
treat a shared or dynamically mutable asset registry as equivalent. A native
stable unit is an independent future protocol and is not a dependency of AFP
pay, ZKA/Pay, or the Base WETH launch.
The profile inherits all six Freedom Safeguards (§12.8). In particular, expiry, dispute, rejection, replay failure, insufficient finality, or route failure affects only acceptance of this interaction. It MUST NOT freeze, revoke, redirect, delay, seize, or otherwise gate a holder's existing ZKA notes or unconditional withdrawal (Safeguards 3 and 5). AFP applications and APL adapters MUST NOT add an administrator recovery, override, clawback, or forced-reroute path. Any compliance proof requested alongside payment remains atomic and requester-authenticated with a holder-retained receipt (Safeguards 2 and 6).
15.8 Conformance
A conforming pay v1.0 implementation:
- registers exactly the descriptor and atom kinds in §15.1;
- accepts and emits only the canonical encodings and deterministic arithmetic of §15.2;
- derives the exact §15.3 hashes and the §15.4 fold — reproducing, byte-
identically, every vector of the published
docs/pay_interaction_context_kat_v1.jsonartifact and the inline §15.4 end-to-end vector — and requires two KEL-current signatures bound to the live session; - atomically prevents quote-ID and nonce replay without consuming replay state for a rejected quote;
- dispatches only the configured Base/WETH immutable single-asset pool under the hard-S3 risk profile and preserves the signed amount and context;
- matches receipt evidence exactly and enforces monotonic finality; and
- makes every failure interaction-local while preserving unconditional ZKA spend, withdrawal, and exit.
An implementation that obtains its own price, silently canonicalizes invalid
wire content, reprices after signature, permits one-party authorization, treats
pending as final, reuses a mutable/multi-asset settlement contract, or adds an
administrative note/withdrawal gate is not conforming.
Appendix A: Key Derivation Core (KDC)
Module: Key Derivation Core KDC Version: 1.0.2 Status: Stable Owning specification: AFP (this document) Referenced normatively by: ZKA v0.9.0-draft (§2.1–2.2), ZKC v0.4.1-draft (§1.3, §2.1, §5.9), ZKM v0.1.0-draft (§2), AFP v0.3.9 (§2.2, §4.3, §5.9)
This appendix specifies the Key Derivation Core (KDC) — the master-seed derivation, hash-and-domain-separation scheme, domain-tag registry, and per-entity sub-derivation shared by ZKA, ZKC, ZKM, and AFP. It is a self-contained module: it is published as part of the AFP specification for packaging reasons (AFP is the identity protocol of the family and carries the most complete derivation model), but it depends on no other section of this specification and on no construct specific to AFP, ZKA, ZKC, or ZKM. Sibling protocols reference this appendix without thereby depending on the remainder of AFP.
KDC carries its own version line, independent of the AFP specification version. A dependent specification cites AFP-KDC v1.0.2, not an AFP version. v1.0.1 resolved the domain-tag prose in favor of the already-deployed Blake2b512 reduction. v1.0.2 completely parameterizes the already-deployed Poseidon2 instance and makes its cross-repository vectors normative. Neither patch changes a derived value.
A.1 Scope
In scope (normative for KDC):
- the master seed (§A.2);
- the hash function and domain-separation scheme (§A.3);
- the KDC domain-tag registry (§A.4);
- the per-entity HKDF sub-derivation (§A.5);
- the key-derivation functions producing the spending, viewing, proof, and credential-binding keys, the address, and the credential commitment (§A.6).
Out of scope (owned by the referencing specifications):
- ZKA note structure, commitments, nullifier semantics, Merkle trees, and the
zka/nullifieroperational tag (ZKA §2); - ZKC credential structure, issuance, revocation, and accumulators (ZKC §2–§4);
- ZKM mandate structure, issuance, consumption, delegation, revocation, and its
mandate-specific
zkm/*operational tags (ZKM §2–§7); - AFP KERI machinery — AIDs, KELs, witnesses, delegated inception (AFP §4);
- key rotation and recovery mechanics, including on-chain re-binding of artifacts after a seed rotation (ZKA §5.7). KDC defines how keys are derived from a seed; it does not define how a rotation re-binds artifacts derived from a prior seed.
KDC defines derivation. It does not know what the derived keys are used for.
A.2 Master Seed
A master seed is a 256-bit (32-byte) secret. It is the root of a key hierarchy and MUST be generated from a cryptographically secure random source and stored securely.
A master seed may be:
- a standalone master seed, used directly as the input to §A.6 (e.g. a standalone ZKA deployment with no AFP entity hierarchy); or
- a per-entity sub-seed
M', derived from a tenant master seedMvia §A.5 and then used as the §A.6 input. Under AFP entity identity (AFP §4.3), the §A.6 input is always a per-entity sub-seed.
The two cases are interchangeable as far as §A.6 is concerned: §A.6 takes a 256-bit seed and does not distinguish how that seed was obtained. This is what makes a sub-seed M' a drop-in master seed for the key-derivation functions.
A.3 Hash Function and Domain Separation
KDC uses Poseidon2 over the BN254 scalar field for all key-derivation hashing. Poseidon2 is the hash provided by Noir's standard library (std::hash::poseidon2) and is optimized for UltraHonk constraints, so derivations are efficiently provable inside circuits — required because the ZKA identity-binding circuit (ZKA §5.2) re-derives keys from a seed in-circuit.
The BN254 scalar field modulus is
r = 21888242871839275222246405745257275088548364400416034343698204186575808495617
All KDC hashes use domain separation via a leading domain-tag field element:
H(domain, x...) = Poseidon2( deriveFieldTag(domain), x... )
deriveFieldTag is the one field-domain algorithm for both KDC derivation tags and protocol-owned operational tags:
deriveFieldTag(tag) =
LEInteger(Blake2b-512(UTF8("ZKA-Domain-") || UTF8(tag))) mod r
The 64-byte digest is interpreted as a little-endian non-negative integer before reduction. Implementations MUST NOT substitute direct UTF-8-to-integer encoding, big-endian digest interpretation, a different Blake2 variant, or an unprefixed hash. The historical ZKA-Domain- prefix is frozen compatibility material; it does not assign ownership of an operational tag to ZKA.
encodeFixedTag(tag) is a separate non-field operation. Its input domain is a non-empty string whose UTF-8 encoding is 1–32 bytes and whose first encoded byte is non-zero; implementations MUST reject a leading NUL byte so left-zero-padding remains injective. After validation, left-zero-pad the encoding to exactly 32 bytes. It is used for the §A.5 HKDF salt. A field tag MUST NOT be substituted as HKDF salt. A fixed byte tag MUST NOT be interpreted as a circuit field separator, with exactly one exception: the fixed-byte fold-tag class carved out in §A.4, whose externally-owned tags are consumed directly as field-element domain separators by their owning specification.
A 256-bit seed supplied as a hash input is interpreted as a big-endian integer and reduced modulo r before use as a field element.
Behavioral-identity requirement. §A.6 is behaviorally identical to the deployed ZKA/ZKC derivation and all v1.0.0 vectors. v1.0.1 removed the domain-tag prose/vector ambiguity; v1.0.2 pins the exact permutation and sponge already used by every consumer. Existing field constants, seeds→keys, proofs, verification keys, hashes, and HKDF vectors MUST remain byte-for-byte unchanged.
A.3.1 Poseidon2 Instance
KDC uses exactly one Poseidon2 instance. Any other parameterization is non-conformant even if it is also called “Poseidon2 over BN254.”
| Parameter | Normative value |
|---|---|
| Field | BN254 scalar field Fr with modulus r above |
| State width | t = 4 |
| Rate | 3, lanes 0–2 |
| Capacity | 1, lane 3 |
| S-box | x^5 |
| Full rounds | 8, split 4 before and 4 after the partial rounds |
| Partial rounds | 56 |
| External matrix | [[5,7,1,3],[4,6,1,1],[1,3,5,7],[1,1,4,6]] |
| Internal matrix | all-ones plus the pinned diagonal-minus-one values |
Treat the state as a column vector and reduce every operation modulo r. Apply the external matrix once before round 0. In rounds 0–3, add all four round constants, apply x^5 to all lanes, then apply the external matrix. In rounds 4–59, add the lane-0 round constant, apply x^5 only to lane 0, then apply the internal matrix. In rounds 60–63, repeat the full-round operation.
docs/kdc_v1_0_2_poseidon2_parameters.json is the normative, self-contained parameter artifact. It fixes all 64 four-lane constant rows, the four internal diagonal-minus-one values, both matrices, and operation order. Its canonical constant encoding concatenates every constant row in round/lane order, followed by the four internal diagonal-minus-one values, with each field encoded as canonical 32-byte big-endian. The encoding contains 260 fields and 8,320 bytes and MUST have SHA-256:
8cb18652f4afeac5b6819b156c13fbbc327deacba568b9d8d961ab134123214c
An implementation that imports, embeds, or loads an explicit parameter table MUST verify this digest before use. An opaque backend intrinsic MUST be immutably identified and reproduce every §A.3.3 KAT.
A.3.2 Sponge Mode
Let P(x[0], …, x[n-1]) be the raw length-bound sponge. Its input is a sequence of canonical Fr elements with 0 <= n <= 2^32 - 1. Initialize [0, 0, 0, n * 2^64]; the length IV is in capacity lane 3. For each complete three-element chunk, add it to rate lanes 0–2 and permute. Add any one- or two-element remainder to the corresponding leading rate lanes. Apply one final permutation if and only if n = 0 or n mod 3 != 0. Return lane 0. There is no delimiter or padding field, and a positive multiple of three receives no additional trailing permutation.
Absorption is additive. P accepts canonical field elements rather than arbitrary integers or bytes. KDC semantic domain separation is explicit and is not supplied merely by arity:
H(domain, x...) = P(deriveFieldTag(domain), x...)
A.3.3 Conformance Known-Answer Tests
For raw inputs consisting of the field elements 1, 2, 3, ... in order, a conforming implementation MUST reproduce:
n |
Input | P output |
|---|---|---|
| 0 | [] |
0x18dfb8dc9b82229cff974efefc8df78b1ce96d9d844236b496785c698bc6732e |
| 1 | [1] |
0x168758332d5b3e2d13be8048c8011b454590e06c44bce7f702f09103eef5a373 |
| 2 | [1,2] |
0x038682aa1cb5ae4e0a3f13da432a95c77c5c111f6f030faf9cad641ce1ed7383 |
| 3 | [1,2,3] |
0x23864adb160dddf590f1d3303683ebcb914f828e2635f6e85a32f0a1aecd3dd8 |
| 4 | [1,2,3,4] |
0x130bf204a32cac1f0ace56c78b731aa3809f06df2731ebcf6b3464a15788b1b9 |
| 6 | [1,2,3,4,5,6] |
0x07f57fcda925c06dc0a311f3f17fa0218e079b514552744a25ba8a74ee8c9e7a |
| 7 | [1,2,3,4,5,6,7] |
0x16f929bc0d216df4b05bdc44222463edf2b9791bd949ab926eebda06a502d238 |
n = 0, 3, 6 exercise the conditional trailing-permutation branch in both directions. n = 2, 3, 4 are the historical H2/H3/H4 raw wrappers and remain unchanged.
docs/kdc_v1_0_2_conformance_vectors.json is the normative machine-readable package. Its rawSponge section contains the table above. Its kdc section covers seed values 0, 1, r-1, r, r+1, the high bit, and an asymmetric 32-byte seed, and pins all six §A.6 outputs. The r/zero and r+1/one aliases are intentional consequences of the normative big-endian reduction.
Every conforming runtime MUST reproduce every shared vector applicable to its published interface. Solidity conformance is limited to the raw arities its published interface implements; it MUST NOT claim unsupported higher arities. Noir, Rust, ZKC, ZKM, and Aztec runners MUST execute every applicable vector rather than merely copying the expected values.
A.4 Domain-Tag Registry
The following are the complete KDC-owned derivation tags in v1.0.2:
| Domain tag | Used in | Purpose |
|---|---|---|
zka/spending |
§A.6 | Derive spending key from seed |
zka/viewing |
§A.6 | Derive viewing key from spending key |
zka/proof |
§A.6 | Derive proof key from spending key |
zka/address |
§A.6 | Derive address from viewing key |
zkc/credential |
§A.6 | Derive credential binding key from seed |
zkc/binding |
§A.6 | Derive credential commitment from credential binding key |
The zka/ and zkc/ tag prefixes are retained for continuity with the pre-KDC ZKA and ZKC specifications and with existing test vectors; the prefix is historical and does not imply ownership by ZKA or ZKC. All six tags are owned by KDC.
Ownership and collision rule. A tag string is owned by the specification that defines its operation, irrespective of its prefix. A referencing specification MAY define additional operational tags. Every such tag:
- MUST derive its field value with §A.3
deriveFieldTag, unless it is a fixed-byte fold tag under the carve-out below; - MUST NOT collide with any KDC derivation tag or any published operational tag in the ZK protocol family;
- is owned by the defining specification, not by KDC.
Fixed-byte fold-tag carve-out (ADR-0006). The ZKA zka/pay/* and
zka/coord/* tag families are raw fixed byte tags: each tag's UTF-8
bytes right-aligned in a 32-byte word and consumed directly as a field
element, never derived through deriveFieldTag. Like ZKC's frozen
aggregation literals below, they are deliberate compatibility constants
outside the deriveFieldTag regime and MUST NOT be reinterpreted through
it. They are owned by ZKA as the specification defining their folds. A new
tag in this class MUST NOT collide with any registered tag string, and
collision checking for the class is against ZKA's
protocol-family/poseidon2-operational-domains-v1.json (active_fixed_byte
rows) in addition to the 27-tag snapshot below. That registry holds the
class's members with one known gap: zka/pay/interaction-context/v1 is not
yet registered there — its normative definition lives in ZKA's pay
interaction-binding profile; registration is tracked as
req_zka_register_zkapayinteractioncontextv1__923e6c97, and AFP-side
vendoring of the registry with drift-gate coverage as
req_afp_vendor_zka_operationaldomains_regist_c22a7062. This carve-out
records an existing externally-owned boundary: no KDC-owned tag, derivation,
value, or vector changes, and the AFP-KDC version string is unchanged;
folding the clarification into the next coordinated AFP-KDC patch release is
tracked with the vendoring follow-up.
The machine-readable interoperability snapshot docs/kdc_v1_0_2_domain_registry.json pins all 27 currently tagged constants: six KDC derivation tags, four ZKA operational tags, eight ZKC operational tags, and nine ZKM operational tags. It is a KAT and collision registry, not a transfer of tag-string ownership to KDC. A new tag MUST be checked against the complete snapshot before publication. ZKC's frozen aggregation seed/verify literals are not string tags; they remain explicit compatibility constants outside this registry and MUST NOT be reinterpreted through deriveFieldTag. Replacing them with tagged constants requires a versioned circuit/VK migration. docs/kdc_v1_0_1_domain_registry.json remains published as the historical v1.0.1 artifact; every tag and field_hex value is byte-for-byte identical between the two registry versions.
ZKA, ZKC, and ZKM operational field tags — the deriveFieldTag class — all use the same §A.3 derivation; ZKA's pay/coord fixed-byte fold tags sit outside that class under the carve-out above. The v1.0.2 registry is the active 27-tag KAT and collision snapshot; the preserved v1.0.1 artifact records the version in which those values were introduced.
A.5 Per-Entity Sub-Derivation
A tenant with master seed M derives a distinct sub-seed M' for each entity:
M' = HKDF(M, "entity/{name}")
This sub-derivation is new in KDC v1.0.0: it originates in the AFP entity model (AFP §4.3) and has no pre-KDC ancestor, so KDC is its first and authoritative definition.
Concrete construction. The sub-derivation uses HKDF per RFC 5869 with SHA-256:
M' = HKDF-SHA256(
IKM = M, // 32-byte tenant master seed
salt = encodeFixedTag("kdc/entity-subseed"), // fixed 32-byte domain salt
info = utf8("entity/" || name), // entity name, UTF-8
L = 32 // output length in bytes
)
The output M' is a 32-byte value and is itself a valid §A.2 master seed: it is supplied directly to the §A.6 derivation functions. name is the entity's name as used in its AFP delegated-AID label (AFP §4.2).
Rationale — two primitives by design. KDC deliberately uses HKDF-SHA256 for the seed-tree derivation (
M → M') and Poseidon2/BN254 for the key derivation (M'/seed → keys, §A.6). The seed-tree derivation is never performed inside a zero-knowledge circuit — it is wallet- and key-management-layer work — so the well-audited RFC 5869 standard KDF is the appropriate, conservative choice. The key derivation of §A.6 is performed in-circuit (ZKA §5.2 re-derives keys from a seed as circuit constraints), so it must use the circuit-efficient Poseidon2. The boundary between the two primitives is exactly the boundary between non-circuit and circuit derivation. An implementation MUST NOT substitute one primitive for the other.
Implementation note.
HKDF-SHA256remains fixed and normative. v1.0.1 retains thedocs/kdc_v1_0_0_test_vectors.jsonsub-seed vectors unchanged;encodeFixedTag("kdc/entity-subseed")is exactly the byte encoding that those vectors already pin.
A.6 Key-Derivation Functions
Given a 256-bit seed (a standalone master seed per §A.2, or a per-entity sub-seed M' per §A.5), KDC derives the key hierarchy:
+-----------------+
| Seed (256b) | standalone master seed,
+--------+--------+ or per-entity M' (§A.5)
|
+-------------------+-------------------+
| | |
v v v
+--------------+ +--------------+ +--------------+
| Spending key | | Viewing key | | Proof key |
| (sk) | | (vk) | | (pk) |
+--------------+ +--------------+ +--------------+
| |
| v
| +--------------+
| | Address |
| | (addr) |
| +--------------+
v
+-----------------------+
| Credential binding |
| key (cbk) |
+-----------+-----------+
|
v
+-----------------------+
| Credential commitment |
| (holderCommitment) |
+-----------------------+
interface KeyHierarchy {
seed: Uint8Array; // 256 bits — store securely
spendingKey: Hash; // sk
viewingKey: Hash; // vk
proofKey: Hash; // pk
address: Hash; // addr
credentialBindingKey: Hash; // cbk
credentialCommitment: Hash; // public — holderCommitment in ZKC binding
}
// KDC v1.0.2 key derivation. All hashes: the exact §A.3 Poseidon2 sponge with
// domain separation per §A.3. Behaviorally identical to ZKA v0.6.0 §2.2.
function kdcDeriveKeys(seed: Uint8Array): KeyHierarchy {
const sk = H("zka/spending", seed); // from seed
const vk = H("zka/viewing", sk); // from sk
const pk = H("zka/proof", sk); // from sk
const addr = H("zka/address", vk); // from vk
const cbk = H("zkc/credential", seed); // from seed — NOT from sk
const cc = H("zkc/binding", cbk); // credential commitment
return { seed, spendingKey: sk, viewingKey: vk, proofKey: pk,
address: addr, credentialBindingKey: cbk,
credentialCommitment: cc };
}Two derivation facts are normative and load-bearing for the referencing specifications:
cbkderives from the seed, not fromsk. The credential binding key isH("zkc/credential", seed), a sibling of the spending key, not a descendant of it. This is what lets a holder prove credential ownership without exposing spending authority.- Address and credential commitment are derived along separate paths.
address = H("zka/address", H("zka/viewing", H("zka/spending", seed)))andcredentialCommitment = H("zkc/binding", H("zkc/credential", seed))share only the seed. Given one, the other cannot be computed or linked without the seed. ZKA's identity-binding circuit (ZKA §5.2) proves the link in zero knowledge precisely because the link is otherwise unrecoverable. A KDC implementation MUST preserve this separation.
The names of the derived keys are KDC's; how each key is used — sk to spend ZKA notes, vk to decrypt incoming notes, cbk to bind ZKC credentials — is specified by the referencing protocols, not here.
A.7 Conformance and Versioning
A conforming AFP-KDC v1.0.2 implementation:
- implements the exact Poseidon2 permutation, constants digest, raw sponge
P, tagged wrapperH, and all KATs of §A.3; - implements exactly the domain-tag registry of §A.4 for derivation, and honors the tag-collision rule;
- implements the HKDF-SHA256 sub-derivation of §A.5;
- implements
kdcDeriveKeysper §A.6, preserving the two normative derivation facts; - reproduces every raw and KDC case in
docs/kdc_v1_0_2_conformance_vectors.jsonexactly; - reproduces every
field_hexindocs/kdc_v1_0_2_domain_registry.jsonwithderiveFieldTagand rejects any cross-family collision.
Test vectors. docs/kdc_v1_0_0_test_vectors.json remains normative for HKDF entity sub-seeds. docs/kdc_v1_0_2_domain_registry.json is normative for field tags and the complete cross-protocol collision scan. docs/kdc_v1_0_2_poseidon2_parameters.json and docs/kdc_v1_0_2_conformance_vectors.json are normative for Poseidon2 and the complete KDC hierarchy. All four artifacts are required for v1.0.2 conformance; the v1.0.1 registry remains available as historical compatibility evidence.
Versioning. v1.0.2 is a patch because it completely specifies and tests the already-pinned implementation without altering a value. Any future change to §A.3–§A.6 that alters a derived field, seed, key, proof, VK, or hash is a major KDC version bump and requires an explicit compatibility/migration plan across every consumer. A referencing specification cites a specific AFP-KDC version and may assume its derived values are immutable.
Document History
| Version | Date | Changes |
|---|---|---|
| 0.1.0 | 2025-11 | Initial AFP draft (open-marketplace federation) |
| 0.2.0 | 2025-12 | Marketplace federation: third-party agent registration, capability discovery, task delegation, observation, reputation, trust ladder, HTTP bridge |
| 0.3.0 | 2026-05-16 | Scope narrowed to instance-level federation (three relationships). Marketplace surface removed. KERI-aligned identity adopted (§4). ZKA / ZKC reframed as substrate (§5, §6). Operator-blindness elevated to a structural invariant covering both content and the coordination graph (§3). Telemetry redesigned with no dependence on transport observation (§6). Metadata-privacy routing specified (§8): the AFP Federation Mixnet — a self-contained, consortium-operated Loopix-family mix network with mandatory cover traffic, KERI-witnessed mix-node directory, and private route resolution — adopted as the concrete OB-2 mechanism against a global passive link observer; Katzenpost named as preliminary reference implementation. Key Derivation Core (KDC) v1.0.0 incorporated as Appendix A — the shared master-seed derivation consolidated from ZKA §2.2 and ZKC §2.1 into a self-contained, independently versioned module. |
| 0.3.1-draft | 2026-06-05 | Bilateral Session Core consolidated into AFP (§12), Profile concept defined (§13), per ADR-0003. Lifted the session substrate that PRP v0.2 and VXP v0.2 each independently re-derived — bilateral session establishment / lifecycle / timeout (§12.2), session identity binding (§12.3), the afp.{tenant}.{profile}.* NATS namespace (§12.4), the bilateral dispute / optional-pre-agreed-arbiter model (§12.5), the bilateral commitment + optional Ethereum-L2 anchor primitive (§12.6), the session-envelope-over-ZKA-bundle wire format (§12.7), and the Freedom-Safeguards application at the session layer (§12.8) — into one AFP-owned section group. Defined the profile mechanism and lightweight registration (§13); named work (base, §5), prp, and vxp as the v0.2 profiles. Resolved two profile-flagged seams: VXP's vxp.* namespace is a subtree under AFP (§12.4); the CoordBundle home is ZKA-owns-bundle + AFP-owns-envelope, replacing vxp:coordbundle:v1 (§12.7). Companion PROFILE_MIGRATION_NOTE.md maps the superseded PRP/VXP sections. No change to §1–§9 normative content beyond pointer notes; PRP/VXP specs not edited. |
| 0.3.2-draft | 2026-06-10 | Substrate context refreshed; witness bootstrap, rotation sequencing, public-sample consistency, telemetry predicate catalog, KDC vectors, mixnet implementation parameters, and DGP gateway boundary specified. AFP now targets ZKA v0.6.2 and ZKC v0.2.4. ZKA v0.6.2 resolves the §12.7 ZKA-side bundle dependency by adding optional coord_proof carriage to zka:bundle:v1 and permitting pure work-attestation bundles. ZKC v0.2.4 adds the ZKC-Core credential rebind circuit (zkc:proof:credential_rebind:v1), narrowing AFP's remaining rotation seam to ZKA note migration; issuer migration tokens may optimize integrations but do not replace the proof, and KDC derivation values are unchanged. §1.4.1 defines the non-AFP boundary for tenant-exposed DGP gateways: no AFP discovery feed, no coordination-traffic visibility, and no trust-boundary expansion. §4.5 defines afp:witness-bootstrap:v1; §4.6 defines afp:rotation-presentation:v1; §6.3 defines AFP operator telemetry predicate IDs and verification shape; §6.4 defines afp:telemetry-sample-binding:v1; §8 defines afp.mixnet.profile.ob2-baseline.v1 and afp:mixnet-directory:v1; §10 tracks operator↔︎operator federation as deferred future multi-operator ADAMAS scope without making it AFP v0.3.2 conformance. docs/kdc_v1_0_0_test_vectors.json publishes HKDF entity sub-seed vectors and records unchanged ZKA seed-to-key vector verification. §10 and PROFILE_MIGRATION_NOTE.md are updated so resolved dependencies are not carried as open work. |
| 0.3.3-draft | 2026-06-15 | Co-publication harmonization + safeguard-count correction. Retargeted to ZKA v0.7 and ZKC v0.3.0 (Abstract, §1.4 diagram, Appendix A KDC consumer list). Corrected the inherited Freedom Safeguard count from five to six (Abstract, §1.1, §1.5, §9, §12.8) — ZKA v0.7 §1.6 / ZKC v0.3.0 §1.4 add Safeguard 6 (Verifier Accountability), load-bearing for the unknown-counterparty direction (ADR-0004). No session/envelope/routing/identity/telemetry/profile mechanism change. The full inline ZKA/ZKC citation sweep and the open-federation trust-model change land with the ADR-0004 revision. |
| 0.3.4-draft | 2026-06-16 | Open-federation trust basis (ADR-0004, ratified). Generalized AFP's trust basis from "a bilateral federation agreement is the sole precondition for a session" to either a §5.5 agreement (known counterparty) or per-interaction ZKC compliance attestation (open federation, new §5.8) — unlocking ADAMAS-to-ADAMAS transactions between unknown counterparties with no new protocol and no weakening of operator-blindness. Revised: Abstract; §1.2 (trust-ladder row), §1.3 (relationship 1 admits unknown counterparties); §5.1 (two trust bases), §5.2 (open-federation session context), §5.5 (agreement is one of two bases), §5.8 (the attestation path — authenticated proof request, holder-retained Disclosure Receipt, own-policy acceptance, Safeguard-6 accountability, interaction-boundary-only); §8.8 (route resolution reconciled — trust generalized, discovery/rendezvous stays out); §10 (new stranger-rendezvous-under-OB-2 open item + generalization note); §12.1/§12.3 (session trust-anchored on agreement or attestation; identity binding admits attested AIDs). Supporting open federation is OPTIONAL; when supported, §5.8's requirements are normative. All six Freedom Safeguards inherited identically by both paths; ZKA settlement and ZKC credential semantics unchanged. Also corrected the §1.4 table version labels left at ZKA v0.6.2 / ZKC v0.2.4 after the v0.3.3 bump to ZKA v0.7 / ZKC v0.3.0. Reconciled with the parallel §14 Introduction Tier: §12.1 now enumerates three trust bases (pre-existing agreement / §14 provisional agreement / §5.8 attestation-no-agreement) and §14.2's "sole … without a pre-existing agreement" claim is scoped to provisional-agreement minting so it no longer contradicts §5.8. |
| 0.3.5-draft | 2026-07-02 | Canonical filename established as docs/afp_specification.md. Clarified that the ZKA-Aztec v5/AZUP-2 migration is complete in ZKA.dev and sandbox-accepted, while AFP profile anchoring still inherits the Ethereum-L2-via-APL conformance baseline unless a deployment explicitly wires Aztec as an APL rail. No protocol semantics changed. |
| 0.3.6-draft | 2026-07-11 | Experimental AFP pay v1.0 profile added (§15). Defines a bilaterally authenticated USD-minor-unit obligation and exact WETH-wei settlement quote bound by a domain-separated interaction context to the immutable Base ZKA-native WETH pool; fixes canonical integer, lowercase-address, 32-byte nonce, whole-second UTC timestamp, deterministic ceiling-rate, replay, receipt, and monotonic finality rules. Keeps AFP pricing/orchestration, ZKA settlement/proofs, and APL route/finality ownership separate; requires hard Safeguard 3, single-asset immutable settlement pools, and per-asset risk profiles; and records that a native stable unit is not a prerequisite. Corrects stale §12.8/§14.8 safeguard mappings to all six safeguards. KDC v1.0.0 and existing session/envelope/bundle versions are unchanged. |
| 0.3.6-draft (ZKM alignment) | 2026-08-04 | Adds ZKM as an optional authorization composition peer and KDC seed-model consumer; states the independent AFP §14 bilateral-policy versus ZKM unilateral-mandate checks; and discloses the unresolved KDC encodeTag versus deployed operational-separator conflict under its tracked reconciliation requirement. No AFP envelope, session, profile, KDC constant, or test vector changes. |
| 0.3.7-draft | 2026-08-04 | ZKC v0.4 presentation profiles + AFP-KDC v1.0.1. Adds §5.9's per-flow anonymous/scoped/linkable policy, AID/credential-subject/APR/session binding, open-federation proof-borrowing rejection, consent-gated linkability, profile-minimized receipts/rotation artifacts, and explicit zka:bundle:v1 carriage. Updates telemetry, bilateral, introduction, open federation, rotation, and conformance language. Reconciles field-domain tags with the deployed Blake2b512 little-endian reduction, separates fixed byte tags, and publishes the 27-tag KDC/ZKA/ZKC/ZKM KAT/collision registry without changing any existing constant, proof, VK, hash, or vector. |
| 0.3.8-draft | 2026-08-08 | Vendor-neutral instance attestation. Freezes afp.telemetry.instance_attestation.v1 unchanged and fixes the non-Draft v2 public statement, witness, circuit, cadence, failures, and verifier obligations for build-provenance transparency logs, TEE measurement-to-manifest binding, and publisher-manifest self-attestation. Requires §6.4 public-sample binding plus §3.5 disclosure for publisher-manifest roots and moves §14 counterparty use to v2. No ZKC credential-schema or issuer-adapter contract is redefined. |
| 0.3.9-draft | 2026-08-14 | AFP-KDC v1.0.2 complete Poseidon2 conformance. Publishes the exact width-4 BN254 parameter table and digest, length-bound sponge, raw edge KATs, boundary/asymmetric full-hierarchy vectors, standard Noir/Rust/ZKC/ZKM/Aztec/Solidity execution gates, and scheduled cross-repository copied-code and artifact drift checks. No derived value or existing cryptographic artifact changes. |
| 0.3.9-draft (ADR-0005 tenant definition) | 2026-08-15 | Platform-agnostic tenant definition (ADR-0005, ratified). A tenant is now a runtime that federates as a principal under its own tenant root AID and MUST satisfy the tenant conformance profile, rather than "an ADAMAS instance"; ADAMAS becomes the reference implementation. Tenanthood stays structural so that "Every tenant MUST …" remains enforceable against a shortfall (§3.5, §8.9). New §1.5.1 consolidates the tenant conformance profile — KERI identity custody (§4), operator blindness and topology (§3, §7.1, §7.3), routing participation incl. mandatory cover traffic (§8), the bilateral session core (§12), operator telemetry (§6), the six Freedom Safeguards (§9), and the tenant-internal obligations (§5.6, §5.7) — introducing no requirement not already normative elsewhere. Boundaries previously labeled "ADAMAS-internal" are tenant-internal, with §5.7's implicitly delegated containment/capability-scoping/observability duty restated as a normative tenant obligation. The single trust level reads verified conforming instance (§1.2, §12.1, §12.5, §13.1). §7.1 generalized from "Source-available ADAMAS" to "Auditable implementation": OB-relevant behavior MUST be auditable, an implementation that cannot support inspection MUST NOT claim OB and falls under §3.5. §1.2 restates every retirement as a guard against reading the generalization as reopening registration, discovery, reputation, the trust ladder, or the HTTP bridge. §10 adds the deferred multi-vendor conformance program open item (gated on a second serious implementer) and the ADR-0005 generalization note. Naming Pyramidal Inc. as the attestation root authority is tracked separately and is not in this revision. No wire change: no new or renamed message class, predicate ID, public input, or envelope revision; afp.telemetry.instance_attestation.v1 remains frozen. The interface version is unchanged at 0.3.9-draft because no wire surface moved; the numbered 0.3.10-draft release is a coordinated protocol-family action tracked separately. |
| 0.3.9-draft (ADR-0005 authority naming) | 2026-08-16 | Attestation authority named; the authority set stated open (ADR-0005 item 4). Closes the half of ADR-0005 the 2026-08-15 revision deferred. The single trust level is qualified to verified conforming instance under a verifier-accepted attestation authority — stated canonically in §1.2 and cross-referenced from §12.1, §12.5, and §13.1; §13.1 deliberately keeps the unqualified phrase per ADR-0005's PRP/VXP out-of-scope clause. §1.5.1 is extended so the qualified phrase cannot be read as making instance attestation a precondition of a session; it is not. §6.3.1 fixes an organizational authority as the accountable party — its organizational root AID, never a product, brand, or build line — while leaving the root:tee-attestation and root:build-provenance identifier meanings (hardware vendor product root policy; transparency-log checkpoint signer) exactly as their branches already fix them. It permits KEL-anchored, product-scoped delegated signing on the §4.2 tenant→entity shape, ties root_material_digest to the delegated signing-key state, routes rotation/compromise/succession through the signing AID's KEL anchored by seals in the authority's KEL, and assigns delegation resolution and root_material_digest recomputation to the verifier, out of circuit, before acceptance, mirroring the existing delegated-verifier-AID rule — so no circuit, branch obligation, or verification key changes, and the attest-own-builds-only property is stated as resting on that recomputation rather than on the circuit alone. The previously unimplementable "stale or revoked authority" check is made dischargeable, for a KERI-rooted authority, from its own KEL and the KELs it anchors (§4.2, §4.4, §4.6) — with the transparency-log and hardware classes retaining their verifier-pinned log-policy and vendor-root material, and authority-KEL events checked against a witness set and threshold pinned in the accepted trust material (§4.5). No authority-published registry or feed is on the normative path in any class; authority failure routes to decline-or-alert and nothing more, on every counterparty-facing path (§5.4/§5.5, §5.8, §14.3) and in telemetry (§6.6) (Safeguards 3, 5). root:publisher-manifest gains an explicit attest-own-builds-only scope and a split disclosure duty — the deployment's §3.5 duty unchanged, and the authority's half stated as a verifier acceptance condition so no MUST lands on a party that is neither a principal nor a conformance subject — with class rules otherwise unchanged and the §6.4 binding staying tenant-signed. The operational-status disclosure is lifted into the normative body and stated path-independently. The authority set is open: extending the root-class taxonomy requires a revision to §6.3.1, standing up a new authority does not — no registration, permission, notification, grant, or amendment, and no AFP authority registry (§1.2, §5.5, §5.8, §6.3.1, §9, §10, §12.8). An authority is not a principal (§2.1, which remains closed at three) and certifies no conformance (§1.5.1). Pyramidal Inc. is named non-normatively as the first authority, with a delegated ADAMAS publisher AID, no AID value asserted, and the authority not yet operational (§6.3.1, §14.9); its dual role as operator and authority is stated as independent (§8.6). A shipped default verification policy is implementation-side product configuration — visible, editable, removable, never hardcoded beyond removal, conferring no protocol privilege — stated normatively in §6.3.1 and cross-referenced from §9, §12.8, and §14.4, and this specification defines no default authority. §10 adds the transparency-log upgrade path and the out-of-band authority-binding item. One bounded encoding amendment: afp:publisher-key-state:v1 names the JCS record whose digest root_material_digest already carried for this class, fixing an underdetermined preimage of an existing public input — with KEL SAIDs, keys, and pre-rotation commitments carried verbatim rather than re-encoded under the sha256: *_digest rule, and kt/nt typed to admit KERI's weighted-threshold form; it adds no public input and no wire field. No wire change: no new or renamed message class, predicate ID, public input, envelope revision, or NATS subject; AfpInstanceAttestationPublicInputsV2 and AfpIntroductionPolicy.accepted_instance_attestation_roots are byte-identical. afp.telemetry.instance_attestation.v1 remains frozen textually and semantically — implementations MUST NOT map its publisher AID onto the organizational-root/delegated-publisher construction. The interface version is unchanged at 0.3.9-draft. |
| 0.3.9-draft (ADR-0006 interaction-context fold) | 2026-08-16 | Payment interaction context is the ZKA Poseidon2 fold (ADR-0006, ratified). §15.4 rewritten: interaction_context is derived by the frozen ZKA/Pay Interaction-Context Binding Profile v1 field-count-prefixed Poseidon2 fold under the fixed-byte tag zka/pay/interaction-context/v1, restated self-containedly and pinned to ZKA.dev db39afd (fixture SHA-256 committed; an upstream v2 binds AFP only via a superseding ADR). The SHA-256/canonical-JSON derivation, its afp:payment-context:v1 kind, its published vector, and the 1 + (x mod (r-1)) mapping are retired (no external consumer recomputed them). Fold inputs are the 32-byte digest payloads of quote_hash and the session context — code-agnostic qb64 extraction, plain big-endian mod-r reduction, zero rejected fail-closed at quote-construction time; the wire stays unreduced and recomputing parties MUST reduce first; the pay profile requires the session context be a qb64-encoded 32-byte digest (base-spec encoding: new §10 open item). Conformance re-anchored on the five vendored upstream KATs plus an AFP-native end-to-end vector whose two digests both exceed r. §15.5 item 6 gains the terminal zero-reduction condition; §15.8 wording follows. Appendix A reconciled: new §A.4 fixed-byte fold-tag carve-out (mirroring the ZKC-literals exemption), §A.3 prohibition scoped to KDC key derivation, §2.2 note and the §A.4 blanket same-derivation claim corrected; the tag is registered in no family registry today — ZKA-side registration, protocol-family vendoring/drift-gate, and the APL verification leg are tracked ADR-0006 follow-ups. No wire change: no new or renamed message class, quote field, public input, or envelope revision; the merged downstream carriers stay wire-compatible — only the opaquely carried derived value changes. The interface version is unchanged at 0.3.9-draft. |
License
This specification is released under the Apache 2.0 License.
AFP: Verifiable federation between tenants — with nothing to trust.