Elydora Responsibility
Protocol Specification.
This document defines the Elydora Responsibility Protocol (ERP) version 1.0. It specifies the data structures, cryptographic algorithms, signing procedures, chain integrity model, admission rules, attestation receipts, epoch sealing mechanism, evidence storage model, agent state control, and verification procedures that together constitute the protocol. This specification is normative. All implementations claiming ERP v1.0 conformance MUST adhere to the requirements defined herein.
1. Protocol Overview
The Elydora Responsibility Protocol (ERP) is an application-layer protocol designed to establish cryptographically verifiable, tamper-evident responsibility records for autonomous AI Agent operations. ERP operates on the principle that every action taken by an AI Agent within an organizational boundary MUST produce an immutable, non-repudiable evidence record that can be independently verified by any party with access to the relevant public keys and chain data.
The protocol is transport-agnostic in its core data model, though the reference implementation operates over HTTPS with JSON payloads. ERP does not prescribe the semantics of agent operations; rather, it provides the cryptographic envelope and chain integrity guarantees that transform opaque agent actions into evidence-grade accountability records suitable for enterprise compliance, legal proceedings, and regulatory audit.
ERP is designed to function as infrastructure-level plumbing for agent accountability, analogous to how TLS provides transport security or how OAuth provides delegated authorization. Organizations integrate ERP into their agent orchestration pipelines to achieve continuous, automatic accountability without modifying agent business logic.
1.1 Design Goals
The protocol is designed to satisfy the following goals, listed in order of priority:
Evidence Integrity. Every operation record MUST be cryptographically signed by the submitting agent using Ed25519. The signature covers the entire operation envelope including payload hash, chain linkage, timestamps, and nonce. Any modification to any field after signing invalidates the signature. This guarantee ensures that operation records constitute authentic evidence of agent intent at the time of signing.
Non-Repudiation. Once an agent submits a signed operation and receives an attestation receipt, the agent cannot deny having submitted that operation. The combination of the agent's Ed25519 signature on the operation record and the Elydora platform's counter-signature on the attestation receipt creates a dual-signed evidence chain. The agent's private key is the sole means of producing a valid signature, and the platform's receipt confirms admission into the immutable record. Together, these two signatures provide cryptographic non-repudiation that satisfies the requirements of most enterprise compliance frameworks and legal evidentiary standards.
Independent Verifiability. Any party in possession of the relevant public keys, the operation record, and the attestation receipt MUST be able to independently verify the authenticity and integrity of the record without contacting the Elydora platform. Verification requires no trusted third party beyond the initial key registration. This property is critical for audit scenarios where the verifier may not trust the platform operator. The Merkle epoch proofs extend this property to batch verification, allowing auditors to verify that a specific operation was included in a sealed epoch without downloading the entire epoch dataset.
Enterprise Audit Compliance. The protocol is designed to produce records that satisfy the requirements of SOC 2 Type II, ISO 27001, GDPR Article 30 record-keeping, and the Federal Rules of Evidence (FRE 901(b)(9)) for self-authenticating records. Operation records include timestamps, chain hashes, sequence numbers, and organizational binding that together create a complete audit trail. Epoch sealing with optional RFC 3161 timestamp authority anchoring provides independent temporal attestation suitable for regulatory filings.
Operational Simplicity. The protocol is designed to minimize integration burden. An agent SDK handles all cryptographic operations, chain state management, and receipt verification transparently. The submitting agent need only provide the operation type, subject, action, and payload; the SDK handles nonce generation, chain hash computation, signing, submission, and receipt verification. This design goal ensures that ERP adoption does not require agents to implement complex cryptographic logic.
1.2 Threat Model
ERP considers the following threat categories and provides explicit mitigations for each:
Replay Attacks. An adversary captures a valid signed operation and re-submits it to the platform. Mitigation: each operation includes a cryptographically random nonce that MUST be unique within the TTL window. The platform maintains a nonce cache (implemented via Cloudflare KV with TTL expiration) and rejects any operation whose nonce has been seen before. Additionally, the chain hash linkage ensures that even if a nonce check were bypassed, the replayed operation would fail the prev_chain_hash verification because the chain will have advanced since the original submission.
Operation Forgery. An adversary fabricates an operation record purporting to be from a legitimate agent. Mitigation: every operation MUST carry a valid Ed25519 signature produced with the agent's private key. The platform verifies this signature against the registered public key before admission. Without the agent's 32-byte Ed25519 seed, producing a valid signature is computationally infeasible (security level approximately 2^128).
Chain Manipulation. An adversary attempts to insert, delete, or reorder operations in an agent's chain. Mitigation: each operation includes a prev_chain_hash that MUST match the chain_hash computed for the immediately preceding operation. This creates a hash chain analogous to a blockchain, where any modification to any historical operation would invalidate all subsequent chain hashes. The platform enforces strict sequential ordering via monotonically increasing sequence numbers with a UNIQUE constraint on (agent_id, seq_no). Any gap or inconsistency in the chain is immediately detectable through chain verification procedures defined in Section 13.
Evidence Tampering. An adversary (including a compromised platform operator) attempts to modify stored evidence after admission. Mitigation: evidence is stored in Cloudflare R2 with immutability controls. Additionally, epoch sealing computes Merkle roots over batches of operations, and optional RFC 3161 TSA anchoring provides an independent temporal proof from a trusted third party. Even if R2 storage were compromised, the Merkle proofs and TSA timestamps would reveal the tampering.
Timestamp Manipulation. An agent submits an operation with a falsified issued_at timestamp to create a misleading temporal record. Mitigation: the platform records server_received_at independently and enforces TTL validation. If issued_at + ttl_ms is less than server_received_at, the operation is rejected. This bounds the acceptable clock skew and prevents backdated operations. The server_received_at timestamp in the attestation receipt provides a platform-attested temporal anchor independent of the agent's claimed timestamp.
1.3 Security Objectives
The protocol provides the following security properties:
Data Integrity. The combination of Ed25519 signatures on operation records, SHA-256 payload hashes, and SHA-256 chain hashes ensures that any modification to any component of an operation record is detectable. The signature covers the canonicalized representation of all fields except the signature itself, providing integrity protection for the entire record envelope.
Non-Repudiation. The agent's Ed25519 signature constitutes a cryptographic commitment that the agent (or more precisely, the holder of the agent's private key) authorized the operation at or before the timestamp indicated by issued_at. The platform's counter-signature on the attestation receipt confirms that the platform admitted the operation at server_received_at. These two independent signatures prevent either party from denying their role in the transaction.
Temporal Accountability. Each operation carries two temporal markers: the agent-asserted issued_at and the platform-attested server_received_at. The TTL mechanism bounds the temporal window, and epoch sealing with optional TSA anchoring provides independent third-party temporal attestation. Together, these mechanisms establish when an operation occurred with sufficient precision for audit and compliance purposes.
Chain Continuity. The hash chain model ensures that the complete history of an agent's operations forms a verifiable, ordered sequence. Any attempt to insert, delete, or reorder operations breaks the chain. The chain can be verified end-to-end by any party with access to the operation records, without requiring trust in the platform operator.
1.4 Scope
This specification defines the following aspects of the Elydora Responsibility Protocol:
• Data structures for operation records (EOR), attestation receipts (EAR), and epoch records (EER).
• Cryptographic algorithms and procedures for signing, verification, hashing, and chain computation.
• The admission validation pipeline that the platform MUST execute for each submitted operation.
• The epoch sealing mechanism including Merkle tree construction and optional TSA anchoring.
• The evidence storage model and immutability guarantees.
• Agent state lifecycle and key management procedures.
• Verification procedures for single operations, chains, and epochs.
This specification does NOT define: the semantics of agent operations (operation_type, subject, action, and payload are opaque to the protocol), agent orchestration or scheduling logic, business rules for when agents should or should not perform actions, the internal architecture of the Elydora platform (though informative references to the Cloudflare Workers implementation are provided), or client SDK APIs (which are defined in the Elydora Quick Start guide).
2. Terminology
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 and RFC 8174. When these keywords appear in lowercase, they carry their natural English meaning and are not to be interpreted as RFC 2119 keywords.
| Term | Definition |
|---|---|
| Agent | A registered entity bound to an Organization that possesses one or more Ed25519 key pairs and is authorized to submit operation records. Each Agent is identified by a unique agent_id string and is associated with a Responsible Entity. Agents represent autonomous AI systems, automated workflows, or human-supervised AI processes that perform actions requiring accountability tracking. An Agent's identity is established through the registration process defined in Section 4.1 and is immutably bound to its Organization. |
| Elydora Operation Record (EOR) | The fundamental data unit of the protocol. A signed JSON envelope containing all information necessary to establish who performed what action, when, with what authorization, and in what sequential context. The EOR includes the agent's Ed25519 signature, payload hash, chain linkage, timestamps, and replay-prevention nonce. The complete field specification is defined in Section 5. |
| Elydora Chain Hash (ECH) | A SHA-256 hash value that cryptographically links each operation to its predecessor in an agent's operation chain. The chain hash is computed as: SHA-256(prev_chain_hash + "|" + payload_hash + "|" + operation_id + "|" + issued_at), where "|" is a literal pipe character used as a delimiter. The chain hash is base64url-encoded. Chain hashes create a sequential, tamper-evident linked list of all operations for a given agent. |
| Elydora Acknowledgment Receipt (EAR) | A platform-signed admission receipt returned for every successfully admitted operation. The EAR contains the operation_id, computed chain_hash, assigned sequence number, server timestamp, and the Elydora platform's Ed25519 counter-signature. The EAR constitutes the platform's cryptographic attestation that the operation was received, validated, and admitted into the immutable record. The complete field specification is defined in Section 9. |
| Elydora Epoch Record (EER) | A Merkle tree rollup that aggregates all operations within a defined time window into a single verifiable root hash. The EER enables efficient batch verification and optional anchoring to external timestamp authorities via RFC 3161. Epoch records are defined in Section 10. |
| Organization | The tenant boundary within the Elydora platform. All agents, operations, receipts, and epochs are scoped to exactly one Organization, identified by org_id. Cross-organization operations are not supported by the protocol. Organizations represent legal entities (companies, institutions, government agencies) that deploy AI agents and require accountability records. |
| Responsible Entity | The legal entity (person, department, team, or organization) that bears legal and operational responsibility for an Agent's actions. The Responsible Entity is recorded at agent registration time and is included in all operation records by reference through the agent_id binding. This field supports organizational accountability requirements and regulatory compliance. |
| Key ID (kid) | A unique identifier for a specific Ed25519 public key registered to an Agent. Each Agent MAY have multiple keys (for rotation purposes), but each key is identified by a unique kid string. The kid is included in operation records to identify which key was used for signing, enabling key rotation without breaking chain verification. Key IDs are arbitrary strings assigned at key registration time. |
| Genesis Chain Hash | The initial prev_chain_hash value used for the first operation in an Agent's chain. The Genesis Chain Hash is the base64url encoding of 32 zero bytes: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA". This well-known constant allows any verifier to identify and validate the first operation in a chain without ambiguity. |
| Nonce | A cryptographically random value included in each operation record to prevent replay attacks. The nonce MUST be at least 16 bytes of cryptographically secure random data, base64url-encoded. The maximum encoded length is 64 characters. Nonces are tracked by the platform within the TTL window to detect and reject duplicates. |
| TTL (Time-to-Live) | The validity window for an operation, expressed in milliseconds. The platform rejects operations where issued_at + ttl_ms is less than server_received_at, indicating that the operation has expired. The minimum TTL is 1,000ms and the maximum is 300,000ms (5 minutes). TTL bounds the acceptable clock skew between agent and platform and limits the window for replay attacks. |
| Signing Input | The byte sequence over which an Ed25519 signature is computed. For EOR signatures, the signing input is the UTF-8 encoding of the JCS-canonicalized (RFC 8785) JSON representation of the EOR with the "signature" field excluded. The raw canonical bytes are signed directly; they are NOT hashed before signing (Ed25519 internally applies SHA-512 as part of its signing algorithm per RFC 8032). |
| JCS (JSON Canonicalization Scheme) | The deterministic JSON serialization algorithm defined in RFC 8785. JCS produces a unique, canonical byte representation for any JSON value by sorting object keys lexicographically by their UTF-16 code unit values, removing all insignificant whitespace, and applying ES2015 number serialization rules. JCS is used throughout the protocol to ensure that signing inputs and hash inputs are deterministic across implementations. |
| Elydora Server Key | The Ed25519 key pair used by the Elydora platform to sign attestation receipts and epoch records. The server key ID is "elydora-server-key-v1". The corresponding public key is published via the JWKS endpoint at /.well-known/elydora/jwks.json. The server key provides the platform's non-repudiation guarantee: clients can verify that a receipt was genuinely issued by the Elydora platform. |
| PKCS#8 Wrapper | The ASN.1 DER encoding wrapper used to import Ed25519 private keys via the Web Crypto API. The PKCS#8 prefix for Ed25519 keys is the fixed 16-byte sequence: [0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20]. The 32-byte Ed25519 seed is appended to this prefix to produce the complete PKCS#8-encoded private key. |
3. Cryptographic Foundations
The Elydora Responsibility Protocol relies on a small, well-understood set of cryptographic primitives. This section defines each primitive, its configuration within ERP, and the specific standards that implementations MUST conform to. No algorithm agility is provided in protocol version 1.0; all implementations MUST use exactly the algorithms specified below.
3.1 Ed25519 Signatures
All digital signatures in the protocol MUST use the Ed25519 algorithm as defined in RFC 8032 (Edwards-Curve Digital Signature Algorithm, EdDSA). Ed25519 operates over the Edwards curve Curve25519 and provides approximately 128 bits of security. The algorithm is deterministic: signing the same message with the same key always produces the same signature, eliminating a class of implementation vulnerabilities associated with random nonce generation in ECDSA.
Key Generation. An Ed25519 key pair consists of a 32-byte private seed and a 32-byte public key. The private seed MUST be generated using a cryptographically secure random number generator (CSPRNG). The public key is deterministically derived from the private seed via the Ed25519 key derivation function specified in RFC 8032 Section 5.1.5. Implementations MUST NOT reuse private seeds across different agents or organizations.
Signature Size. Ed25519 signatures are exactly 64 bytes. When encoded for inclusion in JSON documents, signatures MUST use base64url encoding per RFC 4648 Section 5, without padding characters. The resulting encoded string is 86 characters.
Public Key Encoding. Ed25519 public keys are exactly 32 bytes. When encoded for JSON transport or storage, public keys MUST use base64url encoding per RFC 4648 Section 5, without padding characters. The resulting encoded string is 43 characters.
Web Crypto API Usage. When using the Web Crypto API (as in Cloudflare Workers), Ed25519 keys are imported as follows: the 32-byte raw public key is imported with algorithm "Ed25519" and usage ["verify"]. The 32-byte private seed requires PKCS#8 wrapping: the fixed 16-byte ASN.1 prefix [0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05, 0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22, 0x04, 0x20] is prepended to the 32-byte seed to produce a 48-byte PKCS#8-encoded private key, which is then imported with algorithm "Ed25519" and usage ["sign"].
// PKCS#8 prefix for Ed25519 private keys
const PKCS8_PREFIX = new Uint8Array([
0x30, 0x2e, 0x02, 0x01, 0x00, 0x30, 0x05,
0x06, 0x03, 0x2b, 0x65, 0x70, 0x04, 0x22,
0x04, 0x20
]);
// Import Ed25519 private key (32-byte seed)
function importPrivateKey(seed: Uint8Array): Promise<CryptoKey> {
const pkcs8 = new Uint8Array(48);
pkcs8.set(PKCS8_PREFIX, 0);
pkcs8.set(seed, 16);
return crypto.subtle.importKey(
"pkcs8", pkcs8, "Ed25519", false, ["sign"]
);
}
// Import Ed25519 public key (32 raw bytes)
function importPublicKey(raw: Uint8Array): Promise<CryptoKey> {
return crypto.subtle.importKey(
"raw", raw, "Ed25519", true, ["verify"]
);
}3.2 SHA-256
SHA-256 as defined in FIPS 180-4 is the sole hash function used in the protocol. Implementations MUST use SHA-256 for all hash computations. SHA-256 produces a 32-byte (256-bit) digest. The protocol uses SHA-256 for the following purposes:
• Payload hashing: Computing the payload_hash field of an EOR by hashing the JCS-canonicalized payload.
• Chain hash computation: Computing the chain_hash that links each operation to its predecessor.
• Merkle tree node hashing: Computing internal nodes of the Merkle tree during epoch sealing.
• Receipt hash computation: Computing the receipt_hash field of an EAR.
All SHA-256 outputs MUST be encoded as base64url (RFC 4648 Section 5) without padding when included in JSON documents. The resulting encoded string is 43 characters for a 32-byte digest.
// SHA-256 hash computation with base64url output
async function sha256(data: string): Promise<string> {
const encoded = new TextEncoder().encode(data);
const hashBuffer = await crypto.subtle.digest("SHA-256", encoded);
return base64url(new Uint8Array(hashBuffer));
}
// base64url encoding without padding (RFC 4648 §5)
function base64url(bytes: Uint8Array): string {
const binary = String.fromCharCode(...bytes);
return btoa(binary)
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}3.3 JSON Canonicalization Scheme (JCS)
The Elydora Responsibility Protocol uses JSON Canonicalization Scheme (JCS) as defined in RFC 8785 for all deterministic JSON serialization. JCS is REQUIRED whenever a JSON value must be hashed or signed. Without canonicalization, semantically identical JSON documents could produce different byte representations (due to key ordering, whitespace, or number formatting differences), leading to signature verification failures across implementations.
Canonicalization Rules. JCS applies the following transformations:
1. Object key ordering: Object keys MUST be sorted lexicographically by their UTF-16 code unit values. For ASCII keys (which all ERP field names are), this is equivalent to standard lexicographic sorting.
2. Whitespace removal: All insignificant whitespace (spaces, tabs, newlines, carriage returns between tokens) MUST be removed. The output contains no whitespace except within string values.
3. Number serialization: Numbers MUST be serialized according to ECMAScript 2015 (ES6) serialization rules. Integer values are represented without decimal points or exponents. Floating-point values use the shortest representation that uniquely identifies the value.
4. String escaping: String values MUST use minimal escaping. Only the characters mandated by JSON (quotation mark, reverse solidus, and control characters U+0000 through U+001F) are escaped. Forward solidus is NOT escaped.
5. Recursive application: Canonicalization MUST be applied recursively to all nested objects and arrays.
// JCS canonicalization (RFC 8785)
function canonicalize(value: unknown): string {
if (value === null || typeof value !== "object") {
return JSON.stringify(value);
}
if (Array.isArray(value)) {
return "[" + value.map(canonicalize).join(",") + "]";
}
const keys = Object.keys(value as Record<string, unknown>)
.sort(); // lexicographic sort by UTF-16 code units
const pairs = keys.map(
(k) => JSON.stringify(k) + ":" +
canonicalize((value as Record<string, unknown>)[k])
);
return "{" + pairs.join(",") + "}";
}3.4 Base64url Encoding
All binary values (signatures, hashes, nonces, public keys) included in JSON documents MUST be encoded using the base64url alphabet defined in RFC 4648 Section 5. The base64url alphabet replaces "+" with "-" and "/" with "_" compared to standard base64, making the encoded values safe for use in URLs, JSON strings, and HTTP headers without additional escaping.
Padding. Padding characters ("=") MUST NOT be included in base64url-encoded values within the protocol. Implementations MUST strip trailing "=" characters during encoding and MUST accept values without padding during decoding.
Reference Lengths. The following base64url-encoded lengths apply to protocol values:
| Value | Raw Bytes | Base64url Length |
|---|---|---|
| Ed25519 Public Key | 32 | 43 characters |
| Ed25519 Signature | 64 | 86 characters |
| SHA-256 Digest | 32 | 43 characters |
| Nonce (16 bytes min) | 16-48 | 22-64 characters |
| Genesis Hash (32 zero bytes) | 32 | 43 characters |
3.5 Nonce Generation
Each EOR MUST include a nonce field containing a unique, unpredictable value for replay prevention. The nonce MUST be generated as follows:
1. Generate at least 16 bytes (128 bits) of cryptographically secure random data using a CSPRNG (e.g., crypto.getRandomValues() in Web Crypto API or crypto.randomBytes() in Node.js).
2. Encode the random bytes using base64url (RFC 4648 Section 5) without padding.
3. The resulting encoded string MUST NOT exceed 64 characters in length. Implementations SHOULD use exactly 16 bytes (producing a 22-character encoded string) unless there is a specific reason to use more entropy.
The platform MUST reject any EOR with a nonce exceeding 64 characters. The platform MUST reject any EOR whose nonce has been previously observed within the TTL window. Nonce uniqueness tracking is implemented via Cloudflare KV with key "nonce:{nonce_value}" and a TTL equal to the maximum allowed ttl_ms (300,000ms = 5 minutes).
// Nonce generation
function generateNonce(): string {
const bytes = new Uint8Array(16); // 128 bits
crypto.getRandomValues(bytes);
return base64url(bytes); // 22-character string
}3.6 TTL Validation
The TTL (Time-to-Live) mechanism bounds the temporal validity of an operation to prevent delayed replay and limit the impact of clock skew between agent and platform.
Constraints:
• Minimum ttl_ms: 1,000 (1 second). Operations with ttl_ms < 1,000 MUST be rejected.
• Maximum ttl_ms: 300,000 (5 minutes). Operations with ttl_ms > 300,000 MUST be rejected.
• Default ttl_ms: Implementations SHOULD default to 30,000 (30 seconds) when not explicitly specified by the agent.
Expiration Check. The platform MUST compute the expiration as: expiration = issued_at + ttl_ms. If expiration < server_received_at, the operation has expired and MUST be rejected with error code TTL_EXPIRED. The comparison uses strict less-than; an operation that expires at exactly the server receive time is accepted.
// TTL validation
function validateTTL(
issued_at: number,
ttl_ms: number,
server_received_at: number
): { valid: boolean; error?: string } {
if (ttl_ms < 1000)
return { valid: false, error: "TTL below minimum (1000ms)" };
if (ttl_ms > 300000)
return { valid: false, error: "TTL above maximum (300000ms)" };
if (issued_at + ttl_ms < server_received_at)
return { valid: false, error: "TTL_EXPIRED" };
return { valid: true };
}4. Identity Binding Model
The Identity Binding Model defines how agents, keys, organizations, and roles are registered, associated, and governed within the Elydora platform. Identity binding is the foundation of the protocol's non-repudiation guarantees: every operation is cryptographically bound to a specific agent, which is itself bound to a specific organization and responsible entity.
4.1 Agent Registration
Each Agent MUST be registered with the Elydora platform before submitting operations. The registration process establishes the binding between the Agent identity, the Organization, and at least one Ed25519 public key. Registration is performed by an authorized user with the org_owner or security_admin role via the Agent Registration API.
Required Registration Fields:
| Field | Type | Description |
|---|---|---|
| agent_id | string | Unique identifier for the Agent within the Organization. MUST be between 1 and 255 characters. MUST contain only alphanumeric characters, hyphens, underscores, and periods. MUST be unique within the Organization scope. |
| org_id | string | The Organization to which this Agent is bound. This binding is immutable; an Agent cannot be moved between Organizations after registration. |
| display_name | string | Human-readable name for the Agent. Used in audit reports and the Elydora Console UI. Maximum 255 characters. |
| responsible_entity | string | The legal entity (person, team, department, or organization) that bears responsibility for this Agent's actions. This field is recorded for compliance and audit purposes. Maximum 500 characters. |
| status | enum | The Agent's current operational status. One of: "active", "frozen", or "revoked". Newly registered Agents are always created with status "active". Status transitions are governed by Section 12. |
| keys | array | At least one Ed25519 public key MUST be registered at creation time. Each key entry includes: kid (Key ID), algorithm ("ed25519"), public_key (base64url-encoded 32-byte public key), and status ("active"). |
Upon successful registration, the platform assigns a created_at timestamp and initializes the Agent's chain state with seq_no = 0 and latest_chain_hash = Genesis Chain Hash. The Agent is immediately eligible to submit operations.
4.2 Key Binding
Each Agent MAY have multiple Ed25519 key pairs registered, identified by unique Key IDs (kid). Multiple keys enable key rotation without service interruption: a new key can be registered and activated before the old key is retired.
Key Registration Fields:
| Field | Type | Description |
|---|---|---|
| kid | string | Unique identifier for this key within the Agent's key set. MUST be between 1 and 255 characters. Examples: "key-2026-q1", "primary", "backup-key-1". |
| algorithm | string | MUST be "ed25519". No other algorithms are supported in protocol version 1.0. |
| public_key | string | The 32-byte Ed25519 public key, base64url-encoded (43 characters). The platform MUST verify that the decoded value is exactly 32 bytes. |
| status | enum | One of: "active", "retired", or "revoked". Newly registered keys are created with status "active". |
Key States:
• Active: The key MAY be used for signing new operations. Only active keys are accepted during EOR signature verification.
• Retired: The key MUST NOT be used for signing new operations. Retired keys are retained for verifying historical operations that were signed with that key. Retirement is the normal end-of-life transition for a key that has been gracefully rotated.
• Revoked: The key MUST NOT be used for any purpose. Revocation indicates that the key may have been compromised. Revocation is irreversible. Revoked keys are retained for forensic purposes but are flagged as potentially compromised in audit reports.
State Transitions: The following transitions are permitted: active → retired (graceful rotation), active → revoked (emergency compromise response). The transitions retired → active and revoked → active are NOT permitted. Once a key is retired or revoked, it cannot be reactivated. A new key MUST be registered instead.
4.3 Organization Binding
All protocol entities (agents, operations, receipts, epochs) are scoped to exactly one Organization, identified by org_id. The Organization is the tenant boundary within the Elydora platform.
• An Agent's org_id is set at registration time and is immutable.
• Every EOR MUST include the org_id field, and the platform MUST verify that it matches the Agent's registered org_id.
• Cross-organization operations are NOT supported. An Agent registered to Organization A cannot submit operations referencing Organization B.
• API authentication tokens are scoped to a single Organization. A token for Organization A cannot be used to access or modify resources in Organization B.
• Epoch sealing operates within a single Organization. All operations in an epoch belong to the same org_id.
4.4 RBAC Model
Access to Elydora platform resources is governed by a Role-Based Access Control (RBAC) model. Each user or API client is assigned one or more roles within an Organization. Roles determine which API endpoints and operations the principal may access.
| Role | Permissions |
|---|---|
| org_owner | Full administrative access. MAY create, modify, freeze, revoke, and delete agents. MAY register, retire, and revoke keys. MAY manage organization settings, billing, and user access. MAY view all operations, receipts, epochs, and audit logs. MAY export evidence bundles. MAY assign roles to other users. |
| security_admin | Security-focused administrative access. MAY freeze and revoke agents. MAY retire and revoke keys. MAY view all operations, receipts, epochs, and audit logs. MAY export evidence bundles. MUST NOT create new agents or register new keys (to enforce separation of duties). MUST NOT modify organization settings or billing. |
| compliance_auditor | Read-only access to all accountability data. MAY view all operations, receipts, epochs, and audit logs. MAY export evidence bundles and verification reports. MAY initiate chain verification and epoch verification procedures. MUST NOT modify any agent, key, or organization state. |
| readonly_investigator | Scoped read-only access for incident investigation. MAY view operations, receipts, and audit logs for specified agents or time ranges. MAY verify individual operations and chain segments. MUST NOT access organization-wide data without explicit scope assignment. MUST NOT export bulk evidence bundles. |
| integration_engineer | Technical integration access. MAY register new agents and keys. MAY submit operations on behalf of agents (via API). MAY view operations and receipts for agents they manage. MUST NOT freeze, revoke, or delete agents. MUST NOT access audit logs or evidence exports. Intended for CI/CD pipelines and automated integration workflows. |
Role assignments are logged as admin_events (see Section 12.5). The platform MUST enforce role-based access control on every API request. Requests that exceed the caller's authorized permissions MUST be rejected with HTTP 403 Forbidden.
5. Elydora Operation Record (EOR)
The Elydora Operation Record (EOR) is the fundamental data unit of the protocol. Each EOR represents a single operation performed by an Agent, cryptographically signed and linked to the Agent's previous operation via the chain hash. The EOR is the atomic unit of accountability: it answers who did what, when, with what authorization, and in what sequential context.
5.1 Field Definitions
The following table defines every field in an EOR. All fields are REQUIRED unless explicitly marked OPTIONAL.
| Field | Type | Constraints | Semantics |
|---|---|---|---|
| op_version | string | MUST be "1.0" | Protocol version identifier. Enables future protocol evolution while maintaining backward compatibility. The platform MUST reject operations with unrecognized op_version values. |
| operation_id | string | UUIDv7 format (RFC 9562) | Globally unique identifier for this operation. UUIDv7 encodes a Unix millisecond timestamp in its most significant bits, providing natural chronological ordering. The operation_id serves as the idempotency key: if the platform receives two operations with the same operation_id, the second is rejected as a duplicate. This enables safe client-side retry without risk of double-submission. |
| org_id | string | Non-empty, max 255 chars | The Organization identifier. MUST match the Agent's registered org_id. Scopes the operation to the organizational tenant boundary. |
| agent_id | string | Non-empty, max 255 chars | The Agent identifier. MUST reference a registered Agent in the specified Organization. The Agent MUST be in "active" status at the time of submission. |
| issued_at | integer | Unix epoch milliseconds, > 0 | The timestamp at which the Agent generated and signed this operation, expressed as milliseconds since the Unix epoch (1970-01-01T00:00:00Z). This is the Agent's asserted timestamp; the platform independently records server_received_at in the receipt. The difference between these two values indicates clock skew or network latency. |
| ttl_ms | integer | 1,000 – 300,000 | Time-to-live in milliseconds. The operation expires and MUST be rejected if issued_at + ttl_ms < server_received_at. Bounds the temporal validity window to prevent delayed replay. See Section 3.6 for validation rules. |
| nonce | string | Base64url, max 64 chars | Cryptographically random value for replay prevention. MUST be at least 16 bytes of CSPRNG output, base64url-encoded. The platform rejects operations with previously-seen nonces within the TTL window. See Section 3.5 for generation rules. |
| operation_type | string | Non-empty, max 255 chars | A category label describing the type of operation. The protocol does not prescribe a taxonomy; this is application-defined. Examples: "payment.initiate", "document.sign", "trade.execute", "model.inference", "email.send". The value is opaque to the protocol but is indexed for querying and filtering. |
| subject | object | Non-null JSON object | Target resource descriptors identifying what the operation acts upon. The schema is application-defined and opaque to the protocol. Examples: {`{"account_id": "acct_123", "currency": "USD"}`} for a payment, or {`{"document_id": "doc_456"}`} for a document operation. The subject is included in the signed envelope and the payload hash, ensuring its integrity. |
| action | object | Non-null JSON object | Intent descriptors defining what action is being performed. The schema is application-defined and opaque to the protocol. Examples: {`{"type": "debit", "amount": 1500.00}`} for a payment, or {`{"type": "approve"}`} for an approval. The action is included in the signed envelope and the payload hash, ensuring its integrity. |
| payload | object | string | null | Max 256 KB (serialized) | The operation payload containing detailed data. MAY be a JSON object, a JSON string, or null. The payload is hashed (via payload_hash) and included in the chain hash computation but is otherwise opaque to the protocol. Maximum serialized size is 262,144 bytes (256 KB). Payloads exceeding this limit MUST be rejected. |
| payload_hash | string | Base64url, 43 chars | SHA-256 hash of the JCS-canonicalized payload, base64url-encoded. If payload is null, the hash is computed over the literal string "null". This field ensures payload integrity without requiring the full payload to be included in signature verification. See Section 6.3 for computation rules. |
| prev_chain_hash | string | Base64url, 43 chars | The chain_hash of the immediately preceding operation for this Agent, or the Genesis Chain Hash ("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") for the Agent's first operation. This field creates the hash chain linkage. The platform verifies this value against the stored latest_chain_hash for the Agent. See Section 7 for chain integrity rules. |
| agent_pubkey_kid | string | Non-empty, max 255 chars | The Key ID (kid) of the Ed25519 public key used to sign this operation. MUST reference an active key registered to the Agent. The platform uses this value to look up the correct public key for signature verification. |
| signature | string | Base64url, 86 chars | Ed25519 signature over the signing input. The signing input is the UTF-8 encoding of the JCS-canonicalized EOR with the "signature" field excluded. The raw canonical bytes are signed directly with Ed25519 (the algorithm internally applies SHA-512 per RFC 8032). See Section 6 for the complete signing procedure. |
5.2 EOR Encoding Rules
EOR documents MUST be encoded as JSON (RFC 8259) with the following constraints:
• Character encoding: UTF-8. All JSON text MUST be valid UTF-8.
• Whitespace: EOR documents submitted to the platform MAY contain whitespace for readability. The platform MUST parse the document per RFC 8259 rules regardless of whitespace. However, the signing input is always the JCS-canonicalized representation (which contains no insignificant whitespace).
• Field ordering: JSON objects are unordered by specification. Implementations MUST NOT depend on any particular field ordering in EOR documents. JCS canonicalization handles deterministic ordering for signing purposes.
• No duplicate keys: EOR documents MUST NOT contain duplicate keys at any nesting level. If duplicate keys are present, the behavior is undefined.
• No additional fields: EOR documents MUST NOT include fields not defined in Section 5.1. The platform SHOULD reject documents containing unrecognized fields to prevent ambiguity in the signed content.
5.3 Example EOR
The following is a complete, valid EOR with realistic field values:
{
"op_version": "1.0",
"operation_id": "019473a2-7c8b-7d4e-a1b3-5f8e9c2d4a6b",
"org_id": "org_acme_corp",
"agent_id": "payment-processor-v2",
"issued_at": 1735689600000,
"ttl_ms": 30000,
"nonce": "Kx7mP2vQ9wR3sT5u",
"operation_type": "payment.initiate",
"subject": {
"account_id": "acct_8472910365",
"currency": "USD",
"recipient_id": "rcpt_1029384756"
},
"action": {
"type": "debit",
"amount": 1500.00,
"description": "Invoice INV-2026-0042 payment"
},
"payload": {
"invoice_id": "INV-2026-0042",
"payment_method": "ach_transfer",
"routing_number": "021000021",
"account_last_four": "4829",
"memo": "Q1 consulting services"
},
"payload_hash": "dGhpcyBpcyBhIHNhbXBsZSBwYXlsb2FkIGhhc2g",
"prev_chain_hash": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA",
"agent_pubkey_kid": "key-2026-q1",
"signature": "mN4x9Kp2R7vYbT1wZ8sD3fQ6jL5cA0hE2iU9oW3xP7mN4x9Kp2R7vYbT1wZ8sD3fQ6jL5cA0hE2iU9oW3xP7mN4x9K"
}Note: The signature and hash values in this example are illustrative placeholders. In a real EOR, these would be the actual base64url-encoded cryptographic outputs.
6. Signing Procedure
This section defines the exact procedure for constructing the signing input, computing the Ed25519 signature, computing the payload hash, and verifying signatures. These procedures are critical for interoperability: any deviation from the specified steps will result in signature verification failure.
6.1 Constructing the Signing Input
The signing input for an EOR is constructed as follows:
Step 1: Construct a JSON object containing ALL EOR fields EXCEPT the "signature" field. This means the signing input object contains: op_version, operation_id, org_id, agent_id, issued_at, ttl_ms, nonce, operation_type, subject, action, payload, payload_hash, prev_chain_hash, and agent_pubkey_kid.
Step 2: Apply JCS canonicalization (RFC 8785) to the JSON object. This sorts all object keys lexicographically by UTF-16 code unit values (recursively for nested objects), removes all insignificant whitespace, and applies ES2015 number serialization. The result is a deterministic string representation.
Step 3: Encode the canonicalized string as UTF-8 bytes. These raw bytes are the signing input. They are NOT hashed before signing; Ed25519 internally applies SHA-512 as part of its signing algorithm per RFC 8032.
// Step 1: Build signing input object (EOR minus "signature")
function buildSigningInput(eor: EOR): object {
const { signature, ...signingFields } = eor;
return signingFields;
}
// Step 2: JCS canonicalize
const canonicalJson: string = canonicalize(signingFields);
// Step 3: UTF-8 encode
const signingInput: Uint8Array = new TextEncoder().encode(canonicalJson);
// The signingInput bytes are passed directly to Ed25519 sign()Critical Implementation Note: The signing input is the raw UTF-8 bytes of the JCS-canonicalized JSON string. Implementations MUST NOT hash the signing input before passing it to Ed25519. The Ed25519 algorithm (RFC 8032) internally applies SHA-512 to the signing input as part of its deterministic signature computation. Applying an additional hash (e.g., SHA-256) before signing would produce an incompatible signature.
6.2 Computing the Signature
Given the signing input bytes from Step 3 above and the Agent's Ed25519 private key:
Step 1: Import the 32-byte Ed25519 private seed into the cryptographic API. For Web Crypto API, this requires PKCS#8 wrapping as described in Section 3.1.
Step 2: Sign the signing input bytes using the Ed25519 algorithm. The output is a 64-byte signature.
Step 3: Encode the 64-byte signature as base64url (RFC 4648 Section 5) without padding. The result is an 86-character string.
// Complete signing procedure
async function signEOR(
eor: Omit<EOR, "signature">,
privateKeySeed: Uint8Array
): Promise<string> {
// Build signing input
const canonicalJson = canonicalize(eor);
const signingInput = new TextEncoder().encode(canonicalJson);
// Import private key with PKCS#8 wrapper
const pkcs8 = new Uint8Array(48);
pkcs8.set(PKCS8_PREFIX, 0);
pkcs8.set(privateKeySeed, 16);
const privateKey = await crypto.subtle.importKey(
"pkcs8", pkcs8, "Ed25519", false, ["sign"]
);
// Sign
const signatureBuffer = await crypto.subtle.sign(
"Ed25519", privateKey, signingInput
);
// Base64url encode
return base64url(new Uint8Array(signatureBuffer));
}6.3 Payload Hash Computation
The payload_hash field provides integrity protection for the operation payload. It is computed as follows:
If payload is not null: Apply JCS canonicalization (RFC 8785) to the payload value. Compute SHA-256 over the UTF-8 encoding of the canonical string. Base64url-encode the 32-byte digest.
If payload is null: Compute SHA-256 over the UTF-8 encoding of the literal four-character string "null" (without quotes). Base64url-encode the 32-byte digest.
// Payload hash computation
async function computePayloadHash(
payload: object | string | null
): Promise<string> {
let input: string;
if (payload === null) {
input = "null";
} else {
input = canonicalize(payload);
}
const encoded = new TextEncoder().encode(input);
const hash = await crypto.subtle.digest("SHA-256", encoded);
return base64url(new Uint8Array(hash));
}
// The null payload hash is a constant:
// SHA-256("null") = "sKaFSVkDLHwoPPGfXnlLzVNl80F2K78VKMPsWFn1-dA"
// (This value is deterministic and can be precomputed.)6.4 Signature Verification
Signature verification is performed by the platform during EOR admission (Section 8.2) and by any party performing independent verification (Section 13.1). The procedure is:
Step 1: Look up the Agent's Ed25519 public key using the agent_pubkey_kid field. The key MUST be registered to the Agent and MUST be in "active" status.
Step 2: Import the 32-byte raw public key into the cryptographic API with algorithm "Ed25519" and usage ["verify"].
Step 3: Reconstruct the signing input: remove the "signature" field from the EOR, apply JCS canonicalization, and UTF-8 encode the result.
Step 4: Base64url-decode the signature field to obtain the raw 64-byte signature.
Step 5: Verify the Ed25519 signature over the signing input bytes using the imported public key. If verification fails, the operation MUST be rejected with error code INVALID_SIGNATURE.
// Signature verification
async function verifyEORSignature(
eor: EOR,
publicKeyRaw: Uint8Array
): Promise<boolean> {
// Import public key
const publicKey = await crypto.subtle.importKey(
"raw", publicKeyRaw, "Ed25519", true, ["verify"]
);
// Reconstruct signing input
const { signature, ...signingFields } = eor;
const canonicalJson = canonicalize(signingFields);
const signingInput = new TextEncoder().encode(canonicalJson);
// Decode signature from base64url
const signatureBytes = base64urlDecode(signature);
// Verify
return crypto.subtle.verify(
"Ed25519", publicKey, signatureBytes, signingInput
);
}7. Chain Integrity Model
The Chain Integrity Model defines how operations for a given Agent are linked into a sequential, tamper-evident hash chain. This chain ensures that the complete history of an Agent's operations can be verified end-to-end: any insertion, deletion, modification, or reordering of operations is detectable by recomputing the chain hashes. The model is analogous to a blockchain but operates per-agent rather than globally, and is maintained by the platform rather than a distributed consensus mechanism.
7.1 Chain Hash Computation
The chain hash (ECH) for each operation is computed using the following formula:
chain_hash = base64url(
SHA-256(
prev_chain_hash + "|" + payload_hash + "|" + operation_id + "|" + issued_at
)
)Detailed Specification:
• The delimiter is a single pipe character "|" (U+007C, vertical line).
• All four components are concatenated as their string representations with pipe delimiters between them (no leading or trailing delimiters).
• prev_chain_hash: The base64url-encoded chain hash of the immediately preceding operation, or the Genesis Chain Hash for the first operation. This is a 43-character string.
• payload_hash: The base64url-encoded SHA-256 hash of the JCS-canonicalized payload (as computed in Section 6.3). This is a 43-character string.
• operation_id: The UUIDv7 string (e.g., "019473a2-7c8b-7d4e-a1b3-5f8e9c2d4a6b"). This is a 36-character string.
• issued_at: The integer timestamp converted to its string representation (e.g., "1735689600000"). No leading zeros. No decimal point.
• The concatenated string is UTF-8 encoded and then hashed with SHA-256. The 32-byte digest is base64url-encoded to produce the chain_hash.
// Chain hash computation
async function computeChainHash(
prev_chain_hash: string,
payload_hash: string,
operation_id: string,
issued_at: number
): Promise<string> {
const input = prev_chain_hash
+ "|" + payload_hash
+ "|" + operation_id
+ "|" + String(issued_at);
const encoded = new TextEncoder().encode(input);
const hash = await crypto.subtle.digest("SHA-256", encoded);
return base64url(new Uint8Array(hash));
}
// Example input string:
// "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA|dGhpcyBp...|019473a2-7c8b-7d4e-a1b3-5f8e9c2d4a6b|1735689600000"7.2 Genesis Chain Hash
The first operation in an Agent's chain MUST use the Genesis Chain Hash as its prev_chain_hash value. The Genesis Chain Hash is defined as the base64url encoding of 32 zero bytes (all bits zero):
Genesis Chain Hash = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // Derivation: // 32 zero bytes = [0x00, 0x00, ..., 0x00] (32 times) // base64url([0x00 * 32]) = "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" // (43 characters, no padding)
This well-known constant is the same for all Agents across all Organizations. Any verifier can identify the first operation in a chain by checking whether its prev_chain_hash equals the Genesis Chain Hash. The platform MUST verify that an Agent's first operation (seq_no = 1) uses the Genesis Chain Hash and that subsequent operations (seq_no > 1) do NOT use the Genesis Chain Hash.
7.3 Chain Continuity
Chain continuity is the property that each operation's prev_chain_hash equals the chain_hash computed for the immediately preceding operation. This creates an unbroken chain from the genesis operation to the latest operation.
Platform Enforcement: The platform MUST maintain the latest chain_hash for each Agent. When a new operation is submitted, the platform compares the submitted prev_chain_hash against the stored latest_chain_hash. If they do not match, the operation MUST be rejected with error code PREV_HASH_MISMATCH.
Concurrency Control: To prevent race conditions where two operations are submitted simultaneously for the same Agent, the platform MUST serialize chain operations per Agent. The reference implementation uses Cloudflare Durable Objects with one Durable Object instance per Agent, ensuring that all operations for a given Agent are processed sequentially within a single execution context. This eliminates the possibility of two operations reading the same latest_chain_hash and both attempting to advance the chain.
Chain State Update: After successfully admitting an operation, the platform MUST atomically update the Agent's latest_chain_hash to the newly computed chain_hash and increment seq_no. This update MUST be durable (persisted to storage) before returning the attestation receipt to the client.
7.4 Sequence Numbers
Each operation is assigned a monotonically increasing sequence number (seq_no) within its Agent's chain. Sequence numbers provide an explicit ordering independent of timestamps and enable efficient gap detection.
• Sequence numbers start at 1 for the first operation.
• Each subsequent operation increments seq_no by exactly 1.
• The platform MUST enforce a UNIQUE constraint on (agent_id, seq_no) within each Organization.
• Gaps in sequence numbers (e.g., seq_no jumps from 5 to 7) indicate a chain integrity violation and MUST be flagged during verification.
• Sequence numbers are assigned by the platform, not by the Agent. The Agent does not include seq_no in the EOR; it is assigned during admission and included in the EAR.
7.5 Conflict Resolution
When the submitted prev_chain_hash does not match the platform's stored latest_chain_hash for the Agent, the platform MUST reject the operation with the following error response:
{
"error": "PREV_HASH_MISMATCH",
"message": "The submitted prev_chain_hash does not match the
expected chain_hash for this agent.",
"expected": "<current latest_chain_hash>",
"received": "<submitted prev_chain_hash>"
}The error response includes both the expected and received values to enable the client to diagnose the issue. Common causes of PREV_HASH_MISMATCH include:
• Stale client state: The client's cached latest_chain_hash is outdated because another instance of the same Agent submitted an operation concurrently. Resolution: the client should fetch the latest chain_hash and re-sign with the correct prev_chain_hash.
• Client-side error: The client computed the chain hash incorrectly. Resolution: verify the chain hash computation algorithm matches Section 7.1.
• Replay attempt: A replayed operation contains the prev_chain_hash from the time of original signing, which no longer matches the current chain state. This is an expected rejection behavior.
8. Admission & Verification Rules
This section defines the complete validation pipeline that the platform MUST execute for every submitted EOR. The validation steps are ordered to fail fast on cheap checks before performing expensive cryptographic operations. All steps MUST pass for an operation to be admitted; failure at any step MUST result in rejection with the appropriate error code.
8.1 EOR Validation Pipeline
The following validation steps MUST be executed in the specified order. If any step fails, the platform MUST immediately reject the operation and return the error response without executing subsequent steps.
| Step | Validation | Error Code | HTTP Status |
|---|---|---|---|
| 1 | Validate op_version equals "1.0". If the value is missing or not "1.0", reject the operation. This check ensures that the platform only processes operations conforming to a known protocol version. | UNSUPPORTED_VERSION | 400 |
| 2 | Validate all required fields are present and non-empty. Check each field defined in Section 5.1: operation_id, org_id, agent_id, issued_at, ttl_ms, nonce, operation_type, subject, action, payload_hash, prev_chain_hash, agent_pubkey_kid, and signature. The payload field MAY be null but MUST be present. | MISSING_FIELD | 400 |
| 3 | Validate nonce length does not exceed 64 characters. If nonce.length > 64, reject. This prevents excessively long nonces from consuming storage in the nonce replay cache. | INVALID_NONCE | 400 |
| 4 | Validate issued_at is a positive integer greater than zero. This prevents nonsensical timestamps and ensures the TTL computation produces meaningful results. | INVALID_TIMESTAMP | 400 |
| 5 | Validate ttl_ms is an integer in the range [1000, 300000]. Values below 1,000ms or above 300,000ms are rejected. See Section 3.6. | INVALID_TTL | 400 |
| 6 | Check TTL expiration: compute issued_at + ttl_ms and compare against server_received_at (the current server time in Unix milliseconds). If issued_at + ttl_ms < server_received_at, the operation has expired. | TTL_EXPIRED | 400 |
| 7 | Check payload size. If payload is not null, serialize it to JSON and verify the byte length does not exceed 262,144 bytes (256 KB). This prevents denial-of-service via oversized payloads. | PAYLOAD_TOO_LARGE | 413 |
| 8 | Check nonce replay. Look up the nonce value in the KV-based nonce cache using key "nonce:{`{nonce_value}`}". If the key exists, the nonce has been previously used and the operation is a replay. If the key does not exist, store the nonce in KV with a TTL of 300 seconds (5 minutes). | NONCE_REPLAY | 409 |
| 9 | Verify the Agent exists and is in "active" status. Look up the Agent by (org_id, agent_id). If the Agent does not exist, reject. If the Agent's status is "frozen" or "revoked", reject with appropriate error. | AGENT_NOT_FOUND / AGENT_FROZEN / AGENT_REVOKED | 404 / 403 |
| 10 | Verify the key identified by agent_pubkey_kid exists and is in "active" status. Look up the key within the Agent's registered key set. If the key does not exist, reject. If the key is "retired" or "revoked", reject. | KEY_NOT_FOUND / KEY_RETIRED / KEY_REVOKED | 404 / 403 |
| 11 | Verify Ed25519 signature. Reconstruct the signing input per Section 6.1 and verify the signature using the Agent's public key per Section 6.4. This is the most computationally expensive validation step, which is why it is ordered after the cheaper checks. | INVALID_SIGNATURE | 401 |
| 12 | Verify prev_chain_hash matches the platform's stored latest_chain_hash for this Agent. If the Agent has no previous operations, the expected value is the Genesis Chain Hash. If they do not match, reject with PREV_HASH_MISMATCH. | PREV_HASH_MISMATCH | 409 |
| 13 | Compute the new chain_hash using the formula in Section 7.1. Increment seq_no. Atomically update the Agent's latest_chain_hash and seq_no in durable storage. | — | — |
| 14 | Persist the operation record and generate the Attestation Receipt (EAR). Enqueue the operation for asynchronous storage in R2 via the message queue. Return the EAR to the client with HTTP 200. | — | 200 |
8.2 Signature Verification Detail
The platform's signature verification procedure uses the Web Crypto API as follows:
// Platform-side signature verification (Cloudflare Workers)
async function verifySignature(
eor: EOR,
registeredPublicKey: string // base64url-encoded
): Promise<boolean> {
// 1. Decode the registered public key
const pubKeyBytes = base64urlDecode(registeredPublicKey);
if (pubKeyBytes.length !== 32) {
throw new Error("Invalid public key length");
}
// 2. Import as CryptoKey
const publicKey = await crypto.subtle.importKey(
"raw",
pubKeyBytes,
{ name: "Ed25519" },
true,
["verify"]
);
// 3. Reconstruct signing input
const { signature, ...signingFields } = eor;
const canonicalJson = canonicalize(signingFields);
const signingInput = new TextEncoder().encode(canonicalJson);
// 4. Decode signature
const signatureBytes = base64urlDecode(signature);
if (signatureBytes.length !== 64) {
return false; // Invalid signature length
}
// 5. Verify
return crypto.subtle.verify(
"Ed25519",
publicKey,
signatureBytes,
signingInput
);
}8.3 TTL Validation Detail
The TTL validation ensures that operations are submitted within a bounded time window:
// TTL validation with detailed error reporting
function validateTTLDetailed(
issued_at: number,
ttl_ms: number,
server_received_at: number
): { valid: boolean; error?: string; code?: string } {
// Range check
if (ttl_ms < 1000) {
return {
valid: false,
error: "ttl_ms must be >= 1000",
code: "INVALID_TTL"
};
}
if (ttl_ms > 300000) {
return {
valid: false,
error: "ttl_ms must be <= 300000",
code: "INVALID_TTL"
};
}
// Expiration check
const expiration = issued_at + ttl_ms;
if (expiration < server_received_at) {
return {
valid: false,
error: "Operation expired. issued_at + ttl_ms < server time. " +
"expiration=" + expiration +
", server_received_at=" + server_received_at +
", delta=" + (server_received_at - expiration) + "ms",
code: "TTL_EXPIRED"
};
}
return { valid: true };
}8.4 Nonce Replay Detection
Nonce replay detection is implemented using Cloudflare KV as a distributed key-value cache with TTL-based automatic expiration:
// Nonce replay detection via Cloudflare KV
async function checkNonceReplay(
nonce: string,
kvNamespace: KVNamespace
): Promise<{ replay: boolean }> {
const key = "nonce:" + nonce;
// Check if nonce exists
const existing = await kvNamespace.get(key);
if (existing !== null) {
return { replay: true };
}
// Store nonce with TTL (max ttl_ms = 300s = 5 minutes)
await kvNamespace.put(key, "1", {
expirationTtl: 300 // seconds
});
return { replay: false };
}Race Condition Note: There is a theoretical race condition where two operations with the same nonce arrive simultaneously, both perform the KV get before either performs the put, and both pass the replay check. In practice, this window is extremely small (sub-millisecond) and the chain hash check (Step 12) would catch this scenario because only one of the two operations could have the correct prev_chain_hash. Implementations SHOULD treat this as an acceptable residual risk.
8.5 Agent Status Check
The platform's behavior depends on the Agent's current status:
| Agent Status | Behavior | HTTP Status | Error Code |
|---|---|---|---|
| active | Accept the operation and continue with remaining validation steps. The Agent is authorized to submit operations. | — | — |
| frozen | Reject the operation. The Agent has been temporarily suspended by an administrator. No new operations are accepted. Existing records remain intact and verifiable. The freeze MAY be reversed by an authorized administrator. | 403 | AGENT_FROZEN |
| revoked | Reject the operation. The Agent has been permanently disabled. No new operations are accepted. This status is irreversible. All associated keys are automatically retired. The Agent's historical records remain intact for audit purposes. | 403 | AGENT_REVOKED |
| (not found) | Reject the operation. No Agent with the specified agent_id exists in the specified Organization. | 404 | AGENT_NOT_FOUND |
9. Attestation Receipt (EAR)
The Elydora Acknowledgment Receipt (EAR) is the platform's signed attestation that an operation was received, validated, and admitted into the immutable record. The EAR serves as the platform's non-repudiation commitment: by signing the receipt with the Elydora server key, the platform cryptographically commits to having admitted the operation at the specified time with the specified chain position. The combination of the Agent's signature on the EOR and the platform's signature on the EAR creates a dual-signed evidence chain.
9.1 Receipt Fields
| Field | Type | Description |
|---|---|---|
| receipt_version | string | MUST be "1.0". Protocol version for the receipt format. |
| receipt_id | string | UUIDv7 uniquely identifying this receipt. Generated by the platform at admission time. |
| operation_id | string | The operation_id of the admitted EOR. Links the receipt to the operation. |
| org_id | string | Organization identifier, copied from the EOR. |
| agent_id | string | Agent identifier, copied from the EOR. |
| server_received_at | integer | Unix epoch milliseconds. The platform's timestamp recording when the operation was received and processed. This is an independent temporal anchor that does not depend on the Agent's issued_at claim. |
| seq_no | integer | The sequence number assigned to this operation in the Agent's chain. Monotonically increasing starting from 1. |
| chain_hash | string | The chain_hash computed for this operation per Section 7.1. Base64url-encoded SHA-256 digest (43 characters). |
| queue_message_id | string | The message ID assigned by the asynchronous processing queue (Cloudflare Queues). Used for tracking the operation through the storage pipeline. Enables correlation between the admission response and the eventual R2 persistence. |
| receipt_hash | string | SHA-256 hash of the JCS-canonicalized receipt fields (excluding receipt_hash and elydora_signature), base64url-encoded. This is the value over which the platform signature is computed. See Section 9.2 for computation rules. |
| elydora_kid | string | The Key ID of the Elydora server key used to sign this receipt. MUST be "elydora-server-key-v1" in protocol version 1.0. |
| elydora_signature | string | Ed25519 signature over the receipt_hash, produced with the Elydora server private key. Base64url-encoded (86 characters). |
9.2 Receipt Hash Computation
The receipt_hash is computed over a specific subset of the receipt fields:
// Receipt hash computation
async function computeReceiptHash(receipt: {
receipt_version: string;
receipt_id: string;
operation_id: string;
org_id: string;
agent_id: string;
server_received_at: number;
seq_no: number;
chain_hash: string;
queue_message_id: string;
}): Promise<string> {
// Build the hash input object with EXACTLY these fields
const hashInput = {
receipt_version: receipt.receipt_version,
receipt_id: receipt.receipt_id,
operation_id: receipt.operation_id,
org_id: receipt.org_id,
agent_id: receipt.agent_id,
server_received_at: receipt.server_received_at,
seq_no: receipt.seq_no,
chain_hash: receipt.chain_hash,
queue_message_id: receipt.queue_message_id,
};
// JCS canonicalize
const canonical = canonicalize(hashInput);
// SHA-256 hash
const encoded = new TextEncoder().encode(canonical);
const hash = await crypto.subtle.digest("SHA-256", encoded);
return base64url(new Uint8Array(hash));
}Important: The receipt_hash and elydora_signature fields are NOT included in the hash input. The elydora_kid field is also NOT included in the hash input. Only the nine fields listed above are included. The JCS canonicalization ensures deterministic ordering regardless of the order in which fields are specified in the code.
9.3 Receipt Signature
The platform signs the receipt_hash using the Elydora server Ed25519 private key:
// Receipt signing
async function signReceipt(
receiptHash: string, // base64url-encoded SHA-256 hash
serverPrivateKey: CryptoKey
): Promise<string> {
// Sign the receipt_hash string (NOT decoded bytes)
const hashBytes = new TextEncoder().encode(receiptHash);
const signature = await crypto.subtle.sign(
"Ed25519",
serverPrivateKey,
hashBytes
);
return base64url(new Uint8Array(signature));
}The server key ID is "elydora-server-key-v1". The corresponding public key is published at the JWKS endpoint (Section 9.4) to enable independent verification. The server key SHOULD be rotated periodically according to the organization's key management policy. When the server key is rotated, the new key ID MUST be different from the previous key ID, and both keys MUST be published in the JWKS endpoint during the transition period.
9.4 Receipt Verification
Any party can verify an EAR by fetching the Elydora platform's public key and recomputing the receipt hash:
Step 1: Fetch the Elydora JWKS from the well-known endpoint: GET /.well-known/elydora/jwks.json
Step 2: Locate the key entry matching the elydora_kid field ("elydora-server-key-v1").
Step 3: Import the Ed25519 public key from the JWKS entry.
Step 4: Recompute the receipt_hash using the procedure in Section 9.2 and verify it matches the receipt's receipt_hash field.
Step 5: Verify the elydora_signature over the receipt_hash using the imported public key.
// JWKS endpoint response format
// GET /.well-known/elydora/jwks.json
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"kid": "elydora-server-key-v1",
"x": "<base64url-encoded 32-byte public key>",
"use": "sig",
"alg": "EdDSA"
}
]
}9.5 Example EAR
{
"receipt_version": "1.0",
"receipt_id": "019473a2-8d9c-7e5f-b2c4-6a7f0d3e5b8c",
"operation_id": "019473a2-7c8b-7d4e-a1b3-5f8e9c2d4a6b",
"org_id": "org_acme_corp",
"agent_id": "payment-processor-v2",
"server_received_at": 1735689601234,
"seq_no": 1,
"chain_hash": "Vx3kL9mN2pQ5rS7tU0wY1zA4bC6dE8fG3hI5jK7lM9o",
"queue_message_id": "msg_01HQ4R5S6T7U8V9W0X1Y2Z3A",
"receipt_hash": "Bx4mN7pQ2rS5tU8wY0zA3bC6dE9fG1hI4jK7lM0nO2p",
"elydora_kid": "elydora-server-key-v1",
"elydora_signature": "Hx5nP8qR3sT6uV9wY1zA4bC7dE0fG2hI5jK8lM1nO3pHx5nP8qR3sT6uV9wY1zA4bC7dE0fG2hI5jK8lM1nO3p4q6r"
}Note: Hash, signature, and message ID values are illustrative placeholders.
10. Epoch Sealing Mechanism
The Epoch Sealing Mechanism aggregates operations into time-windowed batches and computes a Merkle tree root hash over each batch. This enables efficient batch verification: a verifier can confirm that a specific operation was included in a sealed epoch by checking a Merkle inclusion proof rather than re-verifying every operation in the batch. Optional RFC 3161 timestamp authority (TSA) anchoring provides independent third-party temporal attestation for the epoch root hash.
10.1 Epoch Definition
An epoch is a time-windowed batch of operations within a single Organization. Each epoch is defined by:
• org_id: The Organization scope. All operations in an epoch belong to the same Organization.
• start_time: The inclusive lower bound of the epoch window, in Unix epoch milliseconds.
• end_time: The exclusive upper bound of the epoch window, in Unix epoch milliseconds.
• Default interval: 300,000ms (5 minutes). Organizations MAY configure a different interval, but it MUST NOT be less than 60,000ms (1 minute) or greater than 86,400,000ms (24 hours).
The epoch window is half-open: [start_time, end_time). An operation with server_received_at = T belongs to the epoch where start_time ≤ T < end_time. Each operation belongs to exactly one epoch. Epochs are sealed after end_time has passed and a configurable grace period has elapsed to account for in-flight operations.
If no operations occurred during an epoch window, no epoch record is created. Empty epochs are simply omitted from the sequence.
10.2 Merkle Tree Construction
The Merkle tree for an epoch is constructed as follows:
Step 1 — Collect leaf hashes. The leaf hashes are the chain_hash values of all operations within the epoch window. Each chain_hash is a base64url-encoded SHA-256 digest (43 characters).
Step 2 — Sort leaves. The leaf hashes MUST be sorted lexicographically (ascending, by UTF-16 code unit values). Sorting ensures deterministic tree construction regardless of the order in which operations were admitted.
Step 3 — Handle odd leaf count. If the number of leaves is odd, the last leaf MUST be duplicated so that every internal node has exactly two children. This padding is applied at every level of the tree, not just the leaf level.
Step 4 — Compute internal nodes. Each internal node is computed as SHA-256(left_child_bytes || right_child_bytes) where:
• left_child_bytes and right_child_bytes are the raw decoded bytes of the child hashes (NOT the base64url strings). Each child hash MUST be base64url-decoded to 32 raw bytes before concatenation.
• The concatenation is 64 raw bytes (32 bytes left + 32 bytes right).
• SHA-256 is computed over the 64 raw bytes, producing a 32-byte digest which is then base64url-encoded to form the parent node hash.
// Merkle tree construction
async function buildMerkleTree(
leafHashes: string[] // sorted base64url chain_hash values
): Promise<{ root: string; layers: string[][] }> {
if (leafHashes.length === 0) {
throw new Error("Cannot build Merkle tree with zero leaves");
}
const layers: string[][] = [leafHashes];
let currentLayer = [...leafHashes];
while (currentLayer.length > 1) {
// Duplicate last element if odd count
if (currentLayer.length % 2 !== 0) {
currentLayer.push(currentLayer[currentLayer.length - 1]);
}
const nextLayer: string[] = [];
for (let i = 0; i < currentLayer.length; i += 2) {
const leftBytes = base64urlDecode(currentLayer[i]);
const rightBytes = base64urlDecode(currentLayer[i + 1]);
// Concatenate raw bytes (NOT strings)
const combined = new Uint8Array(64);
combined.set(leftBytes, 0);
combined.set(rightBytes, 32);
const hash = await crypto.subtle.digest("SHA-256", combined);
nextLayer.push(base64url(new Uint8Array(hash)));
}
layers.push(nextLayer);
currentLayer = nextLayer;
}
return {
root: currentLayer[0],
layers
};
}Critical Implementation Note: The internal node hash is computed over the raw decoded bytes of the child hashes concatenated together. Implementations MUST NOT hash the base64url string representations. The distinction is critical: hashing the 64-byte raw concatenation produces a different result than hashing the 87-character string concatenation of two base64url values.
10.3 Root Hash Generation
The Merkle root is the single hash value at the top of the tree after iterating the pairwise hash computation until only one node remains. The root hash MUST be base64url-encoded and stored in the Epoch Record's root_hash field.
Single-leaf special case: If the epoch contains exactly one operation, the root hash equals the single leaf's chain_hash. No internal node computation is necessary.
Layer storage: The platform MUST store all intermediate layers of the Merkle tree to enable efficient proof generation for individual operations (Section 10.5). These layers are stored alongside the epoch record and are not included in the signed EER itself.
10.4 Epoch Record (EER) Fields
| Field | Type | Description |
|---|---|---|
| epoch_id | string | UUIDv7 uniquely identifying this epoch record. Generated by the platform when the epoch is sealed. |
| org_id | string | The Organization to which this epoch belongs. All operations in the epoch share this org_id. |
| start_time | integer | The inclusive lower bound of the epoch window in Unix epoch milliseconds. |
| end_time | integer | The exclusive upper bound of the epoch window in Unix epoch milliseconds. |
| leaf_count | integer | The number of leaf operations (chain hashes) in the Merkle tree. This is the total number of operations in the epoch, before any duplication for odd-count padding. |
| root_hash | string | The Merkle root hash, base64url-encoded. This is the single hash value at the top of the Merkle tree. |
| hash_alg | string | MUST be "sha256". Identifies the hash algorithm used for Merkle tree construction. |
| signature_by_elydora | string | Ed25519 signature over the JCS-canonicalized EER (excluding the signature_by_elydora field itself), produced with the Elydora server private key. Base64url-encoded (86 characters). |
10.5 Merkle Proof Structure
A Merkle inclusion proof demonstrates that a specific operation's chain_hash is included in an epoch's Merkle tree without revealing other operations in the tree. The proof structure contains:
{
"leaf_hash": "<base64url chain_hash of the target operation>",
"leaf_index": 42,
"tree_size": 128,
"proof_hashes": [
"<base64url sibling hash at level 0>",
"<base64url sibling hash at level 1>",
"<base64url sibling hash at level 2>",
...
],
"directions": [
"left", // sibling is on the left
"right", // sibling is on the right
"left",
...
],
"root_hash": "<base64url expected root hash>"
}Proof Fields:
• leaf_hash: The chain_hash of the operation being proven.
• leaf_index: The zero-based index of the leaf in the sorted leaf array.
• tree_size: The total number of leaves in the tree.
• proof_hashes: The sibling hashes at each level of the tree, from leaf level to root. The number of proof hashes equals ceil(log2(tree_size)).
• directions: For each proof hash, whether the sibling appears on the "left" or "right" side. If the direction is "left", the sibling is the left child and the current hash is the right child (sibling || current). If "right", the current hash is the left child (current || sibling).
• root_hash: The expected root hash of the Merkle tree. After traversing the proof, the computed root MUST match this value.
10.6 Proof Verification
To verify a Merkle inclusion proof:
// Merkle proof verification
async function verifyMerkleProof(proof: {
leaf_hash: string;
proof_hashes: string[];
directions: ("left" | "right")[];
root_hash: string;
}): Promise<boolean> {
let currentHash = proof.leaf_hash;
for (let i = 0; i < proof.proof_hashes.length; i++) {
const siblingHash = proof.proof_hashes[i];
const direction = proof.directions[i];
// Decode both hashes to raw bytes
const currentBytes = base64urlDecode(currentHash);
const siblingBytes = base64urlDecode(siblingHash);
// Concatenate in correct order
const combined = new Uint8Array(64);
if (direction === "left") {
// Sibling is on the left: hash(sibling || current)
combined.set(siblingBytes, 0);
combined.set(currentBytes, 32);
} else {
// Sibling is on the right: hash(current || sibling)
combined.set(currentBytes, 0);
combined.set(siblingBytes, 32);
}
// Hash the pair
const hash = await crypto.subtle.digest("SHA-256", combined);
currentHash = base64url(new Uint8Array(hash));
}
// Compare computed root with expected root
return currentHash === proof.root_hash;
}10.7 Optional TSA Anchoring
For organizations requiring independent temporal attestation, epoch root hashes MAY be anchored to an external RFC 3161 Timestamp Authority (TSA). TSA anchoring provides a cryptographic proof from a trusted third party that the epoch root hash existed at a specific point in time.
TSA Request Procedure:
1. Compute the SHA-256 hash of the epoch root_hash value (the base64url string, not the decoded bytes).
2. Construct an RFC 3161 TimeStampReq with the following parameters: version 1, messageImprint using SHA-256 algorithm OID (2.16.840.1.101.3.4.2.1) and the computed hash, certReq = true (request the TSA certificate be included in the response).
3. Submit the DER-encoded TimeStampReq to the configured TSA endpoint via HTTP POST with Content-Type: application/timestamp-query.
4. Receive the DER-encoded TimeStampResp. Verify the response status is "granted" (status 0).
5. Store the DER-encoded TimeStampResp alongside the epoch record in R2 at the path: epochs/{`{org_id}`}/{`{epoch_id}`}/tsa_response.der
TSA anchoring is<span style={kwMust}> OPTIONAL</span> in protocol version 1.0. Organizations that enable TSA anchoring SHOULD use a reputable, publicly audited TSA endpoint. The TSA certificate chain SHOULD be verified against a trusted root certificate store.
11. Evidence Storage Model
The Evidence Storage Model defines how operation records, attestation receipts, and epoch records are persisted and protected. The model uses a two-tier architecture: a hot tier for recent, frequently queried data and a cold tier for long-term immutable evidence retention.
11.1 Index vs. Evidence Separation
The storage architecture separates structured indexes from immutable evidence blobs:
Hot Tier — Cloudflare D1 (SQLite). D1 stores structured metadata and indexes for fast querying. Data includes: operation metadata (operation_id, agent_id, org_id, operation_type, issued_at, server_received_at, seq_no, chain_hash), receipt metadata, epoch metadata, agent state, and key registrations. D1 maintains a rolling window of approximately 30 days of data. Older records are available only from the cold tier (R2). D1 is optimized for the Elydora Console queries: listing operations by agent, filtering by time range, searching by operation type, and checking chain integrity.
Cold Tier — Cloudflare R2 (Object Storage). R2 stores the complete, immutable evidence records for full retention. Every operation record, attestation receipt, and epoch record is stored as a JSON blob in R2 with its full content. R2 is the authoritative long-term store. If D1 data and R2 data conflict, R2 is considered authoritative.
11.2 R2 Object Layout
Evidence objects in R2 are organized using a hierarchical key structure:
# Operation Records (EOR)
evidence/{org_id}/{agent_id}/{operation_id}
# Attestation Receipts (EAR)
receipts/{org_id}/{agent_id}/receipts/{operation_id}
# Epoch Records (EER)
epochs/{org_id}/{epoch_id}
# TSA Responses (optional)
epochs/{org_id}/{epoch_id}/tsa_response.der
# Examples:
evidence/org_acme_corp/payment-processor-v2/019473a2-7c8b-7d4e-a1b3-5f8e9c2d4a6b
receipts/org_acme_corp/payment-processor-v2/receipts/019473a2-7c8b-7d4e-a1b3-5f8e9c2d4a6b
epochs/org_acme_corp/019473a2-9e0d-7f6a-c3d5-7b8f1e4g6h9iEach R2 object is a complete JSON document containing all fields of the respective record type. The Content-Type for all evidence objects MUST be set to "application/json; charset=utf-8".
11.3 Immutability Guarantees
Evidence immutability is enforced through multiple layers:
• R2 Bucket Configuration: The evidence R2 bucket SHOULD be configured with object-level retention controls where supported. Objects MUST NOT be overwritten; the storage pipeline uses put-if-absent semantics (only write if the key does not already exist).
• Cryptographic Integrity: Each operation is signed by the Agent (EOR signature) and counter-signed by the platform (EAR signature). Any modification to a stored record invalidates one or both signatures, making tampering immediately detectable.
• Merkle Anchoring: Operations are rolled up into epochs via Merkle trees. Even if an individual R2 object were modified, the Merkle proof would fail verification against the sealed epoch root hash.
• TSA Anchoring: For organizations using RFC 3161 TSA anchoring, the epoch root hash is independently attested by a trusted third party, providing an external integrity reference.
11.4 Storage Pipeline
Evidence persistence uses a queue-based asynchronous pipeline for reliability:
Step 1 — Admission. The Durable Object admits the operation, computes the chain hash, updates the Agent's chain state, and generates the attestation receipt. The receipt is returned to the client synchronously.
Step 2 — Enqueue. The complete operation record and receipt are enqueued to Cloudflare Queues for asynchronous persistence. The queue message ID is included in the EAR for traceability.
Step 3 — Consume. Queue consumers process messages and write the operation record and receipt to R2 and D1. Consumers are idempotent: if a message is delivered more than once (at-least-once delivery guarantee), the consumer checks whether the operation_id already exists in R2 before writing.
Step 4 — Acknowledge. After successful persistence to both R2 and D1, the queue message is acknowledged. If persistence fails, the message is returned to the queue for retry (with exponential backoff managed by the queue system).
Idempotency: The operation_id serves as the deduplication key. Queue consumers MUST check for existing records before writing and MUST treat duplicate writes as no-ops rather than errors. This ensures that at-least-once delivery does not produce duplicate evidence records.
12. Agent State Control
Agent State Control defines the lifecycle states for Agents and their keys, the permitted state transitions, and the administrative event logging requirements. State control is the primary mechanism for managing Agent access and responding to security incidents.
12.1 Active State
An Agent in the "active" state is fully operational:
• The Agent MAY submit new operations.
• All keys in the "active" key state MAY be used for signing operations.
• The Agent's chain continues to advance normally.
• All query operations (listing operations, fetching receipts, etc.) are available.
"Active" is the default state for newly registered Agents. An Agent MUST be in the "active" state to have operations admitted. This is enforced at Step 9 of the validation pipeline (Section 8.1).
12.2 Frozen State
An Agent in the "frozen" state is temporarily suspended:
• The Agent MUST NOT submit new operations. Any operation submission is rejected with AGENT_FROZEN (HTTP 403).
• All existing records (operations, receipts, chain state) remain intact and unmodified.
• The Agent's records remain queryable and verifiable.
• The freeze is reversible: an authorized administrator (org_owner or security_admin) MAY unfreeze the Agent, returning it to "active" state.
• When unfrozen, the Agent's chain resumes from where it was paused. The next operation's prev_chain_hash MUST match the chain_hash of the last operation admitted before the freeze.
Use Cases: Freezing is appropriate for temporary suspension during security investigations, compliance reviews, or maintenance windows. It preserves the Agent's chain integrity while preventing new actions.
12.3 Revoked State
An Agent in the "revoked" state is permanently disabled:
• The Agent MUST NOT submit new operations. Any operation submission is rejected with AGENT_REVOKED (HTTP 403).
• The revocation is irreversible. An Agent MUST NOT be transitioned from "revoked" back to "active" or "frozen".
• All keys associated with the Agent are automatically set to "retired" status.
• The Agent's historical records remain intact and queryable for audit and compliance purposes. Revocation does not delete or modify any existing records.
• A new Agent with a different agent_id MUST be registered if the organizational function requires continued operation.
Use Cases: Revocation is appropriate when an Agent is permanently decommissioned, when a critical security breach has occurred, or when regulatory action requires permanent cessation of the Agent's operations.
12.4 Key Lifecycle
Keys follow an independent lifecycle from their parent Agent:
Permitted Key State Transitions:
• active → retired: Graceful key rotation. The key is no longer accepted for signing new operations but is retained for verifying historical operations. This is the normal end-of-life path for a key.
• active → revoked: Emergency response. The key is immediately disabled and flagged as potentially compromised. All operations signed with this key after the suspected compromise time should be investigated.
The transitions retired → active and revoked → active are NOT permitted. Once retired or revoked, a key cannot be reactivated.
Verification with non-active keys: When verifying historical operations, the verifier SHOULD accept retired keys as valid for signature verification (since the operation was signed when the key was active). Revoked keys SHOULD be flagged in verification reports with a warning indicating the key was later revoked, which may warrant investigation of the specific operation.
12.5 Admin Event Logging
All administrative state changes MUST be logged as admin_events. Admin events provide an audit trail of who changed what and when. The admin_event schema is:
| Field | Type | Description |
|---|---|---|
| event_id | string | UUIDv7 uniquely identifying this admin event. |
| org_id | string | The Organization in which the event occurred. |
| actor | string | The identity of the user or system that initiated the action. For API key actions, this is the API key identifier. For console actions, this is the user email or ID. |
| action | string | The administrative action performed. Examples: "agent.create", "agent.freeze", "agent.unfreeze", "agent.revoke", "key.register", "key.retire", "key.revoke", "role.assign", "role.remove". |
| target_type | string | The type of entity affected. One of: "agent", "key", "role", "organization". |
| target_id | string | The identifier of the affected entity (e.g., agent_id, kid, user email). |
| details | object | Additional context about the event. For state changes, includes previous_status and new_status. For key operations, includes the kid and algorithm. |
| timestamp | integer | Unix epoch milliseconds when the event occurred. Generated by the platform. |
Admin events are stored in D1 for querying and in R2 for long-term retention. Admin events are immutable: once recorded, they MUST NOT be modified or deleted.
13. Verification Procedures
This section defines the procedures for verifying the integrity and authenticity of individual operations, operation chains, and epochs. These procedures are designed to be executable by any party with access to the relevant public keys and evidence records, without requiring trust in the Elydora platform operator.
13.1 Single Operation Verification
Verifying a single operation requires four independent checks. All four checks MUST pass for the operation to be considered verified. A failure in any check indicates a potential integrity violation.
Check 1 — Signature Verification. Reconstruct the signing input by removing the "signature" field from the EOR, applying JCS canonicalization, and UTF-8 encoding the result. Verify the Ed25519 signature using the Agent's registered public key (identified by agent_pubkey_kid). If verification fails, the operation may have been tampered with or forged. See Section 6.4 for the detailed procedure.
Check 2 — Chain Hash Verification. Recompute the chain_hash using the formula in Section 7.1: SHA-256(prev_chain_hash + "|" + payload_hash + "|" + operation_id + "|" + issued_at). Compare the computed value against the chain_hash in the attestation receipt. If they do not match, the operation record or the receipt may have been modified.
Additionally, verify that the operation's prev_chain_hash matches the chain_hash of the immediately preceding operation (by seq_no) for this Agent. For the first operation (seq_no = 1), verify that prev_chain_hash equals the Genesis Chain Hash ("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA").
Check 3 — Receipt Verification. Recompute the receipt_hash using the procedure in Section 9.2 (JCS-canonicalize the nine receipt fields, SHA-256 hash, base64url encode). Compare against the receipt's receipt_hash field. Then verify the elydora_signature using the Elydora platform's public key (fetched from the JWKS endpoint at /.well-known/elydora/jwks.json, identified by elydora_kid). If verification fails, the receipt may have been tampered with or was not genuinely issued by the platform.
Check 4 — Merkle Inclusion Proof (Optional). If the operation has been included in a sealed epoch, verify the Merkle inclusion proof against the epoch's root_hash using the procedure in Section 10.6. This confirms that the operation's chain_hash was included in the epoch's Merkle tree. This check is optional because epochs are sealed after a time window; recent operations may not yet be included in a sealed epoch.
// Complete single operation verification
async function verifySingleOperation(
eor: EOR,
ear: EAR,
agentPublicKey: Uint8Array,
elydoraPublicKey: Uint8Array,
prevOperation?: { chain_hash: string }
): Promise<{
signatureValid: boolean;
chainHashValid: boolean;
receiptValid: boolean;
prevChainLinkValid: boolean;
}> {
// Check 1: Signature
const signatureValid = await verifyEORSignature(
eor, agentPublicKey
);
// Check 2: Chain hash
const computedChainHash = await computeChainHash(
eor.prev_chain_hash,
eor.payload_hash,
eor.operation_id,
eor.issued_at
);
const chainHashValid =
computedChainHash === ear.chain_hash;
// Check 2b: Previous chain linkage
let prevChainLinkValid: boolean;
if (prevOperation) {
prevChainLinkValid =
eor.prev_chain_hash === prevOperation.chain_hash;
} else {
// First operation: must use genesis hash
prevChainLinkValid =
eor.prev_chain_hash ===
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA";
}
// Check 3: Receipt
const recomputedReceiptHash =
await computeReceiptHash(ear);
const receiptHashValid =
recomputedReceiptHash === ear.receipt_hash;
const receiptSigValid = await verifyReceiptSignature(
ear.receipt_hash,
ear.elydora_signature,
elydoraPublicKey
);
const receiptValid =
receiptHashValid && receiptSigValid;
return {
signatureValid,
chainHashValid,
receiptValid,
prevChainLinkValid
};
}13.2 Chain Verification Algorithm
Chain verification validates the complete history of an Agent's operations from the genesis to the latest operation. This procedure detects insertions, deletions, modifications, and reorderings.
Procedure:
1. Fetch all operations for the Agent, ordered by seq_no ascending (from 1 to the latest).
2. Verify that seq_no values form a contiguous sequence starting from 1 with no gaps.
3. For each operation, starting with seq_no = 1:
a. Verify the Ed25519 signature (Check 1 from Section 13.1).
b. Verify the payload_hash by recomputing SHA-256 of the JCS-canonicalized payload.
c. Recompute the chain_hash using the formula in Section 7.1.
d. For seq_no = 1: verify prev_chain_hash equals the Genesis Chain Hash.
e. For seq_no > 1: verify prev_chain_hash equals the chain_hash computed for the operation at seq_no - 1.
f. Verify the computed chain_hash matches the chain_hash in the attestation receipt.
g. Verify the attestation receipt signature (Check 3 from Section 13.1).
4. If all operations pass all checks, the chain is verified. Report the total number of operations verified, the Agent's latest chain_hash, and the time range covered (earliest issued_at to latest issued_at).
Performance Considerations: Full chain verification requires O(n) signature verifications where n is the number of operations. For Agents with large chains (millions of operations), this may take significant time. Implementations SHOULD support incremental verification: verify only the operations since the last verified checkpoint, rather than reverifying the entire chain from genesis.
13.3 Epoch Verification
Epoch verification validates the integrity of a sealed epoch record:
Step 1: Verify the EER signature. Remove the signature_by_elydora field from the EER, JCS-canonicalize the remaining fields, UTF-8 encode, and verify the Ed25519 signature using the Elydora platform's public key.
Step 2: Fetch all operations within the epoch's time window [start_time, end_time) for the Organization.
Step 3: Collect the chain_hash values of all fetched operations. Sort them lexicographically.
Step 4: Verify the leaf count matches the EER's leaf_count field.
Step 5: Reconstruct the Merkle tree using the procedure in Section 10.2.
Step 6: Compare the computed root hash against the EER's root_hash. They MUST be identical.
Step 7 (Optional): If TSA anchoring is present, verify the RFC 3161 timestamp response. Confirm that the timestamp was issued by a trusted TSA, that the hashed message matches the epoch root_hash, and that the TSA certificate chain is valid.
14. Security Considerations
This section analyzes the security properties of the protocol and describes the mitigations for each identified threat. Implementers MUST understand these considerations and ensure that their deployments maintain the security guarantees described.
14.1 Replay Attacks
Threat: An adversary captures a valid signed EOR during transit and re-submits it to the platform.
Primary Mitigation — Nonce: Each EOR includes a cryptographically random nonce. The platform maintains a nonce cache (KV store with TTL-based expiration) and rejects any operation whose nonce has been previously seen within the TTL window. The nonce cache uses the key format "nonce:{`{nonce_value}`}" with a TTL of 300 seconds (5 minutes), matching the maximum allowed ttl_ms.
Secondary Mitigation — Chain Hash: Even if the nonce check were bypassed, the replayed operation would fail the prev_chain_hash verification (Step 12 of the validation pipeline) because the Agent's chain will have advanced since the original submission. The replayed operation's prev_chain_hash would reference a stale chain position.
Tertiary Mitigation — TTL: The TTL window bounds the temporal validity of an operation. An operation captured and replayed after its TTL has expired will be rejected at Step 6 of the validation pipeline, even before nonce and chain hash checks.
Residual Risk: A replay submitted within the TTL window, before the nonce is cached (due to KV propagation delay), and with the correct prev_chain_hash could theoretically succeed. This window is extremely narrow (sub-millisecond) and is mitigated by the Durable Object serialization model, which processes operations for each Agent sequentially.
14.2 Key Compromise
Threat: An Agent's Ed25519 private key is compromised, allowing an adversary to forge operations.
Recommended Response:
1. Freeze the Agent immediately. This prevents any further operations from being submitted, including operations signed with the compromised key. Freezing is the fastest response action because it applies to all keys associated with the Agent.
2. Revoke the compromised key. Set the key status to "revoked". This is irreversible and flags the key as potentially compromised in all audit reports.
3. Investigate. Review all operations signed with the compromised key after the suspected time of compromise. The agent_pubkey_kid field in each EOR identifies which key was used for signing, enabling targeted investigation.
4. Register a new key. Generate a new Ed25519 key pair and register the public key with a new kid.
5. Unfreeze the Agent. Once the new key is registered and the investigation is complete, unfreeze the Agent to resume operations with the new key.
Prevention: Implementations SHOULD store Ed25519 private seeds in hardware security modules (HSMs) or secure enclaves where available. Private seeds MUST NOT be stored in plaintext in environment variables, configuration files, or source code repositories.
14.3 Malicious Operations
Scope Limitation: The Elydora Responsibility Protocol does NOT validate the business logic or ethical propriety of agent operations. The protocol provides cryptographic proof of who submitted what, when, and in what sequence. It does not determine whether the submitted operation was authorized, appropriate, or legal within the business context.
An Agent can submit an EOR describing a malicious action (e.g., an unauthorized transfer) and Elydora will admit it, provided the cryptographic and chain integrity checks pass. The value of Elydora in this scenario is that the malicious action is now irrevocably recorded with cryptographic non-repudiation: the responsible entity cannot deny having performed the action. This evidence can then be used in subsequent investigations, legal proceedings, or compliance audits.
Abuse Detection Out of Scope: The protocol does not include mechanisms for detecting anomalous patterns, policy violations, or suspicious activity. Organizations SHOULD implement their own monitoring and alerting systems on top of Elydora's evidence records.
14.4 Chain Manipulation
Threat: An adversary (including a compromised platform operator) attempts to insert, delete, or reorder operations in an Agent's chain.
Insertion: Inserting a new operation between two existing operations would require: (a) forging the Agent's Ed25519 signature on the inserted operation, and (b) recomputing all subsequent chain hashes, which would require modifying all subsequent operations and forging all their signatures. Without the Agent's private key, this is computationally infeasible.
Deletion: Deleting an operation from the chain would create a gap in sequence numbers (detectable by inspection) and break the prev_chain_hash linkage (the operation after the deleted one would reference a chain_hash that no longer exists in the chain).
Reordering: Reordering operations would break the prev_chain_hash linkage at every reordered position, as well as invalidating the seq_no sequence.
End-to-End Verification: The chain verification procedure in Section 13.2 detects all three forms of manipulation by verifying the complete chain from genesis to the latest operation.
14.5 Evidence Tampering
Threat: Stored evidence records are modified after admission.
Mitigations:
• R2 Immutability: R2 object storage is configured with retention controls and put-if-absent semantics. Objects cannot be overwritten.
• Cryptographic Signatures: Each operation carries the Agent's signature and each receipt carries the platform's signature. Any modification invalidates the relevant signature(s).
• Merkle Proofs: Operations are included in epoch Merkle trees. Even if an individual stored record were modified, verification against the epoch root hash would fail.
• TSA Anchoring: For organizations using RFC 3161 TSA anchoring, the epoch root hash is independently attested by a trusted third party. Even if both the stored evidence and the epoch record were modified, the TSA timestamp response would reveal the tampering (the root hash in the TSA response would not match the modified epoch root hash).
14.6 Denial of Service
Threat: An adversary floods the platform with operation submissions to exhaust resources.
Mitigations:
• Rate Limiting: The platform SHOULD implement per-Agent and per-Organization rate limits. Recommended default: 100 operations per second per Agent, 1,000 operations per second per Organization.
• Payload Size Limits: Payloads exceeding 256 KB (262,144 bytes) are rejected at Step 7 of the validation pipeline. This prevents resource exhaustion via oversized payloads.
• TTL Bounds: The maximum TTL of 300,000ms (5 minutes) limits the window during which nonces must be tracked, bounding the nonce cache size.
• Nonce Length Limit: Nonces are limited to 64 characters, preventing storage exhaustion in the nonce cache via excessively long nonce values.
15. Compliance & Audit Integrity
The Elydora Responsibility Protocol is designed to produce evidence records that satisfy the requirements of enterprise compliance frameworks, regulatory audits, and legal proceedings. This section defines the export formats, evidence chain requirements, and audit trail completeness criteria.
15.1 Export Formats
The platform MUST support the following evidence export formats:
JSON Evidence Bundle. A single JSON file containing all operations, receipts, and epoch records for a specified scope (Agent, time range, or Organization). The bundle includes the Elydora JWKS (server public keys) to enable offline verification. The JSON bundle MUST include a manifest object listing all included records with their operation_ids, seq_nos, and chain_hashes for quick integrity checking.
{
"export_version": "1.0",
"exported_at": 1735689600000,
"scope": {
"org_id": "org_acme_corp",
"agent_id": "payment-processor-v2",
"start_time": 1735603200000,
"end_time": 1735689600000
},
"jwks": { "keys": [ ... ] },
"manifest": {
"operation_count": 42,
"first_seq_no": 1,
"last_seq_no": 42,
"first_chain_hash": "...",
"last_chain_hash": "..."
},
"operations": [ ... ],
"receipts": [ ... ],
"epochs": [ ... ],
"merkle_proofs": [ ... ]
}PDF Evidence Package. A human-readable PDF document containing formatted operation records, receipt details, chain verification results, and epoch summaries. The PDF package is intended for legal proceedings and compliance filings where machine-readable formats are insufficient. The PDF SHOULD include a cover page with the export scope, a table of contents, and a verification summary indicating whether all integrity checks passed.
15.2 Legal Admissibility
The protocol's evidence chain is designed to support admissibility under the Federal Rules of Evidence (FRE), specifically:
• FRE 901(b)(9) — Self-Authenticating Evidence: Elydora records are suitable for authentication as "evidence describing a process or system and showing that it produces an accurate result." The cryptographic signatures, chain hashes, and Merkle proofs provide mathematical proof of authenticity without requiring testimonial authentication.
• FRE 803(6) — Business Records Exception: Elydora records qualify as records of a regularly conducted activity (business records exception to hearsay) when the organization can establish that the AI Agent operates as part of regular business processes and that the Elydora system records operations at or near the time of occurrence.
• Chain of Custody: The immutable chain hash linkage, cryptographic signatures, and epoch sealing provide a verifiable chain of custody from the moment of agent action through long-term storage. The dual-signature model (Agent signature + Platform counter-signature) provides independent corroboration.
15.3 Audit Trail Completeness
For audit trail completeness, the following requirements apply:
• Every Agent action submitted to the platform MUST produce both an EOR and an EAR. No operation may be admitted without generating a receipt.
• All administrative state changes (Agent create, freeze, unfreeze, revoke; key register, retire, revoke; role assignments) MUST produce admin_events (Section 12.5).
• Sequence numbers MUST be contiguous with no gaps. Any gap indicates a completeness violation.
• Epoch records MUST cover all operations within their time windows with no omissions. The leaf_count in the EER MUST match the actual number of operations in the epoch window.
• Export bundles MUST include complete chain segments. A partial chain export without the genesis operation or with gaps in seq_no is not considered a complete audit trail.
15.4 Compliance Framework Mapping
| Framework | Requirement | ERP Mapping |
|---|---|---|
| SOC 2 Type II | CC7.2 — System monitoring, anomaly detection | Complete operation chain with timestamps enables continuous monitoring. Admin event logging tracks all configuration changes. |
| ISO 27001 | A.12.4 — Logging and monitoring | Cryptographically signed operation records with chain integrity provide tamper-evident logging. Epoch sealing provides periodic rollup verification. |
| GDPR Art. 30 | Records of processing activities | EOR records capture processing activities with subject/action details, timestamps, and responsible entity binding. Export bundles provide Art. 30 compliant records. |
| EU AI Act | Art. 12 — Record-keeping, Art. 14 — Human oversight | Continuous, tamper-evident operation records satisfy record-keeping requirements. Chain verification enables human oversight of AI agent actions. |
| NIST AI RMF | Govern 1.2 — Accountability structures | Responsible entity binding, RBAC model, and admin event logging establish clear accountability structures for AI systems. |
16. Versioning & Compatibility
16.1 Protocol Version
This document defines protocol version "1.0". The protocol version is indicated in the following locations:
• The op_version field in every EOR MUST be "1.0".
• The receipt_version field in every EAR MUST be "1.0".
• The X-Elydora-Protocol-Version HTTP header SHOULD be included in all API requests and responses with value "1.0".
16.2 HTTP Header
The X-Elydora-Protocol-Version header communicates the protocol version between client and platform:
// Request header X-Elydora-Protocol-Version: 1.0 // Response header X-Elydora-Protocol-Version: 1.0
If the client sends a protocol version that the platform does not support, the platform SHOULD respond with HTTP 400 and error code UNSUPPORTED_VERSION. If the header is absent, the platform SHOULD assume version "1.0".
16.3 Backward Compatibility
Future protocol versions MUST adhere to the following compatibility requirements:
• Minor versions (e.g., 1.1, 1.2) MUST be backward compatible with version 1.0. Minor versions MAY add new optional fields to EOR, EAR, or EER, but MUST NOT remove or modify the semantics of existing fields. Version 1.0 clients MUST be able to process records created by version 1.x platforms (ignoring unrecognized fields).
• Major versions (e.g., 2.0) MAY introduce breaking changes including new required fields, modified hash computations, or new cryptographic algorithms. Major version changes require explicit migration procedures.
• Algorithm updates: Protocol version 1.0 uses fixed algorithms (Ed25519, SHA-256, JCS). Any change to the cryptographic algorithms constitutes a major version change. Algorithm agility (negotiation of algorithms between client and platform) is NOT supported in version 1.0 to minimize implementation complexity and interoperability issues.
16.4 Deprecation Policy
When a protocol version is deprecated, the following timeline applies:
• Announcement: At least 12 months notice before a protocol version is deprecated.
• Sunset period: At least 6 months after deprecation during which the version continues to function but returns deprecation warnings in the X-Elydora-Deprecation-Warning response header.
• End of life: After the sunset period, the platform MAY reject operations using the deprecated version. Existing records created under the deprecated version remain valid and verifiable indefinitely.
17. API Reference Summary
This section provides a summary of the Elydora platform API endpoints. All endpoints require authentication via Bearer token (API key) in the Authorization header. All request and response bodies are JSON (Content-Type: application/json). The base URL for the API is https://api.elydora.com/v1.
17.1 Operation Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/operations | Bearer token (integration_engineer, org_owner) | Submit a signed EOR. Executes the full validation pipeline (Section 8.1). Returns an EAR on success (HTTP 200). Request body: complete EOR JSON. Response body: complete EAR JSON. |
| GET | /v1/operations/{`{operation_id}`} | Bearer token (any role) | Retrieve a specific operation record by operation_id. Returns the complete EOR and associated EAR. HTTP 404 if not found. |
| GET | /v1/operations | Bearer token (any role) | List operations with filtering. Query parameters: agent_id, operation_type, start_time, end_time, limit (default 50, max 200), cursor (pagination). Returns paginated list of operations with EARs. |
17.2 Agent Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/agents | Bearer token (org_owner, integration_engineer) | Register a new Agent. Request body includes agent_id, display_name, responsible_entity, and at least one key. Returns the created Agent record with assigned timestamps. |
| GET | /v1/agents/{`{agent_id}`} | Bearer token (any role) | Retrieve Agent details including status, registered keys, chain state (latest seq_no, latest chain_hash), and metadata. |
| GET | /v1/agents | Bearer token (any role) | List all Agents in the Organization. Query parameters: status, limit, cursor. |
| PATCH | /v1/agents/{`{agent_id}`}/freeze | Bearer token (org_owner, security_admin) | Freeze an Agent. Sets status to "frozen". Logs admin_event. Returns updated Agent record. |
| PATCH | /v1/agents/{`{agent_id}`}/unfreeze | Bearer token (org_owner, security_admin) | Unfreeze an Agent. Sets status back to "active". Only valid for frozen Agents. Logs admin_event. |
| PATCH | /v1/agents/{`{agent_id}`}/revoke | Bearer token (org_owner, security_admin) | Permanently revoke an Agent. Sets status to "revoked". Retires all associated keys. Irreversible. Logs admin_event. |
17.3 Key Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/agents/{`{agent_id}`}/keys | Bearer token (org_owner, integration_engineer) | Register a new Ed25519 public key for the Agent. Request body: kid, algorithm ("ed25519"), public_key (base64url). Returns the created key record. |
| GET | /v1/agents/{`{agent_id}`}/keys | Bearer token (any role) | List all keys registered to the Agent with their current statuses. |
| PATCH | /v1/agents/{`{agent_id}`}/keys/{`{kid}`}/retire | Bearer token (org_owner, security_admin) | Retire a key. Sets status to "retired". Logs admin_event. |
| PATCH | /v1/agents/{`{agent_id}`}/keys/{`{kid}`}/revoke | Bearer token (org_owner, security_admin) | Revoke a key. Sets status to "revoked". Irreversible. Logs admin_event. |
17.4 Epoch Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /v1/epochs/{`{epoch_id}`} | Bearer token (any role) | Retrieve a specific epoch record (EER) by epoch_id. Includes leaf_count, root_hash, time window, and platform signature. |
| GET | /v1/epochs | Bearer token (any role) | List epochs with filtering. Query parameters: start_time, end_time, limit, cursor. |
| GET | /v1/epochs/{`{epoch_id}`}/proof/{`{operation_id}`} | Bearer token (any role) | Get the Merkle inclusion proof for a specific operation within an epoch. Returns the proof structure defined in Section 10.5. |
17.5 Verification Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/verify/operation | Bearer token (compliance_auditor, org_owner) | Verify a single operation. Performs the 4-check verification defined in Section 13.1. Request body: operation_id. Returns detailed verification report. |
| POST | /v1/verify/chain | Bearer token (compliance_auditor, org_owner) | Verify an Agent's complete chain or a range of seq_nos. Request body: agent_id, optional start_seq_no, end_seq_no. Returns chain verification report. |
| POST | /v1/verify/epoch | Bearer token (compliance_auditor, org_owner) | Verify an epoch record. Reconstructs Merkle tree and verifies root hash and signature. Request body: epoch_id. Returns epoch verification report. |
17.6 Export & Audit Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /v1/export/json | Bearer token (compliance_auditor, org_owner) | Export evidence as JSON bundle. Request body: scope (agent_id, start_time, end_time). Returns download URL for the generated bundle. |
| POST | /v1/export/pdf | Bearer token (compliance_auditor, org_owner) | Export evidence as PDF package. Request body: scope. Returns download URL for the generated PDF. |
| GET | /v1/audit/events | Bearer token (compliance_auditor, org_owner, security_admin) | List admin events. Query parameters: action, target_type, actor, start_time, end_time, limit, cursor. |
17.7 Discovery Endpoints
| Method | Path | Auth | Description |
|---|---|---|---|
| GET | /.well-known/elydora/jwks.json | None (public) | Elydora server JWKS endpoint. Returns the public keys used for signing receipts and epoch records. The response includes keys in JWK format with kty="OKP", crv="Ed25519", and the base64url-encoded public key in the "x" field. |
| GET | /.well-known/elydora/protocol-version | None (public) | Returns the protocol versions supported by this platform instance. Response: {`{"versions": ["1.0"], "current": "1.0"}`}. |
17.8 Error Response Format
All error responses follow a consistent format:
{
"error": "<ERROR_CODE>",
"message": "Human-readable description of the error.",
"details": { ... } // Optional additional context
}
// Standard error codes:
// UNSUPPORTED_VERSION 400 Unrecognized op_version
// MISSING_FIELD 400 Required field absent or empty
// INVALID_NONCE 400 Nonce exceeds 64 characters
// INVALID_TIMESTAMP 400 issued_at <= 0
// INVALID_TTL 400 ttl_ms out of range [1000, 300000]
// TTL_EXPIRED 400 issued_at + ttl_ms < server time
// PAYLOAD_TOO_LARGE 413 Payload exceeds 256 KB
// NONCE_REPLAY 409 Nonce previously observed
// AGENT_NOT_FOUND 404 Agent does not exist
// KEY_NOT_FOUND 404 Key does not exist
// AGENT_FROZEN 403 Agent is frozen
// AGENT_REVOKED 403 Agent is permanently revoked
// KEY_RETIRED 403 Key is retired
// KEY_REVOKED 403 Key is revoked
// INVALID_SIGNATURE 401 Ed25519 signature verification failed
// PREV_HASH_MISMATCH 409 Chain hash does not match expected
// UNAUTHORIZED 401 Missing or invalid auth token
// FORBIDDEN 403 Insufficient role permissions
// RATE_LIMITED 429 Rate limit exceeded
// INTERNAL_ERROR 500 Unexpected platform errorImplement the protocol.
This specification defines the complete Elydora Responsibility Protocol v1.0. Start building with the developer guide and SDK documentation.