Doğrulama
Tamamlanan her zarfı birbirinden bağımsız iki kanıt korur:
- PDF’in içindeki mühür: herkese açık bir zaman damgası otoritesinden alınan PAdES belge zaman damgası. Adobe Acrobat ve Reader bunu kontrol eder ve belgeyi mühürlendiğinden beri değiştirilmemiş olarak gösterir.
- PDF’in dışındaki çapa: belgenin parmak izi, yalnızca ekleme yapılabilen bir Merkle ağacında bir yapraktır ve Bona Fide her ağaç kökünü Ed25519 ile imzalar. Herkes bu kanıtı yayımlanan açık anahtarlarla kontrol edebilir; hesap gerekmez ve bir PDF görüntüleyicisine güvenmek gerekmez.
Mühürleme
Bölüm başlığı “Mühürleme”Son alıcı imzaladığında zarf tamamlanır (envelope.completed) ve mühürleyici devreye girer:
- Orijinalin SHA-256 değerini, zarf oluşturulurken kaydedilen değerle karşılaştırır.
- Belgeyi düzleştirir: alan değerleri (metin, onay kutuları, çizilen, yazılan veya yüklenen imzalar) sayfa içeriğine çizilir ve her form alanı, ek açıklama, JavaScript eylemi ve gömülü dosya kaldırılır.
- Tamamlama sertifikasını ekler: zarf kimliği, orijinalin SHA-256 değeri, gerçekte kullanılan doğrulama yöntemiyle birlikte her imzacı (örneğin E-posta bağlantısı (… adresine gönderilen benzersiz bağlantı) veya E-posta bağlantısı + tek kullanımlık kod; bkz. imzacı doğrulaması), zaman damgaları, IP adresi ve cihaz ile olay zaman çizelgesi. Her sayfaya şu alt bilgi eklenir: Bona Fide Envelope ID … · Verify at bonafidesign.com/verify.
- Herkese açık bir zaman damgası otoritesinden (otomatik yedeğe geçişli, herkesçe güvenilen birden fazla sağlayıcı)
bir PAdES belge zaman damgası (
/DocTimeStamp,ETSI.RFC3161, SHA-256) ekler ve TSA’nın sertifika zincirinin doğrulama verilerini (DSS içinde OCSP/CRL) gömer; böylece belge uzun vadede doğrulanabilir kalır (LTV). - Kendi çıktısını doğrular, saklar ve mühürlü SHA-256 değerini (
sealedDocumentHash) ve çapa kaydı için bir yaprağı kaydeder.
Ardından envelope.sealed tetiklenir ve GET /v1/envelopes/{id}/document?version=sealed dosyayı döndürür. Hiçbir
TSA kullanılamıyorsa mühürleme yeniden denenir; bu sırada zarf seal: "pending" ile completed durumunda kalır.
Sabitleme
Bölüm başlığı “Sabitleme”Mühürlenen her zarf, aşağıdakilerin kanonik JSON’u (anahtarlar sıralı, boşluksuz) olan bir yaprak ekler:
{ "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>"}Her saat, bir önceki çapadan bu yana kaydedilen yapraklar bir Merkle ağacına dönüşür (RFC 6962 / RFC 9162:
leaf = SHA-256(0x00 ‖ leaf JSON), node = SHA-256(0x01 ‖ left ‖ right)) ve kökü imzalı bir çapa olarak
yayımlanır:
{ "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 imzası bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8) değerini kapsar.
previous_root her çapayı bir öncekine zincirler (ilki 64 sıfırı gösterir); böylece bir çapa zincir bozulmadan
kaldırılamaz veya yeniden sıralanamaz ve çapa tabloları yalnızca ekleme yapılabilir.
İmzalama anahtarları geçerlilik dönemleriyle birlikte yayımlanır; eski çapaların doğrulanabilir kalması için kullanımdan kaldırılan anahtarlar listede kalır:
{ "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" } ]}Bir belgeyi doğrulama
Bölüm başlığı “Bir belgeyi doğrulama”En hızlı yol herkese açık sayfadır: bonafidesign.com/verify. Sayfa PDF’in hash’ini tarayıcınızda hesaplar (dosya hiçbir zaman yüklenmez), hash’i sorgular ve kanıtı ile imzayı cihazınızda kontrol eder. Çapa kaydı her çapayı listeler.
Aynı sorgu herkese açık bir API olarak da sunulur: anahtar gerekmez, her kaynaktan çağrılabilir, IP başına hız limitine tabidir. Yalnızca SHA-256 değerini gönderin:
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 değeri verified, pending_anchor (tamamlandı ve kaydedildi, saatlik bir sonraki çapa bekleniyor) veya
not_found olur: hiçbir tamamlanmış zarf bu dosyanın ta kendisine sahip değildir; yani bu dosya imzalanan belge
değildir (veya o zamandan beri değiştirilmiştir). Hem mühürlü dosya hem de gönderildiği hâliyle orijinal eşleşir.
Kanıtı kendiniz kontrol etme
Bölüm başlığı “Kanıtı kendiniz kontrol etme”API’nin sözüne güvenmek zorunda değilsiniz: yanıtı kontrol etmek için gereken her şey yanıtta ve keys.json
içindedir.
- Belge: dosyanızın SHA-256 değeri
leaf.sealed_document_sha256(veyaoriginal_document_sha256) değerine eşittir. - Yaprak:
SHA-256(0x00 ‖ canonical JSON of leaf)değerileaf_hashdeğerine eşittir. - Dahil olma: yaprak hash’inden başlayarak
proof.hashesüzerinde ilerlemek (RFC 9162 §2.1.3.2,leaf_indexvetree_size == anchor.leaf_countile)anchor.rootdeğerini verir. - Anahtar:
anchor.key_idkeys.jsoniçindedir, anahtar kimliği ham açık anahtarın SHA-256 değerinin ilk 16 onaltılık karakteridir veanchor.created_atanahtarın geçerlilik dönemi içindedir. - İmza: Ed25519 imzası
bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_countüzerinde doğrulanır.
# 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']}")Yaprakta yalnızca ASCII anahtarlı dize değerleri bulunur; bu nedenle json.dumps(…, sort_keys=True, separators=(",", ":"))
aynı kanonik JSON’u verir. Anahtarın valid_from / valid_until değerlerini de anchor["created_at"] ile
karşılaştırın.
Referans doğrulayıcı @bona-fide/verify izomorfiktir (tarayıcı ve Node, Bona Fide sırrı gerekmez) ve /verify
sayfasının çalıştırdığı da odur:
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);}Paket ayrıca bir komut satırı denetleyicisi içerir (bona-fide-verify <file.pdf | sha256> [--api URL] [--json];
Bona Fide deposunun bir kopyasından: pnpm --filter @bona-fide/verify cli signed.pdf). Dosyanın hash’ini yerel
olarak hesaplar, yukarıdaki her adımı kontrol eder ve doğrulandığında 0, bulunamadığında veya geçersiz olduğunda
1, belge henüz sabitlenmediğinde 2 çıkış koduyla sonlanır.
Bir çapanın imzasını keys.json içindeki PEM anahtarıyla kontrol edin (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 SuccessfullyÇapalar GET /v1/public/anchors?limit=20&before=<sequence> ile en yeniden eskiye doğru listelenir (geriye doğru
sayfalamak için next_before iletin) ve GET /v1/public/anchors/{sequence} ile tek tek getirilir.