যাচাই
দুটি স্বাধীন প্রমাণ প্রতিটি সম্পন্ন খামকে সুরক্ষিত রাখে:
- সিল, PDF-এর ভেতরে: একটি পাবলিক টাইমস্ট্যাম্প অথরিটি থেকে পাওয়া PAdES ডকুমেন্ট টাইমস্ট্যাম্প। Adobe Acrobat ও Reader এটি যাচাই করে এবং দেখায় যে সিল হওয়ার পর থেকে নথিটি অপরিবর্তিত।
- অ্যাঙ্কর, PDF-এর বাইরে: নথির ফিঙ্গারপ্রিন্ট একটি শুধু-যোগযোগ্য Merkle ট্রি-র একটি লিফ, এবং Bona Fide প্রতিটি ট্রি রুট Ed25519 দিয়ে স্বাক্ষর করে। যে কেউ প্রকাশিত পাবলিক কী দিয়ে সেই প্রমাণ যাচাই করতে পারেন; কোনো অ্যাকাউন্ট বা কোনো PDF ভিউয়ারের ওপর আস্থার প্রয়োজন নেই।
সিল করা
“সিল করা” শিরোনামের অংশশেষ প্রাপক স্বাক্ষর করলে খামটি সম্পন্ন হয় (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 থাকে।
অ্যাঙ্কর করা
“অ্যাঙ্কর করা” শিরোনামের অংশপ্রতিটি সিল করা খাম একটি লিফ যোগ করে, যা নিচেরটির ক্যানোনিকাল 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" } ]}একটি নথি যাচাই করা
“একটি নথি যাচাই করা” শিরোনামের অংশসবচেয়ে দ্রুত উপায় হলো পাবলিক পৃষ্ঠা: 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: কোনো সম্পন্ন খামে হুবহু এই ফাইলটি নেই, তাই এটি সেই নথি নয় যাতে স্বাক্ষর হয়েছিল (অথবা এর পর থেকে এটি
পরিবর্তিত হয়েছে)। সিল করা ফাইল এবং পাঠানো মূল নথি, দুটিই মেলে।
নিজেই প্রমাণ যাচাই করা
“নিজেই প্রমাণ যাচাই করা” শিরোনামের অংশ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_idkeys.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} দিয়ে একটি একটি করে আনা হয়।