Xác minh bằng chứng
Tự mình xác minh bằng chứng
Bằng chứng là bằng chứng nén SP1 tiêu chuẩn. Xác minh chúng bằng SDK 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)?;
Khóa xác minh có tính xác định, bắt nguồn từ mã nhị phân của chương trình khách. Bất cứ ai cũng có thể sao chép nó.
Tính bất biến của chương trình khách ZkEVM SP1 được chứng minh bằng Khóa chương trình. Mã nguồn chứng minh có sẵn trên GitHub. Bất cứ ai cũng có thể biên dịch nó và so sánh các Khóa chương trình. Khóa chương trình sẽ được thay đổi nếu có bất kỳ thay đổi nào được thực hiện trong chương trình khách SP1.
Kiểm tra bằng chứng phân giải tên
Khi máy khách phân giải tên miền ZCash, quy trình này được thực thi theo bốn giai đoạn logic để đảm bảo tính bảo mật tuyệt đối, tính hợp lệ đồng bộ hóa và tính toàn vẹn trạng thái mà không cần tin cậy vào nút chỉ mục:
-
Truy vấn phân giải tên miền: Máy khách đưa ra yêu cầu tới điểm cuối của Trình lập chỉ mục độ phân giải ZcashNames (ví dụ:
GET /v1.0/resolve/richard.zcash). Trình lập chỉ mục trả về các chi tiết tên miền bao gồm địa chỉ ZCash đích, khóa chung của chủ sở hữu, giá, số không, gốc SMT và 128 giá trị băm anh em đại diện cho đường dẫn trong Cây Merkle thưa thớt (SMT). -
Xác minh bằng chứng SMT cục bộ: Để ngăn người lập chỉ mục trả về độ phân giải giả, khách hàng sẽ xác thực bằng chứng thành viên Merkle cục bộ. Dữ liệu lá được xây dựng bằng cách nối:
domain_name(UTF-8 byte) +owner_pubkey(32 byte từ hệ thập lục phân) +target_address(UTF-8 byte) +price(u64 little-endian) +nonce(u64 little-endian). Chúng tôi tính toán hàm bămSHA-256của dữ liệu lá này và duyệt SMT lên tới 128 cấp bằng cách sử dụng các hàm băm anh em. Nếu gốc được tính khớp vớismt_rootcủa người lập chỉ mục thì bằng chứng là hợp lý về mặt mật mã. -
Xác thực đồng bộ hóa gốc mới nhất: Để kiểm tra xem gốc của người lập chỉ mục có đồng bộ hóa với chuỗi khối hay không, chúng tôi truy vấn gốc SMT được xác thực mới nhất trên chuỗi từ hợp đồng EVM (Arbitrum) bằng cách sử dụng hàm
currentRoot()(bộ chọn0xfdab463d). Nếu nó khớp vớismt_rootcủa người lập chỉ mục thì người lập chỉ mục đã được đồng bộ hóa hoàn toàn. -
Xác thực gốc lịch sử (dự phòng EVM RPC): Nếu gốc của trình lập chỉ mục không khớp với gốc hợp đồng mới nhất (có thể xảy ra do độ trễ đồng bộ hóa), chúng tôi sẽ truy vấn ánh xạ của hợp đồng
rootHistory(bytes32)(bộ chọn0x66dd97ab) để xác minh xem gốc của trình lập chỉ mục có hợp lệ về mặt lịch sử hay không:- Nếu ánh xạ trả về giá trị khác 0: Gốc đã được xác minh lịch sử trên hợp đồng. Khách hàng được cảnh báo về độ trễ của bộ chỉ mục tạm thời nhưng có thể tiếp tục một cách an toàn.
- Nếu ánh xạ trả về 0: Root chưa bao giờ được xác nhận hoặc xác thực trên blockchain. Độ phân giải bị chặn ngay lập tức vì lý do bảo mật.
Ví dụ về mã chứng minh SMT cục bộ
Dưới đây là các ví dụ bằng Python, PHP và JavaScript cho biết cách xác minh bằng chứng SMT về tên đã được phân giải bằng cách sử dụng bố cục hàm băm lá đầy đủ:
- 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();
}
Xác minh đồng bộ hóa điểm kiểm tra trên chuỗi
Nếu gốc của người lập chỉ mục không khớp với gốc mới nhất được xác thực trên hợp đồng, chúng tôi sẽ kiểm tra xem nó có hợp lệ về mặt lịch sử hay không bằng cách truy vấn nút EVM RPC. Dưới đây là các ví dụ về mã trong truy vấn Python, PHP và JavaScript rootHistory(bytes32) thông qua nút 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;
}