Aller au contenu

ProofEnvelope — Schema canonique v2.0.0

Companion document de RFC-PV-PROOF-001 v1.2.0 Definit le format JSON canonique auto-verifiable d'une preuve composite ancree.

1. Principes

Principe Regle
Auto-verifiable La preuve contient TOUT le materiel necessaire a sa verification offline
Canonique Serialisation JCS (RFC 8785) deterministe avant signature
Versionne Champ schemaVersion obligatoire, migration explicite entre versions
Typé Chaque valeur a un type strict (string, number, base64url) — pas de any
Fail-closed L'absence d'un champ obligatoire invalide la preuve entiere

2. Diagramme des 5 artefacts

┌─────────────────────────────────────────────────────────────────────┐
│                    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                        │   │
│  └─────────────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────────────┘

Chaine de verification (4 maillons)

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

3. Schema JSON canonique

{
  // --- 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
  }
}

4. Regles de versioning (invariant produit)

Regle Description
schemaVersion OBLIGATOIRE Toute preuve sans schemaVersion est invalide
Semver strict Format MAJOR.MINOR.PATCH
MAJOR bump Changement cassant (suppression/renommage de champ, changement de type)
MINOR bump Ajout de champ optionnel, nouveau proofType
PATCH bump Correction de description, clarification sans impact structurel
Forward-compatible Un verificateur v2.1.0 DOIT accepter une preuve v2.0.0
Backward-incompatible Un verificateur v2.x NE DOIT PAS accepter une preuve v1.x sans migration

Migration v1 → v2

Champ v1 (actuel) Champ v2 (canonique) Action
mandateEvidenceRef (JSONB refs) legalContext (donnees completes) Resoudre les refs, embarquer
auditLogEvidenceRef.events[].merkleLeafRef (string ref) probativeEvents[].merkleProof (objet complet) Resoudre leaf + inclusion proof
auditLogEvidenceRef.events[].tsaTokenRef (string ref) probativeEvents[].tsaToken (objet complet avec DER) Resoudre token, embarquer DER
anchoringEvidenceRef.anchors[].anchoringBatchRef (string ref) blockchainAnchors[] (objet complet avec txId) Resoudre batch, embarquer
verificationMaterial (legacy) verificationMaterial (enhanced) + envelopeSeal Separer seal du material
absent chainLinkResults + aggregateStatus Calculer lors de la generation
absent schemaVersion Ajouter "2.0.0"

5. Encodages (invariants)

Type de donnee Encodage Exemple
UUID v4 lowercase avec tirets "3fa85f64-5717-4562-b3fc-2c963f66afa6"
Hash SHA3-256 hex lowercase 64 chars "a7ffc6f8bf1ed7...64chars"
Hash SHA-256 hex lowercase 64 chars "e3b0c44298fc1c...64chars"
Timestamp RFC 3339 UTC ms + Z "2026-03-04T14:30:00.000Z"
Certificat DER base64 standard (avec padding) "MIIBkTCB+wIJAL..."
Signature HSM base64url sans padding (RFC 4648 §5) "MEYCIQDr2Z8..."
Adresse Ethereum hex lowercase 42 chars (0x prefix) "0x742d35cc..."
Transaction hash hex lowercase 64 chars "0xabc123..."
Nombres JSON number (pas de string) 137, 42, 1709561400000

6. Procedure de scellement

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

ATTENTION : crypto.verify(null, digest, publicKey, signature) pour la verification. Ne JAMAIS utiliser createVerify('SHA3-384').update(digest) (double-hash, cf. REX PD-282).

7. Procedure de verification offline (Mode A)

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

8. Gaps avec l'implementation actuelle (v1)

Gap Severite Story cible
Events contiennent des refs string au lieu des artefacts complets MAJEUR A creer
chainLinkResults et aggregateStatus absents MAJEUR A creer
schemaVersion est proofVersion (pas de convention schema) MINEUR A creer
Hash SHA3-384 dans le seal vs SHA3-256 dans la spec RFC MINEUR (aligner spec sur impl) PD-282 done
payloadJcs non stocke dans l'evidence ref MAJEUR (requis pour verif offline) A creer
TSA token DER non embarque (seulement ref) MAJEUR A creer
Merkle inclusion proof non embarque (seulement ref) MAJEUR A creer