AttestScopeEN

Guide d’intégration

Écrire et vérifier des fichiers de trace

Du code qui fonctionne tel quel, en JavaScript et en Python, avec des vecteurs de test calculés par l’implémentation de référence.

Langue. Ce guide est traduit de l’anglais. En cas d’écart, la version anglaise de la spécification fait foi. Le code, les noms de champs et les vecteurs de test restent tels quels.

Version 0.1.1 · Publiée le 26 octobre 2026 · Implémentation de référence Riftveil · Éditeur Riftveil

Schéma technique : ce qui est calculé, ce qui circule, ce qui reste

Version 0.1.1 (26/10/2026), en service sur riftveil.ai, avec la signature Ed25519. Les traces de la version 0.1, où le format s’appelle encore assay-record, restent lisibles.

Aller au texte du guide

Émetteur · riftveil.ai Personne qui signe

Navigateurde la personne qui signeriftveil.aifonctions du serveurFournisseurdu modèleTout vérificateurplus tard,n’importe quiPlus tard · n’importe qui vérifie1 · projet · contexte · évaluation2 · Identifiant par le codehorloge UTC à la seconde+ 60 bits aléatoiresjamais par le modèle3 · protocole (instructions)projet · contexte· une exécution4 · rapport5a · Empreinte, sceau, signatureriftveil-report-v1S1-… · riftveil-seal-v1rien n’est conservé5 · rapport · identifiant(X-Riftveil-Record, évènementrecord) · empreinte · sceau S1-…6 · La personne remplit le suivijamais un modèleempreinte calculée ici :riftveil-decision-v27 · POST /api/decide{ id, report_fingerprint,report_seal, fingerprint }8a · Sceau du rapport contrôlépuis sceau du suivi,avec l’heure du serveurrien n’est conservé8 · { seal, sealedAt, signature }HMAC riftveil-decision-v1|id|empreinte|sealed_at9 · Fichier de trace assembléRiftveil <identifiant>.jsonrien ne partil reste chez la personneLa personne remet le fichierjamais un PDF seul : identifiantet sceau se recopient surun document modifié10 · Recalcule les empreintesdepuis le fichier, selonle profil qu’il nomme ; jamaisd’après l’empreinte écrite10 · POST /api/verify{ id, fingerprint, seal, decision? }{ valid, id, issuedAt, origin, decision? }Résultat affichévalide · contenu modifié · sceau non valide ·origine falsifiéeNavigateur de la personne qui signeriftveil.ai · fonctions du serveurFournisseur du modèleTout vérificateur, plus tardNavigateurriftveil.ai1 · projet · contexte · évaluationriftveil.ai2 · Identifiant par le codehorloge UTC à la seconde+ 60 bits aléatoiresjamais par le modèleriftveil.aiFournisseur du modèle3 · protocole (instructions)projet · contexte · une exécutionFournisseur du modèleriftveil.ai4 · rapportriftveil.ai5a · Empreinte, sceau, signatureriftveil-report-v1S1-… · riftveil-seal-v1rien n’est conservériftveil.aiNavigateur5 · rapport · identifiant(X-Riftveil-Record, évènement record)empreinte · sceau S1-…Navigateur6 · La personne remplit le suivijamais un modèleempreinte calculée ici :riftveil-decision-v2Navigateurriftveil.ai7 · POST /api/decide{ id, report_fingerprint,report_seal, fingerprint }riftveil.ai8a · Sceau du rapport contrôlépuis sceau du suivi, avec l’heuredu serveur · rien n’est conservériftveil.aiNavigateur8 · { seal, sealedAt }sceau et signature du suiviHMAC riftveil-decision-v1|id|empreinte|sealed_atNavigateur9 · Fichier de trace assembléRiftveil <identifiant>.jsonrien ne partil reste chez la personnePlus tard · n’importe qui vérifieNavigateurLa personne remet le fichierjamais un PDF seul : identifiant etsceau se recopient sur un documentmodifiéTout vérificateur10 · Recalcule les empreintesdepuis le fichier, selon le profilqu’il nomme ; jamais d’aprèsl’empreinte écriteTout vérificateurriftveil.ai10 · POST /api/verify{ id, fingerprint, seal,decision? }riftveil.aiTout vérificateur{ valid, id, issuedAt,origin, decision? }Tout vérificateurRésultat affichévalide · contenu modifié ·sceau non valide · origine falsifiée
Les échanges entre le navigateur, riftveil.ai et le vérificateur.

Aujourd’huiPage « Enregistrer un rapport »

Rapport fait dans un autre assistant IA

Le protocole a été appliqué dans Microsoft 365 Copilot, ChatGPT, Claude, Gemini, Mistral Vibe ou un autre assistant IA. La personne colle le projet et le rapport sur riftveil.ai.

  1. a · Le navigateur calcule l’empreinteriftveil-content-v1 { v: 1, assistant, project, context, report }
  2. b · Seule l’empreinte partPOST /api/register { fingerprint, profile }
  3. c · riftveil.ai renvoie identifiant et sceau{ id, seal, issuedAt, origin: "registered", profile } libellé riftveil-register-v1

Origine affichée : « enregistré au registre Riftveil », distincte de « généré ». Le sceau prouve que ce texte exact existait à cette date et n’a pas changé depuis ; la bonne application du protocole relève de la personne qui enregistre le rapport.

Le suivi d’un rapport enregistré se remplit, se scelle et se signe comme celui d’un rapport généré sur riftveil.ai.

Points d’accès

En service · version 0.1.1

  • POST /api/verify{ id, fingerprint, seal, signature?, decision? } → { valid: true, id, issuedAt, origin, signature?, decision? } ou { valid: false }

    Ouvert à tout site · reçoit des empreintes seulement · ne garde rien.

  • POST /api/register{ fingerprint, profile: "riftveil-content-v1" } → { id, seal, signature, issuedAt, origin: "registered", profile }
  • POST /api/decide{ id, report_fingerprint, report_seal, fingerprint } → { seal, sealedAt, origin, signature, report_signature }
  • GET /keys.json→ { issuer, updated, keys: [ { id, alg, public_key, status, valid_from, … } ] }

    La liste des clés publiques de riftveil.ai, ouverte à tout site. Aucune clé n’en est retirée.

Enregistrement et suivi : depuis les pages de riftveil.ai, derrière un contrôle anti-robots ; seul un compteur quotidien par adresse IP hachée est gardé.

Préfixes enregistrés : https://riftveil.ai/issuers.json

Module pour un autre site : il vérifie le rapport, sa signature et le sceau du suivi ; le suivi lui-même se lit sur riftveil.ai/verify. 6. Ajouter la vérification à une page

Les règles de calcul

Identifiant

XTémetteur-261026date UTC-143205heure UTC-7K3M-Q9XD-2B8F60 bits aléatoires

<ÉMETTEUR>-<AAMMJJ>-<HHMMSS>-<RRRR>-<RRRR>-<RRRR>^[A-Z]{2,6}-\d{6}-\d{6}-[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}-[0-9A-HJKMNP-TV-Z]{4}$
  • Attribué par le code au moment où la trace est créée, d’une horloge synchronisée sur une source publique.
  • Jamais produit, suggéré ni complété par un modèle.
  • Indépendant du contenu : deux évaluations du même projet font deux traces.
  • Unique : une nouvelle version reçoit un nouvel identifiant.
  • Base32 Crockford : 0-9 et A-Z sans I, L, O, U ; 12 caractères en 3 groupes de 4.
  • RV : Riftveil. XT : réservé aux essais, jamais pour une décision.

Empreintes · SHA-256, 64 caractères

riftveil-report-v1Rapport généré sur riftveil.ai

riftveil-content-v1Rapport fait ailleurs, enregistré

riftveil-decision-v2Suivi et décision (0.1.1), avec le document final et le suivi précédent, tous les champs présents

riftveil-decision-v1Suivi et décision (0.1)

1. Calculer une empreinte

Chaque vérificateur recalcule l’empreinte depuis le fichier. Un seul caractère changé change toute l’empreinte, qui ne permet pas de retrouver le texte. Le suivi contient l’empreinte du rapport : il n’appartient qu’à un rapport.

Sceau · HMAC-SHA256

S1-REQ8-RW0G-J013-M23D-C969-73JZ-MF93-1W6J

S1 : clé n° 1 · 32 caractères base32 : 160 bits

5. Vecteurs de test · 3. Faire vérifier le sceau par l’émetteur

  • riftveil-seal-v1généré sur riftveil.ai
  • riftveil-register-v1enregistré, fait ailleurs
  • riftveil-decision-v1suivi ; le message ajoute |sealed_at
  • Un libellé par origine : un sceau ne peut jamais passer pour l’autre origine.
  • Clé secrète dans un coffre de secrets, jamais dans le code, un fichier partagé ou une conversation.
  • Clés numérotées : une nouvelle clé reçoit un nouveau numéro, les anciennes restent disponibles pour la vérification.

Signature Ed25519 sur le même message, avec une autre clé (ed25519-1) : 86 caractères base64url, que chacun contrôle avec la clé publique. 2. Contrôler la signature avec la clé publique

Un sceau valide prouve que riftveil.ai a attribué cet identifiant, à l’heure indiquée par son horloge, à ce contenu exact. Juger si le projet est juste revient à la personne qui signe.

Ne quitte jamais l’appareil

  • Les noms de la personne qui signe (verified_by) et des personnes à qui les questions sont confiées
  • La décision et ses conditions
  • La signature, tracée à l’écran ou tapée
  • Le document final : seule son empreinte, calculée sur l’appareil, entre dans le suivi
  • Le fichier de trace lui-même
Ce qui part vers riftveil.ai

Pour l’évaluation : le projet et le contexte, pour une seule exécution chez le fournisseur du modèle ; riftveil.ai n’en garde rien.

Pour sceller le suivi, enregistrer, vérifier : identifiants, empreintes, sceaux, signatures et heures, rien d’autre.

Qui détient le fichier de trace détient les textes et les noms du suivi : il se partage comme on partage le rapport.

Les versions du format

Version 0.1.1 · en service

Signature à clé publique

  • Spécification publiée sur GitHub, comme le protocole Riftveil v1.0.0.
  • Signature Ed25519 de chaque rapport et de chaque suivi par riftveil.ai, sur le même message que le sceau.
  • Clés publiques publiées sur GitHub, sur le site du format et à riftveil.ai/keys.json.
  • Contrôle avec la clé publique de riftveil.ai : n’importe qui peut le refaire, sans passer par riftveil.ai.
  • Document final, suivi complété plus tard, suivi des rapports enregistrés.
  • Champ facultatif proof.timestamps : un ou plusieurs jetons RFC 3161 (horodatage qualifié en option, ou autorité de l’organisation).

Version 0.2Prévu

Preuve indépendante et registre

  • JSON canonique (RFC 8785) comme entrée d’empreinte, à côté des profils actuels.
  • Entrée de registre : la trace sans le contenu, pour le registre de l’organisation.
  • Suivi constat par constat, outcome et readers[], chacun avec son empreinte.
  • Référentiel de l’organisation et attestation règle par règle.
  • Journal public.

Règles de passage

Tout ce qui est émis reste lisible

  • Le calcul d’un profil existant reste le même dans toute nouvelle version.
  • Un vérificateur 0.1.1 vérifie toute trace 0.1 ; un vérificateur 0.2 vérifiera toute trace 0.1 et 0.1.1.
  • En 0.1, les fichiers portent "format": "assay-record" ; depuis la 0.1.1, "format": "attestscope". Les traces déjà émises restent lisibles.

Tout ce qui suit fonctionne tel quel. Les empreintes sont celles de l’implémentation de référence (riftveil.ai).

1. Calculer une empreinte

L’entrée d’empreinte est un objet JSON sérialisé sans espaces, sans échappement des caractères non ASCII, avec les clés dans l’ordre indiqué. En JavaScript, c’est exactement JSON.stringify. En Python, utilisez ensure_ascii=False et separators=(",", ":"). Calculez ensuite le SHA-256 des octets UTF-8, en hexadécimal minuscule.

Profil Objet d’entrée, dans cet ordre de clés
riftveil-report-v1 v (1), id, protocol, model, project, context, report
riftveil-content-v1 v (1), assistant, project, context, report
riftveil-decision-v1 v (1), id, report_fingerprint, verified_by {last_name, first_name, role}, checked, questions [{n, question, suggested_owner, assigned_to}], decision, conditions, place, signature {method, typed, image}
riftveil-decision-v2 les champs de riftveil-decision-v1 avec v (2), puis final_document {name, size, sha256}, previous {fingerprint, seal, sealed_at}

JavaScript (navigateur ou Node 18 et plus) :

async function sha256hex(text) {
  const d = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(text));
  return [...new Uint8Array(d)].map(b => b.toString(16).padStart(2, "0")).join("");
}

function reportInput(f) {
  return JSON.stringify({ v: 1, id: f.record_id, protocol: f.assessment.protocol, model: f.assessment.model,
    project: f.content.project, context: f.content.context, report: f.content.report });
}

const file = JSON.parse(recordFileText);
const fingerprint = await sha256hex(reportInput(file));
if (fingerprint !== file.proof.fingerprint.value) throw new Error("content altered");

Python 3 :

import hashlib, json

def canon(obj):
    return json.dumps(obj, ensure_ascii=False, separators=(",", ":"))

def report_fingerprint(f):
    a, c = f["assessment"], f["content"]
    obj = {"v": 1, "id": f["record_id"], "protocol": a["protocol"], "model": a["model"],
           "project": c["project"], "context": c["context"], "report": c["report"]}
    return hashlib.sha256(canon(obj).encode("utf-8")).hexdigest()

f = json.load(open("Riftveil RV-….json", encoding="utf-8"))
assert report_fingerprint(f) == f["proof"]["fingerprint"]["value"], "content altered"

2. Contrôler la signature avec la clé publique

Version 0.1.1. La signature est une signature Ed25519 du message du sceau ; chacun peut la contrôler avec la liste des clés publiée de l’émetteur, sans rien lui demander. Prenez la liste des clés à l’adresse enregistrée pour le préfixe de l’émetteur dans ISSUERS, jamais dans proof.signature.keys : ce champ est écrit dans le fichier, si bien qu’un fichier falsifié pourrait désigner une fausse liste.

JavaScript (navigateurs récents, Node 20 et plus) :

const b64u = (s) => Uint8Array.from(atob(s.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
const label = file.origin === "registered" ? "riftveil-register-v1" : "riftveil-seal-v1";
const message = label + "|" + file.record_id + "|" + fingerprint;   // fingerprint recomputed in step 1
const KEY_LISTS = { RV: "https://riftveil.ai/keys.json" };            // registered addresses, see ISSUERS
const listUrl = KEY_LISTS[file.record_id.split("-")[0]];
if (!listUrl) throw new Error("unknown issuer");
const list = await (await fetch(listUrl)).json();
const k = list.keys.find((x) => x.id === file.proof.signature.key && x.alg === "ed25519");
if (!k || k.status === "compromised") throw new Error("key unknown or compromised");
const pub = await crypto.subtle.importKey("raw", b64u(k.public_key), { name: "Ed25519" }, false, ["verify"]);
const ok = await crypto.subtle.verify({ name: "Ed25519" }, pub, b64u(file.proof.signature.value), new TextEncoder().encode(message));
if (!ok) throw new Error("signature not valid");

Python 3 (pip install cryptography) :

import base64, json, urllib.request
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey

def b64u(s): return base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))

KEY_LISTS = {"RV": "https://riftveil.ai/keys.json"}  # registered addresses, see ISSUERS

fingerprint = report_fingerprint(f)  # recomputed in step 1 (for a registered report, use its own profile)
label = "riftveil-register-v1" if f["origin"] == "registered" else "riftveil-seal-v1"
message = label + "|" + f["record_id"] + "|" + fingerprint
sig = f["proof"]["signature"]
keys = json.load(urllib.request.urlopen(KEY_LISTS[f["record_id"].split("-")[0]]))["keys"]
k = next(k for k in keys if k["id"] == sig["key"] and k["alg"] == "ed25519")
assert k["status"] != "compromised", "key compromised"
Ed25519PublicKey.from_public_bytes(b64u(k["public_key"])).verify(b64u(sig["value"]), message.encode("utf-8"))  # raises if not valid

Pour un suivi, le message est "riftveil-decision-v1|" + id + "|" + follow-up fingerprint + "|" + sealed_at, et la signature est follow_up.proof.signature. Pour un suivi antérieur sous previous, recommencez avec ses propres champs.

3. Faire vérifier le sceau par l’émetteur

Le sceau est un HMAC calculé avec la clé secrète de l’émetteur : seul l’émetteur peut le vérifier. N’envoyez que des empreintes.

const res = await fetch("https://riftveil.ai/api/verify", {
  method: "POST", headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ id: file.record_id, fingerprint, seal: file.proof.seal.value })
});
const r = await res.json();   // { valid: true, id, issuedAt, origin } or { valid: false }
if (!r.valid) throw new Error("seal not valid");
if (r.origin !== file.origin) throw new Error("forged origin");

Pour un fichier avec un suivi, ajoutez decision: { fingerprint, seal, sealedAt } à la même requête ; la réponse contient alors aussi decision: { valid, sealedAt }.

curl -s https://riftveil.ai/api/verify -H 'Content-Type: application/json' \
  -d '{"id":"RV-…","fingerprint":"…64 hex…","seal":"S1-…"}'

4. Valider la structure

pip install jsonschema
python3 -c "import json,jsonschema; jsonschema.validate(json.load(open('record.json')), json.load(open('attestscope-0.1.1.schema.json')))"

Les fichiers de la version 0.1 sont valides selon assay-record-0.1.schema.json.

5. Vecteurs de test

Calculés avec l’implémentation de référence. Les sceaux utilisent la clé de test publique assay-test-key-1, les signatures la clé de test Ed25519 publique ed25519-1 de test-vectors.json (graine SHA-256("attestscope-test-ed25519-1"), clé publique 2FLWc-QnkT78GPsymtkuE5xgzIMLCHzigAAgOs6D2TY), et le préfixe XT, réservé aux tests : ils permettent de tester une implémentation du scellement ou de la signature, et ne sont jamais valides sur riftveil.ai. Entrées complètes : test-vectors.json. La trace d’illustration examples/elena-lambert.json utilise les mêmes clés de test, avec la liste des clés test-keys.json.

Vecteur Empreinte Sceau avec la clé de test
V1 · généré (riftveil-report-v1) abc5ba903a307668121b1fdd0e01ce784069189c1b255e481f65f5e54ee765fc S1-2ZQR-3N0J-PRKP-8G33-Q2ZT-ZPZA-QDFN-MEPH
V2 · enregistré (riftveil-content-v1) 2ae571cf5ba080714e2d04e867a05b09a36f4157ddbdd17d3a1574366af0f9fd S1-7CBT-7WG0-0HBP-2336-45NX-2PDS-HE57-C6DK
V3 · texte non ASCII et saut de ligne 14971ae9adf597868bd4ab39783cc64c7ebff23e38d7a4f2f80d11df9d212f55 S1-CW1S-3W97-PB70-MKYQ-NTD5-JFQK-P8ES-B883
V4 · suivi (riftveil-decision-v1), lié à V1 bfdb75b1260feca991f6f59862c3cdc7e3910f424612244e9fdeb999a951d82e S1-FEK5-BRSD-MC99-S95R-2V46-C40R-CHYM-ARK2
V6 · suivi (riftveil-decision-v2), complète V4, avec un document final 2f283adb5660de3e785220c90a27a7eb9abe43fb310dffbdda579d6b2f87b817 S1-JZHS-ME8Y-5A25-4S9Z-7YBJ-HMYE-7J26-B9AT

Signatures avec la clé de test Ed25519 : V5, sur le message du sceau de V1, 2jKXUE5glnWjDwQg-u4d6-GiOJKflAYbj3lgjDrsqunL4gs6TOentYbyUpIbbFX7IYIZ2qGmHYvVtpVAyI9BDg ; V6, sur le message de son sceau, kWyKO9MpoYuqXmawQUVZZH__4k3rWgWIyHo5mTNIB3w_mIiZqTLRv4sEAn4GGnbBWt5BUPn7jhNwt95aa8nTCw.

Entrée de V1, octet pour octet :

{"v":1,"id":"XT-260101-000000-0000-0000-0000","protocol":"1.3.3","model":"test-model","project":"Project text.","context":"","report":"Report text."}

Sceau : "S1-" suivi des 160 premiers bits de HMAC-SHA256(key, "riftveil-seal-v1|" + id + "|" + fingerprint), en base32 de Crockford (0123456789ABCDEFGHJKMNPQRSTVWXYZ), soit 32 caractères en 8 groupes de 4. Pour V1, l’entrée du HMAC est riftveil-seal-v1|XT-260101-000000-0000-0000-0000|abc5ba903a307668121b1fdd0e01ce784069189c1b255e481f65f5e54ee765fc.

6. Ajouter la vérification à une page

<div data-riftveil-verify data-lang="en"></div>
<script src="https://riftveil.ai/embed/verify.js" defer></script>

La politique de sécurité du contenu (en-tête Content-Security-Policy) de la page qui accueille le module doit autoriser script-src et connect-src https://riftveil.ai.

7. Devenir émetteur

Inscrivez un préfixe dans ISSUERS (préfixe, nom, contact, point d’accès de vérification, adresse de la liste des clés), publiez vos profils d’empreinte si vous en ajoutez, créez vous-même votre clé de signature et gardez-la, comme votre clé de scellement, dans un coffre de secrets, publiez votre liste des clés, et suivez les règles de l’émetteur de la section Conformité.