Assay RecordVERSION 0.1
Guide d’intégration

Écrire et vérifier des fichiers

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.

Version 0.1-draftPubliée le 27 septembre 2026Implémentation de référence RiftveilPorté par Human Frontier

Le texte normatif est en anglais ; il fait foi. Les titres et cette introduction sont traduits.

Everything below runs as is. The fingerprints are those of the reference implementation (riftveil.ai).

1. Compute a fingerprint

The fingerprint input is a JSON object serialised without spaces, without escaping non-ASCII characters, keys in the order shown. In JavaScript this is exactly JSON.stringify. In Python, use ensure_ascii=False and separators=(",", ":"). Then SHA-256 over the UTF-8 bytes, in lower-case hexadecimal.

Profile Input object, in this key order
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}

JavaScript (browser or Node 18+):

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(proofFileText);
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. Check the seal with the issuer

The seal is an HMAC with the issuer's secret key: only the issuer can check it. Send fingerprints only.

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");

For a file with a follow-up, add decision: { fingerprint, seal, sealedAt } to the same request; the answer adds decision: { valid, sealedAt }.

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

3. Validate the structure

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

4. Test vectors

Computed with the reference implementation. The seals use the public test key assay-test-key-1 and the prefix XT, reserved for tests: they let you test a sealing implementation, and are never valid at riftveil.ai. Full inputs: test-vectors.json.

Vector Fingerprint Seal with the test key
V1 · generated (riftveil-report-v1) abc5ba903a307668121b1fdd0e01ce784069189c1b255e481f65f5e54ee765fc S1-2ZQR-3N0J-PRKP-8G33-Q2ZT-ZPZA-QDFN-MEPH
V2 · registered (riftveil-content-v1) 2ae571cf5ba080714e2d04e867a05b09a36f4157ddbdd17d3a1574366af0f9fd S1-7CBT-7WG0-0HBP-2336-45NX-2PDS-HE57-C6DK
V3 · non-ASCII text and line break 14971ae9adf597868bd4ab39783cc64c7ebff23e38d7a4f2f80d11df9d212f55 S1-CW1S-3W97-PB70-MKYQ-NTD5-JFQK-P8ES-B883
V4 · follow-up (riftveil-decision-v1), bound to V1 bfdb75b1260feca991f6f59862c3cdc7e3910f424612244e9fdeb999a951d82e S1-FEK5-BRSD-MC99-S95R-2V46-C40R-CHYM-ARK2

V1 input, byte for byte:

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

Seal: "S1-" + the first 160 bits of HMAC-SHA256(key, "riftveil-seal-v1|" + id + "|" + fingerprint), in Crockford base32 (0123456789ABCDEFGHJKMNPQRSTVWXYZ), 32 characters in 8 groups of 4. For V1 the HMAC input is riftveil-seal-v1|XT-260101-000000-0000-0000-0000|abc5ba903a307668121b1fdd0e01ce784069189c1b255e481f65f5e54ee765fc.

5. Add verification to a page

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

The host page's Content-Security-Policy must allow script-src and connect-src https://riftveil.ai.

6. Become an issuer

Register a prefix in ISSUERS (prefix, name, contact, verification endpoint), publish your fingerprint profiles if you add any, keep your keys in a secret store, and follow the issuer rules of Conformance.