Ana içeriğe geç

İspatları doğrulama

Bu sayfa, bir isim çözümlemesini onu sunan indeksleyiciye güvenmeden nasıl doğrulayacağınızı gösterir. Buradaki her kod örneği, yayımlanmadan önce çalışan bir indeksleyiciye karşı çalıştırılır.

Doğrulama size ne kazandırır

Bir çözümleme şu soruyu yanıtlar: "richard.pivx hangi adrese işaret ediyor?" Onu doğrulamak, yanıtın durum köküne taahhüt edilmiş yanıt olduğunu ve o kökün çıpa sözleşmesinin kabul ettiği bir kök olduğunu kanıtlar. Adres uyduran, eskimiş bir kayıt sunan ya da bir ismi gizleyen bir indeksleyici, bu denetimleri geçen bir ispat üretemez.

Birbirinden bağımsız iki adım vardır ve ikisine de ihtiyacınız olur:

  1. Merkle ispatı, kaydı bir durum köküne bağlar.
  2. Zincir üzerindeki denetim, o kökü çıpa sözleşmesine bağlar.

Tek başına 1. adım işe yaramaz: kötü niyetli bir indeksleyici, baştan sona sahte bir ağaç kurup kendi sahte köküyle tutarlı ispatlar sunabilir. Köke anlam kazandıran 2. adımdır.


1. adım — Merkle ispatını doğrulayın

Ağaç

PiNS, 128 bitlik bir anahtar uzayı üzerinde kompakt seyrek Merkle ağacı kullanır. Bir anahtar, SHA-256(domain_name) değerinin ilk 16 baytıdır. Doğrulayıcı yazarken iki özellik önemlidir:

  • İspatlar kısadır ve uzunlukları değişkendir. Bir yaprak, anahtar önekinin benzersiz olduğu en sığ derinlikte durur; dolayısıyla bir ispat proof_depth kadar kardeş taşır — genellikle bir avuç, 128 değil. Asla sabit bir uzunluk varsaymayın; proof_depth değerini okuyun ve merkle_proof.length ile eşleştiğini doğrulayın.
  • Boş bir alt ağaç her yükseklikte 32 sıfır bayttır. Boş düğümler için önceden hesaplanmış hash merdiveni yoktur. Bu yüzden boş bir ağacın kökü tamamen sıfırdır.

Hash fonksiyonları

Yapraklar ve iç düğümler, hash alanları çakışmasın diye etiketlenir. Etiket baytını atlamak, açık ara en yaygın hatadır — makul görünen ama kökle asla eşleşmeyen bir hash üretir.

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]

domain ve target_address, uzunluk öneki olmayan UTF-8 baytlarıdır; owner_pubkey ham 32 bayttır; price ve nonce ise işaretsiz 64 bit little-endian değerlerdir.

Dolaşım

Yapraktan köke doğru ilerleyin. n = proof_depth kardeşle, i adımında hangi tarafta olduğunuzu belirleyen bit, 0. baytın en anlamlı bitinden sayılarak anahtarın n - 1 - i numaralı bitidir:

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], en derin seviyedeki, yaprağa en yakın kardeştir.

Terminal

Her ispat, yolun sonunda ne olduğunu anlatan bir proof_terminal taşır:

TerminalAnlamı
Occupiedorada anahtarın kendi yaprağı var — isim mevcut
Vacantorada boş bir alt ağaç var — isim kayıtlı değil
Blockedorada başka bir ismin yaprağı var — isim kayıtlı değil ve yanıt o ismin tam kaydını içeriyor

Başarılı bir çözümleme Occupied olmalıdır. Vacant ve Blocked, yokluk ispatlarıdır: bir indeksleyicinin "bulunamadı" demesine inanmak yerine, bir ismin gerçekten kayıtlı olmadığını doğrulamanızı sağlarlar. Terminali Occupied olmayan her çözümlemeyi reddedin.

Kod

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()
ipucu

Uygulamanızı kurcalamaya karşı test edin

Her zaman true döndüren bir doğrulayıcı, tüm olumlu testleri geçer. Kendi doğrulayıcınıza güvenmeden önce, gerçek bir yanıttaki target_address, owner_pubkey veya price alanının bir baytını değiştirin ve artık false döndürdüğünü doğrulayın. Yukarıdaki üç örnek de bu yöntemle denetlenmiştir.


2. adım — kökü çıpa sözleşmesiyle karşılaştırın

Doğrulanmış bir Merkle ispatı yalnızca "bu kayıt bir ağaçta" der. Gerçek ağaçta olduğunu bilmek için, BNB Smart Chain üzerindeki çıpa sözleşmesine o kökü hiç kabul edip etmediğini sorun.

isRootValid(bytes32) kullanın — seçici 0x30ef41b4. ABI kodlu tek bir boole değeri döndürür; bunu doğru ayrıştırmak rootHistory yapısına göre çok daha kolaydır.

ÇağrıSeçiciDöndürdüğü
isRootValid(bytes32)0x30ef41b4bool — bu kök hiç kabul edilmiş mi
currentRoot()0xfdab463dbytes32 — kabul edilen en son kök
verifyRootValidity(bytes32)0xc7179944(bool isValid, uint32 blockHeight)
currentBlockHeight()0x367bf2f9uint32 — en son kökün PIVX yüksekliği
programVkey()0x09665ee7bytes32 — sözleşmenin dayattığı devre
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

Yanıtı yorumlama

isRootValidAnlamıNe yapmalı
true ve kök currentRoot() ile aynıindeksleyici tümüyle senkronizekabul edin
true ama kök currentRoot() değerinden eskiindeksleyici zincirin gerisindekabul edin, isterseniz uyarın — kayıt o kök itibarıyla geçerliydi
falsebu kök zincir üzerinde hiç kabul edilmediçözümlemeyi reddedin

false yanıtı, karşılaştırdığınız ağacın protokol açısından var olmadığı anlamına gelir. Bu, tam da saldırganın denetimindeki bir indeksleyicinin ürettiği durumdur ve bu adım tam olarak bunun içindir.


Devrenin kendisini doğrulamak

Yukarıdaki iki adım, bir kaydın sözleşmenin kabul ettiği bir köke ait olduğunu ispatlar. Sözleşme ise bir kökü ancak yanında geçerli bir ZK ispatı varsa kabul eder — ve o ispat, tam olarak derlenmiş devreye yapılmış 32 baytlık bir taahhüt olan programVkey karşısında denetlenir.

Bu anahtarın yayımlanmış kaynak koda karşılık geldiğini doğrulayabilirsiniz:

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

Elde ettiğiniz doğrulama anahtarını, çıpa sözleşmesindeki programVkey() (seçici 0x09665ee7) değeriyle karşılaştırın. Eşleşiyorlarsa sözleşme tam olarak o kaynak kodu dayatıyordur. Devrenin tek bir satırını değiştirmek farklı bir anahtar üretir; dolayısıyla değiştirilmiş bir devre, değişiklik zincir üzerinde görünmeden yerine konulamaz.

uyarı

Ek özellik olmadan derleyin

Düz bir cargo prove build, yayımlanmış sözleşmenin dayattığı mainnet devresini üretir. Herhangi bir özellik bayrağı derlenen programı, dolayısıyla doğrulama anahtarını değiştirir ve bu anahtar artık eşleşmez.

Bir SP1 ispatını doğrudan doğrulamak

İspatlar standart SP1 Groth16 ispatlarıdır ve SP1 SDK ile denetlenebilir:

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)?;

Açık değerler (bytes32 old_root, bytes32 new_root, uint32 end_block_height) yapısının ABI kodlamasıdır — toplu işin gerçekleştirdiği durum geçişi ve kapsadığı PIVX blok yüksekliği.