Test Vectors
Reference test vectors for INK v0.1 signing, encryption, replay protection, audit, transport auth, containment, key rotation, discovery gating and witness verification. These vectors use fixed key material and deterministic inputs so that two independent implementations can verify byte-for-byte correctness.
Table of Contents
- Key Material
- Signing Test Cases
- Encryption Test Cases
- Audit Chain Test Cases
- Transport Auth Test Vectors
- Containment Test Vectors
- Key Rotation Test Vectors
- Discovery Gating Test Vectors
- Witness Test Vectors
- Audit Response Signature Test Vectors
Files
| File | Description |
|---|---|
keys.json | Fixed Ed25519 and X25519 key pairs for Alice and Bob (hex-encoded) |
signing.json | Signature generation and verification test cases |
encryption.json | ECIES encryption/decryption test cases |
jcs.json | JCS canonicalization test cases |
replay.json | Replay protection acceptance/rejection test cases |
receipts-and-audit.json | Receipt signatures, audit query signatures, hash-chained events and fork detection |
Usage
- Load key material from
keys.json - Run each test case: construct the expected output from the inputs and compare
- All base64url values use no-padding encoding (RFC 4648 S5)
- All hex values are lowercase
Key Material
The test keys 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 full test vectors are 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 method, path, recipient DID, body and timestamp, the signature base is formed as method\npath\nrecipientDid\nbodyHash\ntimestamp.
{ "description": "INK-Ed25519 signature base construction from request components", "input": { "method": "POST", "path": "/agent/did:key:z6MkExampleBob22222222222222222222222222222/inbox", "recipientDid": "did:key:z6MkExampleBob22222222222222222222222222222", "body": "{\"type\":\"ink.intro\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\",\"to\":\"did:key:z6MkExampleBob22222222222222222222222222222\",\"payload\":{\"message\":\"Hello Bob\"}}", "bodyHashSha256Hex": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "timestamp": "2026-04-01T12:00:00Z" }, "expectedSignatureBase": "POST\n/agent/did:key:z6MkExampleBob22222222222222222222222222222/inbox\ndid:key:z6MkExampleBob22222222222222222222222222222\ne3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\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 did=\"<senderDid>\" ts=\"<timestamp>\" sig=\"<base64url-signature>\""}Sender identity cross-check
The body.from field must match the DID extracted from the Authorization header. If they differ, the request is rejected.
{ "description": "Sender identity cross-check — body.from matches authenticated sender", "input": { "authHeader": "INK-Ed25519 did=\"did:key:z6MkExampleAlice1111111111111111111111111\" ts=\"2026-04-01T12:00:00Z\" sig=\"abc123...\"", "authenticatedSenderDid": "did:key:z6MkExampleAlice1111111111111111111111111", "body": { "type": "ink.intro", "from": "did:key:z6MkExampleAlice1111111111111111111111111", "to": "did:key:z6MkExampleBob22222222222222222222222222222" } }, "expectedResult": "accepted", "notes": "body.from matches the DID in the Authorization header — request proceeds"}Sender mismatch error
{ "description": "sender_mismatch error when body.from differs from authenticated sender", "input": { "authHeader": "INK-Ed25519 did=\"did:key:z6MkExampleAlice1111111111111111111111111\" ts=\"2026-04-01T12:00:00Z\" sig=\"abc123...\"", "authenticatedSenderDid": "did:key:z6MkExampleAlice1111111111111111111111111", "body": { "type": "ink.intro", "from": "did:key:z6MkExampleMallory33333333333333333333333333", "to": "did:key:z6MkExampleBob22222222222222222222222222222" } }, "expectedResult": "rejected", "expectedError": { "error": "sender_mismatch", "message": "body.from does not match authenticated sender" }, "httpStatus": 403, "notes": "Prevents spoofed sender identity — the body claims to be from Mallory but the auth header 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": "ink.challenge", "correlationId": "corr-abc-123", "from": "did:key:z6MkExampleBob22222222222222222222222222222", "to": "did:key:z6MkExampleAlice1111111111111111111111111" } }, "expectedResult": "rejected", "expectedError": { "error": "challenge_limit_exceeded", "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": "ink.intro", "from": "did:key:z6MkExampleAlice1111111111111111111111111", "to": "did:key:z6MkExampleBob22222222222222222222222222222" } }, "expectedResult": "rejected", "expectedError": { "error": "rate_limit_exceeded", "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": "ink.challenge", "correlationId": "corr-abc-123", "from": "did:key:z6MkExampleMallory33333333333333333333333333", "to": "did:key:z6MkExampleAlice1111111111111111111111111" } }, "expectedResult": "rejected", "expectedError": { "error": "counterparty_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, retired key handling and signature prefix enforcement.
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", "retiredAt": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "activatedAt": "2026-03-15T00:00:00Z" } ], "message": "{\"type\":\"ink.intro\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\"}", "signedWithKeyId": "key-current-002", "signaturePrefix": "ink-ed25519:" }, "expectedResult": "accepted", "verificationKey": "key-current-002", "notes": "The active key is always tried first for verification"}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", "retiredAt": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "activatedAt": "2026-03-15T00:00:00Z" } ], "message": "{\"type\":\"ink.intro\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\"}", "signedWithKeyId": "key-bootstrap-001", "signaturePrefix": "ink-ed25519:" }, "expectedResult": "rejected", "expectedError": { "error": "key_retired", "message": "Signature was made with a retired key that is no longer accepted for new messages" }, "notes": "Retired keys cannot be used to sign new outbound messages"}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", "activatedAt": "2025-01-01T00:00:00Z" } ], "message": "{\"type\":\"ink.intro\",\"from\":\"did:key:z6MkExampleAlice1111111111111111111111111\"}", "signedWithKeyId": "key-bootstrap-001", "signaturePrefix": "ink-ed25519:" }, "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", "retiredAt": "2026-03-15T00:00:00Z" }, { "keyId": "key-current-002", "publicKeyHex": "f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1", "status": "active", "activatedAt": "2026-03-15T00:00:00Z" } ], "context": "verifying_historical_message", "messageTimestamp": "2026-03-10T00:00:00Z", "signedWithKeyId": "key-bootstrap-001", "signaturePrefix": "ink-ed25519:" }, "expectedResult": "accepted", "expectedFlags": { "usedRetiredKey": true, "retiredKeyId": "key-bootstrap-001", "retiredAt": "2026-03-15T00:00:00Z" }, "notes": "Historical messages signed with a retired key can still be verified, but the result includes a usedRetiredKey flag for the verifier to act on"}Legacy unprefixed signature rejected
{ "description": "Signature without ink-ed25519: prefix is always rejected (legacy window removed 2026-04-07)", "input": { "agentDid": "did:key:z6MkExampleAlice1111111111111111111111111", "signature": "base64url-signature-without-prefix", "signaturePrefix": null }, "expectedResult": "rejected", "expectedError": { "error": "legacy_signature_rejected", "message": "Unprefixed signatures are no longer accepted" }, "notes": "All signatures must include the ink-ed25519: prefix for domain separation. The legacy compatibility window was removed early on 2026-04-07."}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": "listed", "visibility": "network_only", "updatedAt": "2026-04-01T00:00:00Z" }, "invalidRedactedCard": { "agentId": "did:key:z6MkExampleBob22222222222222222222222222222", "displayName": "Bob", "supportsInk": true, "discoveryMode": "listed", "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", "type": "ink.intro.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\",\"type\":\"ink.intro.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", "type": "ink.intro.sent", "timestamp": "2026-04-01T12:01:00Z", "correlationId": "corr-abc-123" } }, "expectedResult": "rejected", "expectedError": { "error": "duplicate_event", "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", "type": "ink.intro.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", "type": "ink.intro.sent", "timestamp": "2026-04-01T12:00:00Z", "correlationId": "corr-abc-123" }, { "eventId": "evt-003", "agentId": "did:key:z6MkExampleAlice1111111111111111111111111", "type": "ink.challenge.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", "type": "ink.intro.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."}