Test vectors
Real signed requests for all three signature contexts, a signed twin mandate, and reference verifiers in Python.
The test vectors are real requests signed with real keys against real DID documents. Use them to check your signer or verifier byte for byte before you go live.
They live in the backend repository under docs/identity/testvectors/. Ask your AY contact for access, or download them from the links below once they are published.
Files
| File | Tag | Signer | Cases |
|---|---|---|---|
rfc9421-ay-agent.json | ay-agent | A QA agent with a developer key #dev-1 | valid, tampered_body, expired, replay_same_nonce, wrong_tag_context |
rfc9421-ay-t2a.json | ay-t2a | A business twin calling an agent | valid, tampered_body, expired, replay_same_nonce, t2t_signature_to_agent, wrong_tag_context, foreign_twin, foreign_twin_signature_itself_valid |
rfc9421-ay-t2t.json | ay-t2t | A QA twin calling another twin | valid, tampered_body, expired, replay_same_nonce, retry_fresh_signature |
Each file carries the profile (alg, label, tag, components, max_age_s, clock_skew_s, nonce_ttl_s), the signer's DID and key, the request, and one entry per case with the exact headers, the body, the expected outcome, and now, the clock to pin your verifier to.
The ay-t2a vector was produced on a local stack whose host is not reachable from the internet. It therefore embeds the DID documents it needs, and it names expected_signer_did, the DID your agent would have pinned from controller.
Reference verifier
verify_example.py verifies a vector file with the same rules as the AY backend.
pip install http-message-signatures==2.0.1 base58
python verify_example.py rfc9421-ay-t2t.json # tag defaults to ay-t2t
python verify_example.py rfc9421-ay-agent.json ay-agent
python verify_example.py rfc9421-ay-t2a.json ay-t2a # expected signer DID from the file, or pass it as third argumentEvery case prints PASS or FAIL; the exit code is 0 only when all cases pass. Run the ay-t2a file with the wrong tag to see the context separation fail as intended.
Twin mandate
twin-mandate.json holds three signed TwinMandateCredential JWTs and the DID documents of the human and the twin. It was produced with a software P-256 key. The status list is not part of the vector. See Mandate for the rules.
| Case | Expected status | Expected reason |
|---|---|---|
valid | valid | |
der_signature | invalid | bad_signature: the signature is DER, not raw 64 bytes |
foreign_issuer | invalid | issuer_not_controller: iss is not the twin's controller |
A short verifier in Python:
import base64, json
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature
b = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))
v = json.load(open("twin-mandate.json"))
docs = {d["id"]: d for d in v["did_documents"]}
for case in v["cases"]:
h, p, s = case["jwt"].split(".")
header, payload, sig = json.loads(b(h)), json.loads(b(p)), b(s)
if docs[payload["sub"]].get("controller") != payload["iss"]:
print(case["name"], "issuer_not_controller"); continue
doc = docs[payload["iss"]]
assert header["kid"] in doc["assertionMethod"]
jwk = next(m for m in doc["verificationMethod"] if m["id"] == header["kid"])["publicKeyJwk"]
key = ec.EllipticCurvePublicNumbers(int.from_bytes(b(jwk["x"]), "big"), int.from_bytes(b(jwk["y"]), "big"), ec.SECP256R1()).public_key()
if len(sig) != 64:
print(case["name"], "bad_signature"); continue
der = encode_dss_signature(int.from_bytes(sig[:32], "big"), int.from_bytes(sig[32:], "big"))
key.verify(der, f"{h}.{p}".encode(), ec.ECDSA(hashes.SHA256()))
print(case["name"], "valid")Expected outcomes
| Outcome | Meaning |
|---|---|
ok | Accept the request. |
digest_mismatch | The body does not match Content-Digest. |
expired | Outside the 300 second window. |
replay | The nonce was already seen for this key. |
bad_signature | The signature does not verify, or the tag or components do not match the profile. |
signer_not_expected | The signing DID is not the pinned sender (ay-t2a only). |