Tous les outils de mesure carbone IA disent à peu près la même chose : « vos émissions valent X tonnes CO₂e ». Le commissaire aux comptes pose alors la question qu'aucun marketing ne prévient : « comment je vérifie que ce X n'a pas été retouché entre la mesure et l'export ? »On a passé 3 semaines à répondre à ça avec une preuve cryptographique vérifiable hors ligne — voici comment on l'a construit, et pourquoi ça change notre façon de vendre la conformité CSRD.
Suivre cela en production →
Envoyez les volumes de tokens vers notre API — mêmes coefficients que cet article. Offre gratuite, sans carte bancaire.
Le problème : faire confiance sans avoir à faire confiance
Voici ce qu'un auditeur dit en réunion, mot pour mot : « je veux bien que carbon-llm m'envoie un PDF avec un chiffre. Mais comment je sais que ce chiffre n'a pas été modifié dans la base après coup ? Comment je sais que la formule appliquée correspond bien à celle de la méthodologie publiée ? »C'est une question légitime. Sans preuve, le rapport CSRD repose sur la confiance dans l'intermédiaire — exactement ce que la directive cherche à éviter.
Trois propriétés sont nécessaires pour répondre :
- Intégrité : si un seul chiffre du bundle est modifié, la signature doit casser.
- Reproductibilité: l'auditeur doit pouvoir recalculer le total localement, sans appeler notre API.
- Indépendance : la vérification doit fonctionner même si carbon-llm disparaît du jour au lendemain.
Pourquoi pas une signature asymétrique (RSA / Ed25519) ?
On y a pensé en premier. Une signature avec une paire clé privée / clé publique est plus « propre » : n'importe qui peut vérifier sans secret partagé. Mais elle introduit un problème de distribution : chaque client doit avoir notre clé publique, et la révocation devient un cauchemar (CRL, OCSP, key rotation à coordonner avec tous les auditeurs).
Plus important encore : l'auditeur ne nous fait pas confiance, il fait confiance au client. Si carbon-llm signe avec sa clé privée, l'auditeur doit vérifier que cette clé n'a pas été compromise. Inversement, si chaque clienta sa propre clé HMAC stockée sur son compte, l'auditeur vérifie avec la clé que le client lui a donnée directement — sans nous comme intermédiaire.
L'architecture finale : HMAC-SHA256 + recompute_proof
Chaque tenant carbon-llm a une clé HMAC de 32 octets (256 bits) générée à la première demande d'export, stockée hex-encodée dans la colonne isv_accounts.audit_signing_key. Cette clé n'est jamais retournée par l'API ; elle est révélée une seule fois au client, qui la stocke dans son coffre-fort de secrets (Vault, AWS Secrets Manager, 1Password Business). Le client la partage avec son auditeur via un canal hors-bande.
Le bundle d'export contient quatre sections :
- events — un tableau immuable des événements bruts pour la période demandée : timestamp, model, prompt_tokens, completion_tokens, tenant_id, co2_grams. Pas de prompt content — RGPD by design.
- coefficients— les coefficients d'émission utilisés pour le calcul, avec source, date, version méthodologique. Si le coefficient gpt-4o = 0.34 g/1k tokens était sourcé du Stanford AI Index 2026 v1.0, c'est bien ça qui apparaît.
- recompute_proof — pour chaque event, le calcul détaillé
co2_grams = (prompt_tokens / 1000) × prompt_g_per_1k + (completion_tokens / 1000) × completion_g_per_1k. L'auditeur lit la formule, fait la somme, vérifie qu'elle correspond au total déclaré. - signature— un HMAC-SHA256 calculé sur la concaténation canonique des trois sections précédentes (JSON sérialisé déterministe, tri alphabétique des clés, pas d'espaces). Si une virgule change, la signature change.
Le code de vérification (que vous pouvez compiler chez vous)
Voici, en TypeScript pur, ce que l'auditeur exécute. Aucune dépendance externe au-delà de la crypto Node standard. Le but est qu'il comprennece qu'il vérifie — pas qu'il fasse confiance à une SDK opaque.
import { createHmac } from "node:crypto"
function verifyBundle(bundle, sharedKeyHex) {
// 1. Recalculer le total à partir des events bruts
const recomputedTotal = bundle.events.reduce((acc, e) => {
const coef = bundle.coefficients[e.model]
if (!coef) throw new Error("missing coefficient for " + e.model)
const co2 = (e.prompt_tokens / 1000) * coef.prompt_g_per_1k
+ (e.completion_tokens / 1000) * coef.completion_g_per_1k
return acc + co2
}, 0)
if (Math.abs(recomputedTotal - bundle.total_g) > 0.001) {
throw new Error("Total mismatch: bundle says " + bundle.total_g
+ ", recomputed " + recomputedTotal)
}
// 2. Vérifier la signature HMAC
const canonical = JSON.stringify({
events: bundle.events,
coefficients: bundle.coefficients,
recompute_proof: bundle.recompute_proof,
})
const expected = createHmac("sha256", Buffer.from(sharedKeyHex, "hex"))
.update(canonical)
.digest("hex")
if (expected !== bundle.signature) {
throw new Error("Signature mismatch — bundle has been tampered with")
}
return { ok: true, total_g: recomputedTotal, events_verified: bundle.events.length }
}50 lignes. Pas de dépendance à carbon-llm.com. Si un commissaire aux comptes refuse cette preuve, ce n'est plus un problème technique — c'est un problème de politique d'audit qu'il faut prendre avec la branche locale du cabinet.
Sérialisation canonique : le piège qui a coûté 2 jours
Tout le système repose sur le fait que la même donnée produit le même hash. Sauf que JSON.stringifyde Node ne garantit pas l'ordre des clés. { a: 1, b: 2 } et { b: 2, a: 1 } peuvent produire deux strings différentes — donc deux signatures différentes — donc une vérification qui échoue alors que les données sont identiques.
Notre solution : un canonical JSONqui trie récursivement les clés, normalise les flottants (notation décimale, pas de notation scientifique), et utilise UTF-8 sans BOM. C'est documenté dans lib/audit-signed-bundle.ts avec une fonction canonicalJsonStringify() et un test qui vérifie l'invariance face à 50 permutations aléatoires. Sans ça, le système est cassé en silence.
Pourquoi c'est un bon levier de vente
On a longtemps essayé de vendre carbon-llm sur les arguments classiques : « 100 fois plus de modèles que nos concurrents », « dashboard plus joli », « API plus simple ». Aucun ne convertit l'acheteur compliance, parce que ces critères ne ferment pas l'objection clé.
Le bundle signé, oui. Quand un acheteur RSE / DAF voit que son auditeur peut vérifier en localsans nous appeler, l'objection « et si vous changez les chiffres » disparaît. C'est la fonctionnalité qui justifie le prix Pro à 49 €/mois et le tier Compliance à 4 800 €/an. C'est aussi pourquoi on l'a positionnée comme l'élément central de notre grille tarifaire.
Voir un bundle exemple
On a publié un bundle d'exemple complet sur le site (no-auth, copiable directement) avec le JSON signé, la clé HMAC de démo, et les 4 étapes que l'auditeur exécute. C'est le livrable qu'un client Pro reçoit en cliquant Exports → Bundle audit signé dans son dashboard.
Si vous travaillez dans un cabinet d'audit ou êtes commissaire aux comptes et que vous voulez nous dire ce qui manque dans ce format, écrivez à hello@carbon-llm. On itère vite, et on cite les retours d'audit dans la prochaine release notes.
Sources et lectures complémentaires
- RFC 2104 — HMAC: Keyed-Hashing for Message Authentication
- RFC 8785 — JSON Canonicalization Scheme (JCS)
- ESRS E1-6 — Gross GHG emissions disclosure (CSRD)
- Stanford HAI AI Index 2026 — Energy & Environmental Impact
Les pages externes sont indépendantes ; carbon-llm n’approuve pas et ne contrôle pas le contenu tiers.