Bỏ qua để đến nội dung

Xác minh

Hai bằng chứng độc lập bảo vệ mọi phong bì đã hoàn tất:

  1. Niêm phong, bên trong PDF: một dấu thời gian tài liệu PAdES từ một tổ chức cấp dấu thời gian công khai. Adobe Acrobat và Reader kiểm tra dấu này và cho thấy tài liệu chưa bị sửa đổi kể từ khi được niêm phong.
  2. Neo, bên ngoài PDF: dấu vân tay của tài liệu là một lá trong cây Merkle chỉ ghi thêm, và Bona Fide ký mỗi gốc của cây bằng Ed25519. Bất kỳ ai cũng có thể kiểm tra bằng chứng đó với các khóa công khai đã công bố, không cần tài khoản và không cần tin tưởng vào trình xem PDF nào.

Khi người nhận cuối cùng ký, phong bì hoàn tất (envelope.completed) và bộ niêm phong tiếp quản:

  1. Đối chiếu SHA-256 của bản gốc với giá trị đã ghi lại khi phong bì được tạo.
  2. Làm phẳng tài liệu: giá trị của các trường (văn bản, hộp kiểm, chữ ký được vẽ, gõ hoặc tải lên) được vẽ vào nội dung trang, và mọi trường biểu mẫu, chú thích, hành động JavaScript và tệp nhúng đều bị xóa.
  3. Nối thêm chứng nhận hoàn tất: id phong bì, SHA-256 của bản gốc, mọi người ký cùng phương thức xác minh thực sự đã dùng (ví dụ Liên kết email (liên kết duy nhất gửi đến …) hoặc Liên kết email + mã dùng một lần, xem xác minh người ký), dấu thời gian, địa chỉ IP và thiết bị, cùng dòng thời gian sự kiện. Mọi trang đều có chân trang Bona Fide Envelope ID … · Verify at bonafidesign.com/verify.
  4. Thêm dấu thời gian tài liệu PAdES (/DocTimeStamp, ETSI.RFC3161, SHA-256) từ một tổ chức cấp dấu thời gian công khai (nhiều nhà cung cấp được tin cậy công khai, tự động chuyển đổi dự phòng), và nhúng dữ liệu xác thực của chuỗi chứng chỉ của TSA (OCSP/CRL trong DSS) để có thể xác minh lâu dài (LTV).
  5. Tự kiểm tra kết quả đầu ra, lưu trữ, và ghi lại SHA-256 đã niêm phong (sealedDocumentHash) cùng một lá cho nhật ký neo.

Sau đó envelope.sealed được phát và GET /v1/envelopes/{id}/document?version=sealed trả về tệp. Nếu mọi TSA đều không khả dụng, việc niêm phong sẽ được thử lại; trong lúc đó phong bì vẫn ở trạng thái completed với seal: "pending".

Mỗi phong bì đã niêm phong đóng góp một lá, là JSON chuẩn tắc (khóa được sắp xếp, không có khoảng trắng) của:

{
"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>"
}

Mỗi giờ, các lá được ghi lại kể từ neo trước trở thành một cây Merkle (RFC 6962 / RFC 9162: leaf = SHA-256(0x00 ‖ leaf JSON), node = SHA-256(0x01 ‖ left ‖ right)), và gốc của cây được công bố dưới dạng một neo có chữ ký:

GET /v1/public/anchors/latest
{
"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"
}

Chữ ký Ed25519 bao trùm bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count (UTF-8). previous_root liên kết mỗi neo với neo trước nó (neo đầu tiên trỏ đến 64 số không), nên không thể xóa hay sắp xếp lại một neo mà không phá vỡ chuỗi, và các bảng neo chỉ cho phép ghi thêm.

Các khóa ký được công bố cùng thời hạn hiệu lực; các khóa đã ngừng dùng vẫn được liệt kê để các neo cũ vẫn xác minh được:

GET /.well-known/bona-fide/keys.json
{
"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"
}
]
}

Cách nhanh nhất là trang công khai: bonafidesign.com/verify. Trang này tính giá trị băm của tệp PDF trong trình duyệt của bạn (tệp không bao giờ được tải lên), tra cứu giá trị băm, và kiểm tra bằng chứng cùng chữ ký ngay trên thiết bị của bạn. Nhật ký neo liệt kê mọi neo.

Phép tra cứu tương tự cũng là một API công khai: không cần khóa, mọi origin, giới hạn tần suất theo IP. Chỉ cần gửi SHA-256:

Terminal window
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 là verified, pending_anchor (đã hoàn tất và được ghi lại, đang chờ neo hằng giờ tiếp theo) hoặc not_found: không có phong bì đã hoàn tất nào chứa đúng tệp này, nên đây không phải tài liệu đã được ký (hoặc tệp đã bị sửa đổi kể từ đó). Cả tệp đã niêm phong lẫn bản gốc như khi gửi đều khớp được.

Đừng chỉ tin vào lời của API: mọi thứ cần thiết để kiểm tra câu trả lời đều có trong phản hồi và trong keys.json.

  1. Tài liệu: SHA-256 của tệp của bạn bằng leaf.sealed_document_sha256 (hoặc original_document_sha256).
  2. Lá: SHA-256(0x00 ‖ canonical JSON of leaf) bằng leaf_hash.
  3. Bao hàm: đi qua proof.hashes bắt đầu từ giá trị băm của lá (RFC 9162 §2.1.3.2, với leaf_index và tree_size == anchor.leaf_count) cho ra anchor.root.
  4. Khóa: anchor.key_id có trong keys.json, id khóa là 16 ký tự thập lục phân đầu tiên của SHA-256 của khóa công khai thô, và anchor.created_at nằm trong thời hạn hiệu lực của khóa.
  5. Chữ ký: chữ ký Ed25519 hợp lệ trên bona-fide-anchor-v1|sequence|root|previous_root|created_at|leaf_count.
# pip install requests cryptography
import base64, hashlib, json, sys, requests
from 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á chỉ chứa các giá trị chuỗi với khóa ASCII, nên json.dumps(…, sort_keys=True, separators=(",", ":")) cho ra cùng một JSON chuẩn tắc. Hãy kiểm tra thêm valid_from / valid_until của khóa so với anchor["created_at"].

Các neo được liệt kê từ mới đến cũ bằng GET /v1/public/anchors?limit=20&before=<sequence> (truyền next_before để xem trang trước) và được lấy từng cái bằng GET /v1/public/anchors/{sequence}.