Le reçu de consentement

Le reçu est l'artéfact présenté au citoyen et vérifiable de façon indépendante. C'est un justificatif vérifiable (Verifiable Credential) du W3C qui porte les éléments consentis et les deux preuves : le companySig réutilisé de l'entreprise et l'assertion WebAuthn du citoyen sur le défi keccak d'Agreely.

Il ne porte délibérément jamais le sel, le consentRef ni l'engagement. Ceux-ci restent dans l'enveloppe destructible afin que le reçu ne défasse pas la non-corrélation et ne bloque pas l'effacement. Comme l'offre, il est sans nombres : chaque valeur est une chaîne.

{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://agreely.ca/credentials/consent/v1"
  ],
  "type": ["VerifiableCredential", "ConsentReceipt"],
  "id": "urn:agreely:receipt:6f9619ff-8b86-d011-b42d-00cf4fc964ff",
  "issuer": "did:web:agreely.ca:c:acme",
  "validFrom": "2026-06-26T15:04:05Z",
  "validUntil": "2031-01-01T00:00:00Z",
  "credentialSubject": {
    "id": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B",
    "consent": {
      "action": "grant",
      "assuranceLevel": "citizen_signed",
      "items": [
        {
          "itemId": "0x9a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081",
          "category": "phone number",
          "purpose": "billing"
        }
      ],
      "requestId": "0x4b08245219c0e1a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081234",
      "grantedAt": "2026-06-26T15:04:05Z",
      "document": {
        "code": "acme-billing-v3",
        "name": "Consentement facturation v3",
        "version": "3",
        "effectiveDate": "2026-01-01",
        "ipfsCid": "bafybeib..."
      },
      "disclosureHash": "0x…sha256…"
    }
  },
  "proof": [
    {
      "type": "DataIntegrityProof",
      "cryptosuite": "eddsa-jcs-2022",
      "created": "2026-06-26T15:00:00Z",
      "verificationMethod": "did:web:agreely.ca:c:acme#kms-1",
      "proofPurpose": "assertionMethod",
      "proofValue": "z3Ge8wQ5...base58btc..."
    },
    {
      "type": "WebAuthnAssertion",
      "verificationMethod": "did:agreely:citizen:9F8K2M4P7Q1R3T5V8W0X2Y4Z6B#passkey-1",
      "nonce": "0x7081234b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f",
      "challenge": "0xc0ffee...keccak256...",
      "authenticatorData": "SZYN5YgOjGh0NBcP...base64url...",
      "clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0Iiw...base64url...",
      "signature": "MEUCIQD...base64url..."
    }
  ]
}

Champ par champ

Champ Signification
@context Le contexte VC v2 plus le contexte du justificatif de consentement d'Agreely.
type ["VerifiableCredential", "ConsentReceipt"].
id urn:agreely:receipt:{uuid}.
issuer Le DID de l'entreprise (repris de l'issuer de l'offre).
validFrom grantedAt, le moment où le citoyen a approuvé.
validUntil Le validUntil de l'offre, la durée de vie du consentement.
credentialSubject.id Le DID du citoyen, did:agreely:citizen:....
credentialSubject.consent.action grant.
credentialSubject.consent.assuranceLevel Le niveau d'assurance public : citizen_signed pour ce reçu, company_attested pour la variante attestée par l'entreprise (voir plus bas).
credentialSubject.consent.items Les éléments consentis (itemId, category normalisée, purpose normalisée).
credentialSubject.consent.requestId Le requestId de l'offre d'origine.
credentialSubject.consent.grantedAt Quand le consentement a été donné (RFC 3339).
credentialSubject.consent.document Le descripteur de document : code, name, version, effectiveDate et, lorsque la version de la divulgation est épinglée sur IPFS, l'ipfsCid de la copie immuable sous laquelle le consentement a été donné (voir intégrité du document sur IPFS). L'ipfsCid est omis quand la version n'est pas épinglée.
credentialSubject.consent.disclosureHash 0x + le SHA-256 de la carte de divulgation canonicalisée.

Deux émetteurs, ne les confondez pas

L'issuer est le DID de l'entreprise (did:web:agreely.ca:c:{slug}); le credentialSubject.id est le DID du citoyen (did:agreely:citizen:...). Ils utilisent des méthodes DID différentes à dessein. Voir documents DID.

La liaison de divulgation

consent.document lie le reçu à la version exacte de la divulgation sous laquelle le consentement a été donné, par son code, sa version et, lorsque la version est épinglée, son ipfsCid sur IPFS. consent.disclosureHash est 0x + le SHA-256 de la carte de divulgation canonicalisée (JCS).

disclosureHash est du SHA-256, pas du keccak256

Soyez précis sur l'algorithme : disclosureHash est du SHA-256, tandis que les engagements par cellule, l'arbre de Merkle et le défi de la clé d'accès sont du keccak256. Ne mélangez pas les deux quand vous recalculez.

La divulgation de l'article 8 couvre les fins, les moyens de collecte, les catégories, la conservation, le retrait du consentement, les destinataires, la communication hors Québec, les droits, le responsable et toute décision automatisée. Sa copie publique et récupérable à l'ipfsCid est décrite sous intégrité du document sur IPFS.

Le tableau des preuves

proof est un tableau d'exactement deux preuves, et l'ordre est fixe.

proof[0] : la moitié entreprise

La preuve Data Integrity eddsa-jcs-2022, réutilisée mot pour mot depuis le companySig de l'offre. Son verificationMethod se résout dans le document DID de l'entreprise; elle signe l'offre d'origine (voir la demande de consentement).

proof[1] : la moitié citoyen

Une WebAuthnAssertion sur le défi d'Agreely.

Champ Signification
type WebAuthnAssertion.
verificationMethod Le fragment de clé d'accès du DID citoyen, p. ex. ...#passkey-1.
nonce Le nonce à usage unique de l'offre.
challenge 0x-hex `keccak256( JCS(context)
authenticatorData Données brutes de l'authentificateur WebAuthn, base64url.
clientDataJSON Données client WebAuthn brutes, base64url. Son challenge intégré égale le champ challenge ci-dessus.
signature La signature de l'authentificateur, base64url.

Effacer une seule cellule invalide la signature partagée

Comme l'entreprise a signé sur l'ensemble complet des éléments, effacer un élément d'un reçu à plusieurs éléments invalide companySig sur l'ensemble survivant : il n'y a pas de preuve à divulgation sélective dans la v1. Les survivants restent vérifiables de façon indépendante via leur commitment + la racine de Merkle + l'ancrage sur la chaîne, et non via le reçu caviardé. La divulgation sélective SD-JWT / BBS+ est le correctif différé.

La variante attestée par l'entreprise

Tous les consentements ne passent pas par une clé d'accès. Lorsqu'une personne a signé un formulaire papier à la main, l'entreprise émet un reçu attesté par l'entreprise (le flux manuel ou hors ligne). Il est bâti comme un reçu citoyen, avec trois différences :

  • son type porte en plus "CompanyAttestedConsentReceipt";
  • consent.assuranceLevel vaut "company_attested" et n'est jamais omis;
  • son credentialSubject n'a aucun id citoyen (il n'y a pas de DID citoyen à lier), et il porte un bloc evidence;
  • son proof est un tableau d'un seul élément : la moitié entreprise seulement, sans assertion WebAuthn.
{
  "@context": [
    "https://www.w3.org/ns/credentials/v2",
    "https://agreely.ca/credentials/consent/v1"
  ],
  "type": ["VerifiableCredential", "ConsentReceipt", "CompanyAttestedConsentReceipt"],
  "id": "urn:agreely:receipt:6f9619ff-8b86-d011-b42d-00cf4fc964ff",
  "issuer": "did:web:agreely.ca:c:acme",
  "validFrom": "2026-06-26T15:04:05Z",
  "validUntil": "2031-01-01T00:00:00Z",
  "credentialSubject": {
    "consent": {
      "action": "grant",
      "assuranceLevel": "company_attested",
      "items": [
        {
          "itemId": "0x9a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081",
          "category": "phone number",
          "purpose": "billing"
        }
      ],
      "requestId": "0x4b08245219c0e1a2b3c4d5e6f70819a2b3c4d5e6f70819a2b3c4d5e6f7081234",
      "grantedAt": "2026-06-26T15:04:05Z",
      "document": {
        "code": "acme-billing-v3",
        "name": "Consentement facturation v3",
        "version": "3",
        "effectiveDate": "2026-01-01",
        "ipfsCid": "bafybeib..."
      },
      "disclosureHash": "0x…sha256…"
    },
    "evidence": {
      "type": "HandSignedPaperConsent",
      "pdfHash": "0x…sha256…",
      "pdfHeldBy": "company",
      "documentVersion": "3"
    }
  },
  "proof": [
    {
      "type": "DataIntegrityProof",
      "cryptosuite": "eddsa-jcs-2022",
      "created": "2026-06-26T15:00:00Z",
      "verificationMethod": "did:web:agreely.ca:c:acme#kms-1",
      "proofPurpose": "assertionMethod",
      "proofValue": "z3Ge8wQ5...base58btc..."
    }
  ]
}
Champ Signification
evidence.type HandSignedPaperConsent.
evidence.pdfHash 0x + le SHA-256 du PDF signé à la main.
evidence.pdfHeldBy "company" ou "agreely-escrow", qui détient le PDF.
evidence.documentVersion La version du document sous laquelle le formulaire a été signé.

Ce qu'un reçu attesté prouve, et ce qu'il ne prouve pas

Un reçu attesté par l'entreprise prouve que l'entreprise a attesté détenir un PDF signé à la main dont le hachage correspond à evidence.pdfHash. Il ne prouve pas qu'un humain a signé : il n'y a aucune assertion de clé d'accès, seulement la signature de l'entreprise. C'est une attestation d'entreprise, pas une preuve cryptographique de la présence du citoyen.

Deux vocabulaires d'assurance

Il y a deux vocabulaires, et ils ne se mélangent pas :

  • Le palier de stockage interne est full | manual.
  • L'étiquette publique du reçu et de /v1/check est citizen_signed | company_attested.

manual correspond à company_attested; full correspond à citizen_signed. Faites la correspondance, ne mélangez jamais les deux vocabulaires.

Ensuite