Signing test vectors

A signature is right to the byte or it is refused, and a refusal never says which byte. These vectors let you find the byte yourself: each one is a real request, answer or enrolment proof with the exact text that was signed and the exact result expected.

Using the .NET SDK?

Use SDK 2.2.0. This page is for a client written by hand — or by an AI assistant.

Test keys only

The keys in these files are test keys — never Anis’s or any partner’s real keys — and partners.anis.ly is an example address. Use them to test, never to enrol.

What is here

Set What it tests Vectors
Requests Your signature base, byte for byte, for every kind of signed call 9
Answers Your answer checks: which answers to accept and which to throw away 50
Enrolment Your proof-of-possession message 2
Webhooks Incoming notification digest, destination, application, time, signature and key purpose See the published index

Webhook vectors use their supplied verification time and raw bytes. Compare the expected accept or reject result before using a verifier with real deliveries.

/vectors/index.json lists every vector with its title, its file and — for answers — whether it must be accepted or rejected. The same index names the signed parts of each kind of call.

Testing request signing

Each request vector carries the request (request), the signature’s times (signature.created, signature.expires), the test key (key) and what must come out (expected).

  1. Build the signature base from request.method, request.authority, request.path, request.canonicalQuery (the query without its ?), request.anisDate, request.nonce, request.idempotencyKey and the body in request.bodyBase64, with key.keyId as the key id.
  2. Compare your Content-Digest with expected.contentDigest, your Signature-Input with expected.signatureInput, and your base with expected.signatureBaseUtf8 — byte for byte.
  3. Sign your base with the test private key (key.privateKeyPkcs8Base64) and check that key.publicJwk verifies it. An ECDSA signature is different every time, so compare the base, never the signature bytes.

RQ-03 gives the host as a caller might type it (request.authorityAsGiven) and as it must be signed (request.authority, lower-case). RQ-06 is a reveal: no body at all, so the digest of zero bytes.

Testing answer verification

Each answer vector carries the answer (response.status, response.headers, response.bodyBase64), the Signature-Input of the request it answers (request.signatureInput and request.signature), Anis’s published keys at that moment (signingKeys) and the time to use as your clock (verifyAt, Unix seconds — use it, not the real clock).

Your verifier must reach expected.outcome: accept or reject. For a rejection, expected.reason says why; your own error names may differ, so match the outcome and use the reason to understand a mismatch. signatureBaseForDebugging is the base Anis signed.

Most answers come in families: one that must be accepted (RS-001-…) and changed copies of it (RS-001-…-M01 onward) that must each be rejected — one body byte changed, a rewritten digest, a DER signature, a signed part missing, a time out of the window. Others test the published keys: an answer signed by the next or the retiring key inside its window must be accepted, an answer created before its key’s notBefore must be rejected, and a key document carrying a private member must be refused whole.

RS-112 tests an active key and RS-113 a retired key with created < notBefore: both must be rejected as key_not_yet_valid.

The answer vectors test the verification rules. Some use a read of the profile as their example answer; Anis does not sign profile answers — it signs the answers of these calls (orders, reveals, enrolment, the signature self-check, application suspension and key revocation), and those are the answers your verifier applies these rules to.

Testing the enrolment proof

Each enrolment vector gives what the key submission returned (keySubmissionResult) and the test key (key).

  1. Build the proof message from keySubmissionResult and compare it with expected.proofMessageUtf8 — and the challenge’s hash with expected.challengeHashHex.
  2. Sign it with the test key; exampleSignature is one valid signature, and key.publicJwk verifies both.
  3. expected.proofRequest is the body to send.

After the vectors pass

Call Check your signature with your real key. It runs every check Anis runs on a signed call, reports what Anis saw, and changes nothing.

Checking the v2 answer profile

SDK 2.0.1 and later require tag="anis-response-v2". The covered order is "@status", "content-digest", "x-request-id", then "signature-input";req and "signature";req when the request was signed, then location, retry-after, idempotency-replayed and cache-control when present. "signature";req is the request’s raw signature field exactly as received.

Test status, notBefore and retiredAt in keys[] and the revoked[] list: a compromised key is rejected as key_compromised; a retired key with created > retiredAt as key_retired; any key — active, next or retired — with created < notBefore as key_not_yet_valid. A missing or different tag is profile_unexpected; a different covered list is covered_components_mismatch. Test the exact time boundaries too.

Retired keys remain listed for 90 days after retiredAt. Anis announces a compromise within 24 hours by email and in revoked[], where the entry remains for 90 days. Follow the verification rules; the vector copy must match the v2 profile before you use it to certify your client.