Verifiering
Två oberoende bevis skyddar varje slutfört kuvert:
- Förseglingen, inuti PDF:en: en PAdES-dokumenttidsstämpel från en offentlig tidsstämpelutfärdare. Adobe Acrobat och Reader kontrollerar den och visar att dokumentet inte har ändrats sedan det förseglades.
- Ankaret, utanför PDF:en: dokumentets fingeravtryck är ett löv i ett Merkle-träd där poster bara kan läggas till, och Bona Fide signerar varje trädrot med Ed25519. Vem som helst kan kontrollera beviset med de publicerade publika nycklarna, utan konto och utan att behöva lita på en PDF-läsare.
Försegling
Section titled “Försegling”När den sista mottagaren signerar slutförs kuvertet (envelope.completed) och förseglingstjänsten tar över:
- Jämför originalets SHA-256 med den som registrerades när kuvertet skapades.
- Plattar till dokumentet: fältvärden (text, kryssrutor, ritade, skrivna eller uppladdade signaturer) ritas in i sidinnehållet, och alla formulärfält, anteckningar, JavaScript-åtgärder och inbäddade filer tas bort.
- Bifogar slutförandecertifikatet: kuvertets id, originalets SHA-256, varje undertecknare med den verifieringsmetod som faktiskt användes (till exempel E-postlänk (unik länk skickad till …) eller E-postlänk + engångskod, se verifiering av undertecknare), tidsstämplar, IP-adress och enhet samt händelseförloppet. Varje sida får sidfoten Bona Fide Envelope ID … · Verify at bonafidesign.com/verify.
- Lägger till en PAdES-dokumenttidsstämpel (
/DocTimeStamp,ETSI.RFC3161, SHA-256) från en offentlig tidsstämpelutfärdare (flera allmänt betrodda leverantörer med automatisk reservväxling) och bäddar in valideringsdata för tidsstämpelutfärdarens certifikatkedja (OCSP/CRL i DSS) så att den förblir verifierbar på lång sikt (LTV). - Validerar sitt eget resultat, lagrar det och registrerar den förseglade filens SHA-256 (
sealedDocumentHash) och ett löv för ankarloggen.
Sedan utlöses envelope.sealed och GET /v1/envelopes/{id}/document?version=sealed returnerar filen. Om ingen
tidsstämpelutfärdare är tillgänglig görs förseglingen om; kuvertet förblir completed med seal: "pending" under tiden.
Förankring
Section titled “Förankring”Varje förseglat kuvert bidrar med ett löv, den kanoniska JSON-representationen (sorterade nycklar, inga blanksteg) 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>"}Varje timme blir de löv som registrerats sedan föregående ankare ett Merkle-träd (RFC 6962 / RFC 9162:
leaf = SHA-256(0x00 ‖ leaf JSON), node = SHA-256(0x01 ‖ left ‖ right)), och dess rot publiceras som ett signerat
ankare:
{ "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 omfattar bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8).
previous_root kedjar varje ankare till det föregående (det första pekar på 64 nollor), så ett ankare kan inte tas
bort eller flyttas utan att kedjan bryts, och ankartabellerna tillåter bara tillägg.
Signeringsnycklarna publiceras med sina giltighetsperioder; avvecklade nycklar finns kvar i listan så att gamla ankare förblir verifierbara:
{ "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" } ]}Verifiera ett dokument
Section titled “Verifiera ett dokument”Det snabbaste sättet är den offentliga sidan: bonafidesign.com/verify. Den beräknar PDF:ens hash i din webbläsare (filen laddas aldrig upp), slår upp hashen och kontrollerar beviset och signaturen på din enhet. Ankarloggen listar alla ankare.
Samma uppslag finns som ett offentligt API: ingen nyckel, valfritt ursprung, hastighetsbegränsat per IP-adress. Skicka bara SHA-256:
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 är verified, pending_anchor (slutfört och registrerat, väntar på nästa ankare varje timme) eller
not_found: inget slutfört kuvert har exakt den här filen, så det är inte det dokument som signerades (eller så har
det ändrats sedan dess). Både den förseglade filen och originalet som det skickades ger träff.
Kontrollera beviset själv
Section titled “Kontrollera beviset själv”Lita inte bara på API:ets ord: allt som behövs för att kontrollera svaret finns i svaret och i keys.json.
- Dokument: SHA-256 för din fil är lika med
leaf.sealed_document_sha256(elleroriginal_document_sha256). - Löv:
SHA-256(0x00 ‖ canonical JSON of leaf)är lika medleaf_hash. - Inkludering: om du går igenom
proof.hashesfrån lövets hash (RFC 9162 §2.1.3.2, medleaf_indexochtree_size == anchor.leaf_count) får duanchor.root. - Nyckel:
anchor.key_idfinns ikeys.json, nyckel-id:t är de första 16 hexadecimala tecknen i SHA-256 av den råa publika nyckeln, ochanchor.created_atligger inom nyckelns giltighetsperiod. - Signatur: Ed25519-signaturen verifieras över
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 bara strängvärden med ASCII-nycklar, så json.dumps(…, sort_keys=True, separators=(",", ":")) ger
samma kanoniska JSON. Kontrollera också nyckelns valid_from / valid_until mot anchor["created_at"].
Referensverifieraren, @bona-fide/verify, är isomorf (webbläsare och Node, inga hemligheter från Bona Fide) och är
det som sidan /verify kör:
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);}Paketet har också ett kommandoradsverktyg (bona-fide-verify <file.pdf | sha256> [--api URL] [--json]; från en
utcheckning av Bona Fides kodförråd: pnpm --filter @bona-fide/verify cli signed.pdf). Det beräknar filens hash
lokalt, kontrollerar alla steg ovan och avslutar med 0 när filen är verifierad, 1 när den inte hittas eller är
ogiltig och 2 när dokumentet inte har förankrats än.
Kontrollera ett ankares signatur med PEM-nyckeln från 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 SuccessfullyAnkare listas med det senaste först med GET /v1/public/anchors?limit=20&before=<sequence> (skicka next_before
för att bläddra bakåt) och hämtas ett i taget med GET /v1/public/anchors/{sequence}.