Beweise überprüfen
Beweise selbst überprüfen
Bei den Beweisen handelt es sich um komprimierte Standard-SP1-Proofe. Überprüfen Sie sie mit dem 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)?;
Der Verifizierungsschlüssel ist deterministisch und wird von der Binärdatei des Gastprogramms abgeleitet. Jeder kann es reproduzieren.
Die Unveränderlichkeit des ZkEVM SP1-Gastprogramms wird durch Program Key bewiesen. Der Quellcode des Prüfers ist unter GitHub verfügbar. Jeder kann es kompilieren und die Programmschlüssel vergleichen. Der Programmschlüssel wird geändert, wenn Änderungen im SP1-Gastprogramm vorgenommen werden.
Überprüfung des Namensauflösungsnachweises
Wenn ein Client einen ZCash-Domänennamen auflöst, wird der Prozess in vier logischen Phasen ausgeführt, um absolute Sicherheit, Synchronisierungsgültigkeit und Statusintegrität zu gewährleisten, ohne dem Indexerknoten zu vertrauen:
-
Abfrage zur Domänenauflösung: Der Client stellt eine Anfrage an einen ZcashNames Resolution Indexer-Endpunkt (z. B.
GET /v1.0/resolve/richard.zcash). Der Indexer gibt die Domänendetails zurück, einschließlich der Zieladresse ZCash, des öffentlichen Schlüssels des Eigentümers, des Preises, der Nonce, des SMT-Stamms und 128 Geschwister-Hashes, die den Pfad im Sparse Merkle Tree (SMT) darstellen. -
Lokale SMT-Proof-Verifizierung: Um zu verhindern, dass der Indexer eine gefälschte Lösung zurückgibt, validiert der Client den Merkle-Mitgliedschaftsnachweis lokal. Die Blattdaten werden durch die Verkettung von Folgendem erstellt:
domain_name(UTF-8 Bytes) +owner_pubkey(32 Bytes aus Hex) +target_address(UTF-8 Bytes) +price(u64 Little-Endian) +nonce(u64 Little-Endian). Wir berechnen denSHA-256-Hash dieser Blattdaten und durchlaufen die SMT bis zu 128 Ebenen mithilfe der Geschwister-Hashes. Wenn die berechnete Wurzel mitsmt_rootdes Indexers übereinstimmt, ist der Beweis kryptografisch einwandfrei. -
Neueste Root-Sync-Validierung: Um zu überprüfen, ob der Root des Indexers mit der Blockchain synchronisiert ist, fragen wir mithilfe der Funktion
currentRoot()(Selektor0xfdab463d) den neuesten validierten SMT-Root in der Kette aus dem EVM-Vertrag (Arbitrum) ab. Wenn es mit demsmt_rootdes Indexers übereinstimmt, ist der Indexer vollständig synchronisiert. -
Historische Root-Validierung (EVM RPC-Fallback): Wenn der Stamm des Indexers nicht mit dem neuesten Vertragsstamm übereinstimmt (was aufgrund von Synchronisierungsverzögerungen auftreten kann), fragen wir die Zuordnung
rootHistory(bytes32)des Vertrags (Selektor0x66dd97ab) ab, um zu überprüfen, ob der Stamm des Indexers historisch gültig ist:- Wenn die Zuordnung einen Wert ungleich Null zurückgibt: Der Stamm wurde in der Vergangenheit im Vertrag überprüft. Der Client wird vor einer vorübergehenden Verzögerung des Indexers gewarnt, kann aber problemlos fortfahren.
- Wenn die Zuordnung 0 zurückgibt: Der Stamm wurde nie in der Blockchain festgeschrieben oder validiert. Die Auflösung wird aus Sicherheitsgründen sofort gesperrt.
Beispiele für lokale SMT-Proof-Codes
Hier sind Beispiele in Python, PHP und JavaScript, die zeigen, wie der SMT-Beweis eines aufgelösten Namens mithilfe des vollständigen Blatt-Hash-Layouts überprüft wird:
- 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();
}
Überprüfung der On-Chain-Checkpoint-Synchronisierung
Wenn der Stamm des Indexers nicht mit dem zuletzt im Vertrag validierten Stamm übereinstimmt, prüfen wir, ob er historisch gültig ist, indem wir den EVM-RPC-Knoten abfragen. Nachfolgend finden Sie Codebeispiele in Python, PHP und JavaScript, die rootHistory(bytes32) über einen EVM-RPC-Knoten abfragen:
- 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;
}