Перевірка доказів
Перевірте докази самостійно
Докази — це стандартні стислі докази SP1. Перевірте їх за допомогою SP1 SDK:
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)?;
Ключ перевірки є детермінованим, похідним від двійкового файлу гостьової програми. Будь-хто може його відтворити.
Незмінність гостьової програми ZkEVM SP1 підтверджено програмним ключем. Вихідний код перевірки доступний на GitHub. Кожен може скомпілювати його та порівняти програмні ключі. Ключ програми буде змінено, якщо внести будь-які зміни в гостьову програму SP1.
Перевірка підтвердження розпізнавання імен
Коли клієнт розпізнає доменне ім’я ZCash, процес виконується в чотири логічні етапи, щоб гарантувати абсолютну безпеку, дійсність синхронізації та цілісність стану без довіри до вузла індексатора:
-
Запит на дозвіл домену: Клієнт робить запит до кінцевої точки індексатора розпізнавання імен Zcash(наприклад,
GET /v1.0/resolve/richard.zcash). Індексатор повертає деталі домену, включаючи цільову адресу ZCash, відкритий ключ власника, ціну, nonce, корінь SMT і 128 однорідних хешів, що представляють шлях у Sparse Merkle Tree (SMT). -
Локальна перевірка доказів SMT: Щоб індексатор не повернув фальшиву резолюцію, клієнт перевіряє підтвердження членства Merkle локально. Листкові дані створюються шляхом конкатенації:
domain_name(UTF-8 байтів) +owner_pubkey(32 байти з шістнадцяткового) +target_address(UTF-8 байтів) +price(u64 з порядковим порядком) +nonce(u64 з прямим порядком). Ми обчислюємо хешSHA-256цих кінцевих даних і проходимо SMT до 128 рівнів, використовуючи хеші-сестри. Якщо обчислений корінь відповідаєsmt_rootіндексатора, доказ є криптографічно обґрунтованим. -
Остання перевірка кореневої синхронізації: Щоб перевірити, чи синхронізований кореневий каталог індексатора з блокчейном, ми запитуємо останній перевірений кореневий SMT у ланцюжку з контракту EVM (Arbitrum) за допомогою функції
currentRoot()(селектор0xfdab463d). Якщо він збігається зsmt_rootіндексатора, індексатор повністю синхронізовано. -
Історична коренева перевірка (резервний EVM RPC): Якщо корінь індексатора не збігається з останнім коренем контракту (що може статися через затримку синхронізації), ми запитуємо відображення контракту
rootHistory(bytes32)(селектор0x66dd97ab), щоб перевірити, чи корінь індексатора історично дійсний:- Якщо зіставлення повертає ненульове значення: корінь було історично перевірено в контракті. Клієнт отримує попередження про тимчасову затримку індексатора, але може безпечно продовжити.
- Якщо зіставлення повертає 0: корінь ніколи не був зафіксований або перевірений у блокчейні. Рішення негайно блокується з міркувань безпеки.
Приклади локального перевірочного коду SMT
Ось приклади на Python, PHP і JavaScript, які показують, як перевірити SMT-доказ розв’язаного імені за допомогою повного макета хеш-листів:
- Python
- PHP
- JavaScript
import hashlib
import struct
def get_bit(key_bytes, bit_idx):
"""
Returns the bit value (0 or 1) at a given index (0 to 127)
from MSB to LSB of the 16-byte key.
"""
byte_pos = bit_idx // 8
bit_pos = 7 - (bit_idx % 8)
return (key_bytes[byte_pos] >> bit_pos) & 1
def verify_smt_proof(domain_name, target_address, owner_pubkey, price, nonce, merkle_proof, expected_root):
# 1. Compute key as SHA-256 of lowercase domain name
key_hash = hashlib.sha256(domain_name.lower().encode('utf-8')).digest()
# Take first 16 bytes (128 bits) for a depth-128 SMT
key = key_hash[:16]
# 2. Compute leaf hash as SHA-256 of concatenated leaf values
domain_buf = domain_name.encode('utf-8')
pubkey_buf = bytes.fromhex(owner_pubkey)
address_buf = target_address.encode('utf-8')
price_buf = struct.pack('<Q', price) # 64-bit unsigned integer in little-endian
nonce_buf = struct.pack('<Q', nonce) # 64-bit unsigned integer in little-endian
leaf_data = domain_buf + pubkey_buf + address_buf + price_buf + nonce_buf
current = hashlib.sha256(leaf_data).digest()
# 3. Hash up the tree using the 128 sibling hashes
# Sibling proof array goes from bottom (closest to leaf, index 0) to top (closest to root, index 127)
for i in range(128):
sibling = bytes.fromhex(merkle_proof[i])
# Level 127 (index 0) corresponds to the bottom-most bit (bit 127)
# Level 0 (index 127) corresponds to the top-most bit (bit 0)
bit_idx = 127 - i
bit = get_bit(key, bit_idx)
if bit == 1:
# Current node is on the right, sibling is on the left
current = hashlib.sha256(sibling + current).digest()
else:
# Current node is on the left, sibling is on the right
current = hashlib.sha256(current + sibling).digest()
return current.hex() == expected_root.lower()
<?php
function verifySmtProof($domainName, $targetAddress, $ownerPubkey, $price, $nonce, $merkleProof, $expectedRoot) {
// 1. Compute key as SHA-256 of lowercase domain name
$keyHash = hash('sha256', strtolower($domainName), true);
// Take first 16 bytes (128 bits) for a depth-128 SMT
$key = substr($keyHash, 0, 16);
// 2. Compute leaf hash as SHA-256 of concatenated leaf values
$domainBuf = $domainName;
$pubkeyBuf = hex2bin($ownerPubkey);
$addressBuf = $targetAddress;
$priceBuf = pack('P', $price); // 64-bit unsigned integer in little-endian
$nonceBuf = pack('P', $nonce); // 64-bit unsigned integer in little-endian
$leafData = $domainBuf . $pubkeyBuf . $addressBuf . $priceBuf . $nonceBuf;
$current = hash('sha256', $leafData, true);
// 3. Hash up the tree using the 128 sibling hashes
for ($i = 0; $i < 128; $i++) {
$sibling = hex2bin($merkleProof[$i]);
# Level 127 (index 0) corresponds to the bottom-most bit (bit 127)
# Level 0 (index 127) corresponds to the top-most bit (bit 0)
$bitIdx = 127 - $i;
$bytePos = intdiv($bitIdx, 8);
$bitPos = 7 - ($bitIdx % 8);
$bit = (ord($key[$bytePos]) >> $bitPos) & 1;
if ($bit === 1) {
// Current node is on the right, sibling is on the left
$current = hash('sha256', $sibling . $current, true);
} else {
// Current node is on the left, sibling is on the right
$current = hash('sha256', $current . $sibling, true);
}
}
return bin2hex($current) === strtolower($expectedRoot);
}
const crypto = require('crypto');
function verifySmtProof(domainName, targetAddress, ownerPubkey, price, nonce, merkleProof, expectedRoot) {
// 1. Compute key as SHA-256 of lowercase domain name
const keyHash = crypto.createHash('sha256').update(domainName.toLowerCase()).digest();
// Take first 16 bytes (128 bits) for a depth-128 SMT
const key = keyHash.subarray(0, 16);
// 2. Compute leaf hash as SHA-256 of concatenated leaf values
const domainBuf = Buffer.from(domainName, 'utf-8');
const pubkeyBuf = Buffer.from(ownerPubkey, 'hex');
const addressBuf = Buffer.from(targetAddress, 'utf-8');
const writeUInt64LE = (val) => {
const buf = Buffer.alloc(8);
let bigVal = BigInt(val);
for (let i = 0; i < 8; i++) {
buf[i] = Number(bigVal & 0xffn);
bigVal >>= 8n;
}
return buf;
};
const priceBuf = writeUInt64LE(price);
const nonceBuf = writeUInt64LE(nonce);
const leafData = Buffer.concat([domainBuf, pubkeyBuf, addressBuf, priceBuf, nonceBuf]);
let current = crypto.createHash('sha256').update(leafData).digest();
// Helper to get bit at index (0-127) from MSB of 16-byte key
const getBit = (bytes, bitIdx) => {
const bytePos = Math.floor(bitIdx / 8);
const bitPos = 7 - (bitIdx % 8);
return (bytes[bytePos] >> bitPos) & 1;
};
// 3. Hash up the tree using the 128 sibling hashes
for (let i = 0; i < 128; i++) {
const sibling = Buffer.from(merkleProof[i], 'hex');
const bitIdx = 127 - i;
const bit = getBit(key, bitIdx);
const hasher = crypto.createHash('sha256');
if (bit === 1) {
// Current node is on the right, sibling is on the left
hasher.update(Buffer.concat([sibling, current]));
} else {
// Current node is on the left, sibling is on the right
hasher.update(Buffer.concat([current, sibling]));
}
current = hasher.digest();
}
return current.toString('hex') === expectedRoot.toLowerCase();
}
Перевірка синхронізації контрольної точки в мережі
Якщо корінь індексатора не збігається з останнім коренем, підтвердженим у контракті, ми перевіряємо, чи він історично дійсний, запитуючи вузол EVM RPC. Нижче наведено приклади коду на Python, PHP і JavaScript, які надсилають запити rootHistory(bytes32) через вузол EVM RPC:
- Python
- PHP
- JavaScript
import requests
def verify_root_on_contract(rpc_url, contract_address, smt_root):
# Selector for rootHistory(bytes32) is 66dd97ab
clean_root = smt_root.replace("0x", "").lower()
data = f"0x66dd97ab{clean_root.zfill(64)}"
payload = {
"jsonrpc": "2.0",
"method": "eth_call",
"params": [
{
"to": contract_address,
"data": data
},
"latest"
],
"id": 1
}
response = requests.post(rpc_url, json=payload).json()
if "error" in response:
raise Exception(f"EVM RPC Error: {response['error']['message']}")
result = response.get("result", "0x")
if not result or result == "0x":
return False
height = int(result.replace("0x", ""), 16)
return height > 0
<?php
function verifyRootOnContract($rpcUrl, $contractAddress, $smtRoot) {
$cleanRoot = str_replace('0x', '', strtolower($smtRoot));
$data = '0x66dd97ab' . str_pad($cleanRoot, 64, '0', STR_PAD_LEFT);
$payload = [
'jsonrpc' => '2.0',
'method' => 'eth_call',
'params' => [
[
'to' => $contractAddress,
'data' => $data
],
'latest'
],
'id' => 1
];
$ch = curl_init($rpcUrl);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
$response = curl_exec($ch);
curl_close($ch);
$data = json_decode($response, true);
if (isset($data['error'])) {
throw new Exception("EVM RPC Error: " . $data['error']['message']);
}
$result = $data['result'] ?? '0x';
if (!$result || $result === '0x') {
return false;
}
$cleanResult = str_replace('0x', '', $result);
$height = hexdec($cleanResult);
return $height > 0;
}
async function verifyRootOnContract(rpcUrl, contractAddress, smtRoot) {
const cleanRoot = smtRoot.replace(/^0x/, '').toLowerCase();
const data = `0x66dd97ab${cleanRoot.padStart(64, '0')}`;
const payload = {
jsonrpc: '2.0',
method: 'eth_call',
params: [
{
to: contractAddress,
data: data
},
'latest'
],
id: 1
};
const response = await fetch(rpcUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
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 hexResult = json.result;
if (!hexResult || hexResult === '0x') {
return false;
}
const height = parseInt(hexResult.replace(/^0x/, ''), 16);
return height > 0;
}