psy-bridge-sp1 是 psy-doge-solana-bridge 当前真实 block-transition 证明路径使用的 SP1 v6.3.1 workspace。
唯一运行时证明 guest 是 block-transition:它验证序列化的 Dogecoin bridge state 与 block witness,使用 manager custody script config、确认数和 deposit fee 参数执行 block transition,并把验证后的状态与旧/新 Solana PsyBridgeHeader 的共识字段绑定。对应的 gen-proof host 使用 ProverClient::builder().cuda(),生成 Groth16 proof 后立即通过 SP1 SDK client.verify(...) 自验证;普通模式是一次性进程,--daemon 模式通过 stdin/stdout JSON lines 复用同一 CUDA prover,不监听网络端口。
Withdrawal 不使用 ZK:request_withdrawal burn 后,operator 提交 bounded snapshot_withdrawals,范围 multiproof 将请求严格绑定到 outputs-only UTX0;Wormhole VAA 和 Manager 5-of-7 signatures 授权 Dogecoin broadcast,Electrs 确认后由 operator 在 OperatorStore 中原子记录 Confirmed/Spent。
该 workspace 证明 block witness 对给定旧状态的转换,以及该结果与提交到 Solana 的 header/config/custodian commitment 一致。它不自行运行 Dogecoin 节点、Solana validator 或 Wormhole Guardian。
lib/ 共享 SHA-256/public-input 公式
program/src/bin/block_transition.rs mandatory block-update proof guest
program/src/bin/manual_claim.rs retained manual-claim guest(无本仓库 runtime host)
program/src/bin/custodian_transition.rs retained custodian-transition guest(无本仓库 runtime host)
script/src/bin/gen_proof.rs block-transition CUDA prover CLI/stdio daemon
script/build.rs 用 sp1-build 编译 program package guest ELF
版本由 Cargo manifests 固定:
sp1-zkvm = "6.3.1"sp1-sdk = "6.3.1"sp1-build = "6.3.1"
这不是 SP1 v5 workspace。若 guest、SP1 crate、proof ABI 或 Solana verifier key 任一发生变化,必须重建 proof,并同步更新/部署 verifier;不能复用旧 proof 或旧 ELF。
-
Rust nightly:根目录
rust-toolchain指定channel = "nightly",并安装llvm-tools、rustc-dev。 -
Edition 2024 能力:虽然本 workspace package edition 是 2021,SP1 v6 依赖图中的 crate 需要能解析/构建 Edition 2024。过旧 stable/nightly 会在依赖解析或编译阶段失败;使用当前
rust-toolchain所选的新 nightly。 -
Succinct/SP1 toolchain:
sp1-build构建 guest 时会调用 Succinct toolchain。正常构建日志会显示类似rustc +succinct --version。 -
protoc:SP1 依赖的 protobuf build scripts 需要 Protocol Buffers compiler。确认:protoc --version
Ubuntu/Debian 通常安装:
sudo apt-get install protobuf-compiler
-
本机资源:当前 CLI 强制使用 CUDA prover,需要可用的 NVIDIA GPU、驱动和 SP1 CUDA runtime。Groth16 proving 是高 GPU/显存负载、明显长于普通 Rust build 的作业。
gen-proof对 setup/execute/prove 使用硬 deadline(可用 CLI flag 覆盖);超时后进程会写出结构化错误并以非零状态退出,而不是在同一可能已污染的 GPU 进程里继续接单。首次运行还会编译 guest/host 依赖。
快速检查:
cd ~/Projects/psy-bridge-sp1
rustc --version
cargo --version
protoc --version
cargo build --release -p psy-bridge-sp1-script --bin gen-proofscript/build.rs 在 host build 期间调用:
sp1_build::build_program_with_args("../program", Default::default());生成并嵌入的 guest ELF 是 regtest block-transition 与 testnet block-transition-testnet。host 必须通过 --network regtest|testnet 显式选择同一编译期嵌入 ELF。host binary 位于:
target/release/gen-proof
Guest 依次调用十一次 sp1_zkvm::io::read_vec();因此这是 十一个 SP1 framed vector,不能拼成一个无 framing 的 blob:
| 顺序 | CLI option | 解码后长度 | 含义 |
|---|---|---|---|
| 1 | --old-state |
可变 | Borsh 编码的旧 Dogecoin bridge state。 |
| 2 | --witness |
可变 | Speedy 编码的 block-transition witness。 |
| 3 | --custody-script-config |
32 bytes | Manager custody script config preimage,即 emitter bridge PDA。 |
| 4 | --required-confirmations |
4 bytes | CLI u32,host 以 little-endian 写入。 |
| 5 | --flat-fee |
8 bytes | Deposit flat fee,host 以 little-endian 写入。 |
| 6 | --fee-num |
8 bytes | Deposit fee numerator,host 以 little-endian 写入。 |
| 7 | --fee-den |
8 bytes | Deposit fee denominator,host 以 little-endian 写入。 |
| 8 | --old-header |
320 bytes | 旧 Solana PsyBridgeHeader canonical #[repr(C)] bytes。 |
| 9 | --new-header |
320 bytes | 新 Solana PsyBridgeHeader canonical #[repr(C)] bytes。 |
| 10 | --config-params |
48 bytes | Bridge config canonical #[repr(C)] bytes。 |
| 11 | --finalized-witness |
可变 | Speedy 编码的 finalized-height incoming witness;空 hex 表示 genesis / 尚未处理该 finalized height。 |
Host 对 custody script config、两个 header 和 config 做精确长度检查;state/tip witness/finalized witness 由 guest 中的 Borsh/Speedy parser 完整消费,且空 finalized witness 编码为第 11 个空 vector。所有 byte options 接受内联 hex、0x 前缀、@path/to/file 或直接存在的文件路径;文件内容仍须是 hex 文本,解析会去掉 ASCII whitespace。
Guest 先解析旧 state 和 tip witness,并通过对应网络的 prover_guest_verify_block_transition_detailed 检查 block/witness transition。随后它检查旧/新 Solana header 的 finalized block hash、Merkle root、auto-claim roots/index 和 block height与验证结果一致。第 11 个 finalized-height witness 为空时,新 header 的 pending-mint/TXO hashes 必须等于规范空 hash;非空时,guest 将其 block hash 和 Merkle root 绑定到验证后的 new_finalized_state,再用相同 manager-custody profile 与 fee 参数重算并绑定这两个 hash。
最后 guest 与 host 使用同一 public-value 公式:
old_header_hash = SHA256(old_header[320])
new_header_hash = SHA256(new_header[320])
config_hash = SHA256(config_params[48])
custodian_hash = CustodyScriptConfig(custody_script_config[32]).hash()
transition_hash = SHA256(old_header_hash || new_header_hash)
public_value = SHA256(transition_hash || config_hash || custodian_hash)
提交值为 32 bytes。Host 独立计算同一公式,要求 proof.public_values 完全相等,然后才把 proof/public values 写到 stdout(或 daemon JSON 响应)。
--network 是所有 gen-proof 调用的必填参数。
完整 proof invocation:
cargo run --release -p psy-bridge-sp1-script --bin gen-proof -- \
--network regtest \
--old-state <hex-or-@file> \
--witness <hex-or-@file> \
--finalized-witness <hex-or-@file-or-empty> \
--custody-script-config <32-byte-hex-or-@file> \
--required-confirmations <u32> \
--flat-fee <u64> \
--fee-num <u64> \
--fee-den <u64> \
--old-header <320-byte-hex-or-@file> \
--new-header <320-byte-hex-or-@file> \
--config-params <48-byte-hex-or-@file>testnet guest:
cargo run --release -p psy-bridge-sp1-script --bin gen-proof -- \
--network testnet \
--old-state <hex-or-@file> \
--witness <hex-or-@file> \
--finalized-witness <hex-or-@file-or-empty> \
--custody-script-config <32-byte-hex-or-@file> \
--required-confirmations <u32> \
--flat-fee <u64> \
--fee-num <u64> \
--fee-den <u64> \
--old-header <320-byte-hex-or-@file> \
--new-header <320-byte-hex-or-@file> \
--config-params <48-byte-hex-or-@file>只需从当前 release ELF 导出 program VK 时,可运行:
cargo run --release -p psy-bridge-sp1-script --bin gen-proof -- --network regtest --vkey-only
cargo run --release -p psy-bridge-sp1-script --bin gen-proof -- --network testnet --vkey-only该模式输出 network、guest_id、block_elf_sha256 与 vkey_hash,不生成 proof artifact。
stdio daemon:
cargo run --release -p psy-bridge-sp1-script --bin gen-proof -- \
--network regtest \
--daemon可选 deadline 覆盖(秒,必须非零):
--setup-timeout-secs <u64> # 默认 600
--execute-timeout-secs <u64> # 默认 900
--prove-timeout-secs <u64> # 默认 7200gen-proof 对每次证明按固定顺序执行:
- setup —
client.setup(ELF)生成 proving/verifying key(daemon 启动时一次;one-shot/--vkey-only每次进程内一次); - execute —
client.execute(ELF, stdin)dry-run guest,要求 exit code 0; - prove —
client.prove(...).groth16().await生成完整 356-byte Groth16 proof; - verify —
client.verify(&proof, verifying_key, None)做 SP1 SDK 自验证; - public-values compare — host 独立计算 public-value 公式,要求与
proof.public_values逐字节相等; - stdout / JSON — 成功后才输出:
- one-shot:
network、guest_id、block_elf_sha256、proof_size、完整proof_byteshex、public_values_size、完整public_valueshex、vkey_hash; - daemon:每个成功响应 JSON 携带同样字段(加
request_id/ok: true)。
- one-shot:
不写共享 /tmp artifact,因此并行的 host/daemon 进程不会互相覆盖结果。调用方应直接消费 stdout/JSON 中的 proof 与 public values 字节。
Daemon 启动成功后先写一行 path-independent identity JSON,再读 stdin 请求:
{
"kind": "identity",
"network": "regtest",
"guest_id": "block-transition",
"block_elf_sha256": "<64-hex>",
"vkey_hash": "<0x-or-hex VK>"
}Identity / proof 契约字段:
| 字段 | 含义 |
|---|---|
network |
显式 --network:regtest 或 testnet |
guest_id |
稳定 guest 标识:block-transition / block-transition-testnet(不依赖构建机绝对路径) |
block_elf_sha256 |
嵌入 guest ELF 的 SHA-256 |
vkey_hash |
SP1 program verifying key hash |
跨仓接口调整(IBC 仍期望 path):当前 IBC ProverIdentityResponse / validate_prover_elf 仍反序列化并 canonicalize block_elf_path,与配置的 SP1_BLOCK_ELF_PATH 做路径相等比较。本仓 identity 已改为 path-independent(guest_id + ELF SHA-256 + VK + network),不再输出 block_elf_path。IBC 仓需要改为:
- 接受/要求
guest_id(或至少不再 requireblock_elf_path); - 用
block_elf_sha256(以及network/vkey_hash)校验嵌入 guest,而不是比较构建产物绝对路径; - 可选保留本地
SP1_BLOCK_ELF_PATH仅作 operator 侧证据归档,不再作为 daemon 握手硬条件。
本批 不修改 IBC 仓;在 IBC 完成上述适配前,直接对接本仓新 identity 的 pipeline 会在 identity 校验处失败。
Deadline / timeout 行为:
- setup(daemon 启动)、单请求 execute、单请求 prove 各自有硬 wall-clock deadline;
- 超时后 daemon/one-shot 先写出结构化 error(daemon:
{"kind":"proof","ok":false,"request_id":...,"error":"... timed out ..."}),然后 进程以 exit code 75 退出; - 设计意图是 fail-fast:取消 in-flight CUDA future 不能可靠释放底层 GPU 资源,因此超时后不在同一进程继续服务后续请求,由 supervisor 拉起干净进程。
- 非 timeout 的输入/guest/证明错误返回结构化
ok: false后继续读下一行请求(daemon),不会永久卡在 Pending。
Withdrawal proof guest、host binary、public-input ABI 和 verification key 已删除。当前 withdrawal 授权由 Solana instruction 直接验证:request_withdrawal burn 后由 operator 调用 bounded snapshot_withdrawals,范围 multiproof 将请求叶严格绑定到 outputs-only UTX0 payload;Wormhole VAA 传递该 payload,5-of-7 Manager signatures 授权 Dogecoin broadcast,Electrs 确认后由 operator 在 OperatorStore 中原子落账为 Confirmed/Spent。
因此 withdrawal 不产生 SP1 proof/public-values artifact,也没有 withdrawal VK、guest 或 prover runtime dependency;block_update 是唯一 ZK 路径。
当前 Solana verifier 接受的 proof 是 356 bytes,不是只取 Groth16 body 的 256 bytes:
4-byte Groth16 circuit-VK hash prefix
+ 96-byte SP1 v6 metadata
+ 256-byte Groth16 proof body
= 356 bytes
因此:
- 不要沿用旧 256-byte 假设;
- 不要删除前 100 bytes;
- 不要把其他 workspace 的 SP1 v5/v6 proof 或 VK 与本 workspace 混用;
- proof length 正确仍不足以证明兼容,VK hash、guest ELF 与 public values 也必须匹配。
顺序见上文 Proof 执行顺序。这建立了 SP1 SDK 层的本机验证。链上兼容性还需要 non-mock-zkp 的 psy-doge-solana-bridge 使用 block-transition key 验证完整 356-byte proof。Withdrawal lifecycle 不进入 SP1 verifier。
- prover 被硬编码为
.cuda();当前没有 CLI flag 切换 CPU 或网络 prover。 - 首次运行包含大量依赖和 guest ELF build;后续缓存命中后 host 启动更快,
--daemon还能复用已加载的 proving key/CUDA runtime。 - GPU 时间依赖硬件、驱动、系统负载和缓存,不应把某台机器的秒数写成保证值。
- 自动化应给 proof job 独立长 timeout(可与
--setup-timeout-secs/--execute-timeout-secs/--prove-timeout-secs对齐或略宽),并监控进程退出状态与 stdout/JSON 中的proof_size/public_values_size(期望356/32)。 - 不要依赖共享
/tmpproof 文件、mtime 或“固定路径被覆盖”来判断成功;gen-proof只通过 stdout/JSON 交付结果。
当前真实本地 smoke / IBC block pipeline 直接执行预构建的 prover:
psy-bridge-sp1/target/release/gen-proof --network <regtest|testnet> ...桥侧 VK 定义位于:
psy-doge-solana-bridge/programs/doge-bridge/src/processor.rs
必须使用 non-mock build。标准 Makefile/大量 legacy tests 默认启用 mock-zkp;这些测试能覆盖状态机和 buffer 流程,但不能证明本 README 所述 proof 被密码学验证。
原因通常是实际执行的 Cargo/Rust 没有使用本目录 rust-toolchain 指向的新 nightly。进入 workspace 根目录重试,并确认 rustc --version、cargo --version。不要通过降级 SP1 crate 来掩盖工具链不匹配。
安装 Protocol Buffers compiler,确认 protoc --version 后重新 build。仅安装 Rust protobuf crate 不会提供系统 protoc binary。
确认 SP1/Succinct toolchain 已安装且可由 sp1-build 调用。删除/覆盖 host binary 不能修复缺失的 guest toolchain。
custody-script-config / old-header / new-header / config-params 分别必须解码为 32 / 320 / 320 / 48 bytes。old-state 与 witness 是可变长度编码,但必须能被 guest 的 Borsh/Speedy parser 完整解析;所有文件参数必须包含 hex 文本。
这是旧 ABI/截断 proof。当前完整输出必须是 356 bytes。重新使用 SP1 v6.3.1 CLI 生成,不要手工抽取 Groth16 body。
按顺序核对:
- CLI stdout / identity JSON 的
vkey_hash; processor.rs的SINGLE_BLOCK_UPDATE_VK;- validator 实际部署的
doge_bridge.so是否是刚构建的 non-mock ELF; - proof 与 public values 是否来自同一次、同一输入运行;
guest_id/block_elf_sha256/--network是否与部署 profile 一致;- 是否错误复用了 SP1 v5、scrypt guest 或其他 workspace 的 proof。
切换 feature/VK 后必须重建并重启 validator/重新部署。target 中存在新文件不代表链上 program 已更新。
这是链上交易 compute budget,不是 SP1 SDK 自验证失败。提交 real Groth16 verification 时增加 Solana compute-unit limit;不要为通过测试而改用 mock-zkp。
表示对应阶段超过硬 deadline。daemon 会先返回结构化 ok: false error,再以 exit code 75 退出以便 supervisor 重启。不要假设取消 CUDA future 后同一进程仍可安全服务;调高 timeout flag 或排查 GPU 负载后重启 prover。
本仓已去掉 path-dependent block_elf_path。在 IBC 按上文跨仓契约改为 guest_id + ELF SHA-256 校验之前,旧 pipeline 会在 identity handshake 失败。这是预期的接口切割,不是 prover 回归。
- 代码和本地 smoke 未经生产审计,不应直接用于真实资产。
- block guest 在 zkVM 内执行 helper 的 block/witness transition verification,并把验证结果锚定到 Solana header 的共识字段;pending-mint/TXO-buffer commitments 仍依赖链上 buffer checks。
block-transition使用DogeRegTestConfig,block-transition-testnet使用DogeTestNetConfig;proof、VK、bridge deployment 与 IBC--network必须选择同一 profile。- Withdrawal 的安全性来自链上 request/snapshot 约束与范围 multiproof、Wormhole VAA、Manager quorum、Dogecoin confirmation,以及 OperatorStore 的原子状态转换,而非 ZK。
跨仓库完整本地验证由 psy-doge-solana-cli 维护,唯一公开入口为 doge-solana-cli --network localhost local-e2e;tools/local/runner.ts 仅是其内部实现。生产/devnet 操作命令使用同一二进制的 --network devnet,且不启动任何本地进程;部署为独立的 tools/deploy/devnet.ts --network devnet。