Test Vectors
The authoritative cross-implementation corpus lives at conformance/v1, indexed by a machine-readable conformance/v1/manifest.json. It ships in the @adastracomputing/ink npm tarball and resolves through the ./conformance/* subpath export. Each manifest entry names its vector file, spec, summary, case count, and a SHA-256 of the vector bytes. The TypeScript reference implementation and an independent Go implementation (under go/) both run this same corpus and MUST make the same accept or reject decision on every vector.
The legacy test-vectors/*.json files predate this corpus and remain only as historical material; conformance/v1 is the contract.
Conformance categories
The conformance/v1 corpus is organized into these categories:
principal-normalizationsignature-basejcs-numberjcs-string-safetykey-rotationreplay-freshnesstimestamp-validitymerkle-inclusionmerkle-consistencymerkle-checkpointmerkle-leafinclusion-receiptaudit-query-responsehandshake-messageconnection-payloadagent-card
Running the corpus
- Read
conformance/v1/manifest.jsonto enumerate the categories and their vector files. - For each vector, run your implementation and compare its accept or reject decision against the expected outcome.
- All base64url values use no-padding encoding (RFC 4648 S5); all hex values are lowercase.
The packaged corpus, not the illustrative material below, is the conformance contract. The examples that follow show the shape of individual decisions and use fixed key material so two implementations can compare byte-for-byte.
Key Material
The keys in the illustrative cases below were generated deterministically from fixed seeds. They are NOT suitable for production use.
{ "alice": { "did": "did:key:z6MkExampleAlice1111111111111111111111111", "signing": { "publicKeyHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "privateKeyHex": "1111111111111111111111111111111111111111111111111111111111111111" }, "encryption": { "publicKeyHex": "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3", "privateKeyHex": "2222222222222222222222222222222222222222222222222222222222222222" } }, "bob": { "did": "did:key:z6MkExampleBob22222222222222222222222222222", "signing": { "publicKeyHex": "c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "privateKeyHex": "3333333333333333333333333333333333333333333333333333333333333333" }, "encryption": { "publicKeyHex": "d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5", "privateKeyHex": "4444444444444444444444444444444444444444444444444444444444444444" } }}The authoritative corpus is available in the INK repository.
Signing Test Cases
Each signing test case provides:
input: The signature base components (method, path, recipientDid, body, timestamp)expectedSignatureBase: The concatenated string before signingexpectedSignature: The base64url-encoded Ed25519 signature
Encryption Test Cases
Each encryption test case provides:
input: Plaintext payload, sender/recipient info, deterministic ephemeral key and nonceexpectedEnvelope: The complete outer envelopeexpectedDecrypted: The decrypted plaintext (must match original)
Audit Chain Test Cases
Each audit chain test case provides:
events: Ordered list of audit events with their signaturesexpectedHashes: ThepreviousEventHashvalue for each event after the firstforkDetection: Cases where duplicate sequence numbers indicate a forked chain
Transport Auth Test Vectors
Test vectors for INK-Ed25519 transport authentication. The signature base is constructed by concatenating request components with newline separators.
Signature base construction
Given a request’s protocol version, method, path, recipient DID, JCS-canonicalized body and timestamp, the signature base is formed as PROTOCOL\nMETHOD\nPATH\nrecipientDid\nJCS(body)\ntimestamp. The body is included as its JCS-canonical bytes, not a hash.
{ "description": "INK-Ed25519 signature base construction from request components", "input": { "protocol": "ink/0.1", "method": "POST", "path": "/ink/v1/intent", "recipientDid": "did:key:z6MkExampleBob22222222222222222222222222222", "body": { "type": "network.tulpa.intent", "from": "did:key:z6MkExampleAlice1111111111111111111111111", "to": "did:key:z6MkExampleBob22222222222222222222222222222", "payload": { "message": "Hello Bob" } }, "jcsCanonicalizedBody": "{\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\",\"payload\":{\"message\":\"Hello Bob\"},\"to\":\"did:key:z6MkExampleBob22222222222222222222222222222\",\"type\":\"network.tulpa.intent\"}", "timestamp": "2026-04-01T12:00:00Z" }, "expectedSignatureBase": "ink/0.1\nPOST\n/ink/v1/intent\ndid:key:z6MkExampleBob22222222222222222222222222222\n{\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\",\"payload\":{\"message\":\"Hello Bob\"},\"to\":\"did:key:z6MkExampleBob22222222222222222222222222222\",\"type\":\"network.tulpa.intent\"}\n2026-04-01T12:00:00Z", "notes": "The signature base is signed with the sender's Ed25519 private key. The resulting signature is placed in the Authorization header as: `INK-Ed25519 <86-char-base64url-signature>`, optionally followed by ` keyId=<keyId>` for rotation hints."}Authorization header form
The reference middleware accepts exactly one header shape, matched against the regex ^INK-Ed25519\s+([A-Za-z0-9_-]{86})(?:\s+keyId=([A-Za-z0-9_:.-]{1,128}))?$. Sender identity comes from body.from, timestamp from body.timestamp; there are no did= or ts= parameters in the header.
{ "description": "Authorization header without keyId", "valid": "INK-Ed25519 ZXhhbXBsZS1iYXNlNjR1cmwtc2lnbmF0dXJlLWV4YWN0bHktODYtY2hhcmFjdGVycy1sb25nLWVuY29kZWQtcw"}{ "description": "Authorization header with optional keyId hint", "valid": "INK-Ed25519 ZXhhbXBsZS1iYXNlNjR1cmwtc2lnbmF0dXJlLWV4YWN0bHktODYtY2hhcmFjdGVycy1sb25nLWVuY29kZWQtcw keyId=sig-2026-03"}Sender identity cross-check
body.from is the authoritative sender DID. After signature verification succeeds against the sender’s resolved key set, integrators that route on identity MUST also ensure any nested identity claims (e.g. payload actor) match body.from. If they differ, the request is rejected.
{ "description": "Sender identity cross-check, body.from matches authenticated sender", "input": { "authHeader": "INK-Ed25519 <86-char-base64url-signature>", "authenticatedSenderDid": "did:key:z6MkExampleAlice1111111111111111111111111", "body": { "type": "network.tulpa.intent", "from": "did:key:z6MkExampleAlice1111111111111111111111111", "to": "did:key:z6MkExampleBob22222222222222222222222222222" } }, "expectedResult": "accepted", "notes": "Authenticated sender DID (resolved from body.from + valid signature against the sender's published key set) matches the identity claim, request proceeds"}Sender mismatch error
{ "description": "sender_mismatch error when nested actor identity differs from authenticated sender", "input": { "authHeader": "INK-Ed25519 <86-char-base64url-signature>", "authenticatedSenderDid": "did:key:z6MkExampleAlice1111111111111111111111111", "body": { "type": "network.tulpa.intent", "from": "did:key:z6MkExampleAlice1111111111111111111111111", "to": "did:key:z6MkExampleBob22222222222222222222222222222", "payload": { "actor": "did:key:z6MkExampleMallory33333333333333333333333333" } } }, "expectedResult": "rejected", "expectedError": { "error": "sender_mismatch", "message": "Nested actor claim does not match authenticated sender" }, "httpStatus": 403, "notes": "Prevents spoofed downstream identity in routed messages, the payload claims to act on behalf of Mallory but the auth proves Alice signed the request"}Containment Test Vectors
Test vectors for transport scope enforcement, handshake budgets and counterparty validation.
Transport scope, token with explicit allowedTransports
{ "description": "Token with allowedTransports: [\"ink_http\"] rejects extension_api transport", "input": { "token": { "tokenVersion": "0.3", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "allowedTransports": ["ink_http"], "issuedAt": "2026-04-01T00:00:00Z" }, "requestTransport": "extension_api" }, "expectedResult": "rejected", "expectedError": { "error": "transport_scope_violation", "message": "Token does not permit transport: extension_api" }, "httpStatus": 403, "notes": "A token scoped to ink_http cannot be used on the extension_api transport"}Transport scope, legacy token (no tokenVersion)
{ "description": "Legacy token without tokenVersion is permissive during migration window", "input": { "token": { "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "issuedAt": "2025-12-01T00:00:00Z" }, "requestTransport": "extension_api", "currentDate": "2026-04-01T00:00:00Z" }, "expectedResult": "accepted", "notes": "Legacy tokens without a tokenVersion field are granted permissive transport access during the migration window to avoid breaking existing deployments"}Transport scope, v0.3 token without allowedTransports
{ "description": "v0.3 token without allowedTransports defaults to ink_http only", "input": { "token": { "tokenVersion": "0.3", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "issuedAt": "2026-04-01T00:00:00Z" }, "requestTransport": "extension_api" }, "expectedResult": "rejected", "expectedError": { "error": "transport_scope_violation", "message": "Token does not permit transport: extension_api" }, "httpStatus": 403, "notes": "When a v0.3 token omits allowedTransports, the default is [\"ink_http\"], restricting it to the standard INK HTTP transport"}Handshake budget, per-correlation challenge limit
{ "description": "Per-correlation challenge limit rejects the 4th challenge on the same correlationId", "input": { "correlationId": "corr-abc-123", "challengesSentSoFar": 3, "maxChallengesPerCorrelation": 3, "newChallengeAttempt": { "type": "network.tulpa.challenge", "correlationId": "corr-abc-123", "from": "did:key:z6MkExampleBob22222222222222222222222222222", "to": "did:key:z6MkExampleAlice1111111111111111111111111" } }, "expectedResult": "rejected", "expectedError": { "error": "handshake_budget_exhausted", "message": "Maximum challenges (3) reached for this correlation" }, "httpStatus": 429, "notes": "Prevents infinite challenge loops within a single handshake correlation"}Handshake budget, per-sender rate limit
{ "description": "Per-sender rate limit rejects the 11th intent within a 60-second window", "input": { "senderDid": "did:key:z6MkExampleAlice1111111111111111111111111", "intentsSentInWindow": 10, "windowDurationSeconds": 60, "maxIntentsPerWindow": 10, "newIntentAttempt": { "type": "network.tulpa.intent", "from": "did:key:z6MkExampleAlice1111111111111111111111111", "to": "did:key:z6MkExampleBob22222222222222222222222222222" } }, "expectedResult": "rejected", "expectedError": { "error": "sender_rate_limited", "message": "Sender rate limit exceeded: 10 intents per 60s" }, "httpStatus": 429, "notes": "Prevents a sender from flooding a recipient with handshake intents"}Counterparty mismatch
{ "description": "Challenge from non-counterparty agent is rejected", "input": { "handshakeState": { "correlationId": "corr-abc-123", "initiator": "did:key:z6MkExampleAlice1111111111111111111111111", "responder": "did:key:z6MkExampleBob22222222222222222222222222222" }, "incomingChallenge": { "type": "network.tulpa.challenge", "correlationId": "corr-abc-123", "from": "did:key:z6MkExampleMallory33333333333333333333333333", "to": "did:key:z6MkExampleAlice1111111111111111111111111" } }, "expectedResult": "rejected", "expectedError": { "error": "sender_mismatch", "message": "Challenge sender is not a participant in this handshake" }, "httpStatus": 403, "notes": "Only the two agents involved in a handshake (initiator and responder) may send challenges on that correlationId"}Key Rotation Test Vectors
Test vectors for Ed25519 key rotation and retired-key handling.
Active key verification succeeds
{ "description": "After key rotation, verification succeeds with the current active key", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "keyHistory": [ { "keyId": "key-bootstrap-001", "publicKeyHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "status": "retired", "validUntil": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "validFrom": "2026-03-15T00:00:00Z" } ], "message": "{\"type\":\"network.tulpa.intent\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\"}", "signedWithKeyId": "key-current-002" }, "expectedResult": "accepted", "verificationKey": "key-current-002", "notes": "The active key is always tried first for verification. The optional `keyId=<id>` hint in the Authorization header lets the receiver jump straight to the matching key."}Bootstrap key rejected after rotation
{ "description": "After key rotation, the bootstrap key is no longer accepted for new signatures", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "keyHistory": [ { "keyId": "key-bootstrap-001", "publicKeyHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "status": "retired", "validUntil": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "validFrom": "2026-03-15T00:00:00Z" } ], "message": "{\"type\":\"network.tulpa.intent\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\"}", "signedWithKeyId": "key-bootstrap-001", "messageTimestamp": "2026-04-01T12:00:00Z" }, "expectedResult": "rejected_by_local_policy", "expectedError": { "error": "signature_verification_failed", "message": "Signature verified against a retired key newer than the receiver's freshness policy" }, "notes": "Retired keys still verify per the authority rule, but receivers MAY refuse on local policy (e.g. reject anything older than 30 days, or refuse retired keys entirely for new messages). Senders SHOULD prefer the active key to avoid this rejection class."}Bootstrap key works when no rotation has occurred
{ "description": "Without key rotation, the bootstrap key is accepted normally", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "keyHistory": [ { "keyId": "key-bootstrap-001", "publicKeyHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "status": "active", "validFrom": "2025-01-01T00:00:00Z" } ], "message": "{\"type\":\"network.tulpa.intent\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\"}", "signedWithKeyId": "key-bootstrap-001" }, "expectedResult": "accepted", "verificationKey": "key-bootstrap-001", "notes": "When no rotation has occurred, the sole bootstrap key is the active key"}Retired key verification, accepted but flagged
{ "description": "Verifying an older message signed with a now-retired key succeeds but flags usedRetiredKey", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "keyHistory": [ { "keyId": "key-bootstrap-001", "publicKeyHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "status": "retired", "validUntil": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "validFrom": "2026-03-15T00:00:00Z" } ], "context": "verifying_historical_message", "messageTimestamp": "2026-03-10T00:00:00Z", "signedWithKeyId": "key-bootstrap-001" }, "expectedResult": "accepted", "expectedFlags": { "usedRetiredKey": true, "retiredKeyId": "key-bootstrap-001", "validUntil": "2026-03-15T00:00:00Z" }, "notes": "Historical messages signed with a retired key can still be verified per the authority rule (`active` then `retired`), and the verification result records which keyId was used so the verifier can apply local policy (e.g. flag historical-only acceptance, refuse retired keys for live writes)."}Revoked key never verifies
{ "description": "A revoked key MUST NEVER verify a signature, even one made before the revocation timestamp", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "keyHistory": [ { "keyId": "key-compromised-001", "publicKeyHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "status": "revoked", "revokedAt": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "validFrom": "2026-03-15T00:00:00Z" } ], "messageTimestamp": "2026-03-10T00:00:00Z", "signedWithKeyId": "key-compromised-001" }, "expectedResult": "rejected", "expectedError": { "error": "signature_verification_failed", "message": "No active or retired key verified the signature" }, "notes": "Revocation is a trust statement: the receiver MUST treat the key as compromised and refuse it even for messages with a pre-revocation timestamp. Retired vs revoked is the key semantic distinction."}Discovery Gating Test Vectors
Test vectors for agent card visibility modes and redacted card schema enforcement.
Public visibility, full card returned
{ "description": "Agent with public visibility returns full card on unauthenticated GET", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "visibility": "public", "authenticated": false, "method": "GET", "path": "/agent/did:key:z6MkExampleAlice1111111111111111111111111" }, "expectedResult": "full_card", "expectedResponseFields": [ "agentId", "displayName", "supportsInk", "discoveryMode", "visibility", "updatedAt", "publicKey", "endpoints", "capabilities" ], "httpStatus": 200, "notes": "Public agents expose their full card to anyone without authentication"}Network-only visibility, redacted on unauthenticated GET
{ "description": "Agent with network_only visibility returns redacted card on unauthenticated GET", "input": { "agentDid": "did:key:z6MkExampleBob22222222222222222222222222222", "visibility": "network_only", "authenticated": false, "method": "GET", "path": "/agent/did:key:z6MkExampleBob22222222222222222222222222222" }, "expectedResult": "redacted_card", "expectedResponseFields": [ "agentId", "displayName", "supportsInk", "discoveryMode", "visibility", "updatedAt" ], "excludedFields": ["publicKey", "endpoints", "capabilities", "bio", "avatar"], "httpStatus": 200, "notes": "Unauthenticated requests see only the redacted card fields"}Network-only visibility, full card on authenticated query
{ "description": "Agent with network_only visibility returns full card on authenticated query", "input": { "agentDid": "did:key:z6MkExampleBob22222222222222222222222222222", "visibility": "network_only", "authenticated": true, "authenticatedAs": "did:key:z6MkExampleAlice1111111111111111111111111", "method": "GET", "path": "/agent/did:key:z6MkExampleBob22222222222222222222222222222" }, "expectedResult": "full_card", "expectedResponseFields": [ "agentId", "displayName", "supportsInk", "discoveryMode", "visibility", "updatedAt", "publicKey", "endpoints", "capabilities" ], "httpStatus": 200, "notes": "Authenticated agents within the network see the full card"}Capability-gated visibility, redacted card on GET
{ "description": "Agent with capability_gated visibility returns redacted card on GET", "input": { "agentDid": "did:key:z6MkExampleBob22222222222222222222222222222", "visibility": "capability_gated", "authenticated": true, "authenticatedAs": "did:key:z6MkExampleAlice1111111111111111111111111", "method": "GET", "path": "/agent/did:key:z6MkExampleBob22222222222222222222222222222" }, "expectedResult": "redacted_card", "expectedResponseFields": [ "agentId", "displayName", "supportsInk", "discoveryMode", "visibility", "updatedAt" ], "excludedFields": ["publicKey", "endpoints", "capabilities", "bio", "avatar"], "httpStatus": 200, "notes": "Capability-gated agents only reveal full cards to agents holding a valid capability token, standard authentication is not sufficient"}Private visibility. 404 on GET
{ "description": "Agent with private visibility returns 404 on GET", "input": { "agentDid": "did:key:z6MkExampleBob22222222222222222222222222222", "visibility": "private", "authenticated": true, "authenticatedAs": "did:key:z6MkExampleAlice1111111111111111111111111", "method": "GET", "path": "/agent/did:key:z6MkExampleBob22222222222222222222222222222" }, "expectedResult": "not_found", "httpStatus": 404, "notes": "Private agents are invisible to discovery, the response is indistinguishable from a nonexistent agent"}Redacted card schema validation
{ "description": "Redacted card must include exactly the allowed fields and nothing else", "redactedCardSchema": { "required": ["agentId", "displayName", "supportsInk", "discoveryMode", "visibility", "updatedAt"], "additionalProperties": false }, "validRedactedCard": { "agentId": "did:key:z6MkExampleBob22222222222222222222222222222", "displayName": "Bob", "supportsInk": true, "discoveryMode": "authenticate_for_details", "visibility": "network_only", "updatedAt": "2026-04-01T00:00:00Z" }, "invalidRedactedCard": { "agentId": "did:key:z6MkExampleBob22222222222222222222222222222", "displayName": "Bob", "supportsInk": true, "discoveryMode": "authenticate_for_details", "visibility": "network_only", "updatedAt": "2026-04-01T00:00:00Z", "publicKey": "LEAKED, this field must not appear in a redacted card" }, "notes": "Implementations must strip all fields except the six required ones when producing a redacted card. Leaking publicKey, endpoints, capabilities, bio or avatar in a redacted response is a security violation."}Witness Test Vectors
Test vectors for Merkle tree construction, signed checkpoints, event deduplication and identity validation in the witness subsystem.
Merkle leaf hash
{ "description": "Merkle leaf hash is SHA-256(0x00 || JCS(event))", "input": { "event": { "eventId": "evt-001", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "eventType": "message.sent", "timestamp": "2026-04-01T12:00:00Z", "correlationId": "corr-abc-123" }, "jcsCanonicalizedEvent": "{\"agentId\":\"did:key:z6MkExampleAlice1111111111111111111111111\",\"correlationId\":\"corr-abc-123\",\"eventId\":\"evt-001\",\"timestamp\":\"2026-04-01T12:00:00Z\",\"eventType\":\"message.sent\"}", "leafPrefix": "0x00" }, "expectedLeafHashHex": "sha256(0x00 || <jcs-bytes>)", "notes": "The 0x00 prefix distinguishes leaf nodes from internal nodes, preventing second-preimage attacks. The event is JCS-canonicalized before hashing to ensure deterministic ordering of fields."}Merkle internal node hash
{ "description": "Merkle internal node hash is SHA-256(0x01 || left || right)", "input": { "leftChildHashHex": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2", "rightChildHashHex": "c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4", "internalPrefix": "0x01" }, "expectedInternalHashHex": "sha256(0x01 || a1b2c3...a1b2 || c3d4e5...c3d4)", "computation": "SHA-256(0x01 + fromHex('a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2') + fromHex('c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4'))", "notes": "The 0x01 prefix distinguishes internal nodes from leaves. Left and right are the raw 32-byte hashes of the child nodes concatenated in order."}Signed checkpoint
{ "description": "Witness produces a signed checkpoint over the Merkle root", "input": { "checkpoint": { "witnessId": "witness-primary", "merkleRoot": "e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6", "eventCount": 42, "timestamp": "2026-04-01T12:05:00Z", "previousCheckpointHash": "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3" }, "signatureBase": "JCS-canonicalized checkpoint body", "witnessSigningKeyHex": "5555555555555555555555555555555555555555555555555555555555555555" }, "expectedOutput": { "checkpoint": { "witnessId": "witness-primary", "merkleRoot": "e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6", "eventCount": 42, "timestamp": "2026-04-01T12:05:00Z", "previousCheckpointHash": "b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3" }, "signature": "<base64url Ed25519 signature over JCS(checkpoint)>" }, "notes": "The checkpoint is JCS-canonicalized before signing. The previousCheckpointHash chains checkpoints together for tamper detection."}Event deduplication, duplicate event ID
{ "description": "Submitting an event with a duplicate eventId returns 409 Conflict", "input": { "existingEventId": "evt-001", "newEvent": { "eventId": "evt-001", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "eventType": "message.sent", "timestamp": "2026-04-01T12:01:00Z", "correlationId": "corr-abc-123" } }, "expectedResult": "rejected", "expectedError": { "error": "duplicate_event_id", "message": "Event with ID evt-001 already exists" }, "httpStatus": 409, "notes": "Event IDs are globally unique within the witness. Re-submission of an already-witnessed event is rejected to prevent replay and double-counting."}Event identity validation, body.from must match event.agentId
{ "description": "Event submission rejected when authenticated sender does not match event.agentId", "input": { "authenticatedSenderDid": "did:key:z6MkExampleAlice1111111111111111111111111", "event": { "eventId": "evt-002", "agentId": "did:key:z6MkExampleMallory33333333333333333333333333", "eventType": "message.sent", "timestamp": "2026-04-01T12:02:00Z", "correlationId": "corr-def-456" } }, "expectedResult": "rejected", "expectedError": { "error": "sender_mismatch", "message": "Authenticated sender does not match event.agentId" }, "httpStatus": 403, "notes": "Agents can only submit witness events for themselves. The authenticated sender DID must match the event's agentId field."}Audit Response Signature Test Vectors
Test vectors for domain-separated audit response signatures. The witness signs audit responses so that querying agents can verify the response was not tampered with in transit.
Domain-separated audit response signature
{ "description": "Audit response signature uses domain-separated prefix before JCS(events)", "input": { "domainSeparator": "ink/audit-response\n", "events": [ { "eventId": "evt-001", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "eventType": "message.sent", "timestamp": "2026-04-01T12:00:00Z", "correlationId": "corr-abc-123" }, { "eventId": "evt-003", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "eventType": "message.received", "timestamp": "2026-04-01T12:00:05Z", "correlationId": "corr-abc-123" } ], "jcsCanonicalizedEvents": "<JCS-canonicalized JSON array of events>", "signatureBase": "ink/audit-response\n<JCS-canonicalized JSON array of events>", "witnessSigningKeyHex": "5555555555555555555555555555555555555555555555555555555555555555" }, "expectedOutput": { "events": ["<events array as above>"], "signature": "<base64url Ed25519 signature over domain-separated content>", "witnessId": "witness-primary" }, "notes": "The domain separator 'ink/audit-response\\n' is prepended to the JCS-canonicalized events array before signing. This prevents cross-context signature reuse, a signature over a checkpoint cannot be confused with a signature over an audit response."}Audit response signature verification
{ "description": "Verifier reconstructs the signature base and checks the witness signature", "input": { "auditResponse": { "events": [ { "eventId": "evt-001", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "eventType": "message.sent", "timestamp": "2026-04-01T12:00:00Z", "correlationId": "corr-abc-123" } ], "signature": "<base64url Ed25519 signature>", "witnessId": "witness-primary" }, "witnessPublicKeyHex": "e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6" }, "verificationSteps": [ "1. Extract events array from the response", "2. JCS-canonicalize the events array", "3. Prepend domain separator: 'ink/audit-response\\n'", "4. Verify Ed25519 signature against the witness public key" ], "expectedResult": "signature_valid", "notes": "The verifier must use the same domain separator and JCS canonicalization to reconstruct the exact bytes that were signed. If the signature does not verify, the audit response may have been tampered with."}