Vérifier un reçu
La vérifiabilité indépendante ne compte que si vous pouvez réellement le faire. Le
moyen le plus court est Agreely.verifyReceipt : une méthode statique, sans
aucune clé d'API, qui rend un verdict sur toute entrée, jamais une
exception. La CLI expose exactement les mêmes statuts par
agreely verify.
Vérifier avec le SDK ou la CLI
Passez le dossier de vérification qu'émet un consentement par clé d'accès (le reçu, l'offre signée et la racine de Merkle, dans un seul fichier JSON), ou un reçu sur papier ou par téléphone. Les contrôles sur la chaîne (les ancrages et le retrait) sont facultatifs et n'ont lieu que si vous fournissez une URL RPC.
import { readFile } from "node:fs/promises";
import { Agreely } from "@agreely/sdk";
const file = JSON.parse(await readFile("./agreely-verification-bundle.json", "utf8"));
const v = await Agreely.verifyReceipt(file, {
rpcUrl: "https://base-rpc.publicnode.com", // optional: the on-chain anchors and the withdrawal
});
v.receiptType; // "company_attested" | "company_documented" | "citizen"
if (v.overall === "verified") {
// the checks listed held; v.withdrawal reads "active" once the chain reads the consent in force
} else if (v.overall === "withdrawn") {
// authentic, and the chain records its withdrawal: no longer in force, NOT a forgery
} else if (v.overall === "partial") {
// a telephone receipt, the organisation's word. A
// partial does not on its own show consent: verify its bundle at app.agreely.ca/verify
} else if (v.overall === "incomplete") {
// a bare passkey receipt: verify the verification bundle issued with it
} else if (v.overall === "unavailable") {
// inconclusive (a DID, a gateway or an RPC did not answer), NOT a forgery: retry later
} else {
// "failed" or "malformed": read which check failed, and the notes, before calling it a tamper
}use Agreely\Sdk\Agreely;
$v = Agreely::verifyReceipt(file_get_contents('agreely-verification-bundle.json'), [
'rpcUrl' => 'https://base-rpc.publicnode.com', // optional: the on-chain anchors and the withdrawal
]);
$v->receiptType; // "company_attested" | "company_documented" | "citizen"
$v->overall; // "verified" | "partial" | "incomplete" | "failed" | "unavailable" | "malformed" | "withdrawn"
$v->withdrawal; // "active" | "withdrawn" | "not_checked"# zero-install (no global install needed):
npx @agreely/cli verify ./agreely-verification-bundle.json
# or, if the CLI is installed globally:
agreely verify ./agreely-verification-bundle.json --json
# human mode prints the check matrix, the withdrawal line, an overall verdict and notes
# exit 0 = verified (or partial on a telephone receipt), 4 = a check could not complete,
# 6 = failed or malformed, 11 = a bare passkey receipt, 12 = withdrawn (with --onchain)
# air-gapped: supply the DID documents locally, taken from the issuer and the citizen
# tier, never out of the file under test:
agreely verify ./agreely-verification-bundle.json --did-doc ./issuer-did.json --did-doc ./citizen-did.json
# opt-in cross-checks: the IPFS copy, the on-chain anchors and the withdrawal:
agreely verify ./agreely-verification-bundle.json --ipfs --onchain --rpc-url https://base-rpc.publicnode.comLe résultat est un ReceiptVerification : un input (bundle ou receipt), un
receiptType, un statut par contrôle, un overall, un withdrawal (active,
withdrawn ou not_checked), et des notes en langage clair, écrites pour les
humains : un programme lit les champs.
Les contrôles
companySignature: la signature Ed25519 de l'entreprise, sur le corps d'un reçu sur papier ou par téléphone, ou sur l'offre signée d'un dossier par clé d'accès.receiptIntegrity: sur un dossier, le reçu correspond à l'offre signée champ par champ, et sa date d'acceptation (grantedAt) tombe dans la fenêtre de l'offre.citizenAssertion: l'assertion WebAuthn de la clé d'accès de la personne. Sur un dossier, son défi se recalcule à partir de l'offre et de la racine de Merkle, et le document DID du citoyen confirme la clé àgrantedAt. Sur un reçu citoyen seul, l'assertion se vérifie sur le défi que porte le reçu, sans relier ce défi au contenu du reçu : elle rendunbound, et à elle seule ne montre pas que cette personne a consenti à ces finalités. Un reçu citoyen se vérifie avec son dossier de vérification.disclosureCopy: recoupement facultatif qui récupère la copie de divulgation sur IPFS et la compare audisclosureHashsigné (optionverifyDisclosure: truedans le SDK,--ipfsà la CLI); autrementskipped.documentAnchor: recoupement facultatif sur la chaîne qui confirme que le document a été ancré. Il exige une URL RPC (rpcUrldans le SDK,--onchainavec--rpc-urlouAGREELY_RPC_URLà la CLI); autrementskipped.consentAnchor: recoupement facultatif sur la chaîne, sur un dossier par clé d'accès seulement. Il lit l'enregistrement de chaque consentement sous la racine du dossier, et c'est lui qui fondewithdrawal.
La matrice affiche aussi cellLabelBinding : un résumé dérivé indiquant si les
étiquettes lisibles (catégorie / finalité) sont liées cryptographiquement.
C'est le champ à lire avant de faire confiance à une étiquette affichée. Sur un
reçu sur papier ou par téléphone, elle suit companySignature (les étiquettes sont
dans le corps signé, donc les modifier casse la vérification); sur un dossier, elle
passe quand l'offre signée et l'intégrité du reçu passent; sur un reçu citoyen
seul, elle est unsupported.
Essayez-le
Un régulateur à qui l'on dit « c'est vérifiable de façon indépendante » a besoin de quelque chose à essayer. Téléchargez un vrai reçu attesté par l'entreprise et le document DID de son émetteur, tirés des vecteurs de référence du SDK, puis vérifiez le reçu hors ligne (aucun réseau, aucune clé d'API) :
- sample-receipt.json : le reçu attesté par l'entreprise
- sample-did.json : le document DID de l'émetteur
agreely verify sample-receipt.json --did-doc sample-did.json
✓ VERIFIED (company_attested, read as a receipt)
companySignature pass
receiptIntegrity pass
citizenAssertion unsupported
disclosureCopy skipped
documentAnchor skipped
consentAnchor skipped
cellLabelBinding pass
withdrawal not checked
! WITHDRAWAL NOT CHECKED: this verify did not read the chain, so it says nothing of a withdrawal since the consent was given. For a passkey consent's verification bundle, add --onchain --rpc-url https://base-rpc.publicnode.com to read it.
· Company signature verified against issuer DID did:web:api.agreely.ca:c:acme (key did:web:api.agreely.ca:c:acme#kms-1).
· This proves the company ATTESTED to a hand-signed PDF (hash 0xabababababababababababababababababababababababababababababababab); it does NOT prove a human signed.
· A company-attested receipt carries no citizen passkey assertion, so citizenAssertion is not applicable.
· Cell labels bound: the category/purpose labels sit inside the company-signed body, so the verified signature covers them. Mutating any item label breaks it.
· disclosureCopy skipped: the receipt carries no document.ipfsCid + disclosureHash pair to fetch and compare.
· documentAnchor skipped: pass an rpcUrl to check the on-chain document anchor.
· consentAnchor skipped: this verifier reads a consent's on-chain record from a passkey bundle only, so the withdrawal of a paper or telephone consent is not checked.
La sortie commence par VERIFIED avec un code de sortie 0. C'est un reçu
attesté par l'entreprise, donc la vérification est propre et non partial. La
ligne WITHDRAWAL NOT CHECKED rappelle que cette vérification ne dit rien d'un
retrait : ce vérificateur lit le retrait sur la chaîne à partir du dossier d'un
consentement par clé d'accès.
La matrice d'honnêteté
Le meilleur verdict possible dépend du type de reçu, et c'est le point d'honnêteté porteur de cette page.
| Type de reçu | Meilleur verdict | Pourquoi |
|---|---|---|
company_attested |
verified |
la signature de l'entreprise porte sur le corps du reçu et se vérifie hors ligne |
company_documented |
partial |
un consentement verbal : la signature de l'entreprise se vérifie, mais la parole de l'organisation est tout ce qu'il y a |
citizen, dans son dossier |
verified |
la signature de l'offre, l'intégrité du reçu et l'assertion liée, avec une clé que le DID du citoyen confirme |
citizen, reçu seul |
incomplete |
l'assertion n'est reliée à aucun contenu (unbound, voir plus bas) |
Un reçu attesté par l'entreprise (typiquement un PDF signé à la main) atteint
verified hors ligne : sa signature d'entreprise porte sur le corps du reçu. Un
consentement par clé d'accès atteint verified à partir de son dossier de
vérification. Sur un reçu citoyen seul, l'assertion par clé d'accès du citoyen se
vérifie sur le défi que porte le reçu, sans le relier à son contenu :
citizenAssertion rend unbound et overall rend incomplete. Un verified
veut dire que les contrôles énumérés tiennent; ce n'est pas une preuve juridique.
Pourquoi un reçu téléphonique s'arrête à partial, et un reçu citoyen seul à incomplete
Un reçu téléphonique porte la signature de l'entreprise, mais aucune signature de la personne : il documente ce que l'organisation dit avoir recueilli. Un partial ne montre donc pas à lui seul que cette personne a consenti : c'est la parole de l'organisation, authentifiée. Le vérificateur public à app.agreely.ca/verify lit en plus, avec son dossier de vérification, si un reçu de version 4 est encore en vigueur. Sur un reçu citoyen, l'entreprise a signé l'offre d'origine, qui porte la divulgation complète; le reçu ne la reproduit pas, pour préserver la non-liaison. Seul le dossier de vérification porte l'offre signée, le requestId et la racine de Merkle qui relient l'assertion au reçu : passez-le au SDK, à la CLI ou au vérificateur public.
Échec contre indisponible
verifyReceipt sépare une vérification qui a échoué (failed : la
cryptographie ou la chaîne contredit activement le fichier) d'une vérification qui
n'a pas pu aboutir (unavailable : un DID, une passerelle IPFS ou un RPC n'a
pas répondu, ou le dossier nomme un registre que le vérificateur ne lit pas). Un
fail actif l'emporte toujours sur un unavailable : un vrai négatif n'est
jamais masqué par une indisponibilité, et un reçu valide durant une panne ne
ressemble jamais à une contrefaçon. À la CLI, c'est sortie 6 (échec) contre
sortie 4 (indisponible).
Un recoupement demandé qui n'a pas pu aboutir (disclosureCopy, documentAnchor
ou consentAnchor à unavailable) laisse le verdict global à verified dans le
SDK; la CLI sort alors en 4. Lisez chaque contrôle et notes avant de vous fier
au verdict.
withdrawn n'est ni un échec ni une indisponibilité : le reçu est authentique, et
la chaîne consigne le retrait du consentement, qui n'est plus en vigueur (sortie
12 à la CLI). Sans URL RPC, rien ne lit le retrait : withdrawal rend
not_checked.
Les clés viennent des documents DID que lit le vérificateur. Un résolveur ou un
--did-doc qui sert un document citoyen sans authentication ni validFrom, ou un
document d'émetteur qui ne liste pas la clé sous assertionMethod, fait rendre
failed à un reçu authentique. Un failed seul n'est donc pas une preuve de
falsification : lisez le contrôle qui a échoué et notes, vérifiez les documents
DID fournis, ou vérifiez le reçu à app.agreely.ca/verify
avec son dossier de vérification.
Ensuite
- Vérifier un ConsentReceipt : la matrice complète « ce qui est prouvé contre ce qui est présumé » et toutes les options.
- La recette de vérification au niveau des champs, pour reconstruire les contrôles sans le SDK.