← Back to Home |

Protocol Specification

Version v1.0

Exqub Protocol Specification v1.0

Version v1 Normative Specification

Exqub Protocol Specification v1.0

Copyright (c) 2026 VSPRY INTERNATIONAL LIMITED (ABN 59 631 026 027). All rights reserved.
Exqub is a trading name of VSPRY INTERNATIONAL LIMITED.

This Exqub Protocol Specification is licensed under the Creative Commons Attribution 4.0 International License. To view a copy of this license, visit http://creativecommons.org/licenses/by/4.0/ or send a letter to Creative Commons, PO Box 1866, Mountain View, CA 94042, USA.

0. Licensing

  • This Exqub Protocol Specification is freely implementable by anyone under Creative Commons Attribution 4.0 International (CC BY 4.0).
  • The SDKs, application crates, and tooling are Apache 2.0 with no restrictions.
  • The core cryptographic engine is dual-licensed — free for integrators under Apache 2.0, commercial only if you're building competing credential infrastructure under Exqub Commercial Licence (ECL).

NORMATIVE SPECIFICATION — V1.0 includes standard credentials (0x01), delegation credentials (0x02), chain linking (via attribute conventions), and content attestation (0x04)

1. Conventions

This document uses RFC 2119 / RFC 8174 key words (MUST, MUST NOT, SHALL, SHOULD, MAY) when in capitals. All sections are normative unless marked otherwise. This specification is authoritative for Exqub v1.0; in case of conflict with other documents, this specification takes precedence.

All multi-byte integers MUST be encoded as unsigned big-endian (network byte order) unless otherwise stated. This applies to length prefixes, timestamps, counters, CBOR integers, tree indices, and all other protocol integer values.

2. Scope

2.1 Included

  • ML-DSA-65 signatures only (FIPS 204, quantum-resistant)
  • SHA3-256 hashing only (all protocol operations)
  • Single canonical signing format
  • Attribute Merkle tree with selective disclosure
  • Sparse Merkle Tree (SMT) revocation registry
  • Device co-signatures (ML-DSA-65)
  • Proximity attestation (optional)
  • Deterministic CBOR encoding (RFC 8949 §4.2)
  • Bounded types for no_std core engine (heap optional for application layer)
  • Delegation credentials with scoped authority and sub-delegation (credential_type 0x02)
  • Chain-linked credentials for tamper-evident ordered logs (via reserved attribute conventions)
  • Content attestation credentials for document provenance (credential_type 0x04)

2.2 Excluded (Explicit Non-Goals)

  • Dual-root modes, alternate roots
  • Alternative hash functions (BLAKE3) or signature algorithms (SLH-DSA, Ed25519)
  • Dynamic crypto suite selection
  • SD-JWT/OIDC bridges
  • EVM smart contract variations
  • Unbounded/heap-allocated containers in core engine
  • Multi-party computation
  • Attribute modification without reissuance

2.3 Future Considerations (Non-Normative)

The following capabilities are out of scope for V1.0 and may be addressed in future versions:

  • Threshold credentials (k-of-n multi-party issuance)
  • Conditional disclosure (predicate proofs, e.g., age > 18 without revealing age)
  • Blind signatures
  • Formal verification proofs (Lean 4)
  • Language tags / internationalization metadata

3. Constants

3.1 Cryptographic Sizes

Constant Value Source
ML-DSA-65 Public Key 1952 bytes FIPS 204
ML-DSA-65 Private Key 4032 bytes FIPS 204
ML-DSA-65 Signature 3309 bytes FIPS 204
SHA3-256 Output 32 bytes FIPS 202
Salt Size 32 bytes
Nonce Size 32 bytes
Domain Separator Size 16 bytes

3.2 Limits

Constant Value Notes
MAX_ATTRIBUTES 64 Maximum attributes per credential
MAX_TREE_DEPTH 8 Safety bound: ceil(log2(64)) + 2. Actual proof depth = log2(next_power_of_2(attr_count))
MAX_SMT_PROOF_DEPTH 256 Supports 2^256 credentials
MAX_CBOR_DEPTH 16 DoS protection
MAX_CBOR_MAP_ENTRIES 128
MAX_CBOR_ARRAY_LENGTH 256
MAX_CBOR_BYTE_STRING 16384 bytes
MAX_CBOR_TEXT_STRING 1024 bytes
MAX_ATTRIBUTE_KEY_LENGTH 64 bytes UTF-8
MAX_STRING_LENGTH 1024 bytes UTF-8
MAX_CREDENTIAL_SIZE 16384 bytes
MAX_PRESENTATION_SIZE 32768 bytes
CORE_ENGINE_STACK 4096 bytes Core crypto operations only (see note)
MAX_DELEGATION_DEPTH 5 Hard cap; prevents deep chains expensive to verify (absolute max is 10)
MAX_SCOPE_ACTIONS 32 Bounded for no_std heapless compatibility
MAX_SCOPE_RESOURCES 64 Bounded for no_std heapless compatibility
MAX_CHAIN_LENGTH 1,000,000 Advisory ceiling — verification is O(n); operational limits SHOULD be lower. Not enforced by the core engine; enforced at the application layer

3.3 Timing

| Constant | Value | | ------------------------ | --------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | DEFAULT_CLOCK_SKEW | 300 seconds | | HIGH_SECURITY_CLOCK_SKEW | 60 seconds | | MAX_CLOCK_SKEW | 600 seconds | | MAX_CREDENTIAL_LIFETIME | 31536000 seconds (365 days) | | MIN_REPLAY_CACHE_TTL | 900 seconds | | MAX_REPLAY_CACHE_TTL | 86400 seconds | | MAX_SMT_ROOT_AGE | 604800 seconds (7 days) | | MIN_DELEGATION_LIFETIME | 60 seconds | Prevents nonsensical sub-second delegations | | MAX_DELEGATION_LIFETIME | 86,400 seconds (24h) | For ephemeral sub-delegations; root delegations MAY use MAX_CREDENTIAL_LIFETIME (365 days). Advisory for root; SHOULD enforce for sub-delegations |

3.4 Status Values

Value Meaning
0x00 STATUS_VALID
0x01 STATUS_REVOKED
0x02 STATUS_SUSPENDED

3.5 Credential Type Values

Value Meaning Notes
0x01 Standard credential Original V1 credential type
0x02 Delegation credential Scoped authority with sub-delegation support
0x03 Reserved Held for future use
0x04 Content attestation credential Document provenance and integrity

Design decision: Chain linking does NOT have its own credential_type. Chain linking attributes (chain_id, chain_seq, chain_prev) can appear on ANY credential type (0x01, 0x02, 0x04). Chains are detected by attribute presence, not by credential_type. Type 0x03 is reserved for future use.

Exhaustion guard: credential_type is a u8 (255 usable values, 0x00 unused). Values 0x05–0xFF are reserved for future assignment and MUST be rejected by V1 parsers. Adding a new credential type requires a spec revision.

3.6 Protocol Version

PROTOCOL_VERSION = 0x01

3.7 Relationships

  • MAX_TREE_DEPTH = ceil(log2(MAX_ATTRIBUTES)) + 2
  • Tree Size = next_power_of_2(attr_count), where attr_count ≤ MAX_ATTRIBUTES
  • Proof Path Length = log2(Tree Size) — this is the ACTUAL depth used in proofs, NOT MAX_TREE_DEPTH
  • Verifiers MUST use the actual mathematical depth, not the safety bound

3.8 Performance Boundaries (Non-Normative)

Note: These are quality targets, not requirements. Actual performance depends on hardware capabilities.

Operation Target Time Notes
ML-DSA-65 Sign < 50ms Hardware-dependent
ML-DSA-65 Verify < 100ms Hardware-dependent
Full Presentation Verify < 200ms Complete verification flow
Merkle Proof Verify < 1ms Hashing operations only
SMT Proof Verify < 5ms Hashing operations only

Note on CORE_ENGINE_STACK: The 4096-byte limit applies to individual cryptographic operations (hashing, proof verification, etc.) in no_std environments. Full presentation parsing including ML-DSA signatures (3309 bytes each) requires additional stack space. Implementations MUST ensure deterministic memory usage appropriate for their target environment.

4. Domain Separators

All domain separators are exactly 16 bytes, defined as compile-time byte literals.

Name Bytes (ASCII) Used In
EXQUB_ISSUER_V1 EXQUB_ISSUER_V1_ Issuer ID derivation
EXQUB_CRED_ID_V1 EXQUB_CRED_ID_V1 Credential ID generation
EXQUB_SIG_V1 EXQUB_SIG_V1____ Credential signature input
EXQUB_ATTR_LEAF_V1 EXQUB_ATTR_LEAF_ Attribute leaf hash
EXQUB_ATTR_NODE_V1 EXQUB_ATTR_NODE_ Attribute tree node hash
EXQUB_ATTR_PAD_V1 EXQUB_ATTR_PAD__ Padding leaf hash
EXQUB_SMT_EMPTY_V1 EXQUB_SMT_EMPTY_ SMT empty subtree
EXQUB_SMT_NODE_V1 EXQUB_SMT_NODE__ SMT internal node hash
EXQUB_SMT_LEAF_V1 EXQUB_SMT_LEAF__ SMT leaf hash
EXQUB_DEV_BIND_V1 EXQUB_DEV_BIND__ Device co-signature binding
EXQUB_DEV_KEY_V1 EXQUB_DEV_KEY_V1 Device key hash
EXQUB_PROX_PROOF_V1 EXQUB_PROX_PROOF Proximity attestation
EXQUB_PRES_HASH_V1 EXQUB_PRES_HASH_ Presentation hash
EXQUB_HOLDER_V1 EXQUB_HOLDER_V1_ Holder ID derivation
EXQUB_REV_SNAP_V1 EXQUB_REV_SNAP__ Revocation snapshot signature
EXQUB_REPLAY_KEY_V1 EXQUB_REPLAY_KEY Replay cache key
EXQUB_DELEG_V1 EXQUB_DELEG_V1__ Delegation credential sig_input
EXQUB_SCOPE_V1 EXQUB_SCOPE_V1__ Scope constraint hash
EXQUB_ACTION_V1 EXQUB_ACTION_V1_ Action request hash
EXQUB_SUBDEL_V1 EXQUB_SUBDEL_V1_ Sub-delegation sig_input
EXQUB_CHAIN_V1 EXQUB_CHAIN_V1__ Chain ID derivation

Requirements: Implementations MUST define these as raw byte constants (e.g., b"EXQUB_ISSUER_V1_"). MUST NOT construct at runtime, concatenate from parts, or null-terminate. Implementations MUST include compile-time tests verifying all 21 separators are exactly 16 bytes and pairwise unique.

5. Cryptographic Requirements

5.1 ML-DSA-65 Requirements

Algorithm: ML-DSA-65 (FIPS 204, Category 3, NIST Security Level 3)

Signing Modes:

  • Issuer backend: MUST use deterministic signing (empty context, no randomization, no pre-hashing)
  • Device co-signature: SHOULD use randomized signing (countermeasure against side-channel attacks on TEE hardware)

Common Parameters: Empty context string. No pre-hashing — sign the 32-byte sig_input directly.

Error Handling: Invalid key format → MUST return error, MUST NOT attempt recovery. Signing failure → MUST return error, MUST NOT retry. Verification failure → MUST return false, MUST NOT accept partial matches.

5.2 Cryptographic Library Requirements

Compliance: MUST use FIPS 204 compliant implementation. Side-channel resistance required for device operations.

RNG Requirements: Implementations MUST use a cryptographically secure random number generator provided by the platform. Entropy source MUST provide at least 256 bits of entropy. Salt generation MUST use fresh randomness for each attribute.

Key Generation: MUST occur in secure hardware when available (HSM, TEE, Secure Enclave). Private keys MUST NOT be exported from secure storage. Key material MUST be zeroized after use.

Library Validation: Implementations SHOULD use validated cryptographic libraries (FIPS 140-3 or equivalent). MUST pass all NIST test vectors for ML-DSA-65 and SHA3-256.

6. Data Structures

6.1 CredentialV1

CredentialV1 {
    version: u8,              // MUST be 0x01
    credential_type: u8,      // MUST be 0x01 (Standard credential)
    credential_id: [u8; 32],  // See §7.2
    issuer_id: [u8; 32],      // See §7.1
    holder_id: [u8; 32],      // See §7.4
    issued_at: u64,           // Unix timestamp
    expires_at: u64,          // Unix timestamp
    attr_count: u32,          // Number of actual attributes (≤64)
    attr_root: [u8; 32],      // Attribute Merkle tree root
}

SignedCredential {
    credential: CredentialV1,
    signature: [u8; 3309],    // ML-DSA-65 over sig_input (§7.3)
}

Boundary Conditions: attr_count = 0 is prohibited. issued_at MUST be < expires_at. credential_type MUST be 0x01 for standard credentials (see §3.5 for all valid types).

6.2 PresentationV1

PresentationV1 {
    credential: SignedCredential,
    nonce_v: [u8; 32],                                          // Verifier challenge nonce
    verifier_id: [u8; 32],                                      // Verifier identifier
    presentation_timestamp: u64,                                 // Unix timestamp
    disclosed_attributes: BoundedVec<DisclosedAttribute, 64>,
    smt_proof: SmtInclusionProof,
    device_signature: DeviceSignature,
    proximity_attestation: Option<ProximityProofData>,           // Optional
}

Disclosure Boundaries: May disclose 0 attributes (proof of possession only) up to attr_count. Disclosing all attributes is valid but defeats selective disclosure purpose.

6.3 DisclosedAttribute

DisclosedAttribute {
    leaf_index: u32,                                // Position in sorted attribute tree (0-based)
    key: BoundedString<64>,                         // Attribute key
    value: BoundedString<1024>,                     // Attribute value (UTF-8)
    salt: [u8; 32],                                 // Per-attribute random salt
    merkle_proof: BoundedVec<MerkleProofNode, 8>,   // Sibling hashes, leaf to root
}

MerkleProofNode {
    sibling_hash: [u8; 32],
}

Direction at each level is inferred from leaf_index: even index = left child, odd index = right child. No explicit direction field.

6.4 SmtInclusionProof

SmtInclusionProof {
    smt_root: [u8; 32],
    siblings: BoundedVec<SmtSibling, 256>,  // Sparse — only non-empty siblings
    sibling_count: u8,
    leaf_status: u8,                        // STATUS_VALID, STATUS_REVOKED, STATUS_SUSPENDED
}

SmtSibling {
    depth: u8,              // 0–255
    sibling_hash: [u8; 32],
}

Siblings MUST be sorted strictly ascending by depth. No duplicate depths. sibling_count MUST equal the array length. Implementations MUST reject proofs violating these constraints before any cryptographic operations.

6.5 DeviceSignature

DeviceSignature {
    device_public_key: [u8; 1952],  // ML-DSA-65 public key
    signature: [u8; 3309],          // ML-DSA-65 signature over device_sig_input (§7.7)
}

No timestamp field — the presentation_timestamp serves this purpose.

6.6 ProximityProofData

ProximityProofData {
    proof_hash: [u8; 32],
    proximity_timestamp: u64,
    proximity_nonce: [u8; 32],
    observer_device_pubkey_hash: [u8; 32],
}

6.7 RevocationSnapshotV1

RevocationSnapshotV1 {
    issuer_id: [u8; 32],
    epoch: u64,             // Monotonic counter — prevents rollback
    smt_root: [u8; 32],
    issued_at: u64,
    signature: [u8; 3309],  // ML-DSA-65 over snapshot_sig_input (§7.8)
}

6.8 DelegationCredentialV1

DelegationCredentialV1 {
    // Base fields (same field names as CredentialV1 §6.1; separate struct — see implementation note)
    version: u8,                        // MUST be 0x01
    credential_type: u8,                // MUST be 0x02 (Delegation)
    credential_id: [u8; 32],
    issuer_id: [u8; 32],
    holder_id: [u8; 32],               // The delegated agent's identity
    issued_at: u64,                     // Unix timestamp
    expires_at: u64,                    // Unix timestamp
    attr_count: u32,                    // Number of agent attestation attributes (may be 0)
    attr_root: [u8; 32],               // Attribute Merkle tree root (§8)

    // Delegation-specific fields
    delegator_credential_id: [u8; 32], // Parent credential ID (all zeros for root delegation)
    delegation_depth: u8,               // 0 = root; max MAX_DELEGATION_DEPTH
    max_delegation_depth: u8,           // Ceiling for sub-delegation (≤ MAX_DELEGATION_DEPTH)
    scope_hash: [u8; 32],              // SHA3-256 of canonical CBOR of ScopeConstraints (§7.16)
}

SignedDelegationCredential {
    credential: DelegationCredentialV1,
    signature: [u8; 3309],             // ML-DSA-65 over deleg_sig_input (§7.15)
}

Boundary Conditions:

  • delegation_depth MUST be ≤ max_delegation_depth MUST be ≤ MAX_DELEGATION_DEPTH (5)
  • delegator_credential_id MUST be all zeros iff delegation_depth == 0
  • delegator_credential_id MUST NOT be all zeros iff delegation_depth > 0
  • issued_at MUST be < expires_at
  • attr_count MAY be 0 (no agent attestation attributes required)

Implementation note: DelegationCredentialV1 is a separate struct, not a wrapper around CredentialV1. The CBOR wire format places all fields (base + delegation-specific) in a single canonical map with interleaved keys per §12.5 canonical ordering. A wrapper struct would complicate this serialisation. Common logic is extracted into helper functions.

6.9 ScopeConstraints

ScopeConstraints {
    actions: ActionVec,                  // Permitted action identifiers (heapless::Vec<AttributeKey, 32> / Vec<String>)
    resource_patterns: ResourceVec,      // Permitted resource patterns (heapless::Vec<ResourcePattern, 64> / Vec<String>)
    max_value: Option<u64>,              // Per-action monetary limit (e.g., cents)
    max_daily_value: Option<u64>,        // Aggregate daily monetary limit
    max_actions_per_hour: Option<u32>,   // Rate limit
    time_window: Option<TimeWindow>,     // Permitted time range
    required_attestations: AttestationVec, // Required agent attestation keys (heapless::Vec<AttributeKey, 16> / Vec<String>)
}

TimeWindow {
    start_hour: u8,     // 0–23 UTC
    end_hour: u8,       // 0–23 UTC
    days_of_week: u8,   // Bitmask: bit 0 = Monday, bit 6 = Sunday
}

Optional field encoding: Option::None fields MUST be omitted from the CBOR map entirely (not encoded as CBOR null). This matches §12.4. The scope_hash is computed over the canonical CBOR of present fields only.

Array ordering: actions and resource_patterns arrays MUST be sorted lexicographically (UTF-8 byte order) before encoding. This ensures scope_hash is deterministic regardless of insertion order.

Scope attenuation: A child ScopeConstraints S' is a valid attenuation of parent S iff S' ⊆ S:

  • actions: S'.actions ⊆ S.actions (exact string match after NFC normalisation)
  • resource_patterns: S'.resource_patterns ⊆ S.resource_patterns (exact string match — patterns are opaque strings, not globs; application-layer interpretation is outside the protocol)
  • max_value — Monotonic narrowing rule:
    • Parent None, child None → valid (both unlimited)
    • Parent None, child Some(y) → valid (narrowing)
    • Parent Some(x), child Some(y) where y ≤ x → valid (narrowing or equal)
    • Parent Some(x), child None → INVALID (would widen scope)
    • Parent Some(x), child Some(y) where y > x → INVALID (would widen scope)
  • max_daily_value: Same monotonic narrowing rule as max_value
  • max_actions_per_hour: Same monotonic narrowing rule as max_value
  • time_window: S'.time_window ⊆ S.time_window (start_hour ≥, end_hour ≤, days_of_week is bitwise subset). Parent None means unrestricted; child None when parent is Some is INVALID
  • required_attestations: S'.required_attestations ⊇ S.required_attestations (child may add requirements, never remove)

Rationale for monotonic narrowing: Once a limit is set in a parent delegation, all children must preserve or tighten it. If a parent sets max_value = Some(5000) and a child omits it (None = "no limit"), the child would have broader authority than the parent — this is a scope widening attack.

6.10 ActionRequest

ActionRequest {
    action: AttributeKey,               // heapless::String<64> / String
    resource: ResourcePattern,           // heapless::String<256> / String
    value: Option<u64>,                  // Monetary value of the action (None if not applicable)
    timestamp: u64,                      // Unix timestamp of the request
    request_nonce: [u8; 32],            // CSPRNG nonce — prevents action replay
}

New collection type: ResourcePattern = heapless::String<256> / String, following the existing AttributeKey/AttributeValue pattern in collections.rs.

6.11 DelegatedActionPresentation

DelegatedActionPresentation {
    delegation_chain: DelegationChainVec, // heapless::Vec<SignedDelegationCredential, 6> / Vec<...>
    action_request: ActionRequest,
    scope_constraints: ScopeConstraints,  // The leaf agent's scope (convenience — authoritative is scope_hash)
    presentation: PresentationV1,         // Standard V1 presentation from the agent (§6.2)
}

New collection type: DelegationChainVec = heapless::Vec<SignedDelegationCredential, 6> / Vec<...> in collections.rs. Max 6 = MAX_DELEGATION_DEPTH + 1 (root credential + up to 5 sub-delegation levels).

scope_constraints trust model: The scope_constraints field is included for the verifier's convenience (display/logging of permitted scope). The authoritative scope is the scope_hash in the signed delegation credential. Verifiers MUST compute compute_scope_hash(presented_scope_constraints) and compare it (constant-time) to the leaf credential's scope_hash before trusting the presented constraints (see §13.3 step 5). A presenter who modifies the scope constraints will fail this hash check.

6.12 Chain Linking Attributes

Chain linking is implemented as reserved attribute keys on any credential type (0x01, 0x02, 0x04). No separate credential_type is used.

Attribute Key Format Description
chain_id hex string, 64 chars (SHA3-256 in hex) Identifies the chain (§7.19)
chain_seq decimal string, "1"-based Sequence number within the chain
chain_prev hex string, 64 chars SHA3-256 of previous credential CBOR (§7.20); all zeros for first entry

Semantics: A credential is a chain member iff it contains all three attributes. These attributes MAY appear on any credential type.

First-entry convention: chain_prev = 64 zeros (hex), chain_seq = "1".

Chain membership detection: Verifiers detect chain membership by attribute presence, not by credential_type.

6.13 Content Attestation Attributes

Content attestation is implemented as reserved attribute keys on a credential with credential_type = 0x04. The following attribute keys are reserved:

Attribute Key Type Required Description
content_hash string, 73 chars (sha3-256: + 64 hex chars) REQUIRED Content hash with algorithm prefix (§7.21)
content_type string (MIME type) RECOMMENDED MIME type of the attested content
creator_id string OPTIONAL Issuer-assigned creator identifier
creation_method enum string REQUIRED How the content was created (see values below)
model_id string CONDITIONAL AI model identifier — REQUIRED when creation_method is ai_assisted or ai_generated

CreationMethod enum: Implementations MUST define a CreationMethod type with the following variants and their canonical string serialisations:

Variant String Value
HumanAuthored "human_authored"
AiAssisted "ai_assisted"
AiGenerated "ai_generated"
Automated "automated"
Scanned "scanned"
Transcribed "transcribed"
Composite "composite"

Implementations MUST reject unrecognised string values with ErrCreationMethodInvalid (0x8005).

model_id requirement: MUST be present when creation_method is ai_assisted or ai_generated. SHOULD be absent otherwise.

7. Cryptographic Constructions

All constructions use SHA3-256. Inputs are concatenated in the order shown. All multi-byte integers are big-endian.

7.1 Issuer ID

issuer_id = SHA3-256(EXQUB_ISSUER_V1 || issuer_public_key)
  • issuer_public_key: 1952 bytes (ML-DSA-65)

7.2 Credential ID

issuer_id = SHA3-256(EXQUB_ISSUER_V1 || issuer_public_key)
credential_id = SHA3-256(EXQUB_CRED_ID_V1 || issuer_id || counter_u64_BE || issued_at_u64_BE)
  • counter: Monotonic, persisted with WAL, never reused. See §7.2.1.

7.2.1 Counter Persistence

The counter MUST be persisted in durable storage. MUST be incremented atomically before each issuance. MUST NEVER be reused after restart. Issuers MUST implement UNIQUE constraints on (issuer_public_key, counter, issued_at).

Counter Protocol: Initialize at 0. Increment atomically with write-ahead logging. On overflow at 2^64-1: fail closed, require new issuer key. Recovery after corruption: fail closed, require new issuer key. No wrap-around permitted.

7.3 Credential Signature Input

sig_input = SHA3-256(
    EXQUB_SIG_V1       ||  // 16 bytes
    version             ||  // 1 byte
    credential_type     ||  // 1 byte
    credential_id       ||  // 32 bytes
    issuer_id           ||  // 32 bytes
    holder_id           ||  // 32 bytes
    issued_at_u64_BE    ||  // 8 bytes
    expires_at_u64_BE   ||  // 8 bytes
    attr_count_u32_BE   ||  // 4 bytes
    attr_root               // 32 bytes
)
// Total input: 166 bytes

signature = ML_DSA_65_Sign(issuer_private_key, sig_input)

7.4 Holder ID

Option 1 — Issuer-Assigned (recommended):

// When holder_public_key is empty:
holder_id = SHA3-256(EXQUB_HOLDER_V1 || issuer_id || issuer_nonce)

// When holder_public_key is present:
holder_id = SHA3-256(EXQUB_HOLDER_V1 || issuer_id || holder_public_key)
  • issuer_nonce: Exactly 32 bytes, CSPRNG-generated, unique per holder per policy.

Option 2 — Self-Sovereign:

holder_id = SHA3-256(EXQUB_HOLDER_V1 || len(pk)_u32_BE || public_key_bytes)

Holder ID MUST be globally unique within an issuer's scope. Different issuers MUST use different holder IDs for the same holder. Deployment profiles MUST declare correlation resistance policy (HIGH PRIVACY: different holder IDs per credential; STANDARD: same holder ID within an issuer).

7.5 Attribute Leaf Hash

leaf_hash = SHA3-256(
    EXQUB_ATTR_LEAF_V1   ||  // 16 bytes
    len(key)_u16_BE      ||  // 2 bytes
    key_bytes            ||  // variable
    salt                 ||  // 32 bytes
    len(value)_u16_BE    ||  // 2 bytes
    value_bytes              // variable
)

Each attribute MUST have a unique 32-byte CSPRNG salt. Key length prefix is u16 BE (not u32).

7.6 Attribute Node Hash

node_hash = SHA3-256(EXQUB_ATTR_NODE_V1 || left_child || right_child)

7.7 Presentation Hash and Device Signature

Disclosed Keys Hash (keys MUST be sorted lexicographically by raw UTF-8 bytes before hashing):

disclosed_keys_hash = SHA3-256(
    len(key_1)_u16_BE || key_1 || len(key_2)_u16_BE || key_2 || ... || len(key_n)_u16_BE || key_n
)

Presentation Hash:

presentation_hash = SHA3-256(
    EXQUB_PRES_HASH_V1       ||  // 16 bytes
    nonce_v                   ||  // 32 bytes
    verifier_id               ||  // 32 bytes
    credential_id             ||  // 32 bytes
    presentation_timestamp_u64_BE  ||  // 8 bytes
    disclosed_count_u32_BE    ||  // 4 bytes
    disclosed_keys_hash       ||  // 32 bytes
    attr_root                 ||  // 32 bytes
    smt_root                      // 32 bytes
)

Device Key Hash:

device_pubkey_hash = SHA3-256(EXQUB_DEV_KEY_V1 || device_public_key)

Device Signature Input:

device_sig_input = SHA3-256(EXQUB_DEV_BIND_V1 || presentation_hash || device_pubkey_hash)

device_signature = ML_DSA_65_Sign(device_private_key, device_sig_input)

The device signs the 32-byte hash, NOT the raw concatenation.

7.8 Revocation Snapshot Signature

snapshot_sig_input = SHA3-256(
    EXQUB_REV_SNAP_V1 || issuer_id || epoch_u64_BE || smt_root || issued_at_u64_BE
)

7.9 Proximity Attestation

observer_key_hash = SHA3-256(EXQUB_DEV_KEY_V1 || observer_device_pubkey)

proof_hash = SHA3-256(
    EXQUB_PROX_PROOF_V1   ||  // 16 bytes
    credential_id          ||  // 32 bytes
    observer_key_hash      ||  // 32 bytes
    proximity_timestamp_u64_BE  ||  // 8 bytes
    proximity_nonce            // 32 bytes
)

7.10 Padding Leaf

padding_leaf = SHA3-256(EXQUB_ATTR_PAD_V1 || [0u8; 32])

All padding leaves are identical. No salt, no randomness.

7.11 SMT Leaf Hash

smt_leaf_hash = SHA3-256(EXQUB_SMT_LEAF_V1 || credential_id || status_byte)

7.12 SMT Internal Node Hash (Depth-Bound)

smt_node_hash = SHA3-256(EXQUB_SMT_NODE_V1 || depth_byte || left_child || right_child)

depth_byte is a single unsigned byte (0–255).

7.13 SMT Empty Nodes

empty[256] = SHA3-256(EXQUB_SMT_EMPTY_V1)
empty[d]   = SHA3-256(EXQUB_SMT_NODE_V1 || d_byte || empty[d+1] || empty[d+1])   for d = 255..0

257 entries (indices 0–256). MUST be stored in .rodata (flash/ROM), NOT on the stack.

Storage Requirements: Precompute at compile-time when possible. For no_std: compute on-demand with O(256-depth) iteration. For std: initialize once, cache in static memory. Total size: 8.2KB (257 × 32 bytes). Alignment: 32-byte boundary for cache efficiency.

7.14 SMT Leaf Position

path_index = SHA3-256(credential_id)

The path_index is a 256-bit big-endian value. Bit extraction: bit = (path_index[depth / 8] >> (7 - (depth % 8))) & 1. Bit 0 = MSB of byte 0. Bit 255 = LSB of byte 31.

7.15 Delegation Credential Signature Input

deleg_sig_input = SHA3-256(
    EXQUB_DELEG_V1          ||  // 16 bytes
    version                  ||  // 1 byte (0x01)
    credential_type          ||  // 1 byte (0x02)
    credential_id            ||  // 32 bytes
    issuer_id                ||  // 32 bytes
    holder_id                ||  // 32 bytes
    issued_at_u64_BE         ||  // 8 bytes
    expires_at_u64_BE        ||  // 8 bytes
    attr_count_u32_BE        ||  // 4 bytes
    attr_root                ||  // 32 bytes
    delegator_credential_id  ||  // 32 bytes
    delegation_depth         ||  // 1 byte
    max_delegation_depth     ||  // 1 byte
    scope_hash                   // 32 bytes
)
// Total preimage: 232 bytes

signature = ML_DSA_65_Sign(issuer_private_key, deleg_sig_input)

7.16 Scope Hash

scope_hash = SHA3-256(EXQUB_SCOPE_V1 || canonical_cbor(scope_constraints))

Where canonical_cbor(scope_constraints) follows §12.5 canonical ordering: None fields omitted, arrays sorted lexicographically before encoding.

7.17 Action Request Hash

action_request_hash = SHA3-256(
    EXQUB_ACTION_V1           ||  // 16 bytes
    action_len_u16_BE         ||  // 2 bytes
    action_utf8               ||  // variable
    resource_len_u16_BE       ||  // 2 bytes
    resource_utf8             ||  // variable
    value_u64_BE              ||  // 8 bytes (0 if None)
    timestamp_u64_BE          ||  // 8 bytes
    request_nonce                 // 32 bytes
)

Length prefixes use u16 BE (2 bytes), matching the convention from §7.5 attribute leaf hashes.

7.18 Sub-Delegation Signature Input

subdel_sig_input = SHA3-256(
    EXQUB_SUBDEL_V1             ||  // 16 bytes
    parent_credential_id         ||  // 32 bytes
    child_credential_id          ||  // 32 bytes
    child_holder_id              ||  // 32 bytes
    child_scope_hash             ||  // 32 bytes
    child_issued_at_u64_BE       ||  // 8 bytes
    child_expires_at_u64_BE      ||  // 8 bytes
    child_delegation_depth           // 1 byte
)
// Total preimage: 161 bytes

Sub-delegation is signed by the delegator's device key (randomised ML-DSA-65), not the issuer key. This is an intentional, explicit act — the delegator knowingly grants authority to a sub-agent. See §18 for the device key linkability tradeoff.

7.19 Chain ID Derivation

chain_id = SHA3-256(
    EXQUB_CHAIN_V1        ||  // 16 bytes
    issuer_id              ||  // 32 bytes
    chain_name_len_u16_BE  ||  // 2 bytes (u16 BE — consistent with §7.5 convention)
    chain_name_utf8            // 1–256 bytes (NFC normalised)
)

The resulting 32-byte hash is encoded as lowercase hex (64 chars) when stored as the chain_id attribute value.

7.20 Chain Previous Hash

chain_prev = SHA3-256(previous_credential_cbor_bytes)

Input is the complete canonical CBOR bytes of the previous credential as issued (including signature). No domain separator — the CBOR bytes are self-describing. The resulting 32-byte hash is encoded as lowercase hex (64 chars) when stored as the chain_prev attribute value. For the first entry, chain_prev attribute value is 64 zero characters.

7.21 Content Hash

content_hash_value     = SHA3-256(raw_content_bytes)
content_hash_attribute = "sha3-256:" || hex_lowercase(content_hash_value)

No domain separator on content hash — the sha3-256: prefix serves this purpose and enables future algorithm agility. Total attribute length: 73 characters (9-char prefix + 64-char hex). Implementations MUST reject unknown prefixes (error 0x8002).

8. Attribute Merkle Tree

8.1 Construction

  1. Validate: attr_count == salts.length, attr_count ≤ MAX_ATTRIBUTES, no duplicate keys
  2. Sort attributes lexicographically by key (UTF-8 bytewise comparison, stable sort)
  3. Compute leaf hashes using §7.5, with each attribute's original salt
  4. Pad to next_power_of_2(attr_count) using padding leaves (§7.10)
  5. Build bottom-up: pair leaves, compute parent via §7.6
  6. Root = final single hash

8.2 Verification

Given a DisclosedAttribute with leaf_index, key, value, salt, and merkle_proof against expected_root and attr_count:

  1. Reject if leaf_index ≥ attr_count (padding leaf disclosure)
  2. Compute tree_size = next_power_of_2(attr_count)
  3. Compute tree_depth = trailing_zeros(tree_size) — the ACTUAL depth, not MAX_TREE_DEPTH
  4. Reject if merkle_proof.length ≠ tree_depth
  5. Compute leaf_hash via §7.5
  6. Walk proof bottom-up: at each level, if current_index is even, current is left child; if odd, current is right child. Compute parent via §7.6. Divide index by 2.
  7. Compare final hash to expected_root using constant-time comparison

8.3 Sorting

Attributes are sorted by key using UTF-8 bytewise lexicographic comparison. Keys MUST be unique. Sort MUST be stable and deterministic.

9. Sparse Merkle Tree (SMT)

9.1 Structure

The SMT is a 256-level binary tree. Each credential maps to a leaf position via §7.14. Leaf values are §7.11 (leaf hash binding credential_id to status). Internal nodes use §7.12 (depth-bound hashing). Empty subtrees use precomputed values from §7.13.

9.2 Proof Verification

Given leaf_hash, path_index (32 bytes), and sorted sparse siblings:

  1. Validate: siblings.length ≤ 256, strictly ascending depths, no duplicates
  2. Set current_hash = leaf_hash, sibling_cursor = last index (deepest sibling)
  3. For level = 256 down to 1:
    • parent_depth = level - 1
    • If sibling at cursor has depth == parent_depth: use it, decrement cursor
    • Else: use PRECOMPUTED_EMPTY_NODES[parent_depth]
    • Extract bit from path_index at parent_depth (§7.14)
    • bit == 0: left = current_hash, right = sibling_hash
    • bit == 1: left = sibling_hash, right = current_hash
    • current_hash = smt_node_hash(parent_depth, left, right) via §7.12
  4. Assert all siblings consumed (cursor == -1). Reject if not.
  5. Return current_hash (computed root)

This algorithm uses O(1) stack space. MUST NOT allocate a 256-element lookup table.

9.3 Membership Semantics

A credential is valid if and only if it has an explicit SMT entry with STATUS_VALID (0x00). Non-membership proofs (empty leaf) MUST be rejected. STATUS_REVOKED → reject. STATUS_SUSPENDED → reject or require step-up authentication per deployment policy. Unknown status values → reject.

9.4 Revocation Snapshots

Verifiers accept snapshots by: (1) verifying ML-DSA-65 signature, (2) checking epoch > last accepted epoch (monotonicity), (3) evaluating freshness against MAX_SMT_ROOT_AGE. If root age exceeds threshold, the core engine returns STATUS_STALE_ROOT as a warning — the application layer decides whether to fail closed or accept with audit flag.

Verifiers MUST persistently store (issuer_id, epoch, root) tuples. State MUST survive restarts. Storage failure → fail closed.

Snapshot Protocol: Distribution is out-of-band (HTTPS, IPFS, etc.). Maximum snapshot size: 16KB. Fallback when unavailable: use last known good with STATUS_STALE_ROOT warning. Snapshot MUST NOT contain credential data, only root and signature. Each epoch MUST have exactly one snapshot (error corrections require incrementing epoch).

Transport Security: When distributed via HTTPS, TLS 1.3+ SHOULD be used.

10. Device Co-Signatures

Algorithm: ML-DSA-65. Signing input computed via §7.7 (device_sig_input).

Key Lifecycle: Generated in TEE using CSPRNG. Private key MUST NOT leave TEE. No rotation in v1.0. Recovery requires credential re-issuance. Key hash is 32 bytes for efficiency.

Device Loss Recovery: No key recovery protocol. Lost device requires credential reissuance with new device binding. Old credential SHOULD be revoked via SMT update.

11. Text Validation

Core engine (no_std): MUST reject null bytes (0x00) in text fields. MUST validate UTF-8. MUST NOT perform NFC normalization or combining character detection.

Issuers (std): MUST apply NFC normalization (Unicode 15.0) to all attribute keys and values before hashing. MUST verify idempotency: NFC(NFC(x)) == NFC(x). MUST reject null bytes. MUST enforce length limits (key ≤ 64 bytes, value ≤ 1024 bytes). MUST strip RTL markers before normalization.

Attribute key format: ^[a-zA-Z][a-zA-Z0-9_-]{0,63}$

Attribute Constraints: Keys MUST be unique within a credential. Empty strings prohibited for both keys and values. Maximum 64 attributes per credential (enforced at issuance). Binary data NOT supported in v1.0 — UTF-8 text only. Attribute modification requires full credential reissuance.

Internationalization: UTF-8 only, no BOM. Homograph attacks prevented via key format restrictions. Display names NOT supported — protocol names only. Language tags NOT supported in v1.0.

12. CBOR Encoding

12.1 Canonical Rules

All CBOR MUST conform to RFC 8949 §4.2 deterministic encoding:

  • Definite-length encodings only (reject 0x9F, 0xBF, 0x7F, 0x5F)
  • Shortest integer representation (reject redundant bytes)
  • Map keys sorted by encoded byte order (shorter first, then lexicographic)
  • Map key uniqueness (reject duplicates)
  • No semantic tags (reject Major Type 6)
  • No undefined values (reject 0xF7)
  • No floating-point values (reject Major Type 7 values 25, 26, 27)
  • Text strings: valid UTF-8, no null bytes

12.2 Streaming Parser

The parser MUST enforce MAX_CBOR_DEPTH during parsing. MUST validate canonical encoding rules during the initial parse (not as a separate pass). MUST operate within the 4KB stack budget. MUST reject trailing bytes after the top-level item.

12.3 Size Limits

During parsing, enforce: byte strings ≤ MAX_CBOR_BYTE_STRING, text strings ≤ MAX_CBOR_TEXT_STRING, arrays ≤ MAX_CBOR_ARRAY_LENGTH, maps ≤ MAX_CBOR_MAP_ENTRIES, total input ≤ MAX_PRESENTATION_SIZE.

12.4 Optional Fields

When an Option field is None, the CBOR map key MUST be omitted entirely. MUST NOT encode CBOR null.

Field Presence Rules: All fields in CredentialV1, SignedCredential, SmtInclusionProof, SmtSibling, DeviceSignature are required. In PresentationV1: proximity_attestation is optional (omit key if absent). Zero values MUST be encoded (not omitted). Unknown fields MUST cause parse failure — no forward compatibility. Arrays under capacity encode actual length, not capacity.

12.5 Wire Format Keys

CBOR map keys are text strings. Canonical key ordering follows RFC 8949 §4.2 (sorted by encoded byte length, then lexicographic).

CredentialV1: "attr_count", "attr_root", "credential_id", "credential_type", "expires_at", "holder_id", "issued_at", "issuer_id", "version"

SignedCredential: "credential", "signature"

DisclosedAttribute: "key", "leaf_index", "merkle_proof", "salt", "value"

SmtInclusionProof: "leaf_status", "siblings", "smt_root"

SmtSibling: "depth", "sibling_hash"

DeviceSignature: "device_public_key", "signature"

PresentationV1: "credential", "device_signature", "disclosed_attributes", "nonce_v", "presentation_timestamp", "proximity_attestation" (if present), "smt_proof", "verifier_id"

ProximityProofData: "observer_device_pubkey_hash", "proof_hash", "proximity_nonce", "proximity_timestamp"

DelegationCredentialV1 (canonical order by CBOR encoded key length, then lexicographic): "version", "attr_root", "holder_id", "issued_at", "issuer_id", "attr_count", "expires_at", "scope_hash", "credential_id", "credential_type", "delegation_depth", "max_delegation_depth", "delegator_credential_id"

Key encoding byte counts: "version" (7→8), "attr_root"/"holder_id"/"issued_at"/"issuer_id" (9→10), "attr_count"/"expires_at"/"scope_hash" (10→11), "credential_id" (13→14), "credential_type" (15→16), "delegation_depth" (16→17), "max_delegation_depth" (20→21), "delegator_credential_id" (23→24).

Must verify: These byte counts are a sanity check only. Pinning tests (Task 2.7.11) MUST be generated from actual CBOR encoder output, not from manual counting. The encoder is authoritative — the H7 bug (proximity_attestation vs presentation_timestamp ordering) proves this step is load-bearing.

SignedDelegationCredential: "signature", "credential"

ScopeConstraints (optional fields omitted when None): "actions", "max_value", "time_window", "max_daily_value", "resource_patterns", "max_actions_per_hour", "required_attestations"

Key encoding byte counts: "actions" (7→8), "max_value" (9→10), "time_window" (11→12), "max_daily_value" (15→16), "resource_patterns" (17→18), "max_actions_per_hour" (20→21), "required_attestations" (22→23).

ActionRequest (canonical order): "value", "action", "resource", "timestamp", "request_nonce"

Key encoding byte counts: "value" (5→6), "action" (6→7), "resource" (8→9), "timestamp" (9→10), "request_nonce" (13→14).

DelegatedActionPresentation (canonical order): "presentation", "action_request", "delegation_chain", "scope_constraints"

Key encoding byte counts: "presentation" (12→13), "action_request" (14→15), "delegation_chain" (16→17), "scope_constraints" (17→18).

TimeWindow: "end_hour", "start_hour", "days_of_week"

13. Verification Procedure

Verifiers MUST execute checks in this exact order (10-step pipeline). MUST NOT proceed if any step fails. MUST NOT reorder steps.

Verification Invariants: No partial verification results — atomic pass/fail only. Memory cleanup required on any failure. Cryptographic comparisons MUST use constant-time operations. The verification steps for a single presentation MUST be executed in sequential order. Multiple independent presentations MAY be verified in parallel. Audit logging SHOULD be performed for all failures.

Step 1 — Parse and Validate CBOR (cheap): Parse presentation_bytes with canonical CBOR validation (§12). Reject if non-canonical, oversized, or malformed.

Step 2 — Version and Type Check (cheap): Reject if version ≠ 0x01 (error 0x1001). Reject if credential_type ∉ {0x01, 0x02, 0x04} (error 0x1005). For delegation presentations, dispatch to §13.3 after completing steps 3–10.

Step 3 — Freshness and Nonce (cheap): Reject if |presentation_timestamp - current_time| > max_clock_skew. Reject if nonce_v ≠ expected verifier nonce (constant-time comparison).

Step 4 — Work Bounding (cheap): Reject if disclosed_attributes count > MAX_ATTRIBUTES (64). Reject if SMT sibling count > MAX_SMT_PROOF_DEPTH.

Step 5 — SMT Membership (hashing only): Verify SMT inclusion proof via §9.2. Compare computed root to expected root (constant-time). Reject non-membership. Reject if leaf_status ≠ STATUS_VALID.

Step 6 — Credential Signature (expensive — ML-DSA-65): Compute sig_input via §7.3. Verify ML-DSA-65 signature against issuer_public_key.

Step 7 — Credential Validity Window: Reject if issued_at ≥ expires_at. Reject if current_time < issued_at - max_clock_skew. Reject if current_time > expires_at + max_clock_skew.

Step 8 — Merkle Proofs (moderate): For each disclosed attribute, verify via §8.2 against credential's attr_root. Reject any padding leaf disclosure (leaf_index ≥ attr_count).

Step 9 — Device Signature (expensive — ML-DSA-65): Compute device_pubkey_hash via §7.7. Compute presentation_hash via §7.7. Compute device_sig_input via §7.7. Verify ML-DSA-65 signature against device_public_key.

Step 10 — Policy (variable): Check required attributes per policy. Verify proximity attestation if required (§13.1). Apply deployment-specific constraints.

13.1 Proximity Attestation Verification

If present: verify proof freshness (|proximity_timestamp - current_time| ≤ max_proximity_age). Verify temporal correlation (|presentation_timestamp - proximity_timestamp| ≤ 60 seconds). Verify observer device trust (match proof_hash against expected hash computed via §7.9 using trusted observer keys, checking both current and previous nonce for rotation window). If policy requires proximity and attestation is absent, reject.

Proximity Protocol: Observers register out-of-band with verifier. Implementations SHOULD support at least 256 trusted observers. Nonce rotation every 300 seconds with 60-second overlap window. Proximity proof expires after 60 seconds. Observer device keys use same ML-DSA-65 as credentials.

13.2 Failure Recovery

After Failed Verification: No automatic retry. Log failure with error code. Clear all temporary state. No information leakage about failure reason to untrusted parties.

Rate Limiting: Application layer MAY implement rate limiting. No protocol-level rate limiting requirements.

13.3 Delegation Chain Verification

Verifiers MUST execute these steps in order for a DelegatedActionPresentation. MUST NOT reorder.

Step 1 — Chain structure: Reject if delegation_chain is empty (error 0x600C). Reject if length > MAX_DELEGATION_DEPTH + 1 (error 0x600D).

Step 2 — Per-link depth check: For each link i, verify delegation_depth == i and delegation_depth ≤ max_delegation_depth (error 0x6001, 0x6002).

Step 3 — Per-link temporal attenuation: For each adjacent pair (parent, child), verify child.expires_at ≤ parent.expires_at (error 0x6009).

Step 4 — Per-link chain continuity: For each adjacent pair (parent, child), verify child.delegator_credential_id == parent.credential_id (error 0x6008). Verify root has delegator_credential_id all zeros (error 0x6003).

Step 5 — Scope hash verification: Compute compute_scope_hash(presented_scope_constraints) via §7.16 and compare (constant-time) to the leaf credential's scope_hash. Reject on mismatch (error 0x600E). This step prevents scope inflation attacks — a presenter cannot claim broader scope than what was signed into the credential.

Step 6 — Per-link scope attenuation: Verify each child scope ⊆ parent scope using the monotonic narrowing rules in §6.9. Reject on violation (error 0x6006).

Step 7 — Per-link ML-DSA-65 signature verification (expensive — deferred to last per-link step): Verify each delegation credential's signature via §7.15 against the issuer's public key (error 0x600A).

Step 8 — Action against scope: Verify the action_request.action and action_request.resource are permitted by the leaf credential's scope (error 0x6005).

Step 9 — Agent presentation verification: Invoke the 10-step core verification pipeline (§13, steps 1–10) on the leaf agent's PresentationV1.

DoS resistance: Steps 1–4 (structural, O(n)) execute before steps 5–6 (scope computation) before step 7 (ML-DSA-65). This ordering ensures cheap checks gate expensive cryptographic operations.

Return type on success: DelegationChainVerificationResult

Field Type Description
chain_depth u8 Depth of the verified chain (0 = root only)
root_credential_id [u8; 32] credential_id of the root delegation
leaf_credential_id [u8; 32] credential_id of the leaf (agent) delegation
leaf_scope_hash [u8; 32] scope_hash of the leaf delegation (authoritative scope)

13.4 Chain Linking Verification

Verifiers receiving a sequence of chain-linked credentials MUST verify integrity in this order:

Step 1 — Attribute presence: Reject any credential missing chain_id, chain_seq, or chain_prev (error 0x7006).

Step 2 — Chain ID consistency: Verify all credentials share the same chain_id (error 0x7001).

Step 3 — Issuer consistency: Verify all credentials share the same issuer_id (error 0x7007).

Step 4 — Sequence numbering: Verify chain_seq values form a gapless ascending sequence starting at "1" (error 0x7002, 0x7005).

Step 5 — First-entry sentinel: Verify the first entry has chain_prev = 64 zero characters (error 0x7004).

Step 6 — Hash chain integrity: For each entry i > 1, compute SHA3-256 of credential i-1's canonical CBOR bytes and compare to chain_prev of entry i (constant-time comparison; error 0x7003).

Step 7 — Per-credential verification: Invoke the core 10-step pipeline (§13, steps 1–10) for each credential. In strict mode, a revoked entry (0x7008) fails the entire chain. In audit mode, revoked entries are flagged but the chain is accepted for historical review.

DoS resistance: Steps 1–5 (structural, O(n)) execute before step 6 (SHA3-256, O(n)) before step 7 (ML-DSA-65 × n, expensive).

ChainVerificationMode: Callers MUST specify one of:

  • Strict — any revoked entry returns ErrChainRevokedEntry (0x7008) and aborts verification
  • Audit — revoked entries are flagged in the result but verification continues; for historical/regulatory review

Return type on success: ChainVerificationResult

Field Type Description
verified_entry_count usize Number of credentials verified
chain_id [u8; 32] The chain identifier (binary form)
revoked_indices Vec<usize> Indices of revoked entries (Audit mode only; empty in Strict mode)

13.5 Content Attestation Verification

Step 1 — Credential type check: Verify credential_type == 0x04 (error 0x1005).

Step 2 — Core verification: Invoke the 10-step core pipeline (§13, steps 1–10) on the attestation credential's presentation.

Step 3 — Required attributes: Verify content_hash and creation_method are among the disclosed attributes (error 0x8004, 0x8005).

Step 4 — Hash prefix validation: Verify content_hash value begins with sha3-256: (error 0x8002). Verify the hex portion is exactly 64 lowercase hex characters (error 0x8003).

Step 5 — model_id requirement: If creation_method is ai_assisted or ai_generated, verify model_id is disclosed (error 0x8006).

Step 6 — Content hash verification: Compute SHA3-256 of the content bytes and compare (constant-time) to the hex-decoded hash from the content_hash attribute (error 0x8001).

Return type on success: ContentAttestationResult

Field Type Description
content_hash [u8; 32] The verified SHA3-256 hash of the content
creation_method CreationMethod The verified creation method (§6.13)
credential_id [u8; 32] The attesting credential's ID

14. Replay Prevention

Core engine (no_std, stateless): Performs timestamp bounds check and nonce match only. Does NOT maintain state.

Application layer (stateful): MUST track used presentation hashes (§7.7) as replay cache keys. The presentation_hash already includes nonce_v — no additional hashing needed. Minimum retention: 900 seconds. Maximum: 86400 seconds. Maximum entries: 100,000 with LRU eviction. MUST NOT silently evict unexpired entries.

15. Error Codes

Range Category
0x1000–0x1FFF Payload & Parsing
0x2000–0x2FFF Temporal & Replay
0x3000–0x3FFF Cryptographic & SMT
0x4000–0x4FFF Merkle Tree
0x5000–0x5FFF Policy & Attribute
0x6000–0x6FFF Delegation
0x7000–0x7FFF Chain Linking
0x8000–0x8FFF Content Attestation
Code Name Description
0x1001 ERR_UNSUPPORTED_VERSION Version ≠ 0x01
0x1002 ERR_CBOR_NON_CANONICAL CBOR encoding violates canonical rules
0x1003 ERR_PARSING_LIMIT_EXCEEDED Payload exceeds memory/stack limits
0x1004 ERR_MISSING_LEAF_INDEX DisclosedAttribute missing leaf_index
0x1005 ERR_UNSUPPORTED_CREDENTIAL_TYPE Credential type ∉ {0x01, 0x02, 0x04}
0x2001 ERR_PRESENTATION_EXPIRED Presentation timestamp outside window
0x2002 ERR_CREDENTIAL_EXPIRED Credential expires_at has passed
0x2003 ERR_CREDENTIAL_NOT_YET_VALID Credential issued_at is in the future
0x2004 ERR_NONCE_REPLAYED Presentation hash in replay cache
0x2005 ERR_PROXIMITY_STALE Proximity attestation too old
0x2006 ERR_PROXIMITY_TEMPORAL_FAIL Proximity/presentation timestamp mismatch
0x2007 STATUS_STALE_ROOT SMT root age exceeds threshold (warning)
0x3001 ERR_INVALID_SIGNATURE ML-DSA-65 verification failed
0x3002 ERR_SMT_DEPTH_VIOLATION SMT proof exceeds bounds
0x3003 ERR_SMT_INVALID_ORDERING SMT siblings not ascending or contain duplicates
0x3004 ERR_SMT_STATUS_REVOKED Credential status is not valid
0x3005 ERR_DEVICE_KEY_MISMATCH device_pubkey_hash mismatch
0x3006 ERR_SMT_PROOF_INVALID SMT proof verification failed
0x4001 ERR_MERKLE_ROOT_MISMATCH Computed root ≠ credential attr_root
0x4002 ERR_MERKLE_PROOF_INVALID Merkle proof path verification failed
0x4003 ERR_PADDING_LEAF_DISCLOSED Disclosed attribute references padding position
0x5001 ERR_MISSING_REQUIRED_ATTR Required attribute not disclosed
0x5002 ERR_POLICY_VIOLATION Presentation violates verifier policy
0x5003 ERR_UNTRUSTED_OBSERVER Proximity observer not in trusted list
0x6001 ErrDelegationDepthExceeded delegation_depth > MAX_DELEGATION_DEPTH
0x6002 ErrDelegationDepthMismatch delegation_depth > max_delegation_depth
0x6003 ErrDelegationRootNotZero Root delegation (depth=0) has non-zero delegator_credential_id
0x6004 ErrDelegationNonRootZero Non-root delegation (depth>0) has zero delegator_credential_id
0x6005 ErrScopeViolation Action not permitted by leaf scope constraints
0x6006 ErrScopeAttenuationFailed Child scope is not a subset of parent scope
0x6007 ErrDelegationExpired Delegation credential has expired
0x6008 ErrDelegationChainBroken child.delegator_credential_id ≠ parent.credential_id
0x6009 ErrDelegationTemporalViolation child.expires_at > parent.expires_at
0x600A ErrDelegationSignatureInvalid ML-DSA-65 signature on delegation credential failed
0x600B ErrSubdelegationSignatureInvalid Sub-delegation signature (device key) verification failed
0x600C ErrDelegationChainEmpty Empty delegation chain in DelegatedActionPresentation
0x600D ErrDelegationChainTooLong Chain length > MAX_DELEGATION_DEPTH + 1
0x600E ErrDelegationScopeHashMismatch Computed scope_hash ≠ leaf credential scope_hash
0x600F ErrDelegationParentRevoked Parent delegation credential is revoked
0x7001 ErrChainIdMismatch chain_id attribute does not match across entries
0x7002 ErrChainSeqGap chain_seq values have a gap (non-consecutive)
0x7003 ErrChainPrevMismatch chain_prev ≠ SHA3-256 of previous entry's CBOR
0x7004 ErrChainFirstEntryPrev First entry chain_prev is not all zeros
0x7005 ErrChainFirstEntrySeq First entry chain_seq is not "1"
0x7006 ErrChainMissingAttribute Required chain attribute missing (chain_id, chain_seq, or chain_prev)
0x7007 ErrChainIssuerMismatch Entries in chain have different issuer_id values
0x7008 ErrChainRevokedEntry Chain entry is revoked (strict verification mode)
0x8001 ErrContentHashMismatch Computed content hash ≠ attested content_hash
0x8002 ErrContentHashPrefixInvalid Unrecognised hash algorithm prefix (only sha3-256: accepted)
0x8003 ErrContentHashLengthInvalid Hex portion of content_hash is not exactly 64 characters
0x8004 ErrContentHashMissing content_hash attribute not present in disclosed attributes
0x8005 ErrCreationMethodInvalid Unrecognised creation_method value
0x8006 ErrModelIdRequired model_id must be disclosed when creation_method is ai_assisted or ai_generated

Implementations MUST return these exact hex codes. MUST NOT panic. All functions MUST return Result types.

Error Handling Protocol: Errors MUST bubble up immediately — no recovery attempts. Cryptographic errors use constant-time handling to prevent timing attacks. Stack unwinding in no_std: return error through Result chain. No error state persistence between operations. Partial results prohibited — atomic success or failure only.

16. Test Vectors

Implementations MUST produce identical outputs for these vectors.

16.1 Domain Separator Hex

EXQUB_ISSUER_V1     = [45,58,51,55,42,5f,49,53,53,55,45,52,5f,56,31,5f]
EXQUB_CRED_ID_V1    = [45,58,51,55,42,5f,43,52,45,44,5f,49,44,5f,56,31]
EXQUB_SIG_V1        = [45,58,51,55,42,5f,53,49,47,5f,56,31,5f,5f,5f,5f]
EXQUB_ATTR_LEAF_V1  = [45,58,51,55,42,5f,41,54,54,52,5f,4c,45,41,46,5f]
EXQUB_ATTR_NODE_V1  = [45,58,51,55,42,5f,41,54,54,52,5f,4e,4f,44,45,5f]
EXQUB_ATTR_PAD_V1   = [45,58,51,55,42,5f,41,54,54,52,5f,50,41,44,5f,5f]
EXQUB_SMT_EMPTY_V1  = [45,58,51,55,42,5f,53,4d,54,5f,45,4d,50,54,59,5f]
EXQUB_SMT_NODE_V1   = [45,58,51,55,42,5f,53,4d,54,5f,4e,4f,44,45,5f,5f]
EXQUB_SMT_LEAF_V1   = [45,58,51,55,42,5f,53,4d,54,5f,4c,45,41,46,5f,5f]
EXQUB_DEV_BIND_V1   = [45,58,51,55,42,5f,44,45,56,5f,42,49,4e,44,5f,5f]
EXQUB_DEV_KEY_V1    = [45,58,51,55,42,5f,44,45,56,5f,4b,45,59,5f,56,31]
EXQUB_PROX_PROOF_V1 = [45,58,51,55,42,5f,50,52,4f,58,5f,50,52,4f,4f,46]
EXQUB_PRES_HASH_V1  = [45,58,51,55,42,5f,50,52,45,53,5f,48,41,53,48,5f]
EXQUB_HOLDER_V1     = [45,58,51,55,42,5f,48,4f,4c,44,45,52,5f,56,31,5f]
EXQUB_REV_SNAP_V1   = [45,58,51,55,42,5f,52,45,56,5f,53,4e,41,50,5f,5f]
EXQUB_REPLAY_KEY_V1 = [45,58,51,55,42,5f,52,45,50,4c,41,59,5f,4b,45,59]
EXQUB_DELEG_V1      = [45,58,51,55,42,5f,44,45,4c,45,47,5f,56,31,5f,5f]
EXQUB_SCOPE_V1      = [45,58,51,55,42,5f,53,43,4f,50,45,5f,56,31,5f,5f]
EXQUB_ACTION_V1     = [45,58,51,55,42,5f,41,43,54,49,4f,4e,5f,56,31,5f]
EXQUB_SUBDEL_V1     = [45,58,51,55,42,5f,53,55,42,44,45,4c,5f,56,31,5f]
EXQUB_CHAIN_V1      = [45,58,51,55,42,5f,43,48,41,49,4e,5f,56,31,5f,5f]

16.2 Attribute Tree

Input: {"age": "25", "country": "US", "name": "Alice Smith"} (pre-sorted)

Salts: salt_age = [0x02; 32], salt_country = [0x03; 32], salt_name = [0x01; 32]

Tree: 3 attributes + 1 padding leaf = tree_size 4, depth 2

Expected age leaf hash:

[38,f3,da,2d,24,d9,c5,bb,48,1d,28,a1,18,e0,e8,cb,2f,08,87,ad,8a,73,3f,8e,75,e1,2e,83,3e,70,39,1d]

Expected country leaf hash:

[10,2b,d9,3b,50,67,03,1d,92,f2,6f,1b,2d,99,b8,32,ad,8d,89,29,25,2a,ca,4a,c9,45,45,b9,0f,a3,9c,da]

Expected name leaf hash:

[12,9c,45,77,a7,61,ea,48,9d,67,32,58,8d,49,b3,d8,a2,1c,ed,fe,9c,7f,ff,f9,e7,a2,12,c0,1c,98,c2,c2]

Expected padding leaf hash:

[b4,4d,07,51,06,ed,f7,cb,a8,8b,6f,19,da,fc,a9,61,f6,87,0c,d3,01,33,2b,2b,3c,4e,e2,39,ea,c5,a4,42]

Expected tree root:

[cf,00,07,42,22,87,6c,35,52,1e,5f,04,00,d8,d9,f3,4b,bf,6f,cb,b8,89,b9,f0,9b,c9,a1,d5,52,1f,3f,05]

16.3 Credential Signature Input

Input: CredentialV1 with version=0x01, credential_type=0x01, credential_id=[0x11;32], issuer_id=[0x55;32], holder_id=[0x99;32], issued_at=1234567890, expires_at=1266103890, attr_count=3, attr_root=tree_root_from_§16.2

Note: Input notation uses [0xNN;32] meaning 32 bytes all set to 0xNN, matching the notation used in §16.6–§16.12.

Expected sig_input hash (SHA3-256 of 166-byte preimage):

[71,f5,64,e4,09,84,93,32,e6,57,27,6b,b5,7e,21,82,8f,a3,31,d8,65,9a,db,49,48,10,b8,75,ba,38,9e,7a]

Preimage structure (166 bytes):

  • Bytes [0..16): EXQUB_SIG_V1____ domain separator
  • Byte [16]: version = 0x01
  • Byte [17]: credential_type = 0x01
  • Bytes [18..50): credential_id = [0x11;32]
  • Bytes [50..82): issuer_id = [0x55;32]
  • Bytes [82..114): holder_id = [0x99;32]
  • Bytes [114..122): issued_at (u64 BE) = 1234567890
  • Bytes [122..130): expires_at (u64 BE) = 1266103890
  • Bytes [130..134): attr_count (u32 BE) = 3
  • Bytes [134..166): attrroot = tree_root_from§16.2

16.4 SMT Leaf

Input: credential_id=[0x11,0x22,0x33,0x44,...], status=STATUS_VALID (0x00)

Expected leaf position (SHA3-256 of credential_id):

[df,ec,3a,48,ea,8c,fd,b1,80,50,30,5a,e4,b7,15,fa,6c,f1,e6,93,0c,2f,22,14,5d,bb,2a,b7,8b,8a,82,d8]

Expected leaf hash:

[37,d9,c2,9a,47,1f,81,0f,0d,d7,56,f1,02,50,32,94,25,d3,6e,56,4e,c0,e5,01,51,4c,87,8c,a0,ca,00,fd]

16.5 Test Requirements

Compliance: All implementations MUST produce bit-identical outputs for the test vectors in this section.

16.6 Delegation Credential Signature Input

Input: DelegationCredentialV1 with version=0x01, credential_type=0x02, credential_id=[0x11;32], issuer_id=[0x55;32], holder_id=[0x99;32], issued_at=1234567890, expires_at=1266103890, attr_count=2, attr_root=[0xAA;32], delegator_credential_id=[0x00;32] (root), delegation_depth=0x00, max_delegation_depth=0x05, scope_hash=[0xBB;32]

Expected deleg_sig_input hash (SHA3-256 of 232-byte preimage):

[e3,8f,d8,fc,6a,90,36,f7,61,5f,76,21,60,96,72,1d,3b,df,87,29,dc,74,4f,39,ab,f4,70,ba,57,56,3b,7f]

Preimage structure (232 bytes):

  • Bytes [0..16): EXQUB_DELEG_V1__ domain separator
  • Byte [16]: version = 0x01
  • Byte [17]: credential_type = 0x02
  • Bytes [18..50): credential_id
  • Bytes [50..82): issuer_id
  • Bytes [82..114): holder_id
  • Bytes [114..122): issued_at (u64 BE)
  • Bytes [122..130): expires_at (u64 BE)
  • Bytes [130..134): attr_count (u32 BE)
  • Bytes [134..166): attr_root
  • Bytes [166..198): delegator_credential_id
  • Byte [198]: delegation_depth
  • Byte [199]: max_delegation_depth
  • Bytes [200..232): scope_hash

16.7 Scope Hash

Input: ScopeConstraints with actions=["approve"], resource_patterns=["invoices/*"] (all optional fields absent)

Canonical CBOR (48 bytes): a267616374696f6e738167617070726f7665717265736f757263655f7061747465726e73816a696e766f696365732f2a

Expected scope_hash (SHA3-256 of EXQUB_SCOPE_V1 || canonical_cbor):

[7a,7a,99,62,85,94,72,6a,0b,78,1a,8e,80,c4,14,57,67,15,f0,de,1b,26,cb,2e,99,db,da,82,5b,de,60,44]

16.8 Action Request Hash

Input: action="approve", resource="invoices/INV-2026-001", value=5000, timestamp=1234567890, request_nonce=[0x77;32]

Expected action_request_hash (SHA3-256 of variable-length preimage):

[3d,78,87,17,b5,58,5c,e8,bd,3e,21,fc,a2,8e,c8,47,e3,4e,64,46,5d,92,2a,f3,ec,0c,7c,94,78,f5,cc,a4]

16.9 Sub-Delegation Signature Input

Input: parent_credential_id=[0x11;32], child_credential_id=[0x22;32], child_holder_id=[0x33;32], child_scope_hash=[0x44;32], child_issued_at=1234567890, child_expires_at=1266103890, child_delegation_depth=0x01

Expected subdel_sig_input hash (SHA3-256 of 161-byte preimage):

[cd,3e,fd,76,bd,1d,15,5c,69,59,ac,ad,72,21,1f,7e,0b,59,ca,4e,5a,81,3a,16,01,0b,57,d0,76,18,68,07]

Preimage structure (161 bytes):

  • Bytes [0..16): EXQUB_SUBDEL_V1_ domain separator
  • Bytes [16..48): parent_credential_id
  • Bytes [48..80): child_credential_id
  • Bytes [80..112): child_holder_id
  • Bytes [112..144): child_scope_hash
  • Bytes [144..152): child_issued_at (u64 BE)
  • Bytes [152..160): child_expires_at (u64 BE)
  • Byte [160]: child_delegation_depth

16.10 Chain ID Derivation

Input: issuer_id=[0x55;32], chain_name="audit-2026" (10 bytes, u16 BE length prefix = 0x000A)

Expected chain_id hash (SHA3-256 of EXQUB_CHAIN_V1 || issuer_id || len_u16_BE || chain_name):

[99,af,f8,98,59,4c,b6,f3,26,49,b4,cb,bc,05,73,00,07,df,78,87,19,55,23,37,65,10,0b,6b,d7,77,b2,f1]

As hex attribute value: 99aff898594cb6f32649b4cbbc05730007df78871955233765100b6bd777b2f1

16.11 Chain Previous Hash

Input: previous_credential_cbor_bytes = [0xCC;64] (mock 64-byte canonical CBOR)

Expected chain_prev hash (SHA3-256 of raw CBOR bytes, no domain separator):

[7f,df,23,68,a2,70,f1,39,aa,f7,89,d7,e6,e2,74,af,32,8f,6c,6e,aa,f6,23,c4,3b,6b,7d,30,17,fa,9e,87]

16.12 Content Hash

Input: raw_content_bytes = Hello, World! (13 bytes, UTF-8)

Expected SHA3-256:

[1a,f1,7a,66,4e,3f,a8,e4,19,b8,ba,05,c2,a1,73,16,9d,f7,61,62,a5,a2,86,e0,c4,05,b4,60,d4,78,f7,ef]

Expected content_hash attribute value (73 chars): sha3-256:1af17a664e3fa8e419b8ba05c2a173169df76162a5a286e0c405b460d478f7ef

17. Implementation Constraints

  • Core engine MUST be no_std compatible with no heap allocation
  • Application layer MAY use heap allocation
  • All functions MUST return Result types — no panics, no unwrap
  • All cryptographic comparisons MUST use constant-time operations
  • No floating-point arithmetic anywhere in the protocol
  • Integer overflow MUST be checked (overflow-checks = true in release builds)
  • GPS coordinates: fixed-point i32

18. Security Considerations

Quantum Resistance: ML-DSA-65 provides NIST Security Level 3 (equivalent to AES-192). Resistant to known quantum attacks via Shor's and Grover's algorithms.

Key Management: Private keys MUST be generated in secure hardware when available. Keys MUST be stored encrypted at rest. Key material MUST be zeroized after use. No key escrow or recovery mechanisms.

Audit Requirements: All verification failures MUST be logged with timestamp, error code, and presentation hash. Logs MUST be tamper-evident. Minimum retention: 90 days. Maximum PII in logs: presentation hash only.

Incident Response: On key compromise: immediate revocation via SMT update. On issuer compromise: revoke all credentials, regenerate issuer key. On protocol vulnerability: freeze issuance, await v2.0.

Delegation Blast Radius: A compromised sub-agent is limited to actions within its signed scope. Compromise does not grant access beyond the leaf delegation's scope_constraints. Verifiers enforce scope server-side — a compromised agent cannot self-elevate.

Delegation Scope Enforcement: Scope is enforced by verifiers, not by issuers. The scope_hash in the signed delegation credential is the authoritative record of permitted scope. Presented scope_constraints that do not hash to scope_hash are rejected.

Sub-Delegation Cascade Revocation: Revoking a parent delegation invalidates all sub-delegations in the chain. Verifiers MUST check revocation status for each link in the chain, not just the leaf.

Sub-Delegation Device Key Linkability: Sub-delegation is signed by the delegator's device key (§7.18). This cryptographically links the delegator's device key to the sub-credential — an intentional tradeoff. Linkability is limited to the issuer and the delegated agent, not exposed to arbitrary verifiers. The delegator_credential_id already links the chain structurally; device key signing adds non-repudiation for the delegation act itself. Randomised vs deterministic signing does not matter here because linkability is inherent in the delegation chain structure.

19. Protocol Invariants

Determinism: All operations produce identical outputs for identical inputs. No randomness except CSPRNG for salts/nonces.

Isolation: No network operations in core protocol. No filesystem operations during verification. No external dependencies in cryptographic operations.

Timing: Application layer MUST provide trustworthy current_time parameter to core engine. Time comparisons use absolute value of difference.

Memory: Stack allocation bounded at 4KB for no_std. No dynamic allocation in verification path. All buffers statically sized.

Concurrency: No shared mutable state. No threading in core engine. Verification is single-threaded only.

20. Credential Issuance Constraints

Rate Limiting: Implementation-specific maximum issuance rate. No protocol-level rate limit.

Batch Issuance: NOT supported in v1.0. Each credential issued independently.

Modification: No in-place attribute modification. Changes require full credential reissuance with new credential_id.

Holder Binding: MUST occur at issuance time. Cannot rebind credential to different holder.

Attribute Sources: Attributes provided at issuance only. No dynamic attribute resolution.

Validity Period: Maximum 365 days (MAX_CREDENTIAL_LIFETIME). Minimum determined by application.

Issuer Key Rotation: No automated rotation protocol. Manual process: (1) Generate new key pair, (2) Update issuer ID, (3) Begin issuing with new key, (4) Maintain old key for verification only, (5) Revoke old credentials as they expire.

Delegation Credentials: Scope MUST be provided at issuance — zero-scope delegations are prohibited. delegation_depth MUST be ≤ max_delegation_depth MUST be ≤ MAX_DELEGATION_DEPTH (5). scope_hash MUST be computed from the canonical CBOR of the provided ScopeConstraints before signing. Lifetime: minimum MIN_DELEGATION_LIFETIME (60s), maximum MAX_DELEGATION_LIFETIME (86,400s) for ephemeral sub-delegations; root delegations MAY use standard MAX_CREDENTIAL_LIFETIME (365d).

21. Deployment Profiles

21.1 High Security Profile

  • Clock skew: 60 seconds (HIGH_SECURITY_CLOCK_SKEW)
  • Proximity: required for all presentations
  • Failure mode: fail closed on any error
  • SMT freshness: 24 hours maximum
  • Replay cache: 24 hours retention
  • Audit: comprehensive logging with remote backup

21.2 Standard Profile

  • Clock skew: 300 seconds (DEFAULT_CLOCK_SKEW)
  • Proximity: optional, policy-driven
  • Failure mode: fail open with audit flag on recoverable errors
  • SMT freshness: 7 days maximum (MAX_SMT_ROOT_AGE)
  • Replay cache: 15 minutes retention (MIN_REPLAY_CACHE_TTL)
  • Audit: local logging only

21.3 Offline Profile

  • Clock skew: 600 seconds (MAX_CLOCK_SKEW)
  • Proximity: not supported
  • Failure mode: accept with warning on stale SMT roots
  • SMT freshness: 30 days acceptable with warning
  • Replay cache: best-effort in-memory only
  • Audit: optional

22. Version Commitment

This specification defines Exqub V1.0. V1.0 includes: standard credentials (credential_type 0x01), delegation credentials (credential_type 0x02), chain linking (via attribute conventions on any credential type), and content attestation credentials (credential_type 0x04). Credential type 0x03 is reserved for future use.

Versioning Rules: No version negotiation — exact match required (0x01). Unknown versions MUST be rejected. Version field checked first in all structures.

Stability: The normative sections (§1–§16) are stable for V1.0. Non-normative guidance sections (§23–§27) may be updated without a version increment.

Future Considerations: V2.0 MAY add: threshold credentials (k-of-n), conditional disclosure (predicate proofs), blind signatures, additional signature algorithms, formal verification proofs. V2.0 MAY introduce breaking changes. Migration strategy will be defined with the V2.0 specification.

23. Agent Authorization Architecture (Non-Normative)

23.1 Problem Statement

Traditional API key authorization has three failure modes for AI agents: (1) blast radius — a compromised agent key has full API access; (2) auditability — no cryptographic record of what an agent was authorized to do; (3) delegation — no mechanism for agents to spawn sub-agents with narrowed authority. Delegation credentials address all three.

23.2 Threat Model

  • Prompt injection: An attacker embeds instructions in external content to hijack an agent's actions. Scope constraints limit the damage — the agent cannot act outside its signed scope even if manipulated.
  • Hallucination: An agent requests a resource it is not authorized for. The verifier rejects the action per scope.
  • Misconfiguration: A developer grants an agent overly broad authority. The max_delegation_depth and scope constraints provide explicit audit trails.
  • Compromised runtime: An agent runtime is fully compromised. Scope constraints limit the blast radius to the signed scope.

23.3 Actors

Actor Role
Delegator Holds a standard credential (0x01) and delegates authority to an agent
Agent Holds a delegation credential (0x02) issued by the delegator
Service Verifies delegated action presentations against the issuer's revocation tree
Sub-Agent Holds a delegation credential issued by an agent (sub-delegation)

23.4 Use Cases

Procurement agent: An employee delegates invoice approval authority up to $500 to an AI purchasing agent. The agent's scope: actions=["approve_invoice"], resource_patterns=["invoices/*"], max_value=50000 (cents).

Clinical agent: A physician delegates read access to patient records to a diagnostic AI. The scope includes required_attestations=["hipaa_trained"], enforcing that the agent must present attestation of HIPAA compliance training.

Trading agent: A financial institution delegates order execution to an algorithmic agent with max_daily_value and time_window constraints.

23.5 Integration Patterns

Framework SDKs (§25) wrap the holder and verifier libraries. The general pattern:

  1. Delegator calls issuer API to create delegation credential
  2. Agent receives credential via claim URL (same flow as §7 holder ID)
  3. Agent constructs DelegatedActionPresentation using exqub-holder
  4. Service verifies via exqub-verifier verify_delegated_action()

24. Agent Attestation Attributes (Non-Normative)

Agent attestation attributes are carried as standard attributes on delegation credentials (credential_type 0x02). They are NOT a separate credential type. These attribute keys are reserved:

Attribute Key Type Required Description
agent_model_id string OPTIONAL AI model identifier (e.g., "claude-sonnet-4-6")
agent_model_version string OPTIONAL Model version string
agent_model_hash hex, 64 chars OPTIONAL SHA3-256 of model weights
agent_runtime string OPTIONAL Runtime identifier (e.g., "langchain-0.3")
agent_runtime_hash hex, 64 chars OPTIONAL SHA3-256 of runtime binary
safety_alignment_version string OPTIONAL Safety alignment specification version
safety_attestation_hash hex, 64 chars OPTIONAL SHA3-256 of safety evaluation results
training_data_hash hex, 64 chars OPTIONAL SHA3-256 of training data manifest
guardrail_policy_hash hex, 64 chars OPTIONAL SHA3-256 of guardrail policy document

Selective disclosure: An agent may present agent_model_id without revealing training_data_hash — standard attribute Merkle tree selective disclosure applies.

Attestation freshness: These attributes represent point-in-time facts (what was true at issuance). Model updates require credential reissuance.

Required attestations: A scope constraint's required_attestations field lists attribute keys the agent MUST disclose in the action presentation. Example: required_attestations=["safety_alignment_version"] forces disclosure of the safety spec version before any action is authorized.

25. Framework Integration Guidance (Non-Normative)

25.1 General SDK Pattern

All framework integrations follow this interface:

// Credential-aware tool execution
fn execute_with_credential(
    presentation: DelegatedActionPresentation,
    action: ActionRequest,
) -> Result<ToolOutput, VerificationError>

The SDK wraps this in framework-specific tool/function call patterns.

25.2 LangChain

Use CredentialAwareTool wrapping standard LangChain tools. The tool verifies the DelegatedActionPresentation before executing. The credential is threaded through the RunnableConfig context.

25.3 AutoGen

In multi-agent conversations, each agent holds a delegation credential issued by the orchestrator. Sub-agents receive sub-delegations with narrowed scope. The CredentialedAgent base class handles presentation construction automatically.

25.4 CrewAI

Crew roles map to delegation scopes. A "Researcher" role receives a delegation with actions=["search","read"]; a "Writer" role receives actions=["write","publish"]. Role credentials are issued at crew initialization.

25.5 LlamaIndex

Query engines receive delegation credentials scoped to specific index resources. credential_gated_query() verifies the agent's scope before executing a query against the index.

25.6 Minimum Supported Versions

Framework Minimum Version
LangChain 0.3.x
AutoGen 0.4.x
CrewAI 0.8.x
LlamaIndex 0.11.x

26. Agent and Audit Deployment Profiles (Non-Normative)

26.1 Agent Deployment Profile

For AI agent authorization scenarios with short-lived credentials and strict enforcement:

  • Clock skew: 60 seconds (HIGH_SECURITY_CLOCK_SKEW) — agent credentials are short-lived
  • Delegation depth: enforce MAX_DELEGATION_DEPTH (5); prefer ≤ 3 for most deployments
  • Scope: require explicit scope constraints; reject zero-scope delegations
  • Revocation: check revocation status for every link in the chain on every request
  • Credential lifetime: prefer MAX_DELEGATION_LIFETIME (86,400s) or shorter
  • Replay cache: required — agents make repeated requests
  • Failure mode: fail closed on any error
  • Audit: log all delegated action presentations with full chain metadata

26.2 Audit Deployment Profile

For regulatory audit and decision log verification with lenient historical settings:

  • Clock skew: 600 seconds (MAX_CLOCK_SKEW) — historical credentials may have loose timestamps
  • Revocation mode: audit mode (revoked entries flagged, not rejected) — allows full chain traversal
  • SMT freshness: accept stale roots with STATUS_STALE_ROOT warning for historical verification
  • Credential lifetime: accept expired credentials for audit purposes (with explicit allow_expired flag)
  • Replay cache: not applicable for historical verification
  • Failure mode: fail open with comprehensive audit flag for recoverable errors
  • Chain length: enforce MAX_CHAIN_LENGTH but accept up to operational limit
  • Audit: export full chain with selective disclosure for regulatory review

27. Future Considerations (Non-Normative)

The following capabilities are out of scope for V1.0 and are design sketches only. No stability guarantee.

27.1 Threshold Credentials (k-of-n)

A credential requiring k-of-n issuers to sign. Enables multi-party approval workflows. Would require a new credential_type and threshold signature construction. Candidate: FROST (Flexible Round-Optimized Schnorr Threshold Signatures).

27.2 Conditional Disclosure

Zero-knowledge proofs allowing "age > 18" without revealing age. Would require a ZK circuit per predicate and a new proof verification step. Candidate: Groth16 or PLONK on a pairing-friendly curve.

27.3 Vector-Compatible Schemas

Structured attribute schemas enabling semantic search over credentials. A holder could prove membership in a set of credentials matching a vector similarity threshold. Requires standardised schema registry.

27.4 Formal Verification (Lean 4)

Machine-checked proofs of the verification algorithm's correctness properties: (1) soundness (a forged credential is rejected), (2) completeness (a valid credential is accepted), (3) scope monotonicity (child scope cannot exceed parent scope). Candidate toolchain: Lean 4 with Mathlib.