FR EN

Signature d'époque et délégation par clé d'accès

Deux couches, toutes deux additives et neutres, renforcent la reddition de comptes des racines d'époque sans jamais conditionner la vérification.

  1. Signature d'époque : chaque racine d'époque (journal d'accès, consentement par témoin, journal d'attestations, registres scellés) est signée, au moment du scellement, avec la clé DID Ed25519 opérationnelle enregistrée de l'entreprise.
  2. Délégation par clé d'accès racine : une fois, à la mise en place de l'espace de travail, l'entreprise enregistre une clé d'accès (WebAuthn) comme racine de son DID, puis signe une délégation autorisant cette clé de signature opérationnelle. L'entreprise signe rarement (mise en place, rotation), jamais à chaque époque.

Ce que ces couches sont, et ce qu'elles ne sont pas

La clé de signature opérationnelle est hébergée par Agreely : elle est générée côté serveur et son secret est scellé sous la clé de chiffrement du locataire, de sorte qu'Agreely peut la déballer et signer pour le compte de l'entreprise. Ces couches documentent que la racine porte l'attestation de l'entreprise et que l'entreprise a autorisé la clé de signature avec sa propre clé d'accès, engagée dans son DNS. Ce n'est pas de la non-répudiation, ce n'est pas « vous seul », et ce n'est pas à l'épreuve de toute falsification. La formule honnête est : « signé avec la clé DID enregistrée de votre entreprise » et « vous autorisez la clé de signature avec votre propre clé d'accès, engagée dans votre DNS, vérifiable et révocable ». C'est un appui à la responsabilité de l'organisation à l'égard des renseignements personnels qu'elle détient (P-39.1, art. 3.1 al. 1 ; A-2.1, art. 52.2), non une garantie de conformité.

1. La signature d'époque

Une époque est une tranche périodique d'un journal, résumée par une racine de Merkle : le journal d'accès (par entreprise), le journal de consentement par témoin (par site web), le journal d'attestations (par entreprise) ou les registres scellés (par entreprise : incidents, diffusions, horloge de réception des demandes d'exercice de droits, purges déclarées). La racine est ancrée sur la chaîne via CookieConsentRootAnchored (consentement par témoin) ou AuditRootAnchored (les trois voies de l'entreprise), de sorte qu'un auditeur peut confirmer que l'époque existait et n'a pas été réécrite après coup.

Au moment du scellement, la racine est aussi signée avec la clé DID opérationnelle enregistrée de l'entreprise (#kms-1, la même clé Ed25519 que companySig). Cela découple « pouvoir ancrer une racine sur la chaîne » (le relayeur d'Agreely) de « pouvoir produire l'attestation de l'entreprise sur cette racine » (la clé DID enregistrée de l'entreprise), un renforcement réel de défense en profondeur.

Le descripteur signé

La signature est prise sur un descripteur sans nombre (chaque valeur numérique voyage sous forme de chaîne) afin qu'il se canonicalise (JCS, RFC 8785) de manière reproductible. Un tiers reconstruit exactement les mêmes octets :

{
  "type": "agreely.auditEpoch.v1",
  "did": "did:web:app.agreely.ca:c:acme",
  "root": "0x<racine de Merkle de l'époque, 32 octets en hex>",
  "seqFrom": "1024",
  "seqTo": "2048",
  "chainId": "8453",
  "scope": "<l'identifiant de l'entreprise, ou du site web pour une époque de témoins>",
  "registry": "0x<adresse minuscule du contrat AgreelyRegistry>",
  "keyId": "0x<sha256 de la clé publique brute, le keyId sur la chaîne>",
  "didKeyId": "kms-1"
}
Champ Signification
type La voie et la recette de Merkle : agreely.auditEpoch (journal d'accès), agreely.cookieEpoch (consentement par témoin), agreely.attestationEpoch (journal d'attestations) ou agreely.registerEpoch (registres scellés), suivi de .v1 ou .v2. Le suffixe suit la recette d'arbre figée sur l'époque : .v2 distingue une feuille d'un nœud interne en hachant chaque feuille avec un octet de voie.
did Le DID d'entreprise enregistré qui atteste (did:web:app.agreely.ca:c:<slug>).
root La racine de Merkle de l'époque, 32 octets, pliée en hex 0x minuscule.
seqFrom / seqTo La plage inclusive de séquences que l'époque couvre. Rejoue l'appartenance sans l'identifiant auto-incrémenté de la base. Pour une époque d'attestations ou de registres, qui n'a pas de séquence, c'est la plage d'index des feuilles : de "1" au nombre de feuilles.
chainId La chaîne d'ancrage (8453 = Base, le réseau principal).
scope L'identifiant de l'entreprise (journal d'accès, journal d'attestations, registres scellés) ou du site web (consentement par témoin) auquel l'époque appartient.
registry L'adresse du contrat AgreelyRegistry (minuscule, hex 0x). Épingle la signature au bon contrat, car chainId seul ne lie pas le contrat (nous redéployons ; deux registres peuvent partager un chainId).
keyId Le keyId sur la chaîne de la clé de signature = 0x + sha256(clé publique brute), le même bytes32 que registerCompanyDid publie. Il décrit quelle clé a signé et permet de raccorder la signature à la délégation.
didKeyId Le libellé local de la clé de signature (kms-1, ...), afin que la ligne locale et la clé sur la chaîne se résolvent toutes deux.

La signature elle-même est une signature Ed25519 détachée de 64 octets sur les octets JCS-canoniques de ce descripteur. Un vérificateur résout la clé publique depuis le #kms-1 du did.json de l'entreprise et contrôle la signature elle-même. Que cette clé soit bien celle de l'entreprise, c'est son enregistrement DNS et le registre sur la chaîne qui le confirment, plus bas.

Une déclaration isolée (une attestation d'anonymisation, une destruction, une version publiée d'une politique) est scellée de la même manière, sans plage de séquences : le descripteur agreely.declarationSeal.v1 lie declarationHash (le keccak256 de la déclaration canonique) à did, chainId, registry, keyId et didKeyId. Il documente que la déclaration a été scellée avec la clé de l'entreprise, jamais que l'anonymisation ou la destruction a réellement eu lieu.

Neutralité : une époque non signée reste pleinement valide

La signature d'époque est additive. Une époque sans signature (une entreprise sans clé provisionnable, ou une époque scellée avant cette couche) se vérifie exactement comme avant : sa propre intégrité (le rejeu de la chaîne de hachage plus l'ancrage sur la chaîne) est jugée à part et n'est pas touchée. Une signature absente ou invalide dégrade vers « non attesté par l'entreprise », jamais vers un échec d'intégrité.

2. La délégation par clé d'accès racine

La clé de signature opérationnelle est hébergée par Agreely. Pour qu'une entreprise puisse autoriser cette clé avec du matériel qu'elle contrôle elle-même, elle enregistre une clé d'accès racine et signe une délégation.

La racine : une clé d'accès WebAuthn

À la mise en place de l'espace de travail (dans la phase des fondations, après la vérification du domaine), l'entreprise enregistre une clé d'accès WebAuthn dédiée et découvrable. Chaque identifiant racine reçoit son propre userHandle aléatoire de 32 octets, de sorte qu'une racine de secours n'écrase jamais la première sur le même authentificateur. La clé publique COSE brute est le matériel racine faisant autorité ; elle est publique. Le rpId est figé sur le domaine apex d'Agreely, agreely.ca. Seuls ES256 (-7, P-256) et EdDSA (-8, Ed25519) sont acceptés. La toute première racine de l'entreprise est activée par son propriétaire, qui saisit de nouveau son mot de passe ; toute racine ultérieure exige l'assertion d'une racine déjà active, ou le cérémonial de récupération.

Cette racine n'est jamais réutilisée pour l'authentification ou la connexion : elle sert uniquement à la délégation de capacité (capabilityDelegation). Un vérificateur rejette une assertion de la clé racine présentée pour tout autre cérémonial.

La délégation : une autorisation signée

L'entreprise signe ensuite, avec sa clé d'accès racine, une délégation : une autorisation sans nombre indiquant qu'une clé de signature opérationnelle précise peut sceller des époques en son nom, dans une fenêtre bornée. Le défi WebAuthn est le keccak256 de la délégation canonicalisée, de sorte que le sens de la signature vit entièrement dans le corps signé :

challenge = keccak256( JCS(corps de la délégation) )
{
  "type": "agreely.keyDelegation.v1",
  "companyDid": "did:web:app.agreely.ca:c:acme",
  "delegatee": { "keyId": "0x<sha256 de la clé opérationnelle brute>" },
  "rootKeyId": "0x<keccak de la clé COSE racine : quelle racine a signé>",
  "registry": "0x<adresse minuscule du contrat>",
  "chainId": "8453",
  "rpId": "agreely.ca",
  "nonce": "0x<32 octets aléatoires, usage unique>",
  "validFrom": "2026-07-20T15:00:00Z",
  "validUntil": "2027-07-20T15:00:00Z"
}

Une seule délégation couvre le journal d'accès et le consentement par témoin pour tous les sites de l'entreprise. L'entreprise signe rarement (mise en place, rotation), jamais à chaque époque ; Agreely opère la clé opérationnelle déléguée au jour le jour. La fenêtre [validFrom, validUntil] est bornée (12 mois par défaut). Lorsqu'un descripteur d'époque nomme un registry, la délégation doit nommer le même, sinon l'époque se lit comme non attestée par l'entreprise.

La racine est liée à l'entreprise par DEUX ancres

Une délégation qu'Agreely servirait dans un did.json qu'Agreely épingle ne prouverait rien à elle seule (risque de substitution de racine). La racine est donc liée à l'entreprise par deux ancres indépendantes :

  1. DNS-TXT root= (l'ancre décisive). L'entreprise étend son enregistrement durable _agreely.<domaine> avec un engagement root= égal à l'empreinte keccak de la clé COSE racine :

    Hôte :   _agreely.<domaine-d-entreprise>
    Valeur : agreely-did=did:web:app.agreely.ca:c:<slug>; key=<empreinte sha256 de la clé opérationnelle>; root=<0x + keccak de la clé COSE racine>
    

    La racine est aussi forte que le contrôle de l'entreprise sur sa zone DNS, la source de vérité déclarée du système. Sans cet engagement, la racine routerait uniquement par un did.json qu'un relayeur d'Agreely écrit.

  2. CID did.json sur la chaîne. Le did.json de l'entreprise (qui publie la racine sous #root-1) est épinglé sur IPFS et son CID est enregistré sur la chaîne via registerCompanyDid. Un vérificateur confirme que le did.json qu'il lit hache bien vers le CID sur la chaîne.

La racine n'est liée de façon vérifiable que lorsque les deux ancres la confirment : le #root-1 doit égaler le root= du DNS, et le did.json doit être celui que le CID sur la chaîne engage.

Révocable

L'autorisation est révocable. La révocation est réalisée comme une rotation de la clé opérationnelle sur la chaîne (rotateCompanyDid) : la clé révoquée sort de la fenêtre de clés sur la chaîne, de sorte que les époques qu'elle signe après le bloc de rotation ne se vérifient plus comme autorisées.

Caveat honnête sur la révocation

C'est le relayeur d'Agreely qui soumet la transaction de rotation. Rien ne rend donc la révocation visible indépendamment du relayeur ; ce qui est vrai, c'est que l'absence de la rotation attendue est détectable par l'entreprise, qui contrôle sa propre zone DNS et peut lire la chaîne directement.

Récupération, et la limite honnête de la garde

L'entreprise peut enregistrer plusieurs clés d'accès racines et une clé de récupération Ed25519 optionnelle (autorisée uniquement à faire tourner la racine, jamais à signer un consentement). L'ajout d'une deuxième racine ou d'une clé de récupération doit être signé par une racine active existante (une assertion par clé d'accès), jamais simplement authentifié par session. Une récupération par domaine doit engager la nouvelle empreinte racine dans le root= du DNS.

Agreely ne peut pas restaurer une racine perdue

Comme la racine est du matériel que l'entreprise contrôle, Agreely ne peut sincèrement pas la restaurer si elle est perdue et qu'aucune racine de secours ni clé de récupération n'existe. Enregistrez une deuxième racine ou une clé de récupération à la mise en place. Notez aussi le caveat de niveau ~AAL2 pour une clé d'accès synchronisée (une clé synchronisée par le fournisseur de plateforme n'est pas isolée au niveau matériel comme l'est une clé liée à un appareil).

3. Le modèle de confiance et la vérification hors ligne

La chaîne complète qu'un vérificateur contrôle, du bas vers le haut :

signature d'époque (clé opérationnelle #kms-1)
  -> la délégation autorise ce keyId opérationnel (signée par clé d'accès, dans la
     fenêtre, non révoquée)
  -> la délégation est signée par la clé d'accès RACINE de l'entreprise (assertion
     WebAuthn, #root-1)
  -> la racine est liée à l'entreprise par le `root=` du DNS ET le CID did.json sur
     la chaîne
  -> liaison de domaine (DNS-TXT `_agreely.<domaine>`)

Ce qu'un auditeur peut rejouer

La page de vérification qu'Agreely sert sur l'hôte de l'entreprise (/verify, avec verify-core.js / epoch-chain.js / webauthn.js) rejoue toute la chaîne dans le navigateur à partir d'un paquet de preuve téléchargé, en lisant Base par un point d'accès RPC public et le DNS de l'entreprise par un résolveur DNS sur HTTPS public (Cloudflare par défaut). La page elle-même est servie par Agreely : elle n'est donc pas indépendante d'Agreely ; le paquet, lui, l'est. Chaque étape ci-dessous repose sur des données publiques et des formules publiées, de sorte qu'un auditeur peut la refaire avec ses propres outils. Le SDK TypeScript vérifie les reçus de consentement, pas les chaînes d'époques.

  1. Signature d'époque : recomposer le descripteur, vérifier la signature Ed25519 par rapport au #kms-1 du did.json.
  2. Assertion de délégation : re-dériver le défi depuis le corps (keccak(JCS(corps)), jamais un défi fourni), puis vérifier l'assertion WebAuthn par rapport au #root-1. Contrôles durcis : l'hôte de l'origin est le rpId ou un de ses sous-domaines, authData.rpIdHash = sha256(rpId), drapeau UV posé, type = webauthn.get, et s bas pour ES256.
  3. Couverture : la délégation autorise-t-elle ce keyId opérationnel, dans la fenêtre [validFrom, validUntil] résolue par le point (numéro de bloc, index de journal) de l'ancre, et liée au bon registry / chainId / did ?
  4. Liaison de la racine : le #root-1 égale-t-il le root= lu en direct dans la zone DNS de l'entreprise, et le did.json hache-t-il vers le CID sur la chaîne ?
  5. Fenêtre de clé sur la chaîne : la clé opérationnelle est-elle dans sa fenêtre de clés sur la chaîne au moment de l'ancrage (une clé sortie par rotation résout « non autorisée », c'est la révocation) ?

Le verdict « attesté par l'entreprise » (le vert) exige la chaîne entière : la signature d'époque, la signature de délégation, la couverture, la fenêtre effectivement résolue par rapport au temps d'ancrage, la liaison de la racine, et la clé non sortie par rotation.

Neutralité : le non-délégué reste vert

Une époque sans délégation, ou avec une délégation présente mais invalide, dégrade vers « non attesté par l'entreprise » (attested: false), jamais vers un échec d'intégrité. Les époques non signées et antérieures à la délégation restent vérifiées comme avant. La signature et la délégation sont additives : elles ne conditionnent jamais la vérification.

Ensuite

  • Registre sur la chaîne : AuditRootAnchored, CookieConsentRootAnchored, registerCompanyDid / rotateCompanyDid et le CID did.json sur la chaîne.
  • Documents DID : les méthodes #root-1 (capabilityDelegation) et le bloc delegation dans le did.json de l'entreprise.
  • Frontières honnêtes : pourquoi ceci est de la reddition de comptes, non de la non-répudiation.