Signatures et clés d'accès

Un reçu porte deux signatures : celle de l'entreprise, attestant l'offre qu'elle a faite, et celle du citoyen, attestant la réponse qu'il a donnée. Elles utilisent des suites différentes parce que les deux parties sont des signataires de nature différente.

La signature d'entreprise : eddsa-jcs-2022

L'entreprise signe l'offre avec eddsa-jcs-2022 : une signature détachée Ed25519 sur les octets JCS-canoniques de l'offre, publiée comme une preuve W3C Data Integrity.

companySig = Ed25519-sign( JCS(offer), companySecretKey )

La signature est exposée comme un DataIntegrityProof :

{
  "type": "DataIntegrityProof",
  "cryptosuite": "eddsa-jcs-2022",
  "created": "2026-06-26T15:00:00Z",
  "verificationMethod": "did:web:agreely.ca:c:acme#kms-1",
  "proofPurpose": "assertionMethod",
  "proofValue": "z3Ge8...base58btc..."
}

proofValue est la signature de 64 octets sous forme de chaîne multibase base58btc préfixée par z.

Le DID de l'entreprise : did:web, hébergé par Agreely

Une entreprise signe en tant que did:web:agreely.ca:c:{slug}. Agreely héberge le document à https://agreely.ca/c/{slug}/did.json. Le document est lié au domaine réel et vérifié de l'entreprise via alsoKnownAs, de sorte qu'un vérificateur voit à la fois l'identifiant à faible friction hébergé par Agreely et le domaine prouvé. Une entreprise sans domaine vérifié n'a pas de document résoluble, ce qui est l'exigence de liaison. did:web est le bon choix ici précisément parce qu'une entreprise est un domaine; la mise à niveau ultérieure remplace simplement l'hôte par le propre domaine de l'entreprise.

La clé est publiée sous forme de Multikey publicKeyMultibase, de sorte qu'un vérificateur la résout depuis le document DID et contrôle companySig sans aucune confiance en Agreely. Voir documents DID.

La signature du citoyen : les clés d'accès WebAuthn

Les citoyens ne détiennent jamais de fichier de clé privée. Ils signent avec une clé d'accès WebAuthn : l'authentificateur de plateforme (Face ID, Touch ID, une clé de sécurité) détient le secret, et le reçu porte l'assertion qui en résulte. La bibliothèque garantit, gratuitement, que le défi de l'assertion égale celui qu'Agreely a émis et que la signature se vérifie par rapport à la clé publique COSE stockée.

Il y a deux défis différents, et la différence compte.

Intégration : un défi aléatoire

L'inscription et la connexion utilisent un défi aléatoire de 32 octets, le flux WebAuthn standard. Cela prouve que la personne contrôle l'authentificateur. L'inscription exige la vérification de l'utilisateur et une clé résidente (découvrable), et accepte les justificatifs ES256, EdDSA ou RS256.

Approbation : un défi keccak lié au consentement

Lorsque la personne approuve un consentement, le défi n'est pas aléatoire. C'est un hachage keccak256 qui lie la signature exactement à cette offre et à sa racine de Merkle :

challenge = keccak256( JCS(context) || nonce32 )

où le contexte de liaison est figé ainsi :

{
  "issuer": "did:web:agreely.ca:c:acme",
  "subject": "did:agreely:citizen:9F8K...",
  "requestId": "0x...",
  "action": "grant",
  "grantedAt": "2026-06-26T15:04:05Z",
  "merkleRoot": "0x..."
}

L'assertion qui atterrit dans le reçu est une preuve WebAuthnAssertion qui porte le verificationMethod (le fragment #passkey-n du DID), le challenge, ainsi que les authenticatorData, clientDataJSON et signature bruts. Un vérificateur résout la clé COSE qui était valide à grantedAt et contrôle l'assertion par rapport à elle.

Ensuite