Перейти к основному содержимому

Проверка доказательств

Проверка доказательств самостоятельно

Доказательства представляют собой стандартные сжатые доказательства 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 подтверждается Program Key. Исходный код прувера доступен на GitHub. Любой желающий может скомпилировать его и сравнить ключи программы. Программный ключ будет изменен, если в гостевой программе SP1 будут выполнены какие-либо изменения.

Проверка доказательства разрешения имени

Когда клиент разрешает доменное имя ZCash, процесс выполняется в четыре логических этапа, чтобы гарантировать абсолютную безопасность, достоверность синхронизации и целостность состояния без доверия узлу индексатора:

  1. Запрос на разрешение домена: Клиент отправляет запрос к конечной точке индексатора разрешения имен Zcash (например, GET /v1.0/resolve/richard.zcash). Индексатор возвращает сведения о домене, включая целевой адрес ZCash, открытый ключ владельца, цену, одноразовый номер, корень SMT и 128 одноуровневых хешей, представляющих путь в разреженном дереве Меркла (SMT).

  2. Локальная проверка SMT: Чтобы индексатор не возвращал поддельное разрешение, клиент проверяет подтверждение членства в Merkle локально. Листовые данные создаются путем объединения: domain_name (UTF-8 байт) + owner_pubkey (32 байта из шестнадцатеричного формата) + target_address (UTF-8 байт) + price (u64 с прямым порядком байтов) + nonce (u64 с прямым порядком байтов). Мы вычисляем хэш SHA-256 этих конечных данных и проходим SMT до 128 уровней, используя одноуровневые хэши. Если вычисленный корень соответствует smt_root индексатора, доказательство является криптографически надежным.

  3. Последняя проверка корневой синхронизации: Чтобы проверить, синхронизирован ли корень индексатора с блокчейном, мы запрашиваем последний проверенный корень SMT в цепочке из контракта EVM (Arbitrum) с помощью функции currentRoot() (селектор 0xfdab463d). Если он соответствует smt_root индексатора, индексатор полностью синхронизирован.

  4. Историческая проверка корня (резервный вариант EVM RPC): Если корень индексатора не соответствует последнему корню контракта (что может произойти из-за задержки синхронизации), мы запрашиваем отображение контракта rootHistory(bytes32) (селектор 0x66dd97ab), чтобы проверить, является ли корень индексатора исторически действительным:

    • Если сопоставление возвращает ненулевое значение: корень был исторически проверен в контракте. Клиент получает предупреждение о временной задержке индексатора, но может безопасно продолжить работу.
    • Если сопоставление возвращает 0: корень никогда не был зафиксирован и не проверен в блокчейне. Разрешение немедленно блокируется по соображениям безопасности.

Примеры локального кода подтверждения SMT

Вот примеры на Python, PHP и JavaScript, показывающие, как проверить SMT-доказательство разрешенного имени с использованием полнолистового хеш-макета:

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

Проверка синхронизации контрольной точки в цепочке

Если корень индексатора не соответствует последнему корню, проверенному в контракте, мы проверяем, является ли он исторически действительным, опрашивая узел EVM RPC. Ниже приведены примеры кода на Python, PHP и JavaScript, запрашивающие rootHistory(bytes32) через узел EVM RPC:

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