The protocol

This section is the centerpiece of the documentation. Agreely is a real protocol, not a vendor format: every receipt, request, and DID document is built from published standards (W3C DIDs and Verifiable Credentials, RFC 8785 JSON canonicalization, Ethereum keccak256, OpenZeppelin Merkle proofs, EdDSA, and WebAuthn) wired together so that an auditor with the artifacts and these rules can verify a grant without trusting Agreely.

Each page explains not just what the construction is but why that specific standard was chosen. Start here with identity, then read canonicalization and hashing, signatures and passkeys, the on-chain registry, and IPFS document integrity.

Two kinds of identity

Agreely has two distinct identity subjects, and they use different DID methods on purpose. Do not conflate them:

  • Companies are did:web:agreely.ca:c:{slug}. Agreely hosts the company DID document, bound to the company's verified real domain.
  • Citizens are did:agreely:citizen:{opaque}. A purpose-built, opaque, anchored method.

The company method is covered under signatures and passkeys and DID documents. This page is about the citizen method.

did:agreely:citizen

The citizen DID format is:

did:agreely:citizen:<26-char Crockford base32 of 16 random bytes>

The suffix is fully opaque. It encodes no real-world identity, only 128 bits of randomness rendered in Crockford base32 (digits and uppercase letters, excluding I, L, O, and U so the handle is unambiguous when read aloud). Sixteen random bytes encode to exactly 26 characters. An example:

did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B

The DID is the only key the citizen layer is scoped by; it is tenant-less. Every passkey, recovery key, and key-history row joins through the DID, never through a company. This is half of what gives Agreely its unlinkability: the citizen side knows nothing about a company's customerId.

did:agreely:citizen keeps the identifier stable and opaque while the keys underneath it rotate freely. The identifier is anchored on-chain as an edgeless commitment (a creation/rotation event that links to no consent), so its existence is tamper-evident without exposing anything.

Key history keeps old receipts valid

A DID's keys change over time, but a receipt signed last year must still verify this year. The citizen DID document is assembled from the full key history, and every verification method carries a [validFrom, validUntil] authority window:

{
  "id": "did:agreely:citizen:9F8K...#passkey-1",
  "controller": "did:agreely:citizen:9F8K...",
  "type": "WebAuthnAuthenticationKey2024",
  "publicKeyCose": "0x...",
  "validFrom": "2026-01-10T14:00:00Z",
  "validUntil": null
}

A signature made at time T resolves to whichever key was authoritative at T, even after that key was rotated out. validUntil is present even when null so a verifier can distinguish "still valid" (null) from "expired at" (a timestamp). Recovery keys are exposed as Ed25519VerificationKey2020 and are authorized solely for DID rotation (capabilityInvocation), never for signing consent.

See the full shape in DID documents.