Sübutların yoxlanması
Bu səhifə adın həllini onu təqdim edən indeksləyiciyə etibar etmədən necə yoxlayacağınızı göstərir. Buradakı hər kod nümunəsi dərc olunmazdan əvvəl işlək indeksləyiciyə qarşı icra olunur.
Yoxlama sizə nə verir
Ad həlli bu suala cavab verir: «richard.pivx hansı ünvana işarə edir?» Onu yoxlamaq cavabın
vəziyyət kökünə öhdəlik götürülmüş cavab olduğunu və həmin kökün lövbər müqaviləsinin qəbul etdiyi
kök olduğunu sübut edir. Ünvan uyduran, köhnəlmiş qeyd təqdim edən və ya adı gizlədən indeksləyici
bu yoxlamalardan keçən sübut yarada bilməz.
İki müstəqil addım var və hər ikisi lazımdır:
- Merkle sübutu qeydi vəziyyət kökünə bağlayır.
- Zəncir üzərindəki yoxlama həmin kökü lövbər müqaviləsinə bağlayır.
Təkbaşına 1-ci addım heç nəyə dəyməz: zərərli indeksləyici bütöv saxta ağac qurub öz saxta kökünə uyğun sübutlar təqdim edə bilər. Kökə məna verən məhz 2-ci addımdır.
1-ci addım — Merkle sübutunu yoxlayın
Ağac
PiNS 128 bitlik açar fəzası üzərində kompakt seyrək Merkle ağacından istifadə edir. Açar —
SHA-256(domain_name) dəyərinin ilk 16 baytıdır. Doğrulayıcı yazarkən iki xüsusiyyət vacibdir:
- Sübutlar qısadır və uzunluğu dəyişkəndir. Yarpaq açar prefiksinin unikal olduğu ən az
dərinlikdə yerləşir, ona görə sübut
proof_depthsayda qonşu daşıyır — adətən bir neçə ədəd, 128 deyil. Heç vaxt sabit uzunluq fərz etməyin;proof_depthdəyərini oxuyun və onunmerkle_proof.lengthilə uyğun gəldiyini yoxlayın. - Boş alt ağac istənilən hündürlükdə 32 sıfır baytdır. Boş qovşaqlar üçün əvvəlcədən hesablanmış heş pilləkəni yoxdur. Buna görə boş ağacın kökü tamamilə sıfırdır.
Heş funksiyaları
Yarpaqlar və daxili qovşaqlar heş sahələri kəsişməsin deyə teqlənir. Teq baytını buraxmaq — ən çox yayılmış səhvdir: o, inandırıcı görünən, lakin köklə heç vaxt uyğun gəlməyən heş verir.
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 və target_address — uzunluq prefiksi olmayan UTF-8 baytlarıdır; owner_pubkey — xam 32
baytdır; price və nonce isə işarəsiz 64 bitlik little-endian dəyərlərdir.
Keçid
Yarpaqdan kökə doğru gedin. n = proof_depth qonşu ilə, i addımında hansı tərəfdə olduğunuzu
0-cı baytın ən əhəmiyyətli bitindən sayılmaqla açarın n - 1 - i nömrəli biti müəyyən edir:
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] — ən dərin səviyyədəki, yarpağa ən yaxın qonşudur.
Terminal
Hər sübut yolun sonunda nə olduğunu təsvir edən proof_terminal daşıyır:
| Terminal | Mənası |
|---|---|
Occupied | orada açarın öz yarpağı var — ad mövcuddur |
Vacant | orada boş alt ağac var — ad qeydiyyatda deyil |
Blocked | orada başqa adın yarpağı var — ad qeydiyyatda deyil və cavab həmin adın tam qeydini ehtiva edir |
Uğurlu həll Occupied olmalıdır. Vacant və Blocked — yoxluq sübutlarıdır: onlar
indeksləyicinin «tapılmadı» cavabına inanmaq əvəzinə adın həqiqətən qeydiyyatda olmadığını
yoxlamağa imkan verir. Terminalı Occupied olmayan hər həlli rədd edin.
Kod
- Python
- PHP
- JavaScript
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()
<?php
const LEAF_TAG = "\x00";
const NODE_TAG = "\x01";
const KEY_LEN = 16;
const MAX_DEPTH = 128;
function hashLeaf(string $domain, string $ownerPubkeyHex, string $targetAddress, int $price, int $nonce): string
{
return hash('sha256',
LEAF_TAG
. $domain
. hex2bin($ownerPubkeyHex)
. $targetAddress
. pack('P', $price) // u64 little-endian
. pack('P', $nonce),
true);
}
function hashNode(string $left, string $right): string
{
return hash('sha256', NODE_TAG . $left . $right, true);
}
function keyOf(string $domain): string
{
return substr(hash('sha256', $domain, true), 0, KEY_LEN);
}
function keyBit(string $key, int $i): int
{
return (ord($key[intdiv($i, 8)]) >> (7 - ($i % 8))) & 1;
}
function fold(string $key, string $start, array $siblings): string
{
$h = $start;
$n = count($siblings);
foreach ($siblings as $i => $sib) {
$h = keyBit($key, $n - 1 - $i) === 1 ? hashNode($sib, $h) : hashNode($h, $sib);
}
return $h;
}
function verifyResolution(array $entry, string $expectedRoot): bool
{
if ($entry['proof_terminal'] !== 'Occupied') {
throw new RuntimeException('a resolution must carry an Occupied terminal');
}
$siblings = array_map('hex2bin', $entry['merkle_proof']);
if (count($siblings) !== (int)$entry['proof_depth']) {
throw new RuntimeException('proof_depth does not match the sibling count');
}
if ((int)$entry['proof_depth'] > MAX_DEPTH) {
throw new RuntimeException('proof depth exceeds MAX_DEPTH');
}
$leaf = hashLeaf($entry['domain_name'], $entry['owner_pubkey'], $entry['target_address'],
(int)$entry['price'], (int)$entry['nonce']);
return bin2hex(fold(keyOf($entry['domain_name']), $leaf, $siblings)) === strtolower($expectedRoot);
}
import crypto from "node:crypto";
const LEAF_TAG = 0x00;
const NODE_TAG = 0x01;
const KEY_LEN = 16;
const MAX_DEPTH = 128;
const sha256 = (...parts) =>
crypto.createHash("sha256").update(Buffer.concat(parts)).digest();
const u64le = (v) => {
const b = Buffer.alloc(8);
b.writeBigUInt64LE(BigInt(v));
return b;
};
function hashLeaf(domain, ownerPubkeyHex, targetAddress, price, nonce) {
return sha256(
Buffer.from([LEAF_TAG]),
Buffer.from(domain, "utf8"),
Buffer.from(ownerPubkeyHex, "hex"),
Buffer.from(targetAddress, "utf8"),
u64le(price),
u64le(nonce),
);
}
const hashNode = (l, r) => sha256(Buffer.from([NODE_TAG]), l, r);
const keyOf = (domain) =>
crypto.createHash("sha256").update(domain, "utf8").digest().subarray(0, KEY_LEN);
const keyBit = (key, i) => (key[Math.floor(i / 8)] >> (7 - (i % 8))) & 1;
function fold(key, start, siblings) {
let h = start;
const n = siblings.length;
for (let i = 0; i < n; i++) {
h = keyBit(key, n - 1 - i) ? hashNode(siblings[i], h) : hashNode(h, siblings[i]);
}
return h;
}
export function verifyResolution(entry, expectedRoot) {
if (entry.proof_terminal !== "Occupied") {
throw new Error("a resolution must carry an Occupied terminal");
}
const siblings = entry.merkle_proof.map((s) => Buffer.from(s, "hex"));
if (siblings.length !== entry.proof_depth) {
throw new Error("proof_depth does not match the sibling count");
}
if (entry.proof_depth > MAX_DEPTH) {
throw new Error("proof depth exceeds MAX_DEPTH");
}
const leaf = hashLeaf(
entry.domain_name,
entry.owner_pubkey,
entry.target_address,
Number(entry.price),
Number(entry.nonce),
);
return fold(keyOf(entry.domain_name), leaf, siblings).toString("hex")
=== expectedRoot.toLowerCase();
}
Öz tətbiqinizi saxtakarlığa qarşı sınayın
Həmişə true qaytaran doğrulayıcı istənilən müsbət testdən keçir. Özünüzünkünə etibar etməzdən
əvvəl real cavabdakı target_address, owner_pubkey və ya price sahəsinin bir baytını dəyişin
və indi false qaytardığını təsdiqləyin. Yuxarıdakı üç nümunə də məhz belə yoxlanılıb.
2-ci addım — kökü lövbər müqaviləsi ilə tutuşdurun
Yoxlanmış Merkle sübutu yalnız «bu qeyd hansısa ağacdadır» deyir. Onun həqiqi ağacda olduğunu bilmək üçün BNB Smart Chain üzərindəki lövbər müqaviləsindən həmin kökü nə vaxtsa qəbul edib-etmədiyini soruşun.
isRootValid(bytes32) istifadə edin — seçici 0x30ef41b4. O, ABI kodlaşdırmasında tək bir məntiqi
dəyər qaytarır; bunu düzgün təhlil etmək rootHistory strukturuna nisbətən xeyli asandır.
| Çağırış | Seçici | Qaytarır |
|---|---|---|
isRootValid(bytes32) | 0x30ef41b4 | bool — bu kök nə vaxtsa qəbul edilibmi |
currentRoot() | 0xfdab463d | bytes32 — qəbul edilmiş son kök |
verifyRootValidity(bytes32) | 0xc7179944 | (bool isValid, uint32 blockHeight) |
currentBlockHeight() | 0x367bf2f9 | uint32 — son kökün PIVX hündürlüyü |
programVkey() | 0x09665ee7 | bytes32 — müqavilənin tələb etdiyi sxem |
- Python
- PHP
- JavaScript
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
<?php
function isRootValid(string $rpcUrl, string $contractAddress, string $smtRoot): bool
{
$clean = str_pad(str_replace('0x', '', strtolower($smtRoot)), 64, '0', STR_PAD_LEFT);
$payload = [
'jsonrpc' => '2.0',
'method' => 'eth_call',
'params' => [['to' => $contractAddress, 'data' => '0x30ef41b4' . $clean], 'latest'],
'id' => 1,
];
$ch = curl_init($rpcUrl);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode($payload),
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
CURLOPT_TIMEOUT => 15,
]);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if (isset($data['error'])) {
throw new RuntimeException('EVM RPC error: ' . $data['error']['message']);
}
$result = $data['result'] ?? '0x';
if ($result === '' || $result === '0x') {
return false;
}
// A bool is ABI-encoded as a full 32-byte word: 0x00..01 for true.
return hexdec(substr($result, -1)) === 1;
}
async function isRootValid(rpcUrl, contractAddress, smtRoot) {
const clean = smtRoot.replace(/^0x/, "").toLowerCase().padStart(64, "0");
const response = await fetch(rpcUrl, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
jsonrpc: "2.0",
method: "eth_call",
params: [{ to: contractAddress, data: `0x30ef41b4${clean}` }, "latest"],
id: 1,
}),
});
if (!response.ok) throw new Error(`RPC request failed: ${response.statusText}`);
const json = await response.json();
if (json.error) throw new Error(`EVM RPC error: ${json.error.message}`);
const result = json.result;
if (!result || result === "0x") return false;
// A bool is ABI-encoded as a full 32-byte word: 0x00..01 for true.
return BigInt(result) === 1n;
}
Cavabın şərhi
isRootValid | Mənası | Nə etməli |
|---|---|---|
true və kök currentRoot() ilə eynidir | indeksləyici tam sinxronlaşıb | qəbul edin |
true, lakin kök currentRoot() dəyərindən köhnədir | indeksləyici zəncirdən geri qalır | qəbul edin, istəsəniz xəbərdarlıq edin — qeyd həmin kök üçün etibarlı idi |
false | bu kök zəncir üzərində heç vaxt qəbul edilməyib | həlli rədd edin |
false cavabı o deməkdir ki, tutuşdurduğunuz ağac protokol baxımından mövcud deyil. Məhz bu hal
təcavüzkarın nəzarətindəki indeksləyicinin yaratdığı vəziyyətdir və bu addım məhz bunun üçündür.
Sxemin özünün yoxlanması
Yuxarıdakı iki addım qeydin müqavilənin qəbul etdiyi kökə aid olduğunu sübut edir. Müqavilə isə
kökü yalnız yanında etibarlı ZK sübutu varsa qəbul edir — və həmin sübut konkret kompilyasiya
edilmiş sxemə verilmiş 32 baytlıq öhdəlik olan programVkey qarşısında yoxlanılır.
Bu açarın dərc olunmuş mənbə koduna uyğun gəldiyini təsdiqləyə bilərsiniz:
git clone https://github.com/PIVX-Name/pivx-name-prover
cd pivx-name-prover/program
cargo prove build # plain build = mainnet
Alınan doğrulama açarını lövbər müqaviləsindəki programVkey() (seçici 0x09665ee7) dəyəri ilə
müqayisə edin. Onlar üst-üstə düşürsə, müqavilə məhz həmin mənbə kodunu tələb edir. Sxemin bir
sətrini dəyişmək başqa açar verir, ona görə dəyişdirilmiş sxemi dəyişiklik zəncirdə görünmədən
əvəzləmək mümkün deyil.
Əlavə xüsusiyyətlər olmadan kompilyasiya edin
Sadə cargo prove build yerləşdirilmiş müqavilənin tələb etdiyi mainnet sxemini verir. İstənilən
xüsusiyyət bayrağı kompilyasiya edilmiş proqramı, deməli onun doğrulama açarını dəyişir və bu
açar artıq uyğun gəlməyəcək.
SP1 sübutunun birbaşa yoxlanması
Sübutlar standart SP1 Groth16 sübutlarıdır və SP1 SDK ilə yoxlana bilər:
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)?;
İctimai dəyərlər (bytes32 old_root, bytes32 new_root, uint32 end_block_height) strukturunun ABI kodlaşdırmasıdır — paketin həyata keçirdiyi vəziyyət
keçidi və onun əhatə etdiyi PIVX blok hündürlüyü.