← Back to Home

Zero-Knowledge Agents

Privacy-preserving protocols for autonomous AI agent interactions

Version 0.9.0-draft | Status: Public Specification | Domain: ZKA.dev | Noir / Barretenberg / BN254

Abstract

Zero-Knowledge Agents (ZKA) is a protocol family enabling privacy-preserving interactions between autonomous agents. ZKA defines cryptographic primitives for private payments, verifiable coordination, and anonymous attestation.

This document specifies the protocol layer: circuit designs, proof formats, nullifier semantics, settlement interfaces, and — new in v0.6.0 — the normative ZKA/ZKC integration: how zero-knowledge compliance credentials bind to ZKA notes without linking identities. Implementation details are left to conforming systems.

ZKA targets provable confidentiality, not unconditional anonymity: a holder can prove specific properties (compliance predicates, balance thresholds, identity binding) to a chosen counterparty while revealing nothing else. The design treats compliance as an interaction-boundary concern, never a custody-boundary one — the architectural commitments that enforce this distinction are specified normatively in §1.6 (Freedom Safeguards as Protocol Invariants).

Version 0.9.0-draft Changes:

Version 0.8.1-draft Changes:

Version 0.8.0-draft Changes:

Version 0.7.1-draft Changes:

Version 0.7.0 Changes:

Version 0.6.4-draft Changes:

Version 0.6.2-draft Changes:

Version 0.6.1 Changes:

Version 0.6.0 Changes:


1. Overview

1.1 Design Goals

  1. Payment Privacy: Transfer amounts and party identities are hidden from observers
  2. Double-Spend Prevention: Nullifier-based consumption tracking prevents replay
  3. Verifiable Coordination: Recursive proofs attest to multi-agent workflow execution
  4. Settlement Agnosticism: Protocol works with any settlement layer (L2, L1, centralized)
  5. Selective Disclosure: Viewing keys and predicate proofs enable audits without compromising privacy
  6. Provable Confidentiality: A holder proves chosen properties to a chosen counterparty; non-compliance reduces counterparty access, never fund access
  7. Compliance Compatibility: Normative integration with ZKC for user-controlled, pluralistic compliance
  8. Multi-Chain Deployment: Aztec native, standalone EVM, and hybrid deployment models
  9. Freedom by Default: The protocol invariants of §1.6 hold regardless of proving backend, settlement layer, or deployment context

1.2 Protocol Family

Protocol Purpose Status
ZKA/Pay Privacy-preserving payments Specified
ZKA/Coord Recursive coordination proofs Specified
ZKA/Attest Anonymous credentials / ZKC binding Specified (§5)
ZKA/Reputation Privacy-preserving reputation Draft
ZKA/Compute Verifiable private computation Future
Protocol Relationship
ZKC User-controlled compliance layer; shares the key hierarchy with ZKA; integration is normative as of §5
ZKM Principal-issued spend-authorization layer. ZKM reuses ZKA note, nullifier, and settlement primitives in its spend-conjoined statement, but remains optional: ordinary ZKA transfers do not require a mandate. ZKA alone owns note validity, custody, settlement, and unconditional exit; revoking or rejecting a ZKM mandate MUST NOT invalidate, freeze, redirect, or seize a ZKA note.
AFP Agent Federation Protocol; uses ZKA/Coord for cross-org workflow attestation. AFP also owns the Key Derivation Core (AFP-KDC v1.0.2, AFP Appendix A) that this specification references for its key hierarchy (§2.1–§2.2). The dependency is acyclic: ZKA depends only on AFP Appendix A, a self-contained module that depends on nothing else in AFP.

1.4 Circuit Implementation

ZKA circuits are specified in Noir (ACIR IR) and proven using the Barretenberg backend (UltraHonk proving system) over the BN254 scalar field. This enables:

Retired target (2026-06-12). Earlier drafts named Leo (zka.leo / zkc.leo, Aleo deployment) as a second conforming implementation target with invariant-level (not value- or proof-level) conformance. The Aleo-native execution path was retired per ADR-0009 (no Aleo value flow expected); the design documents are archived under docs/archive/ (sdd_leo_pay.md, sdd_bridge_aleo.md, sdd_coord_aleo.md). The protocol invariants of §1.6 remain implementation-independent and MUST hold for any future conforming target.

1.5 Notation

Symbol Meaning
H_n(x...) Canonical Poseidon2 sponge hash of exactly n BN254 field elements; the message length is bound into the sponge IV
H(domain, x...) H_n(deriveFieldTag(domain), x...), where n includes the leading domain field and deriveFieldTag is AFP-KDC v1.0.2 §A.3
commit(asset, v, recipient, r) H_2(H_3(asset, v, recipient), r) note commitment
nullifier(n, k) Nullifier derived from note n and key k
π UltraHonk proof (Barretenberg)
vk Verification key
cbk Credential binding key (for ZKC integration)
holderCommitment H("zkc/binding", cbk) — public ZKC identity anchor
predicate An open-source compliance circuit verifying one property

1.6 Freedom Safeguards as Protocol Invariants

These six invariants are normative. A conforming implementation MUST satisfy all six; they are not optional features but the architectural commitments that distinguish privacy infrastructure from a compliance control mechanism. Safeguards 3 and 5 are core ZKA invariants; Safeguards 1, 2, and 4 are integration-level invariants binding any ZKC predicate or bundle used with ZKA (§5); Safeguard 6 (added in v0.7.0) is a verifier-accountability invariant that constrains the counterparty — the first safeguard that does — and is orthogonal to the operator-blindness of Safeguards 3 and 5.

Safeguard 1 — Predicate Pluralism. ZKA/ZKC MUST support multiple competing predicate providers simultaneously. No predicate provider may be hardcoded, privileged, or treated as canonical by the protocol, and the protocol MUST NOT include a mechanism for designating an "official" or "default" predicate set. Predicate providers are independent, permissionless services.

Safeguard 2 — Minimal Disclosure by Default. Predicate circuits MUST be designed for atomic, single-predicate proofs (e.g., "not sanctioned"), never bundled composites. Composite proofs MUST require explicit, separate consent per predicate. SDK and tooling defaults MUST generate minimal proofs; broader disclosure MUST require explicit additional configuration. Proof requests MUST be principal-readable, enumerating exactly what is proved.

Safeguard 3 — No Revocability (core ZKA invariant). ZKA note validity MUST depend solely on cryptographic properties — valid nullifier, valid commitment, valid Merkle path — never on an external authorization check. No admin, governance, emergency, or regulatory key may exist that can invalidate, freeze, redirect, or force-transfer a note. Smart-contract implementations MUST NOT include pausable, upgradeable, or admin-controlled functions that could affect note validity. The only consequence of non-compliance is counterparty refusal, never fund inaccessibility.

Safeguard 4 — Open-Source Predicate Verification. All predicate circuits MUST be open-source and publicly auditable, expressed in inspectable Noir code. If an agent cannot inspect the predicate it is asked to satisfy, the SDK MUST refuse to generate the proof by default. Predicate updates MUST be versioned, published, and subject to a public review period before activation.

Safeguard 5 — Agent Exit Rights (core ZKA invariant). Unshielding/withdrawal from ZKA to a base layer (e.g. Ethereum) MUST be unconditional — no compliance proof may gate exit. The protocol MUST distinguish custody operations (always permissionless) from interaction operations (may require compliance proofs for specific counterparties). This aligns with and depends on the CLARITY Act self-custody protection: a federal agency may not impair the ability of a covered user to self-custody digital assets using a self-hosted wallet.

Safeguard 6 — Verifier Accountability. A verifier that requests a disclosure MUST authenticate the request under a stable verifier identity (an Authenticated Proof Request), and the holder MUST be able to retain a self-contained receipt of exactly what was demanded, for what declared purpose, and what was answered. A conforming SDK MUST refuse by default to answer an unauthenticated request, MUST bind declared purpose into the proof context, and MUST NOT require any operator or service to custody receipts. This safeguard governs the request; it makes no claim over the verifier's decision, which the protocol cannot reach (request accountability is reachable — no signature ⇒ no proof — but a verifier may decline in silence, so decision accountability is structurally unreachable). Safeguard 6 is the first invariant to constrain the counterparty rather than the operator; it is orthogonal to operator-blindness and reinforces Safeguard 2 (the signature is over the itemized request) while complementing Safeguard 5 (exit rights say you may leave; the receipt says why you were pushed). The mechanism is specified normatively in ZKC §2.2, §5.5–§5.8 and is enforced for any ZKC predicate or bundle used with ZKA.

A conforming ZKA deployment MUST NOT require an operator, proxy, indexer, prover, predicate provider, compliance service, or settlement administrator to receive masterSeed, spending keys, note plaintext, or custody of Disclosure Receipts in order for an agent to hold, receive, spend, or withdraw ZKA notes. Remote services MAY assist with indexing, verification, proving, or signing only when they receive public inputs, encrypted material, viewing-limited material, or delegated authority that is insufficient for unilateral spending.

# Invariant Audit Check
1 Predicate Pluralism No hardcoded predicate-provider addresses or privileged roles in contract code
2 Minimal Disclosure SDK defaults emit single-predicate proofs; composites require explicit opt-in
3 No Revocability No admin/pausable/force-transfer paths in note-management contracts
4 Open-Source Predicates Predicate inspection mandatory before proof generation in SDK flow
5 Exit Rights Unshield/withdraw functions contain no compliance-gating conditional logic; operators and services cannot require custody of masterSeed, spending keys, or note plaintext
6 Verifier Accountability SDK refuses unauthenticated requests by default; declared purpose is a bound public input under the purpose-bound proof context; receipts are holder-custodied and operator retention is absent

2. Cryptographic Primitives

2.1 Hash Function

ZKA uses Poseidon2 over the BN254 scalar field for all circuit-facing hash operations. The canonical implementation is noir-lang/poseidon v0.3.0's Poseidon2::hash, backed by Noir/Aztec/Barretenberg's Poseidon2 permutation and optimised for UltraHonk constraints. Its state width is 4: three rate lanes and one capacity lane. H_2, H_3, and H_4 below name the number of message fields supplied to this same width-4 sponge; they do not select width-2, width-3, or width-4 permutations.

For a message of n field elements, the canonical sponge places n * 2^64 in the capacity lane, absorbs up to three message fields per permutation, and returns state lane zero. The length-bearing IV makes the message arity part of the hash definition; for example, H_1(x) != H_2(x, 0).

Domain separation is construction-specific. A tagged construction prepends

deriveFieldTag(tag) = LEInteger(Blake2b-512("ZKA-Domain-" || UTF8(tag))) mod r

as specified by AFP-KDC v1.0.2 §A.3. Implementations MUST use the pinned field constants and MUST NOT substitute direct UTF-8-to-field encoding, big-endian digest interpretation, another Blake2 variant, or an unprefixed digest. Untagged constructions are explicitly identified below; the mere use of Poseidon2 does not imply a leading domain field.

Key-derivation hashing — the derivation of the spending, viewing, proof, and credential-binding keys, the address, and the credential commitment, and the domain tags zka/spending, zka/viewing, zka/proof, zka/address, zkc/credential, zkc/binding — is defined by AFP-KDC v1.0.2 (AFP Appendix A §A.4, §A.6) and is not restated here. See §2.2.

Operational (non-derivation) hashing remains defined by this specification. ZKA defines one operational domain tag:

H_2(H_2(DOMAIN_ZKA_NULLIFIER, commitment), sk)  → nullifier

DOMAIN_ZKA_NULLIFIER = deriveFieldTag("zka/nullifier"). Per the AFP-KDC tag-collision rule (§A.4), it does not collide with a registered KDC or sibling-protocol tag and remains owned by ZKA. The two-layer note commitment and binary Merkle parent in §2.3–§2.4 are deliberately untagged, fixed-arity constructions pinned by the protocol vectors.

Migration note: v0.4.0 used Poseidon over the Pallas scalar field; v0.5.0 onward uses Poseidon2 over BN254. Domain-tag strings are unchanged across all versions; only the field and hash variant differ. v0.6.1 introduces no field, hash, or derivation change relative to v0.5.0/v0.6.0 — it only relocates the derivation definition to KDC.

2.2 Key Hierarchy

ZKA key derivation is defined by the Key Derivation Core (AFP-KDC v1.0.2), specified in Appendix A of the AFP Specification. ZKA does not restate the derivation; a conforming ZKA implementation MUST derive keys per AFP-KDC v1.0.2. The 32-byte seed is interpreted as an unsigned big-endian integer reduced modulo BN254.Fr before any derivation hash.

KDC takes a 256-bit master seed and derives the spending key (sk), viewing key (vk), proof key (pk), address (addr), credential binding key (cbk), and credential commitment (holderCommitment). Two KDC derivation facts are load-bearing for this specification (KDC §A.6):

Seed provenance. The 256-bit seed supplied to KDC is, in a standalone ZKA deployment, a master seed generated directly. Under AFP entity identity (AFP §4.3), it is a per-entity sub-seed M' derived from the tenant master seed via KDC §A.5. KDC key derivation does not distinguish the two cases; ZKA circuits that take a masterSeed private input (e.g. §5.2) accept whichever the deployment uses.

Note: the KeyHierarchy interface and deriveKeys function previously shown here are now KDC §A.6. The ASCII key-hierarchy diagram is now KDC §A.6. The unlinkability paragraph is now KDC §A.6, fact 2.

Aztec mapping (for Aztec native deployment, §8):

ZKA Key Aztec Equivalent
masterSeed App-siloed nullifier secret nsk_app
spendingKey H("zka/spending", nsk_app)
viewingKey H("zka/viewing", spendingKey)
credentialBindingKey H("zkc/credential", nsk_app) — sibling of spendingKey

2.3 Notes

interface Note {
  asset: AssetDescriptor;    // Multi-asset support
  value: u64;                // Hidden in proofs
  recipient: ZKAAddress;
  randomness: Scalar;        // BN254 scalar field element
  commitment: Commitment;    // H_2(H_3(asset_commitment, value, recipient), randomness)
  nullifier: Nullifier;      // Revealed when spent
  memo: Uint8Array;          // Up to 512 bytes, encrypted
}

interface AssetDescriptor {
  settlement: SettlementType;
  assetId: AssetId;
  decimals: u8;
  metadata?: { symbol: string; name: string };
}

type SettlementType = "ethereum" | "namada" | "penumbra" | "aztec" | "native";

type AssetId =
  | { type: "ethereum",  address: Address }
  | { type: "namada",    denom: string }
  | { type: "penumbra",  assetId: Hash }
  | { type: "aztec",     assetId: Hash }
  | { type: "native",    id: Hash };

function computeCommitment(note: Note): Commitment {
  const assetCommitment = H_2(DOMAIN_ZKA_ASSET, canonicalAssetIdField(note.asset));
  const inner = H_3(assetCommitment, field(note.value), note.recipient);
  return H_2(inner, note.randomness);
}

function computeNullifier(note: Note, sk: SpendingKey): Nullifier {
  return H_2(H_2(DOMAIN_ZKA_NULLIFIER, note.commitment), sk);
}

DOMAIN_ZKA_ASSET = deriveFieldTag("zka/asset"). The settlement-specific canonical asset-id projection is defined by the applicable deployment or bridge profile. The inner and outer note-commitment hashes are untagged; their distinct fixed arities and nesting are normative.

2.3.1 Note randomness generation

For every active output note, the holder implementation MUST generate randomness internally from an operating-system cryptographically secure random number generator. Sampling MUST cover the complete BN254 scalar field by rejection sampling canonical 32-byte representatives; modulo reduction of a narrow, biased, deterministic, user-supplied, or protocol-derived input is not conforming. Zero is never a valid active-output randomness value. Inactive fixed-width output slots continue to use zero.

A production SDK, wallet, bridge adapter, or rotation path MUST NOT accept caller-supplied note randomness. It MUST reject any reuse it can detect before proof construction, including reuse from an input note and repetition between active outputs in one transaction. A holder implementation MUST retain a bounded non-reversible in-process fingerprint window for values it generates so a recent accidental repeated CSPRNG output fails closed without unbounded memory growth. Deterministic constructors MAY exist only in an explicitly non-production test-vector build and MUST reject zero.

The transfer circuits enforce only witness-visible safety checks: each active output randomness is nonzero and differs from every active input and earlier active output randomness in the same transition. These constraints do not prove entropy, unpredictability, CSPRNG use, or uniqueness against notes absent from the witness; those remain generation-boundary obligations.

Randomness, note openings, and their reuse fingerprints are private material. Errors, logs, events, debug formatting, and telemetry MUST NOT include them. Generation failures use a stable redacted error and fail closed. Preparation and submission APIs that span multiple calls MUST retain the exact opening in holder memory (or holder-controlled encrypted storage); they MUST NOT regenerate an opening after publishing its commitment. A post-send transport, receipt, or event-decoding failure MUST return recoverable holder state containing that exact opening and any known transaction hash rather than a bare error that discards them. Settlement adapters MUST reconcile or resubmit only the exact retained transaction; they MUST NOT rebuild it with fresh note randomness. Confirmation requires a matching canonical settlement event for the retained note commitment (and transaction hash when known).

2.4 Merkle Tree

interface MerkleTree {
  depth: 32;                   // Supports 2^32 notes
  hashFunction: "Poseidon2";   // BN254 field
  root: Hash;
  leaves: Commitment[];
  nextIndex: u32;
}

Merkle path verification uses the untagged binary parent H_2(left, right) over the depth-32 path (zka_lib::compute_merkle_root). Path-index bits are consumed least-significant-bit first across tree levels. The empty leaf is zero and the depth-32 empty root is obtained by 32 applications of H_2(empty, empty). The former std::merkle::compute_merkle_root stdlib helper was Pedersen-based and was removed from the Noir stdlib in nargo 1.0.0-beta.4; ZKA computes the root with Poseidon2 for consistency with all other hashing.

The canonical append source for a ZKA deployment MUST be the settlement contract, settlement validity predicate, or deployment-defined append log that authorizes note creation. Balance servers, wallets, mirrors, and indexers MAY maintain local Merkle trees, but they MUST be reconstructable from the canonical append source and MUST NOT be authoritative for leaf order or root validity.

Every deposit, transfer, bridge import, or settlement action that creates a ZKA note MUST append that note commitment exactly once in deterministic settlement order. The leaf index is the zero-based append position in that canonical order. If multiple commitments are created by one settlement action, their intra-action order MUST be specified by the deployment profile and MUST be stable across all clients.

Canonical append events MUST expose enough public or holder-authorized data for clients and indexers to reconstruct the tree:

interface CommitmentAppendEvent {
  commitment: Commitment;
  leafIndex: u32;
  previousRoot: Hash;
  newRoot: Hash;
  context: Hash; // deployment, chain, asset-domain, and circuit-domain binding
}

A verifier, settlement contract, or validity predicate MUST accept Merkle roots only from a deployment-defined acceptedRootSet. The deployment profile MUST define stale-root handling as either:

Roots outside the accepted window MUST be rejected with a machine-readable stale-root error. Rejection of a stale root MUST NOT invalidate the note; the holder can regenerate a Merkle path against an accepted root and resubmit.


3. ZKA/Pay: Private Transfers

3.1 Transfer Circuit (zka_transfer)

Circuit constraints (in Noir):

  1. Input note Merkle membership
  2. Nullifier correctness: derived from commitment + spending key
  3. Ownership: input note recipient matches derived address
  4. Output commitment correctness
  5. Value conservation: input.value == output.value, output.value > 0
  6. Asset conservation: input.asset == output.asset, asset commitment matches
  7. Context binding: proof bound to epoch/chain
interface TransferProof {
  publicInputs: {
    nullifier: Nullifier;
    outputCommitment: Commitment;
    merkleRoot: Hash;
    context: Hash;             // H(epoch, chainId, applicationId)
    assetCommitment: Hash;     // H(asset.assetId)
  };
  privateInputs: {
    inputNote: Note;
    inputMerklePath: MerklePath;
    spendingKey: SpendingKey;
    outputNote: Note;
  };
  proof: UltraHonkProof;
}

3.2 Withdrawal Circuit (zka_withdrawal)

Consumes a note and reveals amount, asset, and external recipient. Used for bridge exits (ZKA shielded pool → external chain).

Per Safeguard 5 (§1.6), the withdrawal circuit MUST NOT take any compliance predicate, credential, or binding proof as an input or constraint. Exit to self-custody is unconditional.

interface WithdrawalProof {
  publicInputs: {
    nullifier: Nullifier;
    merkleRoot: Hash;
    amount: u64;               // Revealed
    asset: AssetDescriptor;    // Revealed
    externalRecipient: Address; // Revealed
    context: Hash;
  };
  proof: UltraHonkProof;
}

3.3 Root Update Circuit (zka_root_update)

Used by deployments that anchor an append-only commitment tree on an external rollup or L1 contract. Proves that a claimed newRoot is the correct Poseidon2 incremental insertion of the public deposit list followed by a prover-supplied private blob-commitment list into a prior accepted prevRoot. The proof preserves the order of the supplied values, but by itself does not prove that the private list came from published blob data and therefore does not prevent omission or substitution of blob commitments.

interface RootUpdateProof {
  publicInputs: {
    prevRoot: Hash;
    newRoot: Hash;
    prevTotal: u64;                       // commitments folded so far
    newTotal: u64;                        // commitments folded after this update
    blobKzgCommitment: Hash;              // legacy-named non-zero proof tag; not a blob binding
    depositCommitments: Hash[];           // ordered by on-chain event-log position
  };
  privateInputs: {
    blobCommitments: Hash[];              // prover-supplied; not bound to blobKzgCommitment
    intermediatePaths: MerklePath[];      // one path per insertion, depth = MERKLE_DEPTH (32)
  };
  proof: UltraHonkProof;
}

Circuit constraints (in Noir):

  1. Count consistency: newTotal - prevTotal == depositCommitments.length + blobCommitments.length
  2. Deterministic interleave order: deposit commitments first (in event-log order), then blob commitments (in blob-payload order)
  3. Incremental insertion: fold each commitment in order into the Merkle tree starting from prevRoot using intermediatePaths; the final fold MUST equal newRoot
  4. Hash function: Poseidon2 over BN254 per §2.1
  5. Tree depth: MERKLE_DEPTH = 32 per §2.4
  6. Legacy blob-tag check: blobKzgCommitment is a public input and MUST be non-zero; this binds only the exact tag value into the proof and establishes no KZG opening, blob preimage, or relation to the private blobCommitments

Despite its historical name, blobKzgCommitment is not sufficient to bind the private values folded by this circuit. The immutable Base APLBridge passes a caller-supplied value directly into this public-input slot and never executes BLOBHASH; the legacy circuit independently accepts any non-zero value. Consequently, that deployment and circuit establish no blob data-availability or blob-to-leaf binding property.

A successor profile claiming EIP-4844 data availability MUST (1) obtain the blob versioned hash with BLOBHASH in the same blob-carrying transaction and compare it with a proof-bound public input, and (2) verify a cryptographic relation showing that the ordered commitment projection decoded from the blob's canonical announcement log equals the exact private commitments folded into newRoot. A BLOBHASH comparison alone binds the transaction to a blob, not the circuit's private witness values to that blob.

3.4 Immutable Base WETH9 Pool Profile

ZKA/Pay defines an additive, immutable, single-asset settlement profile for Base WETH9. Its four circuits are:

Circuit Purpose
zka_pay_deposit Binds a queued note commitment to the exact public WETH deposit amount and immutable pool/asset context; the contract stores the exact announcement-hash limbs with the queue entry.
zka_pay_transition Proves a private payment with up to two inputs and three ordered outputs (recipient, change, fee).
zka_pay_batch Recursively verifies payments, consumes the mandatory deposit prefix, advances both roots, and binds canonical batch calldata.
zka_pay_withdrawal Consumes a note, advances the sparse nullifier root, and releases the proof-bound amount to the proof-bound Base recipient.

Each accepted deposit appends the immutable tuple (queue index, note commitment, amount, full announcement Keccak-256 high/low u128 limbs, depositor proof hash). A batch computes deposit_prefix_digest from that stored queue range. It separately computes batch_data_digest from strict- parsed canonical calldata, recomputes every deposit announcement hash, and requires the calldata commitment, amount, and both hash limbs to match the immutable queue entry before verification or state advancement. The batch circuit constrains the same five deposit fields into both digest streams.

The pool context is contract-derived, never a deployer-selected constructor value. It uses the exact UTF-8 tag zka/pay/pool-context/v1 and the field-count-prefixed BN254 Poseidon2 fold over profile version, Base chain id, pool and asset addresses, asset commitment, full Keccak-256 limbs of all four circuit ids, and every frozen pool bound/depth. The complete ordered 22-field stream is normative in the pool SDD. Changing any field requires a new pool.

The complete identifiers, flattened public-input order, fixed bounds, tree depths, canonical batchData encoding, digest folds, and deterministic error taxonomy are normative in the ZKA/Pay pool SDD. The machine-readable public-input encoders are schemas/public-inputs/zka_pay_*.json.

This profile supersedes neither the existing bridge circuits (§7) nor the existing ZKA note (§2.3). It uses the existing note-commitment semantics in a separate pool state domain with separate commitment/nullifier trees, escrow, verifiers, and accepted-root history.


4. ZKA/Coord v2: Profile-Bound Recursive Execution

ZKA/Coord v2 uses a fixed, 30-field execution receipt and bounded recursive DAG composition. The normative ABI, domains, profile-manifest contract, workflow-definition encoding, base and recursive cases, trust boundaries, failure codes, migration rules, and graduation gate are defined in docs/sdd/sdd_coord_v2.md. The machine-readable ABI is schemas/public-inputs/zka_coord_v2.json.

A final proof attests that every accepted step was proven by an execution profile in a verifier-selected policy, every active predecessor proof was cryptographically verified, and ordered predecessor outputs exactly equal the successor inputs. Workflow definition, instance, context, initial state, topology, profile artifacts, verification keys, assurance class, and inactive padding are all bound.

The three assurance classes are intentionally distinct: ZK-native execution, proof-producing external execution, and attested execution. The latter proves correct checking of a named trust anchor, not physical-world truth. ZKA defines no global profile allowlist.

Because ZKA/Coord had not launched, v2 is the canonical pre-launch draft rather than a compatibility revision. It remains unaudited and not production-supported until the signed independent-review gate in docs/security/coord_v2_audit_gate.md passes. The pre-launch four-field base circuit, three-field aggregate, and five-field bundle object are removed from active schemas, artifacts, registries, and SDK decoders. A conforming verifier MUST reject them; there is no legacy alias, width-based interpretation, or fallback verification key.

The public Rust API is canonical and unversioned (CoordWorkflowBuilder, CoordProver, CoordVerifier). Version markers remain mandatory in the cryptographic identities that prevent cross-protocol substitution, including the receipt/schema identifiers, circuit and artifact IDs, policy documents, manifest formats, and hash domains.

interface CoordFinalPublicInputsV2 {
  receipt: CoordReceiptV2; // exact 30-field order from the normative SDD/schema
  graphCommitment: Field;
  proofNodeCount: u32;     // 1..=4096
  maxDepth: u32;           // 1..=32
}

interface CoordVerifierPolicyV2 {
  policyDocument: CoordVerifierPolicyDocumentV2;
  verifierPolicyRoot: Field;         // MUST equal recomputed policyDocument root
  acceptedAssuranceClasses: Array<1 | 2 | 3>;
  workflowDefinition: CoordWorkflowDefinitionV2;
  expectedWorkflowInstanceId?: Field;
  expectedContext?: Field;
  acceptedProfileManifestHashes: Field[];
}

Serializers flatten receipt first and then the three aggregate fields, for a fixed total of 33 public field elements.

Proof size is backend-bounded, but proving time and memory depend on active fan-in and recursion depth. Implementations MUST publish measured bounds and MUST NOT claim workflow-length-independent proving cost.

An Aztec deployment is conforming only through an explicit, manifest-bound profile whose claim is tested independently. Kernel composition alone does not silently inherit the Coord v2 claim. The reference SDK publishes the off-chain UltraHonk path. An Aztec target additionally requires an independently registered profile and target verifier; no built-in Aztec Coord contract is provided. An EVM request fails with UnsupportedTarget until an exact EVM artifact and verifier are published, and MUST NOT fall back to an off-chain VK.


5. ZKA/Attest: ZKC Credential Integration (Normative)

This section folds the ZKA-ZKC Integration Specification (docs/archive/zka_zkc_integration_v1_0_0.md, originally Halo 2) into the ZKA protocol layer, ported to Noir / UltraHonk / BN254 / Poseidon2. The constraint logic is field- and proof-system-independent; only the proving backend and performance parameters change relative to the source document.

5.1 Identity Binding Model

The credential binding key links ZKA and ZKC identities without revealing either. ZKC v0.4 defines three presentation profiles. Legacy linkable v1 exposes the stable holderCommitment; v2 scoped exposes only H("zkc/presentation-scope:v1", cbk, scopeId); v2 anonymous exposes neither handle (both presentation public inputs are zero). A scoped pseudonym is unlinkable across distinct scopes unless the principal discloses the link.

IDENTITY BINDING
================

ZKA Identity                    ZKC Identity
------------                    ------------

   address                      holderCommitment
      |                              |
      v                              v
  H("zka/address", vk)          H("zkc/binding", cbk)
      |                              |
      +--------------+---------------+
                     |
                     v
               masterSeed  (private witness only)

5.2 Identity Binding Circuits (zka_credential, zka_transfer_binding_v2)

Proves that a ZKC credential belongs to the same identity as a ZKA note, without revealing the master seed.

IDENTITY BINDING CIRCUIT
========================

Public Inputs:
  - noteCommitment: Commitment
  - holderCommitment: Hash
  - merkleRoot: Hash
  - context: Hash

Private Inputs:
  - masterSeed: Uint8Array[32]
  - note: Note
  - merklePath: MerklePath

Constraints:

1. KEY DERIVATION  (per AFP-KDC v1.0.2 §A.6 — AFP Appendix A)
   sk  = H("zka/spending", masterSeed)
   vk  = H("zka/viewing", sk)
   addr = H("zka/address", vk)
   cbk  = H("zkc/credential", masterSeed)
   // masterSeed is the per-entity sub-seed M' under AFP entity identity

2. NOTE OWNERSHIP
   note.recipient == addr
   note.commitment == noteCommitment
   verifyMembership(note.commitment, merklePath, merkleRoot)

3. CREDENTIAL BINDING
   H("zkc/binding", cbk) == holderCommitment

4. CONTEXT BINDING
   // context bound into the proof; prevents replay to a different
   // session/counterparty (context includes a session id)
interface IdentityBindingProof {
  publicInputs: {
    noteCommitment: Commitment;
    holderCommitment: Hash;
    merkleRoot: Hash;
    context: Hash;
  };
  privateInputs: {
    masterSeed: Uint8Array;     // 32 bytes — proves ownership of both
    note: Note;
    merklePath: MerklePath;
  };
  proof: UltraHonkProof;
}
Parameter Halo 2 (v1.0 source) UltraHonk / BN254 (this spec)
Proving system Halo 2 UltraHonk (Barretenberg)
Circuit size ~2^16 rows ~2^15 UltraHonk gates
Proof size ~5.5 KB ~2 KB
Verification ~12 ms <50 ms (native), <100 ms (browser)
Solidity gas n/a <300k gas

The four-public-input zka_credential circuit above and the five-public-input zka_transfer_binding circuit in §5.3 are the frozen linkable v1 profile. They remain accepted for backward compatibility.

For ZKC v0.4 v2 payments, zka_transfer_binding_v2 is normative. It privately derives sk = H("zka/spending", masterSeed) and cbk = H("zkc/credential", masterSeed), proves that sk owns the input note, and constrains presentationPseudonym = H("zkc/presentation-scope:v1", cbk, scopeId). Its public inputs are (nullifier, outputCommitment, merkleRoot, context, presentationPseudonym, scopeId). scopeId and presentationPseudonym MUST both be non-zero. Neither masterSeed, cbk, nor holderCommitment is public. An anonymous v2 presentation cannot bind a payment note to a credential subject and MUST be rejected for a payment bundle; it remains valid for a payment-free bundle whose proofs share the AFP/session context.

Issuer-signature verification is NOT duplicated here: a ZKA binding proof attests only seed-linkage and note ownership; valid issuance remains in ZKC's per-credential circuits carried by compliance.proofs[]. context MUST bind the relevant replay-protection context and equals the AFP session_context when carried by afp:envelope:v1.

5.3 Combined Transfer + Binding Circuit

For efficiency, a combined circuit proves transfer validity and compliance binding in a single proof. zka_transfer_binding is the frozen linkable-v1 circuit:

COMBINED TRANSFER + BINDING CIRCUIT
===================================

Public Inputs:
  - nullifier: Nullifier
  - outputCommitment: Commitment
  - merkleRoot: Hash
  - context: Hash
  - holderCommitment: Hash      // for compliance binding

Private Inputs:
  - masterSeed: Uint8Array[32]
  - inputNote: Note
  - inputMerklePath: MerklePath
  - outputNote: Note

Constraints:
  // all transfer constraints from §3.1
  +
  // binding constraint
  H("zkc/binding", H("zkc/credential", masterSeed)) == holderCommitment

Parameters (UltraHonk / BN254):
  - ~2^16 gates, ~2 KB proof, single proof for transfer + binding

zka_transfer_binding_v2 replaces holderCommitment with the ordered public inputs presentationPseudonym, scopeId and replaces the private spendingKey witness with masterSeed. The transfer spending key and ZKC v0.4 credential binding key are derived independently from that same secret in-circuit. This is the required v2 payment proof and prevents a prover from pairing a note owned by one subject with a scoped credential presentation from another.

5.4 Compliance Bundle

A Compliance Bundle is the unit a verifier checks: it may carry a ZKA transaction proof, zero or more atomic ZKC predicate proofs, the identity binding that links a payment note to a credential, and — optionally — a recursive ZKA/Coord work-attestation proof (§4). The bundle is a ZKA-side envelope that wraps, but does not redefine, ZKC compliance proofs: each element of compliance.proofs[] is an authoritative ZKC zkc:proof:v1 (linkable) or zkc:proof:v2 (anonymous/scoped) object from ZKC v0.4. The bundle envelope remains independently versioned as zka:bundle:v1; carrying the canonical pre-launch Coord v2 draft proof does not change that envelope version. The carried Coord, ZKC, and presentation-profile identities independently select their exact verifier/VK. Conforming cross-protocol implementations MUST select exactly one combination from the canonical zka:protocol-family:2026-08-15.2 manifest and compatibility matrix; no match or multiple matches is invalid.

Bundle profiles and the substantiveness rule (v0.6.2-draft). Both transaction and coord_proof are OPTIONAL members, but a bundle MUST carry at least one of {transaction, coord_proof} — a bundle with neither attests no substantive ZKA-side work and is invalid (EMPTY_BUNDLE). compliance.proofs[] always accompanies a substantive proof (carried when the counterparty requires predicates); it is never the sole content of a valid bundle. The admissible profiles are:

The normative reciprocal compute/acknowledgement encodings and assurance boundaries are specified in sdd/sdd_coord_reciprocal_data_exchange.md. vxp.reciprocal MUST travel through the existing zka:bundle:v1 pure work-attestation shape inside an authenticated AFP session, with the bundle context equal to the AFP session_context; it introduces no new envelope or session-binding primitive.

Why transaction is now optional: ADR-0003 generalized the AFP federation to non-monetary flows (PRP reconciliation and vxp.reciprocal), which carry compliance/work evidence over zka:bundle:v1 with no payment leg. Requiring a payment transaction proof would make those flows unable to form a valid bundle. Relaxing transaction to OPTIONAL (with the substantiveness rule above) admits the pure work-attestation profile while leaving every existing payment bundle a valid, unchanged instance.

The bundle is also the single ZKA-owned cryptographic payload that AFP's session envelope (afp:envelope:v1, AFP v0.3.1 §12.7) carries verbatim across a tenant boundary. Both relaxations here — the OPTIONAL coord_proof member and the now-OPTIONAL transaction member — are backward-compatible and do not bump the envelope version string (zka:bundle:v1 is unchanged): every existing payment/compliance bundle remains a valid instance, a v0.6.1 verifier never reads coord_proof, and a transaction-absent bundle is a new instance shape that older payment-only flows simply never emit. (These are pre-release, draft-only relaxations; see the version-string note in the Document History.)

Authenticated verifier context is mandatory. Verification MUST receive the expected session context and context-profile name from the authenticated outer interaction (under AFP, afp:envelope:v1.session_context and the selected AFP profile); it MUST NOT copy either expectation from the untrusted bundle. A scoped v2 payment additionally requires a verifier-selected scopeId. The bundle metadata, transaction public input, every ZKC context, and every Coord context MUST match that authenticated context, and a scoped presentation MUST match the selected scope.

Verifier selection is part of verification. For each ZKC predicate, the parser resolves the exact (envelopeVersion, presentationProfile, proofType) tuple and supplies it to the ZKC verifier/VK registry; an unsupported or mismatched tuple fails closed. For the payment binding, the verifier receives the exact selected circuit plus the complete ordered public statement: five fields for zka_transfer_binding v1 or six fields for zka_transfer_binding_v2. A verifier that has no configured cryptographic implementation MUST reject; structural proof-byte checks are not proof verification.

ZKM carriage boundary. zka:bundle:v1 currently defines no authorization or ZKM member. An implementation MUST NOT place a ZKM proof in compliance.proofs[], reinterpret coord_proof as mandate authorization, or claim recursive ZKM carriage through this envelope. A future ZKA/ZKM integration must select either an additive bundle member/profile or a direct settlement interface and must make the ZKA note/nullifier effects and ZKM mandate transition atomic. Until then, ZKM's separately versioned, spend-conjoined statement remains outside zka:bundle:v1 and is accepted only through a ZKM-aware settlement integration and ZKM's deployment registry.

interface ComplianceBundle {
  transaction?: {                      // OPTIONAL since v0.6.2-draft — present for PAYMENT
    type: "transfer" | "deposit" | "withdrawal";   //   flows; OMITTED for pure work-attestation
    proof: UltraHonkProof;             //   bundles (PRP reconciliation, non-monetary VXP).
    publicInputs: ZKAPublicInputs;
  };
  compliance: {
    proofs: (ZKCProofV1 | ZKCProofV2)[]; // Version/profile-discriminated ZKC v0.4 objects;
                                       // each proves ONE atomic predicate (Safeguard 2).
                                       // MAY be empty when the counterparty requires none;
                                       // compliance always ACCOMPANIES a substantive proof,
                                       // it is never the sole content of a valid bundle.
    aggregation?: UltraHonkProof;      // optional recursive aggregate (ZKC v0.4 §5.2)
  };
  binding?: ({                         // Legacy linkable v1 (version may be omitted).
    proof: IdentityBindingProof;
    transactionCommitment: Hash;
    zkcEnvelopeVersion?: "zkc:proof:v1";
    holderCommitment: Hash;
  } | {                                // ZKC v0.4 scoped v2; no stable holder handle.
    proof: TransferBindingV2Proof;
    transactionCommitment: Hash;
    zkcEnvelopeVersion: "zkc:proof:v2";
    presentationPseudonym: Hash;
    scopeId: Hash;
  });
  coord_proof?: {                      // OPTIONAL — ZKA/Coord recursive work-attestation
    proof: UltraHonkProof;             //   profile-bound recursive DAG proof (§4)
    publicInputs: CoordFinalPublicInputsV2; // 30-field final receipt plus
                                           // graph commitment/count/depth;
                                           // receipt.context MUST equal
                                           // metadata.context
  };
  metadata: {
    version: "zka:bundle:v1";
    generatedAt: Timestamp;
    expiresAt: Timestamp;
    context: Hash;                     // Deployment/profile-defined replay context.
                                       // Under AFP, equals afp:envelope:v1.session_context.
  };
}
async function verifyComplianceBundle(
  bundle: ComplianceBundle,
  requirements: ComplianceRequirements,
  trustPolicy: TrustPolicy,
  authenticated: {
    sessionContext: Hash;
    contextProfile: string;
    selectedScopeId?: Hash;
  }
): Promise<VerificationResult> {
  const errors: VerificationError[] = [];

  // Expectations are verifier-supplied, never learned from `bundle`.
  if (bundle.metadata.context !== authenticated.sessionContext)
    errors.push({ code: "BUNDLE_INCONSISTENT", details: "unauthenticated bundle context" });
  if (bundle.transaction !== undefined &&
      bundle.transaction.publicInputs.context !== authenticated.sessionContext)
    errors.push({ code: "BUNDLE_INCONSISTENT", details: "transaction context mismatch" });

  // 0. Substantiveness (v0.6.2-draft) — a valid bundle MUST carry at least one
  //    substantive proof. `compliance.proofs[]` alone is never sufficient.
  if (bundle.transaction === undefined && bundle.coord_proof === undefined)
    errors.push({ code: "EMPTY_BUNDLE",
                  details: "bundle carries neither transaction nor coord_proof" });

  // 1. ZKA transaction proof (OPTIONAL) — verify only when present.
  //    A pure work-attestation bundle (profile b) has no transaction leg.
  if (bundle.transaction !== undefined) {
    if (!(await verifyUltraHonk(bundle.transaction)).valid)
      errors.push({ code: "INVALID_ZKA_PROOF" });
  }

  // 2. ZKC predicate proofs — each predicate MUST be open-source and
  //    inspectable per Safeguard 4; trustPolicy enumerates accepted
  //    predicate providers (no canonical provider — Safeguard 1).
  //    Verified over whatever proofs ARE present (the set MAY be empty).
  if (!(await verifyZKCProofs(bundle.compliance, requirements, trustPolicy, authenticated)).valid)
    errors.push({ code: "INVALID_ZKC_PROOF" });

  // 3. Binding links the payment NOTE <-> ZKC credential. It is present IFF
  //    `transaction` is present (no payment ⇒ no note ⇒ nothing to bind).
  if (bundle.transaction !== undefined) {
    if (bundle.binding === undefined) {
      errors.push({ code: "BUNDLE_INCONSISTENT",
                    details: "transaction present but binding absent" });
    } else {
      const statement = resolveBindingStatement(bundle.transaction, bundle.binding);
      if (!(await verifyManifestSelectedBindingProof(statement)).valid)
        errors.push({ code: "INVALID_BINDING" });

      // 4. Bundle consistency — note<->credential checks, IFF the relevant
      //    members are present.
      if (bundle.binding.transactionCommitment !==
          bundle.transaction.publicInputs.outputCommitment)
        errors.push({ code: "BUNDLE_INCONSISTENT", details: "tx commitment mismatch" });
      for (const envelope of bundle.compliance.proofs) {
        const target = resolveZkcVerifierTarget(
          envelope.version, envelope.presentationBinding?.profile, envelope.proofs);
        if (!target.valid)
          errors.push({ code: "INVALID_ZKC_PROOF", details: "unsupported version/profile" });
        else if (!bindingSubjectMatchesEnvelope(bundle.binding, envelope))
          errors.push({ code: "BUNDLE_INCONSISTENT", details: "binding subject/profile mismatch" });
      }
    }
  } else if (bundle.binding !== undefined) {
    // A transaction-absent bundle MUST NOT carry a note<->credential binding:
    // there is no note for it to bind (it would be unanchored/meaningless).
    errors.push({ code: "BUNDLE_INCONSISTENT",
                  details: "binding present without a transaction" });
  }

  // When `transaction` is absent, the bundle is anchored to a party identity
  // NOT via a note<->credential binding (N/A) but via the shared bundle context:
  // every present proof carries `context == metadata.context` (coord_proof step
  // 6b below; each ZKC proof's claimBinding/context per §5.2), and AFP equates
  // that context to the authenticated `session_context` of the carrying
  // `afp:envelope:v1` (AFP §12.7). Identity binding is thus over whatever proofs
  // ARE present, reusing the existing §5.2 context anchor — no new mechanism.

  // 5. Freshness — bundle expiry AND ZKC accumulator-epoch recency.
  //    Per ZKC v0.4 §6.2, each ZKC proof's accumulator epoch MUST be
  //    within MAX_EPOCH_LAG = 10 epochs of the verifier's current epoch.
  if (bundle.metadata.expiresAt < Date.now())
    errors.push({ code: "BUNDLE_EXPIRED" });
  if (!zkcEpochsWithinLag(bundle.compliance.proofs, /*MAX_EPOCH_LAG*/ 10))
    errors.push({ code: "ZKC_EPOCH_STALE" });

  // 6. ZKA/Coord work-attestation (OPTIONAL) — verify only when present.
  //    A bundle without coord_proof is unaffected by this step.
  if (bundle.coord_proof !== undefined) {
    // 6a. Verify the manifest-bound Coord v2 recursive artifact. This authenticates
    //     profile-adapter and predecessor proofs plus their constrained receipts;
    //     step 6c still applies the verifier's own policy and completion definition.
    //     An Aztec proof is accepted only through an independently conforming
    //     manifest-bound profile (§8.2), never by kernel composition alone.
    if (!(await verifyManifestBoundUltraHonk({
            proof: bundle.coord_proof.proof,
            publicInputs: bundle.coord_proof.publicInputs,
            circuit: "zka_coordination_v2_recursive",
            artifactPolicy: trustPolicy.coord })).valid)
      errors.push({ code: "INVALID_COORD_PROOF" });

    // 6b. Context binding — the coordination proof MUST share the bundle context
    //     (and hence, under AFP, the session_context of the carrying envelope, §12.7).
    if (bundle.coord_proof.publicInputs.receipt.context !== bundle.metadata.context)
      errors.push({ code: "BUNDLE_INCONSISTENT", details: "coord context mismatch" });

    // 6c. Verifier-selected policy and complete-workflow checks. A prover's
    //     public policy root or workflow root is never authoritative.
    const coord = trustPolicy.coord;
    const receipt = bundle.coord_proof.publicInputs.receipt;
    if (coord === undefined) {
      errors.push({ code: "COORD_V2_PROFILE_POLICY", details: "no verifier Coord policy" });
    } else {
      const definition = validateAndHashCoordWorkflow(coord.workflowDefinition);
      const policy = validateAndHashCoordPolicy(
        coord.policyDocument,
        coord.acceptedAssuranceClasses,
        coord.acceptedProfileManifestHashes);
      if (!definition.valid ||
          receipt.workflowDefinitionHash !== definition.root ||
          receipt.stepId !== definition.completionStepId)
        errors.push({ code: "COORD_V2_INCOMPLETE_WORKFLOW" });
      if (!policy.valid || policy.root !== coord.verifierPolicyRoot ||
          receipt.verifierPolicyRoot !== policy.root)
        errors.push({ code: "COORD_V2_PROFILE_POLICY" });
      if (definition.valid && !definition.steps.every(step =>
            coord.acceptedAssuranceClasses.includes(step.assuranceClass)))
        errors.push({ code: "COORD_V2_ASSURANCE" });
      if (definition.valid && !definition.steps.every(step =>
            coord.acceptedProfileManifestHashes.includes(step.profileManifestHash)))
        errors.push({ code: "COORD_V2_ARTIFACT" });
      if (coord.expectedWorkflowInstanceId !== undefined &&
          receipt.workflowInstanceId !== coord.expectedWorkflowInstanceId)
        errors.push({ code: "COORD_V2_SCOPE", details: "workflow instance mismatch" });
      if (coord.expectedContext !== undefined && receipt.context !== coord.expectedContext)
        errors.push({ code: "COORD_V2_SCOPE", details: "verifier context mismatch" });
    }
  }

  return { valid: errors.length === 0, errors };
}

Conditional verification (v0.6.2-draft). Each member is verified IFF present, and the transaction-dependent consistency checks fire only when a payment leg exists:

Identity anchor when transaction is absent. A pure work-attestation bundle still binds to a single party identity, but not through the note↔︎credential binding (which is N/A without a note). Instead, identity binding is over whatever proofs ARE present, reusing the existing anchor with no new cryptography: every present proof carries the shared bundle context (coord_proof.publicInputs.receipt.context per step 6b; each ZKC proof's claimBinding/context per §5.2), and AFP equates that context to the authenticated session_context of the carrying afp:envelope:v1 (AFP v0.3.1 §12.7). The envelope's session authentication is therefore the party-identity anchor for a transaction-absent bundle, exactly as it is the cross-tenant transport anchor for a payment bundle.

A bundle verification failure has exactly one permitted consequence: the counterparty declines the interaction. It MUST NOT trigger any note invalidation, freeze, or force-transfer (Safeguard 3). This holds for a coord_proof failure as well: a failed (or context-mismatched) coordination proof means the work-attestation is not accepted, never that any note is affected.

Coord proof carriage supersedes vxp:coordbundle:v1 (AFP v0.3.1 §12.7, ADR-0003). The optional coord_proof member makes zka:bundle:v1 the single ZKA-owned wire format that carries the ZKA/Coord recursive work-attestation proof alongside the Pay/compliance/binding proofs — the exact capability VXP v0.2 §11 had introduced vxp:coordbundle:v1 to provide. Under ADR-0003 the home for that capability is ZKA, not a profile: VXP's vxp:coordbundle:v1 (and any PRP bundle wrapper) is retired in favor of AFP envelope (afp:envelope:v1) over this extended zka:bundle:v1. A profile never authors a bundle wire format; it places a ZKA bundle inside an AFP envelope. The Coord proof's receipt.context (above) is what AFP binds to the session: it equals the bundle's metadata.context, which an AFP envelope in turn equates to its session_context (AFP §12.7). Coord v2 changes the carried public-input shape and verifier policy checks as specified in §4; the pre-launch five-field coordination carriage is invalid.

AFP §12.7 dependency status: resolved by this §5.4 revision. AFP's previously flagged requirement that a ZKA-owned bundle carry the ZKA/Coord proof is satisfied by the optional coord_proof member of zka:bundle:v1; AFP remains responsible only for afp:envelope:v1 session framing, routing, and session_context binding.

5.4.1 ZKM Authorization Carriage and Atomic Settlement

ZKM spend mandates are an optional interaction authorization, not a ZKA note-validity rule. A conforming ZKA deployment MAY expose the interface in this section; an ordinary transfer, deposit, withdrawal, force exit, or bundle that does not opt into ZKM MUST remain valid under exactly the pre-ZKM rules.

Canonical carriage decision — direct settlement. ZKA selects the ZKM reference path: the settlement target verifies one zkm:spend-conjoined:v2 UltraHonk proof and applies its ZKA and ZKM effects in one target transaction. The proof already contains the ZKM SPEND transition, the ZKA value-note spend, and the policy predicate over shared wires; a second ZKA transaction proof MUST NOT be accepted as a substitute for that structural conjunction. This revision does not add a zkm_authorization (or any other ZKM member) to zka:bundle:v1. Merely placing unrelated ZKA and ZKM proofs in an envelope is non-conforming.

The spend-conjoined public vector is exactly 14 BN254 field elements, in this order (integers are zero-extended to one field element):

(kind, mandateNullifier, mandateSuccessorCommitment,
 mandateSecondSuccessorCommitment, mandateRoot, revocationRoot,
 revocationScheme, currentTime, settlementContext, assetId,
 zkaNoteRoot, zkaNullifier, zkaRecipientCommitment, zkaChangeCommitment)

kind MUST equal ZKM SPEND (0), revocationScheme MUST equal 2, and mandateSecondSuccessorCommitment MUST be the zero sentinel. The verifier MUST flatten named inputs in this order; it MUST NOT sort fields or accept an alternate encoder. ZKM keeps the private amount off this vector. The two ZKA output commitments bind it inside the proof.

revocationScheme names the ZKM revocation-accumulator profile the proof's non-membership witness was constructed against. Scheme 2 is the depth-254 full-identifier exclusion tree; scheme values 0, 1, and 3..255 are invalid and MUST be rejected before verification. The target treats it as a statement discriminator it checks, never as a policy dial it selects: the registry-pinned VK already accepts exactly one scheme, and the explicit equality check makes a scheme mismatch a deterministic rejection rather than a verification failure whose cause is opaque.

Verifier, registry, and binding checks. Before changing state, a conforming target MUST perform all of the following:

  1. Select the one deployment-registry entry for zkm:spend-conjoined:v2, including its exact VK hash, verifier artifact, public-input encoder, ZKM version, settlement target, and asset profile. The value-moving entry MUST be immutable or version-pinned for the lifetime of the adapter. A proof verified by zkm:mandate-only:v2, a possession, disclosure, revocation, control-attestation, or unknown VK MUST NOT reach a value-moving entry point.
  2. Require settlementContext to equal the target's own non-zero ZKA deployment context. The fixed spend-conjoined circuit also proves that the consumed mandate commits to this domain. A caller-supplied context is never authoritative.
  3. Require assetId to equal the target registry's asset identifier. The fixed circuit proves that the consumed ZKA note and both output notes use the corresponding ZKA asset commitment. A settlement adapter MUST document the exact assetId↔︎ZKA asset-commitment mapping it accepts.
  4. Recognize mandateRoot in its own recent mandate-root history, zkaNoteRoot in its own recent ZKA note-root history, and revocationRoot in its own ZKM revocation-root history. It derives the revocation epoch from that history and applies ZKM freshness policy. Post-cutover, that history MUST contain only scheme-2 roots published at or after the ZKM activation epoch; a scheme-1 root MUST NOT be recognized, relabelled, or reinterpreted under scheme 2. currentTime MUST equal the target clock value substituted for this settlement attempt. revocationScheme MUST equal 2.
  5. Verify the proof against the registry-selected spend-conjoined VK and the exact vector above. That circuit is the same-note/same-holder proof: its public zkaNullifier consumes the ZKA note proven under zkaNoteRoot, while one private holder secret derives the mandate holder key, the consumed ZKA note address, and the ZKA nullifier key. Host code MUST NOT emulate this private equality with caller-supplied holder fields.
  6. Reject if mandateNullifier is already present in the target's single mandate-nullifier set for settlementContext, shared by every accepted ZKM VK, or if zkaNullifier is already present in ZKA's nullifier state. Per-VK mandate-nullifier sets are forbidden: the same mandate note could otherwise be replayed once per statement/VK.

All field elements MUST be canonical BN254 representatives. Required roots, nullifiers, context, asset, and live output commitments MUST be non-zero. Apart from the SPEND kind discriminator, the only zero in a conforming SPEND vector is the unused second mandate-successor slot.

ZKM statement version boundary (fail-closed). The statements selected here are the post-cutover ZKM ones. zkm:spend-conjoined:v1 and zkm:mandate-only:v1 — the 13- and 8-element ABIs of the previous revision, whose witnesses were built against the depth-128 low-half revocation tree and which carry no revocationScheme — are valid only under pre-cutover ZKM keys and roots. A post-cutover deployment registry MUST omit both, MUST NOT reinterpret or upgrade a v1 proof, and MUST NOT accept a v1 vector under a v2 VK or a v2 vector under a v1 VK. The two profiles never coexist in one registry: ZKM's cutover is atomic, so before activation only the v1 profile is accepted and at or after activation only scheme 2 and its v2 keys are. A deployment that must remain on pre-cutover ZKM is conforming to the previous revision of this section and MUST document that it is pre-cutover; it is not conforming to this one, and a target MUST NOT present the two as interchangeable.

Atomic state transition. After every precondition and proof check succeeds, the target applies exactly these five effects as one transaction:

  1. check-and-insert mandateNullifier into the shared ZKM nullifier set;
  2. check-and-insert zkaNullifier into ZKA nullifier state;
  3. insert mandateSuccessorCommitment into the mandate tree;
  4. insert zkaRecipientCommitment into the ZKA note tree; and
  5. insert zkaChangeCommitment into the ZKA note tree.

Either all five effects and their events become visible, or none do. A target MUST use a transactional state primitive (for example an EVM transaction, a database transaction, or copy-on-write staging) and MUST propagate any late tree, capacity, or callback failure so the two nullifiers roll back with every commitment insertion. It MUST NOT acknowledge, queue, or emit a successful effect before the whole transition commits. All five effects use the target's ordinary ZKA finality semantics.

The non-value entry point verifies zkm:mandate-only:v2 over its distinct 9-element ABI and MAY update only mandate nullifier/commitment state:

(kind, mandateNullifier, mandateSuccessorCommitment,
 mandateSecondSuccessorCommitment, mandateRoot, revocationRoot,
 revocationScheme, currentTime, settlementContext)

kind MUST NOT be SPEND (0) and revocationScheme MUST equal 2. Its ABI contains no ZKA note root, ZKA nullifier, ZKA output commitment, or asset ID; this absence is a deployment-safety property, not a host-side policy flag. The two vectors are the same nine leading elements plus the spend-conjoined form's five-element ZKA value leg, so an implementation MUST distinguish them by registry-selected VK and vector length, never by content sniffing. Both entry points MUST share the mandate-nullifier set.

Exit and revocation boundary. The ordinary ZKA withdrawal and force-exit entry points MUST NOT call the ZKM adapter, inspect mandate state, require a ZKM proof, or consult ZKM revocation/expiry. Revoking or exhausting a mandate can prevent a future mandate-authorized interaction; it cannot invalidate, freeze, redirect, or delay any ZKA note or exit. ZKM RETIRE concerns remaining mandate authority and is not the ZKA custody exit.

zka:bundle:v1 compatibility proof. The JSON interface in §5.4, its version literal, substantiveness rule, verifier algorithm, error taxonomy, and all existing serialized instances are unchanged. The direct-settlement input is a separate target method and is never parsed by verifyComplianceBundle. Therefore every previously valid zka:bundle:v1 remains byte-for-byte valid, unknown ZKM carriage is not implied, and no envelope version bump is warranted. A future recursive/bundle path requires an explicitly specified member, registry binding, equality checks, atomic target transition, and compatibility decision; until then it is non-conforming.

The executable reference boundaries are contracts/ethereum/ZKMAtomicSettlement.sol and src/zka-sdk/src/zkm/mod.rs. The Solidity contract fixes separate immutable spend-conjoined and mandate-only verifier lanes, authenticates both verifier/VK-hash pairs through the target registry at construction, and uses one cross-lane mandate-nullifier mapping; the SDK model stages a cloned state and publishes it only after every effect succeeds. They define the adapter contract, not a claim that every existing ZKA pool deployment has enabled ZKM.

5.5 Predicate Model

A predicate is an open-source compliance circuit that proves exactly one property (e.g. "holder is not on an OFAC SDN list", "holder is over an age threshold"). Predicates are published by independent, permissionless predicate providers.

Resolved in ZKC v0.2.2 — proof-type registry is non-exclusive. Earlier ZKC drafts (v0.2.1 Appendix A) enumerated fixed credential/proof-type identifiers and closed schemas, which tensioned ZKA Safeguard 1. ZKC v0.2.2 reframes Appendix A as a "Well-Known Identifier Registry (Non-Exclusive)" and §3.1–3.6 as reference (non-mandatory) schemas, stating normatively that no type/provider is privileged or required, that a verifier's trust policy MAY accept types/predicates outside the registry, and that the protocol defines no mechanism for designating a canonical set. This matches the non-exclusive reading this spec already adopted: ZKC well-known type IDs are permitted but never required, and a verifier's trustPolicy MAY accept predicates outside the ZKC registry. No residual conflict with Safeguard 1.

5.6 Credential Lifecycle

Status: normative by reference to ZKC. The binding, bundle, and predicate model (§5.2–5.5) are normative in this specification. The issuance, revocation, and accumulator-witness mechanics below are owned by ZKC and are now finalized there: the concrete issuer-bootstrapping and accumulator-witness-distribution protocols are specified normatively by the ZKC Specification §4 (Attestation Layer) and the ZKC Issuer Adapter Protocol v1 (ZKC/docs/zkc_issuer_adapter_protocol_v1.md, with schemas/issuer-metadata.schema.json and a machine-checked conformance kit). ZKA references that protocol rather than redefining it.

Issuance / Issuer Bootstrapping. A ZKC credential is issued by a credential issuer (e.g. a KYC provider) binding an attestation to the holder's cbk via holderCommitment = H("zkc/binding", cbk). The issuer never learns the holder's ZKA address or spending key. The issuer-bootstrapping protocol (issuer registration, issuer key publication, holder ↔︎ issuer attestation exchange) is REQUIRED for a conforming ZKC deployment and is specified normatively by ZKC (ZKC Specification §4 and the ZKC Issuer Adapter Protocol v1); ZKA references it rather than redefining it.

Revocation. Credential validity is tracked by a cryptographic accumulator. A predicate proof MUST demonstrate non-revocation by proving a current accumulator witness for the credential. Revocation MUST NOT be able to affect ZKA note validity (Safeguard 3) — a revoked credential causes counterparty refusal only.

Accumulator Witness Update Distribution. On any revocation the accumulator changes and all non-revoked holders need an updated witness. A conforming deployment MUST provide a witness-update distribution channel that does not deanonymize holders (e.g. publish epoch accumulator deltas that holders apply locally). The concrete distribution protocol is Draft.

5.7 Structural Requirements

The following are REQUIRED for a complete deployment. The note discovery, key custody, and key rotation/recovery requirements are normative in this revision; delegated proof generation remains Draft where marked.

5.8 Aztec Composability

When deployed as an aztec.nr contract, an identity binding proof (§5.2) is composable with private DeFi within a single Aztec transaction via the kernel circuit call stack (see §8.4). Credential non-revocation checks run against Aztec public state without revealing which credential is checked.


6. Selective Disclosure

6.1 Balance Proof Circuit (zka_balance)

Proves note.value >= threshold without revealing the exact value. Uses Noir's built-in range constraints. A balance proof is an atomic single-property disclosure and is subject to Safeguard 2.

interface SelectiveDisclosure {
  claim: DisclosureClaim;
  proof: UltraHonkProof;
  publicInputs: {
    noteCommitment: Commitment;
    merkleRoot: Hash;
    claimBinding: Hash;         // H(claim_type, threshold)
  };
}

7. Bridge Circuits

7.1 Ethereum Bridge

Import/export between the ZKA shielded pool and Ethereum. Solidity verifier auto-generated via nargo codegen-verifier (UltraVerifier.sol). Verifier gas target: <300k gas per verification. Export (withdrawal) is unconditional per Safeguard 5.

A conforming ZKA Ethereum bridge contract MUST satisfy the following invariants:

  1. Ownerless and non-upgradeable. No owner, no pause(), no upgradeTo(), no setOperator(), no setVerifier(), no rescue(), no migrate(), no proxy slot, no selfdestruct, no constructor delegatecall.
  2. Immutable configuration. Asset address, verifier addresses, operator address (if any), liveness parameters, initial root, and the deployment-binding context MUST be declared immutable and set in the constructor.
  3. Append-only mutable state. Root index, total appended commitments, and last-root-submitted timestamp MUST be strictly monotonic. Consumed nullifiers MUST transition only from false to true. Accepted roots MUST be removed only by deterministic window pruning.
  4. Root advancement MUST be gated by a root-update proof verifying that the new root is the correct Poseidon2 incremental fold of the deterministic interleave of on-chain deposit-event commitments and blob-published transfer-announcement commitments into the prior root. The proof system MUST also establish that its private blob-commitment list is the canonical list encoded by the blob identified under invariant 6; the legacy zka_root_update circuit (§3.3) does not establish that relation on its own.
  5. Permissionless force-exit path. When operator silence exceeds a published immutable threshold, any address MUST be able to advance the root and call withdrawal. The contract MUST NOT distinguish operator and non-operator callers for withdrawal.
  6. EIP-4844 blob data availability. Internal-transfer NoteAnnouncements (§5.7) MUST be carried in EIP-4844 blobs referenced by the same transaction that advances the root. The bridge MUST obtain the blob versioned hash through BLOBHASH, bind it to the root-update proof's public inputs, and verify the proof relation required by invariant 4 so the folded leaf list equals the ordered commitment projection decoded from the canonical announcement log. Blob sidecars provide public retrieval only for the network's blob-availability window; a conforming deployment MUST durably archive the canonical payload before expiry, and holders MUST retain the witness data required by §7.1.5. The versioned hash and root-update event alone are not enough to reconstruct expired blob contents.

Historical deployment: APL v0.3.2 §5.4 in pyramidal-io/ADAMAS.Network — see also contracts/ethereum/APLBridge.sol. The immutable Base APLBridge predates this correction and does not satisfy invariants 4 and 6 because it neither executes BLOBHASH nor proves the blob-to-private-leaf relation; it MUST NOT be used as a conforming reference implementation or receive new escrow.

7.1.1 Bridge State (normative)

A conforming bridge MUST hold, on-chain, all state needed for any holder to exit without any off-chain operator service:

State Description Mutability
Canonical append log Note commitments in deterministic settlement order with zero-based leaf indices (§2.4). Carried as contract storage plus CommitmentAppendEvents. Append-only
Accepted root set Recent Merkle roots per the deployment's accepted-root window (§2.4): latest N roots (N >= 1), a block-height/epoch/wall-clock window, or both. Window parameters MUST be immutable constructor configuration. Deterministic window pruning only
Nullifier set Consumed nullifiers. false → true only
Verifier references Addresses of the withdrawal verifier and (where root advancement is SNARK-gated, invariant 4) the root-update verifier. immutable, or version-pinned by immutable configuration
Escrow balances Per-asset collateral backing the shielded pool's circulating supply. The bridge MUST maintain escrow[asset] >= circulatingSupply[asset] (solvency invariant). Changed only by deposits and withdrawals
Deployment context The deployment-binding context hash (chain, contract, asset-domain, circuit-domain) that withdrawal proofs MUST bind via their context public input. immutable

No other party — operator, indexer, balance server, prover — holds authoritative state. Anything they hold MUST be reconstructable from the bridge's events (§7.1.3) and MUST NOT be required for exit.

7.1.2 Force-Exit Path (normative)

Force-exit is the ordinary withdrawal, exercised permissionlessly. It is an unconditional withdrawal path, not an operator-mediated rescue path: there is no separate rescue circuit, no operator co-signature, no rescue court, and no admin intervention point.

The submitted proof is the standard zka_withdrawal proof (§3.2), whose public inputs are (nullifier, merkleRoot, amount, asset, externalRecipient, context). Per Safeguard 5 (§1.6), no compliance predicate, credential, or binding proof is an input, and the bridge MUST NOT add one.

A conforming bridge MUST process a withdrawal in this order:

  1. Root acceptance. merkleRoot MUST be in the accepted root set; otherwise reject with UNKNOWN_ROOT or STALE_ROOT (§7.1.4).
  2. Nullifier freshness. nullifier MUST NOT be in the nullifier set; otherwise reject with NULLIFIER_ALREADY_SPENT.
  3. Asset support and context binding. asset MUST be a configured asset and context MUST equal the deployment context; otherwise reject with UNSUPPORTED_ASSET / INVALID_CONTEXT.
  4. Proof verification. The proof MUST verify against the immutable withdrawal verifier over exactly the §3.2 public-input vector; otherwise reject with INVALID_PROOF.
  5. Escrow sufficiency. Available escrow for asset MUST cover amount; otherwise reject with INSUFFICIENT_ESCROW. (Under the solvency invariant of §7.1.1 this cannot occur absent a bridge defect; the check is defense-in-depth and MUST still be explicit.)
  6. Effects before interactions. The bridge MUST record the nullifier and decrement supply/escrow accounting before transferring funds to externalRecipient.

No challenge period. For a pure on-chain shielded pool, the accepted root history and the nullifier set are the complete audit surface: a withdrawal is valid iff the proof verifies against an accepted root and the nullifier is fresh. A conforming bridge MUST NOT impose a challenge, delay, or dispute window on shielded-pool exits. Challenge periods exist to police off-chain data withholding (state channels, optimistic rollup state) and are out of scope for this path.

Permissionlessness. The withdrawal entry point MUST be callable by any address, MUST NOT distinguish operator and non-operator callers, and — together with invariant 5 — MUST remain exercisable when the operator is silent: after the published immutable liveness threshold, any address can advance the root with a root-update proof satisfying invariants 4 and 6 and then withdraw.

7.1.3 Root Publication Model (normative)

Root advancement and commitment-append metadata MUST be observable from settlement-chain execution history. Blob-backed announcement payloads MUST additionally remain retrievable from consensus blob sidecars during the network availability window and from the deployment's durable archive afterward:

7.1.4 Failure Codes (normative)

Withdrawal rejections MUST be machine-readable (e.g. Solidity custom errors) and distinguish at minimum:

Code Condition Class
UNKNOWN_ROOT merkleRoot never accepted by this bridge Retryable after re-proving against an accepted root
STALE_ROOT merkleRoot was accepted but has aged out of the window Retryable: regenerate the Merkle path against a current accepted root and re-prove
NULLIFIER_ALREADY_SPENT nullifier already recorded Terminal for this note
INVALID_PROOF Proof fails verification Retryable after correcting the witness
UNSUPPORTED_ASSET asset not configured at deployment Terminal for this bridge instance
INVALID_CONTEXT context does not bind this deployment Retryable with the correct context
INSUFFICIENT_ESCROW Escrow for asset cannot cover amount Retryable; indicates a bridge solvency defect that MUST be observable

A stale or unknown root MUST NOT invalidate the note: the note remains spendable, and the holder retries with a fresh proof. No failure code, and no party, can transition a note itself to an invalid state — only nullifier consumption by the holder's own proof does that (Safeguard 3).

7.1.5 Client-Retained Witness Data (normative)

Force-exit is only credible if holders can construct the withdrawal witness with every operator service unavailable. A conforming client/SDK MUST retain, locally or holder-controlled:

A conforming SDK MUST be able to produce a valid withdrawal proof from this retained data plus public L1 history alone. When witness data is missing, diagnostics MUST be deterministic and machine-readable (missing plaintext, missing/stale path, stale root, spent nullifier), so a holder can distinguish recoverable from unrecoverable states. Retained data MUST NOT expose spending keys to any deployment service.

7.1.6 Relationship to the Aztec Portal Path (informative)

The Aztec native deployment (§8) settles L2 → L1 withdrawals through the Aztec Outbox and an ownerless per-asset portal contract (ADR-0002). That path consumes Aztec kernel-proven L2→L1 messages; it does not use the §3.2 standalone withdrawal proof, this section's bridge state, or the §3.3 root-update circuit. The two exit paths are deliberately separate mechanisms satisfying the same Safeguard 5 obligation; conformance to one does not imply conformance to the other.

7.2 Namada Bridge

Noir package Circuit ID Proves
bridge_namada_import NAMADA_IMPORT_V1 MASP nullifier valid + ZKA note correctly created
bridge_namada_export NAMADA_EXPORT_V1 ZKA nullifier valid + MASP note correctly created

Namada bridge proofs use the canonical Noir / Barretenberg UltraHonk / BN254 circuit stack. The Rust crate src/zka-bridge-namada is the settlement integration boundary: it handles MASP/ZKA translation, IBC request and packet handling, VP state/event access, ordered public inputs, proof requests, and verifier envelopes for the Noir packages above. The SDK adapter src/zka-sdk/src/bridges/namada.rs wires this bridge into the common SettlementBridge interface. Any retained Groth16 code is non-canonical migration or test support; conforming Namada bridge deployments MUST verify the NAMADA_IMPORT_V1 and NAMADA_EXPORT_V1 UltraHonk/BN254 proof artifacts identified in §9.3.

7.3 Penumbra Bridge

Noir package Circuit ID Proves
bridge_penumbra_import PENUMBRA_IMPORT_V1 Penumbra note consumed + ZKA note correctly created
bridge_penumbra_export PENUMBRA_EXPORT_V1 ZKA note consumed + Penumbra note correctly created

Translates between Penumbra value commitments and ZKA note commitments across field boundaries. Penumbra bridge proofs use the canonical Noir / Barretenberg UltraHonk / BN254 circuit stack. The Rust crate src/zka-bridge-penumbra is fresh settlement integration code for Penumbra RPC/view/custody boundaries, asset/nullifier mapping, transaction plans, receipt/event parsing, and Noir proof artifact loading. It is not the removed legacy Halo2/Pallas Penumbra bridge crate and MUST NOT depend on halo2_proofs 0.3 or zka-circuits. The SDK adapter src/zka-sdk/src/bridges/penumbra.rs wires Penumbra into the common SettlementBridge interface.

7.4 Optional external shielded-pool settlement (deployment note)

Distinct from the ZKA bridge circuits above (§7.1–§7.3), the native ZKA/Pay pool profile (§3.4), and Aztec-native deployment (§8), a conforming deployment MAY delegate settlement to an external, immutable, operator-free shielded pool holding a non-freezable asset. ZKA's own transfer circuits (§3) do not run in this mode; the host pool provides the shielded value transfer, and ZKA contributes only the additive ZKA/Coord (§4), ZKC (§5), and AFP federation layer off-chain. Identity therefore binds at the session/context level (§5.4), because host-pool notes are not ZKA notes.

This optional delegation mode demonstrates settlement-rail independence; it is not the flagship ADAMAS production architecture. ADAMAS APL 0.4 selects the native Base WETH9 ZKA/Pay profile: AFP signs the USD obligation and exact wei settlement quote, while the immutable single-asset pool executes ZKA/Pay proofs. Railgun/BOLD references in the v0.7.1 history describe the superseded APL ADR-0011/0012 decision, not current deployment guidance.


8. Aztec Native Deployment

Aztec native is a first-class deployment target for ZKA notes and transfers. ZKA/Coord v2 conformance is separate: an Aztec implementation MUST publish a manifest-bound execution profile and demonstrate the §4 claim with its own conformance evidence; kernel composition alone is insufficient.

8.1 ZKA Transfer as Aztec Private Function

#[private]
fn transfer(
    input_note: ZKANote,
    output_note: ZKANote,
    spending_key: SpendingKey,
    merkle_path: MerklePath
) {
    // ZKA note -> Aztec note stored in Aztec's note hash tree
    // Nullifier managed by Aztec's indexed nullifier tree
    // Kernel circuit handles proof composition
}

ZKA notes become Aztec notes. Aztec's note hash tree replaces the protocol-level Merkle tree from §2.4. Aztec's indexed nullifier tree handles nullifier management from §2.3. Aztec contract code MUST NOT introduce pausable/upgradeable/admin paths over note validity (Safeguard 3).

8.2 ZKA/Coord via Aztec Kernel Circuits

An Aztec Coord profile MAY represent a step as a private function call and use kernel proof composition. The profile circuit/adapter MUST still emit the canonical Coord v2 receipt, bind the exact Aztec program and artifact identity, enforce the workflow's per-step predicate/topology, and satisfy the same verifier-selected policy and completion checks. Until that profile passes the Coord v2 conformance gate, it MUST be labelled non-conforming experimental. The active Aztec workspace contains no built-in Coord contract: consumers use the canonical SDK verification flow and may delegate an Aztec proof only through an independently registered profile, exact artifact identity, and target-specific verifier implementation. Off-chain proof/VK material is never an Aztec fallback.

8.3 Key Mapping

ZKA Key Aztec Key
masterSeed App-siloed nullifier secret nsk_app
spendingKey (sk) H("zka/spending", nsk_app)
viewingKey (vk) H("zka/viewing", spendingKey)
credentialBindingKey (cbk) H("zkc/credential", nsk_app) — sibling of spendingKey
address H("zka/address", viewingKey)

8.4 Composable ZKC Compliance

On Aztec, a single transaction can atomically combine a shielded ZKA transfer (§8.1) and an identity binding / predicate proof (§5) via the kernel circuit call stack, without revealing individual claims. Non-revocation checks run against Aztec public state without revealing which credential is checked.

8.5 Cross-Environment Interoperability

Standalone EVM and Aztec-native deployments can share circuit-level field, hash, note, and receipt semantics where their manifests say so. A proof object is target- and VK-bound: an EVM UltraHonk proof is not assumed to verify in an Aztec kernel, or vice versa. Cross-environment interoperability requires an explicit adapter or independently generated target proof over the same canonical commitments.

8.6 Relationship to Aztec Primitives

ZKA deliberately shares Aztec's cryptographic substrate — BN254, Poseidon2, UltraHonk/Barretenberg, and Noir — so that the Aztec-native deployment delegates to Aztec's primitives rather than reimplementing them. This subsection makes the delegation boundary explicit: what ZKA stops carrying on Aztec, and what it continues to add. It is descriptive, not a new normative requirement.

Delegated to Aztec (native deployment). In an aztec.nr deployment, the host provides the following and ZKA does not carry its own:

ZKA mechanism (spec) Provided on Aztec by Notes
Note commitment + Merkle tree (§2.3–§2.4) Aztec note hash tree ZKA notes become Aztec notes; the depth-32 Poseidon2 tree is replaced by Aztec's append-only note hash tree
Nullifier set (§2.3, §11.5) Aztec indexed nullifier tree Non-membership and double-spend prevention are Aztec's
Host proof composition Aztec kernel circuits Kernel aggregation is usable only inside an explicit Coord v2 profile; it does not itself establish §4 semantics
Key hierarchy (§2.2) Aztec key derivation app-siloed nsk_app is the AFP-KDC seed; sk and cbk derive as siblings (§8.3)
Proof system / field / hash (§2.1, §10) Aztec native (Honk, BN254, Poseidon2) Identical by construction; ZKA adopted this substrate in v0.5.0 (§2.1 migration note)

On Aztec, the ZKA settlement layer can delegate state primitives to the host. The Coord v2 layer does not reduce to naming conventions: it remains the profile-bound receipt, workflow, policy, and conformance contract defined in §4. ZKA does not claim an improvement over Aztec's host primitives.

Additive — no Aztec-native equivalent. The following are ZKA/ZKC contributions that Aztec does not provide and that remain ZKA's own in every deployment, including the Aztec-native one:

  1. ZKC compliance / attestation (§5). The seed-sibling credential binding (cbk = H("zkc/credential", seed), §2.2), the ZKA-note ↔︎ ZKC-holderCommitment identity-binding circuit (§5.2), selective-disclosure predicates (§5.5, §6), the revocation accumulator and credential lifecycle (§5.6), and the zka:bundle:v1 compliance envelope (§5.4). On Aztec these are implemented as contract logic using Aztec's app-siloed keys (§5.8), but Aztec specifies no equivalent compliance-credential system.
  2. Freedom Safeguards as invariants (§1.6). Operator-blindness, unconditional exit, no admin/upgrade/pause path over note validity, and verifier accountability. These constrain how Aztec may be used rather than duplicating an Aztec feature: §8.1 requires that Aztec contract code introduce no pausable/upgradeable/admin path over note validity (Safeguard 3) — a discipline Aztec's contract model does not impose by default.
  3. Cross-environment compatibility (§8.5). ZKA pins the note commitment scheme, asset-id encoding (ADR-0003), and domain separators so that the same circuit semantics are provable in each environment against that environment's verifier (the standalone Solidity UltraVerifier and the Aztec deployment), given a shared BN254 field. This is a compatibility contract at the circuit/field/hash level. It is not a claim that a single proof object verifies in both environments, that one note simultaneously occupies an Aztec tree and an external bridge tree, or that either verifier accepts the other's proofs; each environment keeps its own state, and value crosses between them only through the bridge circuits (§7).
  4. Settlement-rail independence. ZKA's note/nullifier/proof model is specified independently of any settlement rail, so the same model runs Aztec-native, against a standalone EVM bridge (§7.1), or with no chain at all on an operator-run platform. Aztec's primitives are inseparable from the Aztec rollup; ZKA's are not bound to any one host.

Scope summary. On Aztec specifically, ZKA's value is the additive set above — ZKC compliance, the Freedom Safeguards, and cross-environment note compatibility — layered on delegated Aztec machinery. ZKA's rail-independent primitives (its own tree, nullifier set, recursion plumbing, and the standalone verifier path) are justified by the non-Aztec deployments, not by the Aztec one. Aztec is treated as a first-class — and the most primitive-complete — deployment target; this subsection records that relationship so the boundary between delegation and addition is explicit rather than implied.


9. Circuit Specifications

9.1 Transfer Circuit

Parameter Value
Proving system UltraHonk (Barretenberg)
Curve BN254
Target proving time <5s (consumer hardware, browser WASM)
Proof size ~2 KB
Solidity verification gas <300k gas
Verification time <50ms (native), <100ms (browser)

9.2 Coordination Circuit

Parameter Value
Receipt public inputs 30 fields
Final recursive public inputs 33 fields
Maximum predecessor fan-in 4
Maximum recursive depth 32
Maximum unrolled proof nodes 4096
Proving time / memory / proof size / gas Unclaimed until the §4 conformance benchmark gate publishes reproducible measurements

9.3 Circuit Identifiers

const CIRCUIT_IDS = {
  // Core circuits — Barretenberg/UltraHonk/BN254
  TRANSFER_V1:            "zka:transfer:ultrahonk:bn254:v1",
  WITHDRAWAL_V1:          "zka:withdrawal:ultrahonk:bn254:v1",
  ROOT_UPDATE_V1:         "zka:root_update:ultrahonk:bn254:v1",
  COORD_RECEIPT_V2:       "zka:coord:receipt:ultrahonk:bn254:v2",
  COORD_PROFILE_V2:       "zka:coord:profile:ultrahonk:bn254:v2",
  COORD_RECURSIVE_V2:     "zka:coord:recursive:ultrahonk:bn254:v2",
  BALANCE_PROOF_V1:       "zka:balance:ultrahonk:bn254:v1",
  TRANSFER_MULTIASSET_V1: "zka:transfer:multiasset:ultrahonk:bn254:v1",

  // Immutable Base WETH9 ZKA/Pay pool profile
  PAY_DEPOSIT_V1:         "zka:pay_deposit:ultrahonk:bn254:v1",
  PAY_TRANSITION_V1:      "zka:pay_transition:ultrahonk:bn254:v1",
  PAY_BATCH_V1:           "zka:pay_batch:ultrahonk:bn254:v1",
  PAY_WITHDRAWAL_V1:      "zka:pay_withdrawal:ultrahonk:bn254:v1",

  // Bridge circuits
  NAMADA_IMPORT_V1:       "zka:bridge:namada:import:ultrahonk:bn254:v1",
  NAMADA_EXPORT_V1:       "zka:bridge:namada:export:ultrahonk:bn254:v1",
  PENUMBRA_IMPORT_V1:     "zka:bridge:penumbra:import:ultrahonk:bn254:v1",
  PENUMBRA_EXPORT_V1:     "zka:bridge:penumbra:export:ultrahonk:bn254:v1",

  // Credential / ZKC integration circuits
  CREDENTIAL_BINDING_V1:  "zka:zkc:binding:ultrahonk:bn254:v1",
  TRANSFER_BINDING_V1:    "zka:zkc:transfer_binding:ultrahonk:bn254:v1",

  // Selective disclosure circuits
  AGE_THRESHOLD_V1:       "zka:disclosure:age_threshold:ultrahonk:bn254:v1",
  RANGE_MEMBERSHIP_V1:    "zka:disclosure:range_membership:ultrahonk:bn254:v1",
  SET_MEMBERSHIP_V1:      "zka:disclosure:set_membership:ultrahonk:bn254:v1",
};

Predicate circuits are NOT enumerated here: per Safeguard 1 the protocol carries no canonical predicate set. Predicate circuit identifiers are assigned by their independent providers and selected by a verifier's trustPolicy.

9.4 Noir Project Structure

noir/
├── Nargo.toml                        # Workspace root (Noir v1.0, pinned)
├── zka_lib/                          # Shared primitives (Poseidon2, key derivation, Merkle)
├── zka_transfer/                     # ZKA/Pay transfer
├── zka_withdrawal/                   # Bridge withdrawal
├── zka_root_update/                  # Bridge root advancement (§3.3)
├── zka_balance/                      # Balance proof (selective disclosure)
├── zka_coord_v2/                     # Shared Coord v2 receipt constraints
├── zka_coordination_v2/              # Coord v2 canonical receipt validator
├── zka_coordination_v2_profile/      # Profile/policy-bound execution adapter
├── zka_coordination_v2_recursive/    # Sound bounded DAG composition
├── zka_coord_v2_sample_application/  # Profile-adapter conformance example
├── zka_credential/                   # ZKC identity binding (§5.2)
├── bridge_namada_import/
├── bridge_namada_export/
├── bridge_penumbra_import/
└── bridge_penumbra_export/

10. Proof Format

10.1 UltraHonk Proof (Barretenberg)

interface UltraHonkProof {
  proof: Uint8Array;           // Serialised UltraHonk proof bytes
  publicInputs: Field[];       // BN254 scalar field elements
  verificationKey: Uint8Array; // Serialised verification key
}

Proofs are generated by Barretenberg (bb prove) or NoirJS (noir_js + WASM). Verification is performed on-chain by the auto-generated UltraVerifier.sol Solidity contract.

10.2 Solidity Verifier Interface

interface IUltraVerifier {
  function verify(
    bytes calldata proof,
    bytes32[] calldata publicInputs
  ) external view returns (bool);
}

The verifier is generated per-circuit via nargo codegen-verifier. Each circuit has its own deployed UltraVerifier instance. Verifier contracts MUST be non-upgradeable and admin-free (Safeguard 3).


11. Security Considerations

11.1 Cryptographic Assumptions

Assumption Implication if Broken
Discrete log (BN254) Spending key recovery
Poseidon2 collision resistance (BN254 field) Note forgery / identity binding forgery
UltraHonk soundness (Barretenberg) Invalid proofs accepted
Noir ACIR compilation correctness Circuit constraints deviate from spec
Accumulator security Revoked credentials appear valid
Master seed secrecy Full compromise of both ZKA and ZKC identities

11.2 Implementation Requirements

Requirement Rationale
Constant-time nullifier queries Prevent timing attacks
Secure randomness for note creation Prevent commitment grinding
Atomic nullifier recording Prevent double-spend race conditions
Merkle root recency checks Prevent stale state attacks
Pinned Noir + Barretenberg versions Prevent breaking changes in proof format
No admin/pausable/upgrade paths over notes Enforce Safeguard 3
Unconditional unshield/withdraw Enforce Safeguard 5

11.3 Privacy Limitations

Limitation Mitigation
Timing correlation Randomized delays, batching
Transaction graph (long-term) Dummy transactions
Note value distribution Standardized denominations
Metadata leakage Encrypted memos
Cross-chain correlation Bridge batching, timing randomization
Disclosure accumulation over time Minimal disclosure default (Safeguard 2), per-session fresh binding

11.4 Bridge Security

Risk Mitigation
Bridge contract compromise Multi-sig governance of bridge liquidity only — never of note validity
Cross-chain replay Chain-specific context binding
Asset mismatch Strict asset ID verification
Aztec kernel upgrade Version-pinned circuit IDs

11.5 Nullifier Storage Requirements

Nullifier storage MUST provide atomic append with at-least-once delivery guarantees. Event-sourced stores (e.g. KurrentDB/EventStoreDB) satisfy this; the nullifier event log doubles as a compliance audit trail. In Aztec native deployments, Aztec's indexed nullifier tree provides these guarantees natively.

11.6 Post-Quantum Considerations (Draft)

BN254 discrete-log and the UltraHonk proving system are not post-quantum secure. A future revision will specify a PQ migration path (hash-based commitments are Poseidon2 and remain conjecturally PQ-resistant for collision; key derivation and proof system are the migration surface). This remains open work for a future revision.


12. Conformance

12.1 ZKA Conformance Levels

Level Requirements
ZKA-Core Transfer proofs, nullifier tracking, canonical Merkle append/root handling, note discovery, non-custodial key ownership, Safeguards 3 & 5
ZKA-Coord Core + Coord v2 profile-bound receipt, recursive DAG proof, verifier policy, and completed conformance gate (§4)
ZKA-Full Coord + viewing keys + selective disclosure
ZKA-MultiChain Full + Namada/Penumbra/Aztec bridges
ZKA-Compliant MultiChain + ZKC integration (§5), all six Safeguards
ZKA-Aztec Full Aztec native deployment plus an independently conforming manifest-bound Coord v2 Aztec profile

The ZKA-Coord level requires the exact soundness claim and evidence gate of §4. The reference profile is bounded to fan-in 4, depth 32, and 4096 unrolled proof nodes; no target may claim unbounded depth or infer conformance from host proof composition alone. Another proving mechanism can conform only through an explicit manifest-bound profile with equivalent receipt, workflow, policy, data-flow, completion, and adversarial-verification evidence.

Bridge-deploying conformance. Any conformance level claimed by a deployment that operates a ZKA Ethereum bridge MUST additionally deploy a zka_root_update verifier (§3.3) referenced by the bridge contract as VERIFIER_ROOTUPDATE, and the bridge MUST satisfy all six invariants of §7.1.

12.2 ZKC Integration Conformance Levels

ZKC conformance levels are owned by the ZKC Specification v0.4 §9.1. The core ladder is strictly monotonic — ZKC-CoreZKC-StandardZKC-Full (Full is the highest core level) — and ZKC-Agent is an orthogonal extension profile, not a level above ZKC-Full. ZKC-Core includes KYC proofs, the credential rebind circuit (zkc:proof:credential_rebind:v1), and basic verification. This spec does not re-declare a parallel Integration-* taxonomy; a deployment integrating ZKC MUST conform to the ZKC v0.4 §9.1 level it claims, and tooling MUST treat the level name as ZKC-namespaced rather than inferring rank from the ZKA taxonomy. The ZKA-side requirements that gate the ZKA-Compliant level (§12.1) are: shared key hierarchy (§2.2), identity binding circuit (§5.2), compliance bundle envelope (§5.4), and all six Freedom Safeguards (§1.6).

A deployment claiming ZKA-Compliant MUST satisfy all six Freedom Safeguards (§1.6); they are not gated by conformance level.

12.3 Test Vectors

Test vectors are provided in the companion zka-test-vectors repository, generated over the BN254 scalar field using Poseidon2. v0.5.0 circuit vectors remain valid for v0.6.0 (no field/hash change). New vectors are added for the identity binding and combined transfer+binding circuits.


Appendix A: UltraHonk Proof Format

interface UltraHonkProof {
  proof: Uint8Array;            // ~2 KB serialised proof
  publicInputs: Field[];        // Circuit-specific public inputs (BN254 field elements)
  verificationKey: Uint8Array;  // ~3 KB serialised VK
}

interface RecursiveProofInputs {
  proof: Field[];               // Proof as field array for std::verify_proof
  verificationKey: Field[];     // VK as field array
  publicInputs: Field[];
}

Appendix B: Poseidon2 Parameters (BN254)

Poseidon2 Configuration for ZKA
================================
Field:          BN254 scalar field
                (r = 21888242871839275222246405745257275088548364400416034343698204186575808495617)
State width:    4
Rate:           3 field elements
Capacity:       1 field element
Full rounds:    8
Partial rounds: 56
S-box:          x^5
Length IV:      message_length * 2^64 in state[3]
Library:        noir-lang/poseidon v0.3.0 Poseidon2::hash,
                backed by Noir/Aztec/Barretenberg Poseidon2

Appendix C: ZKA-ZKC Integration Provenance

The normative content of §5 is ported from docs/archive/zka_zkc_integration_v1_0_0.md (2026-01-19, Halo 2 era). Mapping:

v1.0.0 integration spec This spec
§2 Shared Key Hierarchy §2.2 (unchanged derivation)
§2.3 Binding Proof / §5.1 Identity Binding Circuit §5.2 (Halo 2 → UltraHonk/BN254)
§4.2 Compliance Bundle / verifyComplianceBundle §5.4 (ZKA envelope zka:bundle:v1 wrapping ZKC zkc:proof:v1 or zkc:proof:v2)
§5.2 Combined Transfer + Binding §5.3
§7 Conformance Levels §12.2 (defers to ZKC v0.4 §9.1)
§8 Roadmap (Phases 1–4) folded into §5.2–5.7 status markers

Freedom Safeguards (§1.6) incorporate the intent of the historical design memo docs/archive/zka_zkc_freedom_safeguards_v1_0_0.md (2026-04-01), which proposed embedding Safeguards 3 & 5 as ZKA invariants and 1, 2, 4 as integration invariants. The memo is retained for provenance only; this specification is the canonical, normative source for the current six-safeguard set. Structural requirements (§5.6–5.7, §11.6) are drawn from the ZKA/ZKC/APL investigation-priorities analysis (2026-03-26; tracking file since retired) and marked Draft where only the requirement (not the mechanism) is settled. The selective-confidentiality framing is from selective-disclosure-compliance-infrastructure.md (2026-05-13).

C.1 Reconciliation with ZKC v0.4

ZKC Specification v0.4 is the current ZKC integration target. It retains linkable zkc:proof:v1 and adds zkc:proof:v2 with anonymous and scoped presentation profiles. ZKA uses the same cbk = H("zkc/credential", masterSeed), scoped domain separator, anonymous zero sentinel, Poseidon2/BN254 field encoding, proof-type/version alignment, replay context, issuance, and revocation rules. ZKA adds only the note-ownership conjunction in zka_transfer_binding_v2; it does not redefine ZKC predicate verification.

ZKA-side reconciliation: zka:bundle:v1 wraps both ZKC envelope families and dispatches verification by the exact (envelopeVersion, presentationProfile, proofType) tuple. Payment bindings accept v1/linkable or v2/scoped only; payment-free work attestations may carry v2/anonymous because identity remains session/context-bound. The legacy v1 circuit and wire shape remain accepted unchanged.

Precedence: Per ZKC v0.4 §1.4, where ZKC and ZKA §1.6 differ in wording on a Freedom Safeguard, the safeguard intent as stated in ZKA §1.6 governs. ZKA §1.6 is the authoritative source for safeguard intent; ZKC mirrors it.

Previously-flagged ZKC-layer divergences — all resolved in ZKC v0.2.2:

  1. Canonical proof-type registry (§5.5). ✅ Resolved. ZKC v0.2.2 reframes Appendix A as a "Well-Known Identifier Registry (Non-Exclusive)" and §3.1–3.6 as reference (non-mandatory) schemas, with normative text that no type/provider is privileged or required and that the protocol defines no mechanism for a canonical set. Consistent with Safeguard 1.
  2. Open-Source Predicate mandate (Safeguard 4). ✅ Resolved. ZKC v0.2.2 §1.4 adds Safeguard 4 normatively (open-source/auditable circuits, versioned publication with public review period, SDK refusal of opaque predicates by default with logged opt-in). ZKC v0.2.2 §1.4 also adds Safeguard 2 (Minimal Disclosure).
  3. Conformance hierarchy semantics. ✅ Resolved. ZKC v0.2.2 §9.1 is now a strictly monotonic ladder (ZKC-CoreZKC-StandardZKC-Full, Full highest) with ZKC-Agent as an orthogonal extension profile, not a level above Full.

Notation reference (resolved by KDC). Both specifications reference AFP-KDC for hashing, domain separation, and seed-to-key derivation rather than a ZKA-versioned derivation. AFP-KDC v1.0.2 §A.3 resolves the former domain-tag prose ambiguity in favor of the deployed Blake2b-512 little-endian field-tag derivation; it is byte-for-byte compatible with the existing tag constants and vectors. The derivation facts cited above (cbk = H("zkc/credential", masterSeed), H("zkc/binding", cbk), Poseidon2/BN254) are defined by AFP-KDC §A.6 and are cited here, not redefined.

Credential rebind reference (added by ZKC v0.2.4, carried in ZKC v0.4). ZKC v0.4 §4.5 defines zkc:proof:credential_rebind:v1 as a ZKC-Core circuit that proves holder-controlled migration from oldHolderCommitment to newHolderCommitment without revealing either seed, either cbk, any ZKA address, any spending key, or payment activity. ZKA does not duplicate that circuit. ZKA's obligations are to keep note migration private via self-transfer batching (§5.7), keep any issuer-retained old→new holder-commitment mappings sealed and minimized (§5.7), and ensure rebind proofs never affect ZKA note validity, custody, transferability, withdrawal, nullifier semantics, or revocation semantics.


Document History

Version Date Changes
0.9.0-draft 2026-08-17 Corrected the legacy zka_root_update blob-binding claim: its non-zero blobKzgCommitment field is only a proof-bound tag, private blobCommitments are not constrained to blob contents, and the immutable Base APLBridge does not execute BLOBHASH. Successor blob-backed settlement requires both same-transaction BLOBHASH verification and a proof relation from canonical blob contents to the folded leaf list. Specification/comment correction only; no legacy circuit, verification key, deployed bytecode, or test-vector change.
0.9.0-draft 2026-08-15 Family epoch pins ZKM's cryptographic surface, not its revision label. Epoch zka:protocol-family:2026-08-15.2 adds four ZKM projection assertions over protocol-family/zkm-statement-registry-v1.json: its statement_set_id (a SHA-256 over ZKM's projected statement/retired-statement/revocation/toolchain surface, with prose fields excluded by construction), its last_breaking_revision, its family_epoch back-reference, and the registry's existence. The 2026-08-15.1 revision label discriminated one ZKM revision from another but pinned no verification key, public-input vector, bytecode digest, or toolchain; the digest does. The binding is bidirectional — ZKM's own gate requires the family's pinned label to be at least its last breaking revision. The family pins the registry's statement_set_id FIELD and deliberately not the file's digest, since the file carries prose that is not protocol surface. No ZKA circuit, hash, bundle, statement, ABI, or test vector changes.
0.9.0-draft 2026-08-15 §5.4.1 reconciled with ZKM rev 10. ZKM replaced its depth-128 low-half revocation tree with the depth-254 full-identifier exclusion tree and re-versioned both composed statements, so §5.4.1 now selects zkm:spend-conjoined:v2 over a 14-element vector (adding revocationScheme, which MUST equal 2, between revocationRoot and currentTime) and zkm:mandate-only:v2 over its 9-element vector. Adds the fail-closed statement-version boundary: a post-cutover registry MUST omit the 13-/8-field v1 statements and MUST NOT reinterpret, upgrade, or cross-verify their proofs, and only scheme-2 revocation roots at or after the ZKM activation epoch may be recognized. Reference boundaries contracts/ethereum/ZKMAtomicSettlement.sol and src/zka-sdk/src/zkm/mod.rs updated with the new element and a deterministic scheme-mismatch rejection. Published protocol-family epoch zka:protocol-family:2026-08-15.2, whose ZKM pin carries the revision label because ZKM's version string does not change between revisions. No ZKA circuit, hash, bundle, or test vector changes.
0.9.0-draft 2026-08-14 Adopted AFP-KDC v1.0.2 as the current normative authority for the byte-identical domain registry, Poseidon2 parameters, and seven-case raw-sponge KAT package. No derivation, domain field, circuit, proof, verification key, or afp-kdc-v1.0.1 artifact/profile identity changed. Published protocol-family epoch zka:protocol-family:2026-08-14.1.
0.9.0-draft 2026-08-12 Corrected all canonical derivation paths to AFP-KDC v1.0.1: unsigned big-endian seed reduction, seed-sibling cbk, and holderCommitment under zkc/binding. Corrected circuits, VKs, proofs, and verifier artifacts have explicit profile identities; legacy identities remain hash-pinned for custody migration.
0.1.0 2025-12-04 Initial specification
0.2.0 2025-12-23 Orchard-native design
0.3.0 2026-01-13 Settlement-agnostic redesign, L2 bridge reference
0.4.0 2026-01-19 Abstract settlement interface, credential binding key, Namada/Penumbra bridges, ZKC integration (stub)
0.5.0 2026-03-06 Noir/UltraHonk circuit layer (replaces Halo 2), BN254 field (replaces Pallas), Poseidon2, Aztec native deployment, updated circuit IDs, test vectors regenerated
0.6.0 2026-05-15 Normative ZKA/ZKC integration (§5: identity binding circuit, compliance bundle, combined circuit, predicate model, credential lifecycle, structural requirements); Freedom Safeguards as protocol invariants (§1.6); selective-confidentiality framing; post-quantum considerations. No circuit-layer or test-vector change vs v0.5.0. Reconciled with ZKC v0.2.2 (all three prior ZKC-layer divergences resolved; see Appendix C.1).
0.6.0 (clarif) 2026-05-26 Additive clarifications — no invariant, circuit-layer, or test-vector change. §1.4: Leo conformance is invariant-level, not value/proof-level. §4.1: Leo/Aleo deployment carve-out (no in-circuit recursion → Varuna call-graph composition; scoped Coord; see docs/sdd/sdd_coord_aleo.md). §12.1: ZKA-Coord is mechanism/environment-dependent — new tooling MUST NOT re: §9.2 unbounded recursive depth on backends lacking in-circuit recursion. Whether to bump to v0.6.1 is a separate maintainer decision (see tracking req req_zka_spec_ratify_leoaleo_conf_d6406b19).
0.6.1 2026-06-04 Key derivation relocated to the Key Derivation Core (KDC v1.0.0, AFP Specification Appendix A) and referenced normatively from §2.1–§2.2; §5.2 in-circuit derivation annotated as KDC-defined; §1.3 records the ZKA→AFP-KDC dependency; Appendix C.1 records the KDC notation reference. Relocation only — KDC v1.0.0 is behaviorally identical to v0.6.0 §2.2; no circuit, field, hash, or test-vector change; existing zka-test-vectors seed→key vectors remain normative.
0.6.2-draft 2026-06-05 §5.4: optional coord_proof member added to zka:bundle:v1 — carries a recursive ZKA/Coord work-attestation proof (§4) + its public inputs (workflowId, inputCommitments, outputCommitment, stepIndex, context) alongside the existing transaction/compliance/binding proofs; verifyComplianceBundle gains an optional step 6 (recursive verification + context == metadata.context binding). Backward-compatible and additive: the member is OPTIONAL, existing bundles are unaffected, and the envelope version string stays zka:bundle:v1 (no envelope-version bump). Satisfies the ZKA-side follow-up flagged in AFP v0.3.1 §12.7 (ADR-0003) and supersedes VXP's vxp:coordbundle:v1. No circuit-layer, field, hash, or test-vector change — the ZKA/Coord circuit (§4) is unchanged; only the bundle envelope gains an optional carriage slot.
0.6.2-draft 2026-06-05 §5.4: payment transaction member made OPTIONAL (and binding optional, present IFF transaction is) — a valid bundle MUST carry at least one of {transaction, coord_proof} (an EMPTY_BUNDLE is invalid); compliance proofs only accompany a substantive proof. Adds the pure work-attestation bundle profile (coord_proof, no payment) for the non-monetary federation flows of AFP §12.7 / ADR-0003 — PRP reconciliation and non-monetary VXP vxp.reciprocal — which carry evidence over zka:bundle:v1 with no payment leg. verifyComplianceBundle verifies each member IFF present and gates the note↔︎credential consistency checks on transaction presence; when transaction is absent the note↔︎credential binding is N/A and identity is anchored over the present proofs via the shared context (which AFP equates to the envelope session_context) — no new crypto. Version-string decision: the envelope string stays zka:bundle:v1 — this is a relaxation of a still-draft, pre-release envelope (no released consumer pins a required transaction), existing payment bundles remain valid byte-for-byte, and a transaction-absent bundle is simply a new instance shape; a v2 bump would falsely signal a breaking wire change. Backward-compatible; no circuit-layer, field, hash, or test-vector change.
0.6.2-draft 2026-06-06 §5.7: Note Discovery Protocol finalized from Draft placeholder using an append-only encrypted note announcement log with optional viewing tags; recipients discover notes with incoming viewing keys and indexers remain non-authoritative. §1.6 / §5.7: Agent Key Custody Model added as a non-custodial protocol invariant: ZKA conformance cannot require platform custody of masterSeed, spending keys, or note plaintext; hosted custody belongs to separate deployment/product profiles. No circuit-layer, field, hash, or test-vector change.
0.6.2-draft 2026-06-06 §2.4: Merkle Tree State Management finalized with canonical append source, deterministic leaf ordering, commitment append events, accepted root windows, stale-root errors, and non-authoritative indexer/client reconstruction. No circuit-layer, field, hash, or test-vector change.
0.6.2-draft 2026-06-06 §5.4: context binding comment generalized from the payment-verifier shape to a deployment/profile-defined replay context; under AFP it equals afp:envelope:v1.session_context. Adds explicit note that the AFP §12.7 ZKA-side dependency is resolved by the optional coord_proof member. No circuit-layer, field, hash, or test-vector change.
0.6.2-draft 2026-06-06 §5.7: Key Rotation / Recovery requirements refined: SDKs must provide bucketed rotation batching policies, and ZKA deployments using issuer-assisted credential rebinding must require sealed/minimized issuer audit mappings for old→new holder-commitment links. No circuit-layer, field, hash, or test-vector change.
0.6.3-draft 2026-06-08 §3.3 (NEW): Root Update Circuit (zka_root_update) — public/private inputs, six normative constraints; §7.1 promoted to a normative subsection with six conforming-bridge invariants (ownerless, immutable config, append-only state, root-update SNARK, permissionless force-exit, EIP-4844 blob DA); §9.3 adds ROOT_UPDATE_V1 circuit identifier; §9.4 adds zka_root_update/ package; §12.1 adds bridge-deploying conformance clause. No change to existing circuits, field, hash, or test vectors.
0.6.4-draft 2026-06-10 §7.1 standalone force-exit normatively specified (closes the bridge-state/proof-semantics gap of investigation item 3): §7.1.1 bridge-held state enumeration (append log, accepted root set + immutable window config, nullifier set, immutable/version-pinned verifier references, per-asset escrow with solvency invariant, deployment context); §7.1.2 force-exit = ordinary zka_withdrawal proof with fixed verification order, effects-before-interactions nullifier recording, no challenge period for pure shielded-pool exits, permissionless caller model; §7.1.3 root publication model (append + root-update events; full reconstruction from L1 history); §7.1.4 machine-readable failure-code taxonomy with retryable/terminal classes (stale root retryable, never invalidates a note); §7.1.5 client-retained witness data sufficient to exit with all operator services down; §7.1.6 Aztec Outbox/portal path explicitly separate (ADR-0002). ZKC v0.2.4 alignment: §5.7 now references the ZKC-Core credential rebind circuit (zkc:proof:credential_rebind:v1), treats issuer migration tokens as optional optimizations, and preserves sealed/minimized issuer audit mappings as the ZKA-side privacy requirement. No ZKA circuit-layer, field, hash, or test-vector change.
0.6.4-draft 2026-06-12 Aleo/Leo target retired (ADR-0009). Removed the §1.4 Leo conforming-target language, the "aleo" SettlementType/AssetId variant (§2.1), the §4.1 Leo/Aleo coordination carve-out, and Aleo mentions in §1.6 Safeguards 4–5, §5.4, §5.7, §12.1. Aleo design docs (sdd_leo_pay.md, sdd_bridge_aleo.md, sdd_coord_aleo.md) archived under docs/archive/. Implementation tree drops the leo/ programs, the Aleo finality oracle + EVM claim anchor, the [aleo] settlement-ops chain kind, and the aleo registry/certification entries. No circuit-layer, field, hash, public-input, or test-vector change.
0.6.4-draft 2026-06-10 Namada/Penumbra bridge implementation boundary aligned with completed Rust/SDK work: §7.2–§7.3 identify the active Noir package names (bridge_namada_*, bridge_penumbra_*) while preserving the existing circuit IDs; §7.2 documents src/zka-bridge-namada plus src/zka-sdk/src/bridges/namada.rs as the Namada integration boundary around UltraHonk/BN254 artifacts; §7.3 documents the fresh src/zka-bridge-penumbra crate plus SDK adapter and explicitly excludes the removed legacy Halo2/Pallas crate path. §9.4 updates the Noir project tree. No circuit-layer, field, hash, public-input, circuit-ID, or test-vector change.
0.7.0 2026-06-13 Added Safeguard 6 (Verifier Accountability) to §1.6 as a normative invariant and audit-table row — the first safeguard constraining the counterparty rather than the operator, orthogonal to operator-blindness. The mechanism is specified normatively on the ZKC side (ZKC §2.2 purpose-bound proof context, §5.5 Authenticated Proof Request, §5.6 holder-custodied Disclosure Receipt, §5.7–§5.8); ZKA records the invariant and extends the non-custody clause to receipts. No change to notes, bundles, circuits, fields, hashes, the zka:bundle:v1 envelope, or any test vector.
0.7.1-draft 2026-06-15 §8.6 (NEW): Relationship to Aztec Primitives — documents the delegation boundary (Aztec note hash tree, indexed nullifier tree, kernel-circuit recursion, key hierarchy, and the shared Honk/BN254/Poseidon2 substrate provide ZKA's §2–§4 machinery in the native deployment) versus the additive layer with no Aztec-native equivalent (ZKC compliance §5, Freedom Safeguards §1.6, cross-environment compatibility §8.5, settlement-rail independence). Clarifies §8.5 as a circuit/field/hash-level compatibility contract, not a one-proof-verifies-in-both claim. §7.4 (NEW): Operator-free external shielded-pool settlement (deployment note) — a deployment MAY delegate settlement to an external immutable operator-free shielded pool (e.g. Railgun) holding a non-freezable asset (e.g. Liquity BOLD/LUSD); ZKA's §3 circuits do not run, ZKA contributes the additive Coord/ZKC/AFP layer off-chain, identity binds at the session/context level (§5.4). Reflects the resolved ADAMAS settlement architecture (APL ADR-0011/0012); the §8.6 delegation principle applied to a non-ZKA-native host. Descriptive / deployment-layer; no circuit-layer, field, hash, envelope, or test-vector change. Also retargets ZKC cross-references from v0.2.4 to the co-published v0.3.0 (incl. Appendix C.1 reconciliation, bilateral Safeguard-6 alignment).
0.8.0-draft 2026-07-11 Makes the immutable Base WETH9 ZKA/Pay single-asset pool (§3.4) the flagship ADAMAS APL 0.4 production direction. AFP owns USD obligation pricing and signs the exact wei quote; ZKA/Pay adds no repricing oracle or stablecoin authority. Additional assets require separate immutable pools and explicit risk profiles; a native stable unit remains independent future work. §7.4 is retained as an optional external-host mode but no longer describes the resolved ADAMAS architecture. No existing circuit identifier, public-input shape, hash, bundle, or published test vector changes.
0.8.1-draft 2026-08-04 Adds the informative ZKM authorization/settlement boundary and states explicitly that current zka:bundle:v1 has no ZKM carriage member. ZKM remains optional and cannot affect ZKA note validity, custody, transferability, or unconditional exit. Future bundle carriage and atomic settlement remain separately tracked integration work. No circuit, contract, field, hash, envelope, or test vector changes.
0.9.0-draft 2026-08-05 Makes ZKA/Coord v2 the canonical pre-launch coordination draft, explicitly unaudited until its signed independent-review gate passes; defines its fixed execution receipt, verifier-selected profile policy, manifest/VK and assurance binding, sound predecessor data-flow/DAG semantics, final recursive ABI, deterministic errors, and evidence gate; removes the pre-launch artifacts, registry/API surface, and five-field bundle object; keeps public Rust SDK names unversioned and security identities explicitly v2; leaves zka:bundle:v1 independently versioned.
0.9.0-draft 2026-08-05 §5.4.1: ZKM direct authorization settlement. Selects zkm:spend-conjoined:v1 direct settlement rather than extending zka:bundle:v1; fixes the 13-field ABI and context/asset/root/clock/VK checks; requires one mandate-nullifier set across spend-conjoined and mandate-only VKs; atomically commits both nullifiers, one mandate successor, and two ZKA outputs; keeps mandate-only VKs structurally unable to move value and keeps withdrawal/force-exit independent of mandate revocation. Adds Solidity and Rust reference boundaries with success, rollback, mismatch, cross-VK replay, mandate-only, and unconditional-exit tests. No existing bundle, ZKA circuit, hash, or test vector changes.
0.9.0-draft 2026-08-05 Adopts ZKC v0.4 presentation profiles in zka:bundle:v1: preserves linkable v1, adds anonymous/scoped v2 carriage and deterministic verifier selection, requires scoped v2 for payment bindings, and adds zka_transfer_binding_v2 so note ownership and credential control share a private master seed without exposing holderCommitment.

License

This specification is released under the Apache 2.0 License.

Patent Non-Assertion Covenant

The Apache 2.0 patent grant (License §3) covers patent claims necessarily infringed by a contribution alone or by its combination with the Work it was submitted to. An independent implementation of this specification — one written from the specification text without deriving from any reference code — falls outside that scope, and so is not clearly covered by that grant. The following covenant, modeled on the Open Web Foundation Agreement (OWFa) 1.0 non-assert and the patent commitment of the Community Specification License 1.0, closes that gap. By submitting a contribution to this specification (for example, a pull request modifying this document), each contributor accepts and makes this covenant for every version of this specification that incorporates its contribution.

Each contributor to this specification irrevocably covenants, on behalf of itself and its successors and assigns, not to assert any Essential Claims against any party for making, having made, using, selling, offering for sale, importing, or distributing a Conformant Implementation of this specification. Essential Claims are patent claims owned or controlled by the contributor — now or in the future, including claims later acquired — that are necessarily infringed by implementing the required portions (those designated MUST, REQUIRED, or SHALL) of a version of this specification to which the contributor contributed. "Necessarily infringed" follows the W3C Patent Policy definition of Essential Claims: it applies only where no commercially reasonable non-infringing alternative exists, and it excludes claims covering implementation choices this specification leaves open and technologies this specification merely references rather than describes in detail. A Conformant Implementation is any implementation that conforms to those required portions — whether or not it derives from any contributor's code or specification text; where an implementation forms part of a larger work, this covenant extends only to the portions that so conform. This covenant is royalty-free and worldwide, and supplements — never limits — the rights granted under the Apache 2.0 License.

This covenant runs with the Essential Claims: it is intended to bind any future owner, assignee, or exclusive licensee that acquires the right to enforce them, and a contributor that transfers a patent containing Essential Claims satisfies this obligation by notifying the transferee of this covenant. The covenant is suspended with respect to any party that asserts a patent infringement claim alleging that a Conformant Implementation of this specification infringes that party's patents (excluding claims brought defensively in response to a prior such assertion by a contributor or any successor or assignee bound by this covenant), for as long as that assertion is maintained.


ZKA: Provable confidentiality for autonomous agents.