सत्यापन
हर पूरे हुए लिफ़ाफ़े की सुरक्षा दो स्वतंत्र प्रमाण करते हैं:
- सील, PDF के अंदर: किसी सार्वजनिक टाइमस्टैम्प अथॉरिटी से मिला PAdES दस्तावेज़ टाइमस्टैम्प। Adobe Acrobat और Reader इसकी जाँच करते हैं और दिखाते हैं कि सील होने के बाद से दस्तावेज़ में कोई बदलाव नहीं हुआ है।
- एंकर, PDF के बाहर: दस्तावेज़ का फ़िंगरप्रिंट एक सिर्फ़-जोड़ने-योग्य Merkle ट्री में एक लीफ़ होता है, और Bona Fide हर ट्री रूट पर Ed25519 से हस्ताक्षर करता है। कोई भी प्रकाशित सार्वजनिक कुंजियों से इस प्रमाण की जाँच कर सकता है; न अकाउंट की ज़रूरत है, न किसी PDF व्यूअर पर भरोसा करने की।
सीलिंग
Section titled “सीलिंग”जब आख़िरी प्राप्तकर्ता हस्ताक्षर करता है, तो लिफ़ाफ़ा पूरा होता है (envelope.completed) और सीलर काम सँभाल लेता है:
- मूल दस्तावेज़ के SHA-256 को उस SHA-256 से मिलाता है, जो लिफ़ाफ़ा बनाते समय दर्ज किया गया था।
- दस्तावेज़ को फ़्लैट करता है: फ़ील्ड के मान (टेक्स्ट, चेकबॉक्स, बनाए गए, टाइप किए गए या अपलोड किए गए हस्ताक्षर) पेज के कंटेंट में बना दिए जाते हैं, और हर फ़ॉर्म फ़ील्ड, एनोटेशन, JavaScript ऐक्शन और एम्बेड की गई फ़ाइल हटा दी जाती है।
- पूर्णता प्रमाणपत्र जोड़ता है: लिफ़ाफ़ा id, मूल दस्तावेज़ का SHA-256, हर हस्ताक्षरकर्ता और उसका वास्तव में इस्तेमाल हुआ सत्यापन तरीका (उदाहरण के लिए ईमेल लिंक (… पर भेजा गया यूनिक लिंक) या ईमेल लिंक + वन-टाइम कोड, हस्ताक्षरकर्ता सत्यापन देखें), टाइमस्टैम्प, IP पता और डिवाइस, और इवेंट की टाइमलाइन। हर पेज पर यह फ़ुटर लगता है: Bona Fide Envelope ID … · Verify at bonafidesign.com/verify।
- किसी सार्वजनिक टाइमस्टैम्प अथॉरिटी (स्वचालित फ़ेलओवर वाले कई सार्वजनिक रूप से विश्वसनीय प्रोवाइडर) से PAdES दस्तावेज़ टाइमस्टैम्प
(
/DocTimeStamp,ETSI.RFC3161, SHA-256) जोड़ता है, और TSA की प्रमाणपत्र चेन का वैलिडेशन डेटा (DSS में OCSP/CRL) एम्बेड करता है, ताकि यह लंबे समय तक सत्यापित किया जा सके (LTV)। - अपने आउटपुट को ख़ुद वैलिडेट करता है, उसे संग्रहीत करता है, और सील किया गया SHA-256 (
sealedDocumentHash) तथा एंकर लॉग के लिए एक लीफ़ दर्ज करता है।
फिर envelope.sealed फ़ायर होता है और GET /v1/envelopes/{id}/document?version=sealed फ़ाइल लौटाता है। अगर सभी TSA
अनुपलब्ध हों, तो सीलिंग फिर से की जाती है; तब तक लिफ़ाफ़ा seal: "pending" के साथ completed रहता है।
एंकरिंग
Section titled “एंकरिंग”हर सील किया गया लिफ़ाफ़ा एक लीफ़ जोड़ता है, जो इसका कैनॉनिकल JSON (कुंजियाँ क्रमबद्ध, कोई व्हाइटस्पेस नहीं) होता है:
{ "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>"}हर घंटे, पिछले एंकर के बाद दर्ज हुए लीफ़ एक Merkle ट्री बनाते हैं (RFC 6962 / RFC 9162:
leaf = SHA-256(0x00 ‖ leaf JSON), node = SHA-256(0x01 ‖ left ‖ right)), और उसका रूट एक हस्ताक्षरित
एंकर के रूप में प्रकाशित होता है:
{ "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 हस्ताक्षर bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8) पर होता है।
previous_root हर एंकर को उससे पहले वाले एंकर से जोड़ता है (पहला एंकर 64 शून्यों की ओर इशारा करता है), इसलिए चेन तोड़े बिना कोई एंकर
हटाया या उसका क्रम बदला नहीं जा सकता, और एंकर टेबल सिर्फ़-जोड़ने-योग्य हैं।
हस्ताक्षर कुंजियाँ उनकी वैधता अवधि के साथ प्रकाशित की जाती हैं; सेवामुक्त कुंजियाँ सूची में बनी रहती हैं, ताकि पुराने एंकर सत्यापित किए जा सकें:
{ "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" } ]}किसी दस्तावेज़ को सत्यापित करना
Section titled “किसी दस्तावेज़ को सत्यापित करना”सबसे तेज़ तरीका सार्वजनिक पेज है: bonafidesign.com/verify। यह PDF का हैश आपके ब्राउज़र में निकालता है (फ़ाइल कभी अपलोड नहीं होती), हैश को खोजता है, और प्रमाण तथा हस्ताक्षर की जाँच आपके डिवाइस पर करता है। एंकर लॉग में हर एंकर सूचीबद्ध है।
यही खोज एक सार्वजनिक API भी है: कोई कुंजी नहीं, कोई भी ओरिजिन, प्रति IP रेट लिमिट। सिर्फ़ 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 होता है verified, pending_anchor (पूरा और दर्ज हो चुका, अगले घंटे वाले एंकर का इंतज़ार) या
not_found: किसी भी पूरे हुए लिफ़ाफ़े में ठीक यही फ़ाइल नहीं है, इसलिए यह वह दस्तावेज़ नहीं है जिस पर हस्ताक्षर हुए थे (या तब से इसमें
बदलाव हुआ है)। सील की गई फ़ाइल और भेजा गया मूल दस्तावेज़, दोनों मेल खाते हैं।
प्रमाण की जाँच ख़ुद करना
Section titled “प्रमाण की जाँच ख़ुद करना”API की बात पर आँख मूँदकर भरोसा न करें: जवाब की जाँच के लिए ज़रूरी सब कुछ रिस्पॉन्स में और keys.json में मौजूद है।
- दस्तावेज़: आपकी फ़ाइल का SHA-256,
leaf.sealed_document_sha256(याoriginal_document_sha256) के बराबर है। - लीफ़:
SHA-256(0x00 ‖ canonical JSON of leaf),leaf_hashके बराबर है। - समावेशन: लीफ़ हैश से
proof.hashesपर चलने पर (RFC 9162 §2.1.3.2,leaf_indexऔरtree_size == anchor.leaf_countके साथ)anchor.rootमिलता है। - कुंजी:
anchor.key_id,keys.jsonमें है, कुंजी id रॉ सार्वजनिक कुंजी के SHA-256 के पहले 16 हेक्स अक्षर हैं, औरanchor.created_atकुंजी की वैधता अवधि के भीतर है। - हस्ताक्षर: Ed25519 हस्ताक्षर
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']}")लीफ़ में ASCII कुंजियों के साथ सिर्फ़ स्ट्रिंग मान होते हैं, इसलिए json.dumps(…, sort_keys=True, separators=(",", ":")) से
वही कैनॉनिकल JSON मिलता है। कुंजी के valid_from / valid_until को भी anchor["created_at"] से मिलाकर जाँचें।
रेफ़रेंस वेरिफ़ायर, @bona-fide/verify, आइसोमॉर्फ़िक है (ब्राउज़र और Node, कोई Bona Fide सीक्रेट नहीं) और यही
/verify पेज पर चलता है:
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);}पैकेज में एक कमांड-लाइन चेकर भी है (bona-fide-verify <file.pdf | sha256> [--api URL] [--json]; Bona Fide रिपॉज़िटरी के
चेकआउट से: pnpm --filter @bona-fide/verify cli signed.pdf)। यह फ़ाइल का हैश लोकल रूप से निकालता है,
ऊपर के हर चरण की जाँच करता है, और सत्यापित होने पर 0, न मिलने या अमान्य होने पर 1, और दस्तावेज़ के
अभी एंकर न होने पर 2 के साथ बाहर निकलता है।
keys.json की PEM कुंजी से किसी एंकर के हस्ताक्षर की जाँच करें (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एंकर सबसे नए पहले के क्रम में GET /v1/public/anchors?limit=20&before=<sequence> से सूचीबद्ध होते हैं (पीछे के पेज देखने के लिए
next_before भेजें) और एक-एक करके GET /v1/public/anchors/{sequence} से प्राप्त किए जाते हैं।