Registre sur la chaîne

La chaîne est la couche d'inviolabilité d'Agreely. Elle consigne qu'un consentement a existé, qu'une révocation s'est produite, qu'une identité d'entreprise a été créée ou a fait l'objet d'une rotation, qu'un document de consentement existait, et que le journal d'accès est intègre, d'une manière que personne (y compris Agreely) ne peut réécrire en douce. C'est une preuve, jamais le chemin de lecture.

La chaîne est une preuve, pas le chemin de lecture

La vérification synchrone lit une ligne dans le backend d'Agreely et ne touche jamais à la chaîne, à l'indexeur ni à aucun état d'ancrage. Un octroi ou une révocation fait autorité à l'instant où il est validé dans la base de données; l'ancrage se produit ensuite de façon asynchrone. Ainsi, une révocation refuse dès la toute prochaine vérification, sans aucune attente de la chaîne, et la chaîne donne tout de même à un auditeur une preuve indépendante et à l'épreuve de la falsification de ce qui s'est passé.

Ce qui est ancré

Quatre types de données sont écrits sur Base Sepolia. Chaque charge utile est une valeur bytes32 opaque -- une racine, un hachage ou un engagement -- jamais des renseignements personnels, jamais un identifiant ou une chaîne DID citoyen.

  1. Consentement -- la racine de Merkle d'un consentement signé ainsi que le consentRef par cellule couvrant chaque portée de collecte; les révocations font passer une ref de Active à Revoked. (anchorBatch / revoke)
  2. Registre DID d'entreprise -- une table de correspondance de domainHash (keccak256(domaine-d-entreprise)) vers la chaîne DID opérationnelle de l'entreprise, l'empreinte de la clé de signature et le CID IPFS du did.json courant. Prend en charge l'enregistrement, la rotation et la révocation. (registerCompanyDid / rotateCompanyDid / revokeCompanyDid, émettant CompanyDidRegistered / CompanyDidRotated / CompanyDidRevoked)
  3. Document de consentement -- keccak256( utf8(cid) ) du CID IPFS du document de divulgation publié. Un ancrage à émission seule et sans arête prouvant que le document existait à un instant donné, sans lien vers quelque consentement ou citoyen que ce soit. (anchorIdentity utilisé comme ancrage de document, émettant IdentityAnchored)
  4. Racines d'époque du journal d'accès -- une racine à l'épreuve de la falsification sur l'époque périodique du journal d'accès, satisfaisant l'obligation de piste d'audit de la Loi 25. (AuditRootAnchored)

Le DID citoyen n'est PAS ancré

L'identité du citoyen -- l'identifiant did:agreely:citizen:..., le document DID, les clés publiques -- n'est jamais écrite sur la chaîne. L'identité citoyenne est hors chaîne et pseudonyme par conception. Seuls des hachages et des racines opaques atteignent la chaîne : jamais un identifiant pouvant créer un lien, jamais des renseignements personnels. Le lien du citoyen dans un reçu est sa signature WebAuthn, vérifiable par le résolveur DID citoyen hors chaîne à /did/{did}, et non un enregistrement sur la chaîne. C'est délibéré : inscrire un identifiant citoyen sur la chaîne romprait la non-corrélation et pourrait constituer un traitement interdit de renseignements personnels au sens de la Loi 25.

AgreelyRegistry

AgreelyRegistry est un petit contrat Solidity : une table de correspondance de clés opaques, une liste d'autorisation onlyRelayer régie par Ownable, et des événements indexés que l'indexeur hors chaîne décode mot pour mot. Il n'y a aucun ether, aucun appel externe et aucun delegatecall, de sorte que la réentrance est structurellement impossible.

Un portefeuille chaud relayeur d'Agreely approvisionné (jamais le citoyen) soumet les écritures. Chacune ne porte que des clés opaques : une racine, un engagement ou un bytes32 sans arête, jamais de renseignements personnels, jamais une chaîne DID.

anchorBatch

function anchorBatch(bytes32 merkleRoot, bytes32[] calldata consentRefs) external onlyRelayer

Ancre un consentement : sa racine de Merkle signée plus les consentRefs par cellule qu'il couvre. Le merkleRoot est stocké contre chaque ref de sorte qu'un reçu détenu puisse vérifier l'inclusion d'une cellule sur la chaîne. Une ref déjà Active est sans effet (aucune écriture d'état, aucun événement), de sorte qu'une tâche de relayeur re-livrée ne peut ni agiter l'état ni dupliquer un journal. Une ref Revoked peut être ré-ancrée vers Active, ce qui est le chemin de ré-octroi. L'argument consentRefs[] existe pour qu'une future fenêtre de regroupement réservée au relayeur puisse rassembler plusieurs consentements sous une seule racine sans aucune agitation du contrat.

revoke

function revoke(bytes32 consentRef) external onlyRelayer

Fait passer une ref Active à Revoked. Elle annule la transaction si la ref n'est pas Active (jamais ancrée, ou déjà révoquée), de sorte qu'une tâche de révocation re-livrée est interceptée par le contrôle préalable du relayeur plutôt que d'émettre un second ConsentRevoked parasite.

anchorIdentity

function anchorIdentity(bytes32 commitment) external onlyRelayer

Ancre un engagement bytes32 opaque. Émission seule : aucune écriture dans la table de correspondance, de sorte que l'ancrage ne renvoie jamais à un quelconque consentRef. Achemine actuellement deux types d'engagements : l'ancrage de document de consentement (keccak256( utf8(cid) ) pour le CID IPFS d'une divulgation publiée), et les engagements opaques issus des événements de cycle de vie de création ou de rotation de DID citoyen. Aucune de ces écritures ne porte un identifiant DID, un identifiant client ni aucune donnée pouvant créer un lien.

registerCompanyDid / rotateCompanyDid / revokeCompanyDid

Ces méthodes constituent le registre DID d'entreprise introduit lors du redéploiement du 4 juillet 2026. registerCompanyDid stocke la chaîne DID opérationnelle, l'empreinte de la clé de signature (keyId) et le CID IPFS du did.json, indexé par domainHash = keccak256(domaine vérifié en minuscules). rotateCompanyDid met à jour la clé et le CID pour un domaine existant. revokeCompanyDid marque l'enregistrement d'un domaine comme révoqué.

Un vérificateur dérive le même domainHash depuis le champ alsoKnownAs du did.json du reçu (le domaine de l'entreprise) et interroge le registre pour confirmer que la clé, la chaîne DID et le CID du did.json sont durablement sur la chaîne.

État et événements

enum State { None, Active, Revoked, Superseded }

struct Anchor {
    State state;
    bytes32 merkleRoot;
    uint64  anchoredAt;
    uint64  revokedAt;
}

event AnchorBatch(bytes32 indexed merkleRoot, uint256 refCount, uint64 timestamp);
event ConsentAnchored(bytes32 indexed consentRef, bytes32 indexed merkleRoot, uint64 timestamp);
event ConsentRevoked(bytes32 indexed consentRef, uint64 timestamp);
event IdentityAnchored(bytes32 indexed commitment, uint64 timestamp);
event AuditRootAnchored(bytes32 indexed epochRoot, uint64 timestamp);
event CompanyDidRegistered(bytes32 indexed domainHash, string did, bytes32 keyId, string didDocCid, uint64 verifiedAt);
event CompanyDidRotated(bytes32 indexed domainHash, bytes32 newKeyId, string newDidDocCid, uint64 rotatedAt);
event CompanyDidRevoked(bytes32 indexed domainHash, uint64 timestamp);
event RelayerSet(address indexed relayer, bool allowed);

State.None est la valeur par défaut, de sorte qu'une ref inconnue se relit comme None. Superseded est réservé au remplacement lors d'un ré-ancrage ou d'un renouvellement. Deux méthodes de lecture ouvertes, registry(consentRef) et stateOf(consentRef), permettent à tout vérificateur d'interroger directement l'état d'une ref.

L'indexeur hors chaîne décode l'ensemble complet des événements : AnchorBatch, ConsentAnchored, ConsentRevoked, IdentityAnchored, AuditRootAnchored, CompanyDidRegistered, CompanyDidRotated, CompanyDidRevoked et RelayerSet. Les charges utiles d'ancrage ne portent que des valeurs bytes32 opaques (des racines ou des engagements), jamais de renseignements personnels.

Modèle DID d'entreprise : trois couches

Plusieurs utilisateurs ont été confondus par le fonctionnement de l'identité d'entreprise. Voici l'explication en langage courant des deux DID d'entreprise et des trois couches vérifiables qui les ancrent.

Les deux DID

Une entreprise dans Agreely possède deux DID a des positions différentes sur le spectre de l'auto-gestion de l'identité.

DID opérationnel (actif aujourd'hui) : did:web:agreely.ca:c:<slug>

C'est le DID qu'Agreely héberge pour le compte de l'entreprise. C'est le id dans le did.json de l'entreprise, l'issuer sur chaque reçu signé, et la chaîne DID stockée dans le registre sur la chaîne. Son document DID (le did.json contenant la clé publique de l'entreprise) est servi à https://agreely.ca/c/<slug>/did.json. Tout vérificateur le résout par une simple requête HTTPS GET, sans infrastructure particulière.

DID réservé (futur, pas encore actif) : did:web:<domaine-d-entreprise>

Il s'agit d'un espace réservé pour une future mise à niveau vers l'auto-hébergement. Dès qu'une entreprise sert son propre did.json à https://<domaine-d-entreprise>/.well-known/did.json, elle peut prendre l'entière garde de son identifiant. Aujourd'hui ce DID n'est ni résolu ni utilisé pour signer. Il n'apparaît que dans le champ alsoKnownAs du did.json opérationnel, en tant qu'ancre de domaine dont le vérificateur se sert pour dériver le domainHash. Aucun did.json n'est servi sur le domaine propre de l'entreprise aujourd'hui. Ne prétendez pas que le modèle auto-hébergé est actif; il est réservé pour une future mise à niveau de souveraineté.

Le domaine de l'entreprise est l'ancre d'identité dans les deux cas : l'enregistrement DNS TXT, le domainHash sur la chaîne et le champ alsoKnownAs du did.json font tous référence au domaine de l'entreprise.

Couche 1 : L'enregistrement DNS TXT (preuve de contrôle du domaine)

L'entreprise publie un enregistrement TXT permanent dans son propre DNS :

Hôte :   _agreely.<domaine-d-entreprise>
Valeur : agreely-did=did:web:agreely.ca:c:<slug>; key=<empreinte-sha256>

Cet enregistrement est la déclaration propre de l'entreprise indiquant quel DID et quelle clé lui appartiennent. Agreely vérifie l'enregistrement avant que quoi que ce soit n'arrive sur la chaîne : pas d'enregistrement TXT valide, pas d'inscription sur la chaîne, pas de résolution du document DID. L'empreinte de clé dans le TXT (key=...) est le même keyId stocké dans le registre sur la chaîne, de sorte que la déclaration DNS, le document DID et le registre désignent tous une seule et même clé.

Couche 2 : Le document DID (did.json)

Le did.json est le Document DID W3C contenant la clé publique de signature de l'entreprise. Agreely l'héberge à https://agreely.ca/c/<slug>/did.json -- c'est le chemin de résolution did:web. Une copie IPFS est également épinglée via Lighthouse et son CID est enregistré sur la chaîne.

IPFS est une copie ancrée pour la durabilité et l'intégrité, pas le chemin de résolution. did:web se résout toujours via HTTPS. L'épinglage IPFS garantit que le cliché du did.json est récupérable et à l'épreuve de la falsification indépendamment du serveur d'Agreely, et le CID sur la chaîne permet à un vérificateur de confirmer que la copie épinglée n'a pas changé depuis l'enregistrement.

Le champ alsoKnownAs du did.json porte did:web:<domaine-d-entreprise> (le DID réservé). Un vérificateur utilise le domaine de l'entreprise à l'intérieur de alsoKnownAs pour dériver le domainHash et confirmer qu'il correspond à l'enregistrement sur la chaîne. Il s'agit d'un contrôle de liaison de domaine, pas d'une étape de résolution DID.

Couche 3 : Le registre sur la chaîne

Lorsque l'enregistrement DNS TXT est vérifié, Agreely met en file d'attente une transaction registerCompanyDid qui stocke :

Champ Valeur
domainHash keccak256(domaine-d-entreprise en minuscules) -- la clé de domaine opaque
did La chaîne DID opérationnelle (did:web:agreely.ca:c:<slug>)
keyId Empreinte SHA-256 en bytes32 de la clé de signature (identique au DNS TXT)
didDocCid CID IPFS du cliché du did.json au moment de l'enregistrement

Le vérificateur dérive le domainHash depuis le domaine alsoKnownAs du reçu et interroge le registre. Une correspondance prouve que l'entreprise a enregistré cette clé et ce DID de façon durable sur la chaîne, indépendamment du backend d'Agreely.

Déploiement

Le registre est en service sur Base Sepolia (chain id 84532) à 0x3532a9DfF3C910152543afCa49De7c66113C5312. Le réseau principal Base (8453) n'est pas encore déployé.

La chaîne est une preuve, jamais le chemin de lecture, et elle ne conditionne jamais la vérification synchrone.

Ensuite

  • Intégrité du document sur IPFS : d'où vient le CID de document ancré, et pourquoi IPFS est une copie ancrée et non le chemin de résolution did:web.
  • Vérifier un reçu de bout en bout par rapport à la racine ancrée.
  • Documents DID : les formes des documents DID d'entreprise et citoyen qu'un vérificateur résout.