DID documents
A verifier resolves two DID documents to check a receipt: the company document
for companySig, and the citizen document for the WebAuthn assertion. They
use different DID methods and expose keys differently.
The company DID document
Resolved from did:web:agreely.ca:c:{slug} at
https://agreely.ca/c/{slug}/did.json. It resolves only when the company has a
verified domain and an active signing key.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/data-integrity/v2"
],
"id": "did:web:agreely.ca:c:acme",
"alsoKnownAs": ["did:web:acme.com"],
"verificationMethod": [
{
"id": "did:web:agreely.ca:c:acme#kms-1",
"type": "Multikey",
"controller": "did:web:agreely.ca:c:acme",
"publicKeyMultibase": "z6Mk...base58btc..."
}
],
"assertionMethod": ["did:web:agreely.ca:c:acme#kms-1"],
"authentication": ["did:web:agreely.ca:c:acme#kms-1"]
}
| Field | Meaning |
|---|---|
id |
The Agreely-hosted company DID. |
alsoKnownAs |
The company's verified real domain DID, the binding that proves the Agreely-hosted id belongs to the real organization. |
verificationMethod[].type |
Multikey. |
verificationMethod[].publicKeyMultibase |
The Ed25519 public key as z-prefixed base58btc of `(ed25519-pub multicodec 0xed01 |
assertionMethod / authentication |
Reference the signing key fragment. |
The root passkey and delegation (additive)
Once a company has registered a root passkey and signed a delegation, the same document additively publishes two more elements. A company with no root or delegation produces exactly the document above.
{
"verificationMethod": [
{ "id": "did:web:agreely.ca:c:acme#kms-1", "type": "Multikey", "controller": "did:web:agreely.ca:c:acme", "publicKeyMultibase": "z6Mk..." },
{
"id": "did:web:agreely.ca:c:acme#root-1",
"type": "WebAuthnAuthenticationKey2024",
"controller": "did:web:agreely.ca:c:acme",
"publicKeyCose": "0xa5010203262001...",
"rpId": "agreely.ca"
}
],
"capabilityDelegation": ["#root-1"],
"delegation": {
"type": "agreely.keyDelegation.v1",
"body": { "type": "agreely.keyDelegation.v1", "companyDid": "did:web:agreely.ca:c:acme", "delegatee": { "keyId": "0x..." }, "rootKeyId": "0x...", "registry": "0x...", "chainId": "8453", "rpId": "agreely.ca", "nonce": "0x...", "validFrom": "2026-07-20T15:00:00Z", "validUntil": "2027-07-20T15:00:00Z" },
"proof": {
"type": "WebAuthnDelegationProof2026",
"verificationMethod": "did:web:agreely.ca:c:acme#root-1",
"authenticatorData": "<base64url>",
"clientDataJSON": "<base64url>",
"signature": "<base64url>"
}
}
}
| Field | Meaning |
|---|---|
#root-1 |
The company's root passkey, under capabilityDelegation (the W3C-correct relationship: "the root delegates a capability to a subordinate key"). Its publicKeyCose is the raw COSE public key as 0x-hex; its keccak thumbprint must equal the company's DNS root=. |
capabilityDelegation |
References #root-1: the root's only capability is to delegate, never to sign a consent or authenticate. |
delegation.body |
The exact number-free canonical body the WebAuthn challenge was hashed over (challenge = keccak(JCS(body))), so a verifier recomputes the challenge itself. |
delegation.proof |
The documented proprietary proof type WebAuthnDelegationProof2026: the raw WebAuthn assertion artifacts (base64url) verified against #root-1's COSE key. There is no ratified WebAuthn Data Integrity cryptosuite, hence a published verification recipe. |
An enrolled recovery key, if any, additionally appears as Ed25519VerificationKey2020
under capabilityInvocation (authorized only to rotate the root).
The citizen DID document
Resolved from did:agreely:citizen:.... It is public, unauthenticated, and
tenant-less by design (any verifier may resolve it). It is assembled from the
full key history, so every key the DID ever held appears with its authority
window.
{
"@context": [
"https://www.w3.org/ns/did/v1",
"https://w3id.org/security/suites/ed25519-2020/v1"
],
"id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B",
"verificationMethod": [
{
"id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B#passkey-1",
"controller": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B",
"validFrom": "2026-01-10T14:00:00Z",
"validUntil": null,
"type": "WebAuthnAuthenticationKey2024",
"publicKeyCose": "0xa5010203262001..."
},
{
"id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B#recovery-1",
"controller": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B",
"validFrom": "2026-01-10T14:00:00Z",
"validUntil": null,
"type": "Ed25519VerificationKey2020",
"publicKeyMultibase": "z6Mk...base58btc..."
}
],
"authentication": [
"did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B#passkey-1"
],
"capabilityInvocation": [
"did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B#recovery-1"
]
}
| Field | Meaning |
|---|---|
validFrom / validUntil |
The key's authority window. validUntil is present even when null so a verifier distinguishes "still valid" from "expired at T". A receipt signed at time T resolves to whichever key was authoritative at T. |
WebAuthnAuthenticationKey2024 |
A passkey. Its publicKeyCose is the COSE-encoded public key as 0x-hex; verify the receipt's WebAuthn assertion against it. |
Ed25519VerificationKey2020 |
A recovery key, exposed as publicKeyMultibase. |
authentication |
Passkey fragments (authorized to sign consent). |
capabilityInvocation |
Recovery-key fragments, authorized solely for DID rotation, never for signing consent. |
The window is what keeps old receipts valid
When you verify proof[1] of a receipt, take its verificationMethod fragment, find that method in the citizen document, and confirm its [validFrom, validUntil] window contains the receipt's grantedAt. Then verify the assertion against that method's COSE key. A rotated-out key still verifies its historical receipts.
Next
- Verify a receipt ties both documents into the four-check recipe.