Chuyển tới nội dung chính

Kiểm chứng bằng chứng

Trang này chỉ cách kiểm chứng một kết quả phân giải tên mà không cần tin trình lập chỉ mục đã trả về nó. Mọi ví dụ mã ở đây đều được chạy với một trình lập chỉ mục thật trước khi xuất bản.

Việc kiểm chứng mang lại điều gì

Một kết quả phân giải trả lời «richard.pivx trỏ tới địa chỉ nào?». Kiểm chứng nó chứng minh rằng câu trả lời đúng là câu đã được cam kết vào gốc trạng thái — và rằng gốc đó là một gốc mà hợp đồng neo đã chấp nhận. Một trình lập chỉ mục bịa ra địa chỉ, trả về bản ghi cũ hay giấu một cái tên đều không thể tạo ra bằng chứng vượt qua những kiểm tra này.

Có hai bước độc lập, và bạn cần cả hai:

  1. Bằng chứng Merkle buộc bản ghi vào một gốc trạng thái.
  2. Việc kiểm tra trên chuỗi buộc gốc đó vào hợp đồng neo.

Riêng bước 1 thì vô giá trị: một trình lập chỉ mục xấu có thể dựng nguyên một cây giả và trả về các bằng chứng nhất quán với gốc giả của chính nó. Chính bước 2 mới làm cho gốc có ý nghĩa.


Bước 1 — kiểm chứng bằng chứng Merkle

Cây

PiNS dùng một cây Merkle thưa nén trên không gian khóa 128 bit. Một khóa là 16 byte đầu của SHA-256(domain_name). Khi viết bộ kiểm chứng, có hai tính chất quan trọng:

  • Bằng chứng ngắn và có độ dài thay đổi. Một lá nằm ở độ sâu nông nhất mà tiền tố khóa của nó là duy nhất, nên một bằng chứng mang proof_depth anh em — thường chỉ vài cái, không phải 128. Đừng bao giờ giả định độ dài cố định; hãy đọc proof_depth và kiểm tra rằng nó khớp với merkle_proof.length.
  • Một cây con rỗng là 32 byte 0 ở mọi độ cao. Không có bậc thang băm tiền tính cho các nút rỗng. Vì thế gốc của một cây rỗng toàn số 0.

Các hàm băm

Lá và nút trong được gắn thẻ để miền băm của chúng không chồng lấn. Bỏ sót byte thẻ là lỗi phổ biến nhất — nó tạo ra một băm trông hợp lý nhưng không bao giờ khớp với gốc.

leaf = SHA-256( 0x00 ‖ domain ‖ owner_pubkey ‖ target_address ‖ price_le64 ‖ nonce_le64 )
node = SHA-256( 0x01 ‖ left ‖ right )
key = SHA-256( domain )[0..16]

domaintarget_address là byte UTF-8 không có tiền tố độ dài; owner_pubkey là 32 byte thô; pricenonce là số nguyên 64 bit không dấu dạng little-endian.

Cách duyệt

Đi từ lá ngược về gốc. Với n = proof_depth anh em, ở bước i bit quyết định bạn đang ở phía nào là bit n - 1 - i của khóa, đếm từ bit có trọng số lớn nhất của byte 0:

h = leaf
for i in 0..n:
h = key_bit(key, n-1-i) ? node(sibling[i], h) : node(h, sibling[i])

merkle_proof[0] là anh em ở mức sâu nhất, gần lá nhất.

Terminal

Mọi bằng chứng đều mang một proof_terminal mô tả thứ nằm ở cuối đường đi:

TerminalÝ nghĩa
Occupiedở đó là lá của chính khóa — tên tồn tại
Vacantở đó là một cây con rỗng — tên chưa được đăng ký
Blockedở đó là lá của một tên khác — tên chưa được đăng ký, và phản hồi kèm bản ghi đầy đủ của tên kia

Một kết quả phân giải thành công phải là Occupied. VacantBlocked là bằng chứng vắng mặt: chúng cho phép bạn kiểm chứng rằng một cái tên thực sự chưa được đăng ký, thay vì tin lời «không tìm thấy» của trình lập chỉ mục. Hãy từ chối mọi kết quả phân giải có terminal khác Occupied.

import hashlib
import struct

LEAF_TAG, NODE_TAG = b"\x00", b"\x01"
KEY_LEN, MAX_DEPTH = 16, 128


def hash_leaf(domain, owner_pubkey_hex, target_address, price, nonce):
h = hashlib.sha256()
h.update(LEAF_TAG)
h.update(domain.encode())
h.update(bytes.fromhex(owner_pubkey_hex))
h.update(target_address.encode())
h.update(struct.pack("<Q", price))
h.update(struct.pack("<Q", nonce))
return h.digest()


def hash_node(left, right):
h = hashlib.sha256()
h.update(NODE_TAG)
h.update(left)
h.update(right)
return h.digest()


def key_of(domain):
return hashlib.sha256(domain.encode()).digest()[:KEY_LEN]


def key_bit(key, i):
return (key[i // 8] >> (7 - (i % 8))) & 1


def fold(key, start, siblings):
h, n = start, len(siblings)
for i, sib in enumerate(siblings):
h = hash_node(sib, h) if key_bit(key, n - 1 - i) else hash_node(h, sib)
return h


def verify_resolution(entry, expected_root):
if entry["proof_terminal"] != "Occupied":
raise ValueError("a resolution must carry an Occupied terminal")

siblings = [bytes.fromhex(s) for s in entry["merkle_proof"]]
if len(siblings) != entry["proof_depth"]:
raise ValueError("proof_depth does not match the sibling count")
if entry["proof_depth"] > MAX_DEPTH:
raise ValueError("proof depth exceeds MAX_DEPTH")

leaf = hash_leaf(
entry["domain_name"],
entry["owner_pubkey"],
entry["target_address"],
int(entry["price"]),
int(entry["nonce"]),
)
root = fold(key_of(entry["domain_name"]), leaf, siblings)
return root.hex() == expected_root.lower()
mẹo

Hãy thử bộ kiểm chứng của bạn trước hành vi sửa đổi

Một bộ kiểm chứng luôn trả về true sẽ vượt qua mọi bài kiểm tra dương tính. Trước khi tin bộ của mình, hãy đổi một byte của target_address, owner_pubkey hoặc price trong một phản hồi thật và xác nhận rằng giờ nó trả về false. Cả ba ví dụ ở trên đều được kiểm theo cách này.


Bước 2 — đối chiếu gốc với hợp đồng neo

Một bằng chứng Merkle đã kiểm chứng chỉ nói «bản ghi này nằm trong một cây nào đó». Để biết nó nằm trong cây thật, hãy hỏi hợp đồng neo trên BNB Smart Chain xem nó có từng chấp nhận gốc đó không.

Dùng isRootValid(bytes32) — bộ chọn 0x30ef41b4. Nó trả về một giá trị boolean mã hóa ABI duy nhất, dễ phân tích đúng hơn nhiều so với cấu trúc rootHistory.

Lời gọiBộ chọnTrả về
isRootValid(bytes32)0x30ef41b4bool — gốc này đã từng được chấp nhận chưa
currentRoot()0xfdab463dbytes32 — gốc được chấp nhận gần nhất
verifyRootValidity(bytes32)0xc7179944(bool isValid, uint32 blockHeight)
currentBlockHeight()0x367bf2f9uint32 — chiều cao PIVX của gốc gần nhất
programVkey()0x09665ee7bytes32 — mạch mà hợp đồng bắt buộc
import requests


def is_root_valid(rpc_url, contract_address, smt_root):
"""Returns True if the anchor contract has ever accepted this root."""
clean = smt_root.replace("0x", "").lower().rjust(64, "0")
payload = {
"jsonrpc": "2.0",
"method": "eth_call",
"params": [{"to": contract_address, "data": f"0x30ef41b4{clean}"}, "latest"],
"id": 1,
}
r = requests.post(rpc_url, json=payload, timeout=15).json()
if "error" in r:
raise RuntimeError(f"EVM RPC error: {r['error']['message']}")

result = r.get("result", "0x")
# A bool is ABI-encoded as a full 32-byte word: 0x00..01 for true.
return int(result, 16) == 1 if result not in ("", "0x") else False

Diễn giải câu trả lời

isRootValidÝ nghĩaNên làm gì
true, và gốc bằng currentRoot()trình lập chỉ mục đã đồng bộ hoàn toànchấp nhận
true, nhưng gốc cũ hơn currentRoot()trình lập chỉ mục đang tụt sau chuỗichấp nhận, có thể cảnh báo — bản ghi hợp lệ tại thời điểm gốc đó
falsegốc này chưa bao giờ được chấp nhận trên chuỗitừ chối kết quả phân giải

Giá trị false nghĩa là cái cây mà bạn đối chiếu không tồn tại dưới góc nhìn của giao thức. Đó chính là tình huống mà một trình lập chỉ mục do kẻ tấn công kiểm soát tạo ra, và bước này sinh ra đúng vì lý do đó.


Kiểm chứng chính cái mạch

Hai bước ở trên chứng minh rằng một bản ghi thuộc về một gốc mà hợp đồng đã chấp nhận. Hợp đồng chỉ chấp nhận một gốc nếu đi kèm là một bằng chứng ZK hợp lệ — và bằng chứng đó được kiểm với programVkey, một cam kết 32 byte với đúng cái mạch đã biên dịch.

Bạn có thể xác nhận khóa đó tương ứng với mã nguồn đã công bố:

git clone https://github.com/PIVX-Name/pivx-name-prover
cd pivx-name-prover/program
cargo prove build # plain build = mainnet

So sánh khóa xác minh thu được với programVkey() (bộ chọn 0x09665ee7) trên hợp đồng neo. Nếu chúng khớp nhau, hợp đồng đang bắt buộc đúng mã nguồn đó. Đổi một dòng duy nhất của mạch sẽ cho ra khóa khác, nên không thể tráo một mạch đã sửa đổi mà thay đổi đó không lộ ra trên chuỗi.

cảnh báo

Hãy biên dịch không kèm tính năng bổ sung

Một lệnh cargo prove build thuần túy tạo ra mạch mainnet mà hợp đồng đã triển khai bắt buộc. Bất kỳ cờ tính năng nào cũng thay đổi chương trình đã biên dịch, và do đó thay đổi khóa xác minh của nó, khiến nó không còn khớp nữa.

Kiểm chứng trực tiếp một bằng chứng SP1

Các bằng chứng là bằng chứng SP1 Groth16 tiêu chuẩn và có thể kiểm bằng SDK của SP1:

use sp1_sdk::{ProverClient, SP1ProofWithPublicValues};

let client = ProverClient::from_env();
let (_, vk) = client.setup(ELF);
let proof = SP1ProofWithPublicValues::load("proof.bin")?;
client.verify(&proof, &vk)?;

Các giá trị công khai là mã hóa ABI của (bytes32 old_root, bytes32 new_root, uint32 end_block_height) — phép chuyển trạng thái mà lô đã thực hiện, và chiều cao khối PIVX mà nó bao phủ.