Verifisering
To uavhengige bevis beskytter hver fullførte konvolutt:
- Seglet, inne i PDF-en: et PAdES-dokumenttidsstempel fra en offentlig tidsstempeltjeneste. Adobe Acrobat og Reader kontrollerer det og viser dokumentet som uendret siden det ble forseglet.
- Ankeret, utenfor PDF-en: dokumentets fingeravtrykk er et løv i et Merkle-tre der oppføringer bare kan legges til, og Bona Fide signerer hver trerot med Ed25519. Alle kan kontrollere beviset med de publiserte offentlige nøklene, uten konto og uten å måtte stole på en PDF-leser.
Forsegling
Section titled “Forsegling”Når den siste mottakeren signerer, fullføres konvolutten (envelope.completed), og forseglingstjenesten tar over:
- Kontrollerer originalens SHA-256 mot verdien som ble registrert da konvolutten ble opprettet.
- Flater ut dokumentet: feltverdiene (tekst, avmerkingsbokser, tegnede, skrevne eller opplastede signaturer) tegnes inn i sideinnholdet, og alle skjemafelt, merknader, JavaScript-handlinger og innebygde filer fjernes.
- Legger til fullføringssertifikatet: konvolutt-ID, originalens SHA-256, hver signatar med verifiseringsmetoden som faktisk ble brukt (for eksempel E-postlenke (unik lenke sendt til …) eller E-postlenke + engangskode, se verifisering av signatarer), tidsstempler, IP-adresse og enhet, og tidslinjen over hendelser. Hver side får bunnteksten Bona Fide Envelope ID … · Verify at bonafidesign.com/verify.
- Legger til et PAdES-dokumenttidsstempel (
/DocTimeStamp,ETSI.RFC3161, SHA-256) fra en offentlig tidsstempeltjeneste (flere offentlig klarerte leverandører med automatisk failover), og bygger inn valideringsdataene for tidsstempeltjenestens sertifikatkjede (OCSP/CRL i DSS) slik at det forblir verifiserbart på lang sikt (LTV). - Validerer sitt eget resultat, lagrer det og registrerer den forseglede SHA-256-verdien (
sealedDocumentHash) og et løv for ankerloggen.
Deretter utløses envelope.sealed, og GET /v1/envelopes/{id}/document?version=sealed returnerer filen. Hvis ingen
tidsstempeltjeneste er tilgjengelig, prøves forseglingen på nytt; konvolutten forblir completed med
seal: "pending" i mellomtiden.
Forankring
Section titled “Forankring”Hver forseglede konvolutt bidrar med ett løv, den kanoniske JSON-en (nøkler sortert, uten mellomrom) av:
{ "completed_at": "2026-10-13T14:09:52.004Z", "envelope_id": "env_8Gm2kQ4rT1", "event_chain_head": "<head of the envelope's audit hash chain>", "original_document_sha256": "<SHA-256 of the document as sent>", "sealed_document_sha256": "<SHA-256 of the sealed PDF>"}Hver time blir løvene som er registrert siden forrige anker, til et Merkle-tre (RFC 6962 / RFC 9162:
leaf = SHA-256(0x00 ‖ leaf JSON), node = SHA-256(0x01 ‖ left ‖ right)), og roten publiseres som et signert
anker:
{ "id": "…", "version": "bona-fide-anchor-v1", "sequence": 412, "root": "<hex Merkle root>", "previous_root": "<root of anchor 411>", "leaf_count": 37, "created_at": "2026-10-13T15:00:00.118Z", "key_id": "<first 16 hex of SHA-256(public key)>", "signature": "<base64url Ed25519 signature>", "signed_message": "bona-fide-anchor-v1|412|<root>|<previous_root>|2026-10-13T15:00:00.118Z|37"}Ed25519-signaturen dekker bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8).
previous_root kjeder hvert anker til det forrige (det første peker på 64 nuller), så et anker kan ikke fjernes
eller flyttes i rekkefølgen uten å bryte kjeden, og ankertabellene tillater bare at oppføringer legges til.
Signeringsnøklene publiseres med gyldighetsperiodene sine; nøkler som er tatt ut av bruk, blir stående på listen, så gamle ankre fortsatt kan verifiseres:
{ "issuer": "Bona Fide", "anchor_format": "bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8, Ed25519; root = RFC 6962 SHA-256 Merkle tree)", "keys": [ { "key_id": "<16 hex>", "algorithm": "Ed25519", "public_key": "<base64url raw 32-byte key>", "public_key_pem": "-----BEGIN PUBLIC KEY-----…", "valid_from": "2026-10-14T00:00:00.000Z", "valid_until": null, "status": "active" } ]}Verifisere et dokument
Section titled “Verifisere et dokument”Den raskeste måten er den offentlige siden: bonafidesign.com/verify. Den beregner hashen av PDF-en i nettleseren din (filen lastes aldri opp), slår opp hashen og kontrollerer beviset og signaturen på enheten din. Ankerloggen viser alle ankre.
Det samme oppslaget finnes som et offentlig API: ingen nøkkel, hvilken som helst opprinnelse, hastighetsbegrenset per IP-adresse. Send bare SHA-256-verdien:
curl https://api.bonafidesign.com/v1/public/verify \ -H "Content-Type: application/json" \ -d "{\"sha256\": \"$(shasum -a 256 signed.pdf | cut -d' ' -f1)\"}"{ "sha256": "…", "status": "verified", "results": [ { "status": "verified", "matched": "sealed_document", "envelope": { "id": "env_8Gm2kQ4rT1", "status": "completed", "completed_at": "2026-10-13T14:09:52.004Z", "sealed_at": "2026-10-13T14:10:31.442Z", "organization_name": "Acme Robotics, Inc.", "signer_count": 1, "signers": [{ "name": "Jane Doe", "email": "jane@acme.dev", "role": "client", "verification_method": "Email OTP", "signed_at": "…" }], "verification_methods": ["Email OTP"], "test_mode": false }, "leaf": { "envelope_id": "env_8Gm2kQ4rT1", "…": "…" }, "leaf_hash": "…", "proof": { "leaf_index": 12, "tree_size": 37, "hashes": ["…", "…"] }, "anchor": { "sequence": 412, "…": "…" }, "links": { "anchor": "…", "keys": "…/.well-known/bona-fide/keys.json" } } ]}status er verified, pending_anchor (fullført og registrert, venter på neste ankring som skjer hver time) eller
not_found: ingen fullført konvolutt har nøyaktig denne filen, så den er ikke dokumentet som ble signert (eller den er
endret siden). Både den forseglede filen og originalen slik den ble sendt, gir treff.
Kontrollere beviset selv
Section titled “Kontrollere beviset selv”Du trenger ikke ta API-et på ordet: alt som trengs for å kontrollere svaret, finnes i svaret og i keys.json.
- Dokument: SHA-256 av filen din er lik
leaf.sealed_document_sha256(elleroriginal_document_sha256). - Løv:
SHA-256(0x00 ‖ canonical JSON of leaf)er likleaf_hash. - Inkludering: når du går gjennom
proof.hashesfra løvhashen (RFC 9162 §2.1.3.2, medleaf_indexogtree_size == anchor.leaf_count), får duanchor.root. - Nøkkel:
anchor.key_idfinnes ikeys.json, nøkkel-ID-en er de første 16 heksadesimale tegnene i SHA-256 av den rå offentlige nøkkelen, oganchor.created_atligger innenfor nøkkelens gyldighetsperiode. - Signatur: Ed25519-signaturen verifiseres over
bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count.
# pip install requests cryptographyimport base64, hashlib, json, sys, requestsfrom cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
API = "https://api.bonafidesign.com"b64url = lambda s: base64.urlsafe_b64decode(s + "=" * (-len(s) % 4))sha = lambda b: hashlib.sha256(b).digest()
def root_from_proof(leaf: bytes, index: int, size: int, path: list[bytes]) -> bytes: fn, sn, r = index, size - 1, leaf for p in path: if sn == 0: raise ValueError("proof too long") if fn % 2 == 1 or fn == sn: r = sha(b"\x01" + p + r) while fn % 2 == 0 and fn != 0: fn, sn = fn // 2, sn // 2 else: r = sha(b"\x01" + r + p) fn, sn = fn // 2, sn // 2 if sn != 0: raise ValueError("proof too short") return r
digest = hashlib.sha256(open(sys.argv[1], "rb").read()).hexdigest()res = requests.post(f"{API}/v1/public/verify", json={"sha256": digest}).json()keys = {k["key_id"]: k for k in requests.get(f"{API}/.well-known/bona-fide/keys.json").json()["keys"]}
for m in res["results"]: leaf, proof, anchor = m["leaf"], m["proof"], m["anchor"] if not anchor: print("pending anchor"); continue assert digest in (leaf["sealed_document_sha256"], leaf["original_document_sha256"]) leaf_json = json.dumps(leaf, sort_keys=True, separators=(",", ":")).encode() leaf_hash = sha(b"\x00" + leaf_json) assert proof["tree_size"] == anchor["leaf_count"] root = root_from_proof(leaf_hash, proof["leaf_index"], proof["tree_size"], [bytes.fromhex(h) for h in proof["hashes"]]) assert root.hex() == anchor["root"], "not included under the anchor root" key = keys[anchor["key_id"]] raw = b64url(key["public_key"]) assert hashlib.sha256(raw).hexdigest()[:16] == anchor["key_id"] message = "|".join([anchor["version"], str(anchor["sequence"]), anchor["root"], anchor["previous_root"], anchor["created_at"], str(anchor["leaf_count"])]).encode() Ed25519PublicKey.from_public_bytes(raw).verify(b64url(anchor["signature"]), message) # raises if invalid print(f"VERIFIED {leaf['envelope_id']} in anchor #{anchor['sequence']}")Løvet har bare strengverdier med ASCII-nøkler, så json.dumps(…, sort_keys=True, separators=(",", ":")) gir den samme
kanoniske JSON-en. Kontroller også nøkkelens valid_from / valid_until mot anchor["created_at"].
Referanseverifikatoren, @bona-fide/verify, er isomorf (nettleser og Node, ingen Bona Fide-hemmeligheter) og er det
/verify-siden kjører:
import { sha256Hex, verifyProof, type KeySet, type PublicVerifyResponse } from '@bona-fide/verify';import { readFile } from 'node:fs/promises';
const api = 'https://api.bonafidesign.com';const sha256 = sha256Hex(new Uint8Array(await readFile('signed.pdf')));const body = (await (await fetch(`${api}/v1/public/verify`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ sha256 }),})).json()) as PublicVerifyResponse;const keys = (await (await fetch(`${api}/.well-known/bona-fide/keys.json`)).json()) as KeySet;
for (const m of body.results) { if (!m.leaf || !m.proof || !m.anchor) continue; // pending_anchor const r = await verifyProof(m.leaf, m.proof, m.anchor, keys, { documentSha256: sha256, expectedLeafHash: m.leaf_hash ?? undefined }); console.log(r.valid ? 'VERIFIED' : 'INVALID', m.envelope.id, r.checks);}Pakken har også et kommandolinjeverktøy (bona-fide-verify <file.pdf | sha256> [--api URL] [--json]; fra en
utsjekking av Bona Fide-repositoriet: pnpm --filter @bona-fide/verify cli signed.pdf). Det beregner hashen av filen
lokalt, kontrollerer hvert steg ovenfor og avslutter med 0 når filen er verifisert, 1 når den ikke finnes eller er
ugyldig, og 2 når dokumentet ikke er forankret ennå.
Kontroller signaturen til et anker med PEM-nøkkelen fra keys.json (OpenSSL 3):
curl -s https://api.bonafidesign.com/v1/public/anchors/412 > anchor.jsoncurl -s https://api.bonafidesign.com/.well-known/bona-fide/keys.json > keys.json
printf %s "$(jq -r .signed_message anchor.json)" > message.txtjq -r .signature anchor.json | python3 -c 'import base64,sys; s=sys.stdin.read().strip(); sys.stdout.buffer.write(base64.urlsafe_b64decode(s + "=" * (-len(s) % 4)))' > signature.binKEY_ID=$(jq -r '.key_id' anchor.json)jq -r --arg k "$KEY_ID" '.keys[] | select(.key_id == $k) | .public_key_pem' keys.json > key.pem
openssl pkeyutl -verify -pubin -inkey key.pem -rawin -in message.txt -sigfile signature.bin# Signature Verified SuccessfullyAnkre listes opp med de nyeste først med GET /v1/public/anchors?limit=20&before=<sequence> (send next_before for å
bla bakover) og hentes ett og ett med GET /v1/public/anchors/{sequence}.