┌─────────────────────────────────────────────────────────────────────┐
│ ProofEnvelope v2.0.0 │
│ schemaVersion: "2.0.0" proofId: uuid generatedAt: RFC3339Z │
├─────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────────────┐ │
│ │ 1. EVENTS │ │ 2. MERKLE │ │ 3. TSA RFC 3161 │ │
│ │ NDJSON │───▶│ PROOF │───▶│ TIMESTAMP │ │
│ │ │ │ │ │ │ │
│ │ eventHash │ │ leafHash │ │ tstDer (base64) │ │
│ │ (SHA3-256) │ │ leafIndex │ │ genTime │ │
│ │ payloadJcs │ │ inclusion[] │ │ serialNumber │ │
│ │ │ │ merkleRoot │ │ policyOid │ │
│ └──────────────┘ └──────┬───────┘ └──────────────────────┘ │
│ │ │
│ ▼ │
│ ┌──────────────────────────────────────┐ ┌──────────────────┐ │
│ │ 4. HSM SIGNATURE │ │ 5. BLOCKCHAIN │ │
│ │ (sceau global ECDSA P-384) │ │ ANCHOR │ │
│ │ │ │ │ │
│ │ algorithm: ECDSA-P384-SHA3-384 │ │ txId │ │
│ │ signature (base64url) │ │ blockNumber │ │
│ │ kid (label HSM) │ │ chainId (EIP155)│ │
│ │ certificateChain[] (base64-DER) │ │ merkleRoot │ │
│ │ signedAt (RFC3339Z) │ │ signerAddress │ │
│ └──────────────────────────────────────┘ └──────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────────┐ │
│ │ VERIFICATION MATERIAL (autonome, embarque) │ │
│ │ tsaCertificateChain[], eidasCertificateChain[] │ │
│ │ ocspResponses[], validationTimestamp │ │
│ └─────────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────────┘
Event payload ──SHA3-256──▶ eventHash
│
Merkle inclusion proof
│
▼
merkleRoot ◀── Merkle tree window
│
TSA timestamp (RFC 3161)
│
▼
blockchain tx_id ── Polygon finalized
│
HSM envelope seal (couvre tout)
│
▼
PREUVE VALIDE
{
// --- Metadata ---
"schemaVersion": "2.0.0", // semver, OBLIGATOIRE, invariant produit
"proofId": "uuid-v4", // identifiant unique de la preuve
"generatedAt": "2026-03-04T..Z", // RFC 3339 UTC millisecondes, suffixe Z
"proofType": "COMPOSITE_ANCHORED", // enum: COMPOSITE_ANCHORED | COMPOSITE_PARTIAL
// --- Section 1: Contexte legal ---
"legalContext": {
"mandateId": "uuid-v4",
"issuerIdentity": "string", // identite du demandeur
"issuerRole": "LEGAL_OFFICER | DPO | ADMIN",
"validFrom": "RFC3339Z",
"validUntil": "RFC3339Z",
"scopeDocumentIds": ["uuid-v4"], // documents couverts
"mandateHashSha3": "hex-64" // SHA3-256 du mandat original
},
// --- Section 2: Double validation ---
"dualValidation": {
"cases": [
{
"caseId": "uuid-v4",
"validator1": { "role": "string", "validatedAt": "RFC3339Z" },
"validator2": { "role": "string", "validatedAt": "RFC3339Z" },
"state": "VALIDATED | REJECTED"
}
]
},
// --- Section 3: Events probatoires (ARTEFACT 1 — NDJSON) ---
"probativeEvents": [
{
"eventId": "uuid-v4",
"eventType": "string", // ACCESS_GRANTED, DOWNLOAD, etc.
"actorIdentity": "string",
"actorRole": "string",
"eventAt": "RFC3339Z",
"payloadHashSha3": "hex-64", // SHA3-256 du payload JCS
"payloadJcs": "string", // payload canonicalise RFC 8785
// ARTEFACT 2 — Merkle proof (embarque, pas une ref)
"merkleProof": {
"leafHash": "hex-64", // SHA-256 de la feuille
"leafIndex": 0, // number, >= 0
"inclusionPath": ["hex-64"], // chemin d'inclusion (hashes)
"treeId": "uuid-v4",
"merkleRoot": "hex-64" // racine de l'arbre
},
// ARTEFACT 3 — TSA token (embarque, pas une ref)
"tsaToken": {
"tstDer": "base64", // token DER complet RFC 3161
"genTime": "RFC3339Z",
"serialNumber": "hex",
"policyOid": "string", // ex: "1.3.6.1.4.1...."
"hashAlgorithm": "SHA-256",
"hashedMessage": "hex-64"
}
}
],
// --- Section 4: Ancrage blockchain (ARTEFACT 5) ---
"blockchainAnchors": [
{
"batchId": "uuid-v4",
"merkleRoot": "hex-64", // doit matcher la racine Merkle ci-dessus
"txId": "hex-64", // hash de la transaction Polygon
"blockNumber": 0, // number, >= 0
"chainId": 137, // EIP-155 (137 = Polygon mainnet)
"signerAddress": "hex-42", // adresse Ethereum 0x...
"finalizedAt": "RFC3339Z",
"eventIds": ["uuid-v4"] // events couverts par ce batch
}
],
// --- Section 5: ReKey lifecycle ---
"rekeyLifecycle": [
{
"rekeyId": "uuid-v4",
"status": "ISSUED | EXPIRED | REVOKED",
"issuedAt": "RFC3339Z",
"expiresAt": "RFC3339Z",
"ttlSeconds": 0, // number
"scopeDocumentIds": ["uuid-v4"]
}
],
// --- Materiel de verification autonome ---
"verificationMaterial": {
"tsaCertificateChain": ["base64-DER"], // 1..10 certs
"eidasCertificateChain": ["base64-DER"], // 1..10 certs
"ocspResponses": [
{
"certSerialNumber": "hex", // uppercase
"response": "base64-DER",
"producedAt": "RFC3339Z",
"status": "good | revoked | unknown"
}
],
"validationTimestamp": "RFC3339Z",
"validationPolicy": "GENERATION_TIME_SNAPSHOT | OCSP_UNAVAILABLE"
},
// --- Statut de validation (4 maillons) ---
"chainLinkResults": {
"eventIntegrity": "OK | KO | INDETERMINATE",
"merkleInclusion": "OK | KO | INDETERMINATE",
"timestampValidity": "OK | KO | INDETERMINATE",
"blockchainAnchor": "OK | KO | INDETERMINATE"
},
"aggregateStatus": "VALID | PARTIAL | INVALID",
// --- ARTEFACT 4: Sceau HSM global (DOIT etre exclu avant JCS+sign) ---
"envelopeSeal": {
"algorithm": "ECDSA-P384-SHA3-384",
"signature": "base64url-no-pad", // 80..200 chars
"kid": "string", // label cle HSM, 3..128 ASCII
"signedAt": "RFC3339Z",
"certificateChain": ["base64-DER"] // 1..10 certs
}
}
1. Construire l'objet ProofEnvelope SANS le champ `envelopeSeal`
2. Serialiser en JCS (RFC 8785) → bytes deterministes
3. Hash SHA3-384 des bytes JCS → digest 48 bytes
4. Signer le digest via HSM (CKM_ECDSA raw, P-384) → signature raw
5. Encoder la signature en base64url sans padding
6. Assembler le champ `envelopeSeal` avec signature, kid, certificateChain, signedAt
7. Ajouter `envelopeSeal` a l'objet final
Pour chaque event dans probativeEvents:
1. Recalculer SHA3-256(payloadJcs) → comparer avec payloadHashSha3 [maillon 1]
2. Verifier merkleProof.inclusionPath mene a merkleProof.merkleRoot [maillon 2]
3. Verifier le TSA token DER (signature, certificat, genTime) [maillon 3]
4. Trouver le blockchainAnchor dont merkleRoot == merkleProof.merkleRoot [maillon 4]
5. Verifier txId on-chain (optionnel en mode offline)
Verification du sceau global:
6. Retirer `envelopeSeal` de l'objet
7. JCS → SHA3-384 → crypto.verify(null, digest, publicKey, signature)
8. Verifier la certificateChain contre le trust store eIDAS
Resultat:
- 4 maillons OK + seal OK → VALID
- >= 1 maillon INDETERMINATE → PARTIAL
- >= 1 maillon KO ou seal KO → INVALID