AY Developer docs

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

FileTagSignerCases
rfc9421-ay-agent.jsonay-agentA QA agent with a developer key #dev-1valid, tampered_body, expired, replay_same_nonce, wrong_tag_context
rfc9421-ay-t2a.jsonay-t2aA business twin calling an agentvalid, tampered_body, expired, replay_same_nonce, t2t_signature_to_agent, wrong_tag_context, foreign_twin, foreign_twin_signature_itself_valid
rfc9421-ay-t2t.jsonay-t2tA QA twin calling another twinvalid, 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 argument

Every 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.

CaseExpected statusExpected reason
validvalid
der_signatureinvalidbad_signature: the signature is DER, not raw 64 bytes
foreign_issuerinvalidissuer_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

OutcomeMeaning
okAccept the request.
digest_mismatchThe body does not match Content-Digest.
expiredOutside the 300 second window.
replayThe nonce was already seen for this key.
bad_signatureThe signature does not verify, or the tag or components do not match the profile.
signer_not_expectedThe signing DID is not the pinned sender (ay-t2a only).