{
"spec_version": "1",
"comment": "Canonical Ag^id (ag_id) v1 test vectors. Any conforming implementation in any language MUST reproduce the positive vectors byte-for-byte AND reject the negative cases as specified. Inputs are given as ASCII strings; treat them as their UTF-8 byte sequence.",
"protocol": {
"prefix": "agid:v1:",
"hash": "BLAKE3-256",
"encoding": "base58 (Bitcoin alphabet, no 0/O/I/l)",
"uri_prefix": "did:agid:"
},
"domain_bytes": {
"User": "0x01",
"Document": "0x02",
"Session": "0x03",
"Device": "0x04",
"Concept": "0x05",
"Opaque": "0x00 (reserved sentinel \u2014 never used to derive)"
},
"vectors": [
{
"name": "user_alice_at_example",
"domain": "User",
"domain_byte": "0x01",
"input_utf8": "alice@example.com",
"raw_hex": "1a2fdea0f9612c18e110ea43eb149d397f997a730d93ca2d8e1178835d9e1925",
"did_string": "did:agid:2mDwJhrvWdJsqHAhRTQWpaLgWmnTZxEZJv6hnDmjiYtt"
},
{
"name": "golden_v1_anchor",
"comment": "Stability anchor \u2014 MUST match across all v1.x releases. The Rust integration test tests/determinism.rs::did__derive__golden_input__matches_hardcoded_hex pins this raw_hex value.",
"domain": "User",
"domain_byte": "0x01",
"input_utf8": "agid:golden:v1",
"raw_hex": "7c62f564159188295c2eb1b4fa3b67edb81c21b07b53d21b040f1a03340de849",
"did_string": "did:agid:9NZ5Hr3TjTUMCZcx7yAUmVgvjKiXAYPGWk99jBsXqeTJ"
},
{
"name": "empty_input_user",
"comment": "Empty input is allowed and well-defined.",
"domain": "User",
"domain_byte": "0x01",
"input_utf8": "",
"raw_hex": "971734801ac0437067ac7f521b422a0e4619ef7e7ff2d15bb6adf5da0f7b7058",
"did_string": "did:agid:BAo5w7gcSBMsLqYRqDEJ7pVuj5MQM8W567A5cWmyCUcf"
},
{
"name": "unicode_cyrillic",
"comment": "Hash is over UTF-8 bytes (\"\u041c\u0438\u0445\u0430\u0438\u043b\" \u2192 0xd09c d0b8 d185 d0b0 d0b8 d0bb in UTF-8). Encoding identity is the caller's responsibility.",
"domain": "User",
"domain_byte": "0x01",
"input_utf8": "\u041c\u0438\u0445\u0430\u0438\u043b",
"raw_hex": "38f12866493850cbe71cbe622551bda534945321f7ed3ebd27364b986911fcf5",
"did_string": "did:agid:4qH7WfFHC1fcaczryBD9mHXC1Jc8PDYcWGmwdNvVr7QY"
},
{
"name": "domain_separation_user",
"comment": "Pair with `domain_separation_document` and `domain_separation_session` \u2014 same input, different domains, must produce different IDs.",
"domain": "User",
"domain_byte": "0x01",
"input_utf8": "same-input",
"raw_hex": "32bd40b0c58f71e094bb6f278fb39e6d01faa4826e59416fea77d9bdd71f5658",
"did_string": "did:agid:4R4m8n9tdzRXdMEPV2pbyWwdnX8mDyavKo68A7etME5m"
},
{
"name": "domain_separation_document",
"domain": "Document",
"domain_byte": "0x02",
"input_utf8": "same-input",
"raw_hex": "b8b7595855b3a25023ef241757c35b5913f2532889ac8e360f26d89efc4a2799",
"did_string": "did:agid:DS4AvqeTHmgHxPAHKjX3ZBLKHYqkerj5gwSUwBbXXPKW"
},
{
"name": "domain_separation_session",
"domain": "Session",
"domain_byte": "0x03",
"input_utf8": "same-input",
"raw_hex": "e256b85b39e02bd0b1251820b7bd4a295d757d302c2e1a86cc937b415a1201cd",
"did_string": "did:agid:GEXqTNt4cvGMtwatn1y7KLyor19nLexU8UMGyvJ1QsQQ"
},
{
"name": "custom_domain_0xff",
"comment": "Custom domain with byte 0xFF.",
"domain": "Custom",
"domain_byte": "0xff",
"input_utf8": "some-input",
"raw_hex": "2eb5364a26c6dbee6b5716d18d5356d42b5e7b8644c481f277c54396fc7751a4",
"did_string": "did:agid:49L26pAbHYxQpENhZT7qmMLCYJhmUeg2HaBJPsBuL2su"
},
{
"name": "long_input_1024_zeros",
"comment": "1024 bytes of 0x00. Verifies that BLAKE3 is fed the bytes verbatim with no length-prefix tampering.",
"domain": "Document",
"domain_byte": "0x02",
"input_utf8": "(1024 bytes of 0x00 \u2014 see Rust generator tests/vector_export.rs)",
"raw_hex": "b0f561357d041b2220fbf570abb85966081db915b2b6480b83b5844236edb810",
"did_string": "did:agid:Cummes8JKskjxa73rshacP3DTdvFo3eweuWGYcqvaTaw"
}
],
"negative_cases": {
"comment": "Conforming implementations MUST reject these inputs. Error names are the Rust reference's enum variants (ag_id::Error). Other-language implementations should use equivalent typed-error semantics.",
"derivation_rejections": [
{
"name": "reserved_domain_zero",
"comment": "Derivation with domain_byte = 0x00 is reserved for Opaque and must be rejected at construction. The Rust type system also prevents this at compile time (DeriveDomain has no Opaque variant and DeriveDomain::custom(0) returns Error::ReservedDomain).",
"domain_byte": "0x00",
"expected_error": "ReservedDomain"
}
],
"parse_rejections": [
{
"name": "missing_prefix_plain_string",
"comment": "A string with no DID-shape at all.",
"input_string": "not a did at all",
"expected_error": "MissingPrefix"
},
{
"name": "missing_prefix_wrong_method",
"comment": "A DID URI for a different method.",
"input_string": "did:other:3Yu9e55XiwG1UyLwh2ojPB2tUkZdJmpRv8gWf7jazngP",
"expected_error": "MissingPrefix"
},
{
"name": "missing_prefix_legacy_agf",
"comment": "An unrelated DID method prefix MUST NOT be accepted as equivalent to `did:agid:`.",
"input_string": "did:foo:3Yu9e55XiwG1UyLwh2ojPB2tUkZdJmpRv8gWf7jazngP",
"expected_error": "MissingPrefix"
},
{
"name": "missing_prefix_uppercase_method",
"comment": "DID method names are case-sensitive per W3C DID Core 1.0 \u00a73.1; `DID:AGID:` is not the same as `did:agid:`.",
"input_string": "DID:AGID:2mDwJhrvWdJsqHAhRTQWpaLgWmnTZxEZJv6hnDmjiYtt",
"expected_error": "MissingPrefix"
},
{
"name": "wrong_length_empty_payload",
"comment": "Empty base58 payload after the prefix.",
"input_string": "did:agid:",
"expected_error": "WrongLength"
},
{
"name": "wrong_length_payload_too_long",
"comment": "Payload length exceeds 44 characters; cannot decode to exactly 32 bytes.",
"input_string": "did:agid:2mDwJhrvWdJsqHAhRTQWpaLgWmnTZxEZJv6hnDmjiYttExtraTrailingCharacters",
"expected_error": "WrongLength"
},
{
"name": "invalid_base58_ambiguous_chars",
"comment": "Contains characters explicitly excluded from the Bitcoin base58 alphabet (0, O, I, l).",
"input_string": "did:agid:O0Il",
"expected_error": "InvalidBase58"
},
{
"name": "invalid_base58_underscore",
"comment": "Underscore is not in the base58 alphabet.",
"input_string": "did:agid:invalid_underscore_payload",
"expected_error": "InvalidBase58"
},
{
"name": "invalid_base58_non_ascii_payload",
"comment": "Payload contains non-ASCII bytes; conforming parsers MUST reject before base58 decoding.",
"input_string": "did:agid:\u041c\u0438\u0445\u0430\u0438\u043b",
"expected_error": "InvalidBase58"
},
{
"name": "malformed_did_partial_garbage",
"comment": "Valid prefix immediately followed by punctuation outside the base58 alphabet.",
"input_string": "did:agid:!!!",
"expected_error": "InvalidBase58"
}
]
}
}