diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 554b2c10..28495239 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,10 +47,13 @@ jobs: profile: minimal toolchain: nightly-2025-12-11 override: true + components: clippy + - run: curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/ProjectZKM/toolchain/refs/heads/main/setup.sh | sh - name: Install Dependencies run: sudo apt update && sudo apt install protobuf-compiler - - run: rustup component add clippy - - run: cargo clippy --all-targets -- -D warnings + - run: | + source ~/.zkm-toolchain/env + cargo clippy --all-targets -- -D warnings test: name: Cargo Test runs-on: ubuntu-latest @@ -67,7 +70,7 @@ jobs: - name: Launch the Regtest run: cd scripts && docker compose up -d - name: Run all unit tests - run: | + run: | set -e source ~/.zkm-toolchain/env cargo test -r --all --all-targets diff --git a/.gitignore b/.gitignore index 13c8f4ab..6d9263a0 100644 --- a/.gitignore +++ b/.gitignore @@ -34,4 +34,6 @@ circuits/*/*/*.bin.in **/*.out **/*/output.data* -proof-builder-rpc/*.ckpt \ No newline at end of file +proof-builder-rpc/*.ckpt + +runner \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 8f8f2862..486c0f9d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -44,7 +44,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "generic-array 0.14.7", ] @@ -1325,7 +1325,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1336,7 +1336,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -1345,6 +1345,15 @@ version = "1.0.102" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" +[[package]] +name = "arc-swap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] + [[package]] name = "ark-bls12-381" version = "0.5.0" @@ -1995,6 +2004,49 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "aws-config" +version = "1.8.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e33f815b73a3899c03b380d543532e5865f230dce9678d108dc10732a8682275" +dependencies = [ + "aws-credential-types", + "aws-runtime", + "aws-sdk-sso", + "aws-sdk-ssooidc", + "aws-sdk-sts", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "hex", + "http 1.4.0", + "sha1 0.10.6", + "time", + "tokio", + "tracing", + "url", + "zeroize", +] + +[[package]] +name = "aws-credential-types" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f20799b373a1be121fe3005fba0c2090af9411573878f224df44b42727fcaf7" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "zeroize", +] + [[package]] name = "aws-lc-rs" version = "1.16.2" @@ -2017,6 +2069,414 @@ dependencies = [ "fs_extra", ] +[[package]] +name = "aws-runtime" +version = "1.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ed8e8c52d2dc2390ad9f15647fe663f71e9780b4262c190fbb823a32721566" +dependencies = [ + "aws-credential-types", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "bytes-utils", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "percent-encoding", + "pin-project-lite", + "tracing", + "uuid 1.23.0", +] + +[[package]] +name = "aws-sdk-s3" +version = "1.135.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f97e3e7e7d86fd26fcdc18bc382da5ca9e8b2ff8d54030d187fd0dac8a236d96" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-sigv4", + "aws-smithy-async", + "aws-smithy-checksums", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "bytes", + "fastrand", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.0", + "http-body 1.0.1", + "lru 0.16.3", + "percent-encoding", + "regex-lite", + "sha2 0.11.0", + "tracing", + "url", +] + +[[package]] +name = "aws-sdk-sso" +version = "1.101.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b647baea49ff551960b904f905681e9b4765a6c4ea08631e89dc52d8bd3f5896" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-ssooidc" +version = "1.103.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ae401c65ff288aa7873117fe535cd32b7b1bb0bc43751d28901a1d5f20636b9" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sdk-sts" +version = "1.106.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c80de7bb7d03e9ca8c9fd7b489f20f3948d3f3be91a7953591347d238115408" +dependencies = [ + "arc-swap", + "aws-credential-types", + "aws-runtime", + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-json", + "aws-smithy-observability", + "aws-smithy-query", + "aws-smithy-runtime", + "aws-smithy-runtime-api", + "aws-smithy-types", + "aws-smithy-xml", + "aws-types", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "regex-lite", + "tracing", +] + +[[package]] +name = "aws-sigv4" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bae38512beae0ffee7010fc24e7a8a123c53efdfef42a61e80fda4882418dc71" +dependencies = [ + "aws-credential-types", + "aws-smithy-eventstream", + "aws-smithy-http", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "crypto-bigint", + "form_urlencoded", + "hex", + "hmac 0.13.0", + "http 0.2.12", + "http 1.4.0", + "p256", + "percent-encoding", + "sha2 0.11.0", + "subtle", + "time", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-async" +version = "1.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffcaf626bdda484571968400c326a244598634dc75fd451325a54ad1a59acfc" +dependencies = [ + "futures-util", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "aws-smithy-checksums" +version = "0.64.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9e8e65f4f81fcccdeb6c3eca2af17ac21d421a1786a26a394aecf421d616d3a" +dependencies = [ + "aws-smithy-http", + "aws-smithy-types", + "bytes", + "crc-fast", + "hex", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "md-5 0.11.0", + "pin-project-lite", + "sha1 0.11.0", + "sha2 0.11.0", + "tracing", +] + +[[package]] +name = "aws-smithy-eventstream" +version = "0.60.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "faf09d74e5e32f76b8762da505a3cd59303e367a664ca67295387baa8c1d7548" +dependencies = [ + "aws-smithy-types", + "bytes", + "crc32fast", +] + +[[package]] +name = "aws-smithy-http" +version = "0.63.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba1ab2dc1c2c3749ead27180d333c42f11be8b0e934058fb4b2258ee8dbe5231" +dependencies = [ + "aws-smithy-eventstream", + "aws-smithy-runtime-api", + "aws-smithy-types", + "bytes", + "bytes-utils", + "futures-core", + "futures-util", + "http 1.4.0", + "http-body 1.0.1", + "http-body-util", + "percent-encoding", + "pin-project-lite", + "pin-utils", + "tracing", +] + +[[package]] +name = "aws-smithy-http-client" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3ef8931ad1c98aa6a55b4256f847f3116090819844e0dd41ea682cac5dd2d3" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-types", + "h2 0.3.27", + "h2 0.4.13", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "hyper 0.14.32", + "hyper 1.9.0", + "hyper-rustls 0.24.2", + "hyper-rustls 0.27.7", + "hyper-util", + "pin-project-lite", + "rustls 0.21.12", + "rustls 0.23.37", + "rustls-native-certs 0.8.3", + "rustls-pki-types", + "tokio", + "tokio-rustls 0.26.4", + "tower 0.5.3", + "tracing", +] + +[[package]] +name = "aws-smithy-json" +version = "0.62.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "701a947f4797e52a911e114a898667c746c39feea467bbd1abd7b3721f702ffa" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", +] + +[[package]] +name = "aws-smithy-observability" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06c2315d173edbf1920da8ba3a7189695827002e4c0fc961973ab1c54abca9c" +dependencies = [ + "aws-smithy-runtime-api", +] + +[[package]] +name = "aws-smithy-query" +version = "0.60.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a56d79744fb3edb5d722ef79d86081e121d3b9422cb209eb03aea6aa4f21ebd" +dependencies = [ + "aws-smithy-types", + "urlencoding", +] + +[[package]] +name = "aws-smithy-runtime" +version = "1.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e6f5caf6fea86f8c2206541ab5857cfcda9013426cdbe8fa0098b9e2d32182" +dependencies = [ + "aws-smithy-async", + "aws-smithy-http", + "aws-smithy-http-client", + "aws-smithy-observability", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "bytes", + "fastrand", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "pin-project-lite", + "pin-utils", + "tokio", + "tracing", +] + +[[package]] +name = "aws-smithy-runtime-api" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9db177daa6ba8afb9ee1aefcf548c907abcf52065e394ee11a92780057fe0e8c" +dependencies = [ + "aws-smithy-async", + "aws-smithy-runtime-api-macros", + "aws-smithy-types", + "bytes", + "http 0.2.12", + "http 1.4.0", + "pin-project-lite", + "tokio", + "tracing", + "zeroize", +] + +[[package]] +name = "aws-smithy-runtime-api-macros" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d7396fd9500589e62e460e987ecb671bad374934e55ec3b5f498cc7a8a8a7b7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "aws-smithy-schema" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7442cb268338f0eb8278140a107c046756aa01093d8ef5e99628d34ae09c94f5" +dependencies = [ + "aws-smithy-runtime-api", + "aws-smithy-types", + "http 1.4.0", +] + +[[package]] +name = "aws-smithy-types" +version = "1.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f93074121a1be41317b9aa607143ae17900631f7f59a99f2b905d519d6783b" +dependencies = [ + "base64-simd", + "bytes", + "bytes-utils", + "futures-core", + "http 0.2.12", + "http 1.4.0", + "http-body 0.4.6", + "http-body 1.0.1", + "http-body-util", + "itoa", + "num-integer", + "pin-project-lite", + "pin-utils", + "ryu", + "serde", + "time", + "tokio", + "tokio-util", +] + +[[package]] +name = "aws-smithy-xml" +version = "0.60.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce02add1aa3677d022f8adf81dcbe3046a95f17a1b1e8979c145cd21d3d22b3" +dependencies = [ + "xmlparser", +] + +[[package]] +name = "aws-types" +version = "1.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16bf10b03a3c01e6b3b7d47cd964e873ffe9e7d4e80fad16bd4c077cb068531" +dependencies = [ + "aws-credential-types", + "aws-smithy-async", + "aws-smithy-runtime-api", + "aws-smithy-schema", + "aws-smithy-types", + "rustc_version 0.4.1", + "tracing", +] + [[package]] name = "axum" version = "0.6.20" @@ -2225,8 +2685,8 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" dependencies = [ - "bitcoin-internals 0.3.0", - "bitcoin_hashes 0.14.1", + "bitcoin-internals", + "bitcoin_hashes", ] [[package]] @@ -2247,6 +2707,16 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339abbe78e73178762e23bea9dfd08e697eb3f3301cd4be981c0f78ba5859195" +dependencies = [ + "outref", + "vsimd", +] + [[package]] name = "base64ct" version = "1.8.3" @@ -2259,12 +2729,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d86b93f97252c47b41663388e6d155714a9d0c398b99f1005cbc5f978b29f445" -[[package]] -name = "bech32" -version = "0.10.0-beta" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98f7eed2b2781a6f0b5c903471d48e15f56fb4e1165df8a9a2337fd1a59d45ea" - [[package]] name = "bech32" version = "0.11.1" @@ -2295,7 +2759,7 @@ dependencies = [ "bitflags 2.11.0", "cexpr", "clang-sys", - "itertools 0.13.0", + "itertools 0.10.5", "log", "prettyplease 0.2.37", "proc-macro2", @@ -2360,20 +2824,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "bitcoin" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69197dee21fe23b45f5239bf88086efaa0cb8679f3e704906eb818e8ea169c14" -dependencies = [ - "bech32 0.10.0-beta", - "bitcoin-internals 0.2.1", - "bitcoin_hashes 0.13.1", - "hex-conservative 0.1.2", - "hex_lit", - "secp256k1 0.28.2", -] - [[package]] name = "bitcoin" version = "0.32.8" @@ -2382,22 +2832,16 @@ checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" dependencies = [ "base58ck", "bech32 0.11.1", - "bitcoin-internals 0.3.0", + "bitcoin-internals", "bitcoin-io", "bitcoin-units", - "bitcoin_hashes 0.14.1", - "hex-conservative 0.2.2", + "bitcoin_hashes", + "hex-conservative", "hex_lit", "secp256k1 0.29.1", "serde", ] -[[package]] -name = "bitcoin-internals" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "994dc6fcc13751c85370b7de118e672b193b9b65167bf09e258f124c97fb9685" - [[package]] name = "bitcoin-internals" version = "0.3.0" @@ -2415,12 +2859,12 @@ checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-light-client-circuit" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", "bincode", - "bitcoin 0.32.8", + "bitcoin", "blake3", "borsh", "commit-chain", @@ -2432,11 +2876,11 @@ dependencies = [ "revm-database-interface", "serde", "serde_json", - "sha2 0.10.9", "state-chain", "tendermint", "tendermint-light-client-verifier", "tracing", + "verifier", "zkm-primitives", "zkm-verifier", "zkm-zkvm", @@ -2447,7 +2891,7 @@ name = "bitcoin-script" version = "0.4.0" source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" dependencies = [ - "bitcoin 0.32.8", + "bitcoin", "script-macro", "stdext", ] @@ -2457,7 +2901,7 @@ name = "bitcoin-script-stack" version = "0.0.1" source = "git+https://github.com/BitVM/rust-bitcoin-script-stack#643c5f1a44af448274849c01a5ae7fbdd54d8213" dependencies = [ - "bitcoin 0.32.8", + "bitcoin", "bitcoin-script", "bitcoin-scriptexec", ] @@ -2467,7 +2911,7 @@ name = "bitcoin-scriptexec" version = "0.0.0" source = "git+https://github.com/BitVM/rust-bitcoin-scriptexec#ba96bc2bd76774c9d1b011461cb79d983c2c43a1" dependencies = [ - "bitcoin 0.32.8", + "bitcoin", "clap", "console_error_panic_hook", "getrandom 0.2.17", @@ -2484,19 +2928,10 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5285c8bcaa25876d07f37e3d30c303f2609179716e11d688f51e8f1fe70063e2" dependencies = [ - "bitcoin-internals 0.3.0", + "bitcoin-internals", "serde", ] -[[package]] -name = "bitcoin_hashes" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "446819536d8121575eeb7e89efdbadb3f055e87e4bb66c6679a6d5cc2f4b64fd" -dependencies = [ - "hex-conservative 0.1.2", -] - [[package]] name = "bitcoin_hashes" version = "0.14.1" @@ -2504,32 +2939,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" dependencies = [ "bitcoin-io", - "hex-conservative 0.2.2", - "serde", -] - -[[package]] -name = "bitcoincore-rpc" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aedd23ae0fd321affb4bbbc36126c6f49a32818dc6b979395d24da8c9d4e80ee" -dependencies = [ - "bitcoincore-rpc-json", - "jsonrpc", - "log", - "serde", - "serde_json", -] - -[[package]] -name = "bitcoincore-rpc-json" -version = "0.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8909583c5fab98508e80ef73e5592a651c954993dc6b7739963257d19f0e71a" -dependencies = [ - "bitcoin 0.32.8", + "hex-conservative", "serde", - "serde_json", ] [[package]] @@ -2573,7 +2984,7 @@ dependencies = [ "ark-relations", "ark-serialize 0.5.0", "ark-std 0.5.0", - "bitcoin 0.32.8", + "bitcoin", "bitcoin-script", "bitcoin-script-stack", "bitcoin-scriptexec", @@ -2592,42 +3003,75 @@ dependencies = [ ] [[package]] -name = "bitvm2-lib" -version = "0.3.3" +name = "bitvm" +version = "0.1.0" +source = "git+https://github.com/blake-pro/BitVM.git?branch=fix-gc#9ab670396e5fab825d5b05911b7bf95fd8daa475" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "bitcoin", + "bitcoin-script", + "bitcoin-script-stack", + "bitcoin-scriptexec", + "blake3", + "colored", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rand 0.8.5", + "rand_chacha 0.3.1", + "regex", + "serde", + "sha2 0.10.9", + "tqdm", +] + +[[package]] +name = "bitvm-gc" +version = "0.4.0" dependencies = [ "anyhow", "ark-bn254", + "ark-crypto-primitives", + "ark-ff 0.5.0", "ark-groth16", "ark-serialize 0.5.0", "bincode", - "bitcoin 0.31.3", - "bitcoin 0.32.8", - "bitcoin-light-client-circuit", + "bitcoin", "bitcoin-script", - "bitcoincore-rpc", - "bitvm", + "bitvm 0.1.0 (git+https://github.com/blake-pro/BitVM.git?branch=fix-gc)", "chacha20poly1305", "clap", - "client", - "esplora-client", + "garbled-snark-verifier", "goat", "hex", "hkdf", "musig2", "rand 0.8.5", + "rand_chacha 0.3.1", + "rayon", "secp256k1 0.29.1", "serde", + "serde-big-array", "serde_json", "sha2 0.10.9", + "soldering-host", "strum 0.26.3", - "tokio", "tracing", "uuid 1.23.0", + "verifiable-circuit-babe", ] [[package]] -name = "bitvm2-noded" -version = "0.3.3" +name = "bitvm-noded" +version = "0.4.0" dependencies = [ "alloy", "anyhow", @@ -2635,14 +3079,16 @@ dependencies = [ "ark-groth16", "ark-serialize 0.5.0", "async-trait", + "aws-config", + "aws-sdk-s3", "axum 0.8.8", "base64 0.21.7", "bincode", - "bitcoin 0.32.8", + "bitcoin", "bitcoin-light-client-circuit", "bitcoin-script", - "bitvm", - "bitvm2-lib", + "bitvm 0.1.0 (git+https://github.com/blake-pro/BitVM.git?branch=fix-gc)", + "bitvm-gc", "borsh", "cbft-rpc", "clap", @@ -2663,12 +3109,13 @@ dependencies = [ "libp2p-swarm-derive", "musig2", "once_cell", + "p3-bn254-fr", + "p3-field", "prometheus-client", "proof-builder", "rand 0.8.5", "reqwest 0.12.28", "secp256k1 0.29.1", - "semver 1.0.28", "serde", "serde_json", "sha2 0.10.9", @@ -2686,7 +3133,9 @@ dependencies = [ "util", "uuid 1.23.0", "zeroize", + "zkm-recursion-core", "zkm-sdk", + "zkm-stark", "zkm-verifier", ] @@ -2742,6 +3191,15 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "block-buffer" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdd35008169921d80bc60d3d0ab416eecb028c4cd653352907921d95084790be" +dependencies = [ + "hybrid-array", +] + [[package]] name = "block2" version = "0.6.2" @@ -2884,6 +3342,16 @@ dependencies = [ "serde", ] +[[package]] +name = "bytes-utils" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dafe3a8757b027e2be6e4e5601ed563c55989fcf1546e933c66c8eb3a058d35" +dependencies = [ + "bytes", + "either", +] + [[package]] name = "bzip2" version = "0.4.4" @@ -2953,7 +3421,7 @@ dependencies = [ [[package]] name = "cbft-rpc" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "async-trait", @@ -3065,7 +3533,7 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "inout", "zeroize", ] @@ -3123,12 +3591,12 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "client" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy", "anyhow", "async-trait", - "bitcoin 0.32.8", + "bitcoin", "esplora-client", "hex", "rand 0.8.5", @@ -3151,6 +3619,12 @@ dependencies = [ "cc", ] +[[package]] +name = "cmov" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c9ea0ac24bc397ab3c98583a3c9ba74fa56b09a4449bbe172b9b1ddb016027a" + [[package]] name = "coins-bip32" version = "0.8.7" @@ -3160,7 +3634,7 @@ dependencies = [ "bs58", "coins-core", "digest 0.10.7", - "hmac", + "hmac 0.12.1", "k256", "serde", "sha2 0.10.9", @@ -3175,7 +3649,7 @@ checksum = "3db8fba409ce3dc04f7d804074039eb68b960b0829161f8e06c95fea3f122528" dependencies = [ "bitvec", "coins-bip32", - "hmac", + "hmac 0.12.1", "once_cell", "pbkdf2 0.12.2", "rand 0.8.5", @@ -3195,7 +3669,7 @@ dependencies = [ "digest 0.10.7", "generic-array 0.14.7", "hex", - "ripemd", + "ripemd 0.1.3", "serde", "serde_derive", "sha2 0.10.9", @@ -3231,12 +3705,12 @@ dependencies = [ [[package]] name = "commit-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", "bincode", - "bitcoin 0.32.8", + "bitcoin", "blake3", "borsh", "guest-executor", @@ -3250,17 +3724,17 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] [[package]] name = "commit-chain-proof" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "bincode", - "bitcoin 0.32.8", + "bitcoin", "clap", "client", "commit-chain", @@ -3330,6 +3804,12 @@ version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +[[package]] +name = "const-oid" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" + [[package]] name = "const-str" version = "0.4.3" @@ -3457,6 +3937,16 @@ version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5" +[[package]] +name = "crc-fast" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e75b2483e97a5a7da73ac68a05b629f9c53cff58d8ed1c77866079e18b00dba5" +dependencies = [ + "digest 0.10.7", + "spin 0.10.0", +] + [[package]] name = "crc32fast" version = "1.5.0" @@ -3569,6 +4059,15 @@ dependencies = [ "typenum", ] +[[package]] +name = "crypto-common" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6e4c961d6cd6c9a86db418387425e8bdeaf05b3c8bc1411e6dca4c252f1453" +dependencies = [ + "hybrid-array", +] + [[package]] name = "csv" version = "1.4.0" @@ -3610,6 +4109,15 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "ctutils" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5515a3834141de9eafb9717ad39eea8247b5674e6066c404e8c4b365d2a29e" +dependencies = [ + "cmov", +] + [[package]] name = "curve25519-dalek" version = "4.1.3" @@ -3845,7 +4353,7 @@ version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ - "const-oid", + "const-oid 0.9.6", "pem-rfc7468", "zeroize", ] @@ -3966,11 +4474,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", - "const-oid", - "crypto-common", + "const-oid 0.9.6", + "crypto-common 0.1.7", "subtle", ] +[[package]] +name = "digest" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1dd6dbb5841937940781866fa1281a1ff7bd3bf827091440879f9994983d5c2" +dependencies = [ + "block-buffer 0.12.0", + "const-oid 0.10.2", + "crypto-common 0.2.2", + "ctutils", +] + [[package]] name = "dirs" version = "5.0.1" @@ -4304,7 +4824,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -4312,8 +4832,8 @@ name = "esplora-client" version = "0.11.0" source = "git+https://github.com/BitVM/rust-esplora-client#a29ee89e6fa003655e179615405761b27e67b973" dependencies = [ - "bitcoin 0.32.8", - "hex-conservative 0.2.2", + "bitcoin", + "hex-conservative", "log", "minreq", "reqwest 0.11.27", @@ -4343,7 +4863,7 @@ dependencies = [ "ctr", "digest 0.10.7", "hex", - "hmac", + "hmac 0.12.1", "pbkdf2 0.11.0", "rand 0.8.5", "scrypt", @@ -4632,7 +5152,7 @@ dependencies = [ "ethers-core", "glob", "home", - "md-5", + "md-5 0.10.6", "num_cpus", "once_cell", "path-slash", @@ -5053,6 +5573,33 @@ dependencies = [ "byteorder", ] +[[package]] +name = "garbled-snark-verifier" +version = "0.1.0" +source = "git+https://github.com/GOATNetwork/bitvm2-gc?branch=feat%2Fbabe#834d4337341e14da569c8c98c9fff546c9aedab6" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-relations", + "ark-serialize 0.5.0", + "bincode", + "blake3", + "getrandom 0.2.17", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "once_cell", + "poseidon2", + "rand 0.8.5", + "rand_chacha 0.3.1", + "rayon", + "serde", + "serde_json", + "serial_test", +] + [[package]] name = "gcd" version = "2.3.0" @@ -5190,7 +5737,7 @@ dependencies = [ [[package]] name = "goat" version = "0.1.0" -source = "git+https://github.com/GOATNetwork/BitVM.git?branch=GA#26b0bd61b61b24b50b2d2443a7fda8e58412edfa" +source = "git+https://github.com/blake-pro/BitVM.git?branch=fix-gc#9ab670396e5fab825d5b05911b7bf95fd8daa475" dependencies = [ "ark-bn254", "ark-crypto-primitives", @@ -5202,10 +5749,10 @@ dependencies = [ "ark-std 0.5.0", "bincode", "bitcode", - "bitcoin 0.32.8", + "bitcoin", "bitcoin-script", "bitcoin-scriptexec", - "bitvm", + "bitvm 0.1.0 (git+https://github.com/blake-pro/BitVM.git?branch=fix-gc)", "clap", "colored", "hex", @@ -5217,6 +5764,7 @@ dependencies = [ "reqwest 0.11.27", "secp256k1 0.29.1", "serde", + "serde-big-array", "serde_json", "serial_test", "sha2 0.10.9", @@ -5471,28 +6019,28 @@ dependencies = [ [[package]] name = "header-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ - "bitcoin 0.32.8", + "bitcoin", "borsh", "crypto-bigint", "hex-literal", "serde", "sha2 0.10.9", - "zkm-verifier", + "verifier", "zkm-zkvm", ] [[package]] name = "header-chain-proof" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", "ark-bn254", "ark-groth16", "ark-serialize 0.5.0", "bincode", - "bitcoin 0.32.8", + "bitcoin", "borsh", "clap", "client", @@ -5541,12 +6089,6 @@ dependencies = [ "serde", ] -[[package]] -name = "hex-conservative" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "212ab92002354b4819390025006c897e8140934349e8635c9b077f47b4dcbd20" - [[package]] name = "hex-conservative" version = "0.2.2" @@ -5672,7 +6214,7 @@ version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" dependencies = [ - "hmac", + "hmac 0.12.1", ] [[package]] @@ -5684,6 +6226,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6303bc9732ae41b04cb554b844a762b4115a61bfaa81e3e83050991eeb56863f" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "home" version = "0.5.12" @@ -5812,6 +6363,15 @@ version = "1.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +[[package]] +name = "hybrid-array" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3944cf8cf766b40e2a1a333ee5e9b563f854d5fa49d6a8ca2764e97c6eddb214" +dependencies = [ + "typenum", +] + [[package]] name = "hyper" version = "0.14.32" @@ -5867,6 +6427,7 @@ dependencies = [ "futures-util", "http 0.2.12", "hyper 0.14.32", + "log", "rustls 0.21.12", "tokio", "tokio-rustls 0.24.1", @@ -5882,6 +6443,7 @@ dependencies = [ "hyper 1.9.0", "hyper-util", "rustls 0.23.37", + "rustls-native-certs 0.8.3", "rustls-pki-types", "tokio", "tokio-rustls 0.26.4", @@ -6429,18 +6991,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "jsonrpc" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3662a38d341d77efecb73caf01420cfa5aa63c0253fd7bc05289ef9f6616e1bf" -dependencies = [ - "base64 0.13.1", - "minreq", - "serde", - "serde_json", -] - [[package]] name = "jsonwebtoken" version = "8.3.0" @@ -7262,6 +7812,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "md-5" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69b6441f590336821bb897fb28fc622898ccceb1d6cea3fde5ea86b090c4de98" +dependencies = [ + "cfg-if", + "digest 0.11.3", +] + [[package]] name = "memchr" version = "2.8.0" @@ -7479,7 +8039,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baebdafa62ee63bfbb09d9e9664e9f8ba3f5765efcc813c97a92aebdaa06b8a5" dependencies = [ "base16ct", - "hmac", + "hmac 0.12.1", "once_cell", "rand 0.8.5", "secp", @@ -7622,7 +8182,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -8041,7 +8601,7 @@ dependencies = [ [[package]] name = "operator-proof" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "alloy-provider 1.0.41", @@ -8050,10 +8610,10 @@ dependencies = [ "ark-groth16", "ark-serialize 0.5.0", "bincode", - "bitcoin 0.32.8", + "bitcoin", "bitcoin-light-client-circuit", "bitcoin-script", - "bitvm2-lib", + "bitvm-gc", "borsh", "cbft-rpc", "chrono", @@ -8086,12 +8646,41 @@ dependencies = [ "zkm-verifier", ] +[[package]] +name = "operator-wrapper-proof" +version = "0.4.0" +dependencies = [ + "anyhow", + "bincode", + "bitcoin", + "bitcoin-light-client-circuit", + "clap", + "dotenv", + "hex", + "proof-builder", + "serde", + "sha2 0.10.9", + "tokio", + "tracing", + "tracing-subscriber 0.3.23", + "zkm-build", + "zkm-primitives", + "zkm-prover", + "zkm-sdk", +] + [[package]] name = "option-ext" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "p256" version = "0.13.2" @@ -8554,7 +9143,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83a0692ec44e4cf1ef28ca317f14f8f07da2d95ec3fa01f86e4467b725e60917" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", "password-hash", "sha2 0.10.9", ] @@ -8566,7 +9155,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" dependencies = [ "digest 0.10.7", - "hmac", + "hmac 0.12.1", ] [[package]] @@ -8974,6 +9563,18 @@ dependencies = [ "portable-atomic", ] +[[package]] +name = "poseidon2" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/poseidon2#1ab68f436e220df5870cd6e39006804d5c806279" +dependencies = [ + "cfg-if", + "p3-field", + "p3-koala-bear", + "p3-symmetric", + "zkm-primitives", +] + [[package]] name = "potential_utf" version = "0.1.5" @@ -9155,11 +9756,10 @@ dependencies = [ [[package]] name = "proof-builder" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", - "bitcoin 0.32.8", - "bitcoin-light-client-circuit", + "bitcoin", "commit-chain", "header-chain", "serde", @@ -9167,19 +9767,17 @@ dependencies = [ "state-chain", "strum 0.26.3", "thiserror 1.0.69", - "zkm-prover", "zkm-sdk", - "zkm-verifier", ] [[package]] name = "proof-builder-rpc" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "anyhow", "axum 0.8.8", - "bitcoin 0.32.8", + "bitcoin", "bitcoin-light-client-circuit", "clap", "client", @@ -9190,6 +9788,7 @@ dependencies = [ "header-chain-proof", "http-body-util", "operator-proof", + "operator-wrapper-proof", "prometheus-client", "proof-builder", "serde", @@ -9206,6 +9805,7 @@ dependencies = [ "util", "uuid 1.23.0", "watchtower-proof", + "zkm-sdk", ] [[package]] @@ -9275,7 +9875,7 @@ version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ - "heck 0.5.0", + "heck 0.4.1", "itertools 0.14.0", "log", "multimap 0.10.1", @@ -9674,6 +10274,12 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "regex-lite" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab834c73d247e67f4fae452806d17d3c7501756d98c8808d7c9c7aa7d18f973" + [[package]] name = "regex-syntax" version = "0.8.10" @@ -10448,7 +11054,7 @@ dependencies = [ "k256", "p256", "revm-primitives", - "ripemd", + "ripemd 0.1.3", "sha2 0.10.9", "substrate-bn 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -10481,7 +11087,7 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" dependencies = [ - "hmac", + "hmac 0.12.1", "subtle", ] @@ -10523,6 +11129,15 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "ripemd" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dd4211456b4172d7e44261920c25acf07367c4f04bb5f5d54fc21b090d9b159" +dependencies = [ + "digest 0.11.3", +] + [[package]] name = "rlp" version = "0.5.2" @@ -10576,7 +11191,7 @@ version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ - "const-oid", + "const-oid 0.9.6", "digest 0.10.7", "num-bigint-dig", "num-integer", @@ -10716,7 +11331,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys 0.12.1", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10819,7 +11434,7 @@ dependencies = [ "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -10980,7 +11595,7 @@ name = "script-macro" version = "0.4.0" source = "git+https://github.com/BitVM/rust-bitcoin-script#01b4cb66cbf5b525079cabe006f9f99627da97cd" dependencies = [ - "bitcoin 0.32.8", + "bitcoin", "proc-macro-error", "proc-macro2", "quote", @@ -10992,7 +11607,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f9e24d2b632954ded8ab2ef9fea0a0c769ea56ea98bddbafbad22caeeadf45d" dependencies = [ - "hmac", + "hmac 0.12.1", "pbkdf2 0.11.0", "salsa20", "sha2 0.10.9", @@ -11044,25 +11659,15 @@ dependencies = [ "subtle", ] -[[package]] -name = "secp256k1" -version = "0.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d24b59d129cdadea20aea4fb2352fa053712e5d713eee47d700cd4b2bc002f10" -dependencies = [ - "bitcoin_hashes 0.13.1", - "secp256k1-sys 0.9.2", -] - [[package]] name = "secp256k1" version = "0.29.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9465315bc9d4566e1724f0fffcbcc446268cb522e60f9a27bcded6b19c108113" dependencies = [ - "bitcoin_hashes 0.14.1", + "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.10.1", + "secp256k1-sys", "serde", ] @@ -11072,21 +11677,12 @@ version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" dependencies = [ - "bitcoin_hashes 0.14.1", + "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.10.1", + "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1-sys" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5d1746aae42c19d583c3c1a8c646bfad910498e2051c551a7f2e3c0c9fbb7eb" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.10.1" @@ -11182,6 +11778,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-big-array" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11fc7cc2c76d73e0f27ee52abbd64eec84d46f370c88371120433196934e4b7f" +dependencies = [ + "serde", +] + [[package]] name = "serde-wasm-bindgen" version = "0.6.5" @@ -11375,6 +11980,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha1" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aacc4cc499359472b4abe1bf11d0b12e688af9a805fa5e3016f9a386dc2d0214" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha2" version = "0.9.9" @@ -11399,6 +12015,17 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha2" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "446ba717509524cb3f22f17ecc096f10f4822d76ab5c0b9822c5f9c284e825f4" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.0", + "digest 0.11.3", +] + [[package]] name = "sha3" version = "0.10.8" @@ -11564,7 +12191,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -11579,7 +12206,7 @@ dependencies = [ "httparse", "log", "rand 0.8.5", - "sha1", + "sha1 0.10.6", ] [[package]] @@ -11596,6 +12223,25 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "soldering-host" +version = "1.1.0" +source = "git+https://github.com/GOATNetwork/bitvm2-gc?branch=feat%2Fbabe#834d4337341e14da569c8c98c9fff546c9aedab6" +dependencies = [ + "ark-bn254", + "ark-crypto-primitives", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "bitvm 0.1.0 (git+https://github.com/GOATNetwork/BitVM.git?branch=GA)", + "garbled-snark-verifier", + "rand 0.8.5", + "tracing", + "verifiable-circuit-babe", + "zkm-build", + "zkm-sdk", +] + [[package]] name = "spin" version = "0.5.2" @@ -11611,6 +12257,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "spin" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d5fe4ccb98d9c292d56fec89a5e07da7fc4cf0dc11e156b41793132775d3e591" + [[package]] name = "spki" version = "0.7.3" @@ -11729,17 +12381,17 @@ dependencies = [ "generic-array 0.14.7", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "itoa", "log", - "md-5", + "md-5 0.10.6", "memchr", "once_cell", "percent-encoding", "rand 0.8.5", "rsa", "serde", - "sha1", + "sha1 0.10.6", "sha2 0.10.9", "smallvec", "sqlx-core", @@ -11768,11 +12420,11 @@ dependencies = [ "futures-util", "hex", "hkdf", - "hmac", + "hmac 0.12.1", "home", "itoa", "log", - "md-5", + "md-5 0.10.6", "memchr", "once_cell", "rand 0.8.5", @@ -11821,13 +12473,13 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "state-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-consensus 1.0.41", "alloy-primitives", "base64 0.21.7", "bincode", - "bitcoin 0.32.8", + "bitcoin", "blake3", "borsh", "cosmos-sdk-proto", @@ -11845,13 +12497,13 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] [[package]] name = "state-chain-proof" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-consensus 1.0.41", "alloy-primitives", @@ -11903,10 +12555,10 @@ checksum = "4af28eeb7c18ac2dbdb255d40bee63f203120e1db6b0024b177746ebec7049c1" [[package]] name = "store" -version = "0.3.3" +version = "0.4.0" dependencies = [ "anyhow", - "bitcoin 0.32.8", + "bitcoin", "futures", "hex", "indexmap 2.14.0", @@ -12031,7 +12683,7 @@ dependencies = [ [[package]] name = "substrate-bn" version = "0.6.0" -source = "git+https://github.com/ziren-patches/bn.git?branch=patch-0.6.0#aba71380457d798039111e6cc0fdf2e0718c6766" +source = "git+https://github.com/ziren-patches/bn?branch=patch-0.6.0#aba71380457d798039111e6cc0fdf2e0718c6766" dependencies = [ "bytemuck", "byteorder", @@ -12224,7 +12876,7 @@ dependencies = [ "getrandom 0.4.2", "once_cell", "rustix 1.1.4", - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -12242,7 +12894,7 @@ dependencies = [ "num-traits", "once_cell", "prost 0.13.5", - "ripemd", + "ripemd 0.1.3", "serde", "serde_bytes", "serde_json", @@ -12955,7 +13607,7 @@ dependencies = [ "log", "rand 0.8.5", "rustls 0.21.12", - "sha1", + "sha1 0.10.6", "thiserror 1.0.69", "url", "utf-8", @@ -12975,7 +13627,7 @@ dependencies = [ "rand 0.9.3", "rustls 0.23.37", "rustls-pki-types", - "sha1", + "sha1 0.10.6", "thiserror 2.0.18", "utf-8", ] @@ -13110,7 +13762,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common", + "crypto-common 0.1.7", "subtle", ] @@ -13161,6 +13813,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "utf-8" version = "0.7.6" @@ -13181,9 +13839,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "util" -version = "0.3.3" +version = "0.4.0" dependencies = [ - "bitcoin 0.32.8", + "bitcoin", "hex", ] @@ -13250,12 +13908,54 @@ dependencies = [ "time", ] +[[package]] +name = "verifiable-circuit-babe" +version = "0.0.1" +source = "git+https://github.com/GOATNetwork/bitvm2-gc?branch=feat%2Fbabe#834d4337341e14da569c8c98c9fff546c9aedab6" +dependencies = [ + "aes", + "ark-bn254", + "ark-crypto-primitives", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-relations", + "ark-serialize 0.5.0", + "bincode", + "bitcoin", + "bitvm 0.1.0 (git+https://github.com/GOATNetwork/BitVM.git?branch=GA)", + "blake3", + "cfg-if", + "garbled-snark-verifier", + "p3-maybe-rayon", + "rand 0.8.5", + "rand_chacha 0.3.1", + "ripemd 0.2.0", + "serde", + "sha2 0.10.9", + "zkm-sdk", + "zkm-zkvm", +] + +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "wait-timeout" version = "0.2.1" @@ -13432,7 +14132,7 @@ dependencies = [ [[package]] name = "watchtower-proof" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "anyhow", @@ -13440,7 +14140,7 @@ dependencies = [ "ark-groth16", "ark-serialize 0.5.0", "bincode", - "bitcoin 0.32.8", + "bitcoin", "bitcoin-light-client-circuit", "borsh", "chrono", @@ -13579,7 +14279,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.61.2", + "windows-sys 0.60.2", ] [[package]] @@ -14210,6 +14910,12 @@ version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" +[[package]] +name = "xmlparser" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "66fee0b777b0f5ac1c69bb06d361268faafa61cd4682ae064a171c16c433e9e4" + [[package]] name = "xmltree" version = "0.10.3" @@ -14395,9 +15101,9 @@ dependencies = [ "crc32fast", "crossbeam-utils", "flate2", - "hmac", + "hmac 0.12.1", "pbkdf2 0.11.0", - "sha1", + "sha1 0.10.6", "time", "zstd 0.11.2+zstd.1.5.2", ] @@ -14432,7 +15138,7 @@ dependencies = [ [[package]] name = "zkm-build" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "anyhow", "cargo_metadata", @@ -14443,7 +15149,7 @@ dependencies = [ [[package]] name = "zkm-core-executor" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "anyhow", "bincode", @@ -14484,7 +15190,7 @@ dependencies = [ [[package]] name = "zkm-core-machine" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "bincode", "cfg-if", @@ -14536,7 +15242,7 @@ dependencies = [ [[package]] name = "zkm-cuda" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "bincode", "ctrlc", @@ -14554,7 +15260,7 @@ dependencies = [ [[package]] name = "zkm-curves" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "cfg-if", "curve25519-dalek", @@ -14578,7 +15284,7 @@ dependencies = [ [[package]] name = "zkm-derive" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "proc-macro2", "quote", @@ -14588,7 +15294,7 @@ dependencies = [ [[package]] name = "zkm-lib" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "bincode", "cfg-if", @@ -14601,7 +15307,7 @@ dependencies = [ [[package]] name = "zkm-primitives" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "bincode", "hex", @@ -14619,7 +15325,7 @@ dependencies = [ [[package]] name = "zkm-prover" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "anyhow", "bincode", @@ -14656,7 +15362,7 @@ dependencies = [ [[package]] name = "zkm-recursion-circuit" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "hashbrown 0.14.5", "itertools 0.13.0", @@ -14689,7 +15395,7 @@ dependencies = [ [[package]] name = "zkm-recursion-compiler" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "backtrace", "itertools 0.13.0", @@ -14710,7 +15416,7 @@ dependencies = [ [[package]] name = "zkm-recursion-core" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "backtrace", "ff 0.13.1", @@ -14747,7 +15453,7 @@ dependencies = [ [[package]] name = "zkm-recursion-derive" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "quote", "syn 1.0.109", @@ -14756,7 +15462,7 @@ dependencies = [ [[package]] name = "zkm-recursion-gnark-ffi" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "anyhow", "bincode", @@ -14780,7 +15486,7 @@ dependencies = [ [[package]] name = "zkm-sdk" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "alloy-primitives", "alloy-signer 1.8.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -14827,7 +15533,7 @@ dependencies = [ [[package]] name = "zkm-stark" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "arrayref", "hashbrown 0.14.5", @@ -14892,7 +15598,7 @@ dependencies = [ "serde", "sha2 0.10.9", "strum_macros 0.26.4", - "substrate-bn 0.6.0 (git+https://github.com/ziren-patches/bn.git?branch=patch-0.6.0)", + "substrate-bn 0.6.0 (git+https://github.com/ziren-patches/bn?branch=patch-0.6.0)", "thiserror 2.0.18", "zkm-core-executor", "zkm-core-machine", @@ -14905,7 +15611,7 @@ dependencies = [ [[package]] name = "zkm-zkvm" version = "1.2.5" -source = "git+https://github.com/ProjectZKM/Ziren#c736a41cccd623427295f89b53306673adc89966" +source = "git+https://github.com/ProjectZKM/Ziren#53a8eff4e716d6091c2ced0401b7b9537535392a" dependencies = [ "bincode", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 88256e8c..2566f161 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace.package] -version = "0.3.3" +version = "0.4.0" edition = "2024" [workspace] @@ -8,7 +8,7 @@ members = [ "node", "proof-builder-rpc", "crates/cbft-rpc", - "crates/bitvm2-ga", + "crates/bitvm-gc", "crates/store", "crates/client", "crates/util", @@ -17,18 +17,20 @@ members = [ "crates/state-chain", #"crates/mara-slipstream-client", "crates/bitcoin-light-client-circuit", + "crates/verifier", "circuits/header-chain-proof/host", "circuits/commit-chain-proof/host", "circuits/state-chain-proof/host", "circuits/watchtower-proof/host", "circuits/operator-proof/host", + "circuits/operator-wrapper-proof/host", "circuits/proof-builder", ] default-members = ["node"] [workspace.dependencies] -bitvm = { git = "https://github.com/GOATNetwork/BitVM.git", branch = "GA" } -goat = { git = "https://github.com/GOATNetwork/BitVM.git", branch = "GA" } +bitvm = { git = "https://github.com/blake-pro/BitVM.git", branch = "fix-gc" } +goat = { git = "https://github.com/blake-pro/BitVM.git", branch = "fix-gc" } libp2p = { version = "0.55.0", features = ["tokio", "dns", "kad", "noise", "tcp", "yamux", "rsa", "ping", "mdns"] } libp2p-swarm-derive = "0.35.0" @@ -46,6 +48,8 @@ hex = "0.4.3" indexmap = { version = "2.11.0", features = ["serde"] } ark-bn254 = { version = "0.5.0", features = ["curve", "scalar_field"], default-features = false } ark-groth16 = "0.5.0" +ark-ff = "0.5.0" +ark-crypto-primitives = "0.5.0" base64 = "0.21" ark-serialize = "0.5.0" sha2 = "0.10.9" @@ -55,8 +59,10 @@ chacha20poly1305 = "0.10.1" tokio-util = "0.7.15" esplora-client = { git = "https://github.com/BitVM/rust-esplora-client" } serde_json = "1.0.116" +serde-big-array = "0.5.1" toml = "0.8" rand = "0.8.5" +rand_chacha = { version = "0.3", default-features = false } dotenv = "0.15.0" blake3 = "=1.5.1" musig2 = { version = "0.1.0", features = ["serde", "rand"] } @@ -89,6 +95,10 @@ zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren" } zkm-verifier = { git = "https://github.com/ProjectZKM/Ziren" } zkm-primitives = { git = "https://github.com/ProjectZKM/Ziren" } zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } +zkm-recursion-core = { git = "https://github.com/ProjectZKM/Ziren" } +zkm-stark = { git = "https://github.com/ProjectZKM/Ziren" } +p3-bn254-fr = { git = "https://github.com/ProjectZKM/Plonky3" } +p3-field = { git = "https://github.com/ProjectZKM/Plonky3" } #zkm-build = { path = "../Ziren/crates/build" } #zkm-core-executor = { path = "../Ziren/crates/core/executor" } @@ -96,7 +106,10 @@ zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] #zkm-sdk = { path = "../Ziren/crates/sdk" } #zkm-verifier = { path = "../Ziren/crates/verifier" } -bitvm2-lib = { path = "crates/bitvm2-ga" } +bitvm-lib = { package = "bitvm-gc", path = "crates/bitvm-gc" } +verifiable-circuit-babe = { git = "https://github.com/GOATNetwork/bitvm2-gc", branch = "feat/babe" } +garbled-snark-verifier = { git = "https://github.com/GOATNetwork/bitvm2-gc", branch = "feat/babe" } +soldering-host = { git = "https://github.com/GOATNetwork/bitvm2-gc", branch = "feat/babe" } store = { path = "crates/store" } util = { path = "crates/util" } client = { path = "crates/client" } @@ -107,8 +120,10 @@ commit-chain = { path = "crates/commit-chain" } commit-chain-proof = { path = "circuits/commit-chain-proof/host" } cbft-rpc = { path = "crates/cbft-rpc" } state-chain = { path = "crates/state-chain" } +verifier = { path = "crates/verifier" } state-chain-proof = { path = "circuits/state-chain-proof/host" } operator-proof = { path = "circuits/operator-proof/host" } +operator-wrapper-proof = { path = "circuits/operator-wrapper-proof/host" } watchtower-proof = { path = "circuits/watchtower-proof/host" } proof-builder = { path = "circuits/proof-builder" } @@ -176,4 +191,4 @@ rust.missing_debug_implementations = "warn" rust.unreachable_pub = "warn" rust.unused_must_use = "deny" rust.rust_2018_idioms = { level = "deny", priority = -1 } -rustdoc.all = "warn" \ No newline at end of file +rustdoc.all = "warn" diff --git a/circuits/README.md b/circuits/README.md index a740c67f..1f15c81a 100644 --- a/circuits/README.md +++ b/circuits/README.md @@ -242,10 +242,18 @@ export WATCHTOWER_CHALLENGE_INIT_TXID="e7723e03ac97172cf033e40d4b9d9c0e22efa7a41 RUST_LOG=info cargo run --package operator-proof --bin operator-proof -r -- --output "data/operator-proof/output.bin" ``` +Then wrap the operator proof before the operator sends assert-commit. The Challenge script validates +the wrapper proof public inputs: + +``` +bash run-operator-wrapper-proof.sh "data/operator-proof/output.bin" "data/operator-wrapper-proof/output.bin" +``` + * latest-sequencer-commit-txid: the latest publisher's commitment Bitcoin transaction id * header-chain-input-proof: the header chain's proof, input and vk. * commit-chain-input-proof: the commit chain's proof, input and vk. -* included-watchtower: a 256-bit bitmask; each bit flags a valid watchtower. +* included-watchtower: a 256-bit bitmask; each bit flags a valid watchtower inside operator proof. +* operator-wrapper-proof: wraps operator proof and exposes `operator_vk_hash_raw`, raw 16-byte `graph_id`, and `genesis_sequencer_commit_txid` as public inputs for Challenge. * execution-layer-block-number: the block number that including `proceedWithdraw`(Peg-out) transaction of GOAT Network's execution layer(Geth). * watchtower-challenge-info: list of watchtower's challenge transaction id and compressed public key, i.e: [wachtower_info.json](./data/watchtower/watchtower_info.json). * watchtower-challenge-init-txid: the watchtower challenge init transaction id in GOAT's BitVM2 graph. diff --git a/circuits/commit-chain-proof/guest/Cargo.lock b/circuits/commit-chain-proof/guest/Cargo.lock index 51593fa1..460084d2 100644 --- a/circuits/commit-chain-proof/guest/Cargo.lock +++ b/circuits/commit-chain-proof/guest/Cargo.lock @@ -1256,7 +1256,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "commit-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", @@ -1271,7 +1271,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -2235,7 +2235,6 @@ dependencies = [ "commit-chain", "sha2 0.10.9", "tracing", - "zkm-verifier", "zkm-zkvm", ] @@ -5649,6 +5648,13 @@ dependencies = [ "serde", ] +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/circuits/commit-chain-proof/guest/Cargo.toml b/circuits/commit-chain-proof/guest/Cargo.toml index 395813bd..8f2d2e57 100644 --- a/circuits/commit-chain-proof/guest/Cargo.toml +++ b/circuits/commit-chain-proof/guest/Cargo.toml @@ -12,8 +12,6 @@ commit-chain = { path = "../../../crates/commit-chain" } # Ziren zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } -zkm-verifier = { git = "https://github.com/ProjectZKM/Ziren" } -#zkm-verifier = { path = "../../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } # Statically turns off logging diff --git a/circuits/commit-chain-proof/host/src/lib.rs b/circuits/commit-chain-proof/host/src/lib.rs index 72963ff6..400281e8 100644 --- a/circuits/commit-chain-proof/host/src/lib.rs +++ b/circuits/commit-chain-proof/host/src/lib.rs @@ -101,11 +101,10 @@ pub async fn fetch_commit_chain( let commit_txn = btc_client.get_tx(&txid).await?.unwrap(); let op_return_data = extract_op_return_data(&commit_txn.output); - let mut sequencer_set_hash: [u8; 32] = [0u8; 32]; - sequencer_set_hash.copy_from_slice(&op_return_data[0..32]); + let commitment = parse_commit_chain_commitment(&op_return_data); if let tendermint::Hash::Sha256(expected_hash) = sequencer_hash(&ci.sequencers) { - assert_eq!(expected_hash, sequencer_set_hash); + assert_eq!(expected_hash, commitment.sequencer_set_hash); } else { panic!("Invalid sequencer set hash"); } @@ -206,8 +205,7 @@ impl ProofBuilder for CommitChainProofBuilder { format!("invalid UTF-8 in zkm_version file '{version_path}'") }) })?; - let prev_output: CommitChainCircuitOutput = - zkm_sdk::ZKMPublicValues::from(&public_inputs).read(); + let prev_output = decode_commit_chain_circuit_output(&public_inputs); ( CommitChainPrevProofType::PrevProof(prev_output), proof_bytes, @@ -317,8 +315,7 @@ mod tests { fn test_parse_commit_chain_proof() { let proof_path = "/home/ubuntu/data/proof-builder-rpc/circuits/data/commit-chain/10-1.bin.public_inputs.bin"; let proof_bytes = std::fs::read(proof_path).unwrap(); - let mut pis = zkm_sdk::ZKMPublicValues::from(&proof_bytes); - let public_input: CommitChainCircuitOutput = pis.read(); + let public_input = decode_commit_chain_circuit_output(&proof_bytes); let hash = sequencer_hash(&public_input.chain_state.sequencers); println!("proof: {public_input:?}, hash : {:?}", hash); diff --git a/circuits/cron-header-chain-proof.sh b/circuits/cron-header-chain-proof.sh index 5307187a..f41bdf4c 100644 --- a/circuits/cron-header-chain-proof.sh +++ b/circuits/cron-header-chain-proof.sh @@ -12,15 +12,32 @@ batch=${2:-$_batch} function find_input_proof() { local start="$1" - local input_file - input_file=$(find $DATA -maxdepth 1 -type f -regex '.*[0-9]+-[0-9]+\.bin$' -printf '%f\n' | - awk -v sum="$start" -F '[-.]' '($1 + $2) == sum { print $0; exit }') + local input_file="" + local proof_path + local proof_file + local proof_start + local proof_batch - if [ ! $input_file ]; then - echo "Can not find the input proof" - exit -1 + # Match proof files by filename because batch size may vary between runs. + shopt -s nullglob + for proof_path in "$DATA"/*.bin; do + proof_file="${proof_path##*/}" + if [[ "$proof_file" =~ ^([0-9]+)-([0-9]+)\.bin$ ]]; then + proof_start="${BASH_REMATCH[1]}" + proof_batch="${BASH_REMATCH[2]}" + if (( proof_start + proof_batch == start )); then + input_file="$proof_file" + break + fi + fi + done + shopt -u nullglob + + if [ -z "$input_file" ]; then + echo "Can not find the input proof for start=$start in $DATA" >&2 + exit 1 fi - echo $input_file + echo "$input_file" } if [ $start -ne 0 ]; then diff --git a/circuits/cron-state-chain-proof.sh b/circuits/cron-state-chain-proof.sh index 5e94224f..b3e561bf 100644 --- a/circuits/cron-state-chain-proof.sh +++ b/circuits/cron-state-chain-proof.sh @@ -9,15 +9,32 @@ batch=${2:-$_batch} function find_input_proof() { local start="$1" - local input_file - input_file=$(find $DATA -maxdepth 1 -type f -name '*-*.bin' -printf '%f\n' | - awk -v sum="$start" -F '[-.]' '($1 + $2) == sum { print $0; exit }') + local input_file="" + local proof_path + local proof_file + local proof_start + local proof_batch - if [ ! $input_file ]; then - echo "Can not find the input proof" - exit -1 + # Match proof files by filename because batch size may vary between runs. + shopt -s nullglob + for proof_path in "$DATA"/*.bin; do + proof_file="${proof_path##*/}" + if [[ "$proof_file" =~ ^([0-9]+)-([0-9]+)\.bin$ ]]; then + proof_start="${BASH_REMATCH[1]}" + proof_batch="${BASH_REMATCH[2]}" + if (( proof_start + proof_batch == start )); then + input_file="$proof_file" + break + fi + fi + done + shopt -u nullglob + + if [ -z "$input_file" ]; then + echo "Can not find the input proof for start=$start in $DATA" >&2 + exit 1 fi - echo $input_file + echo "$input_file" } if [ $start -ne $EL_START_BLOCK_NUMBER ]; then diff --git a/circuits/data/watchtower/output3.bin.proof_part_stark_vk.bin b/circuits/data/watchtower/output3.bin.proof_part_stark_vk.bin deleted file mode 100644 index 7fb52b0c..00000000 Binary files a/circuits/data/watchtower/output3.bin.proof_part_stark_vk.bin and /dev/null differ diff --git a/circuits/header-chain-proof/guest/Cargo.lock b/circuits/header-chain-proof/guest/Cargo.lock index 6e9bc5a8..cd02d292 100644 --- a/circuits/header-chain-proof/guest/Cargo.lock +++ b/circuits/header-chain-proof/guest/Cargo.lock @@ -1011,7 +1011,6 @@ dependencies = [ "log", "sha2", "tracing", - "zkm-verifier", "zkm-zkvm", ] @@ -1110,14 +1109,14 @@ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "header-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "bitcoin", "borsh", "crypto-bigint", "serde", "sha2", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -2770,6 +2769,13 @@ dependencies = [ "serde", ] +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/circuits/header-chain-proof/guest/Cargo.toml b/circuits/header-chain-proof/guest/Cargo.toml index 42e819c2..fe58279e 100644 --- a/circuits/header-chain-proof/guest/Cargo.toml +++ b/circuits/header-chain-proof/guest/Cargo.toml @@ -14,8 +14,6 @@ borsh = { version = "1.5.3", features = ["derive"] } # Ziren zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } -zkm-verifier = { git = "https://github.com/ProjectZKM/Ziren" } -#zkm-verifier = { path = "../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } # Statically turns off logging diff --git a/circuits/operator-proof/guest/Cargo.lock b/circuits/operator-proof/guest/Cargo.lock index 2a543d93..f09ea595 100644 --- a/circuits/operator-proof/guest/Cargo.lock +++ b/circuits/operator-proof/guest/Cargo.lock @@ -1008,7 +1008,7 @@ checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-light-client-circuit" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", @@ -1022,11 +1022,11 @@ dependencies = [ "revm-database-interface", "serde", "serde_json", - "sha2 0.10.9", "state-chain", "tendermint", "tendermint-light-client-verifier", "tracing", + "verifier", "zkm-primitives", "zkm-verifier", "zkm-zkvm", @@ -1282,7 +1282,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "commit-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", @@ -1297,7 +1297,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -2273,10 +2273,10 @@ dependencies = [ "bitcoin-light-client-circuit", "commit-chain", "header-chain", + "hex", "sha2 0.10.9", "state-chain", "tracing", - "zkm-verifier", "zkm-zkvm", ] @@ -2427,14 +2427,14 @@ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "header-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "bitcoin", "borsh", "crypto-bigint", "serde", "sha2 0.10.9", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -5165,7 +5165,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "state-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -5186,7 +5186,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -5755,6 +5755,13 @@ dependencies = [ "serde", ] +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/circuits/operator-proof/guest/Cargo.toml b/circuits/operator-proof/guest/Cargo.toml index 521f3c95..a3be8844 100644 --- a/circuits/operator-proof/guest/Cargo.toml +++ b/circuits/operator-proof/guest/Cargo.toml @@ -15,8 +15,6 @@ bitcoin-light-client-circuit = { path = "../../../crates/bitcoin-light-client-ci # Ziren zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } -zkm-verifier = { git = "https://github.com/ProjectZKM/Ziren" } -#zkm-verifier = { path = "../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } # Statically turns off logging @@ -28,6 +26,9 @@ alloy-primitives = { version = "1.0.0", features = ["sha3-keccak", "map-foldhash #revm = { git = "https://github.com/ziren-patches/revm", branch = "patch-31.0.2", features = ["serde", "bn"], default-features = false } sha2 = "0.10.9" +[build-dependencies] +hex = "0.4.3" + [patch.crates-io] # Precompile patches @@ -42,4 +43,3 @@ alloy-primitives-v1-4-1 = { git = "https://github.com/ziren-patches/core.git", p secp256k1-v0-29-1 = { git = "https://github.com/ziren-patches/rust-secp256k1", package = "secp256k1", branch = "patch-0.29.1" } #secp256k1-v0-30-0 = { git = "https://github.com/ziren-patches/rust-secp256k1", package = "secp256k1", branch = "patch-0.30.0" } #bitcoin = { git = "https://github.com/ProjectZKM/rust-bitcoin", branch = "patch-0.32.7" } - diff --git a/circuits/operator-proof/guest/build.rs b/circuits/operator-proof/guest/build.rs new file mode 100644 index 00000000..6263faaf --- /dev/null +++ b/circuits/operator-proof/guest/build.rs @@ -0,0 +1,68 @@ +use std::{env, fs, path::PathBuf}; + +const ENV_FIXED_WATCHTOWER_KEYS: &str = "FIXED_WATCHTOWER_XONLY_PUBLIC_KEYS"; + +fn parse_xonly_key(raw: &str) -> Result<[u8; 32], String> { + let hex = raw.trim().strip_prefix("0x").unwrap_or(raw.trim()); + let bytes = hex::decode(hex).map_err(|err| format!("invalid x-only public key hex: {err}"))?; + bytes.try_into().map_err(|bytes: Vec| { + format!("x-only public key must be 32 bytes, got {}", bytes.len()) + }) +} + +fn fixed_watchtower_keys_from_env() -> Vec<[u8; 32]> { + let value = match env::var(ENV_FIXED_WATCHTOWER_KEYS) { + Ok(value) => value, + Err(_) => { + println!( + "cargo:warning={ENV_FIXED_WATCHTOWER_KEYS} is not set; building operator guest with an empty fixed watchtower list" + ); + return Vec::new(); + } + }; + let keys = value + .split(',') + .map(str::trim) + .filter(|key| !key.is_empty()) + .map(parse_xonly_key) + .collect::, _>>() + .unwrap_or_else(|err| panic!("invalid {ENV_FIXED_WATCHTOWER_KEYS}: {err}")); + + if keys.is_empty() { + println!( + "cargo:warning={ENV_FIXED_WATCHTOWER_KEYS} contains no keys; building operator guest with an empty fixed watchtower list" + ); + return Vec::new(); + } + if keys.len() > 256 { + panic!("{ENV_FIXED_WATCHTOWER_KEYS} contains {} keys, max 256", keys.len()); + } + keys +} + +fn main() { + println!("cargo:rerun-if-env-changed={ENV_FIXED_WATCHTOWER_KEYS}"); + + let keys = fixed_watchtower_keys_from_env(); + let mut generated = String::new(); + generated.push_str(&format!("pub const FIXED_WATCHTOWER_COUNT: usize = {};\n", keys.len())); + generated.push_str(&format!( + "pub const FIXED_WATCHTOWER_XONLY_PUBLIC_KEYS: [[u8; 32]; {}] = [\n", + keys.len() + )); + for key in keys { + generated.push_str(" ["); + for (index, byte) in key.iter().enumerate() { + if index > 0 { + generated.push_str(", "); + } + generated.push_str(&format!("0x{byte:02x}")); + } + generated.push_str("],\n"); + } + generated.push_str("];\n"); + + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR is set by cargo")); + fs::write(out_dir.join("fixed_watchtowers.rs"), generated) + .expect("failed to write fixed_watchtowers.rs"); +} diff --git a/circuits/operator-proof/guest/src/main.rs b/circuits/operator-proof/guest/src/main.rs index f5c6dbcd..960d8445 100644 --- a/circuits/operator-proof/guest/src/main.rs +++ b/circuits/operator-proof/guest/src/main.rs @@ -1,20 +1,20 @@ #![no_main] zkm_zkvm::entrypoint!(main); -use std::str::FromStr; -use header_chain::{ - HeaderChainCircuitInput, SPV, -}; -use alloy_primitives::{U256, Address}; -use bitcoin_light_client_circuit::{EthClientExecutorInput, OperatorAttestationInputs}; +use alloy_primitives::{Address, U256}; +use bitcoin::{ScriptBuf, Transaction, TxOut}; +use bitcoin_light_client_circuit::EthClientExecutorInput; use commit_chain::CommitChainCircuitInput; +use header_chain::{HeaderChainCircuitInput, SPV}; use state_chain::StateChainCircuitInput; -use bitcoin::{ScriptBuf, TxOut, Transaction}; +use std::str::FromStr; + +include!(concat!(env!("OUT_DIR"), "/fixed_watchtowers.rs")); pub fn main() { // calculate operator public input: https://github.com/ProjectZKM/Ziren/blob/main/crates/sdk/src/utils.rs#L42 - let included_watchertowers: U256 = zkm_zkvm::io::read::(); + let included_watchtowers: U256 = zkm_zkvm::io::read::(); let graph_id: [u8; 16] = zkm_zkvm::io::read::<[u8; 16]>(); - let operator_genesis_sequencer_commit_txid: [u8; 32] = zkm_zkvm::io::read(); + let operator_genesis_sequencer_commit_txid: [u8; 32] = zkm_zkvm::io::read(); println!("read operator commit txn"); let operator_latest_sequencer_commit_txn: Transaction = zkm_zkvm::io::read(); // private inputs let latest_sequencer_commit_txid = operator_latest_sequencer_commit_txn.compute_txid(); // public input @@ -27,25 +27,30 @@ pub fn main() { let operator_header_chain: HeaderChainCircuitInput = zkm_zkvm::io::read(); let operator_commit_chain: CommitChainCircuitInput = zkm_zkvm::io::read(); let operator_state_chain: StateChainCircuitInput = zkm_zkvm::io::read(); - let attestation: OperatorAttestationInputs = zkm_zkvm::io::read(); let spv_ss_commit: SPV = zkm_zkvm::io::read(); let operator_committed_blockhash: [u8; 32] = zkm_zkvm::io::read(); + let actual_operator_vk_hash: [u8; 32] = zkm_zkvm::io::read(); - let output = bitcoin_light_client_circuit::propose_longest_chain( - included_watchertowers, - graph_id, - operator_genesis_sequencer_commit_txid, - watchtower_challenge_txns, - watchtower_challenge_txn_pubkey, - watchtower_challenge_txn_scripts, - watchtower_challenge_txn_prev_outs, - operator_header_chain, - operator_commit_chain, - operator_state_chain, - attestation, - spv_ss_commit, - operator_committed_blockhash, - ); + let (btc_best_block_hash, constant, included_watchtowers, operator_vk_hash) = + bitcoin_light_client_circuit::propose_longest_chain( + included_watchtowers, + graph_id, + operator_genesis_sequencer_commit_txid, + watchtower_challenge_txns, + watchtower_challenge_txn_pubkey, + watchtower_challenge_txn_scripts, + watchtower_challenge_txn_prev_outs, + &FIXED_WATCHTOWER_XONLY_PUBLIC_KEYS, + operator_header_chain, + operator_commit_chain, + operator_state_chain, + spv_ss_commit, + operator_committed_blockhash, + actual_operator_vk_hash, + ); - zkm_zkvm::io::commit(&output); + zkm_zkvm::io::commit(&btc_best_block_hash); + zkm_zkvm::io::commit(&constant); + zkm_zkvm::io::commit(&included_watchtowers); + zkm_zkvm::io::commit(&operator_vk_hash); } diff --git a/circuits/operator-proof/host/Cargo.toml b/circuits/operator-proof/host/Cargo.toml index 2bc6c649..18605494 100644 --- a/circuits/operator-proof/host/Cargo.toml +++ b/circuits/operator-proof/host/Cargo.toml @@ -50,7 +50,7 @@ zkm-prover.workspace = true zkm-verifier = { workspace = true, features = ["ark"] } [dev-dependencies] -bitvm2-lib.workspace = true +bitvm-lib.workspace = true [build-dependencies] zkm-build.workspace = true diff --git a/circuits/operator-proof/host/build.rs b/circuits/operator-proof/host/build.rs index 0c21839a..bd89ff46 100644 --- a/circuits/operator-proof/host/build.rs +++ b/circuits/operator-proof/host/build.rs @@ -1,4 +1,13 @@ use zkm_build::build_program; + +const ENV_FIXED_WATCHTOWER_KEYS: &str = "FIXED_WATCHTOWER_XONLY_PUBLIC_KEYS"; + fn main() { + println!("cargo:rerun-if-env-changed={ENV_FIXED_WATCHTOWER_KEYS}"); + if std::env::var(ENV_FIXED_WATCHTOWER_KEYS).is_err() { + println!( + "cargo:warning={ENV_FIXED_WATCHTOWER_KEYS} is not set; building operator guest with an empty fixed watchtower list" + ); + } build_program("../guest"); } diff --git a/circuits/operator-proof/host/src/lib.rs b/circuits/operator-proof/host/src/lib.rs index 4d2f0fc0..575b1172 100644 --- a/circuits/operator-proof/host/src/lib.rs +++ b/circuits/operator-proof/host/src/lib.rs @@ -6,31 +6,21 @@ use bitcoin::{ hashes::Hash, secp256k1::{PublicKey, XOnlyPublicKey}, }; +use bitcoin_light_client_circuit::{build_spv, zkm_vk_hash_to_raw}; +use bitcoin_script::script; use borsh::BorshDeserialize; +use clap::Parser; use client::btc_chain::BTCClient; -use commit_chain::{ - CommitChainCircuitInput, CommitChainPrevProofType, extract_data_from_commitment_outputs, -}; -use header_chain::{ - BlockHeaderCircuitOutput, CircuitBlockHeader, HeaderChainCircuitInput, HeaderChainPrevProofType, -}; +use commit_chain::{CommitChainCircuitInput, CommitChainPrevProofType}; +use header_chain::{CircuitBlockHeader, HeaderChainCircuitInput, HeaderChainPrevProofType}; use proof_builder::{LongRunning, ProofBuilder, ProofRequest}; -use state_chain::{StateChainCircuitInput, StateChainCircuitOutput, StateChainPrevProofType}; +use state_chain::{StateChainCircuitInput, StateChainPrevProofType}; use std::str::FromStr; use util::get_btc_block_confirms; use zkm_sdk::{ HashableKey, Prover, ProverClient, ZKMProofKind, ZKMProofWithPublicValues, ZKMStdin, include_elf, }; -use zkm_verifier::Groth16Verifier; - -use bincode::deserialize; -use bitcoin_light_client_circuit::{ - OperatorAttestationInputs, build_spv, load_unique_part_stark_vk_witnesses, - parse_watchtower_commitment, part_stark_vk_attestation_dir, -}; -use bitcoin_script::script; -use clap::Parser; /// The arguments for the cli. #[derive(Debug, Clone, Parser, serde::Deserialize, serde::Serialize)] @@ -93,9 +83,7 @@ impl LongRunning for Args { /// A program that aggregates the proofs of the simple program. const OPERATOR: &[u8] = include_elf!("guest"); -use serde::de::DeserializeOwned; use std::fs; -use std::panic::{AssertUnwindSafe, catch_unwind}; use sha2::{Digest, Sha256}; use std::sync::OnceLock; @@ -119,8 +107,14 @@ pub async fn fetch_target_block_and_watchtower_tx( Vec, Vec, )> { - let watchtower_challenge_txids: Vec<&str> = watchtower_challenge_txids.split(",").collect(); - let watchtower_public_keys: Vec<&str> = watchtower_public_keys.split(",").collect(); + let watchtower_challenge_txids: Vec<&str> = + watchtower_challenge_txids.split(",").filter(|s| !s.is_empty()).collect(); + let watchtower_public_keys: Vec<&str> = + watchtower_public_keys.split(",").filter(|s| !s.is_empty()).collect(); + anyhow::ensure!( + watchtower_challenge_txids.len() == watchtower_public_keys.len(), + "watchtower challenge txids and public keys must have equal lengths" + ); let btc_client = BTCClient::new(bitcoin_network, Some(&esplora_url)); let latest_sequencer_commit_txid = Txid::from_str(&latest_sequencer_commit_txid)?; @@ -241,46 +235,6 @@ impl OperatorProofBuilder { } } -fn load_proof_public_output(proof_path: &str) -> anyhow::Result { - let public_inputs = fs::read(format!("{proof_path}.public_inputs.bin")) - .context("Failed to read public inputs")?; - deserialize(&public_inputs).context("Failed to decode proof public outputs") -} - -fn extract_watchtower_part_stark_vk(tx: &Transaction) -> Option> { - let commitment = extract_data_from_commitment_outputs(&tx.output); - let (_, _, _, _, proof_part_stark_vk) = parse_watchtower_commitment(&commitment).ok()?; - Some(proof_part_stark_vk) -} - -fn load_part_stark_vk(zkm_version: &str) -> anyhow::Result> { - catch_unwind(AssertUnwindSafe(|| Groth16Verifier::get_part_stark_vk(zkm_version).to_vec())) - .map_err(|_| anyhow::anyhow!("Failed to load part_stark_vk for zkm_version {zkm_version}")) -} - -/// Collect both the version-derived verifier key and the recursive inner verifier key -/// for header/state subproofs, plus each watchtower proof verifier key from commitments. -fn collect_requested_part_stark_vks( - header_chain_input: &HeaderChainCircuitInput, - header_chain_output: &BlockHeaderCircuitOutput, - state_chain_input: &StateChainCircuitInput, - state_chain_output: &StateChainCircuitOutput, - watchtower_challenge_txns: &[Transaction], -) -> anyhow::Result>> { - let mut requested_part_stark_vks = vec![ - load_part_stark_vk(&header_chain_input.zkm_version)?, - header_chain_output.part_stark_vk.clone(), - load_part_stark_vk(&state_chain_input.zkm_version)?, - state_chain_output.part_stark_vk.clone(), - ]; - for tx in watchtower_challenge_txns { - if let Some(part_stark_vk) = extract_watchtower_part_stark_vk(tx) { - requested_part_stark_vks.push(part_stark_vk); - } - } - Ok(requested_part_stark_vks) -} - impl ProofBuilder for OperatorProofBuilder { fn client(&self) -> &zkm_sdk::ProverClient { &self.client @@ -409,23 +363,6 @@ impl ProofBuilder for OperatorProofBuilder { } }; - let header_chain_output: BlockHeaderCircuitOutput = - load_proof_public_output(header_chain_input_proof)?; - let state_chain_output: StateChainCircuitOutput = - load_proof_public_output(state_chain_input_proof)?; - let requested_part_stark_vks = collect_requested_part_stark_vks( - &header_chain_input, - &header_chain_output, - &state_chain_input, - &state_chain_output, - watchtower_challenge_txns, - )?; - let attestation_dir = part_stark_vk_attestation_dir(); - let (unique_witnesses, _) = - load_unique_part_stark_vk_witnesses(&attestation_dir, &requested_part_stark_vks) - .map_err(anyhow::Error::msg)?; - let attestation_inputs = OperatorAttestationInputs { unique_witnesses }; - // --- spv --- // //let latest_sequencer_commit_txid = Txid::from_str(&latest_sequencer_commit_txid).unwrap(); @@ -463,6 +400,9 @@ impl ProofBuilder for OperatorProofBuilder { &bitcoin_block_headers, ); + let actual_operator_vk_hash = zkm_vk_hash_to_raw(self.verifying_key.bytes32().as_bytes()) + .map_err(anyhow::Error::msg)?; + // Generate the proofs let (proof, cycles, proving_time) = tracing::info_span!("generate proof").in_scope( || -> anyhow::Result<(ZKMProofWithPublicValues, u64, f32)> { @@ -484,9 +424,9 @@ impl ProofBuilder for OperatorProofBuilder { stdin.write(&header_chain_input); stdin.write(&commit_chain_input); stdin.write(&state_chain_input); - stdin.write(&attestation_inputs); stdin.write(&spv_ss_commit); stdin.write(&operator_committed_blockhash.to_byte_array()); + stdin.write(&actual_operator_vk_hash); let elf_id = if ELF_ID.get().is_none() { ELF_ID @@ -528,7 +468,7 @@ impl ProofBuilder for OperatorProofBuilder { std::fs::write(&format!("{}.public_inputs.bin", output), proof.public_values.to_vec())?; std::fs::write(&format!("{}.vk_hash.bin", output), self.verifying_key.bytes32())?; std::fs::write(&format!("{}.zkm_version.bin", output), zkm_version)?; - let proof = bincode::serialize(&proof).unwrap(); + let proof = bincode::serialize(&proof)?; std::fs::write(&format!("{}", output), proof)?; Ok((public_value_hex, proof_size)) } @@ -544,80 +484,6 @@ mod tests { use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES, convert_ark_imm_wrap_vk}; - fn sample_header_input(zkm_version: &str) -> HeaderChainCircuitInput { - HeaderChainCircuitInput { - prev_proof: HeaderChainPrevProofType::GenesisBlock, - zkm_proof: vec![], - zkm_public_values: vec![], - zkm_vk_hash: vec![], - zkm_version: zkm_version.to_string(), - block_headers: vec![], - } - } - - fn sample_state_input(zkm_version: &str) -> StateChainCircuitInput { - StateChainCircuitInput { - prev_proof: StateChainPrevProofType::GenesisBlock, - zkm_proof: vec![], - zkm_public_values: vec![], - zkm_vk_hash: vec![], - zkm_version: zkm_version.to_string(), - blocks: vec![], - } - } - - fn sample_header_output(part_stark_vk: Vec) -> BlockHeaderCircuitOutput { - BlockHeaderCircuitOutput { chain_state: header_chain::ChainState::new(), part_stark_vk } - } - - fn sample_state_output(part_stark_vk: Vec) -> StateChainCircuitOutput { - StateChainCircuitOutput { - chain_state: state_chain::StateChainState::new(0, [0u8; 32], Vec::new()), - part_stark_vk, - } - } - - #[test] - fn test_collect_requested_part_stark_vks_includes_outer_inner_and_watchtower_keys() { - let old_vk = load_part_stark_vk("v1.2.4").unwrap(); - let new_vk = load_part_stark_vk("v1.2.5").unwrap(); - - let graph_id = [7u8; 16]; - let proof = vec![3u8; 260]; - let public_inputs = vec![9u8; 36]; - let vk_hash = "ab".repeat(33); - let watchtower_comm = bitcoin_light_client_circuit::build_watchtower_commitment( - &graph_id, - &proof, - &public_inputs, - &vk_hash, - &new_vk, - ) - .unwrap(); - let watchtower_tx = Transaction { - version: bitcoin::transaction::Version::TWO, - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { - value: bitcoin::Amount::ZERO, - script_pubkey: bitcoin::ScriptBuf::new_op_return( - bitcoin::script::PushBytesBuf::try_from(watchtower_comm).unwrap(), - ), - }], - }; - - let requested = collect_requested_part_stark_vks( - &sample_header_input("v1.2.5"), - &sample_header_output(old_vk.clone()), - &sample_state_input("v1.2.5"), - &sample_state_output(old_vk.clone()), - &[watchtower_tx], - ) - .unwrap(); - - assert_eq!(requested, vec![new_vk.clone(), old_vk.clone(), new_vk.clone(), old_vk, new_vk]); - } - #[tokio::test] #[ignore = "local test"] async fn test_parse_operator_proof() { @@ -627,8 +493,14 @@ mod tests { let proof: ZKMProofWithPublicValues = bincode::deserialize(&proof_bytes).unwrap(); - let a: bitcoin_light_client_circuit::OperatorPublicOutputs = - proof.public_values.clone().read(); + let vk_hash = String::from_utf8(vk_bytes).unwrap(); + let operator_vk_hash = + bitcoin_light_client_circuit::zkm_vk_hash_to_raw(vk_hash.as_bytes()).unwrap(); + let a = bitcoin_light_client_circuit::decode_operator_public_outputs( + proof.public_values.as_slice(), + operator_vk_hash, + ) + .unwrap(); println!( "block hash: {:?}, constant: {:?}, included map: {:?}", hex::encode(a.btc_best_block_hash), @@ -636,7 +508,6 @@ mod tests { U256::from_le_bytes(a.included_watchtowers) ); - let vk_hash = String::from_utf8(vk_bytes).unwrap(); let part_stark_vk = catch_unwind(AssertUnwindSafe(|| { Groth16Verifier::get_part_stark_vk(&proof.zkm_version) })) diff --git a/circuits/operator-wrapper-proof/guest/Cargo.lock b/circuits/operator-wrapper-proof/guest/Cargo.lock new file mode 100644 index 00000000..332cd093 --- /dev/null +++ b/circuits/operator-wrapper-proof/guest/Cargo.lock @@ -0,0 +1,6696 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "addchain" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e33f6a175ec6a9e0aca777567f9ff7c3deefc255660df887e7fa3585e9801d8" +dependencies = [ + "num-bigint 0.3.3", + "num-integer", + "num-traits", +] + +[[package]] +name = "addr2line" +version = "0.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + +[[package]] +name = "alloy-chains" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84e0378e959aa6a885897522080a990e80eb317f1e9a222a604492ea50e13096" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "num_enum", + "serde", + "strum 0.27.2", +] + +[[package]] +name = "alloy-consensus" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-trie", + "alloy-tx-macros", + "auto_impl", + "c-kzg", + "derive_more 2.1.1", + "either", + "k256", + "once_cell", + "rand 0.8.6", + "secp256k1 0.30.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-consensus-any" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-eip2124" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "741bdd7499908b3aa0b159bba11e71c8cddd009a2c2eb7a06e825f1ec87900a5" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "crc", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eip2930" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9441120fa82df73e8959ae0e4ab8ade03de2aaae61be313fbf5746277847ce25" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "serde", +] + +[[package]] +name = "alloy-eip7702" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2919c5a56a1007492da313e7a3b6d45ef5edc5d33416fdec63c0d7a2702a0d20" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "borsh", + "k256", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-eips" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-eip2124", + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "auto_impl", + "c-kzg", + "derive_more 2.1.1", + "either", + "serde", + "serde_with", + "sha2 0.10.9", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-evm" +version = "0.23.0" +source = "git+https://github.com/ziren-patches/evm?branch=patch-0.23.0#3cc30123cc6abb1c0da7e20318202821cab5d663" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-hardforks", + "alloy-primitives", + "alloy-sol-types", + "anyhow", + "auto_impl", + "derive_more 2.1.1", + "revm", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-genesis" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "alloy-trie", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-hardforks" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83ba208044232d14d4adbfa77e57d6329f51bc1acc21f5667bb7db72d88a0831" +dependencies = [ + "alloy-chains", + "alloy-eip2124", + "alloy-primitives", + "auto_impl", + "dyn-clone", +] + +[[package]] +name = "alloy-json-abi" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5513d5e6bd1cba6bdcf5373470f559f320c05c8c59493b6e98912fbe6733943f" +dependencies = [ + "alloy-primitives", + "alloy-sol-type-parser", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-json-rpc" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "alloy-sol-types", + "http", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-network" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-json-rpc", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rpc-types-any", + "alloy-rpc-types-eth", + "alloy-serde", + "alloy-signer", + "alloy-sol-types", + "async-trait", + "auto_impl", + "derive_more 2.1.1", + "futures-utils-wasm", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-network-primitives" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-primitives" +version = "1.4.1" +source = "git+https://github.com/ziren-patches/core.git?branch=patch-alloy-primitives-1.4.1#59313700f710f1373a5b4cdc05c51dc3227d8064" +dependencies = [ + "alloy-rlp", + "bytes", + "cfg-if", + "const-hex", + "derive_more 2.1.1", + "foldhash 0.2.0", + "hashbrown 0.16.1", + "indexmap 2.14.0", + "itoa", + "k256", + "keccak-asm", + "paste", + "proptest", + "rand 0.9.4", + "ruint", + "rustc-hash", + "serde", + "sha3", + "tiny-keccak", + "zkm-zkvm", +] + +[[package]] +name = "alloy-rlp" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc90b1e703d3c03f4ff7f48e82dd0bc1c8211ab7d079cd836a06fcfeb06651cb" +dependencies = [ + "alloy-rlp-derive", + "arrayvec", + "bytes", +] + +[[package]] +name = "alloy-rlp-derive" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f36834a5c0a2fa56e171bf256c34d70fca07d0c0031583edea1c4946b7889c9e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-rpc-types" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "alloy-rpc-types-eth", + "alloy-serde", + "serde", +] + +[[package]] +name = "alloy-rpc-types-any" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-consensus-any", + "alloy-rpc-types-eth", + "alloy-serde", +] + +[[package]] +name = "alloy-rpc-types-debug" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "serde", + "serde_with", +] + +[[package]] +name = "alloy-rpc-types-engine" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "strum 0.27.2", +] + +[[package]] +name = "alloy-rpc-types-eth" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-consensus", + "alloy-consensus-any", + "alloy-eips", + "alloy-network-primitives", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "alloy-sol-types", + "itertools 0.14.0", + "serde", + "serde_json", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-serde" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + +[[package]] +name = "alloy-signer" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "async-trait", + "auto_impl", + "either", + "elliptic-curve", + "k256", + "thiserror 2.0.18", +] + +[[package]] +name = "alloy-sol-macro" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab81bab693da9bb79f7a95b64b394718259fdd7e41dceeced4cad57cb71c4f6a" +dependencies = [ + "alloy-sol-macro-expander", + "alloy-sol-macro-input", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "alloy-sol-macro-expander" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "489f1620bb7e2483fb5819ed01ab6edc1d2f93939dce35a5695085a1afd1d699" +dependencies = [ + "alloy-sol-macro-input", + "const-hex", + "heck", + "indexmap 2.14.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "sha3", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-macro-input" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56cef806ad22d4392c5fc83cf8f2089f988eb99c7067b4e0c6f1971fc1cca318" +dependencies = [ + "const-hex", + "dunce", + "heck", + "macro-string", + "proc-macro2", + "quote", + "syn 2.0.117", + "syn-solidity", +] + +[[package]] +name = "alloy-sol-type-parser" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6df77fea9d6a2a75c0ef8d2acbdfd92286cc599983d3175ccdc170d3433d249" +dependencies = [ + "serde", + "winnow 0.7.15", +] + +[[package]] +name = "alloy-sol-types" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70319350969a3af119da6fb3e9bddb1bce66c9ea933600cb297c8b1850ad2a3c" +dependencies = [ + "alloy-json-abi", + "alloy-primitives", + "alloy-sol-macro", + "serde", +] + +[[package]] +name = "alloy-trie" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f14b5d9b2c2173980202c6ff470d96e7c5e202c65a9f67884ad565226df7fbb" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "nybbles", + "serde", + "smallvec", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "alloy-tx-macros" +version = "1.0.41" +source = "git+https://github.com/ziren-patches/alloy?branch=patch-1.0.41#08557d4e5f4ca766194a45c1925d8f1625258793" +dependencies = [ + "alloy-primitives", + "darling 0.21.3", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anstream" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "824a212faf96e9acacdbd09febd34438f8f711fb84e09a8916013cd7815ca28d" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" + +[[package]] +name = "anstyle-parse" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ce7f38b242319f7cabaa6813055467063ecdc9d355bbb4ce0c68908cd8130e" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys", +] + +[[package]] +name = "anyhow" +version = "1.0.102" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f202df86484c868dbad7eaa557ef785d5c66295e41b460ef922eca0723b842c" + +[[package]] +name = "ark-bls12-381" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df4dcc01ff89867cd86b0da835f23c3f02738353aaee7dde7495af71363b8d5" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-bn254" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d69eab57e8d2663efa5c63135b2af4f396d66424f88954c21104125ab6b3e6bc" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "ark-ec" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d68f2d516162846c1238e755a7c4d131b892b70cc70c471a8e3ca3ed818fce" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-poly", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b3235cc41ee7a12aaaf2c575a2ad7b46713a8a50bda2fc3b003a04845c05dd6" +dependencies = [ + "ark-ff-asm 0.3.0", + "ark-ff-macros 0.3.0", + "ark-serialize 0.3.0", + "ark-std 0.3.0", + "derivative", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.3.3", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec847af850f44ad29048935519032c33da8aa03340876d351dfab5660d2966ba" +dependencies = [ + "ark-ff-asm 0.4.2", + "ark-ff-macros 0.4.2", + "ark-serialize 0.4.2", + "ark-std 0.4.0", + "derivative", + "digest 0.10.7", + "itertools 0.10.5", + "num-bigint 0.4.6", + "num-traits", + "paste", + "rustc_version 0.4.1", + "zeroize", +] + +[[package]] +name = "ark-ff" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a177aba0ed1e0fbb62aa9f6d0502e9b46dad8c2eab04c14258a1212d2557ea70" +dependencies = [ + "ark-ff-asm 0.5.0", + "ark-ff-macros 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "educe", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "paste", + "zeroize", +] + +[[package]] +name = "ark-ff-asm" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db02d390bf6643fb404d3d22d31aee1c4bc4459600aef9113833d17e786c6e44" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed4aa4fe255d0bc6d79373f7e31d2ea147bcf486cba1be5ba7ea85abdb92348" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-asm" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62945a2f7e6de02a31fe400aa489f0e0f5b2502e69f95f853adb82a96c7a6b60" +dependencies = [ + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-ff-macros" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fd794a08ccb318058009eefdf15bcaaaaf6f8161eb3345f907222bac38b20" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7abe79b0e4288889c4574159ab790824d0033b9fdcb2a112a3182fac2e514565" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "ark-ff-macros" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09be120733ee33f7693ceaa202ca41accd5653b779563608f1234f78ae07c4b3" +dependencies = [ + "num-bigint 0.4.6", + "num-traits", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-poly" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "579305839da207f02b89cd1679e50e67b4331e2f9294a57693e5051b7703fe27" +dependencies = [ + "ahash", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "ark-std 0.5.0", + "educe", + "fnv", + "hashbrown 0.15.5", +] + +[[package]] +name = "ark-serialize" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6c2b318ee6e10f8c2853e73a83adc0ccb88995aa978d8a3408d492ab2ee671" +dependencies = [ + "ark-std 0.3.0", + "digest 0.9.0", +] + +[[package]] +name = "ark-serialize" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb7b85a02b83d2f22f89bd5cac66c9c89474240cb6207cb1efc16d098e822a5" +dependencies = [ + "ark-std 0.4.0", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f4d068aaf107ebcd7dfb52bc748f8030e0fc930ac8e360146ca54c1203088f7" +dependencies = [ + "ark-serialize-derive", + "ark-std 0.5.0", + "arrayvec", + "digest 0.10.7", + "num-bigint 0.4.6", +] + +[[package]] +name = "ark-serialize-derive" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213888f660fddcca0d257e88e54ac05bca01885f258ccdf695bafd77031bb69d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "ark-std" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1df2c09229cbc5a028b1d70e00fdb2acee28b1055dfb5ca73eea49c5a25c4e7c" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94893f1e0c6eeab764ade8dc4c0db24caf4fe7cbbaafc0eba0a9030f447b5185" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "ark-std" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "246a225cc6131e9ee4f24619af0f19d67761fff15d7ccc22e42b80846e69449a" +dependencies = [ + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "aurora-engine-modexp" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "518bc5745a6264b5fd7b09dffb9667e400ee9e2bbe18555fac75e1fe9afa0df9" +dependencies = [ + "hex", + "num", +] + +[[package]] +name = "auto_impl" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffdcb70bdbc4d478427380519163274ac86e52916e10f0a8889adf0f96d3fee7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "autocfg" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "backtrace" +version = "0.3.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" +dependencies = [ + "addr2line", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", + "serde", + "windows-link", +] + +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + +[[package]] +name = "base58ck" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c8d66485a3a2ea485c1913c4572ce0256067a5377ac8c75c4960e1cda98605f" +dependencies = [ + "bitcoin-internals", + "bitcoin_hashes", +] + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64ct" +version = "1.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" + +[[package]] +name = "bech32" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitcoin" +version = "0.32.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e499f9fc0407f50fe98af744ab44fa67d409f76b6772e1689ec8485eb0c0f66" +dependencies = [ + "base58ck", + "bech32", + "bitcoin-internals", + "bitcoin-io", + "bitcoin-units", + "bitcoin_hashes", + "hex-conservative", + "hex_lit", + "secp256k1 0.29.0", + "serde", +] + +[[package]] +name = "bitcoin-internals" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bdbe14aa07b06e6cfeffc529a1f099e5fbe249524f8125358604df99a4bed2" +dependencies = [ + "serde", +] + +[[package]] +name = "bitcoin-io" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" + +[[package]] +name = "bitcoin-light-client-circuit" +version = "0.4.0" +dependencies = [ + "alloy-primitives", + "base64 0.21.7", + "bincode", + "bitcoin", + "commit-chain", + "guest-executor", + "header-chain", + "hex", + "prost-build", + "revm-database-interface", + "serde", + "serde_json", + "state-chain", + "tendermint", + "tendermint-light-client-verifier", + "tracing", + "verifier", + "zkm-primitives", + "zkm-verifier", + "zkm-zkvm", +] + +[[package]] +name = "bitcoin-units" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346568ebaab2918487cea76dd55dae13c27bb618cdb737c952e69eb2017c4118" +dependencies = [ + "bitcoin-internals", + "serde", +] + +[[package]] +name = "bitcoin_hashes" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26ec84b80c482df901772e931a9a681e26a1b9ee2302edeff23cb30328745c8b" +dependencies = [ + "bitcoin-io", + "hex-conservative", + "serde", +] + +[[package]] +name = "bitflags" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4512299f36f043ab09a583e57bceb5a5aab7a73db1805848e8fef3c9e8c78b3" +dependencies = [ + "serde_core", +] + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "serde", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq", +] + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array 0.14.9", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array 0.14.9", +] + +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "git+https://github.com/ziren-patches/bls12_381?branch=patch-0.8.0#bcf9397426410acc171ad625416b508dc913be8e" +dependencies = [ + "cfg-if", + "ff 0.13.1", + "group 0.13.0", + "pairing 0.23.0", + "rand_core 0.6.4", + "subtle", + "zkm-lib", +] + +[[package]] +name = "blst" +version = "0.3.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" +dependencies = [ + "cc", + "glob", + "threadpool", + "zeroize", +] + +[[package]] +name = "borsh" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfd1e3f8955a5d7de9fab72fc8373fade9fb8a703968cb200ae3dc6cf08e185a" +dependencies = [ + "borsh-derive", + "bytes", + "cfg_aliases", +] + +[[package]] +name = "borsh-derive" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfcfdc083699101d5a7965e49925975f2f55060f94f9a05e7187be95d530ca59" +dependencies = [ + "once_cell", + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "bumpalo" +version = "3.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" + +[[package]] +name = "byte-slice-cast" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7575182f7272186991736b70173b0ea045398f984bf5ebbb3804736ce1330c9d" + +[[package]] +name = "bytemuck" +version = "1.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9abbd1bc6865053c427f7198e6af43bfdedc55ab791faed4fbd361d789575ff" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" +dependencies = [ + "serde", +] + +[[package]] +name = "c-kzg" +version = "2.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6648ed1e4ea8e8a1a4a2c78e1cda29a3fd500bc622899c340d8525ea9a76b24a" +dependencies = [ + "blst", + "cc", + "glob", + "hex", + "libc", + "once_cell", + "serde", +] + +[[package]] +name = "cc" +version = "1.2.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d16d90359e986641506914ba71350897565610e87ce0ad9e6f28569db3dd5c6d" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" + +[[package]] +name = "chrono" +version = "0.4.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "commit-chain" +version = "0.4.0" +dependencies = [ + "alloy-primitives", + "base64 0.21.7", + "bincode", + "bitcoin", + "guest-executor", + "hex", + "prost-build", + "revm-database-interface", + "serde", + "sha2 0.10.9", + "tendermint", + "tendermint-light-client-verifier", + "tracing", + "verifier", + "zkm-zkvm", +] + +[[package]] +name = "const-hex" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20d9a563d167a9cce0f94153382b33cb6eded6dfabff03c69ad65a28ea1514e0" +dependencies = [ + "cfg-if", + "cpufeatures", + "proptest", + "serde_core", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "const_format" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4481a617ad9a412be3b97c5d403fef8ed023103368908b9c50af598ff467cc1e" +dependencies = [ + "const_format_proc_macros", + "konst", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d57c2eccfb16dbac1f4e61e206105db5820c9d26c3c472bc17c774259ef7744" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "constant_time_eq" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" + +[[package]] +name = "convert_case" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "cosmos-sdk-proto" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95ac39be7373404accccaede7cc1ec942ccef14f0ca18d209967a756bf1dbb1f" +dependencies = [ + "informalsystems-pbjson", + "prost", + "serde", + "tendermint-proto 0.40.4", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crc" +version = "3.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eb8a2a1cd12ab0d987a5d5e825195d372001a4094a0376319d5a0ad71c1ba0d" +dependencies = [ + "crc-catalog", +] + +[[package]] +name = "crc-catalog" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "217698eaf96b4a3f0bc4f3662aaa55bdf913cd54d7204591faa790070c6d0853" + +[[package]] +name = "critical-section" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "790eea4361631c5e7d22598ecd5723ff611904e3344ce8720784c93e3d83d40b" + +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" + +[[package]] +name = "crunchy" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" + +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array 0.14.9", + "rand_core 0.6.4", + "subtle", + "zeroize", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array 0.14.9", + "typenum", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "fiat-crypto", + "rustc_version 0.4.1", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "curve25519-dalek-ng" +version = "4.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c359b7249347e46fb28804470d071c921156ad62b3eef5d34e2ba867533dec8" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.6.4", + "subtle-ng", + "zeroize", +] + +[[package]] +name = "darling" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" +dependencies = [ + "darling_core 0.21.3", + "darling_macro 0.21.3", +] + +[[package]] +name = "darling" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" +dependencies = [ + "darling_core 0.23.0", + "darling_macro 0.23.0", +] + +[[package]] +name = "darling_core" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "serde", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_core" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9865a50f7c335f53564bb694ef660825eb8610e0a53d3e11bf1b0d3df31e03b0" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" +dependencies = [ + "darling_core 0.21.3", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "darling_macro" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" +dependencies = [ + "darling_core 0.23.0", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dashu" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b3e5ac1e23ff1995ef05b912e2b012a8784506987a2651552db2c73fb3d7e0" +dependencies = [ + "dashu-base", + "dashu-float", + "dashu-int", + "dashu-macros", + "dashu-ratio", + "rustversion", +] + +[[package]] +name = "dashu-base" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b80bf6b85aa68c58ffea2ddb040109943049ce3fbdf4385d0380aef08ef289" + +[[package]] +name = "dashu-float" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85078445a8dbd2e1bd21f04a816f352db8d333643f0c9b78ca7c3d1df71063e7" +dependencies = [ + "dashu-base", + "dashu-int", + "num-modular", + "num-order", + "rustversion", + "static_assertions", +] + +[[package]] +name = "dashu-int" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee99d08031ca34a4d044efbbb21dff9b8c54bb9d8c82a189187c0651ffdb9fbf" +dependencies = [ + "cfg-if", + "dashu-base", + "num-modular", + "num-order", + "rustversion", + "static_assertions", +] + +[[package]] +name = "dashu-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93381c3ef6366766f6e9ed9cf09e4ef9dec69499baf04f0c60e70d653cf0ab10" +dependencies = [ + "dashu-base", + "dashu-float", + "dashu-int", + "dashu-ratio", + "paste", + "proc-macro2", + "quote", + "rustversion", +] + +[[package]] +name = "dashu-ratio" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47e33b04dd7ce1ccf8a02a69d3419e354f2bbfdf4eb911a0b7465487248764c9" +dependencies = [ + "dashu-base", + "dashu-float", + "dashu-int", + "num-modular", + "num-order", + "rustversion", +] + +[[package]] +name = "der" +version = "0.7.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" +dependencies = [ + "const-oid", + "pem-rfc7468", + "zeroize", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "powerfmt", + "serde_core", +] + +[[package]] +name = "derivative" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "derive-where" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "0.99.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6edb4b64a43d977b8e99788fe3a04d483834fba1215a7e02caa415b626497f7f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a9b99b9cbbe49445b21764dc0625032a89b145a2642e67603e1c936f5458d05" +dependencies = [ + "derive_more-impl 1.0.0", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl 2.1.1", +] + +[[package]] +name = "derive_more-impl" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7330aeadfbe296029522e6c40f315320aba36fc43a5b3632f3795348f3bd22" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version 0.4.1", + "syn 2.0.117", + "unicode-xid", +] + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array 0.14.9", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer 0.10.4", + "const-oid", + "crypto-common", + "subtle", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "git+https://github.com/ziren-patches/signatures.git?branch=patch-ecdsa-0.16.9#da0cd8753b243e09acf82f8e58f8da0e28e7287d" +dependencies = [ + "der", + "digest 0.10.7", + "elliptic-curve", + "rfc6979", + "serdect", + "signature", + "spki", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-consensus" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8465edc8ee7436ffea81d21a019b16676ee3db267aa8d5a8d729581ecf998b" +dependencies = [ + "curve25519-dalek-ng", + "hex", + "rand_core 0.6.4", + "sha2 0.9.9", + "zeroize", +] + +[[package]] +name = "educe" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7bc049e1bd8cdeb31b68bbd586a9464ecf9f3944af3958a7a9d0f8b9799417" +dependencies = [ + "enum-ordinalize", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +dependencies = [ + "serde", +] + +[[package]] +name = "elf" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4445909572dbd556c457c849c4ca58623d84b27c8fff1e74b0b4227d8b90d17b" + +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest 0.10.7", + "ff 0.13.1", + "generic-array 0.14.9", + "group 0.13.0", + "hkdf", + "pem-rfc7468", + "pkcs8", + "rand_core 0.6.4", + "sec1", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "enum-map" +version = "2.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6866f3bfdf8207509a033af1a75a7b08abda06bbaaeae6669323fd5a097df2e9" +dependencies = [ + "enum-map-derive", + "serde", +] + +[[package]] +name = "enum-map-derive" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f282cfdfe92516eb26c2af8589c274c7c17681f5ecc03c18255fe741c6aa64eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "enum-ordinalize" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a1091a7bb1f8f2c4b28f1fe2cef4980ca2d410a3d727d67ecc3178c9b0800f0" +dependencies = [ + "enum-ordinalize-derive", +] + +[[package]] +name = "enum-ordinalize-derive" +version = "4.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca9601fb2d62598ee17836250842873a413586e5d7ed88b356e38ddbb0ec631" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "env_filter" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e90c2accc4b07a8456ea0debdc2e7587bdd890680d71173a15d4ae604f6eef" +dependencies = [ + "log", + "regex", +] + +[[package]] +name = "env_logger" +version = "0.11.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0621c04f2196ac3f488dd583365b9c09be011a4ab8b9f37248ffcc8f6198b56a" +dependencies = [ + "anstream", + "anstyle", + "env_filter", + "jiff", + "log", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "fastrlp" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139834ddba373bbdd213dffe02c8d110508dcf1726c2be27e8d1f7d7e1856418" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "fastrlp" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce8dba4714ef14b8274c371879b175aa55b16b30f269663f19d576f380018dc4" +dependencies = [ + "arrayvec", + "auto_impl", + "bytes", +] + +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "bitvec", + "byteorder", + "ff_derive", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "ff_derive" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f10d12652036b0e99197587c6ba87a8fc3031986499973c030d8b44fcc151b60" +dependencies = [ + "addchain", + "num-bigint 0.3.3", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand 0.8.6", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "fixedbitset" +version = "0.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" + +[[package]] +name = "flex-error" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" +dependencies = [ + "paste", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-io" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" + +[[package]] +name = "futures-sink" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "futures-utils-wasm" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42012b0f064e01aa58b545fe3727f90f7dd4020f4a3ea735b50344965f5a57e9" + +[[package]] +name = "gcd" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d758ba1b47b00caf47f24925c0074ecb20d6dfcffe7f6d53395c0465674841a" + +[[package]] +name = "generic-array" +version = "0.14.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bb6743198531e02858aeaea5398fcc883e71851fcbcb5a2f773e2fb6cb1edf2" +dependencies = [ + "typenum", + "version_check", + "zeroize", +] + +[[package]] +name = "generic-array" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dab9e9188e97a93276e1fe7b56401b851e2b45a46d045ca658100c1303ada649" +dependencies = [ + "rustversion", + "serde_core", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0de51e6874e94e7bf76d726fc5d13ba782deca734ff60d5bb2fb2607c7406555" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", + "wasip2", + "wasip3", +] + +[[package]] +name = "gimli" +version = "0.32.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "memuse", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff 0.13.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "guest" +version = "1.1.0" +dependencies = [ + "bitcoin", + "bitcoin-light-client-circuit", + "hex", + "tracing", + "verifier", + "zkm-primitives", + "zkm-zkvm", +] + +[[package]] +name = "guest-executor" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/reth-processor?branch=feat%2Fslot-state-check#294aa06127be4bfda47a7fd4905b93bb8e664dca" +dependencies = [ + "alloy-consensus", + "alloy-evm", + "alloy-network", + "alloy-primitives", + "alloy-rpc-types", + "bincode", + "itertools 0.13.0", + "kzg-rs", + "mpt", + "primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-errors", + "reth-ethereum-consensus", + "reth-ethereum-primitives", + "reth-evm", + "reth-evm-ethereum", + "reth-execution-types", + "reth-primitives-traits", + "reth-trie", + "revm", + "revm-primitives", + "serde", + "serde_json", + "serde_with", + "thiserror 1.0.69", +] + +[[package]] +name = "halo2" +version = "0.1.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a23c779b38253fe1538102da44ad5bd5378495a61d2c4ee18d64eaa61ae5995" +dependencies = [ + "halo2_proofs", +] + +[[package]] +name = "halo2_proofs" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e925780549adee8364c7f2b685c753f6f3df23bde520c67416e93bf615933760" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "pasta_curves 0.4.1", + "rand_core 0.6.4", + "rayon", +] + +[[package]] +name = "halo2curves" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d380afeef3f1d4d3245b76895172018cfb087d9976a7cabcd5597775b2933e07" +dependencies = [ + "blake2", + "digest 0.10.7", + "ff 0.13.1", + "group 0.13.0", + "halo2derive", + "hex", + "lazy_static", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "pairing 0.23.0", + "pasta_curves 0.5.1", + "paste", + "rand 0.8.6", + "rand_core 0.6.4", + "rayon", + "serde", + "serde_arrays 0.1.0", + "sha2 0.10.9", + "static_assertions", + "subtle", + "unroll", +] + +[[package]] +name = "halo2derive" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdb99e7492b4f5ff469d238db464131b86c2eaac814a78715acba369f64d2c76" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", + "serde", +] + +[[package]] +name = "hashbrown" +version = "0.15.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" +dependencies = [ + "allocator-api2", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", + "serde", + "serde_core", +] + +[[package]] +name = "hashbrown" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" + +[[package]] +name = "header-chain" +version = "0.4.0" +dependencies = [ + "bitcoin", + "borsh", + "crypto-bigint", + "serde", + "sha2 0.10.9", + "verifier", + "zkm-zkvm", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] + +[[package]] +name = "hex-conservative" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda06d18ac606267c40c04e41b9947729bf8b9efe74bd4e82b61a5f26a510b9f" +dependencies = [ + "arrayvec", +] + +[[package]] +name = "hex_lit" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3011d1213f159867b13cfd6ac92d2cd5f1345762c63be3554e84092d85a50bbd" + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + +[[package]] +name = "hmac" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "http" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2984d1cd16c883d7935b9e07e44071dca8d917fd52ecc02c04d5fa0b5a3f191c" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92219b62b3e2b4d88ac5119f8904c10f8f61bf7e95b640d25ba3075e6cac2c29" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c56e5ee99d6e3d33bd91c5d85458b6005a22140021cc324cea84dd0e72cff3b4" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da3be0ae77ea334f4da67c12f149704f19f81d1adf7c51cf482943e84a2bad38" + +[[package]] +name = "icu_properties" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bee3b67d0ea5c2cca5003417989af8996f8604e34fb9ddf96208a033901e70de" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e2bbb201e0c04f7b4b3e14382af113e17ba4f63e2c9d2ee626b720cbce54a14" + +[[package]] +name = "icu_provider" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "139c4cf31c8b5f33d7e199446eff9c1e02decfc2f0eec2c8d71f65befa45b421" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "id-arena" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d3067d79b975e8844ca9eb072e16b31c3c1c36928edf9c6789548c524d0d954" + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "impl-codec" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba6a270039626615617f3f36d15fc827041df3b78c439da2cadfa47455a77f2f" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0eb5a3343abf848c0984fe4604b2b105da9539376e24fc0a3b0007411ae4fd9" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown 0.17.0", + "serde", + "serde_core", +] + +[[package]] +name = "informalsystems-pbjson" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aa4a0980c8379295100d70854354e78df2ee1c6ca0f96ffe89afeb3140e3a3d" +dependencies = [ + "base64 0.21.7", + "serde", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itertools" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b192c782037fadd9cfa75548310488aabdbf3d2da73885b31bd0abd03351285" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "jiff" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f00b5dbd620d61dfdcb6007c9c1f6054ebd75319f163d886a9055cec1155073d" +dependencies = [ + "jiff-static", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", +] + +[[package]] +name = "jiff-static" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e000de030ff8022ea1da3f466fbb0f3a809f5e51ed31f6dd931c35181ad8e6d7" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "jobserver" +version = "0.1.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" +dependencies = [ + "getrandom 0.3.4", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.97" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1840c94c045fbcf8ba2812c95db44499f7c64910a912551aaaa541decebcacf" +dependencies = [ + "cfg-if", + "futures-util", + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "jubjub" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" +dependencies = [ + "bitvec", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "k256" +version = "0.13.4" +source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-k256-0.13.4#8266b228a39402a0ba68d644b7f26b85b5112fe3" +dependencies = [ + "cfg-if", + "ecdsa", + "elliptic-curve", + "hex", + "once_cell", + "serdect", + "sha2 0.10.9", + "signature", + "zkm-lib", +] + +[[package]] +name = "keccak" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" +dependencies = [ + "cpufeatures", +] + +[[package]] +name = "keccak-asm" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa468878266ad91431012b3e5ef1bf9b170eab22883503a318d46857afa4579a" +dependencies = [ + "digest 0.10.7", + "sha3-asm", +] + +[[package]] +name = "konst" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128133ed7824fcd73d6e7b17957c5eb7bacb885649bd8c69708b2331a10bcefb" +dependencies = [ + "konst_macro_rules", +] + +[[package]] +name = "konst_macro_rules" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4933f3f57a8e9d9da04db23fb153356ecaf00cbd14aee46279c33dc80925c37" + +[[package]] +name = "kzg-rs" +version = "0.2.6" +source = "git+https://github.com/ziren-patches/kzg-rs?branch=ziren#4cd5e3086a49dc1e94ffbd63ee4a7a9b2e342faa" +dependencies = [ + "bls12_381 0.8.0", + "ff 0.13.1", + "hex", + "serde_arrays 0.2.0", + "sha2 0.10.9", + "spin", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin", +] + +[[package]] +name = "leb128fmt" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libm" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" + +[[package]] +name = "macro-string" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b27834086c65ec3f9387b096d66e99f221cf081c2b738042aa252bcd41204e3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "memchr" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" + +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", +] + +[[package]] +name = "modular-bitfield" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a53d79ba8304ac1c4f9eb3b9d281f21f7be9d4626f72ce7df4ad8fbde4f38a74" +dependencies = [ + "modular-bitfield-impl", + "static_assertions", +] + +[[package]] +name = "modular-bitfield-impl" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a7d5f7076603ebc68de2dc6a650ec331a062a13abaa346975be747bbfa4b789" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "mpt" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/reth-processor?branch=feat%2Fslot-state-check#294aa06127be4bfda47a7fd4905b93bb8e664dca" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types", + "alloy-rpc-types-debug", + "reth-trie", + "rlp", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "nohash-hasher" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" + +[[package]] +name = "ntapi" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" +dependencies = [ + "winapi", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint 0.4.6", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6f7833f2cbf2360a6cfd58cd41a53aa7a90bd4c202f5b1c7dd2ed73c57b2c3" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-conv" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6673768db2d862beb9b39a78fdcb1a69439615d5794a1be50caa9bc92c81967" + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-modular" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17bb261bf36fa7d83f4c294f834e91256769097b3cb505d44831e0a179ac647f" + +[[package]] +name = "num-order" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "537b596b97c40fcf8056d153049eb22f481c17ebce72a513ec9286e4986d1bb6" +dependencies = [ + "num-modular", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", + "libm", +] + +[[package]] +name = "num_cpus" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "nums" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf3c74f925fb8cfc49a8022f2afce48a0683b70f9e439885594e84c5edbf5b01" +dependencies = [ + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "rand 0.8.6", +] + +[[package]] +name = "nybbles" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d49ff0c0d00d4a502b39df9af3a525e1efeb14b9dabb5bb83335284c1309210" +dependencies = [ + "alloy-rlp", + "cfg-if", + "proptest", + "ruint", + "serde", + "smallvec", +] + +[[package]] +name = "object" +version = "0.37.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" +dependencies = [ + "critical-section", + "portable-atomic", +] + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + +[[package]] +name = "op-alloy-consensus" +version = "0.22.0" +source = "git+https://github.com/ziren-patches/op-alloy?branch=patch-0.22.0#20048eaa105b1c06ca4fca6108a047a2fda0c494" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-serde", + "derive_more 2.1.1", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "p256" +version = "0.13.2" +source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-p256-0.13.2#a6f1a1fb07020d00f627725a20dc336983be3946" +dependencies = [ + "ecdsa", + "elliptic-curve", + "hex", + "primeorder", + "sha2 0.10.9", + "zkm-lib", +] + +[[package]] +name = "p3-air" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "p3-field", + "p3-matrix", +] + +[[package]] +name = "p3-bn254-fr" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "ff 0.13.1", + "halo2curves", + "num-bigint 0.4.6", + "p3-field", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-challenger" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "p3-field", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-circle" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-fri", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-commit" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-challenger", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-util", + "serde", +] + +[[package]] +name = "p3-dft" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-field" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "nums", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.6", + "serde", + "tracing", +] + +[[package]] +name = "p3-fri" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-interpolation", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.6", + "serde", + "tracing", +] + +[[package]] +name = "p3-interpolation" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", +] + +[[package]] +name = "p3-keccak" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-field", + "p3-symmetric", + "p3-util", + "tiny-keccak", +] + +[[package]] +name = "p3-keccak-air" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "p3-air", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "tracing", +] + +[[package]] +name = "p3-koala-bear" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "p3-field", + "p3-mds", + "p3-monty-31", + "p3-poseidon2", + "p3-symmetric", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-matrix" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-field", + "p3-maybe-rayon", + "p3-util", + "rand 0.8.6", + "serde", + "tracing", + "transpose", +] + +[[package]] +name = "p3-maybe-rayon" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "rayon", +] + +[[package]] +name = "p3-mds" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-symmetric", + "p3-util", + "rand 0.8.6", +] + +[[package]] +name = "p3-merkle-tree" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-commit", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-symmetric", + "p3-util", + "rand 0.8.6", + "serde", + "tracing", +] + +[[package]] +name = "p3-mersenne-31" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "num-bigint 0.4.6", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-monty-31" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "num-bigint 0.4.6", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "rand 0.8.6", + "serde", + "serde_json", + "tracing", + "transpose", +] + +[[package]] +name = "p3-poseidon2" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "gcd", + "p3-field", + "p3-mds", + "p3-symmetric", + "rand 0.8.6", + "serde", +] + +[[package]] +name = "p3-symmetric" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-field", + "serde", +] + +[[package]] +name = "p3-uni-stark" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "itertools 0.13.0", + "p3-air", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-matrix", + "p3-maybe-rayon", + "p3-util", + "serde", + "tracing", +] + +[[package]] +name = "p3-util" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/Plonky3#faa24ca4597eebeecbf71b194b71c7d1a99b3f01" +dependencies = [ + "lock_api", + "serde", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + +[[package]] +name = "pairing" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81fec4625e73cf41ef4bb6846cafa6d44736525f442ba45e407c4a000a13996f" +dependencies = [ + "group 0.13.0", +] + +[[package]] +name = "parity-scale-codec" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799781ae679d79a948e13d4824a40970bfa500058d245760dd857301059810fa" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "const_format", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "rustversion", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34b4653168b563151153c9e4c08ebed57fb8262bebfa79711552fa983c623e7a" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "pasta_curves" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc65faf8e7313b4b1fbaa9f7ca917a0eed499a9663be71477f87993604341d8" +dependencies = [ + "blake2b_simd", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "rand 0.8.6", + "static_assertions", + "subtle", +] + +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff 0.13.1", + "group 0.13.0", + "hex", + "lazy_static", + "rand 0.8.6", + "serde", + "static_assertions", + "subtle", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "pem-rfc7468" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +dependencies = [ + "base64ct", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pest" +version = "2.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0848c601009d37dfa3430c4666e147e49cdcf1b92ecd3e63657d8a5f19da662" +dependencies = [ + "memchr", + "ucd-trie", +] + +[[package]] +name = "petgraph" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.14.0", +] + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + +[[package]] +name = "portable-atomic-util" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a106d1259c23fac8e543272398ae0e3c0b8d33c88ed73d0cc71b0f1d902618" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0103b1cef7ec0cf76490e969665504990193874ea05c85ff9bab8b911d0a0564" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.117", +] + +[[package]] +name = "primeorder" +version = "0.13.1" +source = "git+https://github.com/ziren-patches/elliptic-curves?branch=patch-p256-0.13.2#a6f1a1fb07020d00f627725a20dc336983be3946" +dependencies = [ + "elliptic-curve", +] + +[[package]] +name = "primitive-types" +version = "0.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "primitives" +version = "0.1.0" +source = "git+https://github.com/ProjectZKM/reth-processor?branch=feat%2Fslot-state-check#294aa06127be4bfda47a7fd4905b93bb8e664dca" +dependencies = [ + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rpc-types", + "eyre", + "reth-chainspec", + "reth-primitives-traits", + "reth-trie", + "serde", + "serde_json", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "proptest" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744" +dependencies = [ + "bit-set", + "bit-vec", + "bitflags", + "num-traits", + "rand 0.9.4", + "rand_chacha 0.9.0", + "rand_xorshift", + "regex-syntax", + "rusty-fork", + "tempfile", + "unarray", +] + +[[package]] +name = "prost" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive", +] + +[[package]] +name = "prost-build" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "once_cell", + "petgraph", + "prettyplease", + "prost", + "prost-types", + "regex", + "syn 2.0.117", + "tempfile", +] + +[[package]] +name = "prost-derive" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a56d757972c98b346a9b766e3f02746cde6dd1cd1d1d563472929fdd74bec4d" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost", +] + +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quote" +version = "1.0.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" +dependencies = [ + "libc", + "rand_chacha 0.3.1", + "rand_core 0.6.4", + "serde", +] + +[[package]] +name = "rand" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44c5af06bb1b7d3216d91932aed5265164bf384dc89cd6ba05cf59a35f5f76ea" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", + "serde", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom 0.2.17", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", + "serde", +] + +[[package]] +name = "rand_xorshift" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "513962919efc330f829edb2535844d1b912b0fbe2ca165d613e4e8788bb05a5a" +dependencies = [ + "rand_core 0.9.5", +] + +[[package]] +name = "rayon" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb39b166781f92d482534ef4b4b1b2568f42613b53e5b6c160e24cfbfa30926d" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "rayon-scan" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f87cc11a0140b4b0da0ffc889885760c61b13672d80a908920b2c0df078fa14" +dependencies = [ + "rayon", +] + +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "regex" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" + +[[package]] +name = "reth-chainspec" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-chains", + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "auto_impl", + "derive_more 2.1.1", + "reth-ethereum-forks", + "reth-network-peers", + "reth-primitives-traits", + "serde_json", +] + +[[package]] +name = "reth-codecs" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-trie", + "bytes", + "modular-bitfield", + "op-alloy-consensus", + "reth-codecs-derive", + "reth-zstd-compressors", + "serde", +] + +[[package]] +name = "reth-codecs-derive" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "reth-consensus" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "auto_impl", + "reth-execution-types", + "reth-primitives-traits", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-consensus-common" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "reth-chainspec", + "reth-consensus", + "reth-primitives-traits", +] + +[[package]] +name = "reth-db-models" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "reth-primitives-traits", +] + +[[package]] +name = "reth-errors" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "reth-consensus", + "reth-execution-errors", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-ethereum-consensus" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "reth-chainspec", + "reth-consensus", + "reth-consensus-common", + "reth-execution-types", + "reth-primitives-traits", + "tracing", +] + +[[package]] +name = "reth-ethereum-forks" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-eip2124", + "alloy-hardforks", + "alloy-primitives", + "auto_impl", + "once_cell", +] + +[[package]] +name = "reth-ethereum-primitives" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-serde", + "reth-codecs", + "reth-primitives-traits", + "serde", + "serde_with", +] + +[[package]] +name = "reth-evm" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "anyhow", + "auto_impl", + "derive_more 2.1.1", + "futures-util", + "reth-execution-errors", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-api", + "reth-storage-errors", + "reth-trie-common", + "revm", + "revm-database", +] + +[[package]] +name = "reth-evm-ethereum" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "alloy-rpc-types-engine", + "reth-chainspec", + "reth-ethereum-forks", + "reth-ethereum-primitives", + "reth-evm", + "reth-execution-types", + "reth-primitives-traits", + "reth-storage-errors", + "revm", +] + +[[package]] +name = "reth-execution-errors" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-evm", + "alloy-primitives", + "alloy-rlp", + "nybbles", + "reth-storage-errors", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-execution-types" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-evm", + "alloy-primitives", + "derive_more 2.1.1", + "reth-ethereum-primitives", + "reth-primitives-traits", + "reth-trie-common", + "revm", +] + +[[package]] +name = "reth-network-peers" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "serde_with", + "thiserror 2.0.18", + "url", +] + +[[package]] +name = "reth-primitives-traits" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-genesis", + "alloy-primitives", + "alloy-rlp", + "alloy-rpc-types-eth", + "alloy-trie", + "auto_impl", + "bytes", + "derive_more 2.1.1", + "once_cell", + "op-alloy-consensus", + "reth-codecs", + "revm-bytecode", + "revm-primitives", + "revm-state", + "secp256k1 0.30.0", + "serde", + "serde_with", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-prune-types" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "strum 0.27.2", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-stages-types" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-primitives", + "reth-trie-common", +] + +[[package]] +name = "reth-static-file-types" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-primitives", + "derive_more 2.1.1", + "serde", + "strum 0.27.2", +] + +[[package]] +name = "reth-storage-api" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rpc-types-engine", + "auto_impl", + "reth-chainspec", + "reth-db-models", + "reth-ethereum-primitives", + "reth-execution-types", + "reth-primitives-traits", + "reth-prune-types", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "revm-database", +] + +[[package]] +name = "reth-storage-errors" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "derive_more 2.1.1", + "reth-primitives-traits", + "reth-prune-types", + "reth-static-file-types", + "revm-database-interface", + "thiserror 2.0.18", +] + +[[package]] +name = "reth-trie" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-eips", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "itertools 0.14.0", + "reth-execution-errors", + "reth-primitives-traits", + "reth-stages-types", + "reth-storage-errors", + "reth-trie-common", + "reth-trie-sparse", + "revm-database", + "tracing", +] + +[[package]] +name = "reth-trie-common" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "derive_more 2.1.1", + "itertools 0.14.0", + "nybbles", + "rayon", + "reth-primitives-traits", + "revm-database", +] + +[[package]] +name = "reth-trie-sparse" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "alloy-primitives", + "alloy-rlp", + "alloy-trie", + "auto_impl", + "reth-execution-errors", + "reth-primitives-traits", + "reth-trie-common", + "smallvec", + "tracing", +] + +[[package]] +name = "reth-zstd-compressors" +version = "1.9.3" +source = "git+https://github.com/ziren-patches/reth?branch=patch-1.9.3#fb7e5b4b347ac28d7f3f937698a3b084bd158aa2" +dependencies = [ + "zstd", +] + +[[package]] +name = "revm" +version = "31.0.2" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database", + "revm-database-interface", + "revm-handler", + "revm-inspector", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", +] + +[[package]] +name = "revm-bytecode" +version = "7.1.1" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "bitvec", + "phf", + "revm-primitives", + "serde", +] + +[[package]] +name = "revm-context" +version = "11.0.2" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "alloy-consensus", + "bitvec", + "cfg-if", + "derive-where", + "revm-bytecode", + "revm-context-interface", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-context-interface" +version = "12.0.1" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "alloy-consensus", + "alloy-eip2930", + "alloy-eip7702", + "auto_impl", + "either", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database" +version = "9.0.5" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "alloy-eips", + "revm-bytecode", + "revm-database-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-database-interface" +version = "8.0.5" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "auto_impl", + "either", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-handler" +version = "12.0.2" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "auto_impl", + "derive-where", + "revm-bytecode", + "revm-context", + "revm-context-interface", + "revm-database-interface", + "revm-interpreter", + "revm-precompile", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-inspector" +version = "12.0.2" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "auto_impl", + "either", + "revm-context", + "revm-database-interface", + "revm-handler", + "revm-interpreter", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-interpreter" +version = "29.0.1" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "revm-bytecode", + "revm-context-interface", + "revm-primitives", + "revm-state", + "serde", +] + +[[package]] +name = "revm-precompile" +version = "29.0.1" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "ark-bls12-381", + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "arrayref", + "aurora-engine-modexp", + "cfg-if", + "k256", + "p256", + "revm-primitives", + "ripemd", + "sha2 0.10.9", + "substrate-bn", +] + +[[package]] +name = "revm-primitives" +version = "21.0.2" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "alloy-primitives", + "num_enum", + "once_cell", + "serde", +] + +[[package]] +name = "revm-state" +version = "8.1.1" +source = "git+https://github.com/ziren-patches/revm?branch=patch-31.0.2#0cd8a9cffabcc01fd92b8f12d6d601040e0ec88f" +dependencies = [ + "bitflags", + "revm-bytecode", + "revm-primitives", + "serde", +] + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "git+https://github.com/ziren-patches/signatures.git?branch=patch-ecdsa-0.16.9#da0cd8753b243e09acf82f8e58f8da0e28e7287d" +dependencies = [ + "hmac", + "subtle", +] + +[[package]] +name = "ripemd" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd124222d17ad93a644ed9d011a40f4fb64aa54275c08cc216524a9ea82fb09f" +dependencies = [ + "digest 0.10.7", +] + +[[package]] +name = "rlp" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb919243f34364b6bd2fc10ef797edbfa75f33c252e7998527479c6d6b47e1ec" +dependencies = [ + "bytes", + "rustc-hex", +] + +[[package]] +name = "ruint" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0298da754d1395046b0afdc2f20ee76d29a8ae310cd30ffa84ed42acba9cb12a" +dependencies = [ + "alloy-rlp", + "ark-ff 0.3.0", + "ark-ff 0.4.2", + "ark-ff 0.5.0", + "bytes", + "fastrlp 0.3.1", + "fastrlp 0.4.0", + "num-bigint 0.4.6", + "num-integer", + "num-traits", + "parity-scale-codec", + "primitive-types", + "proptest", + "rand 0.8.6", + "rand 0.9.4", + "rlp", + "ruint-macro", + "serde_core", + "valuable", + "zeroize", +] + +[[package]] +name = "ruint-macro" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" + +[[package]] +name = "rustc-demangle" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" + +[[package]] +name = "rustc-hash" +version = "2.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94300abf3f1ae2e2b8ffb7b58043de3d399c73fa6f4b73826402a5c457614dbe" + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0dfe2087c51c460008730de8b57e6a320782fbfb312e1f4d520e6c6fae155ee" +dependencies = [ + "semver 0.11.0", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver 1.0.28", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rustversion" +version = "1.0.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" + +[[package]] +name = "rusty-fork" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + +[[package]] +name = "scale-info" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "346a3b32eba2640d17a9cb5927056b08f3de90f65b72fe09402c2ad07d684d0b" +dependencies = [ + "cfg-if", + "derive_more 1.0.0", + "parity-scale-codec", + "scale-info-derive", +] + +[[package]] +name = "scale-info-derive" +version = "2.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6630024bf739e2179b91fb424b28898baf819414262c5d376677dbff1fe7ebf" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2b42f36aa1cd011945615b92222f6bf73c599a102a300334cd7f8dbeec726cc" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array 0.14.9", + "pkcs8", + "serdect", + "subtle", + "zeroize", +] + +[[package]] +name = "secp256k1" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e0cc0f1cf93f4969faf3ea1c7d8a9faed25918d96affa959720823dfe86d4f3" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.6", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1" +version = "0.30.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b50c5943d326858130af85e049f2661ba3c78b26589b8ab98e65e80ae44a1252" +dependencies = [ + "bitcoin_hashes", + "rand 0.8.6", + "secp256k1-sys", + "serde", +] + +[[package]] +name = "secp256k1-sys" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4387882333d3aa8cb20530a17c69a3752e97837832f34f6dccc760e715001d9" +dependencies = [ + "cc", +] + +[[package]] +name = "semver" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f301af10236f6df4160f7c3f04eec6dbc70ace82d23326abad5edee88801c6b6" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "semver-parser" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9900206b54a3527fdc7b8a938bffd94a568bac4f4aa8113b209df75a09c0dec2" +dependencies = [ + "pest", +] + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_arrays" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38636132857f68ec3d5f3eb121166d2af33cb55174c4d5ff645db6165cbef0fd" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_bytes" +version = "0.11.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5d440709e79d88e51ac01c4b72fc6cb7314017bb7da9eeff678aa94c10e3ea8" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_json" +version = "1.0.149" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serde_with" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f05839ce67618e14a09b286535c0d9c94e85ef25469b0e13cb4f844e5593eb19" +dependencies = [ + "base64 0.22.1", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.0", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf2ebbe86054f9b45bc3881e865683ccfaccce97b9b4cb53f3039d67f355a334" +dependencies = [ + "darling 0.23.0", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "serdect" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a84f14a19e9a014bb9f4512488d9829a68e04ecabffb0f9904cd1ace94598177" +dependencies = [ + "base16ct", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer 0.9.0", + "cfg-if", + "cpufeatures", + "digest 0.9.0", + "opaque-debug", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "git+https://github.com/ziren-patches/RustCrypto-hashes?branch=patch-sha2-0.10.9#dbfdbd088ac7b55e03c0b9eb192278efe35d67dc" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest 0.10.7", +] + +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak", +] + +[[package]] +name = "sha3-asm" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59cbb88c189d6352cc8ae96a39d19c7ecad8f7330b29461187f2587fdc2988d5" +dependencies = [ + "cc", + "cfg-if", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "digest 0.10.7", + "rand_core 0.6.4", +] + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "size" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fed904c7fb2856d868b92464fc8fa597fce366edea1a9cbfaa8cb5fe080bd6d" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +dependencies = [ + "serde", +] + +[[package]] +name = "snowbridge-amcl" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "460a9ed63cdf03c1b9847e8a12a5f5ba19c4efd5869e4a737e05be25d7c427e5" +dependencies = [ + "parity-scale-codec", + "scale-info", +] + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "state-chain" +version = "0.4.0" +dependencies = [ + "alloy-consensus", + "alloy-primitives", + "base64 0.21.7", + "bincode", + "bitcoin", + "cosmos-sdk-proto", + "guest-executor", + "header-chain", + "hex", + "prost", + "prost-build", + "prost-derive", + "revm-database-interface", + "serde", + "serde_json", + "sha2 0.10.9", + "tendermint", + "tendermint-light-client-verifier", + "tracing", + "verifier", + "zkm-zkvm", +] + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strength_reduce" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe895eb47f22e2ddd4dabc02bce419d2e643c8e3b585c78158b349195bc24d82" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros 0.26.4", +] + +[[package]] +name = "strum" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af23d6f6c1a224baef9d3f61e287d2761385a5b88fdab4eb4c6f11aeb54c4bcf" +dependencies = [ + "strum_macros 0.27.2", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.117", +] + +[[package]] +name = "strum_macros" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7695ce3845ea4b33927c055a39dc438a45b059f7c1b3d91d38d10355fb8cbca7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "substrate-bn" +version = "0.6.0" +source = "git+https://github.com/ziren-patches/bn?branch=patch-0.6.0#aba71380457d798039111e6cc0fdf2e0718c6766" +dependencies = [ + "bytemuck", + "byteorder", + "cfg-if", + "crunchy", + "lazy_static", + "num-bigint 0.4.6", + "rand 0.8.6", + "rustc-hex", + "zkm-lib", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "subtle-encoding" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcb1ed7b8330c5eed5441052651dd7a12c75e2ed88f2ec024ae1fa3a5e59945" +dependencies = [ + "zeroize", +] + +[[package]] +name = "subtle-ng" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "734676eb262c623cec13c3155096e08d1f8f29adce39ba17948b18dad1e54142" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn-solidity" +version = "1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53f425ae0b12e2f5ae65542e00898d500d4d318b4baf09f40fd0d410454e9947" +dependencies = [ + "paste", + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "sysinfo" +version = "0.30.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a5b4ddaee55fb2bea2bf0e5000747e5f5c0de765e5a5ff87f4cd106439f4bb3" +dependencies = [ + "cfg-if", + "core-foundation-sys", + "libc", + "ntapi", + "once_cell", + "rayon", + "windows", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys", +] + +[[package]] +name = "tendermint" +version = "0.40.3" +source = "git+https://github.com/ProjectZKM/tendermint-rs?branch=patch-0.40.3#5bd318a89601f7db2bce05018246c5ef6dc7fb9b" +dependencies = [ + "bytes", + "digest 0.10.7", + "ed25519", + "ed25519-consensus", + "flex-error", + "futures", + "k256", + "num-traits", + "once_cell", + "prost", + "ripemd", + "serde", + "serde_bytes", + "serde_json", + "serde_repr", + "sha2 0.10.9", + "signature", + "subtle", + "subtle-encoding", + "tendermint-proto 0.40.3", + "time", + "zeroize", +] + +[[package]] +name = "tendermint-light-client-verifier" +version = "0.40.3" +source = "git+https://github.com/ProjectZKM/tendermint-rs?branch=patch-0.40.3#5bd318a89601f7db2bce05018246c5ef6dc7fb9b" +dependencies = [ + "derive_more 0.99.20", + "flex-error", + "serde", + "tendermint", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.40.3" +source = "git+https://github.com/ProjectZKM/tendermint-rs?branch=patch-0.40.3#5bd318a89601f7db2bce05018246c5ef6dc7fb9b" +dependencies = [ + "bytes", + "flex-error", + "prost", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "tendermint-proto" +version = "0.40.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2c40e13d39ca19082d8a7ed22de7595979350319833698f8b1080f29620a094" +dependencies = [ + "bytes", + "flex-error", + "prost", + "serde", + "serde_bytes", + "subtle-encoding", + "time", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" +dependencies = [ + "thiserror-impl 2.0.18", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "threadpool" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" +dependencies = [ + "num_cpus", +] + +[[package]] +name = "time" +version = "0.3.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "743bd48c283afc0388f9b8827b976905fb217ad9e647fae3a379a9283c4def2c" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7694e1cfe791f8d31026952abf09c69ca6f6fa4e1a1229e18988f06a04a12dca" + +[[package]] +name = "time-macros" +version = "0.2.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e70e4c5a0e0a8a4823ad65dfe1a6930e4f4d756dcd9dd7939022b5e8c501215" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + +[[package]] +name = "tinystr" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.11+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b59c4d22ed448339746c59b905d24568fcbb3ab65a500494f7b8c3e97739f2b" +dependencies = [ + "indexmap 2.14.0", + "toml_datetime", + "toml_parser", + "winnow 1.0.2", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow 1.0.2", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-forest" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee40835db14ddd1e3ba414292272eddde9dad04d3d4b65509656414d1c42592f" +dependencies = [ + "ansi_term", + "smallvec", + "thiserror 1.0.69", + "tracing", + "tracing-subscriber", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "transpose" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad61aed86bc3faea4300c7aee358b4c6d0c8d6ccc36524c96e4c92ccf26e77e" +dependencies = [ + "num-integer", + "strength_reduce", +] + +[[package]] +name = "typenum" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40ce102ab67701b8526c123c1bab5cbe42d7040ccfd0f64af1a385808d2f43de" + +[[package]] +name = "ucd-trie" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896d95c02a80c6d6a5d6e953d479f5ddf2dfdb6a244441010e373ac0fb88971" + +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unarray" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eaea85b334db583fe3274d12b4cd1880032beab409c0d774be044d4480ab9a94" + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" + +[[package]] +name = "unicode-xid" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" + +[[package]] +name = "unroll" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ad948c1cb799b1a70f836077721a92a35ac177d4daddf4c20a633786d4cf618" +dependencies = [ + "quote", + "syn 1.0.109", +] + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" +dependencies = [ + "serde", +] + +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "wait-timeout" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.3+wasi-0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20064672db26d7cdc89c7798c48a0fdfac8213434a1186e5ef29fd560ae223d6" +dependencies = [ + "wit-bindgen 0.57.1", +] + +[[package]] +name = "wasip3" +version = "0.4.0+wasi-0.3.0-rc-2026-01-06" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5428f8bf88ea5ddc08faddef2ac4a67e390b88186c703ce6dbd955e1c145aca5" +dependencies = [ + "wit-bindgen 0.51.0", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df52b6d9b87e0c74c9edfa1eb2d9bf85e5d63515474513aa50fa181b3c4f5db1" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b1041f495fb322e64aca85f5756b2172e35cd459376e67f2a6c9dffcedb103" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcd0ff20416988a18ac686d4d4d0f6aae9ebf08a389ff5d29012b05af2a1b41" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.117", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49757b3c82ebf16c57d69365a142940b384176c24df52a087fb748e2085359ea" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-encoder" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "990065f2fe63003fe337b932cfb5e3b80e0b4d0f5ff650e6985b1048f62c8319" +dependencies = [ + "leb128fmt", + "wasmparser", +] + +[[package]] +name = "wasm-metadata" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.14.0", + "wasm-encoder", + "wasmparser", +] + +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags", + "hashbrown 0.15.5", + "indexmap 2.14.0", + "semver 1.0.28", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e48a53791691ab099e5e2ad123536d0fff50652600abaf43bbf952894110d0be" +dependencies = [ + "windows-core 0.52.0", + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link", + "windows-result", + "windows-strings", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ee1708bef14716a11bae175f579062d4554d95be2c6829f518df847b7b3fdd0" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "wit-bindgen-core" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7c566e0f4b284dd6561c786d9cb0142da491f46a9fbed79ea69cdad5db17f21" +dependencies = [ + "anyhow", + "heck", + "indexmap 2.14.0", + "prettyplease", + "syn 2.0.117", + "wasm-metadata", + "wit-bindgen-core", + "wit-component", +] + +[[package]] +name = "wit-bindgen-rust-macro" +version = "0.51.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c0f9bfd77e6a48eccf51359e3ae77140a7f50b1e2ebfe62422d8afdaffab17a" +dependencies = [ + "anyhow", + "prettyplease", + "proc-macro2", + "quote", + "syn 2.0.117", + "wit-bindgen-core", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-component" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d66ea20e9553b30172b5e831994e35fbde2d165325bec84fc43dbf6f4eb9cb2" +dependencies = [ + "anyhow", + "bitflags", + "indexmap 2.14.0", + "log", + "serde", + "serde_derive", + "serde_json", + "wasm-encoder", + "wasm-metadata", + "wasmparser", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8ac4bc1dc3381b7f59c34f00b67e18f910c2c0f50015669dde7def656a736" +dependencies = [ + "anyhow", + "id-arena", + "indexmap 2.14.0", + "log", + "semver 1.0.28", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "writeable" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "yoke" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abe8c5fda708d9ca3df187cae8bfb9ceda00dd96231bed36e445a1a48e66f9ca" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zerocopy" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.48" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerofrom" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69faa1f2a1ea75661980b013019ed6687ed0e83d069bc1114e2cc74c6c04c4df" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" +dependencies = [ + "zeroize_derive", +] + +[[package]] +name = "zeroize_derive" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zerotrie" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f9152d31db0792fa83f70fb2f83148effb5c1f5b8c7686c3459e361d9bc20bf" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625dc425cab0dca6dc3c3319506e6593dcb08a9f387ea3b284dbd52a92c40555" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "zkhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4352d1081da6922701401cdd4cbf29a2723feb4cfabb5771f6fee8e9276da1c7" +dependencies = [ + "ark-ff 0.4.2", + "ark-std 0.4.0", + "bitvec", + "blake2", + "bls12_381 0.7.1", + "byteorder", + "cfg-if", + "group 0.12.1", + "group 0.13.0", + "halo2", + "hex", + "jubjub", + "lazy_static", + "pasta_curves 0.5.1", + "rand 0.8.6", + "serde", + "sha2 0.10.9", + "sha3", + "subtle", +] + +[[package]] +name = "zkm-core-executor" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "anyhow", + "bincode", + "bytemuck", + "elf", + "enum-map", + "env_logger", + "eyre", + "hashbrown 0.14.5", + "hex", + "itertools 0.13.0", + "log", + "nohash-hasher", + "num", + "num_enum", + "p3-field", + "p3-koala-bear", + "p3-maybe-rayon", + "p3-symmetric", + "rand 0.8.6", + "rayon-scan", + "serde", + "serde_json", + "sha2 0.10.9", + "strum 0.26.3", + "strum_macros 0.26.4", + "thiserror 1.0.69", + "tiny-keccak", + "tracing", + "tracing-subscriber", + "typenum", + "vec_map", + "zkm-curves", + "zkm-primitives", + "zkm-stark", +] + +[[package]] +name = "zkm-core-machine" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "bincode", + "cfg-if", + "elliptic-curve", + "generic-array 1.4.1", + "hashbrown 0.14.5", + "hex", + "itertools 0.13.0", + "k256", + "log", + "num", + "num_cpus", + "p256", + "p3-air", + "p3-challenger", + "p3-field", + "p3-keccak-air", + "p3-koala-bear", + "p3-matrix", + "p3-maybe-rayon", + "p3-poseidon2", + "p3-uni-stark", + "p3-util", + "rand 0.8.6", + "rayon", + "rayon-scan", + "serde", + "serde_json", + "size", + "snowbridge-amcl", + "static_assertions", + "strum 0.26.3", + "strum_macros 0.26.4", + "tempfile", + "thiserror 1.0.69", + "tiny-keccak", + "tracing", + "tracing-forest", + "tracing-subscriber", + "typenum", + "web-time", + "zkm-core-executor", + "zkm-curves", + "zkm-derive", + "zkm-primitives", + "zkm-stark", +] + +[[package]] +name = "zkm-curves" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "cfg-if", + "curve25519-dalek", + "dashu", + "elliptic-curve", + "generic-array 1.4.1", + "itertools 0.13.0", + "k256", + "num", + "p256", + "p3-field", + "serde", + "snowbridge-amcl", + "thiserror 1.0.69", + "tracing", + "typenum", + "zkm-primitives", + "zkm-stark", +] + +[[package]] +name = "zkm-derive" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "zkm-lib" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "bincode", + "cfg-if", + "elliptic-curve", + "serde", + "sha2 0.10.9", + "zkm-primitives", +] + +[[package]] +name = "zkm-primitives" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "bincode", + "hex", + "lazy_static", + "num-bigint 0.4.6", + "p3-field", + "p3-koala-bear", + "p3-monty-31", + "p3-poseidon2", + "p3-symmetric", + "serde", + "sha2 0.10.9", +] + +[[package]] +name = "zkm-recursion-core" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "backtrace", + "ff 0.13.1", + "hashbrown 0.14.5", + "itertools 0.13.0", + "p3-air", + "p3-bn254-fr", + "p3-challenger", + "p3-commit", + "p3-dft", + "p3-field", + "p3-fri", + "p3-koala-bear", + "p3-matrix", + "p3-maybe-rayon", + "p3-merkle-tree", + "p3-monty-31", + "p3-poseidon2", + "p3-symmetric", + "p3-util", + "rand 0.8.6", + "serde", + "static_assertions", + "thiserror 1.0.69", + "tracing", + "vec_map", + "zkhash", + "zkm-core-machine", + "zkm-derive", + "zkm-primitives", + "zkm-stark", +] + +[[package]] +name = "zkm-stark" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "arrayref", + "hashbrown 0.14.5", + "itertools 0.13.0", + "num-bigint 0.4.6", + "num-traits", + "p3-air", + "p3-challenger", + "p3-circle", + "p3-commit", + "p3-dft", + "p3-field", + "p3-fri", + "p3-keccak", + "p3-koala-bear", + "p3-matrix", + "p3-maybe-rayon", + "p3-mds", + "p3-merkle-tree", + "p3-mersenne-31", + "p3-poseidon2", + "p3-symmetric", + "p3-uni-stark", + "p3-util", + "rand 0.8.6", + "rayon-scan", + "serde", + "strum 0.26.3", + "strum_macros 0.26.4", + "sysinfo", + "tracing", + "tracing-forest", + "tracing-subscriber", + "zkm-derive", + "zkm-primitives", + "zkm-zkvm", +] + +[[package]] +name = "zkm-verifier" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "anyhow", + "bincode", + "hex", + "itertools 0.13.0", + "lazy_static", + "once_cell", + "p3-bn254-fr", + "p3-commit", + "p3-field", + "p3-koala-bear", + "p3-symmetric", + "p3-util", + "rayon", + "serde", + "sha2 0.10.9", + "strum_macros 0.26.4", + "substrate-bn", + "thiserror 2.0.18", + "zkm-core-executor", + "zkm-core-machine", + "zkm-primitives", + "zkm-recursion-core", + "zkm-stark", +] + +[[package]] +name = "zkm-zkvm" +version = "1.2.5" +source = "git+https://github.com/ProjectZKM/Ziren#7c2071eaa8d43d9d0cb76077ec94b9d98f33073e" +dependencies = [ + "bincode", + "cfg-if", + "getrandom 0.2.17", + "lazy_static", + "libm", + "p3-field", + "p3-koala-bear", + "rand 0.8.6", + "serde", + "sha2 0.10.9", + "zkm-lib", + "zkm-primitives", +] + +[[package]] +name = "zmij" +version = "1.0.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8848ee67ecc8aedbaf3e4122217aff892639231befc6a1b58d29fff4c2cabaa" + +[[package]] +name = "zstd" +version = "0.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "7.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" +dependencies = [ + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.16+zstd.1.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" +dependencies = [ + "cc", + "pkg-config", +] + +[[patch.unused]] +name = "alloy-primitives" +version = "1.0.0" +source = "git+https://github.com/ziren-patches/core.git?branch=patch-alloy-primitives-1.0.0#1367f9d00583135013224d764b51d63ffcdd9145" + +[[patch.unused]] +name = "alloy-primitives" +version = "1.1.0" +source = "git+https://github.com/ziren-patches/core.git?branch=patch-alloy-primitives-1.1.0#66386c419c93166f8471d3ace213d41ffcb13635" + +[[patch.unused]] +name = "alloy-primitives" +version = "1.1.2" +source = "git+https://github.com/ziren-patches/core.git?branch=patch-alloy-primitives-1.1.2#288a6856fc6e4160e85903f2af1c4af92ed4ccf9" + +[[patch.unused]] +name = "secp256k1" +version = "0.29.1" +source = "git+https://github.com/ziren-patches/rust-secp256k1?branch=patch-0.29.1#436fc77c1c72fd151f11004bec618d993b9a2f00" diff --git a/circuits/operator-wrapper-proof/guest/Cargo.toml b/circuits/operator-wrapper-proof/guest/Cargo.toml new file mode 100644 index 00000000..e24a035a --- /dev/null +++ b/circuits/operator-wrapper-proof/guest/Cargo.toml @@ -0,0 +1,33 @@ +[workspace] + +[package] +name = "guest" +version = "1.1.0" +edition = "2021" +publish = false + +[dependencies] +bitcoin-light-client-circuit = { path = "../../../crates/bitcoin-light-client-circuit" } +verifier = { path = "../../../crates/verifier" } + +# Ziren +zkm-primitives = { git = "https://github.com/ProjectZKM/Ziren" } +zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } + +# Statically turns off logging +tracing = { version = "0.1", features = ["max_level_trace"] } + +bitcoin = { version = "0.32.6", features = ["serde", "rand", "secp-recovery"] } +hex = "0.4.3" + +[patch.crates-io] +# Precompile patches +sha2 = { git = "https://github.com/ziren-patches/RustCrypto-hashes", branch = "patch-sha2-0.10.9", package = "sha2" } +bn = { git = "https://github.com/ziren-patches/bn", branch = "patch-0.6.0", package = "substrate-bn" } +k256 = { git = "https://github.com/ziren-patches/elliptic-curves", branch = "patch-k256-0.13.4" } +p256 = { git = "https://github.com/ziren-patches/elliptic-curves", branch = "patch-p256-0.13.2" } +alloy-primitives-v1-0-0 = { git = "https://github.com/ziren-patches/core.git", package = "alloy-primitives", branch = "patch-alloy-primitives-1.0.0" } +alloy-primitives-v1-1-0 = { git = "https://github.com/ziren-patches/core.git", package = "alloy-primitives", branch = "patch-alloy-primitives-1.1.0" } +alloy-primitives-v1-1-2 = { git = "https://github.com/ziren-patches/core.git", package = "alloy-primitives", branch = "patch-alloy-primitives-1.1.2" } +alloy-primitives-v1-4-1 = { git = "https://github.com/ziren-patches/core.git", package = "alloy-primitives", branch = "patch-alloy-primitives-1.4.1" } +secp256k1-v0-29-1 = { git = "https://github.com/ziren-patches/rust-secp256k1", package = "secp256k1", branch = "patch-0.29.1" } diff --git a/circuits/operator-wrapper-proof/guest/src/main.rs b/circuits/operator-wrapper-proof/guest/src/main.rs new file mode 100644 index 00000000..d4c5903a --- /dev/null +++ b/circuits/operator-wrapper-proof/guest/src/main.rs @@ -0,0 +1,39 @@ +#![no_main] +zkm_zkvm::entrypoint!(main); + +use bitcoin_light_client_circuit::{ + decode_operator_public_outputs, hash_operator_constant, zkm_vk_hash_to_raw, +}; +use verifier::verify_groth16_proof; + +pub fn main() { + let operator_proof: Vec = zkm_zkvm::io::read(); + let operator_public_values: Vec = zkm_zkvm::io::read(); + let operator_vk_hash: Vec = zkm_zkvm::io::read(); + let operator_zkm_version: String = zkm_zkvm::io::read(); + let graph_id: [u8; 16] = zkm_zkvm::io::read(); + let genesis_sequencer_commit_txid: [u8; 32] = zkm_zkvm::io::read(); + + verify_groth16_proof( + &operator_proof, + &operator_public_values, + &operator_vk_hash, + &operator_zkm_version, + ) + .expect("Failed to verify operator proof"); + + let expected_constant = hash_operator_constant(graph_id, genesis_sequencer_commit_txid); + let operator_vk_hash_raw = + zkm_vk_hash_to_raw(&operator_vk_hash).expect("Invalid operator vk hash"); + let operator_outputs = decode_operator_public_outputs( + &operator_public_values, + operator_vk_hash_raw, + ) + .expect("Invalid operator public values"); + assert_eq!(operator_outputs.constant, expected_constant); + assert_eq!(operator_outputs.operator_vk_hash, operator_vk_hash_raw); + + zkm_zkvm::io::commit(&operator_vk_hash_raw); + zkm_zkvm::io::commit(&graph_id); + zkm_zkvm::io::commit(&genesis_sequencer_commit_txid); +} diff --git a/circuits/operator-wrapper-proof/host/Cargo.toml b/circuits/operator-wrapper-proof/host/Cargo.toml new file mode 100644 index 00000000..c8eb5ded --- /dev/null +++ b/circuits/operator-wrapper-proof/host/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "operator-wrapper-proof" +version.workspace = true +edition.workspace = true + +[dependencies] +anyhow.workspace = true +bincode.workspace = true +bitcoin = { workspace = true, features = ["serde", "rand", "secp-recovery"] } +bitcoin-light-client-circuit = { workspace = true } +clap = { workspace = true, features = ["derive", "env"] } +dotenv.workspace = true +hex.workspace = true +proof-builder.workspace = true +serde.workspace = true +sha2.workspace = true +tokio = { workspace = true, features = ["full"] } +tracing = { workspace = true, features = ["max_level_trace"] } +tracing-subscriber.workspace = true +zkm-primitives.workspace = true +zkm-prover.workspace = true +zkm-sdk.workspace = true + +[build-dependencies] +zkm-build.workspace = true diff --git a/circuits/operator-wrapper-proof/host/build.rs b/circuits/operator-wrapper-proof/host/build.rs new file mode 100644 index 00000000..dcfa0c53 --- /dev/null +++ b/circuits/operator-wrapper-proof/host/build.rs @@ -0,0 +1,5 @@ +use zkm_build::build_program; + +fn main() { + build_program("../guest"); +} diff --git a/circuits/operator-wrapper-proof/host/src/lib.rs b/circuits/operator-wrapper-proof/host/src/lib.rs new file mode 100644 index 00000000..12e181a5 --- /dev/null +++ b/circuits/operator-wrapper-proof/host/src/lib.rs @@ -0,0 +1,302 @@ +//! Generate operator wrapper proof. +use anyhow::{Context, ensure}; +use bitcoin::{Txid, hashes::Hash}; +use bitcoin_light_client_circuit::{ + OperatorPublicOutputs, decode_operator_public_outputs, hash_operator_constant, + zkm_vk_hash_to_raw, +}; +use clap::Parser; +use proof_builder::{LongRunning, ProofBuilder, ProofRequest}; +use sha2::{Digest, Sha256}; +use std::fs; +use std::str::FromStr; +use std::sync::OnceLock; +use zkm_sdk::{ + HashableKey, Prover, ProverClient, ZKMProofKind, ZKMProofWithPublicValues, ZKMStdin, + include_elf, +}; + +static ELF_ID: OnceLock = OnceLock::new(); +const OPERATOR_WRAPPER: &[u8] = include_elf!("guest"); + +#[derive(Debug, Clone, Parser, serde::Deserialize, serde::Serialize)] +pub struct Args { + #[serde(default)] + #[arg(long, default_value_t = true)] + pub enable: bool, + + #[serde(default)] + #[clap(long, env, default_value = "")] + pub operator_input_proof: String, + + #[serde(default)] + #[clap(long, env, default_value = "")] + pub graph_id: String, + + #[serde(default)] + #[clap(long, env, default_value = "")] + pub genesis_sequencer_commit_txid: String, + + #[serde(default)] + #[clap(long, env, default_value = "wrapper-proof.bin")] + pub output: String, + + #[serde(default = "default_scan_interval")] + #[arg(long, default_value_t = 30)] + pub scan_interval: u64, +} + +fn default_scan_interval() -> u64 { + 30 +} + +impl Default for Args { + fn default() -> Self { + Self { + enable: false, + operator_input_proof: String::new(), + graph_id: String::new(), + genesis_sequencer_commit_txid: String::new(), + output: "wrapper-proof.bin".to_string(), + scan_interval: 30, + } + } +} + +impl LongRunning for Args { + fn rotate(&self) -> Self { + self.clone() + } +} + +#[derive(Clone, Debug)] +pub struct OperatorProofInputs { + pub proof_bytes: Vec, + pub public_values: Vec, + pub vk_hash: Vec, + pub vk_hash_raw: [u8; 32], + pub zkm_version: String, + pub outputs: OperatorPublicOutputs, +} + +pub struct OperatorWrapperProofBuilder { + client: ProverClient, + proving_key: zkm_sdk::ZKMProvingKey, + verifying_key: zkm_sdk::ZKMVerifyingKey, +} + +impl OperatorWrapperProofBuilder { + pub fn new() -> Self { + let client = ProverClient::new(); + let (proving_key, verifying_key) = client.setup(OPERATOR_WRAPPER); + Self { client, proving_key, verifying_key } + } +} + +pub fn parse_hex_array(value: &str) -> anyhow::Result<[u8; N]> { + let bytes = hex::decode(value.trim_start_matches("0x")) + .with_context(|| format!("failed to decode {N}-byte hex value"))?; + ensure!(bytes.len() == N, "expected {N} bytes, got {}", bytes.len()); + Ok(bytes.try_into().expect("length already checked")) +} + +pub fn read_operator_proof_inputs(path: &str) -> anyhow::Result { + let proof_file = + fs::read(path).with_context(|| format!("failed to read operator proof '{path}'"))?; + let public_values_path = format!("{path}.public_inputs.bin"); + let public_values = fs::read(&public_values_path) + .with_context(|| format!("failed to read operator public inputs '{public_values_path}'"))?; + let vk_hash_path = format!("{path}.vk_hash.bin"); + let vk_hash = fs::read(&vk_hash_path) + .with_context(|| format!("failed to read operator vk hash '{vk_hash_path}'"))?; + let zkm_version_path = format!("{path}.zkm_version.bin"); + let zkm_version = fs::read(&zkm_version_path) + .with_context(|| format!("failed to read operator zkm version '{zkm_version_path}'")) + .and_then(|raw| { + String::from_utf8(raw).with_context(|| format!("invalid UTF-8 in '{zkm_version_path}'")) + })?; + + let proof_bytes = + if let Ok(proof) = bincode::deserialize::(&proof_file) { + ensure!( + proof.public_values.to_vec() == public_values, + "operator proof public values do not match sidecar" + ); + ensure!( + proof.zkm_version == zkm_version, + "operator proof zkm_version does not match sidecar" + ); + proof.bytes() + } else { + proof_file + }; + + let raw_vk_hash = zkm_vk_hash_to_raw(&vk_hash).map_err(anyhow::Error::msg)?; + let outputs = + decode_operator_public_outputs(&public_values, raw_vk_hash).map_err(anyhow::Error::msg)?; + let vk_hash_raw = validate_operator_vk_hash_binding(&outputs, &vk_hash)?; + + Ok(OperatorProofInputs { + proof_bytes, + public_values, + vk_hash, + vk_hash_raw, + zkm_version, + outputs, + }) +} + +pub fn validate_operator_vk_hash_binding( + outputs: &OperatorPublicOutputs, + vk_hash: &[u8], +) -> anyhow::Result<[u8; 32]> { + let raw_vk_hash = zkm_vk_hash_to_raw(vk_hash).map_err(anyhow::Error::msg)?; + ensure!( + outputs.operator_vk_hash == raw_vk_hash, + "operator vk hash does not match authenticated operator public output" + ); + Ok(raw_vk_hash) +} + +impl ProofBuilder for OperatorWrapperProofBuilder { + fn client(&self) -> &zkm_sdk::ProverClient { + &self.client + } + + fn pk(&self) -> &zkm_sdk::ZKMProvingKey { + &self.proving_key + } + + fn vk(&self) -> &zkm_sdk::ZKMVerifyingKey { + &self.verifying_key + } + + fn name() -> String { + "operator-wrapper".to_string() + } + + #[tracing::instrument(level = "info", skip(self, ctx))] + fn build_proof( + &self, + ctx: &ProofRequest, + ) -> anyhow::Result<(Vec, ZKMProofWithPublicValues, u64, f32)> { + let ProofRequest::WrapperProofRequest { + operator_input_proof, + graph_id, + genesis_sequencer_commit_txid, + .. + } = ctx + else { + anyhow::bail!("Invalid proof request type"); + }; + + let operator_inputs = read_operator_proof_inputs(operator_input_proof)?; + let genesis_txid = Txid::from_str(genesis_sequencer_commit_txid) + .with_context(|| format!("invalid genesis txid '{genesis_sequencer_commit_txid}'"))?; + let genesis_txid_bytes = genesis_txid.to_byte_array(); + let expected_constant = hash_operator_constant(*graph_id, genesis_txid_bytes); + ensure!( + operator_inputs.outputs.constant == expected_constant, + "operator public constant does not match wrapper session" + ); + let (proof, cycles, proving_time) = tracing::info_span!("generate wrapper proof") + .in_scope(|| -> anyhow::Result<(ZKMProofWithPublicValues, u64, f32)> { + let mut stdin = ZKMStdin::new(); + stdin.write(&operator_inputs.proof_bytes); + stdin.write(&operator_inputs.public_values); + stdin.write(&operator_inputs.vk_hash); + stdin.write(&operator_inputs.zkm_version); + stdin.write(graph_id); + stdin.write(&genesis_txid_bytes); + + let elf_id = if ELF_ID.get().is_none() { + ELF_ID + .set(hex::encode(Sha256::digest(&self.proving_key.elf))) + .map_err(anyhow::Error::msg)?; + None + } else { + Some(ELF_ID.get().unwrap().clone()) + }; + tracing::info!("elf id: {:?}", elf_id); + + let proving_start = tokio::time::Instant::now(); + let (proof, cycles) = self.client.prove_with_cycles( + &self.proving_key, + &stdin, + ZKMProofKind::Groth16, + elf_id, + )?; + let proving_duration = proving_start.elapsed().as_secs_f32() * 1000.0; + Ok((proof, cycles, proving_duration)) + })?; + + Ok((vec![], proof, cycles, proving_time)) + } + + fn save_proof( + &self, + ctx: &ProofRequest, + _input: &[u8], + _cycles: u64, + proof: ZKMProofWithPublicValues, + ) -> anyhow::Result<(String, usize)> { + let ProofRequest::WrapperProofRequest { output, .. } = ctx else { + anyhow::bail!("invalid context"); + }; + let public_value_hex = hex::encode(proof.public_values.to_vec()); + let serialized_proof = bincode::serialize(&proof)?; + let proof_size = serialized_proof.len(); + let zkm_version = proof.zkm_version.clone(); + fs::write(format!("{output}.public_inputs.bin"), proof.public_values.to_vec())?; + fs::write(format!("{output}.vk_hash.bin"), self.verifying_key.bytes32())?; + fs::write(format!("{output}.zkm_version.bin"), zkm_version)?; + fs::write(output, serialized_proof)?; + Ok((public_value_hex, proof_size)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bitcoin_light_client_circuit::zkm_vk_hash_from_raw; + + #[derive(serde::Serialize, serde::Deserialize)] + struct LegacyOperatorPublicOutputs { + btc_best_block_hash: [u8; 32], + constant: [u8; 32], + included_watchtowers: [u8; 32], + } + + #[test] + fn decode_operator_public_outputs_accepts_legacy_public_values() { + let actual_vk_hash = [4u8; 32]; + let legacy_outputs = LegacyOperatorPublicOutputs { + btc_best_block_hash: [0u8; 32], + constant: [1u8; 32], + included_watchtowers: [2u8; 32], + }; + let public_values = bincode::serialize(&legacy_outputs).unwrap(); + + let outputs = decode_operator_public_outputs(&public_values, actual_vk_hash).unwrap(); + + assert_eq!(outputs.btc_best_block_hash, legacy_outputs.btc_best_block_hash); + assert_eq!(outputs.constant, legacy_outputs.constant); + assert_eq!(outputs.included_watchtowers, legacy_outputs.included_watchtowers); + assert_eq!(outputs.operator_vk_hash, actual_vk_hash); + } + + #[test] + fn validate_operator_vk_hash_binding_rejects_mismatched_sidecar() { + let outputs = OperatorPublicOutputs { + btc_best_block_hash: [0u8; 32], + constant: [1u8; 32], + included_watchtowers: [2u8; 32], + operator_vk_hash: [3u8; 32], + }; + let mismatched = zkm_vk_hash_from_raw(&[4u8; 32]); + + let err = validate_operator_vk_hash_binding(&outputs, &mismatched).unwrap_err(); + + assert!(err.to_string().contains("operator vk hash does not match")); + } +} diff --git a/circuits/operator-wrapper-proof/host/src/main.rs b/circuits/operator-wrapper-proof/host/src/main.rs new file mode 100644 index 00000000..df1bc2b5 --- /dev/null +++ b/circuits/operator-wrapper-proof/host/src/main.rs @@ -0,0 +1,24 @@ +//! Generate operator wrapper proof. +use clap::Parser; +use operator_wrapper_proof::{Args, OperatorWrapperProofBuilder, parse_hex_array}; +use proof_builder::{ProofBuilder, ProofRequest}; + +#[tokio::main] +async fn main() { + dotenv::dotenv().ok(); + let args = Args::parse(); + zkm_sdk::utils::setup_logger(); + + let builder = OperatorWrapperProofBuilder::new(); + let ctx = ProofRequest::WrapperProofRequest { + operator_proof_id: 0, + operator_input_proof: args.operator_input_proof.clone(), + graph_id: parse_hex_array::<16>(&args.graph_id).unwrap(), + genesis_sequencer_commit_txid: args.genesis_sequencer_commit_txid.clone(), + output: args.output.clone(), + }; + + let (input, proof, cycles, _) = builder.build_proof(&ctx).unwrap(); + tracing::info!("Operator wrapper proof cycles: {cycles}"); + builder.save_proof(&ctx, &input, cycles, proof).unwrap(); +} diff --git a/circuits/proof-builder/Cargo.toml b/circuits/proof-builder/Cargo.toml index 4f630873..cedd6451 100644 --- a/circuits/proof-builder/Cargo.toml +++ b/circuits/proof-builder/Cargo.toml @@ -11,14 +11,14 @@ serde.workspace = true # Ziren zkm-sdk.workspace = true -zkm-prover.workspace = true -zkm-verifier.workspace = true +#zkm-prover.workspace = true +#zkm-verifier.workspace = true strum = { workspace = true, features = ["derive"] } # header-chain.workspace = true commit-chain.workspace = true -bitcoin-light-client-circuit.workspace = true +#bitcoin-light-client-circuit.workspace = true state-chain.workspace = true bitcoin = { workspace = true } diff --git a/circuits/proof-builder/src/lib.rs b/circuits/proof-builder/src/lib.rs index 710d9098..9a992e18 100644 --- a/circuits/proof-builder/src/lib.rs +++ b/circuits/proof-builder/src/lib.rs @@ -68,6 +68,13 @@ pub enum ProofRequest { watchtower_challenge_txn_pubkeys: Vec, watchtower_challenge_txn_scripts: Vec, }, + WrapperProofRequest { + operator_proof_id: i64, + operator_input_proof: String, + graph_id: [u8; 16], + genesis_sequencer_commit_txid: String, + output: String, + }, } #[derive(Error, Debug, Clone)] @@ -114,7 +121,7 @@ pub struct OnDemandTask { pub state_chain_input_proof: String, pub watchtower_challenge_init_txid: Option, - pub watchtower_challenge_txids: Vec, + pub watchtower_challenge_txids: Vec>, pub included_watchtowers: Vec, pub watchtower_public_keys: Vec, pub graph_id: Option, @@ -133,6 +140,7 @@ pub enum ProofType { StateChain, Operator, Watchtower, + Wrapper, } const HEADER_CHAIN_NAME: &str = "header-chain"; @@ -140,6 +148,7 @@ const COMMIT_CHAIN_NAME: &str = "commit-chain"; const STATE_CHAIN_NAME: &str = "state-chain"; const OPERATOR_NAME: &str = "operator"; const WATCHTOWER_NAME: &str = "watchtower"; +const WRAPPER_NAME: &str = "wrapper"; impl ProofType { pub fn get_chain_name(&self) -> &'static str { match self { @@ -148,6 +157,7 @@ impl ProofType { ProofType::StateChain => STATE_CHAIN_NAME, ProofType::Operator => OPERATOR_NAME, ProofType::Watchtower => WATCHTOWER_NAME, + ProofType::Wrapper => WRAPPER_NAME, } } } @@ -182,6 +192,14 @@ pub struct OperatorProofDescRequest { pub graph_id: String, } +#[derive(Debug, Deserialize)] +pub struct WrapperProofDescRequest { + pub operator_proof_id: Option, + pub instance_id: Option, + pub graph_id: Option, + pub genesis_sequencer_commit_txid: Option, +} + #[derive(Debug, Serialize, Deserialize, Default)] pub struct ProofDescResponse { pub proof_desc: Option, @@ -194,7 +212,7 @@ pub struct OperatorProofRequest { pub graph_id: String, pub operator_committed_blockhash: String, pub execution_layer_block_number: i64, - pub watchtower_challenge_txids: Vec, + pub watchtower_challenge_txids: Vec>, pub included_watchtowers: Vec, pub watchtower_challenge_init_txid: String, pub watchtower_challenge_pubkeys: Vec, @@ -206,7 +224,6 @@ pub struct ProofData { pub vk: String, pub public_inputs: Vec, pub zkm_version: String, - pub proof_part_stark_vk: Vec, } impl ProofData { @@ -217,7 +234,8 @@ impl ProofData { | ProofType::CommitChain | ProofType::StateChain | ProofType::Watchtower - | ProofType::Operator => { + | ProofType::Operator + | ProofType::Wrapper => { proof_data.proof = fs::read(path).unwrap_or_default(); proof_data.public_inputs = fs::read(format!("{path}.public_inputs.bin")).unwrap_or_default(); @@ -228,8 +246,6 @@ impl ProofData { fs::read(format!("{path}.zkm_version.bin")).unwrap_or_default(), ) .unwrap_or_default(); - proof_data.proof_part_stark_vk = - fs::read(format!("{path}.proof_part_stark_vk.bin")).unwrap_or_default(); } } proof_data @@ -242,6 +258,35 @@ pub struct OperatorProofResponse { pub error: Option, } +#[derive(Debug, Serialize, Deserialize, Default)] +pub struct WrapperProofMetadata { + pub id: i64, + pub operator_proof_id: i64, + pub instance_id: String, + pub graph_id: String, + pub operator_path_to_proof: String, + pub path_to_proof: Option, + pub public_value_hex: Option, + pub operator_vk_hash: String, + pub genesis_sequencer_commit_txid: String, + pub operator_public_value_hex: Option, + pub proof_state: i64, + pub proof_size: i64, + pub cycles: i64, + pub total_time_to_proof: i64, + pub proving_time: i64, + pub zkm_version: String, + pub created_at: i64, + pub updated_at: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct WrapperProofResponse { + pub proof_data: Option, + pub metadata: Option, + pub error: Option, +} + #[derive(Debug, Serialize, Deserialize)] pub struct WatchtowerProofRequest { pub instance_id: String, @@ -298,27 +343,25 @@ mod tests { } #[test] - fn load_proof_data_reads_proof_part_stark_vk_sidecar() { + fn load_proof_data_omits_legacy_vk_sidecar() { let base = temp_proof_base(); let base_str = base.to_string_lossy().to_string(); fs::write(&base, [1u8, 2, 3]).unwrap(); fs::write(format!("{base_str}.public_inputs.bin"), [4u8, 5, 6]).unwrap(); fs::write(format!("{base_str}.vk_hash.bin"), b"vk-hash").unwrap(); fs::write(format!("{base_str}.zkm_version.bin"), b"v1.2.5").unwrap(); - fs::write(format!("{base_str}.proof_part_stark_vk.bin"), [9u8, 8, 7]).unwrap(); let proof_data = ProofData::load_proof_data(&base_str, ProofType::Watchtower); + let ProofData { proof, vk, public_inputs, zkm_version } = proof_data; - assert_eq!(proof_data.proof, vec![1u8, 2, 3]); - assert_eq!(proof_data.public_inputs, vec![4u8, 5, 6]); - assert_eq!(proof_data.vk, "vk-hash"); - assert_eq!(proof_data.zkm_version, "v1.2.5"); - assert_eq!(proof_data.proof_part_stark_vk, vec![9u8, 8, 7]); + assert_eq!(proof, vec![1u8, 2, 3]); + assert_eq!(public_inputs, vec![4u8, 5, 6]); + assert_eq!(vk, "vk-hash"); + assert_eq!(zkm_version, "v1.2.5"); let _ = fs::remove_file(&base); let _ = fs::remove_file(format!("{base_str}.public_inputs.bin")); let _ = fs::remove_file(format!("{base_str}.vk_hash.bin")); let _ = fs::remove_file(format!("{base_str}.zkm_version.bin")); - let _ = fs::remove_file(format!("{base_str}.proof_part_stark_vk.bin")); } } diff --git a/circuits/run-operator-wrapper-proof.sh b/circuits/run-operator-wrapper-proof.sh new file mode 100755 index 00000000..f5482db1 --- /dev/null +++ b/circuits/run-operator-wrapper-proof.sh @@ -0,0 +1,51 @@ +#!/bin/bash +set -euo pipefail + +source ~/.zkm-toolchain/env +set -a +source .env +set +a + +require_env() { + local name="$1" + if [ -z "${!name:-}" ]; then + echo "Missing required environment variable: $name" >&2 + exit 1 + fi +} + +require_file() { + local path="$1" + if [ ! -f "$path" ]; then + echo "Missing required file: $path" >&2 + exit 1 + fi +} + +operator_input_proof="${1:-}" +output="${2:-}" +if [ -z "$operator_input_proof" ] || [ -z "$output" ]; then + echo "Usage: bash run-operator-wrapper-proof.sh " >&2 + exit 1 +fi + +require_env "GRAPH_ID" +require_env "GENESIS_SEQUENCER_COMMIT_TXID" + +require_file "$operator_input_proof" +require_file "${operator_input_proof}.public_inputs.bin" +require_file "${operator_input_proof}.vk_hash.bin" +require_file "${operator_input_proof}.zkm_version.bin" + +mkdir -p "$(dirname "$output")" + +graph_id_hex="${GRAPH_ID//-/}" + +cargo build -r --bin operator-wrapper-proof --package operator-wrapper-proof +CMD="../target/release/operator-wrapper-proof" + +RUST_LOG=info "$CMD" \ + --operator-input-proof "$operator_input_proof" \ + --graph-id "$graph_id_hex" \ + --genesis-sequencer-commit-txid "$GENESIS_SEQUENCER_COMMIT_TXID" \ + --output "$output" diff --git a/circuits/state-chain-proof/guest/Cargo.lock b/circuits/state-chain-proof/guest/Cargo.lock index 612d3e5b..22665830 100644 --- a/circuits/state-chain-proof/guest/Cargo.lock +++ b/circuits/state-chain-proof/guest/Cargo.lock @@ -2225,7 +2225,6 @@ dependencies = [ "sha2 0.10.9", "state-chain", "tracing", - "zkm-verifier", "zkm-zkvm", ] @@ -2376,14 +2375,14 @@ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "header-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "bitcoin", "borsh", "crypto-bigint", "serde", "sha2 0.10.9", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -5114,7 +5113,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "state-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -5135,7 +5134,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -5704,6 +5703,13 @@ dependencies = [ "serde", ] +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/circuits/state-chain-proof/guest/Cargo.toml b/circuits/state-chain-proof/guest/Cargo.toml index 9e195a81..66d0f092 100644 --- a/circuits/state-chain-proof/guest/Cargo.toml +++ b/circuits/state-chain-proof/guest/Cargo.toml @@ -12,8 +12,6 @@ state-chain = { path = "../../../crates/state-chain" } # Ziren zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } -zkm-verifier = { git = "https://github.com/ProjectZKM/Ziren" } -#zkm-verifier = { path = "../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } # Statically turns off logging diff --git a/circuits/watchtower-proof/guest/Cargo.lock b/circuits/watchtower-proof/guest/Cargo.lock index af3cab9f..0dd35c97 100644 --- a/circuits/watchtower-proof/guest/Cargo.lock +++ b/circuits/watchtower-proof/guest/Cargo.lock @@ -1008,7 +1008,7 @@ checksum = "2dee39a0ee5b4095224a0cfc6bf4cc1baf0f9624b96b367e53b66d974e51d953" [[package]] name = "bitcoin-light-client-circuit" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", @@ -1022,11 +1022,11 @@ dependencies = [ "revm-database-interface", "serde", "serde_json", - "sha2 0.10.9", "state-chain", "tendermint", "tendermint-light-client-verifier", "tracing", + "verifier", "zkm-primitives", "zkm-verifier", "zkm-zkvm", @@ -1282,7 +1282,7 @@ checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" [[package]] name = "commit-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-primitives", "base64 0.21.7", @@ -1297,7 +1297,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -2276,7 +2276,6 @@ dependencies = [ "sha2 0.10.9", "state-chain", "tracing", - "zkm-verifier", "zkm-zkvm", ] @@ -2427,14 +2426,14 @@ checksum = "4f467dd6dccf739c208452f8014c75c18bb8301b050ad1cfb27153803edb0f51" [[package]] name = "header-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "bitcoin", "borsh", "crypto-bigint", "serde", "sha2 0.10.9", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -5165,7 +5164,7 @@ checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "state-chain" -version = "0.3.3" +version = "0.4.0" dependencies = [ "alloy-consensus", "alloy-primitives", @@ -5186,7 +5185,7 @@ dependencies = [ "tendermint", "tendermint-light-client-verifier", "tracing", - "zkm-verifier", + "verifier", "zkm-zkvm", ] @@ -5755,6 +5754,13 @@ dependencies = [ "serde", ] +[[package]] +name = "verifier" +version = "0.4.0" +dependencies = [ + "zkm-verifier", +] + [[package]] name = "version_check" version = "0.9.5" diff --git a/circuits/watchtower-proof/guest/Cargo.toml b/circuits/watchtower-proof/guest/Cargo.toml index fca4952e..d3a665fe 100644 --- a/circuits/watchtower-proof/guest/Cargo.toml +++ b/circuits/watchtower-proof/guest/Cargo.toml @@ -15,8 +15,6 @@ bitcoin-light-client-circuit = { path = "../../../crates/bitcoin-light-client-ci # Ziren zkm-zkvm = { git = "https://github.com/ProjectZKM/Ziren", features = ["verify"] } -zkm-verifier = { git = "https://github.com/ProjectZKM/Ziren" } -#zkm-verifier = { path = "../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } # Statically turns off logging diff --git a/circuits/watchtower-proof/guest/src/main.rs b/circuits/watchtower-proof/guest/src/main.rs index c522794c..2a5431b7 100644 --- a/circuits/watchtower-proof/guest/src/main.rs +++ b/circuits/watchtower-proof/guest/src/main.rs @@ -2,12 +2,8 @@ #![no_main] zkm_zkvm::entrypoint!(main); -use header_chain::{ - HeaderChainCircuitInput, - SPV, -}; -use bitcoin_light_client_circuit::WatchtowerAttestationInputs; use commit_chain::CommitChainCircuitInput; +use header_chain::{HeaderChainCircuitInput, SPV}; use state_chain::StateChainCircuitInput; pub fn main() { @@ -16,17 +12,16 @@ pub fn main() { let header_chain: HeaderChainCircuitInput = zkm_zkvm::io::read(); // private inputs let commit_chain: CommitChainCircuitInput = zkm_zkvm::io::read(); let state_chain: StateChainCircuitInput = zkm_zkvm::io::read(); - let attestation: WatchtowerAttestationInputs = zkm_zkvm::io::read(); let spv: SPV = zkm_zkvm::io::read(); - let output = bitcoin_light_client_circuit::watch_longest_chain( + let (total_work, btc_best_block_height) = bitcoin_light_client_circuit::watch_longest_chain( genesis_sequencer_commit_txid, latest_sequencer_commit_txid, header_chain, commit_chain, state_chain, - attestation, - spv + spv, ); - zkm_zkvm::io::commit(&output); + zkm_zkvm::io::commit(&total_work); + zkm_zkvm::io::commit(&btc_best_block_height); } diff --git a/circuits/watchtower-proof/host/src/lib.rs b/circuits/watchtower-proof/host/src/lib.rs index d01589a9..d7aa2ccc 100644 --- a/circuits/watchtower-proof/host/src/lib.rs +++ b/circuits/watchtower-proof/host/src/lib.rs @@ -1,24 +1,18 @@ #![feature(trim_prefix_suffix)] //! Generate watchtower proof use anyhow::Context; -use bincode::deserialize; use borsh::BorshDeserialize; use commit_chain::{CommitChainCircuitInput, CommitChainPrevProofType}; -use header_chain::{ - BlockHeaderCircuitOutput, CircuitBlockHeader, HeaderChainCircuitInput, HeaderChainPrevProofType, -}; +use header_chain::{CircuitBlockHeader, HeaderChainCircuitInput, HeaderChainPrevProofType}; use zkm_sdk::{ HashableKey, Prover, ProverClient, ZKMProofKind, ZKMProofWithPublicValues, ZKMStdin, include_elf, }; use bitcoin::{Block, Network, Transaction, Txid, hashes::Hash}; -use bitcoin_light_client_circuit::{ - WatchtowerAttestationInputs, build_spv, load_unique_part_stark_vk_witnesses, - part_stark_vk_attestation_dir, -}; +use bitcoin_light_client_circuit::build_spv; use sha2::{Digest, Sha256}; -use state_chain::{StateChainCircuitInput, StateChainCircuitOutput, StateChainPrevProofType}; +use state_chain::{StateChainCircuitInput, StateChainPrevProofType}; use std::str::FromStr; use std::sync::OnceLock; static ELF_ID: OnceLock = OnceLock::new(); @@ -26,10 +20,7 @@ static ELF_ID: OnceLock = OnceLock::new(); use proof_builder::{LongRunning, ProofBuilder, ProofRequest}; use clap::Parser; -use serde::de::DeserializeOwned; use std::fs; -use std::panic::{AssertUnwindSafe, catch_unwind}; -use zkm_verifier::Groth16Verifier; // The arguments for the cli. #[derive(Debug, Clone, Parser, serde::Deserialize, serde::Serialize)] @@ -104,33 +95,6 @@ impl WatchtowerProofBuilder { } } -fn load_proof_public_output(proof_path: &str) -> anyhow::Result { - let public_inputs = fs::read(format!("{proof_path}.public_inputs.bin")) - .context("Failed to read public inputs")?; - deserialize(&public_inputs).context("Failed to decode proof public outputs") -} - -fn load_part_stark_vk(zkm_version: &str) -> anyhow::Result> { - catch_unwind(AssertUnwindSafe(|| Groth16Verifier::get_part_stark_vk(zkm_version).to_vec())) - .map_err(|_| anyhow::anyhow!("Failed to load part_stark_vk for zkm_version {zkm_version}")) -} - -/// Collect both the version-derived verifier key and the recursive inner verifier key -/// for header/state subproofs so witness loading survives Ziren upgrades. -fn collect_requested_part_stark_vks( - header_chain_input: &HeaderChainCircuitInput, - header_chain_output: &BlockHeaderCircuitOutput, - state_chain_input: &StateChainCircuitInput, - state_chain_output: &StateChainCircuitOutput, -) -> anyhow::Result>> { - Ok(vec![ - load_part_stark_vk(&header_chain_input.zkm_version)?, - header_chain_output.part_stark_vk.clone(), - load_part_stark_vk(&state_chain_input.zkm_version)?, - state_chain_output.part_stark_vk.clone(), - ]) -} - impl ProofBuilder for WatchtowerProofBuilder { fn client(&self) -> &zkm_sdk::ProverClient { &self.client @@ -249,22 +213,6 @@ impl ProofBuilder for WatchtowerProofBuilder { blocks: vec![], } }; - let header_chain_output: BlockHeaderCircuitOutput = - load_proof_public_output(header_chain_input_proof)?; - let state_chain_output: StateChainCircuitOutput = - load_proof_public_output(state_chain_input_proof)?; - let attestation_dir = part_stark_vk_attestation_dir(); - let requested_part_stark_vks = collect_requested_part_stark_vks( - &header_chain_input, - &header_chain_output, - &state_chain_input, - &state_chain_output, - )?; - let (unique_witnesses, _) = - load_unique_part_stark_vk_witnesses(&attestation_dir, &requested_part_stark_vks) - .map_err(anyhow::Error::msg)?; - let attestation_inputs = WatchtowerAttestationInputs { unique_witnesses }; - // --- spv --- // let genesis_sequencer_commit_txid = Txid::from_str(&genesis_sequencer_commit_txid)?; let latest_sequencer_commit_txid = Txid::from_str(&latest_sequencer_commit_txid)?; @@ -303,7 +251,6 @@ impl ProofBuilder for WatchtowerProofBuilder { stdin.write(&header_chain_input); stdin.write(&commit_chain_input); stdin.write(&state_chain_input); - stdin.write(&attestation_inputs); stdin.write(&spv); let elf_id = if ELF_ID.get().is_none() { ELF_ID @@ -344,65 +291,9 @@ impl ProofBuilder for WatchtowerProofBuilder { let public_value_hex = hex::encode(proof.public_values.to_vec()); let proof_size = proof.bytes().len(); let zkm_version = proof.zkm_version.clone(); - let proof_part_stark_vk = load_part_stark_vk(&zkm_version)?; std::fs::write(&format!("{}.public_inputs.bin", output), proof.public_values.to_vec())?; std::fs::write(&format!("{}.vk_hash.bin", output), self.verifying_key.bytes32())?; std::fs::write(&format!("{}.zkm_version.bin", output), zkm_version)?; - std::fs::write(&format!("{}.proof_part_stark_vk.bin", output), proof_part_stark_vk)?; Ok((public_value_hex, proof_size)) } } - -#[cfg(test)] -mod tests { - use super::*; - - fn sample_header_input(zkm_version: &str) -> HeaderChainCircuitInput { - HeaderChainCircuitInput { - prev_proof: HeaderChainPrevProofType::GenesisBlock, - zkm_proof: vec![], - zkm_public_values: vec![], - zkm_vk_hash: vec![], - zkm_version: zkm_version.to_string(), - block_headers: vec![], - } - } - - fn sample_state_input(zkm_version: &str) -> StateChainCircuitInput { - StateChainCircuitInput { - prev_proof: StateChainPrevProofType::GenesisBlock, - zkm_proof: vec![], - zkm_public_values: vec![], - zkm_vk_hash: vec![], - zkm_version: zkm_version.to_string(), - blocks: vec![], - } - } - - fn sample_header_output(part_stark_vk: Vec) -> BlockHeaderCircuitOutput { - BlockHeaderCircuitOutput { chain_state: header_chain::ChainState::new(), part_stark_vk } - } - - fn sample_state_output(part_stark_vk: Vec) -> StateChainCircuitOutput { - StateChainCircuitOutput { - chain_state: state_chain::StateChainState::new(0, [0u8; 32], Vec::new()), - part_stark_vk, - } - } - - #[test] - fn test_collect_requested_part_stark_vks_includes_outer_and_inner_versions() { - let old_vk = load_part_stark_vk("v1.2.4").unwrap(); - let new_vk = load_part_stark_vk("v1.2.5").unwrap(); - - let requested = collect_requested_part_stark_vks( - &sample_header_input("v1.2.5"), - &sample_header_output(old_vk.clone()), - &sample_state_input("v1.2.5"), - &sample_state_output(old_vk.clone()), - ) - .unwrap(); - - assert_eq!(requested, vec![new_vk.clone(), old_vk.clone(), new_vk, old_vk]); - } -} diff --git a/crates/bitcoin-light-client-circuit/Cargo.toml b/crates/bitcoin-light-client-circuit/Cargo.toml index 172e5530..b651533d 100644 --- a/crates/bitcoin-light-client-circuit/Cargo.toml +++ b/crates/bitcoin-light-client-circuit/Cargo.toml @@ -16,6 +16,7 @@ state-chain = { workspace = true } # Ziren zkm-verifier = { workspace = true } +verifier = { workspace = true } zkm-zkvm = { workspace = true } zkm-primitives = { workspace = true } #zkm-verifier = { path = "../../../Ziren/crates/verifier" } @@ -25,10 +26,9 @@ zkm-primitives = { workspace = true } tracing = { workspace = true, features = ["max_level_trace"] } alloy-primitives = { workspace = true, features = ["sha3-keccak", "map-foldhash", "serde"] } -revm-database-interface = { workspace = true, features = ["serde"]} +revm-database-interface = { workspace = true, features = ["serde"] } #revm = { workspace = true, branch = "patch-31.0.2", features = ["serde", "bn"], default-features = false } -sha2 = "0.10.9" hex = { workspace = true } #prost = { version = "0.13", features = ["prost-derive"], default-features = false } @@ -40,13 +40,13 @@ serde_json = { workspace = true } tendermint = { git = "https://github.com/ProjectZKM/tendermint-rs", branch = "patch-0.40.3", default-features = false, features = ["secp256k1"] } tendermint-light-client-verifier = { git = "https://github.com/ProjectZKM/tendermint-rs", branch = "patch-0.40.3", default-features = false, features = [ - "rust-crypto", + "rust-crypto", ] } [dev-dependencies] rand = "0.8.5" -borsh = {version = "1.5.3", features = ["derive"] } +borsh = { version = "1.5.3", features = ["derive"] } blake3 = "1.6.1" #zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren" } diff --git a/crates/bitcoin-light-client-circuit/src/attestation.rs b/crates/bitcoin-light-client-circuit/src/attestation.rs index dd4b166b..e69de29b 100644 --- a/crates/bitcoin-light-client-circuit/src/attestation.rs +++ b/crates/bitcoin-light-client-circuit/src/attestation.rs @@ -1,1494 +0,0 @@ -use bitcoin::secp256k1::{Message, PublicKey, Secp256k1, SecretKey, ecdsa::Signature}; -use serde::{Deserialize, Serialize}; -use sha2::{Digest, Sha256}; -use std::collections::{BTreeMap, BTreeSet}; -use std::path::{Path, PathBuf}; - -pub const PART_STARK_VK_TREE_HEIGHT: usize = 6; -pub const PART_STARK_VK_TREE_LEAFS: usize = 1 << PART_STARK_VK_TREE_HEIGHT; -pub const PART_STARK_VK_ROOT_SIGNATURE_DOMAIN: &[u8] = b"bitvm2:part_stark_vk_root:v2"; -pub const PART_STARK_VK_PUBLISHER_SET_DOMAIN: &[u8] = b"bitvm2:publisher_set:v1"; -pub const PART_STARK_VK_ATTESTATION_DIR_ENV: &str = "PART_STARK_VK_ATTESTATION_DIR"; -pub const DEFAULT_PART_STARK_VK_ATTESTATION_DIR: &str = "data/psv-attestations"; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct PartStarkVkRootSignature { - pub signer_pubkey_index: usize, - pub signature: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct PartStarkVkAttestationBundle { - pub part_stark_vk: Vec, - pub leaf_index: usize, - pub merkle_path: Vec<[u8; 32]>, - pub root: [u8; 32], - pub threshold: u16, - pub publisher_set_id: [u8; 32], - pub signatures: Vec, -} - -pub type UniquePartStarkVkWitness = PartStarkVkAttestationBundle; - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct WatchtowerAttestationInputs { - pub unique_witnesses: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct OperatorAttestationInputs { - pub unique_witnesses: Vec, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct PartStarkVkTreeState { - pub tree_height: usize, - pub leaves: Vec>, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct LatestPartStarkVkAttestationManifest { - pub tree_height: usize, - pub ordered_versions: Vec, - pub root: [u8; 32], - pub threshold: Option, - pub publisher_set_id: Option<[u8; 32]>, - pub publisher_public_keys: Option>, - pub signatures: Vec, - pub part_stark_vk_leaf_hashes: BTreeMap, -} - -#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] -pub struct VersionedPartStarkVkMerkleProof { - pub version: String, - pub part_stark_vk: Vec, - pub leaf_index: usize, - pub merkle_path: Vec<[u8; 32]>, -} - -pub fn part_stark_vk_attestation_dir() -> PathBuf { - std::env::var(PART_STARK_VK_ATTESTATION_DIR_ENV) - .map(PathBuf::from) - .unwrap_or_else(|_| PathBuf::from(DEFAULT_PART_STARK_VK_ATTESTATION_DIR)) -} - -/// Hash the ordered publisher set together with the active threshold. -pub fn compute_publisher_set_id(publisher_public_keys: &[PublicKey], threshold: u16) -> [u8; 32] { - let mut bytes = Vec::with_capacity( - PART_STARK_VK_PUBLISHER_SET_DOMAIN.len() + 1 + 2 + 4 + publisher_public_keys.len() * 33, - ); - bytes.extend_from_slice(PART_STARK_VK_PUBLISHER_SET_DOMAIN); - bytes.push(0x00); - bytes.extend_from_slice(&threshold.to_le_bytes()); - bytes.extend_from_slice(&(publisher_public_keys.len() as u32).to_le_bytes()); - for pubkey in publisher_public_keys { - bytes.extend_from_slice(&pubkey.serialize()); - } - - Sha256::digest(bytes).into() -} - -pub fn part_stark_vk_leaf_hash(part_stark_vk: &[u8]) -> [u8; 32] { - let mut bytes = Vec::with_capacity(1 + 4 + part_stark_vk.len()); - bytes.push(0x00); - bytes.extend_from_slice(&(part_stark_vk.len() as u32).to_le_bytes()); - bytes.extend_from_slice(part_stark_vk); - - Sha256::digest(bytes).into() -} - -pub fn part_stark_vk_internal_hash(left: [u8; 32], right: [u8; 32]) -> [u8; 32] { - let mut bytes = Vec::with_capacity(1 + 32 + 32); - bytes.push(0x01); - bytes.extend_from_slice(&left); - bytes.extend_from_slice(&right); - - Sha256::digest(bytes).into() -} - -pub fn empty_part_stark_vk_leaf_hash() -> [u8; 32] { - part_stark_vk_leaf_hash(&[]) -} - -pub fn part_stark_vk_bundle_id(part_stark_vk: &[u8]) -> String { - hex::encode(part_stark_vk_leaf_hash(part_stark_vk)) -} - -pub fn part_stark_vk_tree_state_path(dir: &Path) -> PathBuf { - dir.join("tree_state.json") -} - -pub fn part_stark_vk_bundle_path(dir: &Path, part_stark_vk: &[u8]) -> PathBuf { - dir.join("bundles").join(format!("{}.json", part_stark_vk_bundle_id(part_stark_vk))) -} - -pub fn latest_part_stark_vk_attestation_manifest_path(dir: &Path) -> PathBuf { - dir.join("manifest.json") -} - -pub fn latest_part_stark_vk_attestation_proofs_dir(dir: &Path) -> PathBuf { - dir.join("proofs") -} - -pub fn latest_part_stark_vk_attestation_proof_path(dir: &Path, version: &str) -> PathBuf { - latest_part_stark_vk_attestation_proofs_dir(dir).join(format!("{version}.json")) -} - -fn build_part_stark_vk_leaf_layer( - part_stark_vks: &[Vec], - tree_height: usize, -) -> Result, String> { - let total_leafs = 1usize << tree_height; - if part_stark_vks.len() > total_leafs { - return Err(format!( - "too many part_stark_vk leafs: {}, max {}", - part_stark_vks.len(), - total_leafs - )); - } - - let mut nodes = vec![empty_part_stark_vk_leaf_hash(); total_leafs]; - for (index, part_stark_vk) in part_stark_vks.iter().enumerate() { - nodes[index] = part_stark_vk_leaf_hash(part_stark_vk); - } - Ok(nodes) -} - -pub fn build_part_stark_vk_merkle_root( - part_stark_vks: &[Vec], - tree_height: usize, -) -> Result<[u8; 32], String> { - let mut level = build_part_stark_vk_leaf_layer(part_stark_vks, tree_height)?; - while level.len() > 1 { - level = level - .chunks_exact(2) - .map(|pair| part_stark_vk_internal_hash(pair[0], pair[1])) - .collect(); - } - Ok(level[0]) -} - -pub fn build_part_stark_vk_merkle_path( - part_stark_vks: &[Vec], - tree_height: usize, - leaf_index: usize, -) -> Result, String> { - let total_leafs = 1usize << tree_height; - if leaf_index >= total_leafs { - return Err(format!("leaf index {} out of range {}", leaf_index, total_leafs)); - } - - let mut level = build_part_stark_vk_leaf_layer(part_stark_vks, tree_height)?; - let mut index = leaf_index; - let mut path = Vec::with_capacity(tree_height); - while level.len() > 1 { - path.push(level[index ^ 1]); - level = level - .chunks_exact(2) - .map(|pair| part_stark_vk_internal_hash(pair[0], pair[1])) - .collect(); - index /= 2; - } - Ok(path) -} - -pub fn verify_part_stark_vk_merkle_path( - part_stark_vk: &[u8], - leaf_index: usize, - merkle_path: &[[u8; 32]], - expected_root: [u8; 32], - tree_height: usize, -) -> Result<(), String> { - if merkle_path.len() != tree_height { - return Err(format!( - "invalid merkle path length: {}, expected {}", - merkle_path.len(), - tree_height - )); - } - - let total_leafs = 1usize << tree_height; - if leaf_index >= total_leafs { - return Err(format!("leaf index {} out of range {}", leaf_index, total_leafs)); - } - - let mut node = part_stark_vk_leaf_hash(part_stark_vk); - let mut index = leaf_index; - for sibling in merkle_path { - node = if index.is_multiple_of(2) { - part_stark_vk_internal_hash(node, *sibling) - } else { - part_stark_vk_internal_hash(*sibling, node) - }; - index /= 2; - } - - if node != expected_root { - return Err("part_stark_vk merkle root mismatch".to_string()); - } - Ok(()) -} - -/// Domain-separate the attestation root by tree height, threshold and publisher set identity. -pub fn build_attestation_message_digest( - tree_height: usize, - root: [u8; 32], - threshold: u16, - publisher_set_id: [u8; 32], -) -> [u8; 32] { - let mut bytes = - Vec::with_capacity(PART_STARK_VK_ROOT_SIGNATURE_DOMAIN.len() + 1 + 1 + 1 + 2 + 32 + 32); - bytes.extend_from_slice(PART_STARK_VK_ROOT_SIGNATURE_DOMAIN); - bytes.push(0x00); - bytes.push(tree_height as u8); - bytes.push(0x00); - bytes.extend_from_slice(&threshold.to_le_bytes()); - bytes.extend_from_slice(&publisher_set_id); - bytes.extend_from_slice(&root); - - Sha256::digest(bytes).into() -} - -fn verify_part_stark_vk_root_signatures( - signatures: &[PartStarkVkRootSignature], - publisher_public_keys: &[PublicKey], - threshold: u16, - tree_height: usize, - root: [u8; 32], - publisher_set_id: [u8; 32], -) -> Result<(), String> { - if publisher_public_keys.is_empty() { - return Err("publisher_public_keys is empty".to_string()); - } - let required = usize::from(threshold); - if required == 0 { - return Err("threshold must be greater than 0".to_string()); - } - if required > publisher_public_keys.len() { - return Err(format!( - "threshold {} exceeds publisher_public_keys length {}", - required, - publisher_public_keys.len() - )); - } - let message = Message::from_digest(build_attestation_message_digest( - tree_height, - root, - threshold, - publisher_set_id, - )); - let secp = Secp256k1::verification_only(); - let mut seen = std::collections::BTreeSet::new(); - let mut valid = 0usize; - - for root_signature in signatures { - if root_signature.signer_pubkey_index >= publisher_public_keys.len() { - return Err(format!( - "signer_pubkey_index {} out of range {}", - root_signature.signer_pubkey_index, - publisher_public_keys.len() - )); - } - if !seen.insert(root_signature.signer_pubkey_index) { - continue; - } - - let signature = Signature::from_compact(&root_signature.signature) - .map_err(|err| format!("invalid signature encoding: {err}"))?; - let mut normalized = signature; - normalized.normalize_s(); - if normalized != signature { - return Err("signature is not low-s normalized".to_string()); - } - - secp.verify_ecdsa( - &message, - &signature, - &publisher_public_keys[root_signature.signer_pubkey_index], - ) - .map_err(|err| format!("invalid publisher root signature: {err}"))?; - valid += 1; - } - - if valid < required { - return Err(format!( - "not enough valid publisher signatures: got {}, required {}", - valid, required - )); - } - Ok(()) -} - -/// Verify that a `part_stark_vk` belongs to the fixed-height history tree and that the root is -/// approved by the current commit-chain publisher set. -pub fn verify_part_stark_vk_attestation( - bundle: &PartStarkVkAttestationBundle, - publisher_public_keys: &[PublicKey], - threshold: u16, - tree_height: usize, -) -> Result<(), String> { - if bundle.threshold != threshold { - return Err(format!( - "attestation threshold mismatch: bundle {}, expected {}", - bundle.threshold, threshold - )); - } - let expected_publisher_set_id = compute_publisher_set_id(publisher_public_keys, threshold); - if bundle.publisher_set_id != expected_publisher_set_id { - return Err("attestation publisher_set_id mismatch".to_string()); - } - verify_part_stark_vk_merkle_path( - &bundle.part_stark_vk, - bundle.leaf_index, - &bundle.merkle_path, - bundle.root, - tree_height, - )?; - verify_part_stark_vk_root_signatures( - &bundle.signatures, - publisher_public_keys, - threshold, - tree_height, - bundle.root, - bundle.publisher_set_id, - )?; - - Ok(()) -} - -/// Verify each unique witness once so later lookups can reuse the verified payload. -pub fn verify_unique_part_stark_vk_witnesses( - unique_witnesses: &[UniquePartStarkVkWitness], - publisher_public_keys: &[PublicKey], - threshold: u16, - tree_height: usize, -) -> Result<(), String> { - for witness in unique_witnesses { - verify_part_stark_vk_attestation(witness, publisher_public_keys, threshold, tree_height)?; - } - Ok(()) -} - -/// Check whether a target `part_stark_vk` is present in the verified witness set. -pub fn assert_part_stark_vk_in_verified_witnesses( - unique_witnesses: &[UniquePartStarkVkWitness], - expected_part_stark_vk: &[u8], -) -> Result<(), String> { - if unique_witnesses.iter().any(|witness| witness.part_stark_vk == expected_part_stark_vk) { - return Ok(()); - } - Err("part_stark_vk not found in verified witnesses".to_string()) -} - -pub fn load_part_stark_vk_tree_state(dir: &Path) -> Result { - let path = part_stark_vk_tree_state_path(dir); - let bytes = std::fs::read(&path) - .map_err(|err| format!("failed to read tree state '{}': {err}", path.display()))?; - serde_json::from_slice(&bytes) - .map_err(|err| format!("failed to decode tree state '{}': {err}", path.display())) -} - -fn remove_path_if_exists(path: &Path) -> Result<(), String> { - if !path.exists() { - return Ok(()); - } - if path.is_dir() { - std::fs::remove_dir_all(path) - .map_err(|err| format!("failed to remove dir '{}': {err}", path.display()))?; - } else { - std::fs::remove_file(path) - .map_err(|err| format!("failed to remove file '{}': {err}", path.display()))?; - } - Ok(()) -} - -/// Build the latest snapshot manifest and per-version merkle proofs from ordered versions. -fn build_latest_part_stark_vk_attestation_snapshot( - ordered_versions: &[String], - part_stark_vks: &[Vec], - threshold: Option, - publisher_set_id: Option<[u8; 32]>, - publisher_public_keys: Option>, - signatures: Vec, -) -> Result<(LatestPartStarkVkAttestationManifest, Vec), String> { - if ordered_versions.is_empty() { - return Err("ordered_versions is empty".to_string()); - } - if ordered_versions.len() != part_stark_vks.len() { - return Err(format!( - "ordered_versions length {} does not match part_stark_vks length {}", - ordered_versions.len(), - part_stark_vks.len() - )); - } - - let root = build_part_stark_vk_merkle_root(part_stark_vks, PART_STARK_VK_TREE_HEIGHT)?; - let mut part_stark_vk_leaf_hashes = BTreeMap::new(); - let mut proofs = Vec::with_capacity(ordered_versions.len()); - for (leaf_index, (version, part_stark_vk)) in - ordered_versions.iter().zip(part_stark_vks).enumerate() - { - let leaf_hash = part_stark_vk_bundle_id(part_stark_vk); - if part_stark_vk_leaf_hashes.insert(version.clone(), leaf_hash).is_some() { - return Err(format!("duplicate ordered version '{version}'")); - } - proofs.push(VersionedPartStarkVkMerkleProof { - version: version.clone(), - part_stark_vk: part_stark_vk.clone(), - leaf_index, - merkle_path: build_part_stark_vk_merkle_path( - part_stark_vks, - PART_STARK_VK_TREE_HEIGHT, - leaf_index, - )?, - }); - } - - Ok(( - LatestPartStarkVkAttestationManifest { - tree_height: PART_STARK_VK_TREE_HEIGHT, - ordered_versions: ordered_versions.to_vec(), - root, - threshold, - publisher_set_id, - publisher_public_keys, - signatures, - part_stark_vk_leaf_hashes, - }, - proofs, - )) -} - -/// Rewrite the attestation directory so it only contains the current snapshot files. -fn write_latest_part_stark_vk_attestation_snapshot( - dir: &Path, - manifest: &LatestPartStarkVkAttestationManifest, - proofs: &[VersionedPartStarkVkMerkleProof], -) -> Result<(), String> { - std::fs::create_dir_all(dir) - .map_err(|err| format!("failed to create attestation dir '{}': {err}", dir.display()))?; - remove_path_if_exists(&latest_part_stark_vk_attestation_proofs_dir(dir))?; - remove_path_if_exists(&dir.join("bundles"))?; - remove_path_if_exists(&part_stark_vk_tree_state_path(dir))?; - remove_path_if_exists(&latest_part_stark_vk_attestation_manifest_path(dir))?; - - let proofs_dir = latest_part_stark_vk_attestation_proofs_dir(dir); - std::fs::create_dir_all(&proofs_dir).map_err(|err| { - format!("failed to create attestation proofs dir '{}': {err}", proofs_dir.display()) - })?; - - for proof in proofs { - let path = latest_part_stark_vk_attestation_proof_path(dir, &proof.version); - let bytes = serde_json::to_vec_pretty(proof) - .map_err(|err| format!("failed to encode proof '{}': {err}", path.display()))?; - std::fs::write(&path, bytes) - .map_err(|err| format!("failed to write proof '{}': {err}", path.display()))?; - } - - let manifest_path = latest_part_stark_vk_attestation_manifest_path(dir); - let manifest_bytes = serde_json::to_vec_pretty(manifest).map_err(|err| { - format!("failed to encode latest attestation manifest '{}': {err}", manifest_path.display()) - })?; - std::fs::write(&manifest_path, manifest_bytes).map_err(|err| { - format!("failed to write latest attestation manifest '{}': {err}", manifest_path.display()) - })?; - Ok(()) -} - -/// Persist a full latest snapshot, optionally including publisher metadata and signatures. -pub fn save_latest_part_stark_vk_attestation_snapshot( - dir: &Path, - ordered_versions: &[String], - part_stark_vks: &[Vec], - threshold: Option, - publisher_set_id: Option<[u8; 32]>, - publisher_public_keys: Option>, - signatures: Vec, -) -> Result<(), String> { - let publisher_public_keys = - publisher_public_keys.map(|keys| keys.into_iter().map(|key| key.to_string()).collect()); - let (manifest, proofs) = build_latest_part_stark_vk_attestation_snapshot( - ordered_versions, - part_stark_vks, - threshold, - publisher_set_id, - publisher_public_keys, - signatures, - )?; - write_latest_part_stark_vk_attestation_snapshot(dir, &manifest, &proofs) -} - -/// Load the latest manifest that describes the current attestation snapshot. -pub fn load_latest_part_stark_vk_attestation_manifest( - dir: &Path, -) -> Result { - let path = latest_part_stark_vk_attestation_manifest_path(dir); - let bytes = std::fs::read(&path).map_err(|err| { - format!("failed to read latest attestation manifest '{}': {err}", path.display()) - })?; - serde_json::from_slice(&bytes).map_err(|err| { - format!("failed to decode latest attestation manifest '{}': {err}", path.display()) - }) -} - -/// Load every version proof referenced by the latest manifest in manifest order. -pub fn load_latest_part_stark_vk_attestation_proofs( - dir: &Path, - ordered_versions: &[String], -) -> Result, String> { - let mut proofs = Vec::with_capacity(ordered_versions.len()); - for version in ordered_versions { - let path = latest_part_stark_vk_attestation_proof_path(dir, version); - let bytes = std::fs::read(&path).map_err(|err| { - format!("failed to read attestation proof '{}': {err}", path.display()) - })?; - let proof: VersionedPartStarkVkMerkleProof = - serde_json::from_slice(&bytes).map_err(|err| { - format!("failed to decode attestation proof '{}': {err}", path.display()) - })?; - proofs.push(proof); - } - Ok(proofs) -} - -/// Merge one signer into the latest snapshot, resetting signatures when the signing context changes. -pub fn sign_latest_part_stark_vk_snapshot( - dir: &Path, - ordered_versions: &[String], - part_stark_vks: &[Vec], - publisher_public_keys: &[PublicKey], - threshold: u16, - signer_pubkey_index: usize, - secret_key: &SecretKey, -) -> Result { - if publisher_public_keys.is_empty() { - return Err("publisher_public_keys is empty".to_string()); - } - let required = usize::from(threshold); - if required == 0 { - return Err("threshold must be greater than 0".to_string()); - } - if required > publisher_public_keys.len() { - return Err(format!( - "threshold {} exceeds publisher_public_keys length {}", - required, - publisher_public_keys.len() - )); - } - if signer_pubkey_index >= publisher_public_keys.len() { - return Err(format!( - "signer_pubkey_index {} out of range {}", - signer_pubkey_index, - publisher_public_keys.len() - )); - } - - let signer_public_key = PublicKey::from_secret_key(&Secp256k1::new(), secret_key); - if publisher_public_keys[signer_pubkey_index] != signer_public_key { - return Err(format!( - "publisher_secret_key does not match publisher public key at index {}", - signer_pubkey_index - )); - } - - let root = build_part_stark_vk_merkle_root(part_stark_vks, PART_STARK_VK_TREE_HEIGHT)?; - let publisher_set_id = compute_publisher_set_id(publisher_public_keys, threshold); - let mut signatures = match load_latest_part_stark_vk_attestation_manifest(dir) { - Ok(existing) - if existing.ordered_versions == ordered_versions - && existing.root == root - && existing.publisher_set_id == Some(publisher_set_id) => - { - existing.signatures - } - _ => Vec::new(), - }; - - let signature = PartStarkVkRootSignature { - signer_pubkey_index, - signature: sign_part_stark_vk_root( - secret_key, - PART_STARK_VK_TREE_HEIGHT, - root, - threshold, - publisher_set_id, - ), - }; - - if let Some(existing_signature) = - signatures.iter_mut().find(|existing| existing.signer_pubkey_index == signer_pubkey_index) - { - *existing_signature = signature; - } else { - signatures.push(signature); - } - signatures.sort_by_key(|existing| existing.signer_pubkey_index); - - save_latest_part_stark_vk_attestation_snapshot( - dir, - ordered_versions, - part_stark_vks, - Some(threshold), - Some(publisher_set_id), - Some(publisher_public_keys.to_vec()), - signatures, - )?; - load_latest_part_stark_vk_attestation_manifest(dir) -} - -pub fn save_part_stark_vk_tree_state( - dir: &Path, - state: &PartStarkVkTreeState, -) -> Result<(), String> { - std::fs::create_dir_all(dir) - .map_err(|err| format!("failed to create attestation dir '{}': {err}", dir.display()))?; - let path = part_stark_vk_tree_state_path(dir); - let bytes = serde_json::to_vec_pretty(state) - .map_err(|err| format!("failed to encode tree state '{}': {err}", path.display()))?; - std::fs::write(&path, bytes) - .map_err(|err| format!("failed to write tree state '{}': {err}", path.display())) -} - -pub fn load_part_stark_vk_attestation_bundle( - dir: &Path, - part_stark_vk: &[u8], -) -> Result { - let path = part_stark_vk_bundle_path(dir, part_stark_vk); - let bytes = std::fs::read(&path) - .map_err(|err| format!("failed to read attestation bundle '{}': {err}", path.display()))?; - serde_json::from_slice(&bytes) - .map_err(|err| format!("failed to decode attestation bundle '{}': {err}", path.display())) -} - -pub fn save_part_stark_vk_attestation_bundle( - dir: &Path, - bundle: &PartStarkVkAttestationBundle, -) -> Result<(), String> { - let bundle_dir = dir.join("bundles"); - std::fs::create_dir_all(&bundle_dir).map_err(|err| { - format!("failed to create attestation bundle dir '{}': {err}", bundle_dir.display()) - })?; - let path = part_stark_vk_bundle_path(dir, &bundle.part_stark_vk); - let bytes = serde_json::to_vec_pretty(bundle).map_err(|err| { - format!("failed to encode attestation bundle '{}': {err}", path.display()) - })?; - std::fs::write(&path, bytes) - .map_err(|err| format!("failed to write attestation bundle '{}': {err}", path.display())) -} - -pub fn sign_part_stark_vk_root( - secret_key: &SecretKey, - tree_height: usize, - root: [u8; 32], - threshold: u16, - publisher_set_id: [u8; 32], -) -> Vec { - let secp = Secp256k1::new(); - let message = Message::from_digest(build_attestation_message_digest( - tree_height, - root, - threshold, - publisher_set_id, - )); - let signature = secp.sign_ecdsa(&message, secret_key); - signature.serialize_compact().to_vec() -} - -pub fn build_part_stark_vk_root_signatures( - signer_secret_keys: &[(usize, &SecretKey)], - tree_height: usize, - root: [u8; 32], - threshold: u16, - publisher_set_id: [u8; 32], -) -> Result, String> { - if signer_secret_keys.is_empty() { - return Err("publisher_secret_keys is empty".to_string()); - } - - let mut seen = BTreeSet::new(); - let mut signatures = Vec::with_capacity(signer_secret_keys.len()); - for (signer_pubkey_index, secret_key) in signer_secret_keys { - if !seen.insert(*signer_pubkey_index) { - return Err(format!( - "duplicate signer_pubkey_index {} in publisher_secret_keys", - signer_pubkey_index - )); - } - signatures.push(PartStarkVkRootSignature { - signer_pubkey_index: *signer_pubkey_index, - signature: sign_part_stark_vk_root( - secret_key, - tree_height, - root, - threshold, - publisher_set_id, - ), - }); - } - - Ok(signatures) -} - -pub fn build_part_stark_vk_attestation_bundle( - leaves: &[Vec], - tree_height: usize, - leaf_index: usize, - threshold: u16, - publisher_set_id: [u8; 32], - signatures: Vec, -) -> Result { - let part_stark_vk = leaves - .get(leaf_index) - .ok_or_else(|| format!("leaf index {} out of range {}", leaf_index, leaves.len()))? - .clone(); - Ok(PartStarkVkAttestationBundle { - part_stark_vk, - leaf_index, - merkle_path: build_part_stark_vk_merkle_path(leaves, tree_height, leaf_index)?, - root: build_part_stark_vk_merkle_root(leaves, tree_height)?, - threshold, - publisher_set_id, - signatures, - }) -} - -pub fn append_part_stark_vk_and_sign( - dir: &Path, - part_stark_vk: Vec, - publisher_public_keys: &[PublicKey], - threshold: u16, - publisher_secret_keys: &[SecretKey], -) -> Result { - let indexed_secret_keys = - publisher_secret_keys.iter().enumerate().collect::>(); - append_part_stark_vk_and_sign_with_signers( - dir, - part_stark_vk, - publisher_public_keys, - threshold, - &indexed_secret_keys, - ) -} - -pub fn append_part_stark_vk_and_sign_with_signers( - dir: &Path, - part_stark_vk: Vec, - publisher_public_keys: &[PublicKey], - threshold: u16, - publisher_secret_keys: &[(usize, &SecretKey)], -) -> Result { - let mut state = if part_stark_vk_tree_state_path(dir).exists() { - load_part_stark_vk_tree_state(dir)? - } else { - PartStarkVkTreeState { tree_height: PART_STARK_VK_TREE_HEIGHT, leaves: vec![] } - }; - if state.tree_height != PART_STARK_VK_TREE_HEIGHT { - return Err(format!( - "unsupported tree height {}, expected {}", - state.tree_height, PART_STARK_VK_TREE_HEIGHT - )); - } - if state.leaves.len() >= (1usize << state.tree_height) { - return Err("part_stark_vk history tree is full".to_string()); - } - if state.leaves.iter().any(|leaf| leaf == &part_stark_vk) { - return Err("part_stark_vk already exists in tree".to_string()); - } - state.leaves.push(part_stark_vk.clone()); - let bundle = resign_part_stark_vk_tree( - dir, - &state, - publisher_public_keys, - threshold, - publisher_secret_keys, - state.leaves.len() - 1, - )?; - save_part_stark_vk_tree_state(dir, &state)?; - Ok(bundle) -} - -pub fn resign_current_part_stark_vk_root( - dir: &Path, - publisher_public_keys: &[PublicKey], - threshold: u16, - publisher_secret_keys: &[SecretKey], -) -> Result<(), String> { - let indexed_secret_keys = - publisher_secret_keys.iter().enumerate().collect::>(); - resign_current_part_stark_vk_root_with_signers( - dir, - publisher_public_keys, - threshold, - &indexed_secret_keys, - ) -} - -pub fn resign_current_part_stark_vk_root_with_signers( - dir: &Path, - publisher_public_keys: &[PublicKey], - threshold: u16, - publisher_secret_keys: &[(usize, &SecretKey)], -) -> Result<(), String> { - let state = load_part_stark_vk_tree_state(dir)?; - if state.leaves.is_empty() { - return Err("part_stark_vk tree is empty".to_string()); - } - for leaf_index in 0..state.leaves.len() { - let _ = resign_part_stark_vk_tree( - dir, - &state, - publisher_public_keys, - threshold, - publisher_secret_keys, - leaf_index, - )?; - } - Ok(()) -} - -fn resign_part_stark_vk_tree( - dir: &Path, - state: &PartStarkVkTreeState, - publisher_public_keys: &[PublicKey], - threshold: u16, - publisher_secret_keys: &[(usize, &SecretKey)], - leaf_index: usize, -) -> Result { - let root = build_part_stark_vk_merkle_root(&state.leaves, state.tree_height)?; - let publisher_set_id = compute_publisher_set_id(publisher_public_keys, threshold); - let signatures = build_part_stark_vk_root_signatures( - publisher_secret_keys, - state.tree_height, - root, - threshold, - publisher_set_id, - )?; - let bundle = build_part_stark_vk_attestation_bundle( - &state.leaves, - state.tree_height, - leaf_index, - threshold, - publisher_set_id, - signatures, - )?; - save_part_stark_vk_attestation_bundle(dir, &bundle)?; - Ok(bundle) -} - -pub fn load_unique_part_stark_vk_witnesses( - dir: &Path, - part_stark_vks: &[Vec], -) -> Result<(Vec, Vec), String> { - if !latest_part_stark_vk_attestation_manifest_path(dir).exists() { - return Err(format!( - "missing latest attestation manifest '{}'", - latest_part_stark_vk_attestation_manifest_path(dir).display() - )); - } - - load_unique_part_stark_vk_witnesses_from_latest_snapshot(dir, part_stark_vks) -} - -fn load_unique_part_stark_vk_witnesses_from_latest_snapshot( - dir: &Path, - part_stark_vks: &[Vec], -) -> Result<(Vec, Vec), String> { - let manifest = load_latest_part_stark_vk_attestation_manifest(dir)?; - let threshold = manifest - .threshold - .ok_or_else(|| "latest attestation manifest is missing threshold".to_string())?; - let publisher_set_id = manifest - .publisher_set_id - .ok_or_else(|| "latest attestation manifest is missing publisher_set_id".to_string())?; - let proofs = load_latest_part_stark_vk_attestation_proofs(dir, &manifest.ordered_versions)?; - let mut available_witnesses = BTreeMap::, UniquePartStarkVkWitness>::new(); - - for proof in proofs { - let expected_leaf_hash = - manifest.part_stark_vk_leaf_hashes.get(&proof.version).ok_or_else(|| { - format!("missing part_stark_vk leaf hash for version '{}'", proof.version) - })?; - let actual_leaf_hash = part_stark_vk_bundle_id(&proof.part_stark_vk); - if *expected_leaf_hash != actual_leaf_hash { - return Err(format!( - "part_stark_vk leaf hash mismatch for version '{}': expected {}, got {}", - proof.version, expected_leaf_hash, actual_leaf_hash - )); - } - - verify_part_stark_vk_merkle_path( - &proof.part_stark_vk, - proof.leaf_index, - &proof.merkle_path, - manifest.root, - manifest.tree_height, - )?; - available_witnesses.entry(proof.part_stark_vk.clone()).or_insert_with(|| { - PartStarkVkAttestationBundle { - part_stark_vk: proof.part_stark_vk, - leaf_index: proof.leaf_index, - merkle_path: proof.merkle_path, - root: manifest.root, - threshold, - publisher_set_id, - signatures: manifest.signatures.clone(), - } - }); - } - - let mut unique_witnesses = Vec::new(); - let mut witness_indexes = Vec::with_capacity(part_stark_vks.len()); - let mut seen = BTreeMap::, usize>::new(); - - for part_stark_vk in part_stark_vks { - if let Some(index) = seen.get(part_stark_vk) { - witness_indexes.push(*index); - continue; - } - let bundle = available_witnesses - .get(part_stark_vk) - .cloned() - .ok_or_else(|| "part_stark_vk not found in latest snapshot".to_string())?; - let index = unique_witnesses.len(); - unique_witnesses.push(bundle); - seen.insert(part_stark_vk.clone(), index); - witness_indexes.push(index); - } - - Ok((unique_witnesses, witness_indexes)) -} - -#[cfg(test)] -mod tests { - use bitcoin::secp256k1::{Message, Secp256k1, SecretKey, ecdsa::Signature}; - use std::time::{SystemTime, UNIX_EPOCH}; - - use super::{ - PART_STARK_VK_TREE_HEIGHT, PartStarkVkAttestationBundle, PartStarkVkRootSignature, - assert_part_stark_vk_in_verified_witnesses, build_attestation_message_digest, - build_part_stark_vk_merkle_path, build_part_stark_vk_merkle_root, - build_part_stark_vk_root_signatures, compute_publisher_set_id, - load_latest_part_stark_vk_attestation_manifest, load_unique_part_stark_vk_witnesses, - part_stark_vk_leaf_hash, save_latest_part_stark_vk_attestation_snapshot, - save_part_stark_vk_attestation_bundle, sign_latest_part_stark_vk_snapshot, - verify_part_stark_vk_attestation, verify_unique_part_stark_vk_witnesses, - }; - - fn sample_part_stark_vk() -> Vec { - hex::decode("2000000000000000d157a916a6350249c6e4efd850dcdac3abf5489d36de2d1aa233c9253522871000000000") - .unwrap() - } - - fn sample_part_stark_vk_alt() -> Vec { - b"alternate-part-stark-vk".to_vec() - } - - fn sample_publishers() -> (Vec, Vec) { - let secp = Secp256k1::new(); - let secret_keys = vec![ - SecretKey::from_slice(&[1u8; 32]).unwrap(), - SecretKey::from_slice(&[2u8; 32]).unwrap(), - SecretKey::from_slice(&[3u8; 32]).unwrap(), - SecretKey::from_slice(&[4u8; 32]).unwrap(), - ]; - let publisher_public_keys = secret_keys - .iter() - .map(|sk| bitcoin::secp256k1::PublicKey::from_secret_key(&secp, sk)) - .collect::>(); - (secret_keys, publisher_public_keys) - } - - fn sample_attestation_bundle( - leaves: &[Vec], - leaf_index: usize, - threshold: u16, - publisher_public_keys: &[bitcoin::secp256k1::PublicKey], - signer_secret_keys: &[SecretKey], - ) -> PartStarkVkAttestationBundle { - let root = build_part_stark_vk_merkle_root(leaves, 6).unwrap(); - let publisher_set_id = compute_publisher_set_id(publisher_public_keys, threshold); - let indexed_secret_keys = - signer_secret_keys.iter().enumerate().collect::>(); - let signatures = build_part_stark_vk_root_signatures( - &indexed_secret_keys[..usize::from(threshold)], - 6, - root, - threshold, - publisher_set_id, - ) - .unwrap(); - - PartStarkVkAttestationBundle { - part_stark_vk: leaves[leaf_index].clone(), - leaf_index, - merkle_path: build_part_stark_vk_merkle_path(leaves, 6, leaf_index).unwrap(), - root, - threshold, - publisher_set_id, - signatures, - } - } - - fn unique_test_dir(prefix: &str) -> std::path::PathBuf { - let nanos = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_nanos(); - std::env::temp_dir().join(format!("bitvm2-{prefix}-{nanos}")) - } - - #[test] - fn test_part_stark_vk_leaf_hash_matches_spec() { - assert_eq!( - hex::encode(part_stark_vk_leaf_hash(&[])), - "8855508aade16ec573d21e6a485dfd0a7624085c1a14b5ecdd6485de0c6839a4" - ); - } - - #[test] - fn test_build_part_stark_vk_merkle_root_matches_spec() { - let leaves = vec![sample_part_stark_vk(), b"hello".to_vec()]; - - assert_eq!( - hex::encode(build_part_stark_vk_merkle_root(&leaves, 6).unwrap()), - "8a091f11cab951f0c1228a891c902475b84709e568885a45658840e1b88599d4" - ); - } - - #[test] - fn test_build_attestation_message_digest_matches_spec() { - let secp = Secp256k1::new(); - let secret_keys = [ - SecretKey::from_slice(&[1u8; 32]).unwrap(), - SecretKey::from_slice(&[2u8; 32]).unwrap(), - SecretKey::from_slice(&[3u8; 32]).unwrap(), - SecretKey::from_slice(&[4u8; 32]).unwrap(), - ]; - let publisher_public_keys = secret_keys - .iter() - .map(|sk| bitcoin::secp256k1::PublicKey::from_secret_key(&secp, sk)) - .collect::>(); - let root = hex::decode("8a091f11cab951f0c1228a891c902475b84709e568885a45658840e1b88599d4") - .unwrap() - .try_into() - .unwrap(); - let threshold = 3u16; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, threshold); - let mut reversed_public_keys = publisher_public_keys.clone(); - reversed_public_keys.reverse(); - - assert_ne!( - build_attestation_message_digest(6, root, threshold, publisher_set_id), - build_attestation_message_digest(6, root, threshold - 1, publisher_set_id) - ); - assert_ne!(publisher_set_id, compute_publisher_set_id(&reversed_public_keys, threshold)); - } - - #[test] - fn test_verify_part_stark_vk_attestation_accepts_quorum_and_ignores_duplicate_signers() { - let secp = Secp256k1::new(); - let secret_keys = [ - SecretKey::from_slice(&[1u8; 32]).unwrap(), - SecretKey::from_slice(&[2u8; 32]).unwrap(), - SecretKey::from_slice(&[3u8; 32]).unwrap(), - SecretKey::from_slice(&[4u8; 32]).unwrap(), - ]; - let publisher_public_keys = secret_keys - .iter() - .map(|sk| bitcoin::secp256k1::PublicKey::from_secret_key(&secp, sk)) - .collect::>(); - - let part_stark_vk = sample_part_stark_vk(); - let leaves = vec![part_stark_vk.clone()]; - let root = build_part_stark_vk_merkle_root(&leaves, 6).unwrap(); - let threshold = 3u16; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, threshold); - let digest = build_attestation_message_digest(6, root, threshold, publisher_set_id); - let message = Message::from_digest(digest); - - let sig0 = secp.sign_ecdsa(&message, &secret_keys[0]); - let sig1 = secp.sign_ecdsa(&message, &secret_keys[1]); - let sig2 = secp.sign_ecdsa(&message, &secret_keys[2]); - - let bundle = PartStarkVkAttestationBundle { - part_stark_vk, - leaf_index: 0, - merkle_path: build_part_stark_vk_merkle_path(&leaves, 6, 0).unwrap(), - root, - threshold, - publisher_set_id, - signatures: vec![ - PartStarkVkRootSignature { - signer_pubkey_index: 0, - signature: sig0.serialize_compact().to_vec(), - }, - PartStarkVkRootSignature { - signer_pubkey_index: 1, - signature: sig1.serialize_compact().to_vec(), - }, - PartStarkVkRootSignature { - signer_pubkey_index: 1, - signature: sig1.serialize_compact().to_vec(), - }, - PartStarkVkRootSignature { - signer_pubkey_index: 2, - signature: sig2.serialize_compact().to_vec(), - }, - ], - }; - - assert!( - verify_part_stark_vk_attestation(&bundle, &publisher_public_keys, threshold, 6).is_ok() - ); - } - - #[test] - fn test_verify_part_stark_vk_attestation_rejects_non_member_signer() { - let secp = Secp256k1::new(); - let secret_keys = [ - SecretKey::from_slice(&[1u8; 32]).unwrap(), - SecretKey::from_slice(&[2u8; 32]).unwrap(), - SecretKey::from_slice(&[3u8; 32]).unwrap(), - ]; - let publisher_public_keys = secret_keys - .iter() - .map(|sk| bitcoin::secp256k1::PublicKey::from_secret_key(&secp, sk)) - .collect::>(); - - let outsider = SecretKey::from_slice(&[9u8; 32]).unwrap(); - let part_stark_vk = sample_part_stark_vk(); - let leaves = vec![part_stark_vk.clone()]; - let root = build_part_stark_vk_merkle_root(&leaves, 6).unwrap(); - let threshold = 2u16; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, threshold); - let digest = build_attestation_message_digest(6, root, threshold, publisher_set_id); - let message = Message::from_digest(digest); - let outsider_sig: Signature = secp.sign_ecdsa(&message, &outsider); - - let bundle = PartStarkVkAttestationBundle { - part_stark_vk, - leaf_index: 0, - merkle_path: build_part_stark_vk_merkle_path(&leaves, 6, 0).unwrap(), - root, - threshold, - publisher_set_id, - signatures: vec![PartStarkVkRootSignature { - signer_pubkey_index: 0, - signature: outsider_sig.serialize_compact().to_vec(), - }], - }; - - assert!( - verify_part_stark_vk_attestation(&bundle, &publisher_public_keys, threshold, 6) - .is_err() - ); - } - - #[test] - fn test_build_part_stark_vk_root_signatures_supports_sparse_signer_indexes() { - let secp = Secp256k1::new(); - let secret_keys = [ - SecretKey::from_slice(&[1u8; 32]).unwrap(), - SecretKey::from_slice(&[2u8; 32]).unwrap(), - SecretKey::from_slice(&[3u8; 32]).unwrap(), - SecretKey::from_slice(&[4u8; 32]).unwrap(), - SecretKey::from_slice(&[5u8; 32]).unwrap(), - ]; - let publisher_public_keys = secret_keys - .iter() - .map(|sk| bitcoin::secp256k1::PublicKey::from_secret_key(&secp, sk)) - .collect::>(); - - let leaves = vec![sample_part_stark_vk()]; - let root = build_part_stark_vk_merkle_root(&leaves, 6).unwrap(); - let threshold = 4u16; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, threshold); - let signatures = build_part_stark_vk_root_signatures( - &[ - (0, &secret_keys[0]), - (2, &secret_keys[2]), - (3, &secret_keys[3]), - (4, &secret_keys[4]), - ], - 6, - root, - threshold, - publisher_set_id, - ) - .unwrap(); - - let bundle = PartStarkVkAttestationBundle { - part_stark_vk: leaves[0].clone(), - leaf_index: 0, - merkle_path: build_part_stark_vk_merkle_path(&leaves, 6, 0).unwrap(), - root, - threshold, - publisher_set_id, - signatures, - }; - - assert!( - verify_part_stark_vk_attestation(&bundle, &publisher_public_keys, threshold, 6).is_ok() - ); - } - - #[test] - fn test_verify_part_stark_vk_attestation_rejects_threshold_mismatch() { - let secp = Secp256k1::new(); - let secret_keys = [ - SecretKey::from_slice(&[1u8; 32]).unwrap(), - SecretKey::from_slice(&[2u8; 32]).unwrap(), - SecretKey::from_slice(&[3u8; 32]).unwrap(), - SecretKey::from_slice(&[4u8; 32]).unwrap(), - ]; - let publisher_public_keys = secret_keys - .iter() - .map(|sk| bitcoin::secp256k1::PublicKey::from_secret_key(&secp, sk)) - .collect::>(); - let leaves = vec![sample_part_stark_vk()]; - let root = build_part_stark_vk_merkle_root(&leaves, 6).unwrap(); - let signed_threshold = 3u16; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, signed_threshold); - let signatures = build_part_stark_vk_root_signatures( - &[(0, &secret_keys[0]), (1, &secret_keys[1]), (2, &secret_keys[2])], - 6, - root, - signed_threshold, - publisher_set_id, - ) - .unwrap(); - - let bundle = PartStarkVkAttestationBundle { - part_stark_vk: leaves[0].clone(), - leaf_index: 0, - merkle_path: build_part_stark_vk_merkle_path(&leaves, 6, 0).unwrap(), - root, - threshold: signed_threshold, - publisher_set_id, - signatures, - }; - - assert!( - verify_part_stark_vk_attestation( - &bundle, - &publisher_public_keys, - signed_threshold - 1, - 6 - ) - .is_err() - ); - } - - #[test] - fn test_verify_unique_part_stark_vk_witnesses_accepts_multiple_bundles() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let unique_witnesses = vec![ - sample_attestation_bundle(&leaves, 0, 3, &publisher_public_keys, &secret_keys), - sample_attestation_bundle(&leaves, 1, 3, &publisher_public_keys, &secret_keys), - ]; - - assert!( - verify_unique_part_stark_vk_witnesses(&unique_witnesses, &publisher_public_keys, 3, 6) - .is_ok() - ); - } - - #[test] - fn test_assert_part_stark_vk_in_verified_witnesses_checks_membership() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let unique_witnesses = vec![ - sample_attestation_bundle(&leaves, 0, 3, &publisher_public_keys, &secret_keys), - sample_attestation_bundle(&leaves, 1, 3, &publisher_public_keys, &secret_keys), - ]; - - verify_unique_part_stark_vk_witnesses(&unique_witnesses, &publisher_public_keys, 3, 6) - .unwrap(); - assert!(assert_part_stark_vk_in_verified_witnesses(&unique_witnesses, &leaves[1]).is_ok()); - assert!( - assert_part_stark_vk_in_verified_witnesses(&unique_witnesses, b"missing-part-stark-vk") - .is_err() - ); - } - - #[test] - fn test_load_unique_part_stark_vk_witnesses_reuses_duplicate_indexes_from_latest_snapshot() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let dir = unique_test_dir("attestation-duplicate-indexes"); - let ordered_versions = vec!["v1.2.4".to_string(), "v1.2.5".to_string()]; - let threshold = 3; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, threshold); - let signatures = build_part_stark_vk_root_signatures( - &[(0, &secret_keys[0]), (1, &secret_keys[1]), (2, &secret_keys[2])], - PART_STARK_VK_TREE_HEIGHT, - build_part_stark_vk_merkle_root(&leaves, PART_STARK_VK_TREE_HEIGHT).unwrap(), - threshold, - publisher_set_id, - ) - .unwrap(); - - save_latest_part_stark_vk_attestation_snapshot( - &dir, - &ordered_versions, - &leaves, - Some(threshold), - Some(publisher_set_id), - Some(publisher_public_keys.clone()), - signatures, - ) - .unwrap(); - - let requested = vec![leaves[0].clone(), leaves[1].clone(), leaves[0].clone()]; - let (unique_witnesses, witness_indexes) = - load_unique_part_stark_vk_witnesses(&dir, &requested).unwrap(); - - assert_eq!(unique_witnesses.len(), 2); - assert_eq!(witness_indexes, vec![0, 1, 0]); - - std::fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn test_load_unique_part_stark_vk_witnesses_requires_latest_manifest() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let dir = unique_test_dir("attestation-requires-latest-manifest"); - std::fs::create_dir_all(dir.join("bundles")).unwrap(); - - let first_bundle = - sample_attestation_bundle(&leaves, 0, 3, &publisher_public_keys, &secret_keys); - save_part_stark_vk_attestation_bundle(&dir, &first_bundle).unwrap(); - - let err = load_unique_part_stark_vk_witnesses(&dir, &[leaves[0].clone()]).unwrap_err(); - assert!(err.contains("missing latest attestation manifest")); - - std::fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn test_save_and_load_latest_snapshot_reuses_duplicate_indexes() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let ordered_versions = vec!["v1.2.4".to_string(), "v1.2.5".to_string()]; - let dir = unique_test_dir("latest-attestation-snapshot"); - let threshold = 3; - let publisher_set_id = compute_publisher_set_id(&publisher_public_keys, threshold); - let signatures = build_part_stark_vk_root_signatures( - &[(0, &secret_keys[0]), (1, &secret_keys[1]), (2, &secret_keys[2])], - PART_STARK_VK_TREE_HEIGHT, - build_part_stark_vk_merkle_root(&leaves, PART_STARK_VK_TREE_HEIGHT).unwrap(), - threshold, - publisher_set_id, - ) - .unwrap(); - - save_latest_part_stark_vk_attestation_snapshot( - &dir, - &ordered_versions, - &leaves, - Some(threshold), - Some(publisher_set_id), - Some(publisher_public_keys.clone()), - signatures, - ) - .unwrap(); - - let requested = vec![leaves[0].clone(), leaves[1].clone(), leaves[0].clone()]; - let (unique_witnesses, witness_indexes) = - load_unique_part_stark_vk_witnesses(&dir, &requested).unwrap(); - - assert_eq!(unique_witnesses.len(), 2); - assert_eq!(witness_indexes, vec![0, 1, 0]); - assert_eq!(unique_witnesses[0].part_stark_vk, leaves[0]); - assert_eq!(unique_witnesses[1].part_stark_vk, leaves[1]); - - std::fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn test_sign_latest_snapshot_preserves_existing_signatures_for_same_identity() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let ordered_versions = vec!["v1.2.4".to_string(), "v1.2.5".to_string()]; - let dir = unique_test_dir("latest-attestation-signatures"); - - sign_latest_part_stark_vk_snapshot( - &dir, - &ordered_versions, - &leaves, - &publisher_public_keys, - 3, - 0, - &secret_keys[0], - ) - .unwrap(); - let first_manifest = load_latest_part_stark_vk_attestation_manifest(&dir).unwrap(); - assert_eq!(first_manifest.signatures.len(), 1); - - sign_latest_part_stark_vk_snapshot( - &dir, - &ordered_versions, - &leaves, - &publisher_public_keys, - 3, - 2, - &secret_keys[2], - ) - .unwrap(); - let second_manifest = load_latest_part_stark_vk_attestation_manifest(&dir).unwrap(); - assert_eq!(second_manifest.signatures.len(), 2); - - std::fs::remove_dir_all(dir).unwrap(); - } - - #[test] - fn test_sign_latest_snapshot_clears_stale_signatures_when_publisher_set_changes() { - let (secret_keys, publisher_public_keys) = sample_publishers(); - let leaves = vec![sample_part_stark_vk(), sample_part_stark_vk_alt()]; - let ordered_versions = vec!["v1.2.4".to_string(), "v1.2.5".to_string()]; - let dir = unique_test_dir("latest-attestation-publisher-reset"); - - sign_latest_part_stark_vk_snapshot( - &dir, - &ordered_versions, - &leaves, - &publisher_public_keys, - 3, - 0, - &secret_keys[0], - ) - .unwrap(); - sign_latest_part_stark_vk_snapshot( - &dir, - &ordered_versions, - &leaves, - &publisher_public_keys, - 3, - 1, - &secret_keys[1], - ) - .unwrap(); - - let reordered_publisher_public_keys = vec![ - publisher_public_keys[1], - publisher_public_keys[0], - publisher_public_keys[2], - publisher_public_keys[3], - ]; - sign_latest_part_stark_vk_snapshot( - &dir, - &ordered_versions, - &leaves, - &reordered_publisher_public_keys, - 3, - 0, - &secret_keys[1], - ) - .unwrap(); - - let manifest = load_latest_part_stark_vk_attestation_manifest(&dir).unwrap(); - assert_eq!(manifest.signatures.len(), 1); - assert_eq!(manifest.signatures[0].signer_pubkey_index, 0); - - std::fs::remove_dir_all(dir).unwrap(); - } -} diff --git a/crates/bitcoin-light-client-circuit/src/lib.rs b/crates/bitcoin-light-client-circuit/src/lib.rs index bd3560d3..922500e4 100644 --- a/crates/bitcoin-light-client-circuit/src/lib.rs +++ b/crates/bitcoin-light-client-circuit/src/lib.rs @@ -1,38 +1,36 @@ -mod attestation; mod signature; mod utils; use alloy_primitives::U32; -pub use attestation::*; pub use signature::*; use state_chain::verify_sequencer_commit; pub use utils::*; use alloy_primitives::U256; use bitcoin::Block; -use bitcoin::Transaction; use bitcoin::hashes::{Hash, HashEngine, sha256}; -use commit_chain::sequencer_hash; use commit_chain::{ - CommitChainCircuitInput, CommitChainCircuitOutput, extract_data_from_commitment_outputs, + CommitChainCircuitInput, CommitChainPrevProofType, decode_commit_chain_circuit_output, + extract_data_from_commitment_outputs, parse_commit_chain_commitment, sequencer_hash, }; use header_chain::{ BitcoinMerkleTree, CircuitBlockHeader, CircuitTransaction, HeaderChainCircuitInput, HeaderChainPrevProofType, MMRHost, SPV, verify_merkle_proof, }; use state_chain::{StateChainCircuitInput, StateChainPrevProofType}; -use std::panic::{AssertUnwindSafe, catch_unwind}; use zkm_primitives::io::ZKMPublicValues; -use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES}; -use bitcoin::{ScriptBuf, TxOut, Txid, secp256k1::PublicKey}; +use bitcoin::{ + ScriptBuf, Transaction, TxOut, Txid, + secp256k1::{PublicKey, XOnlyPublicKey}, +}; pub use guest_executor::io::EthClientExecutorInput; use serde::{Deserialize, Serialize}; +use verifier::verify_groth16_proof; pub const GRAPH_ID_SIZE: usize = 16; pub const PROOF_SIZE: usize = 260; pub const PUBLIC_INPUTS_SIZE: usize = 36; -pub const WATCHTOWER_COMMITMENT_PUBLIC_INPUTS_LEN_SIZE: usize = 4; -pub const WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE: usize = 4; +pub const ZKM_VERSION_LEN_SIZE: usize = 4; pub const VK_HASH_SIZE: usize = 66; pub const TOTAL_WORK_SIZE: usize = 32; @@ -49,6 +47,32 @@ pub struct OperatorPublicOutputs { pub btc_best_block_hash: [u8; 32], pub constant: [u8; 32], pub included_watchtowers: [u8; 32], + pub operator_vk_hash: [u8; 32], +} + +#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)] +struct LegacyOperatorPublicOutputs { + btc_best_block_hash: [u8; 32], + constant: [u8; 32], + included_watchtowers: [u8; 32], +} + +pub fn decode_operator_public_outputs( + public_values: &[u8], + actual_operator_vk_hash: [u8; 32], +) -> Result { + if let Ok(outputs) = bincode::deserialize::(public_values) { + return Ok(outputs); + } + + let legacy_outputs = bincode::deserialize::(public_values) + .map_err(|err| format!("failed to decode operator public values: {err}"))?; + Ok(OperatorPublicOutputs { + btc_best_block_hash: legacy_outputs.btc_best_block_hash, + constant: legacy_outputs.constant, + included_watchtowers: legacy_outputs.included_watchtowers, + operator_vk_hash: actual_operator_vk_hash, + }) } pub fn watch_longest_chain( @@ -57,21 +81,26 @@ pub fn watch_longest_chain( header_chain: HeaderChainCircuitInput, commit_chain: CommitChainCircuitInput, state_chain: StateChainCircuitInput, - attestation: WatchtowerAttestationInputs, spv: SPV, -) -> WatchtowerPublicOutputs { +) -> ([u8; 32], u32) { println!("commit header, size: {}", commit_chain.commits.len()); - let commit_chain_output = - verify_commit_chain_output(&commit_chain).expect("Failed to verify commit chain proof"); - let (publisher_public_keys, threshold) = - commit_chain_attestation_authority(&commit_chain_output); - verify_unique_part_stark_vk_witnesses( - &attestation.unique_witnesses, - publisher_public_keys, - threshold, - PART_STARK_VK_TREE_HEIGHT, + // verify latest_sequencer_commit is valid: + // * Check both latest_sequencer_commit_txid and genesis_sequencer_commit_txid are in all_sequencer_commit_txids (which is a private input) + // * Check latest_sequencer_commit_txid is derived from genesis_sequencer_commit_txid + // verify the commit chain proof + verify_groth16_proof( + &commit_chain.zkm_proof, + &commit_chain.zkm_public_values, + &commit_chain.zkm_vk_hash, + &commit_chain.zkm_version, ) - .expect("Failed to verify unique part_stark_vk attestations"); + .expect("Failed to verify commit chain proof"); + + let prev_output = decode_commit_chain_circuit_output(&commit_chain.zkm_public_values); + let prev_proof = CommitChainPrevProofType::PrevProof(prev_output); + let CommitChainPrevProofType::PrevProof(commit_chain_output) = &prev_proof else { + panic!("Only PrevProof is supported in watch_longest_chain"); + }; assert_eq!( commit_chain_output.chain_state.commit_txn.compute_txid(), @@ -81,16 +110,11 @@ pub fn watch_longest_chain( println!("header chain: applying: {}", header_chain.block_headers.len()); // verify header_chain is valid - let header_part_stark_vk = attested_part_stark_vk_for_zkm_version( - &attestation.unique_witnesses, - &header_chain.zkm_version, - ) - .expect("Failed to resolve attested header-chain part_stark_vk"); - verify_proof_with_part_stark_vk( + verify_groth16_proof( &header_chain.zkm_proof, &header_chain.zkm_public_values, &header_chain.zkm_vk_hash, - &header_part_stark_vk, + &header_chain.zkm_version, ) .expect("Failed to verify header chain proof"); @@ -103,16 +127,11 @@ pub fn watch_longest_chain( println!("SPV"); assert!(spv.verify(&btc_header_chain_output.chain_state.block_hashes_mmr)); - let state_part_stark_vk = attested_part_stark_vk_for_zkm_version( - &attestation.unique_witnesses, - &state_chain.zkm_version, - ) - .expect("Failed to resolve attested state-chain part_stark_vk"); - verify_proof_with_part_stark_vk( + verify_groth16_proof( &state_chain.zkm_proof, &state_chain.zkm_public_values, &state_chain.zkm_vk_hash, - &state_part_stark_vk, + &state_chain.zkm_version, ) .expect("Failed to verify state chain proof"); let prev_output = ZKMPublicValues::from(&state_chain.zkm_public_values).read(); @@ -120,17 +139,6 @@ pub fn watch_longest_chain( let StateChainPrevProofType::PrevProof(state_chain_output) = &prev_proof else { panic!("Only PrevProof is supported in watch_longest_chain"); }; - assert_part_stark_vk_in_verified_witnesses( - &attestation.unique_witnesses, - &btc_header_chain_output.part_stark_vk, - ) - .expect("Failed to match header-chain part_stark_vk in verified witnesses"); - assert_part_stark_vk_in_verified_witnesses( - &attestation.unique_witnesses, - &state_chain_output.part_stark_vk, - ) - .expect("Failed to match state-chain part_stark_vk in verified witnesses"); - // check the signature. let cosmos_block_bytes = &state_chain_output.chain_state.latest_cosmos_block; let cosmos_block: LightBlock = @@ -145,19 +153,20 @@ pub fn watch_longest_chain( // check commit chain's genesis block let commitment = commit_chain::extract_op_return_data(&commit_chain_output.chain_state.commit_txn.output); + let commitment = parse_commit_chain_commitment(&commitment); if let tendermint::Hash::Sha256(x) = expected_seqeuencer_set_hash { - assert_eq!(commitment[0..32], x); + assert_eq!(commitment.sequencer_set_hash, x); } else { panic!("Invalid commitment: inconsistent sequencer set hash"); }; - assert_eq!(commitment[32..64], state_chain_output.chain_state.genesis_evm_block_hash[..]); + assert_eq!( + commitment.genesis_evm_block_hash, + state_chain_output.chain_state.genesis_evm_block_hash + ); println!("commit public inputs"); // commit public inputs - WatchtowerPublicOutputs { - total_work: btc_header_chain_output.chain_state.total_work, - consensus_block_height: commit_chain_output.chain_state.block_height.to_le_bytes(), - } + (btc_header_chain_output.chain_state.total_work, commit_chain_output.chain_state.block_height) } pub fn u256_to_le_bits(u: U256) -> [bool; 256] { @@ -178,6 +187,27 @@ pub fn le_bits_to_u256(bits: &[bool]) -> U256 { u } +pub fn verify_fixed_watchtower_pubkey( + fixed_watchtower_xonly_public_keys: &[[u8; 32]], + index: usize, + pubkey: &PublicKey, +) -> Result<(), String> { + // The fixed list is order-sensitive: index must match graph watchtower_pubkeys/node_index. + let Some(expected) = fixed_watchtower_xonly_public_keys.get(index) else { + return Err(format!("watchtower index {index} exceeds fixed watchtower list")); + }; + let xonly: XOnlyPublicKey = (*pubkey).into(); + let actual = xonly.serialize(); + if &actual != expected { + return Err(format!( + "watchtower[{index}] pubkey mismatch: actual={}, expected={}", + hex::encode(actual), + hex::encode(expected) + )); + } + Ok(()) +} + // calculate operator public input: https://github.com/ProjectZKM/Ziren/blob/main/crates/sdk/src/utils.rs#L42 #[allow(clippy::too_many_arguments)] pub fn propose_longest_chain( @@ -189,28 +219,29 @@ pub fn propose_longest_chain( watchtower_challenge_txn_pubkey: Vec, watchtower_challenge_txn_scripts: Vec, watchtower_challenge_txn_prev_outs: Vec, + fixed_watchtower_xonly_public_keys: &[[u8; 32]], operator_header_chain: HeaderChainCircuitInput, commit_chain: CommitChainCircuitInput, state_chain: StateChainCircuitInput, - attestation: OperatorAttestationInputs, spv_ss_commit: SPV, operator_committed_blockhash: [u8; 32], -) -> OperatorPublicOutputs { + actual_operator_vk_hash: [u8; 32], +) -> ([u8; 32], [u8; 32], [u8; 32], [u8; 32]) { // verify operator_latest_sequencer_commit_txid is valid, and on operator head chain // * Check operator_latest_sequencer_commit_txid is derived from genesis_sequencer_commit_txid - let commit_chain_output = - verify_commit_chain_output(&commit_chain).expect("Failed to verify commit chain proof"); - let (publisher_public_keys, threshold) = - commit_chain_attestation_authority(&commit_chain_output); - verify_unique_part_stark_vk_witnesses( - &attestation.unique_witnesses, - publisher_public_keys, - threshold, - PART_STARK_VK_TREE_HEIGHT, + verify_groth16_proof( + &commit_chain.zkm_proof, + &commit_chain.zkm_public_values, + &commit_chain.zkm_vk_hash, + &commit_chain.zkm_version, ) - .expect("Failed to verify unique part_stark_vk attestations"); - + .expect("Failed to verify commit chain proof"); + let prev_output = decode_commit_chain_circuit_output(&commit_chain.zkm_public_values); + let prev_proof = CommitChainPrevProofType::PrevProof(prev_output); + let CommitChainPrevProofType::PrevProof(commit_chain_output) = &prev_proof else { + panic!("Only PrevProof is supported in propose_longest_chain"); + }; assert_eq!( commit_chain_output.chain_state.commit_txn.compute_txid(), spv_ss_commit.transaction.0.compute_txid() @@ -222,16 +253,11 @@ pub fn propose_longest_chain( // https://github.com/KSlashh/BitVM/blob/v2/goat/src/transactions/watchtower_challenge.rs#L128 // verify operator_header_chain is valid - let header_part_stark_vk = attested_part_stark_vk_for_zkm_version( - &attestation.unique_witnesses, - &operator_header_chain.zkm_version, - ) - .expect("Failed to resolve attested header-chain part_stark_vk"); - verify_proof_with_part_stark_vk( + verify_groth16_proof( &operator_header_chain.zkm_proof, &operator_header_chain.zkm_public_values, &operator_header_chain.zkm_vk_hash, - &header_part_stark_vk, + &operator_header_chain.zkm_version, ) .expect("Failed to verify header chain proof"); let prev_output = ZKMPublicValues::from(&operator_header_chain.zkm_public_values).read(); @@ -239,11 +265,6 @@ pub fn propose_longest_chain( let HeaderChainPrevProofType::PrevProof(btc_header_chain_output) = &prev_proof else { panic!("Only PrevProof is supported in propose_longest_chain"); }; - assert_part_stark_vk_in_verified_witnesses( - &attestation.unique_witnesses, - &btc_header_chain_output.part_stark_vk, - ) - .expect("Failed to match header-chain part_stark_vk in verified witnesses"); let operator_total_work = btc_header_chain_output.chain_state.total_work; let operator_consensus_block_height = U32::from(commit_chain_output.chain_state.block_height); // commit header chain best block hash as pis @@ -253,55 +274,133 @@ pub fn propose_longest_chain( assert!(spv_ss_commit.verify(&btc_header_chain_output.chain_state.block_hashes_mmr)); // parse included_watchtowers into bits array - let included_watchertowers_bits = u256_to_le_bits(included_watchtowers); - println!("included watchtowers:{included_watchertowers_bits:?}"); + let included_watchtowers_bits = u256_to_le_bits(included_watchtowers); + println!("included watchtowers:{included_watchtowers_bits:?}"); + + let mut valid_included_watchtower_count = 0usize; // For each watchtowers, if the included_watchtowers[i] is true, // verify the watchtower_challenge_txns[i] is valid // verify watchtower_challenge_txns[i].total_work <= operator_header_chain.total_work // verify watchtower_challenge_txns[i].epoch <= operator_latest_sequencer_commit_tx.epoch for i in 0..watchtower_challenge_txns.len() { - if included_watchertowers_bits[i] { + if included_watchtowers_bits[i] { let tx = &watchtower_challenge_txns[i]; + println!("Verify watchtower[{i}] tx: {}, {:?}", tx.compute_txid(), tx); let prev_out = &watchtower_challenge_txn_prev_outs[i]; + let prev_index = tx.input[0].previous_output.vout as usize; let pubkey = &watchtower_challenge_txn_pubkey[i]; - let watchtower_outputs = verify_included_watchtower_challenge( - i, - &graph_id, + + if let Err(err) = + verify_fixed_watchtower_pubkey(fixed_watchtower_xonly_public_keys, i, pubkey) + { + println!("Watchtower[{i}] fixed pubkey verification: {err}"); + continue; + } + + let sig = match tx + .input + .first() + .and_then(|input| input.witness.iter().next()) + .map(bitcoin::taproot::Signature::from_slice) + { + Some(Ok(sig)) => sig, + Some(Err(err)) => { + println!("Watchtower[{i}] invalid taproot signature: {err}"); + continue; + } + None => { + println!("Watchtower[{i}] missing taproot signature"); + continue; + } + }; + // check tx signature is valid + match verify_taproot_leaf_schnorr_signature( + &watchtower_challenge_txn_scripts[i], tx, + prev_index, prev_out, - &watchtower_challenge_txn_scripts[i], pubkey, - &attestation.unique_witnesses, - operator_total_work, - operator_consensus_block_height, - ) - .unwrap_or_else(|err| panic!("Watchtower[{i}] invalid included challenge: {err}")); + &sig, + ) { + Ok(_) => {} + Err(msg) => { + println!("Watchtower[{i}] signature verification: {msg}"); + continue; + } + }; + + let commitment = &extract_data_from_commitment_outputs(&tx.output)[..]; + println!("commitment: {commitment:?}"); + println!("commitment hex: {}", hex::encode(commitment)); + let ( + parsed_graph_id, + proof, + public_values, + vk, + watchtower_total_work, + watchtower_consensus_block_height, + zkm_version, + ) = match parse_watchtower_commitment(commitment) { + Ok(c) => c, + Err(err) => { + println!("Watchtower[{i}] parse commitment error, {err}"); + continue; + } + }; + + match verify_groth16_proof(&proof, &public_values, &vk, &zkm_version) { + Ok(_) => {} + Err(err) => { + println!("Watchtower[{i}] invalid proof: {err}"); + continue; + } + } - println!( - "watchtower total work: {:?}", - U256::from_be_bytes(watchtower_outputs.total_work) - ); + if parsed_graph_id != graph_id { + println!( + "Watchtower[{i}] invalid commitment: graph id: parsed = {}, expected = {}", + hex::encode(parsed_graph_id), + hex::encode(graph_id) + ); + continue; + } + println!("check total work with watchtower {i}"); + + // extract ChainState + // check watchtower_chain_state.total_work <= operator_header_chain.total_work + println!("watchtower total work: {:?}", U256::from_be_bytes(watchtower_total_work)); println!("operator total work: {operator_total_work:?}"); + println!( "watchtower_consensus_block_height : {:?}", - U32::from_le_bytes(watchtower_outputs.consensus_block_height) + U32::from_le_bytes(watchtower_consensus_block_height) ); println!("operator_consensus_block_height : {operator_consensus_block_height:?}"); + + if U256::from_be_bytes(watchtower_total_work) > U256::from_be_bytes(operator_total_work) + { + println!("Watchtower[{i}] total work exceeds operator total work"); + continue; + } + // check watchtower.consensus.block_height <= consensus.block_height + if U32::from_le_bytes(watchtower_consensus_block_height) + > operator_consensus_block_height + { + println!("Watchtower[{i}] consensus block height exceeds operator block height"); + continue; + } + + valid_included_watchtower_count += 1; } } + assert!(valid_included_watchtower_count > 0, "no included watchtower passed verification"); println!("verify el block"); - - let state_part_stark_vk = attested_part_stark_vk_for_zkm_version( - &attestation.unique_witnesses, - &state_chain.zkm_version, - ) - .expect("Failed to resolve attested state-chain part_stark_vk"); - verify_proof_with_part_stark_vk( + verify_groth16_proof( &state_chain.zkm_proof, &state_chain.zkm_public_values, &state_chain.zkm_vk_hash, - &state_part_stark_vk, + &state_chain.zkm_version, ) .expect("Failed to verify state chain proof"); @@ -310,13 +409,6 @@ pub fn propose_longest_chain( let StateChainPrevProofType::PrevProof(state_chain_output) = &prev_proof else { panic!("Only PrevProof is supported in propose_longest_chain"); }; - - assert_part_stark_vk_in_verified_witnesses( - &attestation.unique_witnesses, - &state_chain_output.part_stark_vk, - ) - .expect("Failed to match state-chain part_stark_vk in verified witnesses"); - // check the signature. let cosmos_block_bytes = &state_chain_output.chain_state.latest_cosmos_block; let cosmos_block: LightBlock = @@ -329,12 +421,16 @@ pub fn propose_longest_chain( // check commit chain's genesis block let commitment = commit_chain::extract_op_return_data(&commit_chain_output.chain_state.commit_txn.output); + let commitment = parse_commit_chain_commitment(&commitment); if let tendermint::Hash::Sha256(x) = expected_seqeuencer_set_hash { - assert_eq!(commitment[0..32], x); + assert_eq!(commitment.sequencer_set_hash, x); } else { panic!("Invalid commitment: inconsistent sequencer set hash"); }; - assert_eq!(commitment[32..64], state_chain_output.chain_state.genesis_evm_block_hash[..]); + assert_eq!( + commitment.genesis_evm_block_hash, + state_chain_output.chain_state.genesis_evm_block_hash + ); assert_eq!(commit_sequencer_set_hash, expected_seqeuencer_set_hash); @@ -370,10 +466,25 @@ pub fn propose_longest_chain( ); //operator_public_input - OperatorPublicOutputs { - btc_best_block_hash: operator_committed_blockhash, + ( + operator_committed_blockhash, constant, - included_watchtowers: included_watchtowers.to_le_bytes::<32>(), + included_watchtowers.to_le_bytes::<32>(), + resolve_operator_vk_hash( + commit_chain_output.chain_state.operator_vk_hash, + actual_operator_vk_hash, + ), + ) +} + +pub fn resolve_operator_vk_hash( + commit_chain_operator_vk_hash: [u8; 32], + actual_operator_vk_hash: [u8; 32], +) -> [u8; 32] { + if commit_chain_operator_vk_hash == commit_chain::LEGACY_OPERATOR_VK_HASH { + actual_operator_vk_hash + } else { + commit_chain_operator_vk_hash } } @@ -388,6 +499,51 @@ pub fn hash_operator_constant( *hash.as_byte_array() } +pub const WRAPPER_PUBLIC_VALUES_SIZE: usize = 32 + GRAPH_ID_SIZE + 32; + +pub fn wrapper_public_values( + operator_vk_hash: [u8; 32], + graph_id: [u8; GRAPH_ID_SIZE], + genesis_sequencer_commit_txid: [u8; 32], +) -> [u8; WRAPPER_PUBLIC_VALUES_SIZE] { + let mut public_values = [0u8; WRAPPER_PUBLIC_VALUES_SIZE]; + public_values[..32].copy_from_slice(&operator_vk_hash); + public_values[32..32 + GRAPH_ID_SIZE].copy_from_slice(&graph_id); + public_values[32 + GRAPH_ID_SIZE..].copy_from_slice(&genesis_sequencer_commit_txid); + public_values +} + +// zkm_vk_hash: 66 bytes, prefix with '0x' +pub fn zkm_vk_hash_to_raw(vk_hash: &[u8]) -> Result<[u8; 32], String> { + let vk_hash = + std::str::from_utf8(vk_hash).map_err(|err| format!("invalid zkm vk hash UTF-8: {err}"))?; + let Some(hex_hash) = vk_hash.strip_prefix("0x") else { + return Err("zkm vk hash must have 0x prefix".to_string()); + }; + if hex_hash.len() != 64 { + return Err(format!("zkm vk hash must be 64 hex chars, got {}", hex_hash.len())); + } + let bytes = hex::decode(hex_hash).map_err(|err| format!("invalid zkm vk hash hex: {err}"))?; + bytes.try_into().map_err(|_| "zkm vk hash must decode to 32 bytes".to_string()) +} + +pub fn zkm_vk_hash_from_raw(raw: &[u8; 32]) -> Vec { + format!("0x{}", hex::encode(raw)).into_bytes() +} + +pub fn hash_partial_binding_witness( + constant: [u8; 32], + btc_best_block_hash: [u8; 32], + included_watchtowers: [u8; 32], +) -> [u8; 32] { + let mut engine = sha256::HashEngine::default(); + engine.input(&constant); + engine.input(&btc_best_block_hash); + engine.input(&included_watchtowers); + let hash = sha256::Hash::from_engine(engine); + *hash.as_byte_array() +} + /// Utility method for converting u32 words to bytes in big endian. pub fn words_to_bytes_be(words: &[u32; 8]) -> [u8; 32] { let mut bytes = [0u8; 32]; @@ -452,54 +608,37 @@ pub fn build_spv( pub fn build_watchtower_commitment( graph_id: &[u8; GRAPH_ID_SIZE], - proof: &[u8], - public_inputs: &[u8], + proof: &[u8; PROOF_SIZE], + public_inputs: &[u8; PUBLIC_INPUTS_SIZE], vk_hash: &str, - proof_part_stark_vk: &[u8], -) -> Result, String> { - if proof.len() != PROOF_SIZE { - return Err(format!("invalid proof length: {}, expected {}", proof.len(), PROOF_SIZE)); - } - if proof_part_stark_vk.is_empty() { - return Err("proof_part_stark_vk must not be empty".to_string()); - } - let mut comm = Vec::with_capacity( - GRAPH_ID_SIZE - + PROOF_SIZE - + WATCHTOWER_COMMITMENT_PUBLIC_INPUTS_LEN_SIZE - + public_inputs.len() - + VK_HASH_SIZE - + WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE - + proof_part_stark_vk.len(), - ); - comm.extend_from_slice(graph_id); + zkm_version: &str, +) -> Vec { + let mut comm = graph_id.to_vec(); comm.extend_from_slice(proof); - comm.extend_from_slice(&(public_inputs.len() as u32).to_le_bytes()); comm.extend_from_slice(public_inputs); - if vk_hash.len() != VK_HASH_SIZE { - return Err(format!( - "invalid vk_hash length: {}, expected {}", - vk_hash.len(), - VK_HASH_SIZE - )); - } + assert_eq!(vk_hash.len(), VK_HASH_SIZE); comm.extend_from_slice(vk_hash.as_bytes()); - comm.extend_from_slice(&(proof_part_stark_vk.len() as u32).to_le_bytes()); - comm.extend_from_slice(proof_part_stark_vk); - Ok(comm) + comm.extend_from_slice(&(zkm_version.len() as u32).to_le_bytes()); + comm.extend_from_slice(zkm_version.as_bytes()); + + comm } -pub type WatchtowerCommitmentResult = - ([u8; GRAPH_ID_SIZE], Vec, Vec, [u8; VK_HASH_SIZE], Vec); +pub type WatchtowerCommitmentResult = ( + [u8; GRAPH_ID_SIZE], + [u8; PROOF_SIZE], + [u8; PUBLIC_INPUTS_SIZE], + [u8; VK_HASH_SIZE], + [u8; TOTAL_WORK_SIZE], + [u8; CONSENSUS_BLOCK_HEIGHT_SIZE], + String, +); pub fn parse_watchtower_commitment( commitment: &[u8], ) -> Result { - let min_commitment_size = GRAPH_ID_SIZE - + PROOF_SIZE - + WATCHTOWER_COMMITMENT_PUBLIC_INPUTS_LEN_SIZE - + VK_HASH_SIZE - + WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE; + let min_commitment_size = + GRAPH_ID_SIZE + PROOF_SIZE + PUBLIC_INPUTS_SIZE + VK_HASH_SIZE + ZKM_VERSION_LEN_SIZE; if commitment.len() < min_commitment_size { return Err(format!( "invalid commitment size: {}, expected at least {}", @@ -511,208 +650,67 @@ pub fn parse_watchtower_commitment( let mut graph_id = [0u8; GRAPH_ID_SIZE]; graph_id.copy_from_slice(&commitment[..end]); - let proof = commitment[end..end + PROOF_SIZE].to_vec(); + let mut proof = [0u8; PROOF_SIZE]; + proof.copy_from_slice(&commitment[end..end + PROOF_SIZE]); end += PROOF_SIZE; - let public_inputs_len = u32::from_le_bytes( - commitment[end..end + WATCHTOWER_COMMITMENT_PUBLIC_INPUTS_LEN_SIZE].try_into().unwrap(), - ) as usize; - end += WATCHTOWER_COMMITMENT_PUBLIC_INPUTS_LEN_SIZE; - - let proof_part_stark_vk_len_offset = end + public_inputs_len + VK_HASH_SIZE; - if commitment.len() - < proof_part_stark_vk_len_offset + WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE - { - return Err(format!( - "invalid commitment size: {}, missing proof_part_stark_vk length field", - commitment.len() - )); - } - let proof_part_stark_vk_len = u32::from_le_bytes( - commitment[proof_part_stark_vk_len_offset - ..proof_part_stark_vk_len_offset + WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE] - .try_into() - .unwrap(), - ) as usize; - let expected_size = min_commitment_size + public_inputs_len + proof_part_stark_vk_len; - if commitment.len() != expected_size { - return Err(format!( - "invalid commitment size: {}, expected {}", - commitment.len(), - expected_size - )); - } - - let zkm_public_values = commitment[end..end + public_inputs_len].to_vec(); - end += public_inputs_len; + let mut zkm_public_values = [0u8; PUBLIC_INPUTS_SIZE]; + zkm_public_values.copy_from_slice(&commitment[end..end + PUBLIC_INPUTS_SIZE]); + end += PUBLIC_INPUTS_SIZE; let mut zkm_vk_hash_bytes = [0u8; VK_HASH_SIZE]; zkm_vk_hash_bytes.copy_from_slice(&commitment[end..end + VK_HASH_SIZE]); end += VK_HASH_SIZE; - let proof_part_stark_vk_len = u32::from_le_bytes( - commitment[end..end + WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE] - .try_into() - .unwrap(), - ) as usize; - if proof_part_stark_vk_len == 0 { - return Err("proof_part_stark_vk must not be empty".to_string()); + let zkm_version_len = + u32::from_le_bytes(commitment[end..end + ZKM_VERSION_LEN_SIZE].try_into().unwrap()) + as usize; + if zkm_version_len == 0 { + return Err("zkm_version must not be empty".to_string()); } - end += WATCHTOWER_COMMITMENT_PROOF_PART_STARK_VK_LEN_SIZE; - let proof_part_stark_vk = commitment[end..end + proof_part_stark_vk_len].to_vec(); - Ok((graph_id, proof, zkm_public_values, zkm_vk_hash_bytes, proof_part_stark_vk)) -} - -pub fn parse_watchtower_public_outputs( - zkm_public_values: &[u8], -) -> Result { - let mut public_values = ZKMPublicValues::from(zkm_public_values); - catch_unwind(AssertUnwindSafe(|| public_values.read::())) - .map_err(|_| "failed to deserialize watchtower public outputs".to_string()) -} - -// Check the public values are consistent with the total work and block hash -fn groth16_verifier_keys(zkm_version: &str) -> Result<(&'static [u8], &'static [u8]), String> { - let imm_groth16_vk = *IMM_GROTH16_VK_BYTES; - let part_stark_vk = - catch_unwind(AssertUnwindSafe(|| Groth16Verifier::get_part_stark_vk(zkm_version))) - .map_err(|_| format!("failed to load part_stark_vk for zkm_version '{zkm_version}'"))?; - Ok((imm_groth16_vk, part_stark_vk)) -} - -/// Resolve the version-derived `part_stark_vk` and require it to be attested before use. -fn attested_part_stark_vk_for_zkm_version( - unique_witnesses: &[UniquePartStarkVkWitness], - zkm_version: &str, -) -> Result, String> { - let (_, part_stark_vk) = groth16_verifier_keys(zkm_version)?; - assert_part_stark_vk_in_verified_witnesses(unique_witnesses, part_stark_vk)?; - Ok(part_stark_vk.to_vec()) -} + end += ZKM_VERSION_LEN_SIZE; -/// Verify one included watchtower challenge end-to-end and return its parsed public outputs. -#[allow(clippy::too_many_arguments)] -fn verify_included_watchtower_challenge( - index: usize, - graph_id: &[u8; GRAPH_ID_SIZE], - tx: &Transaction, - prev_out: &TxOut, - script: &ScriptBuf, - pubkey: &PublicKey, - unique_witnesses: &[UniquePartStarkVkWitness], - operator_total_work: [u8; TOTAL_WORK_SIZE], - operator_consensus_block_height: U32, -) -> Result { - println!("Verify watchtower[{index}] tx: {}, {:?}", tx.compute_txid(), tx); - let input = tx - .input - .first() - .ok_or_else(|| "watchtower tx must contain at least one input".to_string())?; - let witness = input - .witness - .iter() - .next() - .ok_or_else(|| "watchtower tx witness must contain a taproot signature".to_string())?; - let sig = bitcoin::taproot::Signature::from_slice(witness) - .map_err(|err| format!("invalid taproot signature: {err}"))?; - let prev_index = input.previous_output.vout as usize; - verify_taproot_leaf_schnorr_signature(script, tx, prev_index, prev_out, pubkey, &sig) - .map_err(|err| format!("signature verification failed: {err}"))?; - - let commitment = extract_data_from_commitment_outputs(&tx.output); - println!("commitment: {commitment:?}"); - println!("commitment hex: {}", hex::encode(&commitment)); - - let (parsed_graph_id, proof, public_values, vk, proof_part_stark_vk) = - parse_watchtower_commitment(&commitment)?; - if parsed_graph_id != *graph_id { + let expected_size = min_commitment_size + zkm_version_len; + if commitment.len() != expected_size { return Err(format!( - "graph id mismatch: parsed={}, expected={}", - hex::encode(parsed_graph_id), - hex::encode(graph_id) + "invalid commitment size: {}, expected {}", + commitment.len(), + expected_size )); } - assert_part_stark_vk_in_verified_witnesses(unique_witnesses, &proof_part_stark_vk)?; - verify_proof_with_part_stark_vk(&proof, &public_values, &vk, &proof_part_stark_vk)?; - - println!("check total work with watchtower {index}"); - let watchtower_outputs = parse_watchtower_public_outputs(&public_values)?; - if U256::from_be_bytes(watchtower_outputs.total_work) > U256::from_be_bytes(operator_total_work) - { - return Err("watchtower total work exceeds operator total work".to_string()); - } - if U32::from_le_bytes(watchtower_outputs.consensus_block_height) - > operator_consensus_block_height - { - return Err( - "watchtower consensus block height exceeds operator consensus block height".to_string() - ); - } - Ok(watchtower_outputs) -} - -/// Verify commit-chain with the trusted base-layer verifier and return its output. -fn verify_commit_chain_output( - commit_chain: &CommitChainCircuitInput, -) -> Result { - let trusted_part_stark_vk = commit_chain::trusted_commit_chain_part_stark_vk(); - verify_proof_with_part_stark_vk( - &commit_chain.zkm_proof, - &commit_chain.zkm_public_values, - &commit_chain.zkm_vk_hash, - &trusted_part_stark_vk, - )?; - let output: CommitChainCircuitOutput = - ZKMPublicValues::from(&commit_chain.zkm_public_values).read(); + let zkm_version = String::from_utf8(commitment[end..end + zkm_version_len].to_vec()) + .map_err(|err| format!("invalid zkm_version UTF-8: {err}"))?; - Ok(output) -} - -/// Return the publisher set that authorizes part_stark_vk attestations for this commit-chain output. -fn commit_chain_attestation_authority( - commit_chain_output: &CommitChainCircuitOutput, -) -> (&[PublicKey], u16) { - ( - &commit_chain_output.chain_state.publisher_public_keys, - commit_chain_output.chain_state.threshold, - ) -} + // extract ChainState + let mut watchtower_total_work = [0u8; TOTAL_WORK_SIZE]; + watchtower_total_work.copy_from_slice(&zkm_public_values[0..TOTAL_WORK_SIZE]); + let mut watchtower_consensus_block_height = [0u8; CONSENSUS_BLOCK_HEIGHT_SIZE]; + watchtower_consensus_block_height.copy_from_slice( + &zkm_public_values[TOTAL_WORK_SIZE..TOTAL_WORK_SIZE + CONSENSUS_BLOCK_HEIGHT_SIZE], + ); -pub fn verify_proof( - proof: &[u8], - zkm_public_values: &[u8], - zkm_vk_hash: &[u8], - zkm_version: &str, -) -> Result<(), String> { - let (_, part_stark_vk) = groth16_verifier_keys(zkm_version)?; - verify_proof_with_part_stark_vk(proof, zkm_public_values, zkm_vk_hash, part_stark_vk) -} + println!("watchtower total work: {watchtower_total_work:?}"); + println!("watchtower total work: {:?}", U256::from_be_bytes(watchtower_total_work)); + println!("watchtower consensus block height: {watchtower_consensus_block_height:?}"); + println!( + "watchtower consensus block height: {:?}", + U32::from_le_bytes(watchtower_consensus_block_height) + ); -/// Verify a Groth16 proof against an explicit part_stark_vk instead of a version lookup. -pub fn verify_proof_with_part_stark_vk( - proof: &[u8], - zkm_public_values: &[u8], - zkm_vk_hash: &[u8], - part_stark_vk: &[u8], -) -> Result<(), String> { - let groth16_vk = *IMM_GROTH16_VK_BYTES; - let zkm_vk_hash = String::from_utf8(zkm_vk_hash.to_vec()).map_err(|e| e.to_string())?; - match Groth16Verifier::verify_by_imm_groth16_vk( + Ok(( + graph_id, proof, zkm_public_values, - &zkm_vk_hash, - groth16_vk, - part_stark_vk, - ) { - Ok(_) => Ok(()), - Err(err) => Err(format!("Verify Groth16 proof, err: {err:?}")), - } + zkm_vk_hash_bytes, + watchtower_total_work, + watchtower_consensus_block_height, + zkm_version, + )) } #[cfg(test)] mod tests { use super::*; - use crate::PartStarkVkAttestationBundle; use bitcoin::Transaction; const PROOF: &[u8] = include_bytes!("../../../circuits/data/watchtower/output3.bin.proof.bin"); const PUBLIC_INPUTS: &[u8] = @@ -720,43 +718,21 @@ mod tests { const VK_HASH: &str = include_str!("../../../circuits/data/watchtower/output3.bin.vk_hash.bin"); const ZKM_VERSION: &str = "v1.2.4"; - #[test] - fn test_groth16_verifier_keys_keep_common_vk_available() { - assert!(!IMM_GROTH16_VK_BYTES.is_empty()); - - match groth16_verifier_keys(ZKM_VERSION) { - Ok((imm_groth16_vk, part_stark_vk)) => { - assert_eq!(imm_groth16_vk, *IMM_GROTH16_VK_BYTES); - assert!(!part_stark_vk.is_empty()); - } - Err(err) => { - assert!(err.contains("failed to load part_stark_vk")); - } - } - } - #[test] fn test_build_watchtower_commitment() { let graph_id = hex::decode("00112233445566778899aabbccddeeff").unwrap().try_into().unwrap(); - let total_work = 1006120u64; - let block_height = 503043u32; - let proof_part_stark_vk = vec![8u8; 52]; - let expected_outputs = WatchtowerPublicOutputs { - total_work: U256::from(total_work).to_be_bytes(), - consensus_block_height: U32::from(block_height).to_le_bytes(), - }; - let public_inputs = bincode::serialize(&expected_outputs).unwrap(); + let total_work = 1006120; + let block_height = 503043; println!("public inputs: {:?}", PUBLIC_INPUTS.len()); println!("vk hash: {:?}", VK_HASH.len()); let comm = build_watchtower_commitment( &graph_id, - PROOF, - &public_inputs, + &PROOF.try_into().unwrap(), + &PUBLIC_INPUTS.try_into().unwrap(), VK_HASH, - &proof_part_stark_vk, - ) - .unwrap(); + ZKM_VERSION, + ); println!("comm: {:?}", comm.len()); println!("comm hex: {:?}", hex::encode(&comm)); @@ -765,11 +741,11 @@ mod tests { assert_eq!(expected.0, graph_id); assert_eq!(expected.1, PROOF); - assert_eq!(expected.2, public_inputs); + assert_eq!(expected.2, PUBLIC_INPUTS); assert_eq!(expected.3, VK_HASH.as_bytes()); - assert_eq!(expected.4, proof_part_stark_vk); - let parsed_outputs = parse_watchtower_public_outputs(&expected.2).unwrap(); - assert_eq!(parsed_outputs, expected_outputs); + assert_eq!(expected.4, U256::from(total_work).to_be_bytes()); + assert_eq!(expected.5, U32::from(block_height).to_le_bytes()); + assert_eq!(expected.6, ZKM_VERSION.to_string()); } #[test] @@ -783,6 +759,25 @@ mod tests { assert_eq!(words, recovered); } + #[test] + fn test_hash_partial_binding_witness() { + use bitcoin::hashes::Hash as _; + + let constant = [1u8; 32]; + let btc_best_block_hash = [2u8; 32]; + let included_watchtowers = [3u8; 32]; + let mut input = Vec::new(); + input.extend_from_slice(&constant); + input.extend_from_slice(&btc_best_block_hash); + input.extend_from_slice(&included_watchtowers); + let expected = bitcoin::hashes::sha256::Hash::hash(&input); + + assert_eq!( + hash_partial_binding_witness(constant, btc_best_block_hash, included_watchtowers), + *expected.as_byte_array() + ); + } + #[test] fn test_u256_to_le_bits() { use std::str::FromStr; @@ -817,216 +812,56 @@ mod tests { } #[test] - fn test_parse_watchtower_commitment_rejects_legacy_v1_payload() { - let graph_id: [u8; GRAPH_ID_SIZE] = - hex::decode("00112233445566778899aabbccddeeff").unwrap().try_into().unwrap(); - let mut legacy = graph_id.to_vec(); - legacy.extend_from_slice(PROOF); - legacy.extend_from_slice(&(PUBLIC_INPUTS.len() as u32).to_le_bytes()); - legacy.extend_from_slice(PUBLIC_INPUTS); - legacy.extend_from_slice(VK_HASH.as_bytes()); - let mut legacy_zkm_version = [0u8; 16]; - legacy_zkm_version[..ZKM_VERSION.len()].copy_from_slice(ZKM_VERSION.as_bytes()); - legacy.extend_from_slice(&legacy_zkm_version); - assert!(parse_watchtower_commitment(&legacy).is_err()); - } - - #[test] - fn test_parse_watchtower_commitment_rejects_missing_proof_part_stark_vk() { + fn test_parse_watchtower_commitment_rejects_missing_zkm_version_len() { let graph_id: [u8; GRAPH_ID_SIZE] = hex::decode("00112233445566778899aabbccddeeff").unwrap().try_into().unwrap(); let mut commitment = graph_id.to_vec(); commitment.extend_from_slice(PROOF); - commitment.extend_from_slice(&(PUBLIC_INPUTS.len() as u32).to_le_bytes()); commitment.extend_from_slice(PUBLIC_INPUTS); commitment.extend_from_slice(VK_HASH.as_bytes()); assert!(parse_watchtower_commitment(&commitment).is_err()); } #[test] - fn test_parse_watchtower_commitment_accepts_versionless_dual_key_payload() { + fn test_parse_watchtower_commitment_rejects_empty_zkm_version() { let graph_id: [u8; GRAPH_ID_SIZE] = hex::decode("00112233445566778899aabbccddeeff").unwrap().try_into().unwrap(); - let proof_part_stark_vk = vec![9u8; 48]; let mut commitment = graph_id.to_vec(); commitment.extend_from_slice(PROOF); - commitment.extend_from_slice(&(PUBLIC_INPUTS.len() as u32).to_le_bytes()); commitment.extend_from_slice(PUBLIC_INPUTS); commitment.extend_from_slice(VK_HASH.as_bytes()); - commitment.extend_from_slice(&(proof_part_stark_vk.len() as u32).to_le_bytes()); - commitment.extend_from_slice(&proof_part_stark_vk); + commitment.extend_from_slice(&0u32.to_le_bytes()); - assert!( - parse_watchtower_commitment(&commitment).is_ok(), - "versionless dual-key commitment should parse" - ); + assert!(parse_watchtower_commitment(&commitment).is_err()); } #[test] - fn test_parse_watchtower_commitment_rejects_empty_proof_part_stark_vk() { + fn test_parse_watchtower_commitment_rejects_invalid_zkm_version_utf8() { let graph_id: [u8; GRAPH_ID_SIZE] = hex::decode("00112233445566778899aabbccddeeff").unwrap().try_into().unwrap(); let mut commitment = graph_id.to_vec(); commitment.extend_from_slice(PROOF); - commitment.extend_from_slice(&(PUBLIC_INPUTS.len() as u32).to_le_bytes()); commitment.extend_from_slice(PUBLIC_INPUTS); commitment.extend_from_slice(VK_HASH.as_bytes()); - commitment.extend_from_slice(&0u32.to_le_bytes()); + commitment.extend_from_slice(&2u32.to_le_bytes()); + commitment.extend_from_slice(&[0xff, 0xff]); assert!(parse_watchtower_commitment(&commitment).is_err()); } #[test] - fn test_parse_watchtower_commitment_rejects_versioned_payload() { - let graph_id: [u8; GRAPH_ID_SIZE] = - hex::decode("00112233445566778899aabbccddeeff").unwrap().try_into().unwrap(); - let proof_part_stark_vk = vec![3u8; 12]; - let mut versioned = vec![2u8]; - versioned.extend_from_slice(&graph_id); - versioned.extend_from_slice(PROOF); - versioned.extend_from_slice(&(PUBLIC_INPUTS.len() as u32).to_le_bytes()); - versioned.extend_from_slice(PUBLIC_INPUTS); - versioned.extend_from_slice(VK_HASH.as_bytes()); - versioned.extend_from_slice(&(proof_part_stark_vk.len() as u32).to_le_bytes()); - versioned.extend_from_slice(&proof_part_stark_vk); - - assert!( - parse_watchtower_commitment(&versioned).is_err(), - "versioned watchtower commitment should be rejected" - ); - } - - #[test] - fn test_parse_watchtower_public_outputs_rejects_short_public_inputs() { - let result = parse_watchtower_public_outputs(&[0u8; TOTAL_WORK_SIZE + 1]); - assert!(result.is_err()); - } - - #[test] - fn test_parse_watchtower_public_outputs_reads_bincode_serialized_struct() { - let expected = WatchtowerPublicOutputs { - total_work: [9u8; TOTAL_WORK_SIZE], - consensus_block_height: 123u32.to_le_bytes(), - }; - - let public_inputs = bincode::serialize(&expected).unwrap(); - let parsed = parse_watchtower_public_outputs(&public_inputs).unwrap(); - - assert_eq!(parsed, expected); - } - - #[test] - fn test_verify_proof_accepts_non_fixed_length_version() { - let long_version = "v1.12.15-rc1+build.20260319"; - let result = verify_proof(&[], &[], &[], long_version); - assert!(result.is_err()); - assert!(!result.unwrap_err().contains("too long")); - } - - #[test] - fn test_verify_proof_with_part_stark_vk_uses_explicit_vk_bytes() { - let part_stark_vk = groth16_verifier_keys(ZKM_VERSION).unwrap().1.to_vec(); - let result = verify_proof_with_part_stark_vk(&[], &[], &[], &part_stark_vk); - assert!(result.is_err()); - } - - #[test] - fn test_groth16_verifier_keys_reject_unknown_version_without_panic() { - let result = groth16_verifier_keys("v0.0.0-test"); - assert!(result.is_err()); - } - - fn sample_unique_witness(part_stark_vk: Vec) -> PartStarkVkAttestationBundle { - PartStarkVkAttestationBundle { - part_stark_vk, - leaf_index: 0, - merkle_path: vec![], - root: [0u8; 32], - threshold: 1, - publisher_set_id: [0u8; 32], - signatures: vec![], - } - } - - #[test] - fn test_attested_part_stark_vk_for_zkm_version_accepts_verified_witness_payload() { - let part_stark_vk = groth16_verifier_keys(ZKM_VERSION).unwrap().1.to_vec(); - let unique_witnesses = vec![sample_unique_witness(part_stark_vk.clone())]; + fn test_zkm_vk_hash_to_raw_accepts_prefixed_hash() { + let raw = [0xabu8; 32]; + let prefixed = zkm_vk_hash_from_raw(&raw); - assert_eq!( - attested_part_stark_vk_for_zkm_version(&unique_witnesses, ZKM_VERSION).unwrap(), - part_stark_vk - ); + assert_eq!(zkm_vk_hash_to_raw(&prefixed).unwrap(), raw); } #[test] - fn test_attested_part_stark_vk_for_zkm_version_rejects_missing_witness_payload() { - let unique_witnesses = vec![sample_unique_witness(vec![7u8; 32])]; - - assert!(attested_part_stark_vk_for_zkm_version(&unique_witnesses, ZKM_VERSION).is_err()); - } - - fn sample_commit_tx() -> Transaction { - use bitcoin::{absolute::LockTime, transaction::Version}; - - Transaction { - version: Version::TWO, - lock_time: LockTime::ZERO, - input: vec![], - output: vec![], - } - } - - fn sample_commit_chain_output( - genesis_txid: [u8; 32], - commit_txn: Transaction, - publisher_public_keys: Vec, - threshold: u16, - ) -> CommitChainCircuitOutput { - CommitChainCircuitOutput { - chain_state: commit_chain::CommitChainState { - block_height: 7, - commit_txn, - genesis_txid, - sequencers: vec![], - publisher_public_keys, - threshold, - }, - } - } - - #[test] - fn test_commit_chain_attestation_authority_uses_chain_state() { - let publisher_public_keys = - commit_chain::create_dummy_publisher_keys(3, bitcoin::Network::Regtest) - .into_iter() - .map(|(_, pk)| pk) - .collect::>(); - let threshold = 2u16; - let commit_chain_output = sample_commit_chain_output( - [3u8; 32], - sample_commit_tx(), - publisher_public_keys.clone(), - threshold, - ); - - let (actual_keys, actual_threshold) = - commit_chain_attestation_authority(&commit_chain_output); - assert_eq!(actual_keys, publisher_public_keys.as_slice()); - assert_eq!(actual_threshold, threshold); - } - - #[test] - fn test_operator_public_outputs_bincode_shape_excludes_part_stark_vk() { - let expected = OperatorPublicOutputs { - btc_best_block_hash: [1u8; 32], - constant: [2u8; 32], - included_watchtowers: [3u8; 32], - }; - - let public_inputs = bincode::serialize(&expected).unwrap(); - let parsed: OperatorPublicOutputs = bincode::deserialize(&public_inputs).unwrap(); + fn test_zkm_vk_hash_to_raw_rejects_unprefixed_hash() { + let raw = [0xcdu8; 32]; + let unprefixed = hex::encode(raw); - assert_eq!(parsed, expected); + assert!(zkm_vk_hash_to_raw(unprefixed.as_bytes()).is_err()); } } diff --git a/crates/bitcoin-light-client-circuit/src/utils.rs b/crates/bitcoin-light-client-circuit/src/utils.rs index 02ee439c..e796e6ed 100644 --- a/crates/bitcoin-light-client-circuit/src/utils.rs +++ b/crates/bitcoin-light-client-circuit/src/utils.rs @@ -1,6 +1,7 @@ use bitcoin::absolute::LockTime; use bitcoin::blockdata::opcodes::all::*; use bitcoin::blockdata::script::Builder; +use bitcoin::script::PushBytesBuf; use bitcoin::transaction::Version; use bitcoin::{Address, Amount, OutPoint, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness}; @@ -81,7 +82,7 @@ pub fn create_fee_tx( } pub fn create_sequencer_update_partial_tx( - commitment: [u8; 64], + commitment: [u8; 96], update_connector: &Option, replenish_fee_connector: &Option, next_update_connector: Address, @@ -96,6 +97,8 @@ pub fn create_sequencer_update_partial_tx( println!("commitment: {commitment:?}"); + let commitment = + PushBytesBuf::try_from(commitment.to_vec()).expect("commitment payload is pushable"); let script = Builder::new().push_opcode(OP_RETURN).push_slice(commitment).into_script(); // make TxOut with 0 satoshis diff --git a/crates/bitvm2-ga/Cargo.toml b/crates/bitvm-gc/Cargo.toml similarity index 72% rename from crates/bitvm2-ga/Cargo.toml rename to crates/bitvm-gc/Cargo.toml index 4210348b..f84c8d57 100644 --- a/crates/bitvm2-ga/Cargo.toml +++ b/crates/bitvm-gc/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "bitvm2-lib" +name = "bitvm-gc" edition = { workspace = true } version = { workspace = true } @@ -7,9 +7,6 @@ version = { workspace = true } bitvm = { workspace = true } goat = { workspace = true } serde = { workspace = true } -ark-groth16 = { workspace = true } -ark-bn254 = { workspace = true } -ark-serialize = { workspace = true } sha2 = { workspace = true } hkdf = { workspace = true } chacha20poly1305 = { workspace = true } @@ -20,9 +17,17 @@ secp256k1 = { workspace = true } anyhow = { workspace = true } hex = { workspace = true } bitcoin-script = { workspace = true } -bitcoin-light-client-circuit = { workspace = true } tracing = { workspace = true } +ark-bn254 = { workspace = true } +ark-groth16 = { workspace = true } +ark-serialize = { workspace = true } +ark-ff = { workspace = true } +verifiable-circuit-babe = { workspace = true } +garbled-snark-verifier = { workspace = true } +soldering-host = { workspace = true } +rayon = { workspace = true } +serde-big-array = { workspace = true } serde_json = { workspace = true } bincode.workspace = true uuid = { workspace = true } @@ -30,12 +35,9 @@ clap = { workspace = true, features = ["derive"] } strum = { workspace = true, features = ["derive"] } [dev-dependencies] -client = { workspace = true } -esplora-client = { workspace = true } -tokio = { workspace = true, features = ["full"] } -bitcoincore-rpc = "0.19" -bitcoin-old = { version = "0.31.2", package = "bitcoin" } +ark-crypto-primitives = { workspace = true } +rand_chacha = { workspace = true } [features] default = [] -ci-tests = [] \ No newline at end of file +ci-tests = [] diff --git a/crates/bitvm2-ga/src/actors.rs b/crates/bitvm-gc/src/actors.rs similarity index 93% rename from crates/bitvm2-ga/src/actors.rs rename to crates/bitvm-gc/src/actors.rs index 7f664bb3..747e7d7b 100644 --- a/crates/bitvm2-ga/src/actors.rs +++ b/crates/bitvm-gc/src/actors.rs @@ -5,7 +5,7 @@ use strum::{Display, EnumString}; pub enum Actor { Committee, Operator, - Challenger, + Verifier, Watchtower, Publisher, All, diff --git a/crates/bitvm-gc/src/babe_adapter.rs b/crates/bitvm-gc/src/babe_adapter.rs new file mode 100644 index 00000000..6b311b34 --- /dev/null +++ b/crates/bitvm-gc/src/babe_adapter.rs @@ -0,0 +1,1031 @@ +use rayon::prelude::*; +use std::collections::HashSet; +use std::panic::{AssertUnwindSafe, catch_unwind}; +use std::path::PathBuf; + +use anyhow::{Result, bail}; +use ark_bn254::{Bn254, Fq, Fr}; +use ark_groth16::VerifyingKey as Groth16VerifyingKey; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; +use bitvm::signatures::{Wots, Wots64}; +use garbled_snark_verifier::bag::S; +use goat::assert_scripts::{ + INPUT_WIRE_NUM, OperatorAssertPublicKey, OperatorAssertSecretKey, WireHash, label_hash, +}; +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; +use soldering_host::BabeBundle; +pub use soldering_host::BabeBundleBuilder; +use verifiable_circuit_babe::babe::{ + BabeBtcSig, LAMPORT_N, ProverSetupState, VerifierSetupState, WeKnownPi1SetupCt as RealSetupCt, + babe_prover_assert, babe_prover_presign, babe_verifier_challenge_assert_cac, + babe_verifier_presign, +}; +use verifiable_circuit_babe::cac::{ + CACSetupPackage as RealCACSetupPackage, FinalizedInstanceData as RealFinalizedInstanceData, + cac_finalize_indices, +}; +use verifiable_circuit_babe::gc::{ + SparseAdaptorEntry as RealSparseAdaptorEntry, SparseAdaptorRow as RealSparseAdaptorRow, + SparseAdaptorTable as RealSparseAdaptorTable, +}; +use verifiable_circuit_babe::instance::BABEInstance; +use verifiable_circuit_babe::instance::commit::CACInstanceCommit as RealCACInstanceCommit; +use verifiable_circuit_babe::prover::BABEProver; +use verifiable_circuit_babe::soldering::{ + SolderingData as RealSolderingData, SolderingProof as RealSolderingProof, +}; +use verifiable_circuit_babe::transactions::{ + TxAssertWitness, TxChallengeAssertWitness as RealTxChallengeAssertWitness, +}; +use verifiable_circuit_babe::utils::pi1_to_wots64_msg; +use verifiable_circuit_babe::verifier::BABEVerifier; + +use crate::types::BitvmGcCircuitData; + +/// Number of Wots64 digit signatures expected by the remote GOAT connector. +pub const WOTS_SIG_COUNT: usize = Wots64::TOTAL_DIGIT_LEN as usize; +pub const BABE_N_CC: usize = 181; +// TODO: use verifiable_circuit_babe::babe::M_CC instead +pub const BABE_M_CC: usize = 7; + +pub type OpenedInstanceSeeds = Vec<(usize, u64)>; +pub type FinalizedInstances = Vec; +pub type SetupAndSolderingData = (OpenedInstanceSeeds, FinalizedInstances, SolderingData); + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CACSetupPackage { + pub commits: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CACInstanceCommit { + pub epk: Vec<[[u8; 20]; 2]>, + pub wots_padding_epk: [[[u8; 20]; 2]; 4], + pub constant_commits: [[[u8; 32]; 2]; 2], + pub h_msg: [u8; 20], + pub h_ct_setup: [u8; 32], + pub com_adaptor: [u8; 32], + pub com_gc: [u8; 32], +} + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct FinalizedInstanceData { + pub index: usize, + pub final_msg_hash: [u8; 20], + pub wire_hashes: Vec, + pub gc_commitment: [u8; 32], + pub adaptor_commitment: [u8; 32], + pub ct_setup_commitment: [u8; 32], + pub real_data: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct RealFinalizedPayload { + pub gc_ciphertexts: Vec>, + pub adaptor_table: SerializableSparseAdaptorTable, + pub ct_setup: SerializableSetupCt, + pub constant_labels: [[u8; 16]; 2], + pub wots_padding_zero_labels: [[u8; 16]; 4], +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CompactFinalizedInstanceData { + pub index: usize, + pub real_data: RealFinalizedPayload, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CompactSolderingData { + pub finalized_indices: Vec, + pub proof: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CompactSolderingProofPayload { + pub opened: OpenedInstanceSeeds, + pub finalized: Vec, + pub soldering: CompactSolderingData, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SerializableSetupCt { + pub ct2_r_delta_g2: Vec, + pub ct3_masked_msg: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SerializableSparseAdaptorTable { + pub entries: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SerializableSparseAdaptorEntry { + pub x: SerializableSparseAdaptorRow, + pub y: SerializableSparseAdaptorRow, + pub z: SerializableSparseAdaptorRow, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SerializableSparseAdaptorRow { + pub cts: Vec<[u8; 32]>, + pub offset: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct SolderingData { + pub finalized_indices: Vec, + pub soldered_output: SolderedLabelsData, + pub proof: Vec, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)] +pub struct SolderedLabelsData { + pub base_commitment: Vec<([u8; 32], [u8; 32])>, + pub deltas: Vec>, + pub commitments: Vec>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BabeVerifierPrivateState { + pub instance_seeds: Vec, + pub temp_val: [u8; 32], + pub statement_digest: [u8; 32], +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BabeVerifierState { + pub package: CACSetupPackage, + pub finalized_indices: Vec, + pub verifier_pubkey: bitcoin::PublicKey, +} + +#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BabeProverState { + pub package: CACSetupPackage, + pub finalized: Vec, + pub soldering: SolderingData, + pub h_msgs: Vec<[u8; 20]>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BabeAssertWitness { + pub pi1: Vec, + pub wots_sig: Vec<[u8; 21]>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BabeChallengeAssertWitness { + pub verifier_index: usize, + pub input_labels: Vec<[u8; 16]>, + pub wots_sig: Vec<[u8; 21]>, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct BabeWronglyChallengedWitness { + pub verifier_index: usize, + pub final_msgs: Vec>, +} + +impl CACInstanceCommit { + /// Builds deterministic placeholder setup commitments for tests and wiring. + pub fn sample(seed: u8) -> Self { + let epk = (0..LAMPORT_N) + .map(|wire| [hash20(&[seed, wire as u8, 0]), hash20(&[seed, wire as u8, 1])]) + .collect(); + Self { + epk, + wots_padding_epk: padding_wire_hashes(), + constant_commits: [ + [hash32(&[seed, 0xf0, 0]), hash32(&[seed, 0xf0, 1])], + [hash32(&[seed, 0xf1, 0]), hash32(&[seed, 0xf1, 1])], + ], + h_msg: hash20(&[seed, 0xa0]), + h_ct_setup: hash32(&[seed, 0xa1]), + com_adaptor: hash32(&[seed, 0xa2]), + com_gc: hash32(&[seed, 0xa3]), + } + } +} + +impl FinalizedInstanceData { + /// Builds deterministic placeholder finalized data for tests and graph wiring. + pub fn sample(index: usize) -> Self { + let seed = index as u8; + let wire_hashes = (0..INPUT_WIRE_NUM) + .map(|wire| WireHash { + true_label_hash: hash20(&[seed, wire as u8, 1]), + false_label_hash: hash20(&[seed, wire as u8, 0]), + }) + .collect(); + Self { + index, + final_msg_hash: hash20(&[seed, 0xb0]), + wire_hashes, + gc_commitment: hash32(&[seed, 0xb1]), + adaptor_commitment: hash32(&[seed, 0xb2]), + ct_setup_commitment: hash32(&[seed, 0xb3]), + real_data: None, + } + } +} + +impl CompactFinalizedInstanceData { + pub fn try_from_finalized(finalized: &FinalizedInstanceData) -> Result { + let real_data = finalized.real_data.clone().ok_or_else(|| { + anyhow::anyhow!("finalized index {} lacks real BABE payload", finalized.index) + })?; + Ok(Self { index: finalized.index, real_data }) + } +} + +impl SolderingData { + /// Builds deterministic placeholder soldering data for the selected finalized indices. + pub fn sample(finalized_indices: Vec) -> Self { + Self { finalized_indices, soldered_output: SolderedLabelsData::default(), proof: vec![] } + } +} + +/// Builds a deterministic placeholder CAC setup package with `n_cc` instances. +pub fn build_setup_package(n_cc: usize) -> Result { + if n_cc == 0 { + bail!("n_cc must be greater than zero"); + } + Ok(CACSetupPackage { + commits: (0..n_cc).map(|index| CACInstanceCommit::sample(index as u8)).collect(), + }) +} + +/// Builds a real random BABE/CAC setup package bound to the supplied Groth16 statement. +pub fn build_real_setup_package( + n_cc: usize, + vk: &Groth16VerifyingKey, + public_inputs: &[Fr], +) -> Result<(CACSetupPackage, BabeVerifierPrivateState)> { + if n_cc == 0 { + bail!("n_cc must be greater than zero"); + } + ensure_real_gc_assets_configured()?; + let verifier = catch_unwind(AssertUnwindSafe(|| BABEVerifier::new(n_cc, vk, public_inputs))) + .map_err(|_| anyhow::anyhow!("real BABE verifier setup panicked while loading GC assets"))? + .map_err(anyhow::Error::msg)?; + let package = from_real_package(&verifier.commit()); + let private_state = BabeVerifierPrivateState { + instance_seeds: verifier.instances.iter().map(|instance| instance.seed).collect(), + temp_val: verifier.temp_val, + statement_digest: statement_digest(vk, public_inputs)?, + }; + + Ok((package, private_state)) +} + +/// Reconstructs the real Verifier instances and creates CAC opening/soldering output data. +pub fn open_real_setup_and_solder( + soldering_builder: &BabeBundleBuilder, + private_state: &BabeVerifierPrivateState, + package: &CACSetupPackage, + finalized_indices: &[usize], + vk: &Groth16VerifyingKey, + public_inputs: &[Fr], +) -> Result { + ensure_real_gc_assets_configured()?; + if private_state.statement_digest != statement_digest(vk, public_inputs)? { + bail!("BABE setup statement does not match persisted verifier state"); + } + validate_finalized_indices(package, finalized_indices)?; + let verifier = restore_real_verifier(private_state, vk, public_inputs)?; + if from_real_package(&verifier.commit()) != *package { + bail!("persisted BABE verifier state does not reproduce setup package"); + } + let bundle = soldering_builder + .babe_verifier_open_and_solder(&verifier, finalized_indices) + .map_err(anyhow::Error::msg)?; + Ok(( + bundle.opened, + bundle + .finalized + .iter() + .map(|data| from_real_finalized(data, package)) + .collect::>>()?, + from_real_soldering(&bundle.soldering)?, + )) +} + +/// Verifies real CAC openings, commitments, and the native Ziren soldering proof. +pub fn verify_real_setup( + soldering_builder: &BabeBundleBuilder, + package: &CACSetupPackage, + opened: &[(usize, u64)], + finalized: &[FinalizedInstanceData], + soldering: &SolderingData, + vk: &Groth16VerifyingKey, + public_inputs: &[Fr], +) -> Result<()> { + let real_package = to_real_package(package); + let real_finalized = finalized + .iter() + .map(to_real_finalized) + .collect::>>()?; + + let bundle = BabeBundle { + opened: opened.to_vec(), + finalized: real_finalized, + soldering: to_real_soldering(soldering)?, + temp_hashlock: [0u8; 20], + }; + soldering_builder + .babe_prover_verify_setup(&real_package, &bundle, vk, public_inputs) + .map_err(anyhow::Error::msg) +} + +/// Removes setup-derived public fields from the Verifier-to-Operator soldering proof payload. +pub fn compact_soldering_proof_payload( + opened: &[(usize, u64)], + finalized: &[FinalizedInstanceData], + soldering: &SolderingData, +) -> Result { + Ok(CompactSolderingProofPayload { + opened: opened.to_vec(), + finalized: finalized + .iter() + .map(CompactFinalizedInstanceData::try_from_finalized) + .collect::>>()?, + soldering: CompactSolderingData { + finalized_indices: soldering.finalized_indices.clone(), + proof: soldering.proof.clone(), + }, + }) +} + +/// Reconstructs the full BABE setup data using the locally trusted setup package. +pub fn expand_compact_soldering_proof_payload( + package: &CACSetupPackage, + payload: CompactSolderingProofPayload, +) -> Result { + let finalized = payload + .finalized + .into_iter() + .map(|data| expand_compact_finalized_instance(package, data)) + .collect::>>()?; + Ok((payload.opened, finalized, expand_compact_soldering_data(payload.soldering)?)) +} + +/// Derives finalized circuit indices using the real BABE/CAC Fiat-Shamir selection. +pub fn derive_finalized_indices(package: &CACSetupPackage, m_cc: usize) -> Result> { + let n_cc = package.commits.len(); + if m_cc == 0 || m_cc > n_cc { + bail!("invalid m_cc {m_cc} for n_cc {n_cc}"); + } + Ok(cac_finalize_indices(&to_real_package(package), m_cc)) +} + +/// Opens non-finalized placeholder instances and returns finalized data plus soldering data. +pub fn open_and_solder( + package: &CACSetupPackage, + finalized_indices: &[usize], +) -> Result { + let finalized_set = finalized_indices.iter().copied().collect::>(); + if finalized_set.len() != finalized_indices.len() { + bail!("duplicate finalized index"); + } + if finalized_indices.iter().any(|index| *index >= package.commits.len()) { + bail!("finalized index out of range"); + } + let opened = (0..package.commits.len()) + .filter(|index| !finalized_set.contains(index)) + .map(|index| (index, deterministic_seed(index))) + .collect::>(); + let finalized = + finalized_indices.iter().map(|index| FinalizedInstanceData::sample(*index)).collect(); + let soldering = SolderingData::sample(finalized_indices.to_vec()); + Ok((opened, finalized, soldering)) +} + +/// Validates placeholder opened, finalized, and soldering data consistency. +pub fn verify_setup( + package: &CACSetupPackage, + opened: &[(usize, u64)], + finalized: &[FinalizedInstanceData], + soldering: &SolderingData, +) -> Result<()> { + let n_cc = package.commits.len(); + let finalized_set = finalized.iter().map(|data| data.index).collect::>(); + if finalized_set.len() != finalized.len() { + bail!("duplicate finalized data"); + } + for (index, seed) in opened { + if *index >= n_cc { + bail!("opened index {index} out of range"); + } + if finalized_set.contains(index) { + bail!("index {index} cannot be both opened and finalized"); + } + if *seed != deterministic_seed(*index) { + bail!("opened seed mismatch for index {index}"); + } + } + if soldering.finalized_indices != finalized.iter().map(|data| data.index).collect::>() { + bail!("soldering finalized indices mismatch"); + } + for data in finalized { + if data.index >= n_cc { + bail!("finalized index {} out of range", data.index); + } + if data.wire_hashes.len() != INPUT_WIRE_NUM { + bail!("finalized index {} has invalid wire hash count", data.index); + } + } + Ok(()) +} + +/// Extracts one graph slot owned by `verifier_pubkey` from finalized setup data. +pub fn extract_gc_circuit_data( + finalized: &[FinalizedInstanceData], + soldering: &SolderingData, + verifier_pubkey: bitcoin::PublicKey, +) -> Result { + if finalized.len() != BABE_M_CC { + bail!("each verifier must contribute exactly {BABE_M_CC} finalized BABE instances"); + } + if soldering.finalized_indices != finalized.iter().map(|data| data.index).collect::>() { + bail!("soldering finalized indices mismatch"); + } + let data = &finalized[0]; + let wire_hashes: [WireHash; INPUT_WIRE_NUM] = + data.wire_hashes.clone().try_into().map_err(|wire_hashes: Vec| { + anyhow::anyhow!( + "BABE input label count {} is incompatible with GOAT verifier connector wire count {INPUT_WIRE_NUM}", + wire_hashes.len() + ) + })?; + Ok(BitvmGcCircuitData { + verifier_pubkey, + final_msg_hashlocks: finalized.iter().map(|data| data.final_msg_hash).collect(), + wire_hashes, + }) +} + +/// Builds the native BABE assertion witness from the validated wrapper Groth16 proof. +pub fn build_assert_witness( + proof: &ark_groth16::Proof, + assert_secret_key: &OperatorAssertSecretKey, +) -> Result { + if assert_secret_key.is_empty() { + bail!("operator WOTS secret key must not be empty"); + } + let real_witness = babe_prover_assert(proof, assert_secret_key); + Ok(BabeAssertWitness { pi1: real_witness.pi1, wots_sig: real_witness.wots_sig.to_vec() }) +} + +pub fn assert_wots_message(assert_witness: &BabeAssertWitness) -> Result<[u8; 64]> { + let pi1 = ark_bn254::G1Affine::deserialize_compressed(assert_witness.pi1.as_slice()) + .map_err(|error| anyhow::anyhow!("invalid BABE pi1 in assert witness: {error}"))?; + Ok(pi1_to_wots64_msg(&pi1)) +} + +/// Builds a placeholder verifier challenge witness from an assert witness. +pub fn build_challenge_assert_witness( + verifier_state: &BabeVerifierState, + assert_witness: &BabeAssertWitness, + verifier_index: usize, +) -> Result { + if verifier_state.finalized_indices.len() != BABE_M_CC { + bail!("verifier state must contain exactly {BABE_M_CC} finalized BABE instances"); + } + if assert_witness.pi1.is_empty() || assert_witness.wots_sig.is_empty() { + bail!("invalid assert witness"); + } + Ok(BabeChallengeAssertWitness { + verifier_index, + input_labels: (0usize..INPUT_WIRE_NUM) + .map(|index| hash16_with_index(&assert_witness.pi1, index)) + .collect(), + wots_sig: assert_witness.wots_sig.clone(), + }) +} + +/// Verifies a native operator assertion and reveals the real base-instance labels. +#[allow(clippy::too_many_arguments)] +pub fn build_real_challenge_assert_witness( + private_state: &BabeVerifierPrivateState, + package: &CACSetupPackage, + finalized_indices: &[usize], + vk: &Groth16VerifyingKey, + public_inputs: &[Fr], + operator_wots_pubkey: &OperatorAssertPublicKey, + assert_witness: &BabeAssertWitness, + verifier_index: usize, +) -> Result { + if finalized_indices.len() != BABE_M_CC { + bail!("verifier state must contain exactly {BABE_M_CC} finalized BABE instances"); + } + let verifier = restore_real_verifier(private_state, vk, public_inputs)?; + if from_real_package(&verifier.commit()) != *package { + bail!("persisted BABE verifier state does not reproduce setup package"); + } + let real_assert_witness = TxAssertWitness { + pi1: assert_witness.pi1.clone(), + wots_sig: to_real_wots_sig(&assert_witness.wots_sig)?, + }; + let real_verifier_state = VerifierSetupState { + verifier, + package: to_real_package(package), + finalized_indices: finalized_indices.to_vec(), + wots_pk_p: *operator_wots_pubkey, + presigs_p: babe_prover_presign(), + }; + let real_witness = babe_verifier_challenge_assert_cac( + &real_assert_witness, + &real_verifier_state, + BabeBtcSig::ProverPresigChallengeAssert, + ) + .ok_or_else(|| anyhow::anyhow!("operator BABE assertion WOTS signature is invalid"))?; + Ok(BabeChallengeAssertWitness { + verifier_index, + input_labels: real_witness.input_labels, + wots_sig: real_witness.wots_sig.to_vec(), + }) +} + +/// Builds a wrongly-challenged witness from every recovered finalized-message preimage. +pub fn build_wrongly_challenged_witness( + prover_state: &BabeProverState, + challenge_witness: &BabeChallengeAssertWitness, + final_msgs: Vec>, +) -> Result { + build_wrongly_challenged_witness_from_preimages( + &prover_state.h_msgs, + challenge_witness, + final_msgs, + ) +} + +/// Evaluates the native BABE garbled circuit and returns all finalized hashlock preimages. +pub fn recover_real_wrongly_challenged_witness( + prover_state: &BabeProverState, + challenge_witness: &BabeChallengeAssertWitness, + proof: &ark_groth16::Proof, +) -> Result { + let real_state = ProverSetupState { + wots_sk_p: vec![], + finalized: prover_state + .finalized + .iter() + .map(to_real_finalized) + .collect::>>()?, + soldering: to_real_soldering(&prover_state.soldering)?, + h_msgs: prover_state.h_msgs.clone(), + presigs_v: babe_verifier_presign(), + }; + let real_challenge = RealTxChallengeAssertWitness { + input_labels: challenge_witness.input_labels.clone(), + wots_sig: to_real_wots_sig(&challenge_witness.wots_sig)?, + sig_v: BabeBtcSig::VerifierLiveSig, + sig_p: BabeBtcSig::ProverPresigChallengeAssert, + }; + recover_all_finalized_messages( + prover_state, + challenge_witness, + proof, + &real_state, + &real_challenge, + ) +} + +/// Builds a wrongly-challenged witness after validating all finalized preimages. +pub fn build_wrongly_challenged_witness_from_preimages( + h_msgs: &[[u8; 20]], + challenge_witness: &BabeChallengeAssertWitness, + final_msgs: Vec>, +) -> Result { + if h_msgs.len() != BABE_M_CC { + bail!("wrongly challenged setup must contain exactly {BABE_M_CC} finalized hashlocks"); + } + if final_msgs.len() != h_msgs.len() { + bail!("wrongly challenged witness must provide every finalized preimage"); + } + for (position, (final_msg, expected_hash)) in final_msgs.iter().zip(h_msgs).enumerate() { + if label_hash(final_msg) != *expected_hash { + bail!("message at finalized position {position} is not a valid preimage"); + } + } + Ok(BabeWronglyChallengedWitness { + verifier_index: challenge_witness.verifier_index, + final_msgs, + }) +} + +fn ensure_real_gc_assets_configured() -> Result<()> { + for name in ["GC_GATES_PATH", "GC_INDICES_PATH"] { + let path = PathBuf::from( + std::env::var(name) + .map_err(|_| anyhow::anyhow!("{name} is required for real BABE setup"))?, + ); + if !path.is_file() { + bail!("{name} does not point to a readable file: {}", path.display()); + } + } + Ok(()) +} + +fn statement_digest(vk: &Groth16VerifyingKey, public_inputs: &[Fr]) -> Result<[u8; 32]> { + let mut bytes = Vec::new(); + vk.serialize_compressed(&mut bytes)?; + for public_input in public_inputs { + public_input.serialize_compressed(&mut bytes)?; + } + Ok(hash32(&bytes)) +} + +fn restore_real_verifier( + state: &BabeVerifierPrivateState, + vk: &Groth16VerifyingKey, + public_inputs: &[Fr], +) -> Result { + let instances = state + .instance_seeds + .par_iter() + .map(|seed| { + let mut instance = BABEInstance::new_from_seed(*seed); + instance.enc_setup(vk, public_inputs).map_err(anyhow::Error::msg)?; + Ok(instance) + }) + .collect::>>()?; + Ok(BABEVerifier { instances, temp_val: state.temp_val }) +} + +fn validate_finalized_indices( + package: &CACSetupPackage, + finalized_indices: &[usize], +) -> Result<()> { + let finalized_set = finalized_indices.iter().copied().collect::>(); + if finalized_set.len() != finalized_indices.len() { + bail!("duplicate finalized index"); + } + if finalized_indices.is_empty() { + bail!("at least one finalized index is required"); + } + if finalized_indices.iter().any(|index| *index >= package.commits.len()) { + bail!("finalized index out of range"); + } + Ok(()) +} + +fn from_real_package(package: &RealCACSetupPackage) -> CACSetupPackage { + CACSetupPackage { commits: package.commits.iter().map(from_real_commit).collect() } +} + +fn from_real_commit(commit: &RealCACInstanceCommit) -> CACInstanceCommit { + CACInstanceCommit { + epk: commit.epk.clone(), + wots_padding_epk: padding_wire_hashes(), + constant_commits: commit.constant_commits, + h_msg: commit.h_msg, + h_ct_setup: commit.h_ct_setup, + com_adaptor: commit.com_adaptor, + com_gc: commit.com_gc, + } +} + +fn to_real_package(package: &CACSetupPackage) -> RealCACSetupPackage { + RealCACSetupPackage { + commits: package + .commits + .iter() + .map(|commit| RealCACInstanceCommit { + epk: commit.epk.clone(), + constant_commits: commit.constant_commits, + h_msg: commit.h_msg, + h_ct_setup: commit.h_ct_setup, + com_adaptor: commit.com_adaptor, + com_gc: commit.com_gc, + }) + .collect(), + } +} + +fn from_real_finalized( + finalized: &RealFinalizedInstanceData, + package: &CACSetupPackage, +) -> Result { + let real_data = RealFinalizedPayload { + gc_ciphertexts: finalized + .gc_ciphertexts + .iter() + .map(|ciphertext| ciphertext.map(|label| label.0)) + .collect(), + adaptor_table: from_real_adaptor_table(&finalized.adaptor_table), + ct_setup: SerializableSetupCt { + ct2_r_delta_g2: finalized.ct_setup.ct2_r_delta_g2.clone(), + ct3_masked_msg: finalized.ct_setup.ct3_masked_msg.clone(), + }, + constant_labels: [finalized.constant_labels[0].0, finalized.constant_labels[1].0], + wots_padding_zero_labels: [[0u8; 16]; 4], + }; + expand_compact_finalized_instance( + package, + CompactFinalizedInstanceData { index: finalized.index, real_data }, + ) +} + +fn expand_compact_finalized_instance( + package: &CACSetupPackage, + finalized: CompactFinalizedInstanceData, +) -> Result { + let commit = package + .commits + .get(finalized.index) + .ok_or_else(|| anyhow::anyhow!("finalized index {} out of range", finalized.index))?; + if commit.epk.len() != LAMPORT_N { + bail!( + "finalized index {} has {} BABE input commitments; expected {LAMPORT_N}", + finalized.index, + commit.epk.len() + ); + } + let mut wire_hashes = Vec::with_capacity(INPUT_WIRE_NUM); + wire_hashes.extend(commit.epk[..254].iter().map(to_wire_hash)); + wire_hashes.extend(commit.wots_padding_epk[..2].iter().map(to_wire_hash)); + wire_hashes.extend(commit.epk[254..].iter().map(to_wire_hash)); + wire_hashes.extend(commit.wots_padding_epk[2..].iter().map(to_wire_hash)); + Ok(FinalizedInstanceData { + index: finalized.index, + final_msg_hash: commit.h_msg, + wire_hashes, + gc_commitment: commit.com_gc, + adaptor_commitment: commit.com_adaptor, + ct_setup_commitment: commit.h_ct_setup, + real_data: Some(finalized.real_data), + }) +} + +fn to_real_finalized(finalized: &FinalizedInstanceData) -> Result { + let payload = finalized.real_data.as_ref().ok_or_else(|| { + anyhow::anyhow!("finalized index {} lacks real BABE payload", finalized.index) + })?; + Ok(RealFinalizedInstanceData { + index: finalized.index, + gc_ciphertexts: payload.gc_ciphertexts.iter().map(|value| value.map(S)).collect(), + adaptor_table: to_real_adaptor_table(&payload.adaptor_table)?, + ct_setup: RealSetupCt { + ct2_r_delta_g2: payload.ct_setup.ct2_r_delta_g2.clone(), + ct3_masked_msg: payload.ct_setup.ct3_masked_msg.clone(), + }, + constant_labels: [S(payload.constant_labels[0]), S(payload.constant_labels[1])], + }) +} + +fn from_real_adaptor_table(table: &RealSparseAdaptorTable) -> SerializableSparseAdaptorTable { + SerializableSparseAdaptorTable { + entries: table + .entries + .iter() + .map(|entry| SerializableSparseAdaptorEntry { + x: from_real_adaptor_row(&entry.x), + y: from_real_adaptor_row(&entry.y), + z: from_real_adaptor_row(&entry.z), + }) + .collect(), + } +} + +fn from_real_adaptor_row(row: &RealSparseAdaptorRow) -> SerializableSparseAdaptorRow { + let mut offset = Vec::new(); + row.offset.serialize_compressed(&mut offset).expect("serialize adaptor offset"); + SerializableSparseAdaptorRow { cts: row.cts.clone(), offset } +} + +fn to_real_adaptor_table(table: &SerializableSparseAdaptorTable) -> Result { + Ok(RealSparseAdaptorTable { + entries: table + .entries + .iter() + .map(|entry| { + Ok(RealSparseAdaptorEntry { + x: to_real_adaptor_row(&entry.x)?, + y: to_real_adaptor_row(&entry.y)?, + z: to_real_adaptor_row(&entry.z)?, + }) + }) + .collect::>>()?, + }) +} + +fn to_real_adaptor_row(row: &SerializableSparseAdaptorRow) -> Result { + Ok(RealSparseAdaptorRow { + cts: row.cts.clone(), + offset: Fq::deserialize_compressed(row.offset.as_slice())?, + }) +} + +fn from_real_soldering(soldering: &RealSolderingData) -> Result { + let output = soldering.soldering_proof.output().map_err(anyhow::Error::msg)?; + Ok(SolderingData { + finalized_indices: soldering.finalized_indices.clone(), + soldered_output: SolderedLabelsData { + base_commitment: output.base_commitment.clone(), + deltas: output.deltas.clone(), + commitments: output.commitments.clone(), + }, + proof: bincode::serialize(&soldering.soldering_proof.proof)?, + }) +} + +fn expand_compact_soldering_data(soldering: CompactSolderingData) -> Result { + if soldering.proof.is_empty() { + bail!("soldering proof is empty"); + } + let proof = RealSolderingProof { proof: bincode::deserialize(&soldering.proof)? }; + let output = proof.output().map_err(anyhow::Error::msg)?; + Ok(SolderingData { + finalized_indices: soldering.finalized_indices, + soldered_output: SolderedLabelsData { + base_commitment: output.base_commitment.clone(), + deltas: output.deltas.clone(), + commitments: output.commitments.clone(), + }, + proof: soldering.proof, + }) +} + +fn to_real_soldering(soldering: &SolderingData) -> Result { + if soldering.proof.is_empty() { + bail!("soldering proof is empty"); + } + Ok(RealSolderingData { + finalized_indices: soldering.finalized_indices.clone(), + soldering_proof: RealSolderingProof { proof: bincode::deserialize(&soldering.proof)? }, + }) +} + +fn recover_all_finalized_messages( + prover_state: &BabeProverState, + challenge_witness: &BabeChallengeAssertWitness, + proof: &ark_groth16::Proof, + real_state: &ProverSetupState, + real_challenge: &RealTxChallengeAssertWitness, +) -> Result { + if real_state.finalized.len() != prover_state.h_msgs.len() { + bail!("BABE prover state finalized data and hash count differ"); + } + if real_state.finalized.is_empty() { + bail!("BABE prover state has no finalized instances"); + } + + let base_input_labels = pi1_labels_from_challenge(&real_challenge.input_labels)?; + let soldered_output = &prover_state.soldering.soldered_output; + if soldered_output.base_commitment.len() != base_input_labels.len() { + bail!( + "soldering base commitment count {} does not match BABE input label count {}", + soldered_output.base_commitment.len(), + base_input_labels.len() + ); + } + + let prover = BABEProver::new(proof.clone()); + let final_msgs = real_state + .finalized + .iter() + .zip(&prover_state.h_msgs) + .enumerate() + .map(|(position, (finalized, expected_hash))| { + let input_labels = + soldered_input_labels(&base_input_labels, soldered_output, position)?; + recover_finalized_message( + &prover, + proof, + finalized, + &input_labels, + *expected_hash, + position, + ) + }) + .collect::>>()?; + + Ok(BabeWronglyChallengedWitness { + verifier_index: challenge_witness.verifier_index, + final_msgs, + }) +} + +fn pi1_labels_from_challenge(input_labels: &[[u8; 16]]) -> Result> { + if input_labels.len() == LAMPORT_N { + return Ok(input_labels.iter().copied().map(S).collect()); + } + if input_labels.len() != INPUT_WIRE_NUM { + bail!( + "challenge witness has {} input labels; expected {LAMPORT_N} or {INPUT_WIRE_NUM}", + input_labels.len() + ); + } + Ok(input_labels[..254].iter().chain(&input_labels[256..510]).copied().map(S).collect()) +} + +fn soldered_input_labels( + base_input_labels: &[S], + soldered_output: &SolderedLabelsData, + finalized_position: usize, +) -> Result> { + if finalized_position == 0 { + return Ok(base_input_labels.to_vec()); + } + let deltas = soldered_output.deltas.get(finalized_position - 1).ok_or_else(|| { + anyhow::anyhow!("missing soldering deltas for finalized position {finalized_position}") + })?; + if deltas.len() != base_input_labels.len() { + bail!( + "soldering delta count {} does not match BABE input label count {}", + deltas.len(), + base_input_labels.len() + ); + } + + Ok(base_input_labels + .iter() + .enumerate() + .map(|(wire, &base_label)| { + let (delta_false, delta_true) = deltas[wire]; + if hash32(&base_label.0) == soldered_output.base_commitment[wire].0 { + base_label ^ S(delta_false) + } else { + base_label ^ S(delta_true) + } + }) + .collect()) +} + +fn recover_finalized_message( + prover: &BABEProver, + proof: &ark_groth16::Proof, + finalized: &RealFinalizedInstanceData, + input_labels: &[S], + expected_hash: [u8; 20], + finalized_position: usize, +) -> Result> { + let mut full_labels = Vec::with_capacity(2 + input_labels.len()); + full_labels.push(finalized.constant_labels[0]); + full_labels.push(finalized.constant_labels[1]); + full_labels.extend_from_slice(input_labels); + + let (mut circuit, gc_output_indices) = verifiable_circuit_babe::gc::read_fresh_circuit(); + let ct_prove = prover.compute_ct_prove( + &mut circuit, + &gc_output_indices, + &full_labels, + &finalized.gc_ciphertexts, + &finalized.adaptor_table, + ); + let msg = BABEProver::compute_msg(proof, &ct_prove, &finalized.ct_setup) + .map_err(anyhow::Error::msg)?; + let final_msg = msg.to_vec(); + if label_hash(&final_msg) != expected_hash { + bail!( + "recovered message at finalized position {finalized_position} does not match hashlock" + ); + } + Ok(final_msg) +} + +fn to_real_wots_sig(wots_sig: &[[u8; 21]]) -> Result<::Signature> { + wots_sig.try_into().map_err(|_| { + anyhow::anyhow!( + "WOTS signature has {} digit signatures; expected {WOTS_SIG_COUNT}", + wots_sig.len() + ) + }) +} + +fn padding_wire_hashes() -> [[[u8; 20]; 2]; 4] { + let false_hash = label_hash(&vec![0u8; 16]); + let true_hash = label_hash(&vec![1u8; 16]); + [[false_hash, true_hash]; 4] +} + +fn deterministic_seed(index: usize) -> u64 { + u64::from_le_bytes(hash32(&(index as u64).to_le_bytes())[0..8].try_into().expect("8 bytes")) +} + +fn to_wire_hash(pair: &[[u8; 20]; 2]) -> WireHash { + WireHash { false_label_hash: pair[0], true_label_hash: pair[1] } +} + +fn hash20(data: &[u8]) -> [u8; 20] { + let hash = hash32(data); + hash[0..20].try_into().expect("20 bytes") +} + +fn hash16(data: &[u8]) -> [u8; 16] { + let hash = hash32(data); + hash[0..16].try_into().expect("16 bytes") +} + +fn hash16_with_index(data: &[u8], index: usize) -> [u8; 16] { + let mut bytes = Vec::with_capacity(data.len() + std::mem::size_of::()); + bytes.extend_from_slice(data); + bytes.extend_from_slice(&(index as u64).to_le_bytes()); + hash16(&bytes) +} + +fn hash32(data: &[u8]) -> [u8; 32] { + Sha256::digest(data).into() +} diff --git a/crates/bitvm-gc/src/committee/api.rs b/crates/bitvm-gc/src/committee/api.rs new file mode 100644 index 00000000..f0216602 --- /dev/null +++ b/crates/bitvm-gc/src/committee/api.rs @@ -0,0 +1,427 @@ +use anyhow::{Result, bail, ensure}; +use bitcoin::XOnlyPublicKey; +use bitcoin::{PublicKey, Transaction, key::Keypair, taproot::Signature as TaprootSignature}; +use goat::connectors::assert_connectors::ProverConnector; +use goat::connectors::connector_0::Connector0; +use goat::connectors::connector_a::ConnectorA; +use goat::connectors::connector_c::ConnectorC; +use goat::connectors::connector_d::ConnectorD; +use goat::connectors::connector_z::ConnectorZ; +use goat::contexts::base::generate_n_of_n_public_key; +use goat::transactions::base::BaseTransaction; +use goat::transactions::pre_signed_musig2::{get_nonce_message, verify_public_nonce}; +use goat::transactions::signing_musig2::generate_aggregated_nonce; +use musig2::{AggNonce, PartialSignature, PubNonce, SecNonce}; +use secp256k1::schnorr::Signature as SchnorrSignature; +use serde::{Deserialize, Serialize}; + +use crate::keys::hkdf_derive_bytes; +use crate::types::BitvmGcGraph; + +const COMMITTEE_NONCE_HKDF_SALT: &[u8] = b"bitvm-gc/committee-nonce/v1"; + +pub fn take1_pre_sign_num() -> usize { + 2 +} +pub fn take2_pre_sign_num() -> usize { + 1 +} +pub fn challenge_pre_sign_num() -> usize { + 1 +} +pub fn disprove_pre_sign_num(verifier_num: usize) -> usize { + verifier_num * 2 +} + +pub fn sign_pegin_confirm( + graph: &BitvmGcGraph, + committee_member_keypair: Keypair, + committee_member_sec_nonce: SecNonce, + committee_agg_nonce: AggNonce, +) -> Result { + let mut pegin_confirm = graph.parameters.instance_parameters.build_pegin_tx()?.1; + let committee_context = + graph.parameters.instance_parameters.get_committee_context(committee_member_keypair)?; + pegin_confirm + .sign_input_0_musig2(&committee_context, &committee_member_sec_nonce, &committee_agg_nonce) + .map_err(|e| anyhow::anyhow!("fail to sign pegin confirm {}: {e}", pegin_confirm.name())) +} + +pub fn agg_and_push_pegin_confirm_sigs( + graph: &BitvmGcGraph, + partial_sigs: Vec, + agg_nonce: &AggNonce, +) -> Result { + let mut pegin_confirm = graph.parameters.instance_parameters.build_pegin_tx()?.1; + let context = graph.parameters.instance_parameters.get_base_context(); + let agg_sig = pegin_confirm + .aggregate_input_0_musig2_signatures(&context, partial_sigs, agg_nonce) + .map_err(|e| { + anyhow::anyhow!("fail to aggregate pegin confirm {}: {e}", pegin_confirm.name()) + })?; + let connector_z = ConnectorZ::new( + graph.parameters.instance_parameters.network, + &XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey), + &graph.parameters.instance_parameters.user_info.user_xonly_pubkey, + ); + pegin_confirm.push_input_0_signature(&connector_z, agg_sig); + Ok(pegin_confirm.finalize()) +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +pub struct CommitteeMusig2Data { + pub take1: Vec, + pub take2: Vec, + pub challenge: Vec, + pub disprove: Vec, +} + +pub type CommitteeSecNonces = CommitteeMusig2Data; +pub type CommitteePubNonces = CommitteeMusig2Data; +pub type CommitteeNonceSignatures = CommitteeMusig2Data; +pub type CommitteeAggNonces = CommitteeMusig2Data; +pub type CommitteePartialSignatures = CommitteeMusig2Data; +pub type CommitteeSignatures = CommitteeMusig2Data; + +impl CommitteeMusig2Data { + pub fn validate_length(&self, verifier_num: usize) -> Result<()> { + ensure!(self.take1.len() == take1_pre_sign_num(), "invalid number of take1"); + ensure!(self.take2.len() == take2_pre_sign_num(), "invalid number of take2"); + ensure!(self.challenge.len() == challenge_pre_sign_num(), "invalid number of challenge"); + ensure!( + self.disprove.len() == disprove_pre_sign_num(verifier_num), + "invalid number of disprove" + ); + Ok(()) + } + + pub fn new_empty() -> Self { + CommitteeMusig2Data { take1: vec![], take2: vec![], challenge: vec![], disprove: vec![] } + } +} + +pub fn key_aggregation(pubkeys: &[PublicKey]) -> PublicKey { + generate_n_of_n_public_key(pubkeys).0 +} + +pub fn committee_pre_sign( + committee_member_keypair: Keypair, + committee_member_sec_nonce: CommitteeSecNonces, + committee_agg_nonce: CommitteeAggNonces, + graph: &mut BitvmGcGraph, +) -> Result { + let verifier_num = graph.verifier_asserts.len(); + committee_member_sec_nonce.validate_length(verifier_num)?; + committee_agg_nonce.validate_length(verifier_num)?; + + let committee_context = + graph.parameters.instance_parameters.get_committee_context(committee_member_keypair)?; + let mut res = CommitteePartialSignatures::new_empty(); + + { + // take-1 + let sec_nonces = committee_member_sec_nonce.take1.try_into().unwrap(); + let agg_nonces = committee_agg_nonce.take1.try_into().unwrap(); + match graph.take1.pre_sign(&committee_context, &sec_nonces, &agg_nonces) { + Ok(v) => res.take1 = v.to_vec(), + Err(e) => bail!("fail to pre-sign {}: {e}", graph.take1.name()), + }; + } + + { + // take-2 + let sec_nonces = committee_member_sec_nonce.take2.try_into().unwrap(); + let agg_nonces = committee_agg_nonce.take2.try_into().unwrap(); + match graph.take2.pre_sign(&committee_context, &sec_nonces, &agg_nonces) { + Ok(v) => res.take2 = v.to_vec(), + Err(e) => bail!("fail to pre-sign {}: {e}", graph.take2.name()), + }; + } + + { + // challenge + let sec_nonces = committee_member_sec_nonce.challenge.try_into().unwrap(); + let agg_nonces = committee_agg_nonce.challenge.try_into().unwrap(); + match graph.challenge.pre_sign(&committee_context, &sec_nonces, &agg_nonces) { + Ok(v) => res.challenge = v.to_vec(), + Err(e) => bail!("fail to pre-sign {}: {e}", graph.challenge.name()), + }; + } + + { + // disprove + let mut disprove_sigs = vec![]; + for (i, disprove_tx) in graph.disproves.iter_mut().enumerate() { + let sec_nonces = [ + committee_member_sec_nonce.disprove[i * 2].clone(), + committee_member_sec_nonce.disprove[i * 2 + 1].clone(), + ]; + let agg_nonces = [ + committee_agg_nonce.disprove[i * 2].clone(), + committee_agg_nonce.disprove[i * 2 + 1].clone(), + ]; + let sigs = disprove_tx + .pre_sign(&committee_context, &sec_nonces, &agg_nonces) + .map_err(|e| anyhow::anyhow!("fail to pre-sign {}: {e}", disprove_tx.name()))?; + disprove_sigs.extend(sigs); + } + res.disprove = disprove_sigs; + } + + Ok(res) +} + +pub fn nonce_aggregation(pub_nonces: &Vec) -> AggNonce { + generate_aggregated_nonce(pub_nonces) +} + +pub fn nonces_aggregation(pub_nonces_vec: &[CommitteePubNonces]) -> Result { + fn aggregate_field(rows: &[CommitteePubNonces], get: F) -> Result> + where + F: Fn(&CommitteePubNonces) -> &Vec, + { + if rows.is_empty() { + return Ok(Vec::new()); + } + + let expected = get(&rows[0]).len(); + + for (idx, r) in rows.iter().enumerate() { + if get(r).len() != expected { + bail!("length mismatch on row {}: expected {}, got {}", idx, expected, get(r).len()) + } + } + + Ok((0..expected) + .map(|i| { + let column: Vec = rows.iter().map(|r| get(r)[i].clone()).collect(); + nonce_aggregation(&column) + }) + .collect()) + } + + Ok(CommitteeAggNonces { + take1: aggregate_field(pub_nonces_vec, |c| &c.take1)?, + take2: aggregate_field(pub_nonces_vec, |c| &c.take2)?, + challenge: aggregate_field(pub_nonces_vec, |c| &c.challenge)?, + disprove: aggregate_field(pub_nonces_vec, |c| &c.disprove)?, + }) +} + +pub fn signature_aggregation( + partial_sigs: &Vec, + agg_nonces: &CommitteeAggNonces, + graph: &BitvmGcGraph, +) -> Result { + let context = graph.parameters.get_base_context(); + let verifier_num = graph.verifier_asserts.len(); + agg_nonces.validate_length(verifier_num)?; + for r in partial_sigs { + r.validate_length(verifier_num)?; + } + + let mut res: CommitteeSignatures = CommitteeSignatures::new_empty(); + + // take1 + let take1_agg_nonces = agg_nonces.take1.clone().try_into().unwrap(); + let mut take1_partial_sigs = [vec![], vec![]]; + partial_sigs.iter().for_each(|r| { + take1_partial_sigs[0].push(r.take1[0]); + take1_partial_sigs[1].push(r.take1[1]); + }); + match graph.take1.aggregate_pre_sigs(&context, &take1_partial_sigs, &take1_agg_nonces) { + Ok(v) => res.take1 = v.to_vec(), + Err(e) => bail!("fail to aggregate pre-sigs {}: {e}", graph.take1.name()), + }; + + // take2 + let take2_agg_nonces = agg_nonces.take2.clone().try_into().unwrap(); + let take2_partial_sigs = [partial_sigs.iter().map(|r| r.take2[0]).collect()]; + match graph.take2.aggregate_pre_sigs(&context, &take2_partial_sigs, &take2_agg_nonces) { + Ok(v) => res.take2 = v.to_vec(), + Err(e) => bail!("fail to aggregate pre-sigs {}: {e}", graph.take2.name()), + }; + + // challenge + let challenge_agg_nonces = agg_nonces.challenge.clone().try_into().unwrap(); + let challenge_partial_sigs = [partial_sigs.iter().map(|r| r.challenge[0]).collect()]; + match graph.challenge.aggregate_pre_sigs( + &context, + &challenge_partial_sigs, + &challenge_agg_nonces, + ) { + Ok(v) => res.challenge = v.to_vec(), + Err(e) => bail!("fail to aggregate pre-sigs {}: {e}", graph.challenge.name()), + }; + + // disprove + let mut disprove_sigs = vec![]; + for (i, disprove_tx) in graph.disproves.iter().enumerate() { + let _agg_nonces = + [agg_nonces.disprove[i * 2].clone(), agg_nonces.disprove[i * 2 + 1].clone()]; + let mut _partial_sigs = [vec![], vec![]]; + partial_sigs.iter().for_each(|r| { + _partial_sigs[0].push(r.disprove[i * 2]); + _partial_sigs[1].push(r.disprove[i * 2 + 1]); + }); + let sigs = disprove_tx.aggregate_pre_sigs(&context, &_partial_sigs, &_agg_nonces).map_err( + |e| anyhow::anyhow!("fail to aggregate pre-sigs {}: {e}", disprove_tx.name()), + )?; + disprove_sigs.extend(sigs); + } + res.disprove = disprove_sigs; + + Ok(res) +} + +pub fn push_committee_pre_signatures( + graph: &mut BitvmGcGraph, + sigs: &CommitteeSignatures, +) -> Result<()> { + let verifier_num = graph.verifier_asserts.len(); + if graph.committee_pre_signed { + bail!("already pre-signed by committee".to_string()) + }; + sigs.validate_length(verifier_num)?; + + let network = graph.parameters.instance_parameters.network; + let n_of_n_taproot_public_key = + XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); + let operator_taproot_public_key = XOnlyPublicKey::from(graph.parameters.operator_pubkey); + let connector_0 = Connector0::new(network, &n_of_n_taproot_public_key); + let connector_a = + ConnectorA::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); + let connector_c = ConnectorC::new( + network, + &n_of_n_taproot_public_key, + &graph.parameters.operator_wots_pubkeys, + ); + let connector_d = + ConnectorD::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); + + // take1 + graph.take1.push_pre_sigs(&connector_0, &connector_c, sigs.take1.clone().try_into().unwrap()); + + // take2 + graph.take2.push_pre_sigs(&connector_0, sigs.take2.clone().try_into().unwrap()); + + // challenge + graph.challenge.push_pre_sigs(&connector_a, sigs.challenge.clone().try_into().unwrap()); + + // disprove + for (i, disprove_tx) in graph.disproves.iter_mut().enumerate() { + let prover_connector = ProverConnector::new( + network, + n_of_n_taproot_public_key, + graph.parameters.gc_data[i].final_msg_hashlocks.clone(), + ); + disprove_tx.push_pre_sigs( + &prover_connector, + &connector_d, + sigs.disprove[i * 2..(i * 2 + 2)].try_into().unwrap(), + ); + } + + graph.committee_pre_signed = true; + Ok(()) +} + +pub fn generate_nonce_from_seed( + seed: String, + graph_index: usize, + signer_keypair: Keypair, + verifier_num: usize, +) -> (CommitteePubNonces, CommitteeSecNonces, CommitteeNonceSignatures) { + let graph_seed = hkdf_derive_bytes( + seed.as_bytes(), + COMMITTEE_NONCE_HKDF_SALT, + format!("graph/{graph_index}").as_bytes(), + 32, + ); + let mut pub_nonces = CommitteePubNonces::new_empty(); + let mut sec_nonces = CommitteeSecNonces::new_empty(); + let mut nonce_sigs = CommitteeNonceSignatures::new_empty(); + let mut index = 0; + { + // take1 + for _ in 0..take1_pre_sign_num() { + let (sec_nonce, pub_nonce, nonce_sig) = + generate_nonce(signer_keypair, &graph_seed, index); + pub_nonces.take1.push(pub_nonce); + sec_nonces.take1.push(sec_nonce); + nonce_sigs.take1.push(nonce_sig); + index += 1; + } + } + { + // take2 + for _ in 0..take2_pre_sign_num() { + let (sec_nonce, pub_nonce, nonce_sig) = + generate_nonce(signer_keypair, &graph_seed, index); + pub_nonces.take2.push(pub_nonce); + sec_nonces.take2.push(sec_nonce); + nonce_sigs.take2.push(nonce_sig); + index += 1; + } + } + { + // challenge + for _ in 0..challenge_pre_sign_num() { + let (sec_nonce, pub_nonce, nonce_sig) = + generate_nonce(signer_keypair, &graph_seed, index); + pub_nonces.challenge.push(pub_nonce); + sec_nonces.challenge.push(sec_nonce); + nonce_sigs.challenge.push(nonce_sig); + index += 1; + } + } + { + // disprove + for _ in 0..disprove_pre_sign_num(verifier_num) { + let (sec_nonce, pub_nonce, nonce_sig) = + generate_nonce(signer_keypair, &graph_seed, index); + pub_nonces.disprove.push(pub_nonce); + sec_nonces.disprove.push(sec_nonce); + nonce_sigs.disprove.push(nonce_sig); + index += 1; + } + } + (pub_nonces, sec_nonces, nonce_sigs) +} + +pub fn verify_nonce_signatures( + pubkey: &XOnlyPublicKey, + pub_nonces: &CommitteePubNonces, + nonce_sigs: &CommitteeNonceSignatures, + verifier_num: usize, +) -> Result { + pub_nonces.validate_length(verifier_num)?; + nonce_sigs.validate_length(verifier_num)?; + + fn verify_vec(pubkey: &XOnlyPublicKey, nonces: &[PubNonce], sigs: &[SchnorrSignature]) -> bool { + if nonces.len() != sigs.len() { + return false; + } + nonces.iter().zip(sigs.iter()).all(|(nonce, sig)| verify_public_nonce(sig, nonce, pubkey)) + } + + Ok(verify_vec(pubkey, &pub_nonces.take1, &nonce_sigs.take1) + && verify_vec(pubkey, &pub_nonces.take2, &nonce_sigs.take2) + && verify_vec(pubkey, &pub_nonces.challenge, &nonce_sigs.challenge) + && verify_vec(pubkey, &pub_nonces.disprove, &nonce_sigs.disprove)) +} + +pub(crate) fn generate_nonce( + signer_keypair: Keypair, + seed: &[u8], + index: usize, +) -> (SecNonce, PubNonce, SchnorrSignature) { + let nonce_seed = + hkdf_derive_bytes(seed, COMMITTEE_NONCE_HKDF_SALT, format!("nonce/{index}").as_bytes(), 32); + let nonce_seed: [u8; 32] = + nonce_seed.try_into().expect("hkdf output length is fixed to 32 bytes"); + let sec_nonce = SecNonce::build(nonce_seed).build(); + let pub_nonce = sec_nonce.public_nonce(); + let nonce_signature = signer_keypair.sign_schnorr(get_nonce_message(&pub_nonce)); + (sec_nonce, pub_nonce, nonce_signature) +} diff --git a/crates/bitvm2-ga/src/challenger/mod.rs b/crates/bitvm-gc/src/committee/mod.rs similarity index 100% rename from crates/bitvm2-ga/src/challenger/mod.rs rename to crates/bitvm-gc/src/committee/mod.rs diff --git a/crates/bitvm2-ga/src/keys.rs b/crates/bitvm-gc/src/keys.rs similarity index 90% rename from crates/bitvm2-ga/src/keys.rs rename to crates/bitvm-gc/src/keys.rs index 6aff5164..32d3a468 100644 --- a/crates/bitvm2-ga/src/keys.rs +++ b/crates/bitvm-gc/src/keys.rs @@ -2,11 +2,7 @@ use crate::committee::{ CommitteeNonceSignatures, CommitteePubNonces, CommitteeSecNonces, generate_nonce, generate_nonce_from_seed, }; - -use super::{ - operator::generate_wots_keys, - types::{OperatorWotsPublicKeys, OperatorWotsSecretKeys}, -}; +use crate::operator::generate_wots_key; use anyhow::{Context, bail}; use bitcoin::{ Network, PublicKey, @@ -18,6 +14,7 @@ use chacha20poly1305::{ ChaCha20Poly1305, KeyInit, Nonce, aead::{Aead, Payload}, }; +use goat::assert_scripts::{OperatorAssertPublicKey, OperatorAssertSecretKey}; use hex::{decode as hex_decode, encode as hex_encode}; use hkdf::Hkdf; use musig2::{PubNonce, SecNonce}; @@ -30,17 +27,20 @@ use std::time::{SystemTime, UNIX_EPOCH}; use std::{fs, io::Write, path::Path}; use uuid::Uuid; -const HKDF_SALT: &[u8] = b"bitvm2/keys/v1"; +pub type OperatorWotsSecretKey = OperatorAssertSecretKey; +pub type OperatorWotsPublicKey = OperatorAssertPublicKey; +pub type OperatorWotsKeypair = (OperatorWotsSecretKey, OperatorWotsPublicKey); + +const HKDF_SALT: &[u8] = b"bitvm-gc/keys/v1"; const BITVM_BIP32_ROOT_DOMAIN: &[u8] = b"bitvm_bip32_root"; -const PURPOSE_BITVM2_DERIVATION: u32 = 2345; +const PURPOSE_BITVM_GC_DERIVATION: u32 = 2345; const ROLE_COMMITTEE: u32 = 0; const ROLE_OPERATOR: u32 = 1; -const ROLE_CHALLENGER: u32 = 2; +// const ROLE_VERIFIER: u32 = 2; const KEY_KIND_COMMITTEE_ENVELOPE: u32 = 0; const KEY_KIND_OPERATOR_NONCE: u32 = 1; -const KEY_KIND_CHALLENGER_DISPROVE: u32 = 2; const COMMITTEE_ENVELOPE_VERSION: u8 = 1; @@ -95,7 +95,8 @@ fn operator_nonce_derivation_path(nonce: u64) -> DerivationPath { ]; let mut path = vec![ - ChildNumber::from_hardened_idx(PURPOSE_BITVM2_DERIVATION).expect("constant index is valid"), + ChildNumber::from_hardened_idx(PURPOSE_BITVM_GC_DERIVATION) + .expect("constant index is valid"), ChildNumber::from_hardened_idx(ROLE_OPERATOR).expect("constant index is valid"), ChildNumber::from_hardened_idx(KEY_KIND_OPERATOR_NONCE).expect("constant index is valid"), ]; @@ -107,16 +108,6 @@ fn operator_nonce_derivation_path(nonce: u64) -> DerivationPath { DerivationPath::from(path) } -// Path layout: m / purpose' / role' / key_kind' -fn challenger_disprove_derivation_path() -> DerivationPath { - DerivationPath::from(vec![ - ChildNumber::from_hardened_idx(PURPOSE_BITVM2_DERIVATION).expect("constant index is valid"), - ChildNumber::from_hardened_idx(ROLE_CHALLENGER).expect("constant index is valid"), - ChildNumber::from_hardened_idx(KEY_KIND_CHALLENGER_DISPROVE) - .expect("constant index is valid"), - ]) -} - // Path layout: m / purpose' / role_committee' / key_kind' / iid_0' / ... / iid_7' fn committee_kek_derivation_path(instance_id: Uuid) -> DerivationPath { let instance = instance_id.as_u128(); @@ -132,7 +123,8 @@ fn committee_kek_derivation_path(instance_id: Uuid) -> DerivationPath { ]; let mut path = vec![ - ChildNumber::from_hardened_idx(PURPOSE_BITVM2_DERIVATION).expect("constant index is valid"), + ChildNumber::from_hardened_idx(PURPOSE_BITVM_GC_DERIVATION) + .expect("constant index is valid"), ChildNumber::from_hardened_idx(ROLE_COMMITTEE).expect("constant index is valid"), ChildNumber::from_hardened_idx(KEY_KIND_COMMITTEE_ENVELOPE) .expect("constant index is valid"), @@ -343,8 +335,7 @@ impl CommitteeMasterKey { &self, instance_id: Uuid, graph_id: Uuid, - watchtower_num: usize, - assert_commit_num: usize, + verifier_num: usize, signer_keypair: Keypair, ) -> (CommitteePubNonces, CommitteeSecNonces, CommitteeNonceSignatures) { let domain = [ @@ -358,8 +349,7 @@ impl CommitteeMasterKey { nonce_seed, graph_id.as_u128() as usize, signer_keypair, - watchtower_num, - assert_commit_num, + verifier_num, ) } @@ -391,13 +381,10 @@ impl OperatorMasterKey { .expect("valid derivation path should derive child key"); Keypair::from_secret_key(SECP256K1, &child.private_key) } - pub fn wots_keypair_for_graph( - &self, - graph_id: Uuid, - ) -> (OperatorWotsSecretKeys, OperatorWotsPublicKeys) { + pub fn wots_keypair_for_graph(&self, graph_id: Uuid) -> OperatorWotsKeypair { let domain = [b"operator_bitvm_wots_key".to_vec(), graph_id.as_bytes().to_vec()].concat(); - let wot_seed = derive_secret(&self.0, &domain); - generate_wots_keys(&wot_seed) + let key_seed = derive_secret(&self.0, &domain); + generate_wots_key(&key_seed) } pub fn preimage_for_graph(&self, graph_id: Uuid, index: usize) -> Vec { let domain = [ @@ -410,22 +397,14 @@ impl OperatorMasterKey { } } -pub struct ChallengerMasterKey(Keypair); -impl ChallengerMasterKey { +pub struct VerifierMasterKey(Keypair); +impl VerifierMasterKey { pub fn new(inner: Keypair) -> Self { - ChallengerMasterKey(inner) + VerifierMasterKey(inner) } pub fn master_keypair(&self) -> Keypair { NodeMasterKey(self.0).master_keypair() } - pub fn keypair_for_nst_disprove(&self) -> Keypair { - let root = derive_bip32_root(&self.0); - let path = challenger_disprove_derivation_path(); - let child = root - .derive_priv(SECP256K1, &path) - .expect("valid derivation path should derive child key"); - Keypair::from_secret_key(SECP256K1, &child.private_key) - } } pub struct WatchtowerMasterKey(Keypair); @@ -452,7 +431,7 @@ mod tests { fn test_envelope_path(instance_id: Uuid) -> PathBuf { let mut path = std::env::temp_dir(); - path.push(format!("bitvm2-committee-key-{instance_id}.json")); + path.push(format!("bitvm-gc-committee-key-{instance_id}.json")); path } @@ -582,6 +561,23 @@ mod tests { assert_ne!(pub_a1, pub_b, "different nonce should derive different keypairs"); } + #[test] + fn operator_wots_keypair_for_graph_is_deterministic_and_graph_scoped() { + let master = OperatorMasterKey::new(test_master_keypair("seed:test-operator-master")); + let graph_a = Uuid::new_v4(); + let graph_b = Uuid::new_v4(); + + let keypair_a1 = master.wots_keypair_for_graph(graph_a); + let keypair_a2 = master.wots_keypair_for_graph(graph_a); + let keypair_b = master.wots_keypair_for_graph(graph_b); + + assert_eq!(keypair_a1.1, keypair_a2.1, "same graph should derive same WOTS pubkey"); + assert_ne!( + keypair_a1.1, keypair_b.1, + "different graphs should derive different WOTS pubkeys" + ); + } + #[test] fn operator_nonce_derivation_path_has_expected_bip32_layout() { let nonce: u64 = 0x1122_3344_5566_7788; @@ -589,7 +585,10 @@ mod tests { let children: Vec = path.into_iter().cloned().collect(); assert_eq!(children.len(), 7, "path should be purpose/role/key_kind + 4 segments"); - assert_eq!(children[0], ChildNumber::from_hardened_idx(PURPOSE_BITVM2_DERIVATION).unwrap()); + assert_eq!( + children[0], + ChildNumber::from_hardened_idx(PURPOSE_BITVM_GC_DERIVATION).unwrap() + ); assert_eq!(children[1], ChildNumber::from_hardened_idx(ROLE_OPERATOR).unwrap()); assert_eq!(children[2], ChildNumber::from_hardened_idx(KEY_KIND_OPERATOR_NONCE).unwrap()); assert_eq!(children[3], ChildNumber::from_hardened_idx(0x1122).unwrap()); @@ -613,16 +612,4 @@ mod tests { assert_ne!(kek_a1, kek_b, "different instances should derive different kek"); assert_ne!(path_a1, path_b, "different instances should derive different path"); } - - #[test] - fn challenger_nst_disprove_keypair_is_deterministic() { - let master = ChallengerMasterKey::new(test_master_keypair("seed:test-challenger-master")); - - let keypair_1 = master.keypair_for_nst_disprove(); - let keypair_2 = master.keypair_for_nst_disprove(); - - let pub_1: PublicKey = keypair_1.public_key().into(); - let pub_2: PublicKey = keypair_2.public_key().into(); - assert_eq!(pub_1, pub_2, "challenger disprove keypair should be stable"); - } } diff --git a/crates/bitvm2-ga/src/lib.rs b/crates/bitvm-gc/src/lib.rs similarity index 77% rename from crates/bitvm2-ga/src/lib.rs rename to crates/bitvm-gc/src/lib.rs index faa1e805..3e8ed2ee 100644 --- a/crates/bitvm2-ga/src/lib.rs +++ b/crates/bitvm-gc/src/lib.rs @@ -1,13 +1,12 @@ -pub mod challenger; pub mod committee; pub mod operator; +pub mod verifier; pub mod watchtower; pub mod actors; +pub mod babe_adapter; pub mod keys; pub mod pegin; pub mod types; pub use goat::*; - -mod tests; diff --git a/crates/bitvm-gc/src/operator/api.rs b/crates/bitvm-gc/src/operator/api.rs new file mode 100644 index 00000000..6fe15e18 --- /dev/null +++ b/crates/bitvm-gc/src/operator/api.rs @@ -0,0 +1,554 @@ +use anyhow::{Result, bail}; +use bitcoin::{Address, Amount, Transaction, TxIn, key::Keypair}; +use bitcoin::{Network, OutPoint, PublicKey, Witness, XOnlyPublicKey}; +use goat::assert_scripts::{Label, OperatorAssertPublicKey, OperatorAssertSecretKey}; +use goat::connectors::assert_connectors::{ProverConnector, VerifierConnector}; +use goat::connectors::connector_0::Connector0; +use goat::connectors::connector_a::ConnectorA; +use goat::connectors::connector_b::ConnectorB; +use goat::connectors::connector_c::ConnectorC; +use goat::connectors::connector_d::ConnectorD; +use goat::connectors::kickoff_connectors::{ + ForceSkipConnector, GuardianConnector, KickoffConnector, PrekickoffConnector, +}; +use goat::connectors::watchtower_connectors::WatchtowerChallengeConnector; +use goat::constants::{CONNECTOR_A_TIMELOCK, CONNECTOR_D_TIMELOCK}; +use goat::transactions::assert::{ + DisproveTransaction, OperatorAssertTransaction, VerifierAssertTransaction, wrongly_challenged, +}; +use goat::transactions::base::{DUST_AMOUNT, Input}; +use goat::transactions::challenge::ChallengeTransaction; +use goat::transactions::kickoff::KickoffTransaction; +use goat::transactions::pre_signed::PreSignedTransaction; +use goat::transactions::prekickoff::{ + ChallengeIncompleteKickoffTransaction, ForceSkipKickoffTransaction, PrekickoffTransaction, + QuickChallengeTransaction, operator_skip_kickoff, +}; +use goat::transactions::take1::Take1Transaction; +use goat::transactions::take2::Take2Transaction; +use goat::transactions::watchtower_challenge::WatchtowerChallengeInitTransaction; +use goat::utils::num_blocks_per_network; +use goat::wots::{Wots, Wots64}; + +use crate::keys::hkdf_derive_bytes; +use crate::types::{BitvmGcGraph, BitvmGcGraphParameters}; + +const OPERATOR_WOTS_HKDF_SALT: &[u8] = b"bitvm-gc/operator-wots/v1"; + +#[allow(deprecated)] +pub fn generate_wots_key(seed: &str) -> (OperatorAssertSecretKey, OperatorAssertPublicKey) { + let sec_str = + hex::encode(hkdf_derive_bytes(seed.as_bytes(), OPERATOR_WOTS_HKDF_SALT, b"wots64/0", 64)); + let secret = Wots64::secret_from_str(&sec_str); + let public = Wots64::generate_public_key(&secret); + (secret, public) +} + +pub fn operator_presig_num() -> usize { + 6 +} + +pub fn generate_bitvm_graph(params: BitvmGcGraphParameters) -> Result { + let network = params.instance_parameters.network; + let operator_taproot_public_key = XOnlyPublicKey::from(params.operator_pubkey); + let n_of_n_taproot_public_key = + XOnlyPublicKey::from(params.instance_parameters.committee_agg_pubkey); + let watchtower_num = params.watchtower_pubkeys.len(); + let verifier_num = params.gc_data.len(); + + let (_, pegin, _) = params.instance_parameters.build_pegin_tx()?; + let pegin_txid = pegin.tx().compute_txid(); + let connector_0_input = Input { + outpoint: OutPoint { txid: pegin_txid, vout: 0 }, + amount: pegin.tx().output[0].value, + }; + + let cur_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); + let next_force_skip_connector = ForceSkipConnector::new(network, &operator_taproot_public_key); + let next_kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); + let next_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); + let cur_prekickoff = params.prekickoff_parameters.cur_prekickoff_txn.clone(); + let cur_prekickoff_txid = cur_prekickoff.tx().compute_txid(); + let cur_prekickoff_connector_input = Input { + outpoint: OutPoint { txid: cur_prekickoff_txid, vout: 2 }, + amount: cur_prekickoff.tx().output[2].value, + }; + let next_prekickoff = PrekickoffTransaction::new_for_validation( + &cur_prekickoff_connector, + &next_force_skip_connector, + &next_kickoff_connector, + &next_prekickoff_connector, + cur_prekickoff_connector_input, + params.prekickoff_parameters.replenish_fee_inputs.clone(), + params.prekickoff_parameters.replenish_fee_prev_outs.clone(), + params.prekickoff_parameters.fee_amount, + watchtower_num, + verifier_num, + ) + .map_err(|e| anyhow::anyhow!("failed to create pre-kickoff txn: {e}"))?; + let next_prekickoff_txid = next_prekickoff.tx().compute_txid(); + let next_force_skip_connector_input = Input { + outpoint: OutPoint { txid: next_prekickoff_txid, vout: 0 }, + amount: next_prekickoff.tx().output[0].value, + }; + let next_prekickoff_connector_input = Input { + outpoint: OutPoint { txid: next_prekickoff_txid, vout: 2 }, + amount: next_prekickoff.tx().output[2].value, + }; + + // kickoff + let kickoff_connector_input = Input { + outpoint: OutPoint { txid: cur_prekickoff_txid, vout: 1 }, + amount: cur_prekickoff.tx().output[1].value, + }; + let kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); + let connector_a = + ConnectorA::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); + let connector_b = ConnectorB::new(network, &operator_taproot_public_key); + let connector_c = + ConnectorC::new(network, &n_of_n_taproot_public_key, ¶ms.operator_wots_pubkeys); + let guardian_connector = GuardianConnector::new(network, &operator_taproot_public_key); + let kickoff = KickoffTransaction::new_for_validation( + &kickoff_connector, + &connector_a, + &connector_b, + &connector_c, + &guardian_connector, + &kickoff_connector_input, + watchtower_num, + verifier_num, + ) + .map_err(|e| anyhow::anyhow!("failed to create kickoff txn: {e}"))?; + let kickoff_txid = kickoff.tx().compute_txid(); + let connector_a_input = Input { + outpoint: OutPoint { txid: kickoff_txid, vout: 0 }, + amount: kickoff.tx().output[0].value, + }; + let connector_b_input = Input { + outpoint: OutPoint { txid: kickoff_txid, vout: 1 }, + amount: kickoff.tx().output[1].value, + }; + let connector_c_input = Input { + outpoint: OutPoint { txid: kickoff_txid, vout: 2 }, + amount: kickoff.tx().output[2].value, + }; + let guardian_connector_input = Input { + outpoint: OutPoint { txid: kickoff_txid, vout: 3 }, + amount: kickoff.tx().output[3].value, + }; + + // prekickoff challenge + let force_skip_kickoff = ForceSkipKickoffTransaction::new_for_validation( + &kickoff_connector, + &next_force_skip_connector, + kickoff_connector_input, + next_force_skip_connector_input.clone(), + ); + let quick_challenge = QuickChallengeTransaction::new_for_validation( + &guardian_connector, + &next_force_skip_connector, + guardian_connector_input.clone(), + next_force_skip_connector_input, + ); + let challenge_incomplete_kickoff = ChallengeIncompleteKickoffTransaction::new_for_validation( + &guardian_connector, + &next_prekickoff_connector, + guardian_connector_input.clone(), + next_prekickoff_connector_input, + ); + + // take-1 + let connector_0 = Connector0::new(network, &n_of_n_taproot_public_key); + let take1 = Take1Transaction::new_for_validation( + &connector_0, + &connector_a, + &connector_b, + &connector_c, + &guardian_connector, + connector_0_input.clone(), + connector_a_input.clone(), + connector_b_input.clone(), + connector_c_input.clone(), + guardian_connector_input.clone(), + ¶ms.operator_receive_address, + ) + .map_err(|e| anyhow::anyhow!("failed to create take-1 txn: {e}"))?; + + // challenge + let challenge = ChallengeTransaction::new_for_validation( + &connector_a, + connector_a_input, + params.challenge_amount, + ¶ms.operator_receive_address, + ); + + // watchtower-challenge + let watchtower_challenge_connectors = params + .watchtower_pubkeys + .iter() + .map(|pubkey| WatchtowerChallengeConnector::new(network, pubkey)) + .collect::>(); + let watchtower_challenge_init = WatchtowerChallengeInitTransaction::new_for_validation( + &connector_b, + &watchtower_challenge_connectors, + connector_b_input, + ) + .map_err(|e| anyhow::anyhow!("failed to create watchtower-challenge-init txn: {e}"))?; + + // prover-assert + let connector_d = + ConnectorD::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); + let verifier_connectors = params + .gc_data + .iter() + .map(|data| { + VerifierConnector::new( + network, + &n_of_n_taproot_public_key, + ¶ms.operator_wots_pubkeys, + data.wire_hashes.clone(), + ) + }) + .collect::>(); + let operator_assert = OperatorAssertTransaction::new_for_validation( + &connector_c, + &verifier_connectors, + &connector_d, + connector_c_input, + ) + .map_err(|e| anyhow::anyhow!("failed to create operator assert txn: {e}"))?; + let operator_assert_txid = operator_assert.tx().compute_txid(); + let connector_d_input = Input { + outpoint: OutPoint { txid: operator_assert_txid, vout: verifier_num as u32 }, + amount: operator_assert.tx().output[verifier_num].value, + }; + + // verifier-asserts and disproves + let mut verifier_asserts = Vec::with_capacity(verifier_num); + let mut disproves = Vec::with_capacity(verifier_num); + for (i, verifier_connector) in verifier_connectors.iter().enumerate() { + let verifier_input = Input { + outpoint: OutPoint { txid: operator_assert_txid, vout: i as u32 }, + amount: operator_assert.tx().output[i].value, + }; + let prover_connector = ProverConnector::new( + network, + n_of_n_taproot_public_key, + params.gc_data[i].final_msg_hashlocks.clone(), + ); + let verifier_assert = VerifierAssertTransaction::new_for_validation( + verifier_connector, + &prover_connector, + verifier_input, + ) + .map_err(|e| anyhow::anyhow!("failed to create verifier assert txn {i}: {e}"))?; + let prover_input = Input { + outpoint: OutPoint { txid: verifier_assert.tx().compute_txid(), vout: 0 }, + amount: verifier_assert.tx().output[0].value, + }; + let disprove = DisproveTransaction::new_for_validation( + &prover_connector, + &connector_d, + prover_input, + connector_d_input.clone(), + Vec::new(), + ) + .map_err(|e| anyhow::anyhow!("failed to create disprove txn {i}: {e}"))?; + verifier_asserts.push(verifier_assert); + disproves.push(disprove); + } + + // take-2 + let take2 = Take2Transaction::new_for_validation( + &connector_0, + &connector_d, + &guardian_connector, + connector_0_input, + connector_d_input, + guardian_connector_input, + ¶ms.operator_receive_address, + ) + .map_err(|e| anyhow::anyhow!("failed to create take-2 txn: {e}"))?; + + Ok(BitvmGcGraph { + operator_pre_signed: false, + committee_pre_signed: false, + parameters: params, + cur_prekickoff, + next_prekickoff, + force_skip_kickoff, + quick_challenge, + challenge_incomplete_kickoff, + pegin, + kickoff, + take1, + challenge, + watchtower_challenge_init, + operator_assert, + verifier_asserts, + disproves, + take2, + }) +} + +pub fn operator_pre_sign( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, +) -> Result> { + let keypair_pubkey = PublicKey::from(operator_keypair.public_key()); + if keypair_pubkey != graph.parameters.operator_pubkey { + bail!("operator keypair does not match graph operator pubkey".to_string()) + }; + + let mut wits = vec![]; + let context = graph.parameters.get_operator_context(operator_keypair)?; + let network = context.network; + let operator_taproot_public_key = context.operator_taproot_public_key; + + // presign force_skip_kickoff + let kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); + let next_force_skip_connector = ForceSkipConnector::new(network, &operator_taproot_public_key); + graph.force_skip_kickoff.pre_sign_and_push( + &context, + &kickoff_connector, + &next_force_skip_connector, + ); + wits.push(graph.force_skip_kickoff.tx().input[0].witness.clone()); + wits.push(graph.force_skip_kickoff.tx().input[1].witness.clone()); + + // presign quick_challenge + let guardian_connector = GuardianConnector::new(network, &operator_taproot_public_key); + graph.quick_challenge.pre_sign_and_push( + &context, + &guardian_connector, + &next_force_skip_connector, + ); + wits.push(graph.quick_challenge.tx().input[0].witness.clone()); + wits.push(graph.quick_challenge.tx().input[1].witness.clone()); + + // presign challenge_incomplete_kickoff + let next_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); + graph.challenge_incomplete_kickoff.pre_sign_and_push( + &context, + &guardian_connector, + &next_prekickoff_connector, + ); + wits.push(graph.challenge_incomplete_kickoff.tx().input[0].witness.clone()); + wits.push(graph.challenge_incomplete_kickoff.tx().input[1].witness.clone()); + + graph.operator_pre_signed = true; + Ok(wits) +} + +pub fn push_operator_pre_signature( + graph: &mut BitvmGcGraph, + signed_witness: &[Witness], +) -> Result<()> { + if graph.operator_pre_signed { + bail!("already pre-signed by operator".to_string()) + }; + if signed_witness.len() != operator_presig_num() { + bail!("invalid number of pre-signatures".to_string()) + }; + + graph.force_skip_kickoff.tx_mut().input[0].witness = signed_witness[0].clone(); + graph.force_skip_kickoff.tx_mut().input[1].witness = signed_witness[1].clone(); + graph.quick_challenge.tx_mut().input[0].witness = signed_witness[2].clone(); + graph.quick_challenge.tx_mut().input[1].witness = signed_witness[3].clone(); + graph.challenge_incomplete_kickoff.tx_mut().input[0].witness = signed_witness[4].clone(); + graph.challenge_incomplete_kickoff.tx_mut().input[1].witness = signed_witness[5].clone(); + + graph.operator_pre_signed = true; + Ok(()) +} + +/// remember to sign replensish inputs (if any) after this +pub fn operator_sign_prekickoff_input_0( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, +) -> Result { + let operator_context = graph.parameters.get_operator_context(operator_keypair)?; + let prev_prekickoff_connector = PrekickoffConnector::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + ); + graph.cur_prekickoff.sign_input_0(&operator_context, &prev_prekickoff_connector); + Ok(graph.cur_prekickoff.tx().clone()) +} + +pub fn operator_sign_skip_kickoff( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, + operator_receive_address: Address, + fee_rate: f64, +) -> Result> { + let operator_context = graph.parameters.get_operator_context(operator_keypair)?; + let kickoff_connector = KickoffConnector::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + ); + let kickoff_connector_input = Input { + outpoint: OutPoint { txid: graph.cur_prekickoff.tx().compute_txid(), vout: 1 }, + amount: graph.cur_prekickoff.tx().output[1].value, + }; + // create a sample tx to estimate fee + let sample_tx = operator_skip_kickoff( + &operator_context, + &kickoff_connector, + kickoff_connector_input.clone(), + Amount::ZERO, + operator_receive_address.clone(), + ) + .map_err(|e| anyhow::anyhow!("failed to create sample skip-kickoff txn: {e}"))?; + + let fee_amount = + Amount::from_sat((sample_tx.weight().to_vbytes_ceil() as f64 * fee_rate).ceil() as u64); + if fee_amount + Amount::from_sat(DUST_AMOUNT) >= kickoff_connector_input.amount { + // if fee_amount > input_amount - dust_amount, skip-kickoff tx is meaningless + return Ok(None); + } + match operator_skip_kickoff( + &operator_context, + &kickoff_connector, + kickoff_connector_input, + fee_amount, + operator_receive_address, + ) { + Ok(tx) => Ok(Some(tx)), + Err(e) => bail!("failed to create skip-kickoff txn: {e}"), + } +} + +pub fn operator_sign_kickoff( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, +) -> Result { + let operator_context = graph.parameters.get_operator_context(operator_keypair)?; + let kickoff_connector = KickoffConnector::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + ); + graph.kickoff.sign_input_0(&operator_context, &kickoff_connector); + Ok(graph.kickoff.tx().clone()) +} + +pub fn operator_sign_take1( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, +) -> Result { + if !graph.committee_pre_signed() { + bail!("missing pre-signatures from committee".to_string()) + }; + let operator_context = graph.parameters.get_operator_context(operator_keypair)?; + let connector_a = ConnectorA::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + &operator_context.n_of_n_taproot_public_key, + ); + let connector_b = + ConnectorB::new(operator_context.network, &operator_context.operator_taproot_public_key); + let guardian_connector = GuardianConnector::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + ); + graph.take1.sign_input_1(&operator_context, &connector_a); + graph.take1.sign_input_2(&operator_context, &connector_b); + graph.take1.sign_input_4(&operator_context, &guardian_connector); + Ok(graph.take1.tx().clone()) +} + +pub fn operator_sign_take2( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, +) -> Result { + if !graph.committee_pre_signed() { + bail!("missing pre-signatures from committee".to_string()) + }; + let operator_context = graph.parameters.get_operator_context(operator_keypair)?; + let connector_d = ConnectorD::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + &operator_context.n_of_n_taproot_public_key, + ); + let guardian_connector = GuardianConnector::new( + operator_context.network, + &operator_context.operator_taproot_public_key, + ); + graph.take2.sign_input_1(&operator_context, &connector_d); + graph.take2.sign_input_2(&operator_context, &guardian_connector); + Ok(graph.take2.tx().clone()) +} + +pub fn operator_sign_watchtower_challenge_init( + operator_keypair: Keypair, + graph: &mut BitvmGcGraph, +) -> Result { + let operator_context = graph.parameters.get_operator_context(operator_keypair)?; + let connector_b = + ConnectorB::new(operator_context.network, &operator_context.operator_taproot_public_key); + graph.watchtower_challenge_init.sign_input_0(&operator_context, &connector_b); + Ok(graph.watchtower_challenge_init.tx().clone()) +} + +pub fn operator_sign_assert( + graph: &mut BitvmGcGraph, + wots_secret_key: &OperatorAssertSecretKey, + proof: &[u8; 64], +) -> Result { + if Wots64::generate_public_key(wots_secret_key) != graph.parameters.operator_wots_pubkeys { + bail!("provided WOTS secret key does not match expected public key".to_string()) + }; + + let network = graph.parameters.instance_parameters.network; + let n_of_n_taproot_public_key = + bitcoin::XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); + let connector_c = ConnectorC::new( + network, + &n_of_n_taproot_public_key, + &graph.parameters.operator_wots_pubkeys, + ); + + graph + .operator_assert + .operator_commit_proof(wots_secret_key, &connector_c, proof) + .map_err(|e| anyhow::anyhow!("failed to sign operator assert: {e}"))?; + Ok(graph.operator_assert.tx().clone()) +} + +pub fn operator_sign_wrongly_challenged( + graph: &BitvmGcGraph, + verifier_index: usize, + final_msgs: &[Label], +) -> Result<(TxIn, Amount)> { + if verifier_index >= graph.verifier_asserts.len() { + bail!("invalid verifier index {verifier_index}".to_string()) + }; + + let network = graph.parameters.instance_parameters.network; + let n_of_n_taproot_public_key = + bitcoin::XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); + let prover_connector = ProverConnector::new( + network, + n_of_n_taproot_public_key, + graph.parameters.gc_data[verifier_index].final_msg_hashlocks.clone(), + ); + let input = Input { + outpoint: OutPoint { + txid: graph.verifier_asserts[verifier_index].tx().compute_txid(), + vout: 0, + }, + amount: graph.verifier_asserts[verifier_index].tx().output[0].value, + }; + + wrongly_challenged(&prover_connector, &input, final_msgs) + .map(|txin| (txin, input.amount)) + .map_err(|e| anyhow::anyhow!("failed to sign wrongly challenged: {e}")) +} + +pub fn take1_timelock(network: Network) -> u32 { + num_blocks_per_network(network, CONNECTOR_A_TIMELOCK) +} + +pub fn take2_timelock(network: Network) -> u32 { + num_blocks_per_network(network, CONNECTOR_D_TIMELOCK) +} diff --git a/crates/bitvm2-ga/src/committee/mod.rs b/crates/bitvm-gc/src/operator/mod.rs similarity index 100% rename from crates/bitvm2-ga/src/committee/mod.rs rename to crates/bitvm-gc/src/operator/mod.rs diff --git a/crates/bitvm2-ga/src/pegin.rs b/crates/bitvm-gc/src/pegin.rs similarity index 100% rename from crates/bitvm2-ga/src/pegin.rs rename to crates/bitvm-gc/src/pegin.rs diff --git a/crates/bitvm-gc/src/types.rs b/crates/bitvm-gc/src/types.rs new file mode 100644 index 00000000..077ea79b --- /dev/null +++ b/crates/bitvm-gc/src/types.rs @@ -0,0 +1,420 @@ +use std::collections::BTreeMap; + +use anyhow::{Result, bail}; +use bitcoin::{ + Address, Amount, Network, OutPoint, PrivateKey, PublicKey, TxOut, Witness, XOnlyPublicKey, + key::Keypair, taproot::LeafVersion, +}; +use goat::{ + assert_scripts::{INPUT_WIRE_NUM, LabelHash, OperatorAssertPublicKey, WireHash}, + connectors::{base::TaprootConnector, connector_0::Connector0, connector_z::ConnectorZ}, + contexts::{base::BaseContext, committee::CommitteeContext, operator::OperatorContext}, + transactions::{ + assert::{DisproveTransaction, OperatorAssertTransaction, VerifierAssertTransaction}, + base::*, + challenge::ChallengeTransaction, + kickoff::KickoffTransaction, + pegin::*, + pre_signed::PreSignedTransaction, + prekickoff::{ + ChallengeIncompleteKickoffTransaction, ForceSkipKickoffTransaction, + PrekickoffTransaction, QuickChallengeTransaction, + }, + take1::Take1Transaction, + take2::Take2Transaction, + watchtower_challenge::WatchtowerChallengeInitTransaction, + }, +}; +use secp256k1::SECP256K1; +use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; +use uuid::Uuid; + +use crate::{ + committee::{CommitteeSignatures, push_committee_pre_signatures}, + operator::{generate_bitvm_graph, push_operator_pre_signature}, +}; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct UserInfo { + pub depositor_evm_address: [u8; 20], + pub txn_fees: [u64; 3], + pub inputs: Vec, + pub user_xonly_pubkey: XOnlyPublicKey, + #[serde(with = "node_serializer::address")] + pub user_change_address: Address, + #[serde(with = "node_serializer::address")] + pub user_refund_address: Address, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct BitvmGcInstanceParameters { + pub network: Network, + pub instance_id: Uuid, + pub user_info: UserInfo, + pub pegin_amount: Amount, + pub committee_pubkeys: Vec, + pub committee_agg_pubkey: PublicKey, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct PrekickoffParameters { + pub cur_prekickoff_txn: PrekickoffTransaction, + pub replenish_fee_inputs: Vec, + pub replenish_fee_prev_outs: Vec, + pub fee_amount: u64, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct BitvmGcGraphParameters { + pub instance_parameters: BitvmGcInstanceParameters, + pub prekickoff_parameters: PrekickoffParameters, + pub graph_id: Uuid, + pub graph_nonce: u64, + pub challenge_amount: Amount, + pub operator_pubkey: PublicKey, + #[serde(with = "BigArray")] + pub operator_wots_pubkeys: OperatorAssertPublicKey, + #[serde(with = "node_serializer::address")] + pub operator_receive_address: Address, + pub watchtower_pubkeys: Vec, + pub gc_data: Vec, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct BitvmGcCircuitData { + pub verifier_pubkey: PublicKey, + pub final_msg_hashlocks: Vec, + #[serde(with = "BigArray")] + pub wire_hashes: [WireHash; INPUT_WIRE_NUM], +} + +impl BitvmGcInstanceParameters { + pub fn build_pegin_tx( + &self, + ) -> Result<(PegInDepositTransaction, PegInConfirmTransaction, PegInRefundTransaction)> { + let network = self.network; + let n_of_n_taproot_public_key = XOnlyPublicKey::from(self.committee_agg_pubkey); + let user_taproot_public_key = self.user_info.user_xonly_pubkey; + let connector_0 = Connector0::new(network, &n_of_n_taproot_public_key); + let connector_z = + ConnectorZ::new(network, &n_of_n_taproot_public_key, &user_taproot_public_key); + let pegin_message = [ + get_magic_bytes(&network), + self.instance_id.as_bytes().to_vec(), + self.user_info.depositor_evm_address.to_vec(), + ] + .concat(); + + let pegin_deposit = PegInDepositTransaction::new_unsigned( + &connector_z, + self.user_info.inputs.clone(), + self.pegin_amount + Amount::from_sat(self.user_info.txn_fees[1]), + Amount::from_sat(self.user_info.txn_fees[0]), + self.user_info.user_change_address.clone(), + ) + .map_err(|e| anyhow::anyhow!("fail to build pegin deposit txn: {e}"))?; + let deposit_outpoint = Input { + outpoint: OutPoint { txid: pegin_deposit.tx().compute_txid(), vout: 0 }, + amount: pegin_deposit.tx().output[0].value, + }; + let pegin_confirm = PegInConfirmTransaction::new_for_validation( + &connector_0, + &connector_z, + deposit_outpoint.clone(), + Amount::from_sat(self.user_info.txn_fees[1]), + pegin_message, + ) + .map_err(|e| anyhow::anyhow!("fail to build pegin confirm txn: {e}"))?; + let pegin_refund = PegInRefundTransaction::new_for_validation( + &connector_z, + deposit_outpoint, + &self.user_info.user_refund_address, + Amount::from_sat(self.user_info.txn_fees[2]), + ) + .map_err(|e| anyhow::anyhow!("fail to build pegin refund txn: {e}"))?; + + Ok((pegin_deposit, pegin_confirm, pegin_refund)) + } + + pub fn build_pegin_cancel_psbt(&self) -> Result { + let network = self.network; + let n_of_n_taproot_public_key = XOnlyPublicKey::from(self.committee_agg_pubkey); + let user_taproot_public_key = self.user_info.user_xonly_pubkey; + let connector_z = + ConnectorZ::new(network, &n_of_n_taproot_public_key, &user_taproot_public_key); + + let pegin_deposit = PegInDepositTransaction::new_unsigned( + &connector_z, + self.user_info.inputs.clone(), + self.pegin_amount + Amount::from_sat(self.user_info.txn_fees[1]), + Amount::from_sat(self.user_info.txn_fees[0]), + self.user_info.user_change_address.clone(), + ) + .map_err(|e| anyhow::anyhow!("fail to build pegin deposit txn: {e}"))?; + let deposit_outpoint = Input { + outpoint: OutPoint { txid: pegin_deposit.tx().compute_txid(), vout: 0 }, + amount: pegin_deposit.tx().output[0].value, + }; + let pegin_refund = PegInRefundTransaction::new_for_validation( + &connector_z, + deposit_outpoint.clone(), + &self.user_info.user_refund_address, + Amount::from_sat(self.user_info.txn_fees[2]), + ) + .map_err(|e| anyhow::anyhow!("fail to build pegin refund txn: {e}"))?; + + let mut psbt = bitcoin::psbt::Psbt::from_unsigned_tx(pegin_refund.tx().clone()).unwrap(); + let taproot_spend_info = connector_z.generate_taproot_spend_info(); + let mut tap_scripts = BTreeMap::new(); + let tap_script_1 = connector_z.generate_taproot_leaf_script(1); + tap_scripts.insert( + taproot_spend_info + .control_block(&(tap_script_1.clone(), LeafVersion::TapScript)) + .unwrap(), + (tap_script_1, LeafVersion::TapScript), + ); + let psbt_input_0 = bitcoin::psbt::Input { + witness_utxo: { + Some(TxOut { + value: deposit_outpoint.amount, + script_pubkey: connector_z.generate_taproot_address().script_pubkey(), + }) + }, + tap_merkle_root: taproot_spend_info.merkle_root(), + tap_internal_key: Some(n_of_n_taproot_public_key), + tap_scripts, + ..Default::default() + }; + psbt.inputs[0] = psbt_input_0; + + Ok(psbt) + } + + pub fn get_committee_context( + &self, + committee_member_keypair: Keypair, + ) -> Result { + let network = self.network; + let committee_public_key = self.committee_agg_pubkey; + let committee_taproot_public_key = XOnlyPublicKey::from(committee_public_key); + let private_key = PrivateKey::new(committee_member_keypair.secret_key(), network); + let committee_member_public_key = PublicKey::from_private_key(SECP256K1, &private_key); + if !self.committee_pubkeys.contains(&committee_member_public_key) { + bail!("The provided committee member keypair does not match any committee public key"); + } + Ok(CommitteeContext { + network, + committee_keypair: committee_member_keypair, + committee_public_key: committee_member_public_key, + n_of_n_public_keys: self.committee_pubkeys.clone(), + n_of_n_public_key: committee_public_key, + n_of_n_taproot_public_key: committee_taproot_public_key, + }) + } + + pub fn get_base_context(&self) -> BaseBitvmContext { + let network = self.network; + let n_of_n_public_keys = self.committee_pubkeys.clone(); + let n_of_n_public_key = self.committee_agg_pubkey; + let n_of_n_taproot_public_key = XOnlyPublicKey::from(n_of_n_public_key); + BaseBitvmContext { + network, + n_of_n_public_keys, + n_of_n_public_key, + n_of_n_taproot_public_key, + } + } +} + +impl BitvmGcGraphParameters { + pub fn get_operator_context(&self, operator_keypair: Keypair) -> Result { + let network = self.instance_parameters.network; + let operator_public_key = self.operator_pubkey; + let operator_taproot_public_key = XOnlyPublicKey::from(operator_public_key); + let committee_public_key = self.instance_parameters.committee_agg_pubkey; + let committee_taproot_public_key = XOnlyPublicKey::from(committee_public_key); + if operator_public_key + != PublicKey::from_private_key( + SECP256K1, + &PrivateKey::new(operator_keypair.secret_key(), network), + ) + { + bail!("The provided operator keypair does not match the operator public key"); + } + Ok(OperatorContext { + network, + operator_keypair, + operator_public_key, + operator_taproot_public_key, + + n_of_n_public_keys: self.instance_parameters.committee_pubkeys.clone(), + n_of_n_public_key: committee_public_key, + n_of_n_taproot_public_key: committee_taproot_public_key, + }) + } + + pub fn get_base_context(&self) -> BaseBitvmContext { + self.instance_parameters.get_base_context() + } +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct BitvmGcGraph { + pub(crate) operator_pre_signed: bool, + pub(crate) committee_pre_signed: bool, + pub parameters: BitvmGcGraphParameters, + + pub cur_prekickoff: PrekickoffTransaction, + pub next_prekickoff: PrekickoffTransaction, + pub force_skip_kickoff: ForceSkipKickoffTransaction, + pub quick_challenge: QuickChallengeTransaction, + pub challenge_incomplete_kickoff: ChallengeIncompleteKickoffTransaction, + + pub pegin: PegInConfirmTransaction, + pub kickoff: KickoffTransaction, + pub take1: Take1Transaction, + pub challenge: ChallengeTransaction, + pub watchtower_challenge_init: WatchtowerChallengeInitTransaction, + pub operator_assert: OperatorAssertTransaction, + pub verifier_asserts: Vec, + pub disproves: Vec, + pub take2: Take2Transaction, +} + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct SimplifiedBitvmGcGraph { + pub(crate) operator_pre_signed: bool, + pub(crate) committee_pre_signed: bool, + pub parameters: BitvmGcGraphParameters, + pub operator_pre_sigs: Option>, + pub committee_pre_sigs: Option, +} + +impl BitvmGcGraph { + pub fn operator_pre_signed(&self) -> bool { + self.operator_pre_signed + } + pub fn committee_pre_signed(&self) -> bool { + self.committee_pre_signed + } + pub fn to_simplified(&self) -> Result { + fn extract_sig_from_witness(witness: &Witness) -> Result { + witness + .nth(0) + .and_then(|data| bitcoin::taproot::Signature::from_slice(data).ok()) + .ok_or_else(|| anyhow::anyhow!("No valid signature found in witness")) + } + let operator_pre_sigs = if self.operator_pre_signed { + Some(vec![ + self.force_skip_kickoff.tx().input[0].witness.clone(), + self.force_skip_kickoff.tx().input[1].witness.clone(), + self.quick_challenge.tx().input[0].witness.clone(), + self.quick_challenge.tx().input[1].witness.clone(), + self.challenge_incomplete_kickoff.tx().input[0].witness.clone(), + self.challenge_incomplete_kickoff.tx().input[1].witness.clone(), + ]) + } else { + None + }; + let committee_pre_sigs = if self.committee_pre_signed { + let take1 = vec![ + extract_sig_from_witness(&self.take1.tx().input[0].witness)?, + extract_sig_from_witness(&self.take1.tx().input[3].witness)?, + ]; + let take2 = vec![extract_sig_from_witness(&self.take2.tx().input[0].witness)?]; + let challenge = vec![extract_sig_from_witness(&self.challenge.tx().input[0].witness)?]; + let mut disprove = Vec::new(); + for disprove_tx in &self.disproves { + disprove.push(extract_sig_from_witness(&disprove_tx.tx().input[0].witness)?); + disprove.push(extract_sig_from_witness(&disprove_tx.tx().input[1].witness)?); + } + Some(CommitteeSignatures { take1, take2, challenge, disprove }) + } else { + None + }; + Ok(SimplifiedBitvmGcGraph { + operator_pre_signed: self.operator_pre_signed, + committee_pre_signed: self.committee_pre_signed, + parameters: self.parameters.clone(), + operator_pre_sigs, + committee_pre_sigs, + }) + } + pub fn from_simplified(simplified: &SimplifiedBitvmGcGraph) -> Result { + let mut graph = generate_bitvm_graph(simplified.parameters.clone())?; + if simplified.operator_pre_signed { + let operator_pre_sigs = simplified + .operator_pre_sigs + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Missing operator pre signatures"))?; + push_operator_pre_signature(&mut graph, operator_pre_sigs)?; + graph.operator_pre_signed = true; + } + if simplified.committee_pre_signed { + let committee_pre_sigs = simplified + .committee_pre_sigs + .as_ref() + .ok_or_else(|| anyhow::anyhow!("Missing committee pre signatures"))?; + push_committee_pre_signatures(&mut graph, committee_pre_sigs)?; + graph.committee_pre_signed = true; + } + Ok(graph) + } +} + +pub struct BaseBitvmContext { + pub network: Network, + pub n_of_n_public_keys: Vec, + pub n_of_n_public_key: PublicKey, + pub n_of_n_taproot_public_key: XOnlyPublicKey, +} + +impl BaseContext for BaseBitvmContext { + fn network(&self) -> Network { + self.network + } + fn n_of_n_public_keys(&self) -> &Vec { + &self.n_of_n_public_keys + } + fn n_of_n_public_key(&self) -> &PublicKey { + &self.n_of_n_public_key + } + fn n_of_n_taproot_public_key(&self) -> &XOnlyPublicKey { + &self.n_of_n_taproot_public_key + } +} + +pub fn get_magic_bytes(net: &Network) -> Vec { + match net { + Network::Bitcoin => hex::encode(b"GTV6").as_bytes().to_vec(), + _ => hex::encode(b"GTT6").as_bytes().to_vec(), + } +} + +pub mod node_serializer { + use bitcoin::Address; + use serde::{Deserialize, Deserializer, Serializer}; + use std::str::FromStr; + + pub mod address { + use super::*; + + pub fn serialize(addr: &Address, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&addr.to_string()) + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Address::from_str(&s) + .map(|addr| addr.assume_checked()) + .map_err(serde::de::Error::custom) + } + } +} diff --git a/crates/bitvm-gc/src/verifier/api.rs b/crates/bitvm-gc/src/verifier/api.rs new file mode 100644 index 00000000..b5a25827 --- /dev/null +++ b/crates/bitvm-gc/src/verifier/api.rs @@ -0,0 +1,187 @@ +use crate::types::BitvmGcGraph; +use anyhow::{Result, bail}; +use bitcoin::{Address, Amount, Network, Transaction, TxIn, TxOut, XOnlyPublicKey}; +use goat::{ + assert_scripts::{INPUT_WIRE_NUM, Label}, + connectors::{assert_connectors::VerifierConnector, connector_c::ConnectorC}, + constants::PROVER_CONNECTOR_TIMELOCK, + scripts::{generate_opreturn_script, p2a_output}, + transactions::{base::DUST_AMOUNT, pre_signed::PreSignedTransaction}, + utils::num_blocks_per_network, +}; + +/// challenge has a pre-signed SinglePlusAnyoneCanPay input and output +/// get incomplete tx here, add inputs with enough amount, then broadcast it to start challnege progress +pub fn export_challenge_tx(graph: &BitvmGcGraph) -> Result<(Transaction, Amount)> { + if !graph.committee_pre_signed() { + bail!("missing pre-signatures from committee") + }; + Ok((graph.challenge.tx().clone(), graph.challenge.challenge_amount)) +} + +/// return true if anchor output is added +/// return false if change output is added or no output is added +fn add_change_or_anchor_output( + tx: &mut Transaction, + total_input_amount: Amount, + change_address: Address, + fee_rate: f64, +) -> Result { + let dust_amount = Amount::from_sat(DUST_AMOUNT); + let output_amount = tx.output.iter().map(|o| o.value).sum(); + tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change_address.script_pubkey() }); + let min_relay_fee = 1.0; + let min_fee_amount = + Amount::from_sat((tx.weight().to_vbytes_ceil() as f64 * min_relay_fee).ceil() as u64); + let fee_amount = + Amount::from_sat((tx.weight().to_vbytes_ceil() as f64 * fee_rate).ceil() as u64); + if min_fee_amount + dust_amount + output_amount > total_input_amount { + bail!("insufficient input amount to cover min relay fee"); + } + if fee_amount + output_amount + dust_amount < total_input_amount { + // add change output + let change_amount = total_input_amount - fee_amount - output_amount; + tx.output.last_mut().unwrap().value = change_amount; + Ok(false) + } else if fee_amount + output_amount > total_input_amount { + // add anchor output + tx.output.pop(); + tx.output.push(p2a_output()); + Ok(true) + } else { + // not add any output since remaining is just enough to cover fee + tx.output.pop(); + Ok(false) + } +} + +/// return (tx, true) if anchor output is added, subsequently challenger need to cover fee via CPFP +/// return (tx, false) if change output is added or no output is added, challenger can directly broadcast it +pub fn build_force_skip_kickoff_tx( + graph: &BitvmGcGraph, + verifier_receive_address: Address, + fee_rate: f64, +) -> Result<(Transaction, bool)> { + if !graph.operator_pre_signed() { + bail!("missing pre-signatures from operator") + }; + let mut tx = graph.force_skip_kickoff.tx().clone(); + let total_input_amount = graph.force_skip_kickoff.prev_outs().iter().map(|o| o.value).sum(); + let anchor_added = add_change_or_anchor_output( + &mut tx, + total_input_amount, + verifier_receive_address, + fee_rate, + )?; + Ok((tx, anchor_added)) +} + +/// return (tx, true) if anchor output is added, subsequently challenger need to cover fee via CPFP +/// return (tx, false) if change output is added or no output is added, challenger can directly broadcast it +pub fn build_quick_challenge_tx( + graph: &BitvmGcGraph, + verifier_receive_address: Address, + fee_rate: f64, +) -> Result<(Transaction, bool)> { + if !graph.operator_pre_signed() { + bail!("missing pre-signatures from operator") + }; + let mut tx = graph.quick_challenge.tx().clone(); + let total_input_amount = graph.quick_challenge.prev_outs().iter().map(|o| o.value).sum(); + let anchor_added = add_change_or_anchor_output( + &mut tx, + total_input_amount, + verifier_receive_address, + fee_rate, + )?; + Ok((tx, anchor_added)) +} + +/// return (tx, true) if anchor output is added, subsequently challenger need to cover fee via CPFP +/// return (tx, false) if change output is added or no output is added, challenger can directly broadcast it +pub fn build_challenge_incomplete_kickoff_tx( + graph: &BitvmGcGraph, + verifier_receive_address: Address, + fee_rate: f64, +) -> Result<(Transaction, bool)> { + if !graph.operator_pre_signed() { + bail!("missing pre-signatures from operator") + }; + let mut tx = graph.challenge_incomplete_kickoff.tx().clone(); + let total_input_amount = + graph.challenge_incomplete_kickoff.prev_outs().iter().map(|o| o.value).sum(); + let anchor_added = add_change_or_anchor_output( + &mut tx, + total_input_amount, + verifier_receive_address, + fee_rate, + )?; + Ok((tx, anchor_added)) +} + +pub fn verify_prover_assertion(_graph: &BitvmGcGraph, _operator_assert_txin: TxIn) -> Result { + todo!("verify operator assertion") +} + +pub fn build_verifier_assert_tx( + graph: &BitvmGcGraph, + operator_assert_txin: TxIn, + verifier_index: usize, + labels: [Label; INPUT_WIRE_NUM], +) -> Result { + if verifier_index >= graph.verifier_asserts.len() { + bail!("invalid verifier index {verifier_index}") + }; + + let network = graph.parameters.instance_parameters.network; + let n_of_n_taproot_public_key = + XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); + let connector_c = ConnectorC::new( + network, + &n_of_n_taproot_public_key, + &graph.parameters.operator_wots_pubkeys, + ); + let operator_assertion = connector_c + .extract_leaf_1_raw_witness(&operator_assert_txin) + .map_err(|e| anyhow::anyhow!("failed to extract operator assertion: {e}"))?; + + let verifier_connector = VerifierConnector::new( + network, + &n_of_n_taproot_public_key, + &graph.parameters.operator_wots_pubkeys, + graph.parameters.gc_data[verifier_index].wire_hashes.clone(), + ); + let mut verifier_assert = graph.verifier_asserts[verifier_index].clone(); + verifier_assert + .verifier_publish_labels(&verifier_connector, labels, &operator_assertion) + .map_err(|e| anyhow::anyhow!("failed to build verifier assert: {e}"))?; + Ok(verifier_assert.tx().clone()) +} + +pub fn build_disprove_tx( + graph: &BitvmGcGraph, + verifier_index: usize, + verifier_receive_address: Option<[u8; 20]>, +) -> Result { + if verifier_index >= graph.disproves.len() { + bail!("invalid verifier index {verifier_index}") + }; + if !graph.committee_pre_signed { + bail!("missing pre-signatures from committee") + }; + let mut disprove_tx = graph.disproves[verifier_index].tx().clone(); + if let Some(verifier_receive_address) = verifier_receive_address { + disprove_tx.output.insert( + 0, + TxOut { + value: Amount::ZERO, + script_pubkey: generate_opreturn_script(verifier_receive_address.to_vec()), + }, + ); + } + Ok(disprove_tx) +} + +pub fn disprove_timelock(network: Network) -> u32 { + num_blocks_per_network(network, PROVER_CONNECTOR_TIMELOCK) +} diff --git a/crates/bitvm2-ga/src/operator/mod.rs b/crates/bitvm-gc/src/verifier/mod.rs similarity index 100% rename from crates/bitvm2-ga/src/operator/mod.rs rename to crates/bitvm-gc/src/verifier/mod.rs diff --git a/crates/bitvm-gc/src/watchtower/api.rs b/crates/bitvm-gc/src/watchtower/api.rs new file mode 100644 index 00000000..469e2f62 --- /dev/null +++ b/crates/bitvm-gc/src/watchtower/api.rs @@ -0,0 +1,57 @@ +use anyhow::{Result, bail}; +use bitcoin::{Address, Amount, OutPoint, Transaction, key::Keypair}; +use goat::{ + connectors::watchtower_connectors::WatchtowerChallengeConnector, + transactions::{ + base::Input, pre_signed::PreSignedTransaction, watchtower_challenge::watchtower_challenge, + }, +}; + +use crate::types::BitvmGcGraph; + +pub fn estimate_watchtower_challenge_vbytes(commitment_data_len: usize) -> usize { + 120 + commitment_data_len.saturating_mul(12) / 10 +} + +pub fn build_watchtower_challenge_tx( + graph: &BitvmGcGraph, + watchtower_keypair: &Keypair, + watchtower_index: usize, + commitment_data: &[u8], + payer_inputs: Vec, + change_address: &Address, + fee_amount: Amount, +) -> Result { + if watchtower_index >= graph.parameters.watchtower_pubkeys.len() { + bail!("Invalid watchtower index"); + } + let network = graph.parameters.instance_parameters.network; + let watchtower_taproot_public_key = graph.parameters.watchtower_pubkeys[watchtower_index]; + let watchtower_challenge_connector = + WatchtowerChallengeConnector::new(network, &watchtower_taproot_public_key); + let watchtower_challenge_connector_amount = graph + .watchtower_challenge_init + .tx() + .output + .get(watchtower_index) + .ok_or_else(|| anyhow::anyhow!("watchtower index out of bounds"))? + .value; + let input_0 = Input { + outpoint: OutPoint { + txid: graph.watchtower_challenge_init.tx().compute_txid(), + vout: watchtower_index as u32, + }, + amount: watchtower_challenge_connector_amount, + }; + + watchtower_challenge( + watchtower_keypair, + &watchtower_challenge_connector, + commitment_data, + input_0, + payer_inputs, + change_address, + fee_amount, + ) + .map_err(|e| anyhow::anyhow!("failed to build watchtower challenge transaction: {e}")) +} diff --git a/crates/bitvm2-ga/src/watchtower/mod.rs b/crates/bitvm-gc/src/watchtower/mod.rs similarity index 100% rename from crates/bitvm2-ga/src/watchtower/mod.rs rename to crates/bitvm-gc/src/watchtower/mod.rs diff --git a/crates/bitvm-gc/tests/babe_adapter.rs b/crates/bitvm-gc/tests/babe_adapter.rs new file mode 100644 index 00000000..4fe1b9d3 --- /dev/null +++ b/crates/bitvm-gc/tests/babe_adapter.rs @@ -0,0 +1,269 @@ +use ark_bn254::Fr; +use ark_crypto_primitives::snark::CircuitSpecificSetupSNARK; +use ark_groth16::Groth16; +use bitvm_gc::assert_scripts::label_hash; +use bitvm_gc::babe_adapter::{ + BABE_M_CC, BabeBundleBuilder, BabeChallengeAssertWitness, BabeProverState, BabeVerifierState, + CACInstanceCommit, CACSetupPackage, FinalizedInstanceData, SolderingData, WOTS_SIG_COUNT, + build_assert_witness, build_challenge_assert_witness, build_real_setup_package, + build_setup_package, build_wrongly_challenged_witness, + build_wrongly_challenged_witness_from_preimages, derive_finalized_indices, + extract_gc_circuit_data, open_and_solder, open_real_setup_and_solder, verify_real_setup, + verify_setup, +}; +use bitvm_gc::operator::generate_wots_key; +use rand::SeedableRng; +use rand_chacha::ChaCha12Rng; +use std::collections::HashSet; +use std::str::FromStr; +use verifiable_circuit_babe::babe::DummyMulCircuit; + +fn verifier_pubkey() -> bitcoin::PublicKey { + bitcoin::PublicKey::from_str( + "0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798", + ) + .expect("public key") +} + +#[test] +#[ignore = "requires GC_GATES_PATH and GC_INDICES_PATH runtime assets"] +fn real_setup_restores_private_state_and_verifies_soldering_proof() { + let mut rng = ChaCha12Rng::seed_from_u64(42); + let a = Fr::from(3_u64); + let b = Fr::from(7_u64); + let (_, vk) = Groth16::::setup( + DummyMulCircuit:: { a: Some(a), b: Some(b) }, + &mut rng, + ) + .expect("groth16 setup"); + let public_inputs = vec![a * b]; + + let (package, private_state) = + build_real_setup_package(BABE_M_CC, &vk, &public_inputs).expect("real setup"); + let restored = + serde_json::from_slice(&serde_json::to_vec(&private_state).expect("serialize state")) + .expect("deserialize state"); + let soldering_builder = BabeBundleBuilder::new(); + let finalized_indices = (0..BABE_M_CC).collect::>(); + let (opened, finalized, soldering) = open_real_setup_and_solder( + &soldering_builder, + &restored, + &package, + &finalized_indices, + &vk, + &public_inputs, + ) + .expect("open real setup"); + + assert!(opened.is_empty()); + assert_eq!(finalized.len(), BABE_M_CC); + verify_real_setup( + &soldering_builder, + &package, + &opened, + &finalized, + &soldering, + &vk, + &public_inputs, + ) + .expect("verify soldering proof"); + extract_gc_circuit_data(&finalized, &soldering, verifier_pubkey()) + .expect("extract native 508-wire graph data"); +} + +#[test] +fn babe_setup_payload_round_trips_and_derives_gc_data() { + let package = CACSetupPackage { + commits: (0..BABE_M_CC).map(|index| CACInstanceCommit::sample(index as u8)).collect(), + }; + + let encoded = serde_json::to_vec(&package).expect("serialize package"); + let decoded: CACSetupPackage = serde_json::from_slice(&encoded).expect("deserialize package"); + assert_eq!(decoded, package); + + let finalized_indices = + derive_finalized_indices(&decoded, BABE_M_CC).expect("derive finalized"); + assert_eq!(finalized_indices.len(), BABE_M_CC); + + let finalized = finalized_indices + .iter() + .map(|index| FinalizedInstanceData::sample(*index)) + .collect::>(); + let soldering = SolderingData::sample(finalized_indices); + let gc_data = extract_gc_circuit_data(&finalized, &soldering, verifier_pubkey()) + .expect("extract gc data"); + + assert_eq!(gc_data.verifier_pubkey, verifier_pubkey()); + assert_eq!(gc_data.final_msg_hashlocks.len(), BABE_M_CC); +} + +#[test] +fn protocol_finalized_instances_contribute_one_base_wire_slot() { + let finalized = (0..BABE_M_CC).map(FinalizedInstanceData::sample).collect::>(); + let soldering = SolderingData::sample((0..BABE_M_CC).collect()); + + let gc_data = extract_gc_circuit_data(&finalized, &soldering, verifier_pubkey()) + .expect("one verifier graph slot"); + + assert_eq!( + gc_data.final_msg_hashlocks, + finalized.iter().map(|data| data.final_msg_hash).collect::>() + ); + assert!(gc_data.wire_hashes.as_slice() == finalized[0].wire_hashes.as_slice()); +} + +#[test] +fn gc_slot_rejects_invalid_finalized_counts_and_soldering_order() { + for count in [1, 3, 5, 8] { + let finalized = (0..count).map(FinalizedInstanceData::sample).collect::>(); + let soldering = SolderingData::sample((0..count).collect()); + let error = match extract_gc_circuit_data(&finalized, &soldering, verifier_pubkey()) { + Ok(_) => panic!("invalid finalized count"), + Err(error) => error, + }; + assert!(error.to_string().contains(&format!("exactly {BABE_M_CC} finalized"))); + } + + let finalized = (0..BABE_M_CC).map(FinalizedInstanceData::sample).collect::>(); + let mut indices = (0..BABE_M_CC).collect::>(); + indices.swap(0, 1); + let error = match extract_gc_circuit_data( + &finalized, + &SolderingData::sample(indices), + verifier_pubkey(), + ) { + Ok(_) => panic!("mismatched soldering order"), + Err(error) => error, + }; + assert!(error.to_string().contains("soldering finalized indices mismatch")); +} + +#[test] +fn finalized_indices_reject_invalid_counts_and_cover_full_cut() { + let package = build_setup_package(4).expect("setup package"); + + assert!(derive_finalized_indices(&package, 0).is_err()); + assert!(derive_finalized_indices(&package, 5).is_err()); + + let first = derive_finalized_indices(&package, 4).expect("derive full cut"); + let second = derive_finalized_indices(&package, 4).expect("derive full cut again"); + assert_eq!(first, second); + assert_eq!(first.len(), 4); + assert_eq!(first.iter().copied().collect::>().len(), 4); + assert_eq!(first.iter().copied().collect::>(), HashSet::from([0, 1, 2, 3])); +} + +#[test] +fn verify_setup_accepts_valid_opening_and_rejects_invalid_shapes() { + let package = build_setup_package(4).expect("setup package"); + let finalized_indices = derive_finalized_indices(&package, 2).expect("derive finalized"); + let (opened, finalized, soldering) = + open_and_solder(&package, &finalized_indices).expect("open and solder"); + + verify_setup(&package, &opened, &finalized, &soldering).expect("valid setup"); + + let mut duplicate_finalized = finalized.clone(); + duplicate_finalized.push(finalized[0].clone()); + assert!(verify_setup(&package, &opened, &duplicate_finalized, &soldering).is_err()); + + let mut overlapping_opened = opened.clone(); + overlapping_opened.push((finalized[0].index, 0)); + assert!(verify_setup(&package, &overlapping_opened, &finalized, &soldering).is_err()); + + let mut wrong_seed_opened = opened.clone(); + wrong_seed_opened[0].1 ^= 1; + assert!(verify_setup(&package, &wrong_seed_opened, &finalized, &soldering).is_err()); + + let mut mismatched_soldering = soldering.clone(); + mismatched_soldering.finalized_indices.reverse(); + if mismatched_soldering.finalized_indices == soldering.finalized_indices { + mismatched_soldering.finalized_indices.push(usize::MAX); + } + assert!(verify_setup(&package, &opened, &finalized, &mismatched_soldering).is_err()); +} + +#[test] +fn witness_builders_validate_inputs_and_indices() { + let (assert_secret_key, _) = generate_wots_key("graph-scoped-key"); + let proof = ark_groth16::Proof::::default(); + let assert_witness = build_assert_witness(&proof, &assert_secret_key).expect("assert witness"); + assert_eq!(assert_witness.wots_sig.len(), WOTS_SIG_COUNT); + assert!(!assert_witness.pi1.is_empty()); + assert!(build_assert_witness(&proof, &Vec::new()).is_err()); + + let verifier_state = BabeVerifierState { + package: build_setup_package(BABE_M_CC).expect("setup package"), + finalized_indices: (0..BABE_M_CC).collect(), + verifier_pubkey: verifier_pubkey(), + }; + let challenge_witness = build_challenge_assert_witness(&verifier_state, &assert_witness, 12) + .expect("challenge witness"); + assert_eq!(challenge_witness.verifier_index, 12); + assert_eq!(challenge_witness.input_labels.len(), 512); + + let final_msgs = (0..BABE_M_CC) + .map(|index| format!("finalized-preimage-{index}").into_bytes()) + .collect::>(); + let h_msgs = final_msgs.iter().map(label_hash).collect::>(); + let prover_state = BabeProverState { + package: build_setup_package(BABE_M_CC).expect("setup package"), + finalized: (0..BABE_M_CC).map(FinalizedInstanceData::sample).collect(), + soldering: SolderingData::sample((0..BABE_M_CC).collect()), + h_msgs, + }; + let wrongly_challenged = + build_wrongly_challenged_witness(&prover_state, &challenge_witness, final_msgs.clone()) + .expect("wrongly challenged witness"); + assert_eq!(wrongly_challenged.verifier_index, 12); + assert_eq!(wrongly_challenged.final_msgs, final_msgs); + assert!( + build_wrongly_challenged_witness( + &prover_state, + &challenge_witness, + vec![b"missing-preimage".to_vec(); BABE_M_CC], + ) + .is_err() + ); +} + +#[test] +fn wrongly_challenged_witness_requires_all_finalized_preimages() { + let final_msgs = (0..BABE_M_CC) + .map(|index| format!("finalized-preimage-{index}").into_bytes()) + .collect::>(); + let h_msgs = final_msgs.iter().map(label_hash).collect::>(); + let challenge_witness = + BabeChallengeAssertWitness { verifier_index: 0, input_labels: vec![], wots_sig: vec![] }; + + let from_preimages = build_wrongly_challenged_witness_from_preimages( + &h_msgs, + &challenge_witness, + final_msgs.clone(), + ) + .expect("wrongly challenged witness"); + assert_eq!(from_preimages.verifier_index, 0); + assert_eq!(from_preimages.final_msgs, final_msgs); + + let prover_state = BabeProverState { + package: build_setup_package(BABE_M_CC).expect("setup package"), + finalized: (0..BABE_M_CC).map(FinalizedInstanceData::sample).collect(), + soldering: SolderingData::sample((0..BABE_M_CC).collect()), + h_msgs, + }; + let delegated = build_wrongly_challenged_witness( + &prover_state, + &challenge_witness, + from_preimages.final_msgs.clone(), + ) + .expect("delegated wrongly challenged witness"); + assert_eq!(delegated, from_preimages); + + assert!( + build_wrongly_challenged_witness_from_preimages( + &prover_state.h_msgs, + &challenge_witness, + vec![from_preimages.final_msgs[0].clone(); BABE_M_CC], + ) + .is_err() + ); +} diff --git a/crates/bitvm2-ga/src/challenger/api.rs b/crates/bitvm2-ga/src/challenger/api.rs index 30287c60..e69de29b 100644 --- a/crates/bitvm2-ga/src/challenger/api.rs +++ b/crates/bitvm2-ga/src/challenger/api.rs @@ -1,278 +0,0 @@ -use crate::types::{Bitvm2Graph, VerifyingKey}; -use anyhow::{Result, bail}; -use bitcoin::{Address, Amount, Network, ScriptBuf, Transaction, TxIn, TxOut, XOnlyPublicKey}; -use bitvm::chunk::api::{ - NUM_HASH, NUM_PUBS, NUM_TAPS, NUM_U256, type_conversion_utils::RawWitness, -}; -use goat::{ - connectors::{ - assert_connectors::{extract_commits_from_txin, extract_commits_from_txins}, - connector_e::ConnectorE, - }, - constants::{ACK_TIMELOCK, ASSERT_COMMIT_TIMELOCK, CONNECTOR_G_TIMELOCK}, - disprove_scripts::{GUEST_VALIDATION_TAPS, NUM_GUEST_PUBS_ASSERT, NUM_GUEST_PUBS_EXTRA}, - scripts::{generate_opreturn_script, p2a_output}, - transactions::{ - base::{DUST_AMOUNT, Input}, - disprove::{disprove, validate_assert}, - pre_signed::PreSignedTransaction, - watchtower_challenge::extract_operator_preimage_from_ack_txin, - }, - utils::num_blocks_per_network, -}; - -pub fn extract_blockhash_commit_witness( - operator_commit_blockhash_txin: &TxIn, -) -> Result> { - match extract_commits_from_txin(operator_commit_blockhash_txin, NUM_GUEST_PUBS_EXTRA, 0) { - Ok(v) => Ok(v), - Err(e) => bail!("Failed to extract blockhash commit witness: {e}"), - } -} - -pub fn extract_assert_commit_witness( - operator_assert_commit_txins: Vec, -) -> Result> { - match extract_commits_from_txins( - operator_assert_commit_txins, - NUM_GUEST_PUBS_ASSERT + NUM_PUBS + NUM_U256, - NUM_HASH, - ) { - Ok(v) => Ok(v), - Err(e) => bail!("Failed to extract assert commit witness: {e}"), - } -} - -/// return (if any) disprove witness -pub fn verify_operator_commits( - operator_commit_blockhash_txin: TxIn, - operator_assert_commit_txins: Vec, - operator_ack_txins: Vec, - watchtower_num: usize, - vk: &VerifyingKey, - disprove_scripts: &[ScriptBuf; GUEST_VALIDATION_TAPS + NUM_TAPS], -) -> Result> { - let mut preimages = vec![vec![]; watchtower_num]; - for txin in &operator_ack_txins { - let watchtower_index = txin.previous_output.vout as usize / 2; - if watchtower_index >= watchtower_num || txin.previous_output.vout % 2 != 1 { - bail!( - "invalid ack txin in operator_ack_txins, unexpected vout: {}", - txin.previous_output.vout - ); - } - let preimage = extract_operator_preimage_from_ack_txin(txin) - .map_err(|e| anyhow::anyhow!("Failed to extract preimage from ack txin: {e}"))?; - preimages[watchtower_index] = preimage; - } - let (guest_validation_scripts, proof_validation_scripts) = - disprove_scripts.split_at(GUEST_VALIDATION_TAPS); - let guest_validation_scripts = - <&[ScriptBuf; GUEST_VALIDATION_TAPS]>::try_from(guest_validation_scripts).unwrap(); - let proof_validation_scripts = - <&[ScriptBuf; NUM_TAPS]>::try_from(proof_validation_scripts).unwrap(); - let res = validate_assert( - extract_blockhash_commit_witness(&operator_commit_blockhash_txin)?, - extract_assert_commit_witness(operator_assert_commit_txins)?, - preimages, - guest_validation_scripts, - vk, - proof_validation_scripts, - ); - if let Some((_, scr)) = &res { - let guest_index_opt = guest_validation_scripts.iter().position(|s| s == scr); - if let Some(guest_index) = guest_index_opt { - tracing::info!( - "Disprove witness validated against guest validation scripts at index {}", - guest_index - ); - } else { - let proof_index_opt = proof_validation_scripts.iter().position(|s| s == scr); - match proof_index_opt { - Some(idx) => tracing::info!( - "Disprove witness validated against proof validation scripts at index {}", - idx - ), - None => tracing::warn!( - "Disprove witness script not found in either guest or proof validation scripts" - ), - } - } - } - Ok(res) -} - -/// challenge has a pre-signed SinglePlusAnyoneCanPay input and output -/// get incomplete tx here, add inputs with enough amount, then broadcast it to start challnege progress -pub fn export_challenge_tx(graph: &Bitvm2Graph) -> Result<(Transaction, Amount)> { - if !graph.operator_pre_signed() { - bail!("missing pre-signatures from operator") - }; - Ok((graph.challenge.tx().clone(), graph.challenge.challenge_amount)) -} - -/// disprove has a huge disprove input and an optional op_return output -/// get incomplete tx here, add inputs with enough amount, then broadcast it to finish challnege progress -pub fn sign_disprove( - graph: &Bitvm2Graph, - connector_e_input: &Input, - disprove_witness: (RawWitness, ScriptBuf), - disprove_scripts: Vec, - disprover_evm_address: Option<[u8; 20]>, -) -> Result { - if !graph.committee_pre_signed() { - bail!("missing pre-signatures from committee") - }; - let network = graph.parameters.instance_parameters.network; - let operator_pubkey = graph.parameters.operator_pubkey; - let operator_taproot_public_key = XOnlyPublicKey::from(operator_pubkey); - let (_, connector_e_taproot_spend_info) = - ConnectorE::new_with_scripts(network, &operator_taproot_public_key, disprove_scripts); - let (input_script_witness, input_lock_script) = disprove_witness; - let disprove_txin = disprove( - &connector_e_taproot_spend_info, - connector_e_input, - input_script_witness, - input_lock_script, - ) - .map_err(|e| anyhow::anyhow!("Failed to create disprove txin: {e}"))?; - let mut disprove_tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![disprove_txin], - output: vec![], - }; - - // write challenger's l2 address to an op_return output - if let Some(disprover_evm_address) = disprover_evm_address { - disprove_tx.output.push(TxOut { - value: Amount::ZERO, - script_pubkey: generate_opreturn_script(disprover_evm_address.to_vec()), - }); - } - - Ok(disprove_tx) -} - -/// return true if anchor output is added -/// return false if change output is added or no output is added -fn add_change_or_anchor_output( - tx: &mut Transaction, - total_input_amount: Amount, - change_address: Address, - fee_rate: f64, -) -> Result { - let dust_amount = Amount::from_sat(DUST_AMOUNT); - let output_amount = tx.output.iter().map(|o| o.value).sum(); - tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: change_address.script_pubkey() }); - let min_relay_fee = 1.0; - let min_fee_amount = - Amount::from_sat((tx.weight().to_vbytes_ceil() as f64 * min_relay_fee).ceil() as u64); - let fee_amount = - Amount::from_sat((tx.weight().to_vbytes_ceil() as f64 * fee_rate).ceil() as u64); - if min_fee_amount + dust_amount + output_amount > total_input_amount { - bail!("insufficient input amount to cover min relay fee"); - } - if fee_amount + output_amount + dust_amount < total_input_amount { - // add change output - let change_amount = total_input_amount - fee_amount - output_amount; - tx.output.last_mut().unwrap().value = change_amount; - Ok(false) - } else if fee_amount + output_amount > total_input_amount { - // add anchor output - tx.output.pop(); - tx.output.push(p2a_output()); - Ok(true) - } else { - // not add any output since remaining is just enough to cover fee - tx.output.pop(); - Ok(false) - } -} - -/// return (tx, true) if anchor output is added, subsequently challenger need to cover fee via CPFP -/// return (tx, false) if change output is added or no output is added, challenger can directly broadcast it -pub fn build_force_skip_kickoff_tx( - graph: &Bitvm2Graph, - challenger_receive_address: Address, - fee_rate: f64, -) -> Result<(Transaction, bool)> { - if !graph.operator_pre_signed() { - bail!("missing pre-signatures from operator") - }; - let mut tx = graph.force_skip_kickoff.tx().clone(); - let total_input_amount = graph.force_skip_kickoff.prev_outs().iter().map(|o| o.value).sum(); - let anchor_added = add_change_or_anchor_output( - &mut tx, - total_input_amount, - challenger_receive_address, - fee_rate, - )?; - Ok((tx, anchor_added)) -} - -/// return (tx, true) if anchor output is added, subsequently challenger need to cover fee via CPFP -/// return (tx, false) if change output is added or no output is added, challenger can directly broadcast it -pub fn build_quick_challenge_tx( - graph: &Bitvm2Graph, - challenger_receive_address: Address, - fee_rate: f64, -) -> Result<(Transaction, bool)> { - if !graph.operator_pre_signed() { - bail!("missing pre-signatures from operator") - }; - let mut tx = graph.quick_challenge.tx().clone(); - let total_input_amount = graph.quick_challenge.prev_outs().iter().map(|o| o.value).sum(); - let anchor_added = add_change_or_anchor_output( - &mut tx, - total_input_amount, - challenger_receive_address, - fee_rate, - )?; - Ok((tx, anchor_added)) -} - -/// return (tx, true) if anchor output is added, subsequently challenger need to cover fee via CPFP -/// return (tx, false) if change output is added or no output is added, challenger can directly broadcast it -pub fn build_challenge_incomplete_kickoff_tx( - graph: &Bitvm2Graph, - challenger_receive_address: Address, - fee_rate: f64, -) -> Result<(Transaction, bool)> { - if !graph.operator_pre_signed() { - bail!("missing pre-signatures from operator") - }; - let mut tx = graph.challenge_incomplete_kickoff.tx().clone(); - let total_input_amount = - graph.challenge_incomplete_kickoff.prev_outs().iter().map(|o| o.value).sum(); - let anchor_added = add_change_or_anchor_output( - &mut tx, - total_input_amount, - challenger_receive_address, - fee_rate, - )?; - Ok((tx, anchor_added)) -} - -// nack, commit_blockhash_timeout, assert_commit_timeout are already fully signed. -// Just wait for their timelocks to expire, then broadcast them and cover fees via CPFP. - -pub fn nack_timelock(network: Network) -> u32 { - num_blocks_per_network(network, ACK_TIMELOCK) // actual delay on bitcoin network - + if network == Network::Testnet { 18 } else { 0 } // Testnet extra delay - + if network == Network::Testnet4 { 40 } else { 0 } // Testnet4 extra delay - + if network == Network::Regtest { 4 } else { 0 } // Regtest extra delay -} - -pub fn commit_blockhash_timeout_timelock(network: Network) -> u32 { - num_blocks_per_network(network, CONNECTOR_G_TIMELOCK) - + if network == Network::Testnet { 18 } else { 0 } // Testnet extra delay - + if network == Network::Testnet4 { 40 } else { 0 } // Testnet4 extra delay - + if network == Network::Regtest { 4 } else { 0 } // Regtest extra delay -} - -pub fn assert_commit_timeout_timelock(network: Network) -> u32 { - num_blocks_per_network(network, ASSERT_COMMIT_TIMELOCK) - + if network == Network::Testnet4 { 40 } else { 0 } // Testnet4 extra delay - + if network == Network::Regtest { 6 } else { 0 } // Regtest extra delay -} diff --git a/crates/bitvm2-ga/src/committee/api.rs b/crates/bitvm2-ga/src/committee/api.rs deleted file mode 100644 index e92eb8cc..00000000 --- a/crates/bitvm2-ga/src/committee/api.rs +++ /dev/null @@ -1,682 +0,0 @@ -use crate::keys::hkdf_derive_bytes; -use crate::types::Bitvm2Graph; -use anyhow::{Result, bail}; -use bitcoin::{PublicKey, Transaction, XOnlyPublicKey}; -use bitcoin::{key::Keypair, taproot::Signature as TaprootSignature}; -use goat::connectors::assert_connectors::generate_chunked_assert_commit_connectors; -use goat::connectors::connector_0::Connector0; -use goat::connectors::connector_a::ConnectorA; -use goat::connectors::connector_d::ConnectorD; -use goat::connectors::connector_f::ConnectorF; -use goat::connectors::connector_g::ConnectorG; -use goat::connectors::connector_z::ConnectorZ; -use goat::connectors::watchtower_connectors::{ - AckConnector, WatchctowerConnectors, WatchtowerChallengeConnector, -}; -use goat::contexts::base::generate_n_of_n_public_key; -use goat::transactions::pre_signed_musig2::{get_nonce_message, verify_public_nonce}; -use goat::transactions::{base::BaseTransaction, signing_musig2::generate_aggregated_nonce}; -use musig2::{AggNonce, PartialSignature, PubNonce, SecNonce}; -use secp256k1::schnorr::Signature as SchnorrSignature; -use serde::{Deserialize, Serialize}; - -const COMMITTEE_NONCE_HKDF_SALT: &[u8] = b"bitvm2/committee-nonce/v1"; - -pub fn key_aggregation(pubkeys: &[PublicKey]) -> PublicKey { - generate_n_of_n_public_key(pubkeys).0 -} - -pub fn take1_pre_sign_num() -> usize { - 1 -} -pub fn take2_pre_sign_num() -> usize { - 1 -} -pub fn challenge_pre_sign_num() -> usize { - 1 -} -pub fn watchtower_challenge_timeout_pre_sign_num(watchtower_num: usize) -> usize { - watchtower_num -} -pub fn nack_pre_sign_num(watchtower_num: usize) -> usize { - 2 * watchtower_num -} -pub fn blockhash_commit_timeout_pre_sign_num() -> usize { - 2 -} -pub fn assert_commit_timeout_pre_sign_num(assert_commit_num: usize) -> usize { - 2 * assert_commit_num -} - -pub fn sign_pegin_confirm( - graph: &Bitvm2Graph, - committee_member_keypair: Keypair, - committee_member_sec_nonce: SecNonce, - committee_agg_nonce: AggNonce, -) -> Result { - let mut pegin_confirm = graph.parameters.instance_parameters.build_pegin_tx()?.1; - let verifier_context = - graph.parameters.instance_parameters.get_verifier_context(committee_member_keypair)?; - pegin_confirm - .sign_input_0_musig2(&verifier_context, &committee_member_sec_nonce, &committee_agg_nonce) - .map_err(|e| anyhow::anyhow!("fail to sign pegin confirm {}: {e}", pegin_confirm.name())) -} - -pub fn agg_and_push_pegin_confirm_sigs( - graph: &Bitvm2Graph, - partial_sigs: Vec, - agg_nonce: &AggNonce, -) -> Result { - let mut pegin_confirm = graph.parameters.instance_parameters.build_pegin_tx()?.1; - let context = graph.parameters.instance_parameters.get_base_context(); - let agg_sig = pegin_confirm - .aggregate_input_0_musig2_signatures(&context, partial_sigs, agg_nonce) - .map_err(|e| { - anyhow::anyhow!("fail to aggregate pegin confirm {}: {e}", pegin_confirm.name()) - })?; - let connector_z = ConnectorZ::new( - graph.parameters.instance_parameters.network, - &XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey), - &graph.parameters.instance_parameters.user_info.user_xonly_pubkey, - ); - pegin_confirm.push_input_0_signature(&connector_z, agg_sig); - Ok(pegin_confirm.finalize()) -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct CommitteeMusig2Data { - pub take1: Vec, - pub take2: Vec, - pub challenge: Vec, - pub watchtower_challenge_timeout: Vec, - pub nack: Vec, - pub blockhash_commit_timeout: Vec, - pub assert_commit_timeout: Vec, -} - -pub type CommitteeSecNonces = CommitteeMusig2Data; -pub type CommitteePubNonces = CommitteeMusig2Data; -pub type CommitteeNonceSignatures = CommitteeMusig2Data; -pub type CommitteeAggNonces = CommitteeMusig2Data; -pub type CommitteePartialSignatures = CommitteeMusig2Data; -pub type CommitteeSignatures = CommitteeMusig2Data; - -impl CommitteeMusig2Data { - pub fn validate_length(&self, watchtower_num: usize, assert_commit_num: usize) -> Result<()> { - if self.take1.len() != take1_pre_sign_num() { - bail!("invalid number of take1"); - } - if self.take2.len() != take2_pre_sign_num() { - bail!("invalid number of take2"); - } - if self.challenge.len() != challenge_pre_sign_num() { - bail!("invalid number of challenge"); - } - if self.blockhash_commit_timeout.len() != blockhash_commit_timeout_pre_sign_num() { - bail!("invalid number of blockhash_commit_timeout"); - } - if self.watchtower_challenge_timeout.len() - != watchtower_challenge_timeout_pre_sign_num(watchtower_num) - { - bail!("invalid number of watchtower_challenge_timeout"); - } - if self.nack.len() != nack_pre_sign_num(watchtower_num) { - bail!("invalid number of nack"); - } - if self.assert_commit_timeout.len() != assert_commit_timeout_pre_sign_num(assert_commit_num) - { - bail!("invalid number of assert_commit_timeout"); - } - Ok(()) - } - - pub fn new_empty() -> Self { - CommitteeMusig2Data { - take1: vec![], - take2: vec![], - challenge: vec![], - watchtower_challenge_timeout: vec![], - nack: vec![], - blockhash_commit_timeout: vec![], - assert_commit_timeout: vec![], - } - } -} - -pub fn committee_pre_sign( - committee_member_keypair: Keypair, - committee_member_sec_nonce: CommitteeSecNonces, - committee_agg_nonce: CommitteeAggNonces, - graph: &mut Bitvm2Graph, -) -> Result { - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = graph.assert_commit_timeout_txns.len(); - committee_member_sec_nonce.validate_length(watchtower_num, assert_commit_num)?; - committee_agg_nonce.validate_length(watchtower_num, assert_commit_num)?; - - let verifier_context = - graph.parameters.instance_parameters.get_verifier_context(committee_member_keypair)?; - let mut res = CommitteePartialSignatures::new_empty(); - - { - // take-1 - let sec_nonces = committee_member_sec_nonce.take1.try_into().unwrap(); - let agg_nonces = committee_agg_nonce.take1.try_into().unwrap(); - match graph.take1.pre_sign(&verifier_context, &sec_nonces, &agg_nonces) { - Ok(v) => res.take1 = v.to_vec(), - Err(e) => bail!("fail to pre-sign {}: {e}", graph.take1.name()), - }; - } - - { - // take-2 - let sec_nonces = committee_member_sec_nonce.take2.try_into().unwrap(); - let agg_nonces = committee_agg_nonce.take2.try_into().unwrap(); - match graph.take2.pre_sign(&verifier_context, &sec_nonces, &agg_nonces) { - Ok(v) => res.take2 = v.to_vec(), - Err(e) => bail!("fail to pre-sign {}: {e}", graph.take2.name()), - }; - } - - { - // challenge - let sec_nonces = committee_member_sec_nonce.challenge.try_into().unwrap(); - let agg_nonces = committee_agg_nonce.challenge.try_into().unwrap(); - match graph.challenge.pre_sign(&verifier_context, &sec_nonces, &agg_nonces) { - Ok(v) => res.challenge = v.to_vec(), - Err(e) => bail!("fail to pre-sign {}: {e}", graph.challenge.name()), - }; - } - - { - // watchtower_challenge_timeout - let mut watchtower_challenge_timeout_sigs = vec![]; - for (i, watchtower_challenge_timeout_tx) in - graph.watchtower_challenge_timeout_txns.iter_mut().enumerate() - { - let sec_nonces = [committee_member_sec_nonce.watchtower_challenge_timeout[i].clone()]; - let agg_nonces = [committee_agg_nonce.watchtower_challenge_timeout[i].clone()]; - let sigs = watchtower_challenge_timeout_tx - .pre_sign(&verifier_context, &sec_nonces, &agg_nonces) - .map_err(|e| { - anyhow::anyhow!( - "fail to pre-sign {}: {e}", - watchtower_challenge_timeout_tx.name() - ) - })?; - watchtower_challenge_timeout_sigs.extend(sigs); - } - res.watchtower_challenge_timeout = watchtower_challenge_timeout_sigs; - } - - { - // nack - let mut nack_sigs = vec![]; - for (i, nack_tx) in graph.nack_txns.iter_mut().enumerate() { - let sec_nonces = [ - committee_member_sec_nonce.nack[i * 2].clone(), - committee_member_sec_nonce.nack[i * 2 + 1].clone(), - ]; - let agg_nonces = [ - committee_agg_nonce.nack[i * 2].clone(), - committee_agg_nonce.nack[i * 2 + 1].clone(), - ]; - let sigs = nack_tx - .pre_sign(&verifier_context, &sec_nonces, &agg_nonces) - .map_err(|e| anyhow::anyhow!("fail to pre-sign {}: {e}", nack_tx.name()))?; - nack_sigs.extend(sigs); - } - res.nack = nack_sigs; - } - - { - // blockhash-commit-timeout - let sec_nonces = committee_member_sec_nonce.blockhash_commit_timeout.try_into().unwrap(); - let agg_nonces = committee_agg_nonce.blockhash_commit_timeout.try_into().unwrap(); - match graph.blockhash_commit_timeout.pre_sign(&verifier_context, &sec_nonces, &agg_nonces) { - Ok(v) => res.blockhash_commit_timeout = v.to_vec(), - Err(e) => bail!("fail to pre-sign {}: {e}", graph.blockhash_commit_timeout.name()), - } - } - - { - // assert-commit-timeout - let mut assert_commit_timeout_sigs = vec![]; - for (i, assert_commit_timeout_tx) in graph.assert_commit_timeout_txns.iter_mut().enumerate() - { - let sec_nonces = [ - committee_member_sec_nonce.assert_commit_timeout[i * 2].clone(), - committee_member_sec_nonce.assert_commit_timeout[i * 2 + 1].clone(), - ]; - let agg_nonces = [ - committee_agg_nonce.assert_commit_timeout[i * 2].clone(), - committee_agg_nonce.assert_commit_timeout[i * 2 + 1].clone(), - ]; - let sigs = assert_commit_timeout_tx - .pre_sign(&verifier_context, &sec_nonces, &agg_nonces) - .map_err(|e| { - anyhow::anyhow!("fail to pre-sign {}: {e}", assert_commit_timeout_tx.name()) - })?; - assert_commit_timeout_sigs.extend(sigs); - } - res.assert_commit_timeout = assert_commit_timeout_sigs; - } - - Ok(res) -} - -pub fn nonce_aggregation(pub_nonces: &Vec) -> AggNonce { - generate_aggregated_nonce(pub_nonces) -} - -pub fn nonces_aggregation(pub_nonces_vec: &[CommitteePubNonces]) -> Result { - fn aggregate_field(rows: &[CommitteePubNonces], get: F) -> Result> - where - F: Fn(&CommitteePubNonces) -> &Vec, - { - if rows.is_empty() { - return Ok(Vec::new()); - } - - let expected = get(&rows[0]).len(); - - for (idx, r) in rows.iter().enumerate() { - if get(r).len() != expected { - bail!("length mismatch on row {}: expected {}, got {}", idx, expected, get(r).len()) - } - } - - Ok((0..expected) - .map(|i| { - let column: Vec = rows.iter().map(|r| get(r)[i].clone()).collect(); - nonce_aggregation(&column) - }) - .collect()) - } - - Ok(CommitteeAggNonces { - take1: aggregate_field(pub_nonces_vec, |c| &c.take1)?, - take2: aggregate_field(pub_nonces_vec, |c| &c.take2)?, - challenge: aggregate_field(pub_nonces_vec, |c| &c.challenge)?, - watchtower_challenge_timeout: aggregate_field(pub_nonces_vec, |c| { - &c.watchtower_challenge_timeout - })?, - nack: aggregate_field(pub_nonces_vec, |c| &c.nack)?, - blockhash_commit_timeout: aggregate_field(pub_nonces_vec, |c| &c.blockhash_commit_timeout)?, - assert_commit_timeout: aggregate_field(pub_nonces_vec, |c| &c.assert_commit_timeout)?, - }) -} - -pub fn signature_aggregation( - partial_sigs: &Vec, - agg_nonces: &CommitteeAggNonces, - graph: &Bitvm2Graph, -) -> Result { - let context = graph.parameters.get_base_context(); - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = graph.assert_commit_timeout_txns.len(); - agg_nonces.validate_length(watchtower_num, assert_commit_num)?; - for r in partial_sigs { - r.validate_length(watchtower_num, assert_commit_num)?; - } - - let mut res: CommitteeSignatures = CommitteeSignatures::new_empty(); - - // take1 - let take1_agg_nonces = agg_nonces.take1.clone().try_into().unwrap(); - let take1_partial_sigs = [partial_sigs.iter().map(|r| r.take1[0]).collect()]; - match graph.take1.aggregate_pre_sigs(&context, &take1_partial_sigs, &take1_agg_nonces) { - Ok(v) => res.take1 = v.to_vec(), - Err(e) => bail!("fail to aggregate pre-sigs {}: {e}", graph.take1.name()), - }; - - // take2 - let take2_agg_nonces = agg_nonces.take2.clone().try_into().unwrap(); - let take2_partial_sigs = [partial_sigs.iter().map(|r| r.take2[0]).collect()]; - match graph.take2.aggregate_pre_sigs(&context, &take2_partial_sigs, &take2_agg_nonces) { - Ok(v) => res.take2 = v.to_vec(), - Err(e) => bail!("fail to aggregate pre-sigs {}: {e}", graph.take2.name()), - }; - - // challenge - let challenge_agg_nonces = agg_nonces.challenge.clone().try_into().unwrap(); - let challenge_partial_sigs = [partial_sigs.iter().map(|r| r.challenge[0]).collect()]; - match graph.challenge.aggregate_pre_sigs( - &context, - &challenge_partial_sigs, - &challenge_agg_nonces, - ) { - Ok(v) => res.challenge = v.to_vec(), - Err(e) => bail!("fail to aggregate pre-sigs {}: {e}", graph.challenge.name()), - }; - - // blockhash-commit-timeout - let blockhash_commit_timeout_agg_nonces = - agg_nonces.blockhash_commit_timeout.clone().try_into().unwrap(); - let mut blockhash_commit_timeout_partial_sigs = [vec![], vec![]]; - partial_sigs.iter().for_each(|r| { - blockhash_commit_timeout_partial_sigs[0].push(r.blockhash_commit_timeout[0]); - blockhash_commit_timeout_partial_sigs[1].push(r.blockhash_commit_timeout[1]); - }); - match graph.blockhash_commit_timeout.aggregate_pre_sigs( - &context, - &blockhash_commit_timeout_partial_sigs, - &blockhash_commit_timeout_agg_nonces, - ) { - Ok(v) => res.blockhash_commit_timeout = v.to_vec(), - Err(e) => { - bail!("fail to aggregate pre-sigs {}: {e}", graph.blockhash_commit_timeout.name()) - } - }; - - // watchtower_challenge_timeout - let mut watchtower_challenge_timeout_sigs = vec![]; - for (i, watchtower_challenge_timeout_tx) in - graph.watchtower_challenge_timeout_txns.iter().enumerate() - { - let _agg_nonces = [agg_nonces.watchtower_challenge_timeout[i].clone()]; - let mut _partial_sigs = [vec![]]; - partial_sigs.iter().for_each(|r| { - _partial_sigs[0].push(r.watchtower_challenge_timeout[i]); - }); - let sigs = watchtower_challenge_timeout_tx - .aggregate_pre_sigs(&context, &_partial_sigs, &_agg_nonces) - .map_err(|e| { - anyhow::anyhow!( - "fail to aggregate pre-sigs {}: {e}", - watchtower_challenge_timeout_tx.name() - ) - })?; - watchtower_challenge_timeout_sigs.extend(sigs); - } - res.watchtower_challenge_timeout = watchtower_challenge_timeout_sigs; - - // nack - let mut nack_sigs = vec![]; - for (i, nack_tx) in graph.nack_txns.iter().enumerate() { - let _agg_nonces = [agg_nonces.nack[i * 2].clone(), agg_nonces.nack[i * 2 + 1].clone()]; - let mut _partial_sigs = [vec![], vec![]]; - partial_sigs.iter().for_each(|r| { - _partial_sigs[0].push(r.nack[i * 2]); - _partial_sigs[1].push(r.nack[i * 2 + 1]); - }); - let sigs = nack_tx - .aggregate_pre_sigs(&context, &_partial_sigs, &_agg_nonces) - .map_err(|e| anyhow::anyhow!("fail to aggregate pre-sigs {}: {e}", nack_tx.name()))?; - nack_sigs.extend(sigs); - } - res.nack = nack_sigs; - - // assert-commit-timeout - let mut assert_commit_timeout_sigs = vec![]; - for (i, assert_commit_timeout_tx) in graph.assert_commit_timeout_txns.iter().enumerate() { - let _agg_nonces = [ - agg_nonces.assert_commit_timeout[i * 2].clone(), - agg_nonces.assert_commit_timeout[i * 2 + 1].clone(), - ]; - let mut _partial_sigs = [vec![], vec![]]; - partial_sigs.iter().for_each(|r| { - _partial_sigs[0].push(r.assert_commit_timeout[i * 2]); - _partial_sigs[1].push(r.assert_commit_timeout[i * 2 + 1]); - }); - let sigs = assert_commit_timeout_tx - .aggregate_pre_sigs(&context, &_partial_sigs, &_agg_nonces) - .map_err(|e| { - anyhow::anyhow!( - "fail to aggregate pre-sigs {}: {e}", - assert_commit_timeout_tx.name() - ) - })?; - assert_commit_timeout_sigs.extend(sigs); - } - res.assert_commit_timeout = assert_commit_timeout_sigs; - - Ok(res) -} - -pub fn push_committee_pre_signatures( - graph: &mut Bitvm2Graph, - sigs: &CommitteeSignatures, -) -> Result<()> { - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = graph.assert_commit_timeout_txns.len(); - if graph.committee_pre_signed { - bail!("already pre-signed by committee".to_string()) - }; - sigs.validate_length(watchtower_num, assert_commit_num)?; - - let network = graph.parameters.instance_parameters.network; - let n_of_n_taproot_public_key = - XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); - let operator_taproot_public_key = XOnlyPublicKey::from(graph.parameters.operator_pubkey); - let connector_0 = Connector0::new(network, &n_of_n_taproot_public_key); - let connector_a = - ConnectorA::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); - let connector_d = - ConnectorD::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); - let blockhash_wots_pubkey = &graph.parameters.operator_wots_pubkeys.0[0]; - let connector_f = - ConnectorF::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); - let connector_g = ConnectorG::new( - network, - &n_of_n_taproot_public_key, - &operator_taproot_public_key, - blockhash_wots_pubkey, - ); - let watchtower_connectors_array = (0..watchtower_num) - .map(|i| { - ( - WatchtowerChallengeConnector::new( - network, - &operator_taproot_public_key, - &graph.parameters.watchtower_pubkeys[i], - ), - AckConnector::new( - network, - &n_of_n_taproot_public_key, - &graph.parameters.hashlocks[i], - ), - ) - }) - .collect::>(); - let assert_wots_pubkeys = - (graph.parameters.operator_wots_pubkeys.1, *graph.parameters.operator_wots_pubkeys.2); - let assert_commit_connectors = generate_chunked_assert_commit_connectors( - network, - &n_of_n_taproot_public_key, - assert_wots_pubkeys, - ); - - // take1 - graph.take1.push_pre_sigs(&connector_0, sigs.take1.clone().try_into().unwrap()); - - // take2 - graph.take2.push_pre_sigs(&connector_0, sigs.take2.clone().try_into().unwrap()); - - // challenge - graph.challenge.push_pre_sigs(&connector_a, sigs.challenge.clone().try_into().unwrap()); - - // blockhash-commit-timeout - graph.blockhash_commit_timeout.push_pre_sigs( - &connector_g, - &connector_f, - sigs.blockhash_commit_timeout.clone().try_into().unwrap(), - ); - - // watchtower_challenge_timeout - for (i, watchtower_challenge_timeout_tx) in - graph.watchtower_challenge_timeout_txns.iter_mut().enumerate() - { - watchtower_challenge_timeout_tx.push_pre_sigs( - &watchtower_connectors_array[i], - sigs.watchtower_challenge_timeout[i..(i + 1)].try_into().unwrap(), - ); - } - - // nack - for (i, nack_tx) in graph.nack_txns.iter_mut().enumerate() { - nack_tx.push_pre_sigs( - &watchtower_connectors_array[i], - &connector_f, - sigs.nack[i * 2..(i * 2 + 2)].try_into().unwrap(), - ); - } - - // assert-commit-timeout - for (i, assert_commit_timeout_tx) in graph.assert_commit_timeout_txns.iter_mut().enumerate() { - assert_commit_timeout_tx.push_pre_sigs( - &assert_commit_connectors[i], - &connector_d, - sigs.assert_commit_timeout[i * 2..(i * 2 + 2)].try_into().unwrap(), - ); - } - - graph.committee_pre_signed = true; - Ok(()) -} - -pub fn generate_nonce_from_seed( - seed: String, - graph_index: usize, - signer_keypair: Keypair, - watchtower_num: usize, - assert_commit_num: usize, -) -> (CommitteePubNonces, CommitteeSecNonces, CommitteeNonceSignatures) { - let graph_seed = hkdf_derive_bytes( - seed.as_bytes(), - COMMITTEE_NONCE_HKDF_SALT, - format!("graph/{graph_index}").as_bytes(), - 32, - ); - let mut pub_nonces = CommitteePubNonces::new_empty(); - let mut sec_nonces = CommitteeSecNonces::new_empty(); - let mut nonce_sigs = CommitteeNonceSignatures::new_empty(); - let mut index = 0; - { - // take1 - for _ in 0..take1_pre_sign_num() { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.take1.push(pub_nonce); - sec_nonces.take1.push(sec_nonce); - nonce_sigs.take1.push(nonce_sig); - index += 1; - } - } - { - // take2 - for _ in 0..take2_pre_sign_num() { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.take2.push(pub_nonce); - sec_nonces.take2.push(sec_nonce); - nonce_sigs.take2.push(nonce_sig); - index += 1; - } - } - { - // challenge - for _ in 0..challenge_pre_sign_num() { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.challenge.push(pub_nonce); - sec_nonces.challenge.push(sec_nonce); - nonce_sigs.challenge.push(nonce_sig); - index += 1; - } - } - { - // watchtower_challenge_timeout - for _ in 0..watchtower_challenge_timeout_pre_sign_num(watchtower_num) { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.watchtower_challenge_timeout.push(pub_nonce); - sec_nonces.watchtower_challenge_timeout.push(sec_nonce); - nonce_sigs.watchtower_challenge_timeout.push(nonce_sig); - index += 1; - } - } - { - // nack - for _ in 0..nack_pre_sign_num(watchtower_num) { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.nack.push(pub_nonce); - sec_nonces.nack.push(sec_nonce); - nonce_sigs.nack.push(nonce_sig); - index += 1; - } - } - { - // blockhash-commit-timeout - for _ in 0..blockhash_commit_timeout_pre_sign_num() { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.blockhash_commit_timeout.push(pub_nonce); - sec_nonces.blockhash_commit_timeout.push(sec_nonce); - nonce_sigs.blockhash_commit_timeout.push(nonce_sig); - index += 1; - } - } - { - // assert-commit-timeout - for _ in 0..assert_commit_timeout_pre_sign_num(assert_commit_num) { - let (sec_nonce, pub_nonce, nonce_sig) = - generate_nonce(signer_keypair, &graph_seed, index); - pub_nonces.assert_commit_timeout.push(pub_nonce); - sec_nonces.assert_commit_timeout.push(sec_nonce); - nonce_sigs.assert_commit_timeout.push(nonce_sig); - index += 1; - } - } - (pub_nonces, sec_nonces, nonce_sigs) -} - -pub fn verify_nonce_signatures( - pubkey: &XOnlyPublicKey, - pub_nonces: &CommitteePubNonces, - nonce_sigs: &CommitteeNonceSignatures, - watchtower_num: usize, - assert_commit_num: usize, -) -> Result { - pub_nonces.validate_length(watchtower_num, assert_commit_num)?; - nonce_sigs.validate_length(watchtower_num, assert_commit_num)?; - - fn verify_vec(pubkey: &XOnlyPublicKey, nonces: &[PubNonce], sigs: &[SchnorrSignature]) -> bool { - if nonces.len() != sigs.len() { - return false; - } - nonces.iter().zip(sigs.iter()).all(|(nonce, sig)| verify_public_nonce(sig, nonce, pubkey)) - } - - Ok(verify_vec(pubkey, &pub_nonces.take1, &nonce_sigs.take1) - && verify_vec(pubkey, &pub_nonces.take2, &nonce_sigs.take2) - && verify_vec(pubkey, &pub_nonces.challenge, &nonce_sigs.challenge) - && verify_vec( - pubkey, - &pub_nonces.watchtower_challenge_timeout, - &nonce_sigs.watchtower_challenge_timeout, - ) - && verify_vec(pubkey, &pub_nonces.nack, &nonce_sigs.nack) - && verify_vec( - pubkey, - &pub_nonces.blockhash_commit_timeout, - &nonce_sigs.blockhash_commit_timeout, - ) - && verify_vec(pubkey, &pub_nonces.assert_commit_timeout, &nonce_sigs.assert_commit_timeout)) -} - -pub(crate) fn generate_nonce( - signer_keypair: Keypair, - seed: &[u8], - index: usize, -) -> (SecNonce, PubNonce, SchnorrSignature) { - let nonce_seed = - hkdf_derive_bytes(seed, COMMITTEE_NONCE_HKDF_SALT, format!("nonce/{index}").as_bytes(), 32); - let nonce_seed: [u8; 32] = - nonce_seed.try_into().expect("hkdf output length is fixed to 32 bytes"); - let sec_nonce = SecNonce::build(nonce_seed).build(); - let pub_nonce = sec_nonce.public_nonce(); - let nonce_signature = signer_keypair.sign_schnorr(get_nonce_message(&pub_nonce)); - (sec_nonce, pub_nonce, nonce_signature) -} diff --git a/crates/bitvm2-ga/src/operator/api.rs b/crates/bitvm2-ga/src/operator/api.rs index b556e2ee..e69de29b 100644 --- a/crates/bitvm2-ga/src/operator/api.rs +++ b/crates/bitvm2-ga/src/operator/api.rs @@ -1,980 +0,0 @@ -use crate::keys::hkdf_derive_bytes; -use crate::types::{ - Bitvm2Graph, Bitvm2GraphParameters, Groth16Proof, OperatorWotsPublicKeys, - OperatorWotsSecretKeys, OperatorWotsSignatures, PublicInputs, VerifyingKey, -}; -use anyhow::{Result, bail}; -use bitcoin::{Address, Amount, Network, PublicKey, ScriptBuf, Transaction, TxIn}; -use bitcoin::{OutPoint, Witness, XOnlyPublicKey, key::Keypair}; -use bitvm::chunk::api::{ - NUM_HASH, NUM_PUBS, NUM_U256, PublicKeys as Groth16WotsPublicKeys, - api_generate_full_tapscripts, api_generate_partial_script, generate_assertions, -}; -use bitvm::signatures::{HASH_LEN, WinternitzSecret, Wots, Wots16, Wots32}; -use goat::connectors::assert_connectors::generate_chunked_assert_commit_connectors; -use goat::connectors::connector_0::Connector0; -use goat::connectors::connector_a::ConnectorA; -use goat::connectors::connector_b::ConnectorB; -use goat::connectors::connector_c::ConnectorC; -use goat::connectors::connector_d::ConnectorD; -use goat::connectors::connector_e::ConnectorE; -use goat::connectors::connector_f::ConnectorF; -use goat::connectors::connector_g::ConnectorG; -use goat::connectors::kickoff_connectors::{ - ForceSkipConnector, GuardianConnector, KickoffConnector, PrekickoffConnector, -}; -use goat::connectors::watchtower_connectors::{ - AckConnector, WatchctowerConnectors, WatchtowerChallengeConnector, -}; -use goat::constants::{ - CONNECTOR_A_TIMELOCK, CONNECTOR_D_TIMELOCK, CONNECTOR_F_TIMELOCK, WATCHTOWER_CHALLENGE_TIMELOCK, -}; -use goat::disprove_scripts::{ - ChallengeHashType, NUM_GUEST, NUM_GUEST_PUBS_ASSERT, NUM_GUEST_PUBS_EXTRA, hash160, - verify_guest_pubin, -}; -use goat::transactions::assert::{ - AssertCommitTimeoutTransaction, AssertInitTransaction, operator_commit_proof, -}; -use goat::transactions::base::{DUST_AMOUNT, Input}; -use goat::transactions::challenge::ChallengeTransaction; -use goat::transactions::kickoff::KickoffTransaction; -use goat::transactions::pre_signed::PreSignedTransaction; -use goat::transactions::prekickoff::{ - ChallengeIncompleteKickoffTransaction, ForceSkipKickoffTransaction, PrekickoffTransaction, - QuickChallengeTransaction, operator_skip_kickoff, -}; -use goat::transactions::take1::Take1Transaction; -use goat::transactions::take2::Take2Transaction; -use goat::transactions::watchtower_challenge::{ - BlockhashCommitTimeoutTransaction, NackTransaction, WatchtowerChallengeInitTransaction, - WatchtowerChallengeTimeoutTransaction, operator_ack, operator_commit_blockhash, -}; -use goat::utils::num_blocks_per_network; -use hex::encode as hex_encode; - -const OPERATOR_WOTS_HKDF_SALT: &[u8] = b"bitvm2/operator-wots/v1"; - -pub fn generate_wots_keys(seed: &str) -> (OperatorWotsSecretKeys, OperatorWotsPublicKeys) { - let secrets = wots_seed_to_secrets(seed); - let pubkeys = wots_secrets_to_pubkeys(&secrets); - (secrets, pubkeys) -} - -pub fn operator_presig_num() -> usize { - 6 -} - -#[allow(deprecated)] -pub fn wots_secrets_to_pubkeys(secrets: &OperatorWotsSecretKeys) -> OperatorWotsPublicKeys { - let mut index = 0; - - let mut guest_extra = vec![]; - for _ in 0..NUM_GUEST_PUBS_EXTRA { - guest_extra.push(Wots32::generate_public_key(&secrets[index])); - index += 1; - } - - let mut guest_assert = vec![]; - for _ in 0..NUM_GUEST_PUBS_ASSERT { - guest_assert.push(Wots32::generate_public_key(&secrets[index])); - index += 1; - } - - let mut pubins = vec![]; - for _ in 0..NUM_PUBS { - pubins.push(Wots32::generate_public_key(&secrets[index])); - index += 1; - } - let mut fq_arr = vec![]; - for _ in 0..NUM_U256 { - fq_arr.push(Wots32::generate_public_key(&secrets[index])); - index += 1; - } - let mut h_arr = vec![]; - for _ in 0..NUM_HASH { - h_arr.push(Wots16::generate_public_key(&secrets[index])); - index += 1; - } - - let g16_wotspubkey: Groth16WotsPublicKeys = - (pubins.try_into().unwrap(), fq_arr.try_into().unwrap(), h_arr.try_into().unwrap()); - (guest_extra.try_into().unwrap(), guest_assert.try_into().unwrap(), Box::new(g16_wotspubkey)) -} - -#[allow(deprecated)] -pub fn wots_seed_to_secrets(seed: &str) -> OperatorWotsSecretKeys { - let seed_bytes = seed.as_bytes(); - let wot32_seckeys = (0..NUM_GUEST + NUM_PUBS + NUM_U256) - .map(|idx| { - let sec_i = hex_encode(hkdf_derive_bytes( - seed_bytes, - OPERATOR_WOTS_HKDF_SALT, - format!("wots32/{idx}").as_bytes(), - 32, - )); - let sec_str = format!("{sec_i}{:04x}{:04x}", 1, idx); - Wots32::secret_from_str(&sec_str) - }) - .collect::>(); - let wot16_seckeys = (0..NUM_HASH) - .map(|idx| { - let sec_i = hex_encode(hkdf_derive_bytes( - seed_bytes, - OPERATOR_WOTS_HKDF_SALT, - format!("wots16/{idx}").as_bytes(), - 32, - )); - let sec_str = format!("{sec_i}{:04x}{:04x}", 0, idx); - Wots16::secret_from_str(&sec_str) - }) - .collect::>(); - - Box::new([wot32_seckeys, wot16_seckeys].concat().try_into().unwrap()) -} - -pub fn generate_partial_scripts(ark_vkey: &VerifyingKey) -> Vec { - api_generate_partial_script(ark_vkey) -} - -pub fn generate_disprove_scripts( - partial_scripts: &[ScriptBuf], - wots_pubkeys: OperatorWotsPublicKeys, - guest_constant_value: &[u8; 32], - watchtower_hashlocks: &Vec, -) -> (Vec, Vec) { - let (guest_pubkeys_0, guest_pubkeys_1, proof_pubkeys) = wots_pubkeys; - let mut guest_pubin_wots_pubkeys = guest_pubkeys_0.to_vec(); - guest_pubin_wots_pubkeys.extend(guest_pubkeys_1); - let guest_pubin_wots_pubkeys = guest_pubin_wots_pubkeys.try_into().unwrap(); - let guest_pubin_scripts = verify_guest_pubin( - &guest_pubin_wots_pubkeys, - &proof_pubkeys.0, - guest_constant_value, - watchtower_hashlocks, - ); - let guest_pubin_scripts = guest_pubin_scripts.into_iter().map(|s| s.compile()).collect(); - let proof_scripts = api_generate_full_tapscripts(*proof_pubkeys, partial_scripts); - (guest_pubin_scripts, proof_scripts) -} - -#[allow(deprecated)] -pub fn corrupt_proof( - sigs: &mut OperatorWotsSignatures, - wots_sec: &OperatorWotsSecretKeys, - index: usize, -) { - let mut scramble: [u8; 32] = [1u8; 32]; - scramble[16] = 37; - let mut scramble2: [u8; HASH_LEN] = [1u8; HASH_LEN]; - scramble2[HASH_LEN / 2] = 37; - println!("corrupted assertion at index {index}"); - let sec_index = index + NUM_GUEST; - if index < NUM_PUBS { - let i = index; - let assn = scramble; - let sig = Wots32::sign(&wots_sec[sec_index], &assn); - sigs.1.0[i] = sig; - } else if index < NUM_PUBS + NUM_U256 { - let i = index - NUM_PUBS; - let assn = scramble; - let sig = Wots32::sign(&wots_sec[sec_index], &assn); - sigs.1.1[i] = sig; - } else if index < NUM_PUBS + NUM_U256 + NUM_HASH { - let i = index - NUM_PUBS - NUM_U256; - let assn = scramble2; - let sig = Wots16::sign(&wots_sec[sec_index], &assn); - sigs.1.2[i] = sig; - } -} - -pub fn generate_bitvm_graph( - params: Bitvm2GraphParameters, - disprove_scripts: Vec, -) -> Result { - let network = params.instance_parameters.network; - let operator_pubkey = params.operator_pubkey; - let operator_taproot_public_key = XOnlyPublicKey::from(operator_pubkey); - let (connector_e, _) = - ConnectorE::new_with_scripts(network, &operator_taproot_public_key, disprove_scripts); - generate_bitvm_graph_inner(params, connector_e) -} - -pub(crate) fn generate_bitvm_graph_inner( - params: Bitvm2GraphParameters, - connector_e: ConnectorE, -) -> Result { - // TODO: check parameters? - let network = params.instance_parameters.network; - let operator_pubkey = params.operator_pubkey; - let operator_taproot_public_key = XOnlyPublicKey::from(operator_pubkey); - let n_of_n_taproot_public_key = - XOnlyPublicKey::from(params.instance_parameters.committee_agg_pubkey); - let watchtower_num = params.watchtower_pubkeys.len(); - let assert_wots_pubkeys = (params.operator_wots_pubkeys.1, *params.operator_wots_pubkeys.2); - let assert_commit_connectors = generate_chunked_assert_commit_connectors( - network, - &n_of_n_taproot_public_key, - assert_wots_pubkeys, - ); - let assert_commit_num = assert_commit_connectors.len(); - - // pegin - let (_, pegin, _) = params.instance_parameters.build_pegin_tx()?; - let pegin_txid = pegin.tx().compute_txid(); - let connector_0_input = Input { - outpoint: OutPoint { txid: pegin_txid, vout: 0 }, - amount: pegin.tx().output[0].value, - }; - - // prekickoff - let cur_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); - let next_force_skip_connector = ForceSkipConnector::new(network, &operator_taproot_public_key); - let next_kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); - let next_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); - let cur_prekickoff = params.prekickoff_parameters.cur_prekickoff_txn.clone(); - let cur_prekickoff_txid = cur_prekickoff.tx().compute_txid(); - let cur_prekickoff_connector_input = Input { - outpoint: OutPoint { txid: cur_prekickoff_txid, vout: 2 }, - amount: cur_prekickoff.tx().output[2].value, - }; - let next_prekickoff = PrekickoffTransaction::new_for_validation( - &cur_prekickoff_connector, - &next_force_skip_connector, - &next_kickoff_connector, - &next_prekickoff_connector, - cur_prekickoff_connector_input, - params.prekickoff_parameters.replenish_fee_inputs.clone(), - params.prekickoff_parameters.replenish_fee_prev_outs.clone(), - params.prekickoff_parameters.fee_amount, - watchtower_num, - assert_commit_num, - ) - .map_err(|e| anyhow::anyhow!("failed to create pre-kickoff txn: {e}"))?; - let next_prekickoff_txid = next_prekickoff.tx().compute_txid(); - let next_force_skip_connector_input = Input { - outpoint: OutPoint { txid: next_prekickoff_txid, vout: 0 }, - amount: cur_prekickoff.tx().output[0].value, - }; - let next_prekickoff_connector_input = Input { - outpoint: OutPoint { txid: next_prekickoff_txid, vout: 2 }, - amount: cur_prekickoff.tx().output[2].value, - }; - - // kickoff - let kickoff_connector_input = Input { - outpoint: OutPoint { txid: cur_prekickoff_txid, vout: 1 }, - amount: cur_prekickoff.tx().output[1].value, - }; - let kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); - let connector_a = - ConnectorA::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); - let connector_b = ConnectorB::new(network, &operator_taproot_public_key); - let connector_c = ConnectorC::new(network, &operator_taproot_public_key); - let guardian_connector = GuardianConnector::new(network, &operator_taproot_public_key); - let kickoff = KickoffTransaction::new_for_validation( - &kickoff_connector, - &connector_a, - &connector_b, - &connector_c, - &connector_e, - &guardian_connector, - &kickoff_connector_input, - watchtower_num, - assert_commit_num, - ) - .map_err(|e| anyhow::anyhow!("failed to create kickoff txn: {e}"))?; - let kickoff_txid = kickoff.tx().compute_txid(); - let connector_a_input = Input { - outpoint: OutPoint { txid: kickoff_txid, vout: 0 }, - amount: kickoff.tx().output[0].value, - }; - let connector_b_input = Input { - outpoint: OutPoint { txid: kickoff_txid, vout: 1 }, - amount: kickoff.tx().output[1].value, - }; - let connector_c_input = Input { - outpoint: OutPoint { txid: kickoff_txid, vout: 2 }, - amount: kickoff.tx().output[2].value, - }; - let connector_e_input = Input { - outpoint: OutPoint { txid: kickoff_txid, vout: 3 }, - amount: kickoff.tx().output[3].value, - }; - let guardian_connector_input = Input { - outpoint: OutPoint { txid: kickoff_txid, vout: 4 }, - amount: kickoff.tx().output[4].value, - }; - - // prekickoff challenge - let force_skip_kickoff = ForceSkipKickoffTransaction::new_for_validation( - &kickoff_connector, - &next_force_skip_connector, - kickoff_connector_input, - next_force_skip_connector_input.clone(), - ); - let quick_challenge = QuickChallengeTransaction::new_for_validation( - &guardian_connector, - &next_force_skip_connector, - guardian_connector_input.clone(), - next_force_skip_connector_input, - ); - let challenge_incomplete_kickoff = ChallengeIncompleteKickoffTransaction::new_for_validation( - &guardian_connector, - &next_prekickoff_connector, - guardian_connector_input.clone(), - next_prekickoff_connector_input, - ); - - // take-1 - let connector_0 = Connector0::new(network, &n_of_n_taproot_public_key); - let take1 = Take1Transaction::new_for_validation( - &connector_0, - &connector_a, - &connector_b, - &connector_c, - &guardian_connector, - connector_0_input.clone(), - connector_a_input.clone(), - connector_b_input.clone(), - connector_c_input.clone(), - guardian_connector_input.clone(), - ¶ms.operator_receive_address, - ) - .map_err(|e| anyhow::anyhow!("failed to create take-1 txn: {e}"))?; - - // challenge - let challenge = ChallengeTransaction::new_for_validation( - &connector_a, - connector_a_input, - params.challenge_amount, - ¶ms.operator_receive_address, - ); - - // watchtower-challenge-init - let connector_g = ConnectorG::new( - network, - &n_of_n_taproot_public_key, - &operator_taproot_public_key, - ¶ms.operator_wots_pubkeys.0[0], - ); - let connector_f = - ConnectorF::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); - let watchtower_connectors_array = (0..watchtower_num) - .map(|i| { - ( - WatchtowerChallengeConnector::new( - network, - &operator_taproot_public_key, - ¶ms.watchtower_pubkeys[i], - ), - AckConnector::new(network, &n_of_n_taproot_public_key, ¶ms.hashlocks[i]), - ) - }) - .collect::>(); - let watchtower_challenge_init = WatchtowerChallengeInitTransaction::new_for_validation( - &connector_b, - &connector_g, - &connector_f, - &watchtower_connectors_array, - connector_b_input, - ) - .map_err(|e| anyhow::anyhow!("failed to create watchtower-challenge-init txn: {e}"))?; - let watchtower_challenge_init_txid = watchtower_challenge_init.tx().compute_txid(); - let connector_g_vout = watchtower_num * 2; - let connector_g_input = Input { - outpoint: OutPoint { txid: watchtower_challenge_init_txid, vout: connector_g_vout as u32 }, - amount: watchtower_challenge_init.tx().output[connector_g_vout].value, - }; - let connector_f_vout = connector_g_vout + 1; - let connector_f_input = Input { - outpoint: OutPoint { txid: watchtower_challenge_init_txid, vout: connector_f_vout as u32 }, - amount: watchtower_challenge_init.tx().output[connector_f_vout].value, - }; - - // watchtower-challenge-timeout & nack - let mut watchtower_challenge_timeout_txns = vec![]; - let mut nack_txns = vec![]; - for (i, watchtower_connectors) in watchtower_connectors_array.iter().enumerate() { - let watchtower_challenge_connector_input_vout: usize = i * 2; - let watchtower_challenge_connector_input = Input { - outpoint: OutPoint { - txid: watchtower_challenge_init_txid, - vout: watchtower_challenge_connector_input_vout as u32, - }, - amount: watchtower_challenge_init.tx().output - [watchtower_challenge_connector_input_vout] - .value, - }; - let ack_connector_input_vout: usize = i * 2 + 1; - let ack_connector_input = Input { - outpoint: OutPoint { - txid: watchtower_challenge_init_txid, - vout: ack_connector_input_vout as u32, - }, - amount: watchtower_challenge_init.tx().output[ack_connector_input_vout].value, - }; - let watchtower_challenge_timeout_tx = - WatchtowerChallengeTimeoutTransaction::new_for_validation( - watchtower_connectors, - watchtower_challenge_connector_input, - ack_connector_input.clone(), - ); - let nack_tx = NackTransaction::new_for_validation( - watchtower_connectors, - &connector_f, - ack_connector_input, - connector_f_input.clone(), - ); - watchtower_challenge_timeout_txns.push(watchtower_challenge_timeout_tx); - nack_txns.push(nack_tx); - } - - // operator-commit-blockhash-timeout - let blockhash_commit_timeout = BlockhashCommitTimeoutTransaction::new_for_validation( - &connector_g, - &connector_f, - connector_g_input.clone(), - connector_f_input.clone(), - ); - - // assert-init - let connector_d = - ConnectorD::new(network, &operator_taproot_public_key, &n_of_n_taproot_public_key); - let assert_init = AssertInitTransaction::new_for_validation( - &connector_c, - &connector_d, - &assert_commit_connectors, - &connector_c_input, - ) - .map_err(|e| anyhow::anyhow!("failed to create assert-init txn: {e}"))?; - let assert_init_txid = assert_init.tx().compute_txid(); - let connector_d_vout: usize = assert_commit_connectors.len(); - let connector_d_input = Input { - outpoint: OutPoint { txid: assert_init_txid, vout: connector_d_vout as u32 }, - amount: assert_init.tx().output[connector_d_vout].value, - }; - - // assert-commit-timeout - let mut assert_commit_timeout_txns = vec![]; - for (i, assert_commit_connector) in assert_commit_connectors.iter().enumerate() { - let assert_commit_timeout_input_0_vout: usize = i; - let assert_commit_timeout_input_0 = Input { - outpoint: OutPoint { - txid: assert_init_txid, - vout: assert_commit_timeout_input_0_vout as u32, - }, - amount: assert_init.tx().output[assert_commit_timeout_input_0_vout].value, - }; - let assert_commit_timeout_tx = AssertCommitTimeoutTransaction::new_for_validation( - assert_commit_connector, - &connector_d, - &assert_commit_timeout_input_0, - &connector_d_input, - ); - assert_commit_timeout_txns.push(assert_commit_timeout_tx); - } - - // take-2 - let take2 = Take2Transaction::new_for_validation( - &connector_0, - &connector_d, - &connector_e, - &connector_f, - &guardian_connector, - connector_0_input, - connector_d_input, - connector_e_input, - connector_f_input, - guardian_connector_input, - ¶ms.operator_receive_address, - ) - .map_err(|e| anyhow::anyhow!("failed to create take-2 txn: {e}"))?; - - Ok(Bitvm2Graph { - operator_pre_signed: false, - committee_pre_signed: false, - parameters: params, - - cur_prekickoff, - next_prekickoff, - force_skip_kickoff, - quick_challenge, - challenge_incomplete_kickoff, - - pegin, - kickoff, - take1, - challenge, - take2, - - watchtower_challenge_init, - watchtower_challenge_timeout_txns, - nack_txns, - blockhash_commit_timeout, - - assert_init, - assert_commit_timeout_txns, - - connector_e, - }) -} - -pub fn operator_pre_sign( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result> { - let keypair_pubkey = PublicKey::from(operator_keypair.public_key()); - if keypair_pubkey != graph.parameters.operator_pubkey { - bail!("operator keypair does not match graph operator pubkey".to_string()) - }; - - let mut wits = vec![]; - let context = graph.parameters.get_operator_context(operator_keypair)?; - let network = context.network; - let operator_taproot_public_key = context.operator_taproot_public_key; - - // presign force_skip_kickoff - let kickoff_connector = KickoffConnector::new(network, &operator_taproot_public_key); - let next_force_skip_connector = ForceSkipConnector::new(network, &operator_taproot_public_key); - graph.force_skip_kickoff.pre_sign_and_push( - &context, - &kickoff_connector, - &next_force_skip_connector, - ); - wits.push(graph.force_skip_kickoff.tx().input[0].witness.clone()); - wits.push(graph.force_skip_kickoff.tx().input[1].witness.clone()); - - // presign quick_challenge - let guardian_connector = GuardianConnector::new(network, &operator_taproot_public_key); - graph.quick_challenge.pre_sign_and_push( - &context, - &guardian_connector, - &next_force_skip_connector, - ); - wits.push(graph.quick_challenge.tx().input[0].witness.clone()); - wits.push(graph.quick_challenge.tx().input[1].witness.clone()); - - // presign challenge_incomplete_kickoff - let next_prekickoff_connector = PrekickoffConnector::new(network, &operator_taproot_public_key); - graph.challenge_incomplete_kickoff.pre_sign_and_push( - &context, - &guardian_connector, - &next_prekickoff_connector, - ); - wits.push(graph.challenge_incomplete_kickoff.tx().input[0].witness.clone()); - wits.push(graph.challenge_incomplete_kickoff.tx().input[1].witness.clone()); - - graph.operator_pre_signed = true; - Ok(wits) -} - -pub fn push_operator_pre_signature( - graph: &mut Bitvm2Graph, - signed_witness: &[Witness], -) -> Result<()> { - if graph.operator_pre_signed { - bail!("already pre-signed by operator".to_string()) - }; - if signed_witness.len() != operator_presig_num() { - bail!("invalid number of pre-signatures".to_string()) - }; - - graph.force_skip_kickoff.tx_mut().input[0].witness = signed_witness[0].clone(); - graph.force_skip_kickoff.tx_mut().input[1].witness = signed_witness[1].clone(); - graph.quick_challenge.tx_mut().input[0].witness = signed_witness[2].clone(); - graph.quick_challenge.tx_mut().input[1].witness = signed_witness[3].clone(); - graph.challenge_incomplete_kickoff.tx_mut().input[0].witness = signed_witness[4].clone(); - graph.challenge_incomplete_kickoff.tx_mut().input[1].witness = signed_witness[5].clone(); - - graph.operator_pre_signed = true; - Ok(()) -} - -/// remember to sign replensish inputs (if any) after this -pub fn operator_sign_prekickoff_input_0( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result { - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let prev_prekickoff_connector = PrekickoffConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - ); - graph.cur_prekickoff.sign_input_0(&operator_context, &prev_prekickoff_connector); - Ok(graph.cur_prekickoff.tx().clone()) -} - -pub fn operator_sign_skip_kickoff( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, - operator_receive_address: Address, - fee_rate: f64, -) -> Result> { - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let kickoff_connector = KickoffConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - ); - let kickoff_connector_input = Input { - outpoint: OutPoint { txid: graph.cur_prekickoff.tx().compute_txid(), vout: 1 }, - amount: graph.cur_prekickoff.tx().output[1].value, - }; - // create a sample tx to estimate fee - let sample_tx = operator_skip_kickoff( - &operator_context, - &kickoff_connector, - kickoff_connector_input.clone(), - Amount::ZERO, - operator_receive_address.clone(), - ) - .map_err(|e| anyhow::anyhow!("failed to create sample skip-kickoff txn: {e}"))?; - - let fee_amount = - Amount::from_sat((sample_tx.weight().to_vbytes_ceil() as f64 * fee_rate).ceil() as u64); - if fee_amount + Amount::from_sat(DUST_AMOUNT) >= kickoff_connector_input.amount { - // if fee_amount > input_amount - dust_amount, skip-kickoff tx is meaningless - return Ok(None); - } - match operator_skip_kickoff( - &operator_context, - &kickoff_connector, - kickoff_connector_input, - fee_amount, - operator_receive_address, - ) { - Ok(tx) => Ok(Some(tx)), - Err(e) => bail!("failed to create skip-kickoff txn: {e}"), - } -} - -pub fn operator_sign_kickoff( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result { - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let kickoff_connector = KickoffConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - ); - graph.kickoff.sign_input_0(&operator_context, &kickoff_connector); - Ok(graph.kickoff.tx().clone()) -} - -pub fn operator_sign_take1( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result { - if !graph.committee_pre_signed() { - bail!("missing pre-signatures from committee".to_string()) - }; - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let connector_a = ConnectorA::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - &operator_context.n_of_n_taproot_public_key, - ); - let connector_b = - ConnectorB::new(operator_context.network, &operator_context.operator_taproot_public_key); - let connector_c = - ConnectorC::new(operator_context.network, &operator_context.operator_taproot_public_key); - let guardian_connector = GuardianConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - ); - graph.take1.sign_input_1(&operator_context, &connector_a); - graph.take1.sign_input_2(&operator_context, &connector_b); - graph.take1.sign_input_3(&operator_context, &connector_c); - graph.take1.sign_input_4(&operator_context, &guardian_connector); - Ok(graph.take1.tx().clone()) -} - -pub fn operator_sign_take2( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result { - if !graph.committee_pre_signed() { - bail!("missing pre-signatures from committee".to_string()) - }; - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let connector_d = ConnectorD::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - &operator_context.n_of_n_taproot_public_key, - ); - let connector_f = ConnectorF::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - &operator_context.n_of_n_taproot_public_key, - ); - let guardian_connector = GuardianConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - ); - graph.take2.sign_input_1(&operator_context, &connector_d); - graph.take2.sign_input_2(&operator_context, &graph.connector_e); - graph.take2.sign_input_3(&operator_context, &connector_f); - graph.take2.sign_input_4(&operator_context, &guardian_connector); - Ok(graph.take2.tx().clone()) -} - -pub fn operator_sign_watchtower_challenge_init( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result { - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let connector_b = - ConnectorB::new(operator_context.network, &operator_context.operator_taproot_public_key); - graph.watchtower_challenge_init.sign_input_0(&operator_context, &connector_b); - Ok(graph.watchtower_challenge_init.tx().clone()) -} - -pub fn operator_sign_watchtower_challenge_timeout( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, - watchtower_index: usize, -) -> Result { - if !graph.committee_pre_signed() { - bail!("missing pre-signatures from committee".to_string()) - }; - if watchtower_index >= graph.parameters.watchtower_pubkeys.len() { - bail!("invalid watchtower index {watchtower_index}".to_string()) - }; - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let watchtower_challenge_connector = WatchtowerChallengeConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - &graph.parameters.watchtower_pubkeys[watchtower_index], - ); - let ack_connector = AckConnector::new( - operator_context.network, - &operator_context.n_of_n_taproot_public_key, - &graph.parameters.hashlocks[watchtower_index], - ); - let watchtower_connectors = (watchtower_challenge_connector, ack_connector); - graph.watchtower_challenge_timeout_txns[watchtower_index] - .sign_input_0(&operator_context, &watchtower_connectors); - Ok(graph.watchtower_challenge_timeout_txns[watchtower_index].tx().clone()) -} - -pub fn operator_sign_ack( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, - watchtower_index: usize, - preimage: &Vec, -) -> Result<(TxIn, Amount)> { - if hash160(preimage) != graph.parameters.hashlocks[watchtower_index] { - bail!("invalid preimage for watchtower index {watchtower_index}".to_string()) - }; - if watchtower_index >= graph.parameters.watchtower_pubkeys.len() { - bail!("invalid watchtower index {watchtower_index}".to_string()) - }; - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let watchtower_challenge_connector = WatchtowerChallengeConnector::new( - operator_context.network, - &operator_context.operator_taproot_public_key, - &graph.parameters.watchtower_pubkeys[watchtower_index], - ); - let ack_connector = AckConnector::new( - operator_context.network, - &operator_context.n_of_n_taproot_public_key, - &graph.parameters.hashlocks[watchtower_index], - ); - let watchtower_connectors = (watchtower_challenge_connector, ack_connector); - let ack_vout = watchtower_index * 2 + 1; - let ack_input = Input { - outpoint: OutPoint { - txid: graph.watchtower_challenge_init.tx().compute_txid(), - vout: ack_vout as u32, - }, - amount: graph.watchtower_challenge_init.tx().output[ack_vout].value, - }; - match operator_ack(&watchtower_connectors, preimage, ack_input.clone()) { - Ok(txin) => Ok((txin, ack_input.amount)), - Err(e) => bail!("failed to sign ack for watchtower index {watchtower_index}: {e}"), - } -} - -pub fn operator_sign_blockhash_commit( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, - latest_blockhash: &[u8; 32], - wots_secret_key: &WinternitzSecret, -) -> Result<(TxIn, Amount)> { - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let blockhash_wots_pubkey = graph.parameters.operator_wots_pubkeys.0[0]; - if Wots32::generate_public_key(wots_secret_key) != blockhash_wots_pubkey { - bail!("provided WOTS secret key does not match expected public key".to_string()) - }; - let connector_g = ConnectorG::new( - operator_context.network, - &operator_context.n_of_n_taproot_public_key, - &operator_context.operator_taproot_public_key, - &blockhash_wots_pubkey, - ); - let connector_g_vout = 2 * graph.parameters.watchtower_pubkeys.len() as u64; - let connector_g_input = Input { - outpoint: OutPoint { - txid: graph.watchtower_challenge_init.tx().compute_txid(), - vout: connector_g_vout as u32, - }, - amount: graph.watchtower_challenge_init.tx().output[connector_g_vout as usize].value, - }; - match operator_commit_blockhash( - &connector_g, - latest_blockhash, - wots_secret_key, - connector_g_input.clone(), - ) { - Ok(txin) => Ok((txin, connector_g_input.amount)), - Err(e) => bail!("failed to sign blockhash commit: {e}"), - } -} - -pub fn operator_sign_assert_init( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, -) -> Result { - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - let connector_c = - ConnectorC::new(operator_context.network, &operator_context.operator_taproot_public_key); - graph.assert_init.sign_input_0(&operator_context, &connector_c); - Ok(graph.assert_init.tx().clone()) -} - -pub fn operator_sign_assert_commit( - operator_keypair: Keypair, - graph: &mut Bitvm2Graph, - wots_secret_keys: &OperatorWotsSecretKeys, - guest_inputs: [[u8; 32]; NUM_GUEST_PUBS_ASSERT], - proof: Groth16Proof, - groth16_pubin: PublicInputs, - vk: &VerifyingKey, -) -> Result> { - println!("operator_send_assert_commit start operator_sign_assert_commit"); - let operator_context = graph.parameters.get_operator_context(operator_keypair)?; - if !is_valid_wots_secrets(wots_secret_keys, &graph.parameters.operator_wots_pubkeys) { - bail!("provided WOTS secret keys do not match expected public keys".to_string()) - }; - let assert_wots_pubkeys = - (graph.parameters.operator_wots_pubkeys.1, *graph.parameters.operator_wots_pubkeys.2); - let assert_commit_connectors = generate_chunked_assert_commit_connectors( - operator_context.network, - &operator_context.n_of_n_taproot_public_key, - assert_wots_pubkeys, - ); - let mut assert_commit_inputs = vec![]; - for i in 0..assert_commit_connectors.len() { - let vout = i; - let input = Input { - outpoint: OutPoint { txid: graph.assert_init.tx().compute_txid(), vout: vout as u32 }, - amount: graph.assert_init.tx().output[vout].value, - }; - assert_commit_inputs.push(input); - } - let assert_assertions = ( - guest_inputs, - generate_assertions(proof, groth16_pubin, vk) - .map_err(|e| anyhow::anyhow!("failed to generate assertions: {e}"))?, - ); - match operator_commit_proof( - &assert_commit_connectors, - &wots_secret_keys[1..].to_vec(), - &assert_commit_inputs, - &assert_assertions, - ) { - Ok(txins) => Ok(txins - .into_iter() - .enumerate() - .map(|(i, txin)| (txin, assert_commit_inputs[i].amount)) - .collect::>()), - Err(e) => bail!("failed to sign assert commit: {e}"), - } -} - -pub fn is_valid_wots_secrets( - wots_seckeys: &OperatorWotsSecretKeys, - expected_pubkeys: &OperatorWotsPublicKeys, -) -> bool { - // let generated_pubkeys = wots_secrets_to_pubkeys(wots_seckeys); - // &generated_pubkeys == expected_pubkeys - - // Compare each generated WOTS public key against the expected public keys - // without allocating large temporaries. This keeps stack usage small. - let (guest_extra_expected, guest_assert_expected, proof_pubkeys_box) = expected_pubkeys; - let proof_pubkeys = proof_pubkeys_box.as_ref(); - - let mut idx: usize = 0; - - // guest_extra (Wots32) - for expected in guest_extra_expected.iter() { - let generated = Wots32::generate_public_key(&wots_seckeys[idx]); - if &generated != expected { - return false; - } - idx += 1; - } - - // guest_assert (Wots32) - for expected in guest_assert_expected.iter() { - let generated = Wots32::generate_public_key(&wots_seckeys[idx]); - if &generated != expected { - return false; - } - idx += 1; - } - - // proof pubins (Wots32) - for expected in proof_pubkeys.0.iter() { - let generated = Wots32::generate_public_key(&wots_seckeys[idx]); - if &generated != expected { - return false; - } - idx += 1; - } - - // proof fq_arr (Wots32) - for expected in proof_pubkeys.1.iter() { - let generated = Wots32::generate_public_key(&wots_seckeys[idx]); - if &generated != expected { - return false; - } - idx += 1; - } - - // proof h_arr (Wots16) - for expected in proof_pubkeys.2.iter() { - let generated = Wots16::generate_public_key(&wots_seckeys[idx]); - if &generated != expected { - return false; - } - idx += 1; - } - - true -} - -pub fn take1_timelock(network: Network) -> u32 { - num_blocks_per_network(network, CONNECTOR_A_TIMELOCK) -} - -/// take2 has two timelocks, relative to (watchtower_challenge_init, assert_init) -pub fn take2_timelocks(network: Network) -> (u32, u32) { - ( - num_blocks_per_network(network, CONNECTOR_F_TIMELOCK) - + if network == Network::Testnet { 24 } else { 0 } // Testnet extra delay - + if network == Network::Testnet4 { 48 } else { 0 } // Testnet4 extra delay - + if network == Network::Regtest { 6 } else { 0 }, // Regtest extra delay - num_blocks_per_network(network, CONNECTOR_D_TIMELOCK) - + if network == Network::Testnet { 6 } else { 0 } // Testnet extra delay - + if network == Network::Testnet4 { 100 } else { 0 } // Testnet4 extra delay - + if network == Network::Regtest { 6 } else { 0 }, // Regtest extra delay - ) -} - -pub fn watchtower_challenge_timeout_timelock(network: Network) -> u32 { - num_blocks_per_network(network, WATCHTOWER_CHALLENGE_TIMELOCK) - + if network == Network::Testnet { 12 } else { 0 } // Testnet extra delay - + if network == Network::Testnet4 { 20 } else { 0 } // Testnet4 extra delay - + if network == Network::Regtest { 2 } else { 0 } // Regtest extra delay -} diff --git a/crates/bitvm2-ga/src/tests.rs b/crates/bitvm2-ga/src/tests.rs index 0d5867a8..e69de29b 100644 --- a/crates/bitvm2-ga/src/tests.rs +++ b/crates/bitvm2-ga/src/tests.rs @@ -1,1497 +0,0 @@ -#![allow(dead_code)] - -#[cfg(test)] -#[cfg(feature = "ci-tests")] -mod tests { - use crate::{challenger::*, committee::*, keys::*, operator::*, types::*, watchtower::*}; - use bitcoin::{ - Address, Amount, EcdsaSighashType, Network, OutPoint, PublicKey, ScriptBuf, TapSighashType, - Transaction, TxIn, TxOut, Txid, XOnlyPublicKey, hashes::Hash, key::Keypair, - }; - use bitcoincore_rpc::{Auth, Client as BtcdClient, RpcApi}; - use bitvm::{ - chunk::api::{NUM_HASH, NUM_PUBS, NUM_U256}, - treepp::*, - }; - use core::panic; - use esplora_client::{AsyncClient as EsploraClient, Builder}; - use goat::{ - connectors::{ - assert_connectors::chunk_assert_commit, - base::TaprootConnector, - connector_z::ConnectorZ, - kickoff_connectors::{ForceSkipConnector, KickoffConnector, PrekickoffConnector}, - }, - constants::CONNECTOR_Z_TIMELOCK, - contexts::{base::generate_n_of_n_public_key, operator::OperatorContext}, - disprove_scripts::{NUM_GUEST_PUBS_ASSERT, hash160}, - scripts::generate_opreturn_script, - transactions::{ - base::{BaseTransaction, DUST_AMOUNT, Input}, - pre_signed::{PreSignedTransaction, pre_sign_taproot_input_default}, - prekickoff::PrekickoffTransaction, - signing::populate_p2wsh_witness, - }, - utils::num_blocks_per_network, - }; - use musig2::PubNonce; - use secp256k1::SECP256K1; - use sha2::{Digest, Sha256}; - use std::path::PathBuf; - use std::{time::Duration, vec}; - use tokio::time::sleep; - use uuid::Uuid; - - fn network() -> Network { - const ENV_KEY: &str = "BITVM_NETWORK"; - match std::env::var(ENV_KEY) { - Ok(v) => match v.to_lowercase().as_str() { - "regtest" => Network::Regtest, - "testnet" => Network::Testnet, - "testnet4" => Network::Testnet4, - "signet" => Network::Signet, - "bitcoin" | "mainnet" => Network::Bitcoin, - _ => Network::Regtest, - }, - Err(_) => Network::Regtest, - } - } - - fn set_network(n: Network) { - const ENV_KEY: &str = "BITVM_NETWORK"; - let v = match n { - Network::Regtest => "regtest", - Network::Testnet => "testnet", - Network::Testnet4 => "testnet4", - Network::Signet => "signet", - Network::Bitcoin => "bitcoin", - }; - unsafe { - std::env::set_var(ENV_KEY, v); - } - } - - fn fee_rate() -> f64 { - 1.0 // sat/vbyte - } - - async fn get_esplora_client() -> EsploraClient { - match network() { - Network::Regtest => Builder::new("http://127.0.0.1:3002").build_async().unwrap(), - Network::Testnet => { - Builder::new("https://mempool.space/testnet/api").build_async().unwrap() - } - _ => panic!("Mainnet is not supported in tests"), - } - } - - fn get_btcd_client() -> BtcdClient { - if network() != Network::Regtest { - panic!("get_btcd_client only supports Regtest"); - } - BtcdClient::new( - "http://127.0.0.1:18443", - Auth::UserPass("111111".to_string(), "111111".to_string()), - ) - .unwrap() - } - - fn gen_keypair(secret: &str) -> Keypair { - if !secret.starts_with("seed:") { - return Keypair::from_seckey_str_global(secret).unwrap(); - } - // derive private key from seed - let hashed = Sha256::digest(secret.as_bytes()); - let sk = secp256k1::SecretKey::from_slice(&hashed).unwrap(); - Keypair::from_secret_key(SECP256K1, &sk) - } - - fn bank_keypair() -> Keypair { - gen_keypair("seed:faucet") - } - - async fn fund_address( - client: &EsploraClient, - network: Network, - address: Address, - amount: Amount, - ) -> OutPoint { - // In regtest, we can use btcd rpc to fund address directly - if network == Network::Regtest { - let fund_txid = get_btcd_client() - .send_to_address(&address, amount, None, None, None, None, None, None) - .unwrap(); - wait_tx_confirm(client, fund_txid).await; - let fund_tx = client.get_tx(&fund_txid).await.unwrap().unwrap(); - let vout = fund_tx - .output - .iter() - .position(|o| o.script_pubkey == address.script_pubkey()) - .unwrap() as u32; - return OutPoint { txid: fund_txid, vout }; - } - - fn estimate_fee(num_inputs: usize, fee_rate: f64) -> Amount { - let tx_size = (num_inputs * 148 + 2 * 34 + 10) as f64; - let fee = (tx_size * fee_rate).ceil() as u64; - Amount::from_sat(fee) - } - let mut fund_tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![], - output: vec![bitcoin::TxOut { value: amount, script_pubkey: address.script_pubkey() }], - }; - let bank_pubkey = bank_keypair().public_key().into(); - let bank_address = node_p2wsh_address(network, &bank_pubkey); - let utxos = client.get_address_utxo(bank_address.clone()).await.unwrap(); - let mut sorted_utxos = utxos; - sorted_utxos.sort_by(|a, b| b.value.cmp(&a.value)); - let mut selected = Vec::new(); - let mut total_value = Amount::ZERO; - - for utxo in sorted_utxos.into_iter() { - selected.push(utxo.clone()); - total_value += utxo.value; - fund_tx.input.push(bitcoin::TxIn { - previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }); - if total_value >= amount + estimate_fee(fund_tx.input.len(), fee_rate()) { - break; - } - } - - let change = total_value - amount - estimate_fee(fund_tx.input.len(), fee_rate()); - if change.to_sat() > DUST_AMOUNT { - fund_tx.output.push(bitcoin::TxOut { - value: change, - script_pubkey: bank_address.script_pubkey(), - }); - } - - let script = node_p2wsh_script(&bank_pubkey); - (0..fund_tx.input.len()).for_each(|index| { - let amount = selected[index].value; - populate_p2wsh_witness( - &mut fund_tx, - index, - EcdsaSighashType::All, - &script, - amount, - &vec![&bank_keypair()], - ); - }); - - client.broadcast(&fund_tx).await.unwrap(); - wait_tx_confirm(client, fund_tx.compute_txid()).await; - OutPoint { txid: fund_tx.compute_txid(), vout: 0 } - } - - async fn fund_address_batch( - client: &EsploraClient, - network: Network, - receivers: Vec<(Address, Amount)>, - ) -> Vec { - fn estimate_fee(num_inputs: usize, num_outputs: usize, fee_rate: f64) -> Amount { - let tx_size = (num_inputs * 148 + (num_outputs + 1) * 34 + 10) as f64; - let fee = (tx_size * fee_rate).ceil() as u64; - Amount::from_sat(fee) - } - let mut fund_tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![], - output: vec![], - }; - let output_num = receivers.len(); - let total_output_amount: Amount = receivers.iter().map(|(_, amt)| *amt).sum(); - for (address, amount) in receivers { - fund_tx - .output - .push(bitcoin::TxOut { value: amount, script_pubkey: address.script_pubkey() }); - } - let bank_pubkey = bank_keypair().public_key().into(); - let bank_address = node_p2wsh_address(network, &bank_pubkey); - let utxos = client.get_address_utxo(bank_address.clone()).await.unwrap(); - let mut sorted_utxos = utxos; - sorted_utxos.sort_by(|a, b| b.value.cmp(&a.value)); - let mut selected = Vec::new(); - let mut total_value = Amount::ZERO; - - for utxo in sorted_utxos.into_iter() { - selected.push(utxo.clone()); - total_value += utxo.value; - fund_tx.input.push(bitcoin::TxIn { - previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }); - if total_value - >= total_output_amount + estimate_fee(fund_tx.input.len(), output_num, fee_rate()) - { - break; - } - } - - let change = total_value - - total_output_amount - - estimate_fee(fund_tx.input.len(), output_num, fee_rate()); - if change.to_sat() > DUST_AMOUNT { - fund_tx.output.push(bitcoin::TxOut { - value: change, - script_pubkey: bank_address.script_pubkey(), - }); - } - - let script = node_p2wsh_script(&bank_pubkey); - (0..fund_tx.input.len()).for_each(|index| { - let amount = selected[index].value; - populate_p2wsh_witness( - &mut fund_tx, - index, - EcdsaSighashType::All, - &script, - amount, - &vec![&bank_keypair()], - ); - }); - - client.broadcast(&fund_tx).await.unwrap(); - wait_tx_confirm(client, fund_tx.compute_txid()).await; - let fund_txid = fund_tx.compute_txid(); - (0..output_num).map(|i| OutPoint { txid: fund_txid, vout: i as u32 }).collect() - } - - async fn find_utxo( - client: &EsploraClient, - address: Address, - min_amount: Amount, - ) -> Option { - let utxos = client.get_address_utxo(address).await.unwrap(); - for utxo in utxos { - if utxo.value >= min_amount { - return Some(Input { - outpoint: OutPoint { txid: utxo.txid, vout: utxo.vout }, - amount: utxo.value, - }); - } - } - None - } - - fn user_master_key() -> Keypair { - gen_keypair("seed:user") - } - - fn operator_master_key() -> OperatorMasterKey { - OperatorMasterKey::new(gen_keypair("seed:operator")) - } - - fn challenger_master_key() -> ChallengerMasterKey { - ChallengerMasterKey::new(gen_keypair("seed:challenger")) - } - - fn committee_member_master_key() -> [CommitteeMasterKey; 3] { - [ - CommitteeMasterKey::new(gen_keypair("seed:committee-1")), - CommitteeMasterKey::new(gen_keypair("seed:committee-2")), - CommitteeMasterKey::new(gen_keypair("seed:committee-3")), - ] - } - - fn committee_instance_keys_envelope_path(instance_id: Uuid, committee_index: usize) -> PathBuf { - let mut path = std::env::temp_dir(); - path.push("bitvm2-ga-committee-instance-keys"); - path.push(format!("{instance_id}-{committee_index}.json")); - path - } - - fn is_io_not_found_error(err: &anyhow::Error) -> bool { - err.chain().any(|cause| { - cause - .downcast_ref::() - .is_some_and(|io_err| io_err.kind() == std::io::ErrorKind::NotFound) - }) - } - - fn load_committee_instance_keypair( - committee_master_key: &CommitteeMasterKey, - instance_id: Uuid, - committee_index: usize, - ) -> Keypair { - let envelope_path = committee_instance_keys_envelope_path(instance_id, committee_index); - committee_master_key.load_instance_keypair(instance_id, &envelope_path).unwrap_or_else( - |err| { - panic!( - "load committee instance keypair failed for {instance_id} at {}: {err}", - envelope_path.display() - ) - }, - ) - } - - fn create_committee_instance_keypair_envelopes(instance_id: Uuid) { - let committee_master_keys = committee_member_master_key(); - for (index, committee_master_key) in committee_master_keys.iter().enumerate() { - let envelope_path = committee_instance_keys_envelope_path(instance_id, index); - match committee_master_key.load_instance_keypair(instance_id, &envelope_path) { - Ok(_) => {} - Err(load_err) => { - if !is_io_not_found_error(&load_err) { - panic!( - "load committee instance keypair failed for {instance_id} at {}: {load_err}", - envelope_path.display() - ); - } - committee_master_key - .create_instance_keypair_envelope(instance_id, &envelope_path) - .unwrap_or_else(|err| { - panic!( - "create committee instance key envelope failed for {instance_id} at {}: {err}", - envelope_path.display() - ) - }); - } - } - } - } - - fn watchtower_master_key() -> [WatchtowerMasterKey; 3] { - [ - WatchtowerMasterKey::new(gen_keypair("seed:watchtower-1")), - WatchtowerMasterKey::new(gen_keypair("seed:watchtower-2")), - WatchtowerMasterKey::new(gen_keypair("seed:watchtower-3")), - ] - } - - fn hashlocks() -> ([Vec; 3], [[u8; 20]; 3]) { - let preimages = [vec![0x11u8; 20], vec![0x22u8; 20], vec![0x33u8; 20]]; - let mut hashlocks = [[0u8; 20]; 3]; - for (i, preimage) in preimages.iter().enumerate() { - let hash = hash160(preimage); - hashlocks[i] = hash; - } - (preimages, hashlocks) - } - - pub fn node_p2wsh_script(pubkey: &PublicKey) -> ScriptBuf { - script! { - { *pubkey } - OP_CHECKSIG - } - .compile() - } - pub fn node_p2wsh_address(network: Network, pubkey: &PublicKey) -> Address { - Address::p2wsh(&node_p2wsh_script(pubkey), network) - } - pub fn node_sign( - tx: &mut Transaction, - input_index: usize, - input_value: Amount, - sighash_type: EcdsaSighashType, - node_keypair: &Keypair, - ) -> Result<(), Box> { - let node_pubkey = node_keypair.public_key(); - populate_p2wsh_witness( - tx, - input_index, - sighash_type, - &node_p2wsh_script(&node_pubkey.into()), - input_value, - &vec![node_keypair], - ); - Ok(()) - } - - fn assert_commit_num() -> usize { - chunk_assert_commit(NUM_GUEST_PUBS_ASSERT + NUM_PUBS + NUM_U256, NUM_HASH, false).len() - } - - fn get_test_proof() - -> ([[u8; 32]; NUM_GUEST_PUBS_ASSERT], Groth16Proof, PublicInputs, VerifyingKey) { - let proof = hex::decode( - "b6ef2c5aa48a2f599a13bc4d8010e4d0190aeb05ff79e21266aff8dde6353d1756191f0959c787f6dedfc0c47751aed2648775101285b9da2d6c4e912e74891f884bd672f94f4d78528fb10b5410a94b53bcef07f99952ef72b68c72a5c4ff2a3de7c314ffbf17df018a753f070448c2f698706d4c2b99bdb06f928cffe1bea0", - ).unwrap(); - let pis = hex::decode( - "02000000000000002000000000000000721db33a295a3b29a61c7360486e6d8346288822dc5cab652722e34d4b423d002000000000000000cfdc2f035c3699c6d17563570ea05a3d6d08302487937dd079a6b1671d484c0d", - ).unwrap(); - let vk = hex::decode( - "e2f26dbea299f5223b646cb1fb33eadb059d9407559d7441dfd902e3a79a4d2dabb73dc17fbc13021e2471e0c08bd67d8401f52b73d6d07483794cad4778180e0c06f33bbc4c79a9cadef253a68084d382f17788f885c9afd176f7cb2f036789edf692d95cbdde46ddda5ef7d422436779445c5e66006a42761e1f12efde0018c212f3aeb785e49712e7a9353349aaf1255dfb31b7bf60723a480d9293938e19ffdb10cf9f7e2b08673477187c33a695a397702cf22005900724518b57f92f2ce08f8dfe36ca3eff63b1743d64812936d8cab0d74c063d260e20a9a3339b2a8c0300000000000000d17e1efc51d15eef04bde8dc794edc9e5788eb7539171d3a49d970ab9215b89c9ab6c5ab119ca81927393ef29332a1d15ac5f197b878ea89a1f8f686b747011eaad636dcb52cdfd674d155ddd67d21186fbdd1c0a62ebd74dcd6ddc6784b819e", - ).unwrap(); - let proof = goat::proof::deserialize_proof(proof); - let pis = goat::proof::deserialize_pubin(pis); - let vk = goat::proof::deserialize_vk(vk); - let guest_pubs = [[0xddu8; 32]; NUM_GUEST_PUBS_ASSERT]; - (guest_pubs, proof, pis, vk) - } - - async fn gen_test_graph( - esplora: &EsploraClient, - disprove_scripts: Vec, - ) -> Bitvm2Graph { - let instance_id = Uuid::new_v4(); - let graph_id = Uuid::new_v4(); - let pegin_amount = Amount::from_sat(10000); - let challenge_amount = Amount::from_sat(10000); - let default_fee_amount = Amount::from_sat(1000); - let bank_address = node_p2wsh_address(network(), &bank_keypair().public_key().into()); - let user_xonly_pubkey = XOnlyPublicKey::from(user_master_key().public_key()); - let user_address = node_p2wsh_address(network(), &user_master_key().public_key().into()); - let pegin_deposit_input_amount = pegin_amount + default_fee_amount * 2; - let pegin_deposit_inputs = vec![Input { - outpoint: fund_address( - esplora, - network(), - user_address.clone(), - pegin_deposit_input_amount, - ) - .await, - amount: pegin_deposit_input_amount, - }]; - let user_info = UserInfo { - depositor_evm_address: [0x11u8; 20], - txn_fees: [default_fee_amount.to_sat(); 3], - inputs: pegin_deposit_inputs, - user_xonly_pubkey, - user_change_address: bank_address.clone(), - user_refund_address: bank_address.clone(), - }; - - // Initialize committee instance envelopes once at flow start. - create_committee_instance_keypair_envelopes(instance_id); - - let committee_pubkeys: Vec = committee_member_master_key() - .iter() - .enumerate() - .map(|(index, k)| { - load_committee_instance_keypair(k, instance_id, index).public_key().into() - }) - .collect(); - let committee_agg_pubkey = generate_n_of_n_public_key(&committee_pubkeys).0; - let instance_params = Bitvm2InstanceParameters { - network: network(), - instance_id, - user_info, - pegin_amount, - committee_pubkeys, - committee_agg_pubkey, - }; - - let mut pegin_deposit_tx = instance_params.build_pegin_tx().unwrap().0; - node_sign( - pegin_deposit_tx.tx_mut(), - 0, - pegin_deposit_input_amount, - EcdsaSighashType::All, - &user_master_key(), - ) - .unwrap(); - println!("broadcasting pegin deposit tx {}", pegin_deposit_tx.tx().compute_txid()); - esplora.broadcast(&pegin_deposit_tx.finalize()).await.unwrap(); - wait_tx_confirm(esplora, pegin_deposit_tx.tx().compute_txid()).await; - println!("pegin deposit tx confirmed"); - - let operator_master_key = operator_master_key(); - let operator_keypair = operator_master_key.master_keypair(); - let operator_wots_pubkeys = operator_master_key.wots_keypair_for_graph(graph_id).1; - let operator_taproot_public_key = XOnlyPublicKey::from(operator_keypair.public_key()); - let prekickoff_connector = - PrekickoffConnector::new(network(), &operator_taproot_public_key); - let force_skip_connector = ForceSkipConnector::new(network(), &operator_taproot_public_key); - let kickoff_connector = KickoffConnector::new(network(), &operator_taproot_public_key); - let operator_prekickoff_input_amount = Amount::from_sat(50000); - let cur_prekickoff_connector_input = Input { - outpoint: fund_address( - esplora, - network(), - prekickoff_connector.generate_taproot_address(), - operator_prekickoff_input_amount, - ) - .await, - amount: operator_prekickoff_input_amount, - }; - let mut cur_prekickoff_txn = PrekickoffTransaction::new_for_validation( - &prekickoff_connector, - &force_skip_connector, - &kickoff_connector, - &prekickoff_connector, - cur_prekickoff_connector_input, - vec![], - vec![], - default_fee_amount.to_sat(), - 3, - assert_commit_num(), - ) - .unwrap(); - let base_context = instance_params.get_base_context(); - let operator_context = OperatorContext { - network: network(), - operator_keypair, - operator_public_key: operator_keypair.public_key().into(), - operator_taproot_public_key, - n_of_n_public_key: base_context.n_of_n_public_key, - n_of_n_public_keys: base_context.n_of_n_public_keys, - n_of_n_taproot_public_key: base_context.n_of_n_taproot_public_key, - }; - cur_prekickoff_txn.sign_input_0(&operator_context, &prekickoff_connector); - println!("broadcasting prekickoff tx {}", cur_prekickoff_txn.tx().compute_txid()); - esplora.broadcast(&cur_prekickoff_txn.finalize()).await.unwrap(); - wait_tx_confirm(esplora, cur_prekickoff_txn.tx().compute_txid()).await; - println!("prekickoff tx confirmed"); - let prekickoff_parameters = PrekickoffParameters { - cur_prekickoff_txn, - replenish_fee_inputs: vec![], - replenish_fee_prev_outs: vec![], - fee_amount: default_fee_amount.to_sat(), - }; - - let graph_parameters = Bitvm2GraphParameters { - instance_parameters: instance_params, - prekickoff_parameters, - graph_id, - graph_nonce: 0, - challenge_amount, - operator_pubkey: operator_keypair.public_key().into(), - operator_wots_pubkeys, - operator_receive_address: bank_address.clone(), - watchtower_pubkeys: watchtower_master_key() - .iter() - .map(|k| k.master_keypair().public_key().x_only_public_key().0) - .collect(), - hashlocks: hashlocks().1.to_vec(), - guest_constant_value: [0u8; 32], // all zero for test - }; - - generate_bitvm_graph(graph_parameters, disprove_scripts).unwrap() - } - - fn operator_presign_graph(graph: &mut Bitvm2Graph) { - operator_pre_sign(operator_master_key().master_keypair(), graph).unwrap(); - } - - fn committee_presign_graph(graph: &mut Bitvm2Graph) { - let instance_id = graph.parameters.instance_parameters.instance_id; - let graph_id = graph.parameters.graph_id; - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = assert_commit_num(); - let commitee_master_keys = committee_member_master_key(); - let commitee_pub_nonces: Vec = commitee_master_keys - .iter() - .enumerate() - .map(|(index, k)| { - let committee_keypair = load_committee_instance_keypair(k, instance_id, index); - k.nonces_for_graph_with_keypair( - instance_id, - graph_id, - watchtower_num, - assert_commit_num, - committee_keypair, - ) - .0 - }) - .collect(); - let agg_nonces = nonces_aggregation(&commitee_pub_nonces).unwrap(); - let committee_partial_sigs = commitee_master_keys - .iter() - .enumerate() - .map(|(index, k)| { - let committee_keypair = load_committee_instance_keypair(k, instance_id, index); - let s = k - .nonces_for_graph_with_keypair( - instance_id, - graph_id, - watchtower_num, - assert_commit_num, - committee_keypair, - ) - .1; - committee_pre_sign(committee_keypair, s, agg_nonces.clone(), graph).unwrap() - }) - .collect::>(); - let commitee_agg_sigs = - signature_aggregation(&committee_partial_sigs, &agg_nonces, graph).unwrap(); - push_committee_pre_signatures(graph, &commitee_agg_sigs).unwrap(); - } - - async fn send_pegin_confirm(esplora: &EsploraClient, graph: &Bitvm2Graph) { - let instance_id = graph.parameters.instance_parameters.instance_id; - let commitee_master_keys = committee_member_master_key(); - let commitee_pub_nonces: Vec = commitee_master_keys - .iter() - .enumerate() - .map(|(index, k)| { - let committee_keypair = load_committee_instance_keypair(k, instance_id, index); - k.nonce_for_instance_with_keypair(instance_id, committee_keypair).1 - }) - .collect(); - let agg_nonce = nonce_aggregation(&commitee_pub_nonces); - let committee_musig2_sigs = commitee_master_keys - .iter() - .enumerate() - .map(|(index, k)| { - let committee_keypair = load_committee_instance_keypair(k, instance_id, index); - let (s, _, _) = k.nonce_for_instance_with_keypair(instance_id, committee_keypair); - sign_pegin_confirm(&graph, committee_keypair, s, agg_nonce.clone()).unwrap() - }) - .collect::>(); - let tx = agg_and_push_pegin_confirm_sigs(graph, committee_musig2_sigs, &agg_nonce).unwrap(); - println!("broadcasting pegin confirm tx {}", tx.compute_txid()); - esplora.broadcast(&tx).await.unwrap(); - wait_tx_confirm(esplora, tx.compute_txid()).await; - println!("pegin confirm tx confirmed"); - } - - async fn send_pegin_refund(esplora: &EsploraClient, graph: &Bitvm2Graph) { - let (pegin_deposit, _, mut pegin_refund) = - graph.parameters.instance_parameters.build_pegin_tx().unwrap(); - let pegin_deposit_txid = pegin_deposit.tx().compute_txid(); - let pegin_deposit_height = wait_tx_confirm(esplora, pegin_deposit_txid).await; - let refund_timelock = num_blocks_per_network(network(), CONNECTOR_Z_TIMELOCK); - println!("waiting for pegin refund timelock"); - wait_timelock(esplora, pegin_deposit_height, refund_timelock).await; - let committee_taproot_pubkey = - XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); - let connector_z = ConnectorZ::new( - network(), - &committee_taproot_pubkey, - &graph.parameters.instance_parameters.user_info.user_xonly_pubkey, - ); - pre_sign_taproot_input_default( - &mut pegin_refund, - 0, - TapSighashType::All, - connector_z.generate_taproot_spend_info(), - &vec![&user_master_key()], - ); - println!("broadcasting pegin refund tx {}", pegin_refund.tx().compute_txid()); - esplora.broadcast(&pegin_refund.finalize()).await.unwrap(); - wait_tx_confirm(esplora, pegin_refund.tx().compute_txid()).await; - println!("pegin refund tx confirmed"); - } - - async fn wait_timelock(esplora: &EsploraClient, start_height: u32, timelock: u32) { - if network() == Network::Regtest { - // In regtest, we can just mine blocks to satisfy the timelock - let current_height = esplora.get_height().await.unwrap(); - let mint_blocks_num = (start_height + timelock + 1).saturating_sub(current_height); - if mint_blocks_num == 0 { - return; - } - let rpc = get_btcd_client(); - regtest_mint_blocks(&rpc, mint_blocks_num).await; - sleep(Duration::from_secs(1)).await; - } else { - // In testnet, we have to wait for real blocks - let wait_start = std::time::Instant::now(); - let expected_heigth = start_height + timelock + 1; - loop { - let current_height = esplora.get_height().await.unwrap(); - if current_height >= expected_heigth { - break; - } - sleep(Duration::from_secs(5)).await; - let elapsed_secs = wait_start.elapsed().as_secs(); - if elapsed_secs % 60 == 0 { - println!( - "Waited {} mins for timelock, current height {current_height}, expected_height {expected_heigth}", - elapsed_secs / 60 - ); - } - } - } - } - - async fn wait_tx_confirm(esplora: &EsploraClient, txid: Txid) -> u32 { - if network() == Network::Regtest { - regtest_mint_blocks(&get_btcd_client(), 1).await; - let wait_start = std::time::Instant::now(); - loop { - let tx_status = esplora.get_tx_status(&txid).await.unwrap(); - if let Some(height) = tx_status.block_height { - return height; - } - sleep(Duration::from_secs(1)).await; - let elapsed_secs = wait_start.elapsed().as_secs(); - if elapsed_secs > 60 { - panic!( - "Transaction {txid} not confirmed after {} seconds in regtest", - elapsed_secs - ); - } - } - } else { - let wait_start = std::time::Instant::now(); - loop { - let tx_status = esplora.get_tx_status(&txid).await.unwrap(); - if let Some(height) = tx_status.block_height { - return height; - } - sleep(Duration::from_secs(5)).await; - let elapsed_secs = wait_start.elapsed().as_secs(); - if elapsed_secs % 60 == 0 { - println!("Waited {} mins for {txid}", elapsed_secs / 60); - } - } - } - } - - fn check_tx_sig_witness( - tx: &Transaction, - input_index: usize, - pubkey: &XOnlyPublicKey, - sighash: bitcoin::TapSighash, - ) -> bool { - let secp = bitcoin::secp256k1::Secp256k1::verification_only(); - - let wit = match tx.input.get(input_index) { - Some(input) => &input.witness, - None => return false, - }; - - if wit.is_empty() { - return false; - } - - let sig_with_hash = &wit[0]; - - if sig_with_hash.len() != 65 { - return false; - } - - let sig_bytes = &sig_with_hash[..64]; - let _sighash_flag = sig_with_hash[64]; - - let sig = match bitcoin::secp256k1::schnorr::Signature::from_slice(sig_bytes) { - Ok(s) => s, - Err(_) => return false, - }; - - let msg = bitcoin::secp256k1::Message::from(sighash); - - secp.verify_schnorr(&sig, &msg, pubkey).is_ok() - } - - async fn build_sign_and_broadcast_tx( - esplora: &EsploraClient, - node_keypair: Keypair, - txins: Vec, - total_input_amount: Amount, - txouts: Vec, - ) -> Txid { - let txouts = if txouts.is_empty() { - vec![TxOut { value: Amount::ZERO, script_pubkey: generate_opreturn_script(vec![]) }] - } else { - txouts - }; - let mut tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: txins, - output: txouts, - }; - let total_output_amount: Amount = tx.output.iter().map(|o| o.value).sum(); - let fee_amount = Amount::from_sat( - ((tx.weight().to_vbytes_ceil() + 200) as f64 * fee_rate()).ceil() as u64, - ); - let node_address = node_p2wsh_address(network(), &node_keypair.public_key().into()); - let shortfall = Amount::from_sat( - (total_output_amount + fee_amount) - .to_sat() - .saturating_sub(total_input_amount.to_sat() + DUST_AMOUNT) - + DUST_AMOUNT, - ); - let payer_outpoint = - fund_address(esplora, network(), node_address.clone(), shortfall).await; - wait_tx_confirm(esplora, payer_outpoint.txid).await; - tx.input.push(bitcoin::TxIn { - previous_output: payer_outpoint, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }); - let payer_input_index = tx.input.len() - 1; - node_sign(&mut tx, payer_input_index, shortfall, EcdsaSighashType::All, &node_keypair) - .unwrap(); - esplora.broadcast(&tx).await.unwrap(); - tx.compute_txid() - } - - async fn merge_bank_utxo(esplora: &EsploraClient) { - // get_address_utxo may fail if there are too many UTXOs (e.g. 500), so we merge them before its too late - println!("merging bank UTXOs, network: {}", network()); - let bank_address = node_p2wsh_address(network(), &bank_keypair().public_key().into()); - println!("bank_address: {bank_address}"); - let utxos = esplora.get_address_utxo(bank_address.clone()).await.unwrap(); - if utxos.len() < 300 { - println!("bank UTXOs are not too many {}, no need to merge", utxos.len()); - return; - } - let mut selected_utxos = vec![]; - let current_height = esplora.get_height().await.unwrap(); - // coinbase outputs need 100 blocks to mature, so we leave them untouched - for utxo in utxos.into_iter() { - let tx_info = esplora.get_tx_info(&utxo.txid).await.unwrap().unwrap(); - if tx_info.vin[0].txid == Txid::from_slice(&[0u8; 32]).unwrap() - && tx_info.vin[0].vout == u32::MAX - { - // coinbase output - if let Some(height) = tx_info.status.block_height { - if current_height.saturating_sub(height) < 100 { - continue; - } - } else { - continue; - } - } - selected_utxos.push(utxo.clone()); - } - if selected_utxos.len() < 2 { - println!("not enough mature UTXOs to merge"); - return; - } - let txins: Vec = selected_utxos - .iter() - .map(|u| TxIn { - previous_output: OutPoint { txid: u.txid, vout: u.vout }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }) - .collect(); - let total_input_amount: Amount = selected_utxos.iter().map(|u| u.value).sum(); - let txout = TxOut { - value: total_input_amount - Amount::from_sat(100000), - script_pubkey: bank_address.script_pubkey(), - }; - let mut tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: txins, - output: vec![txout], - }; - for i in 0..tx.input.len() { - node_sign(&mut tx, i, selected_utxos[i].value, EcdsaSighashType::All, &bank_keypair()) - .unwrap(); - } - esplora.broadcast(&tx).await.unwrap(); - wait_tx_confirm(esplora, tx.compute_txid()).await; - println!("bank UTXOs merged"); - } - - async fn regtest_mint_blocks(btcd: &BtcdClient, num: u32) { - if network() != Network::Regtest { - panic!("can only mint blocks in regtest"); - } - let bank_address = node_p2wsh_address(network(), &bank_keypair().public_key().into()); - btcd.generate_to_address(num.into(), &bank_address).unwrap(); - } - - #[tokio::test] - async fn test_take1() { - set_network(Network::Regtest); - let esplora = get_esplora_client().await; - let disprove_scripts = vec![script! {OP_TRUE}.compile()]; // No disprove, use empty vector to simplify - - merge_bank_utxo(&esplora).await; - let mut graph = gen_test_graph(&esplora, disprove_scripts).await; - operator_presign_graph(&mut graph); - committee_presign_graph(&mut graph); - send_pegin_confirm(&esplora, &graph).await; - - // kickoff - let operator_keypair = operator_master_key().master_keypair(); - let kickoff = operator_sign_kickoff(operator_keypair, &mut graph).unwrap(); - println!("broadcasting kickoff tx {}", kickoff.compute_txid()); - esplora.broadcast(&kickoff).await.unwrap(); - let kickoff_height = wait_tx_confirm(&esplora, kickoff.compute_txid()).await; - println!("kickoff tx confirmed"); - - // take1 - println!("waiting for take1 timelock"); - wait_timelock(&esplora, kickoff_height, take1_timelock(network())).await; - let take1 = operator_sign_take1(operator_keypair, &mut graph).unwrap(); - println!("broadcasting take1 tx {}", take1.compute_txid()); - esplora.broadcast(&take1).await.unwrap(); - wait_tx_confirm(&esplora, take1.compute_txid()).await; - println!("take1 tx confirmed"); - } - - #[tokio::test] - async fn test_take2() { - set_network(Network::Regtest); - let esplora = get_esplora_client().await; - let disprove_scripts = vec![script! {OP_TRUE}.compile()]; // No disprove, use empty vector to simplify - - let bank_address = node_p2wsh_address(network(), &bank_keypair().public_key().into()); - let default_fee_amount = Amount::from_sat(1000); - - merge_bank_utxo(&esplora).await; - let mut graph = gen_test_graph(&esplora, disprove_scripts).await; - operator_presign_graph(&mut graph); - committee_presign_graph(&mut graph); - send_pegin_confirm(&esplora, &graph).await; - - // kickoff - let operator_keypair = operator_master_key().master_keypair(); - let kickoff = operator_sign_kickoff(operator_keypair, &mut graph).unwrap(); - println!("broadcasting kickoff tx {}", kickoff.compute_txid()); - esplora.broadcast(&kickoff).await.unwrap(); - wait_tx_confirm(&esplora, kickoff.compute_txid()).await; - println!("kickoff tx confirmed"); - - // challenge - let (mut challenge_tx, _) = export_challenge_tx(&graph).unwrap(); - let challenge_keypair = challenger_master_key().master_keypair(); - challenge_tx.output.push(bitcoin::TxOut { - value: Amount::ZERO, - script_pubkey: generate_opreturn_script(vec![0xffu8; 20]), - }); - println!("broadcasting challenge tx {}", challenge_tx.compute_txid()); - build_sign_and_broadcast_tx( - &esplora, - challenge_keypair, - challenge_tx.input, - kickoff.output[0].value, - challenge_tx.output, - ) - .await; - println!("challenge tx confirmed"); - - // watchtower challenge init - let watchtower_challenge_init = - operator_sign_watchtower_challenge_init(operator_keypair, &mut graph).unwrap(); - let watchtower_challenge_init_txid = watchtower_challenge_init.compute_txid(); - println!("broadcasting watchtower challenge init tx {}", watchtower_challenge_init_txid); - esplora.broadcast(&watchtower_challenge_init).await.unwrap(); - let watchtower_challenge_init_height = - wait_tx_confirm(&esplora, watchtower_challenge_init_txid).await; - println!("watchtower challenge init tx confirmed"); - - // watchtower[0] challenge - let watchtower_0_keypair = watchtower_master_key()[0].master_keypair(); - let watchtower_challenge_payer_amount = Amount::from_sat(30000); - let watchtower_0_challenge_payer_input = Input { - outpoint: fund_address( - &esplora, - network(), - node_p2wsh_address(network(), &watchtower_0_keypair.public_key().into()), - watchtower_challenge_payer_amount, - ) - .await, - amount: watchtower_challenge_payer_amount, - }; - - const PROOF: &[u8] = - include_bytes!("../../../circuits/data/watchtower/output3.bin.proof.bin"); - const PUBLIC_INPUTS: &[u8] = - include_bytes!("../../../circuits/data/watchtower/output3.bin.public_inputs.bin"); - const VK_HASH: &str = - include_str!("../../../circuits/data/watchtower/output3.bin.vk_hash.bin"); - const PROOF_PART_STARK_VK: &[u8] = - include_bytes!("../../../circuits/data/watchtower/output3.bin.proof_part_stark_vk.bin"); - let proof_part_stark_vk = PROOF_PART_STARK_VK.to_vec(); - - let graph_id = *graph.parameters.graph_id.as_bytes(); - //let total_work = 1006120; - //let consensus_commit_block_height = 503043; - let comm = bitcoin_light_client_circuit::build_watchtower_commitment( - &graph_id, - PROOF, - PUBLIC_INPUTS, - VK_HASH, - &proof_part_stark_vk, - ) - .unwrap(); - - let mut watchtower_0_challenge = build_watchtower_challenge_tx( - &graph, - &watchtower_0_keypair, - 0, - &comm, - vec![watchtower_0_challenge_payer_input], - &bank_address, - default_fee_amount, - ) - .unwrap(); - node_sign( - &mut watchtower_0_challenge, - 1, - watchtower_challenge_payer_amount, - EcdsaSighashType::All, - &watchtower_0_keypair, - ) - .unwrap(); - println!( - "broadcasting watchtower 0 challenge tx {}", - watchtower_0_challenge.compute_txid() - ); - esplora.broadcast(&watchtower_0_challenge).await.unwrap(); - wait_tx_confirm(&esplora, watchtower_0_challenge.compute_txid()).await; - println!("watchtower 0 challenge tx confirmed"); - - // operator ack - let (ack_txin, ack_txin_amount) = - operator_sign_ack(operator_keypair, &mut graph, 0, &hashlocks().0[0]).unwrap(); - println!("broadcasting ack tx"); - let ack_txid = build_sign_and_broadcast_tx( - &esplora, - operator_keypair, - vec![ack_txin], - ack_txin_amount, - vec![], - ) - .await; - wait_tx_confirm(&esplora, ack_txid).await; - println!("ack tx {} confirmed", ack_txid); - - // watchtower challenge timeout - println!("waiting for watchtower challenge timeout"); - wait_timelock( - &esplora, - watchtower_challenge_init_height, - watchtower_challenge_timeout_timelock(network()), - ) - .await; - let watchtower_challenge_timeout_tx = - operator_sign_watchtower_challenge_timeout(operator_keypair, &mut graph, 1).unwrap(); - println!( - "broadcasting watchtower challenge timeout tx {}", - watchtower_challenge_timeout_tx.compute_txid() - ); - esplora.broadcast(&watchtower_challenge_timeout_tx).await.unwrap(); - wait_tx_confirm(&esplora, watchtower_challenge_timeout_tx.compute_txid()).await; - println!("watchtower challenge timeout tx confirmed"); - - // operator_commit_blockhash - let wots_secret_keys = - operator_master_key().wots_keypair_for_graph(graph.parameters.graph_id).0; - let blockhash_wots_secret_key = &wots_secret_keys[0]; - let (operator_commit_blockhash_txin, operator_commit_blockhash_txin_amount) = - operator_sign_blockhash_commit( - operator_keypair, - &mut graph, - &[0xeeu8; 32], - blockhash_wots_secret_key, - ) - .unwrap(); - println!("broadcasting operator commit blockhash tx"); - let operator_commit_blockhash_txid = build_sign_and_broadcast_tx( - &esplora, - operator_keypair, - vec![operator_commit_blockhash_txin], - operator_commit_blockhash_txin_amount, - vec![], - ) - .await; - wait_tx_confirm(&esplora, operator_commit_blockhash_txid).await; - println!("operator commit blockhash tx {} confirmed", operator_commit_blockhash_txid); - - // assert-init - let assert_init_tx = operator_sign_assert_init(operator_keypair, &mut graph).unwrap(); - let assert_init_txid = assert_init_tx.compute_txid(); - println!("broadcasting assert-init tx {}", assert_init_txid); - esplora.broadcast(&assert_init_tx).await.unwrap(); - let assert_init_height = wait_tx_confirm(&esplora, assert_init_txid).await; - println!("assert-init tx confirmed"); - - // assert-commit - let (guest_inputs, proof, groth16_pubin, vk) = get_test_proof(); - let assert_commit_txins = operator_sign_assert_commit( - operator_keypair, - &mut graph, - &wots_secret_keys, - guest_inputs, - proof, - groth16_pubin, - &vk, - ) - .unwrap(); - println!("broadcasting assert-commit tx"); - let mut index = 0; - for (txin, amount) in assert_commit_txins { - let assert_commit_txid = - build_sign_and_broadcast_tx(&esplora, operator_keypair, vec![txin], amount, vec![]) - .await; - wait_tx_confirm(&esplora, assert_commit_txid).await; - println!("assert-commit tx-{index} {} confirmed", assert_commit_txid); - index += 1; - } - - // take2 - println!("waiting for take2 timelock"); - wait_timelock(&esplora, watchtower_challenge_init_height, take2_timelocks(network()).0) - .await; - wait_timelock(&esplora, assert_init_height, take2_timelocks(network()).1).await; - let take2 = operator_sign_take2(operator_keypair, &mut graph).unwrap(); - println!("broadcasting take2 tx {}", take2.compute_txid()); - esplora.broadcast(&take2).await.unwrap(); - wait_tx_confirm(&esplora, take2.compute_txid()).await; - println!("take2 tx confirmed"); - } - - #[tokio::test] - async fn test_nack() { - set_network(Network::Regtest); - let esplora = get_esplora_client().await; - let disprove_scripts = vec![script! {OP_TRUE}.compile()]; // No disprove, use empty vector to simplify - - merge_bank_utxo(&esplora).await; - let mut graph = gen_test_graph(&esplora, disprove_scripts).await; - operator_presign_graph(&mut graph); - committee_presign_graph(&mut graph); - send_pegin_refund(&esplora, &graph).await; - - // kickoff - let operator_keypair = operator_master_key().master_keypair(); - let kickoff = operator_sign_kickoff(operator_keypair, &mut graph).unwrap(); - println!("broadcasting kickoff tx {}", kickoff.compute_txid()); - esplora.broadcast(&kickoff).await.unwrap(); - wait_tx_confirm(&esplora, kickoff.compute_txid()).await; - println!("kickoff tx confirmed"); - - // challenge - let (mut challenge_tx, _) = export_challenge_tx(&graph).unwrap(); - let challenge_keypair = challenger_master_key().master_keypair(); - challenge_tx.output.push(bitcoin::TxOut { - value: Amount::ZERO, - script_pubkey: generate_opreturn_script(vec![0xffu8; 20]), - }); - println!("broadcasting challenge tx {}", challenge_tx.compute_txid()); - build_sign_and_broadcast_tx( - &esplora, - challenge_keypair, - challenge_tx.input, - kickoff.output[0].value, - challenge_tx.output, - ) - .await; - println!("challenge tx confirmed"); - - // watchtower challenge init - let watchtower_challenge_init = - operator_sign_watchtower_challenge_init(operator_keypair, &mut graph).unwrap(); - let watchtower_challenge_init_txid = watchtower_challenge_init.compute_txid(); - println!("broadcasting watchtower challenge init tx {}", watchtower_challenge_init_txid); - esplora.broadcast(&watchtower_challenge_init).await.unwrap(); - let watchtower_challenge_init_height = - wait_tx_confirm(&esplora, watchtower_challenge_init_txid).await; - println!("watchtower challenge init tx confirmed"); - - // nack - println!("waiting for nack timelock"); - wait_timelock(&esplora, watchtower_challenge_init_height, nack_timelock(network())).await; - let nack_tx = graph.nack_txns[0].finalize(); - println!("broadcasting nack tx {}", nack_tx.compute_txid()); - esplora.broadcast(&nack_tx).await.unwrap(); - wait_tx_confirm(&esplora, nack_tx.compute_txid()).await; - println!("nack tx confirmed"); - } - - #[tokio::test] - async fn test_commit_timeout() { - set_network(Network::Regtest); - let esplora = get_esplora_client().await; - let disprove_scripts = vec![script! {OP_TRUE}.compile()]; // No disprove, use empty vector to simplify - - merge_bank_utxo(&esplora).await; - let mut graph = gen_test_graph(&esplora, disprove_scripts).await; - operator_presign_graph(&mut graph); - committee_presign_graph(&mut graph); - send_pegin_refund(&esplora, &graph).await; - - // kickoff - let operator_keypair = operator_master_key().master_keypair(); - let kickoff = operator_sign_kickoff(operator_keypair, &mut graph).unwrap(); - println!("broadcasting kickoff tx {}", kickoff.compute_txid()); - esplora.broadcast(&kickoff).await.unwrap(); - wait_tx_confirm(&esplora, kickoff.compute_txid()).await; - println!("kickoff tx confirmed"); - - // challenge - let (mut challenge_tx, _) = export_challenge_tx(&graph).unwrap(); - let challenge_keypair = challenger_master_key().master_keypair(); - challenge_tx.output.push(bitcoin::TxOut { - value: Amount::ZERO, - script_pubkey: generate_opreturn_script(vec![0xffu8; 20]), - }); - println!("broadcasting challenge tx {}", challenge_tx.compute_txid()); - build_sign_and_broadcast_tx( - &esplora, - challenge_keypair, - challenge_tx.input, - kickoff.output[0].value, - challenge_tx.output, - ) - .await; - println!("challenge tx confirmed"); - - // watchtower challenge init - let watchtower_challenge_init = - operator_sign_watchtower_challenge_init(operator_keypair, &mut graph).unwrap(); - let watchtower_challenge_init_txid = watchtower_challenge_init.compute_txid(); - println!("broadcasting watchtower challenge init tx {}", watchtower_challenge_init_txid); - esplora.broadcast(&watchtower_challenge_init).await.unwrap(); - let watchtower_challenge_init_height = - wait_tx_confirm(&esplora, watchtower_challenge_init_txid).await; - println!("watchtower challenge init tx confirmed"); - - // assert-init - let assert_init_tx = operator_sign_assert_init(operator_keypair, &mut graph).unwrap(); - let assert_init_txid = assert_init_tx.compute_txid(); - println!("broadcasting assert-init tx {}", assert_init_txid); - esplora.broadcast(&assert_init_tx).await.unwrap(); - let assert_init_height = wait_tx_confirm(&esplora, assert_init_txid).await; - println!("assert-init tx confirmed"); - - // assert-commit timeout - // normally, only one of assert-commit or blockhash-commit timeout will be triggered - println!("waiting for assert-commit timeout"); - wait_timelock(&esplora, assert_init_height, assert_commit_timeout_timelock(network())) - .await; - let assert_commit_timeout_tx = graph.assert_commit_timeout_txns[0].finalize(); - println!( - "broadcasting assert-commit timeout tx {}", - assert_commit_timeout_tx.compute_txid() - ); - esplora.broadcast(&assert_commit_timeout_tx).await.unwrap(); - wait_tx_confirm(&esplora, assert_commit_timeout_tx.compute_txid()).await; - println!("assert-commit timeout tx confirmed"); - - // blockhash-commit timeout - println!("waiting for blockhash-commit timeout"); - wait_timelock( - &esplora, - watchtower_challenge_init_height, - commit_blockhash_timeout_timelock(network()), - ) - .await; - let blockhash_commit_timeout_tx = graph.blockhash_commit_timeout.finalize(); - println!( - "broadcasting blockhash-commit timeout tx {}", - blockhash_commit_timeout_tx.compute_txid() - ); - esplora.broadcast(&blockhash_commit_timeout_tx).await.unwrap(); - wait_tx_confirm(&esplora, blockhash_commit_timeout_tx.compute_txid()).await; - println!("blockhash-commit timeout tx confirmed"); - } - - #[ignore = "debug"] - #[tokio::test] - async fn test_broadcast_cpfp_package() { - use client::btc_chain::BTCClient; - use goat::scripts::*; - - let network = Network::Testnet; - set_network(network); - let esplora = get_esplora_client().await; - let client = BTCClient::new(network, None); - let test_keypair = gen_keypair("seed:test"); - let test_address = node_p2wsh_address(network, &test_keypair.public_key().into()); - let default_input_amount = Amount::from_sat(2000); - let receivers = std::iter::repeat((test_address.clone(), default_input_amount)) - .take(3) - .collect::>(); - let utxos = fund_address_batch(&esplora, network, receivers).await; - let [utxo0, utxo1, utxo2]: [OutPoint; 3] = utxos.try_into().unwrap(); - let mut txn0 = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: utxo0, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }], - output: vec![ - TxOut { value: Amount::ZERO, script_pubkey: test_address.script_pubkey() }, - p2a_output(), - ], - }; - node_sign( - &mut txn0, - 0, - default_input_amount, - bitcoin::EcdsaSighashType::All, - &test_keypair, - ) - .unwrap(); - let output0_amount = - default_input_amount - Amount::from_sat(txn0.weight().to_vbytes_ceil()) - p2a_amount(); - txn0.output[0].value = output0_amount; - txn0.input[0].witness.clear(); - node_sign( - &mut txn0, - 0, - default_input_amount, - bitcoin::EcdsaSighashType::All, - &test_keypair, - ) - .unwrap(); - let txn0_txid = txn0.compute_txid(); - - let mut cpfp_txn0 = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![ - TxIn { - previous_output: OutPoint { txid: txn0_txid, vout: 1 }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }, - TxIn { - previous_output: utxo1, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }, - ], - output: vec![p2a_output()], - }; - node_sign( - &mut cpfp_txn0, - 1, - default_input_amount, - bitcoin::EcdsaSighashType::All, - &test_keypair, - ) - .unwrap(); - - let mut txn1 = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { txid: txn0_txid, vout: 0 }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }], - output: vec![ - TxOut { value: Amount::ZERO, script_pubkey: test_address.script_pubkey() }, - p2a_output(), - ], - }; - let input0_amount = txn0.output[0].value; - node_sign(&mut txn1, 0, input0_amount, bitcoin::EcdsaSighashType::All, &test_keypair) - .unwrap(); - let output0_amount = - input0_amount - Amount::from_sat(txn1.weight().to_vbytes_ceil()) - p2a_amount(); - txn1.output[0].value = output0_amount; - txn1.input[0].witness.clear(); - node_sign(&mut txn1, 0, input0_amount, bitcoin::EcdsaSighashType::All, &test_keypair) - .unwrap(); - let txn1_txid = txn1.compute_txid(); - - let mut cpfp_txn1 = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![ - TxIn { - previous_output: OutPoint { txid: txn1_txid, vout: 1 }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }, - TxIn { - previous_output: utxo2, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }, - ], - output: vec![p2a_output()], - }; - node_sign( - &mut cpfp_txn1, - 1, - default_input_amount, - bitcoin::EcdsaSighashType::All, - &test_keypair, - ) - .unwrap(); - - let tx_package_1 = vec![txn0, cpfp_txn0]; - let tx_package_2 = vec![txn1, cpfp_txn1]; - println!( - "txids in the package: \ntxn0 {}, \ncpfp_txn0 {}, \ntxn1 {}, \ncpfp_txn1 {}", - tx_package_1[0].compute_txid(), - tx_package_1[1].compute_txid(), - tx_package_2[0].compute_txid(), - tx_package_2[1].compute_txid() - ); - client.broadcast_package(&tx_package_1).await.unwrap(); - client.broadcast_package(&tx_package_2).await.unwrap(); - - // this will fail because cpfp_txn0 and cpfp_txn1 not parent & child, only 1c1p is allowed in a package - // let tx_package_1 = vec![txn0, txn1]; - // let tx_package_2 = vec![cpfp_txn0, cpfp_txn1]; - // println!("txids in the package: \ntxn0 {}, \ncpfp_txn0 {}, \ntxn1 {}, \ncpfp_txn1 {}", - // tx_package_1[0].compute_txid(), - // tx_package_2[0].compute_txid(), - // tx_package_1[1].compute_txid(), - // tx_package_2[1].compute_txid()); - // client.broadcast_package(&tx_package_1).await.unwrap(); - // client.broadcast_package(&tx_package_2).await.unwrap(); - - // // this will fail because both cpfp_txn0 and txn1 depends on txn0, only 1c1p is allowed in a package - // let tx_package = vec![txn0, cpfp_txn0, txn1, cpfp_txn1]; - // client.broadcast_package(&tx_package).await.unwrap(); - } - - #[tokio::test] - async fn test_unlimited_opreturn() { - let network = Network::Testnet; - set_network(network); - let esplora = get_esplora_client().await; - let bank_address = node_p2wsh_address(network, &bank_keypair().public_key().into()); - let utxos = esplora.get_address_utxo(bank_address.clone()).await.unwrap(); - // select the largest amount of utxo - let utxo = utxos - .into_iter() - .max_by_key(|utxo| utxo.value) - .expect("No utxo found for bank_address"); - - let msg = b"test OP_RETURN with more than 80 bytes.\n\"A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution. Digital signatures provide part of the solution, but the main benefits are lost if a trusted third party is still required to prevent double-spending.We propose a solution to the double-spending problem using a peer-to-peer network.The network timestamps transactions by hashing them into an ongoing chain of hash-based proof-of-work, forming a record that cannot be changed without redoing the proof-of-work. The longest chain not only serves as proof of the sequence of events witnessed, but proof that it came from the largest pool of CPU power. As long as a majority of CPU power is controlled by nodes that are not cooperating to attack the network, they'll generate the longest chain and outpace attackers. The network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest proof-of-work chain as proof of what happened while they were gone.\""; - // let msg = b"short OP_RETURN message"; - let opreturn_script = script! { - OP_RETURN - {msg.to_vec()} - } - .compile(); - let mut tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![TxIn { - previous_output: OutPoint { txid: utxo.txid, vout: utxo.vout }, - script_sig: ScriptBuf::new(), - sequence: bitcoin::Sequence::MAX, - witness: bitcoin::Witness::default(), - }], - output: vec![ - TxOut { value: Amount::ZERO, script_pubkey: opreturn_script }, - TxOut { value: Amount::ZERO, script_pubkey: bank_address.script_pubkey() }, - ], - }; - let tx_size = tx.weight().to_vbytes_ceil() + 200; - if utxo.value < Amount::from_sat(tx_size) { - panic!("cannot find utxo with enough value for the test"); - } - let change_amount = utxo.value - Amount::from_sat(tx_size); // 1 sat/vbyte fee - if change_amount > Amount::from_sat(330) { - tx.output[1].value = change_amount; - } else { - tx.output.pop(); // remove change output if it's too small - } - node_sign(&mut tx, 0, utxo.value, EcdsaSighashType::All, &bank_keypair()).unwrap(); - esplora.broadcast(&tx).await.unwrap(); - println!( - "Broadcasted transaction with {} bytes OP_RETURN: {}", - msg.len(), - tx.compute_txid() - ); - } -} diff --git a/crates/bitvm2-ga/src/types.rs b/crates/bitvm2-ga/src/types.rs deleted file mode 100644 index 5229ab89..00000000 --- a/crates/bitvm2-ga/src/types.rs +++ /dev/null @@ -1,747 +0,0 @@ -use std::collections::BTreeMap; - -use anyhow::{Result, bail}; -use bitcoin::taproot::LeafVersion; -use bitcoin::{Address, Amount, Network, OutPoint, PublicKey, TxOut, XOnlyPublicKey, key::Keypair}; -use bitcoin::{PrivateKey, Witness}; -use bitvm::chunk::api::{ - NUM_HASH, NUM_PUBS, NUM_U256, PublicKeys as ProofWotsPubkeys, - Signatures as Groth16ProofSignatures, -}; -use bitvm::signatures::{WinternitzSecret, Wots, Wots32}; -use goat::connectors::base::TaprootConnector; -use goat::connectors::connector_0::Connector0; -use goat::connectors::connector_e::ConnectorE; -use goat::connectors::connector_z::ConnectorZ; -use goat::contexts::base::BaseContext; -use goat::contexts::operator::OperatorContext; -use goat::contexts::verifier::VerifierContext; -use goat::disprove_scripts::{ - GuestPubinSignatures, NUM_GUEST, NUM_GUEST_PUBS_ASSERT, NUM_GUEST_PUBS_EXTRA, -}; -use goat::transactions::assert::{AssertCommitTimeoutTransaction, AssertInitTransaction}; -use goat::transactions::base::Input; -use goat::transactions::challenge::ChallengeTransaction; -use goat::transactions::kickoff::KickoffTransaction; -use goat::transactions::pegin::{ - PegInConfirmTransaction, PegInDepositTransaction, PegInRefundTransaction, -}; -use goat::transactions::pre_signed::PreSignedTransaction; -use goat::transactions::prekickoff::{ - ChallengeIncompleteKickoffTransaction, ForceSkipKickoffTransaction, PrekickoffTransaction, - QuickChallengeTransaction, -}; -use goat::transactions::take1::Take1Transaction; -use goat::transactions::take2::Take2Transaction; -use goat::transactions::watchtower_challenge::{ - BlockhashCommitTimeoutTransaction, NackTransaction, WatchtowerChallengeInitTransaction, - WatchtowerChallengeTimeoutTransaction, -}; -use rand::{Rng, distributions::Alphanumeric}; -use secp256k1::SECP256K1; -use serde::{Deserialize, Serialize}; -use uuid::Uuid; - -use crate::committee::{CommitteeSignatures, push_committee_pre_signatures}; -use crate::operator::{generate_bitvm_graph_inner, push_operator_pre_signature}; - -pub type VerifyingKey = ark_groth16::VerifyingKey; -pub type Groth16Proof = ark_groth16::Proof; -pub type PublicInputs = Vec; -pub type GuestInputs = [[u8; 32]; NUM_GUEST_PUBS_ASSERT]; - -pub type OperatorWotsSignatures = (GuestPubinSignatures, Groth16ProofSignatures); - -const NUM_SIGS: usize = NUM_GUEST + NUM_PUBS + NUM_HASH + NUM_U256; -pub type OperatorWotsSecretKeys = Box<[WinternitzSecret; NUM_SIGS]>; - -pub type OperatorWotsPublicKeys = ( - [::PublicKey; NUM_GUEST_PUBS_EXTRA], - [::PublicKey; NUM_GUEST_PUBS_ASSERT], - Box, -); - -pub fn random_string(len: usize) -> String { - rand::thread_rng().sample_iter(&Alphanumeric).take(len).map(char::from).collect() -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct UserInfo { - pub depositor_evm_address: [u8; 20], - pub txn_fees: [u64; 3], // [ peginDeposit , peginComfirm peginReufnd ] fees in satoshi - pub inputs: Vec, - pub user_xonly_pubkey: XOnlyPublicKey, - #[serde(with = "node_serializer::address")] - pub user_change_address: Address, - #[serde(with = "node_serializer::address")] - pub user_refund_address: Address, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct Bitvm2InstanceParameters { - pub network: Network, - pub instance_id: Uuid, - pub user_info: UserInfo, - pub pegin_amount: Amount, - pub committee_pubkeys: Vec, - pub committee_agg_pubkey: PublicKey, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct PrekickoffParameters { - pub cur_prekickoff_txn: PrekickoffTransaction, - pub replenish_fee_inputs: Vec, - pub replenish_fee_prev_outs: Vec, - pub fee_amount: u64, -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct Bitvm2GraphParameters { - pub instance_parameters: Bitvm2InstanceParameters, - pub prekickoff_parameters: PrekickoffParameters, - pub graph_id: Uuid, - pub graph_nonce: u64, - pub challenge_amount: Amount, - pub operator_pubkey: PublicKey, - #[serde(with = "node_serializer::wots_pubkeys")] - pub operator_wots_pubkeys: OperatorWotsPublicKeys, - #[serde(with = "node_serializer::address")] - pub operator_receive_address: Address, - pub watchtower_pubkeys: Vec, - pub hashlocks: Vec<[u8; 20]>, // one for each watchtower - pub guest_constant_value: [u8; 32], -} - -impl Bitvm2InstanceParameters { - pub fn check_parameters(&self) -> Result { - // TODO - bail!("Not implemented"); - } - - pub fn build_pegin_tx( - &self, - ) -> Result<(PegInDepositTransaction, PegInConfirmTransaction, PegInRefundTransaction)> { - let network = self.network; - let n_of_n_taproot_public_key = XOnlyPublicKey::from(self.committee_agg_pubkey); - let user_taproot_public_key = self.user_info.user_xonly_pubkey; - let connector_0 = Connector0::new(network, &n_of_n_taproot_public_key); - let connector_z = - ConnectorZ::new(network, &n_of_n_taproot_public_key, &user_taproot_public_key); - let pegin_message = [ - get_magic_bytes(&network), - self.instance_id.as_bytes().to_vec(), - self.user_info.depositor_evm_address.to_vec(), - ] - .concat(); - - let pegin_deposit = PegInDepositTransaction::new_unsigned( - &connector_z, - self.user_info.inputs.clone(), - self.pegin_amount + Amount::from_sat(self.user_info.txn_fees[1]), - Amount::from_sat(self.user_info.txn_fees[0]), - self.user_info.user_change_address.clone(), - ) - .map_err(|e| anyhow::anyhow!("fail to build pegin deposit txn: {e}"))?; - let deposit_outpoint = Input { - outpoint: OutPoint { txid: pegin_deposit.tx().compute_txid(), vout: 0 }, - amount: pegin_deposit.tx().output[0].value, - }; - let pegin_confirm = PegInConfirmTransaction::new_for_validation( - &connector_0, - &connector_z, - deposit_outpoint.clone(), - Amount::from_sat(self.user_info.txn_fees[1]), - pegin_message, - ) - .map_err(|e| anyhow::anyhow!("fail to build pegin confirm txn: {e}"))?; - let pegin_refund = PegInRefundTransaction::new_for_validation( - &connector_z, - deposit_outpoint, - &self.user_info.user_refund_address, - Amount::from_sat(self.user_info.txn_fees[2]), - ) - .map_err(|e| anyhow::anyhow!("fail to build pegin refund txn: {e}"))?; - - Ok((pegin_deposit, pegin_confirm, pegin_refund)) - } - - pub fn build_pegin_cancel_psbt(&self) -> Result { - let network = self.network; - let n_of_n_taproot_public_key = XOnlyPublicKey::from(self.committee_agg_pubkey); - let user_taproot_public_key = self.user_info.user_xonly_pubkey; - let connector_z = - ConnectorZ::new(network, &n_of_n_taproot_public_key, &user_taproot_public_key); - - let pegin_deposit = PegInDepositTransaction::new_unsigned( - &connector_z, - self.user_info.inputs.clone(), - self.pegin_amount + Amount::from_sat(self.user_info.txn_fees[1]), - Amount::from_sat(self.user_info.txn_fees[0]), - self.user_info.user_change_address.clone(), - ) - .map_err(|e| anyhow::anyhow!("fail to build pegin deposit txn: {e}"))?; - let deposit_outpoint = Input { - outpoint: OutPoint { txid: pegin_deposit.tx().compute_txid(), vout: 0 }, - amount: pegin_deposit.tx().output[0].value, - }; - let pegin_refund = PegInRefundTransaction::new_for_validation( - &connector_z, - deposit_outpoint.clone(), - &self.user_info.user_refund_address, - Amount::from_sat(self.user_info.txn_fees[2]), - ) - .map_err(|e| anyhow::anyhow!("fail to build pegin refund txn: {e}"))?; - - let mut psbt = bitcoin::psbt::Psbt::from_unsigned_tx(pegin_refund.tx().clone()).unwrap(); - let taproot_spend_info = connector_z.generate_taproot_spend_info(); - let mut tap_scripts = BTreeMap::new(); - let tap_script_1 = connector_z.generate_taproot_leaf_script(1); - tap_scripts.insert( - taproot_spend_info - .control_block(&(tap_script_1.clone(), LeafVersion::TapScript)) - .unwrap(), - (tap_script_1, LeafVersion::TapScript), - ); - let psbt_input_0 = bitcoin::psbt::Input { - witness_utxo: { - Some(TxOut { - value: deposit_outpoint.amount, - script_pubkey: connector_z.generate_taproot_address().script_pubkey(), - }) - }, - tap_merkle_root: taproot_spend_info.merkle_root(), - tap_internal_key: Some(n_of_n_taproot_public_key), - tap_scripts, - ..Default::default() - }; - psbt.inputs[0] = psbt_input_0; - - Ok(psbt) - } - - pub fn get_verifier_context( - &self, - committee_member_keypair: Keypair, - ) -> Result { - let network = self.network; - let committee_public_key = self.committee_agg_pubkey; - let committee_taproot_public_key = XOnlyPublicKey::from(committee_public_key); - let private_key = PrivateKey::new(committee_member_keypair.secret_key(), network); - let committee_member_public_key = PublicKey::from_private_key(SECP256K1, &private_key); - if !self.committee_pubkeys.contains(&committee_member_public_key) { - bail!("The provided committee member keypair does not match any committee public key"); - } - Ok(VerifierContext { - network, - verifier_keypair: committee_member_keypair, - verifier_public_key: committee_member_public_key, - n_of_n_public_keys: self.committee_pubkeys.clone(), - n_of_n_public_key: committee_public_key, - n_of_n_taproot_public_key: committee_taproot_public_key, - }) - } - - pub fn get_base_context(&self) -> BaseBitvmContext { - let network = self.network; - let n_of_n_public_keys = self.committee_pubkeys.clone(); - let n_of_n_public_key = self.committee_agg_pubkey; - let n_of_n_taproot_public_key = XOnlyPublicKey::from(n_of_n_public_key); - BaseBitvmContext { - network, - n_of_n_public_keys, - n_of_n_public_key, - n_of_n_taproot_public_key, - } - } -} - -impl Bitvm2GraphParameters { - pub fn get_operator_context(&self, operator_keypair: Keypair) -> Result { - let network = self.instance_parameters.network; - let operator_public_key = self.operator_pubkey; - let operator_taproot_public_key = XOnlyPublicKey::from(operator_public_key); - let committee_public_key = self.instance_parameters.committee_agg_pubkey; - let committee_taproot_public_key = XOnlyPublicKey::from(committee_public_key); - if operator_public_key - != PublicKey::from_private_key( - SECP256K1, - &PrivateKey::new(operator_keypair.secret_key(), network), - ) - { - bail!("The provided operator keypair does not match the operator public key"); - } - Ok(OperatorContext { - network, - operator_keypair, - operator_public_key, - operator_taproot_public_key, - - n_of_n_public_keys: self.instance_parameters.committee_pubkeys.clone(), - n_of_n_public_key: committee_public_key, - n_of_n_taproot_public_key: committee_taproot_public_key, - }) - } - - pub fn get_base_context(&self) -> BaseBitvmContext { - self.instance_parameters.get_base_context() - } -} - -#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)] -pub struct Bitvm2Graph { - pub(crate) operator_pre_signed: bool, - pub(crate) committee_pre_signed: bool, - pub parameters: Bitvm2GraphParameters, - - pub cur_prekickoff: PrekickoffTransaction, - pub next_prekickoff: PrekickoffTransaction, - pub force_skip_kickoff: ForceSkipKickoffTransaction, - pub quick_challenge: QuickChallengeTransaction, - pub challenge_incomplete_kickoff: ChallengeIncompleteKickoffTransaction, - - pub pegin: PegInConfirmTransaction, - pub kickoff: KickoffTransaction, - pub take1: Take1Transaction, - pub challenge: ChallengeTransaction, - pub take2: Take2Transaction, - - pub watchtower_challenge_init: WatchtowerChallengeInitTransaction, - pub watchtower_challenge_timeout_txns: Vec, - pub nack_txns: Vec, - pub blockhash_commit_timeout: BlockhashCommitTimeoutTransaction, - - pub assert_init: AssertInitTransaction, - pub assert_commit_timeout_txns: Vec, - - pub connector_e: ConnectorE, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SimplifiedBitvm2Graph { - pub(crate) operator_pre_signed: bool, - pub(crate) committee_pre_signed: bool, - pub parameters: Bitvm2GraphParameters, - pub connector_e: ConnectorE, - pub assert_commit_num: usize, - pub operator_pre_sigs: Option>, - pub committee_pre_sigs: Option, -} - -impl Bitvm2Graph { - pub fn operator_pre_signed(&self) -> bool { - self.operator_pre_signed - } - pub fn committee_pre_signed(&self) -> bool { - self.committee_pre_signed - } - pub fn to_simplified(&self) -> Result { - fn extract_sig_from_witness(witness: &Witness) -> Result { - witness - .nth(0) - .and_then(|data| bitcoin::taproot::Signature::from_slice(data).ok()) - .ok_or_else(|| anyhow::anyhow!("No valid signature found in witness")) - } - let operator_pre_sigs = if self.operator_pre_signed { - Some(vec![ - self.force_skip_kickoff.tx().input[0].witness.clone(), - self.force_skip_kickoff.tx().input[1].witness.clone(), - self.quick_challenge.tx().input[0].witness.clone(), - self.quick_challenge.tx().input[1].witness.clone(), - self.challenge_incomplete_kickoff.tx().input[0].witness.clone(), - self.challenge_incomplete_kickoff.tx().input[1].witness.clone(), - ]) - } else { - None - }; - let committee_pre_sigs = if self.committee_pre_signed { - let take1 = vec![extract_sig_from_witness(&self.take1.tx().input[0].witness)?]; - let take2 = vec![extract_sig_from_witness(&self.take2.tx().input[0].witness)?]; - let challenge = vec![extract_sig_from_witness(&self.challenge.tx().input[0].witness)?]; - let blockhash_commit_timeout = vec![ - extract_sig_from_witness(&self.blockhash_commit_timeout.tx().input[0].witness)?, - extract_sig_from_witness(&self.blockhash_commit_timeout.tx().input[1].witness)?, - ]; - let mut watchtower_challenge_timeout = Vec::new(); - let mut nack = Vec::new(); - for i in 0..self.parameters.watchtower_pubkeys.len() { - let watchtower_challeng_timeout_sig = extract_sig_from_witness( - &self.watchtower_challenge_timeout_txns[i].tx().input[1].witness, - )?; - watchtower_challenge_timeout.push(watchtower_challeng_timeout_sig); - let nack_sig0 = extract_sig_from_witness(&self.nack_txns[i].tx().input[0].witness)?; - let nack_sig1 = extract_sig_from_witness(&self.nack_txns[i].tx().input[1].witness)?; - nack.push(nack_sig0); - nack.push(nack_sig1); - } - let mut assert_commit_timeout = Vec::new(); - for i in 0..self.assert_commit_timeout_txns.len() { - let sig_0 = extract_sig_from_witness( - &self.assert_commit_timeout_txns[i].tx().input[0].witness, - )?; - let sig_1 = extract_sig_from_witness( - &self.assert_commit_timeout_txns[i].tx().input[1].witness, - )?; - assert_commit_timeout.push(sig_0); - assert_commit_timeout.push(sig_1); - } - Some(CommitteeSignatures { - take1, - take2, - challenge, - blockhash_commit_timeout, - watchtower_challenge_timeout, - nack, - assert_commit_timeout, - }) - } else { - None - }; - Ok(SimplifiedBitvm2Graph { - operator_pre_signed: self.operator_pre_signed, - committee_pre_signed: self.committee_pre_signed, - parameters: self.parameters.clone(), - connector_e: self.connector_e.clone(), - assert_commit_num: self.assert_commit_timeout_txns.len(), - operator_pre_sigs, - committee_pre_sigs, - }) - } - pub fn from_simplified(simplified: &SimplifiedBitvm2Graph) -> Result { - let mut graph = generate_bitvm_graph_inner( - simplified.parameters.clone(), - simplified.connector_e.clone(), - )?; - if simplified.operator_pre_signed { - let operator_pre_sigs = simplified - .operator_pre_sigs - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Missing operator pre signatures"))?; - push_operator_pre_signature(&mut graph, operator_pre_sigs)?; - graph.operator_pre_signed = true; - } - if simplified.committee_pre_signed { - let committee_pre_sigs = simplified - .committee_pre_sigs - .as_ref() - .ok_or_else(|| anyhow::anyhow!("Missing committee pre signatures"))?; - push_committee_pre_signatures(&mut graph, committee_pre_sigs)?; - graph.committee_pre_signed = true; - } - Ok(graph) - } -} - -pub struct BaseBitvmContext { - pub network: Network, - pub n_of_n_public_keys: Vec, - pub n_of_n_public_key: PublicKey, - pub n_of_n_taproot_public_key: XOnlyPublicKey, -} - -impl BaseContext for BaseBitvmContext { - fn network(&self) -> Network { - self.network - } - fn n_of_n_public_keys(&self) -> &Vec { - &self.n_of_n_public_keys - } - fn n_of_n_public_key(&self) -> &PublicKey { - &self.n_of_n_public_key - } - fn n_of_n_taproot_public_key(&self) -> &XOnlyPublicKey { - &self.n_of_n_taproot_public_key - } -} - -pub fn get_magic_bytes(net: &Network) -> Vec { - match net { - Network::Bitcoin => hex::encode(b"GTV6").as_bytes().to_vec(), - _ => hex::encode(b"GTT6").as_bytes().to_vec(), - } -} - -pub mod node_serializer { - use serde::{self, Deserialize, Deserializer, Serializer}; - use std::str::FromStr; - - pub mod address { - use super::*; - use bitcoin::Address; - - pub fn serialize(addr: &Address, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&addr.to_string()) - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let s = String::deserialize(deserializer)?; - match Address::from_str(&s) { - Ok(addr) => Ok(addr.assume_checked()), - Err(e) => Err(serde::de::Error::custom(e)), - } - } - } - - pub mod wots_pubkeys { - use super::*; - use crate::types::OperatorWotsPublicKeys; - use bitvm::chunk::api::{NUM_HASH, NUM_PUBS, NUM_U256}; - use bitvm::signatures::{Wots, Wots16, Wots32}; - use goat::disprove_scripts::{NUM_GUEST_PUBS_ASSERT, NUM_GUEST_PUBS_EXTRA}; - use serde::de::Error as DeError; - use serde::ser::SerializeSeq; - - pub fn serialize( - pubkeys: &OperatorWotsPublicKeys, - serializer: S, - ) -> Result - where - S: Serializer, - { - let total_len = pubkeys.0.len() - + pubkeys.1.len() - + pubkeys.2.0.len() - + pubkeys.2.1.len() - + pubkeys.2.2.len(); - - let mut seq = serializer.serialize_seq(Some(total_len))?; - - fn push_pk(seq: &mut S, pk: &::PublicKey) -> Result<(), S::Error> - where - S: SerializeSeq, - W: Wots, - { - // pk: AsRef<[[u8; 20]]> - let digits = pk.as_ref(); - - debug_assert_eq!(digits.len(), W::TOTAL_DIGIT_LEN as usize); - - let out: Vec> = digits.iter().map(|d| d.to_vec()).collect(); - seq.serialize_element(&out) - } - - for pk in pubkeys.0.iter() { - push_pk::<_, Wots32>(&mut seq, pk)?; - } - for pk in pubkeys.1.iter() { - push_pk::<_, Wots32>(&mut seq, pk)?; - } - for pk in pubkeys.2.0.iter() { - push_pk::<_, Wots32>(&mut seq, pk)?; - } - for pk in pubkeys.2.1.iter() { - push_pk::<_, Wots32>(&mut seq, pk)?; - } - for pk in pubkeys.2.2.iter() { - push_pk::<_, Wots16>(&mut seq, pk)?; - } - - seq.end() - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let all: Vec>> = Vec::deserialize(deserializer)?; - let expected = - NUM_GUEST_PUBS_EXTRA + NUM_GUEST_PUBS_ASSERT + NUM_PUBS + NUM_U256 + NUM_HASH; - - if all.len() != expected { - return Err(D::Error::custom(format!( - "Invalid WOTS pubkey count: expected {expected}, got {}", - all.len() - ))); - } - - let mut cursor = 0; - fn extract_wots_pubkeys( - src: &[Vec>], - cursor: usize, - label: &str, - ) -> Result<[::PublicKey; N], E> - where - W: Wots, - E: DeError, - { - let digit_len = W::TOTAL_DIGIT_LEN as usize; - if src.len().checked_sub(cursor).is_none_or(|r| r < N) { - return Err(E::custom(format!( - "{label}: not enough elements: need {N}, have {}", - src.len() - cursor - ))); - } - let slice = &src[cursor..cursor + N]; - - let mut out: Vec<::PublicKey> = Vec::with_capacity(N); - for (i, pk) in slice.iter().enumerate() { - if pk.len() != digit_len { - return Err(E::custom(format!( - "{label}[{i}] invalid digit len: expected {digit_len}, got {}", - pk.len() - ))); - } - - let mut digits: Vec<[u8; 20]> = Vec::with_capacity(digit_len); - for (j, d) in pk.iter().enumerate() { - let arr: [u8; 20] = d.as_slice().try_into().map_err(|_| { - E::custom(format!("{label}[{i}][{j}] invalid hash len (expected 20)")) - })?; - digits.push(arr); - } - - let pk: ::PublicKey = digits - .try_into() - .map_err(|_| E::custom(format!("{label}[{i}] size mismatch")))?; - - out.push(pk); - } - - out.try_into().map_err(|_| E::custom(format!("{label}: final size mismatch"))) - } - - let pk0 = extract_wots_pubkeys::( - &all, - cursor, - "guestpk.extra", - )?; - cursor += NUM_GUEST_PUBS_EXTRA; - - let pk1 = extract_wots_pubkeys::( - &all, - cursor, - "guestpk.assert", - )?; - cursor += NUM_GUEST_PUBS_ASSERT; - - let pk20 = - extract_wots_pubkeys::(&all, cursor, "groth16pk.pub")?; - cursor += NUM_PUBS; - - let pk21 = extract_wots_pubkeys::( - &all, - cursor, - "groth16pk.wots256", - )?; - cursor += NUM_U256; - - // FIXME: this is a tricky way to handle Wots16: if we use ? modifier, this will raise SEGV. - #[allow(clippy::question_mark)] - let pk22 = match extract_wots_pubkeys::( - &all, - cursor, - "groth16pk.wots_hash", - ) { - Err(e) => return Err(e), - Ok(pk) => pk, - }; - Ok((pk0, pk1, Box::new((pk20, pk21, pk22)))) - } - } -} - -#[cfg(test)] -mod tests { - use crate::operator::generate_wots_keys; - use crate::types::{OperatorWotsPublicKeys, node_serializer}; - use bitcoin::{Address, Network, key::PublicKey}; - use rand::rngs::OsRng; - use secp256k1::{Keypair, Secp256k1, SecretKey}; - use serde::{Deserialize, Serialize}; - use std::fmt::Debug; - - #[derive(Clone, Copy)] - pub enum AddrKind { - P2pkh, - P2wpkh, - P2shWpkh, - P2tr, - } - - fn random_address(network: Network, kind: AddrKind) -> Address { - let secp = Secp256k1::new(); - - match kind { - AddrKind::P2tr => { - let kp = Keypair::new(&secp, &mut OsRng); - let (xonly, _) = kp.x_only_public_key(); - Address::p2tr(&secp, xonly, None, network) - } - _ => { - let sk = SecretKey::new(&mut OsRng); - let pk = PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk)); - - let privkey = bitcoin::key::PrivateKey { - compressed: true, - network: network.into(), - inner: sk, - }; - let cpk = bitcoin::CompressedPublicKey::from_private_key(&secp, &privkey).unwrap(); - - match kind { - AddrKind::P2pkh => Address::p2pkh(&pk, network), - AddrKind::P2wpkh => Address::p2wpkh(&cpk, network), - AddrKind::P2shWpkh => Address::p2shwpkh(&cpk, network), - AddrKind::P2tr => unreachable!(), - } - } - } - } - - #[derive(Clone, PartialEq, Eq, Serialize, Deserialize)] - pub struct WotsKeys { - #[serde(with = "node_serializer::wots_pubkeys")] - pub pubs: OperatorWotsPublicKeys, - #[serde(with = "node_serializer::address")] - pub address: Address, - } - - #[cfg(test)] - impl Debug for WotsKeys { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "WotsKeys(..)") - } - } - - #[test] - fn test_wots_keys_serializer() { - for &network in &[Network::Bitcoin, Network::Testnet, Network::Signet, Network::Regtest] { - for kind in &[AddrKind::P2pkh, AddrKind::P2wpkh, AddrKind::P2shWpkh, AddrKind::P2tr] { - let (_, pubs) = generate_wots_keys("seed"); - let address = random_address(network, *kind); - let original = WotsKeys { pubs, address }; - - let json = serde_json::to_vec(&original).unwrap(); - let parsed: WotsKeys = serde_json::from_slice(&json).unwrap(); - assert_eq!(original, parsed); - - let encoded = bincode::serialize(&original).unwrap(); - let decoded: WotsKeys = bincode::deserialize(&encoded).unwrap(); - assert_eq!(original, decoded); - } - } - } - - #[test] - fn test_address_serializer() { - #[derive(Serialize, Deserialize)] - struct AddressTest { - #[serde(with = "node_serializer::address")] - address: Address, - } - for &network in &[Network::Bitcoin, Network::Testnet, Network::Signet, Network::Regtest] { - for kind in &[AddrKind::P2pkh, AddrKind::P2wpkh, AddrKind::P2shWpkh, AddrKind::P2tr] { - let address = random_address(network, *kind); - let test_instance = AddressTest { address: address.clone() }; - let json = serde_json::to_string(&test_instance).unwrap(); - let parsed: AddressTest = serde_json::from_str(&json).unwrap(); - assert_eq!(address, parsed.address); - } - } - } -} diff --git a/crates/bitvm2-ga/src/watchtower/api.rs b/crates/bitvm2-ga/src/watchtower/api.rs deleted file mode 100644 index 468769f3..00000000 --- a/crates/bitvm2-ga/src/watchtower/api.rs +++ /dev/null @@ -1,74 +0,0 @@ -use anyhow::{Result, bail}; -use bitcoin::{Address, Amount, OutPoint, Transaction, XOnlyPublicKey, key::Keypair}; -use goat::{ - connectors::watchtower_connectors::{AckConnector, WatchtowerChallengeConnector}, - transactions::{ - base::Input, pre_signed::PreSignedTransaction, watchtower_challenge::watchtower_challenge, - }, -}; - -use crate::types::Bitvm2Graph; - -pub fn estimate_watchtower_challenge_vbytes(commitment_data_len: usize) -> usize { - let base_vbytes = 120; - let commitment_vbytes = commitment_data_len * 12 / 10; // assuming 1.2 vbytes per byte of commitment data - base_vbytes + commitment_vbytes -} - -pub fn build_watchtower_challenge_tx( - graph: &Bitvm2Graph, - watchtower_keypair: &Keypair, - watchtower_index: usize, - commitment_data: &[u8], - payer_inputs: Vec, - change_address: &Address, - fee_amount: Amount, -) -> Result { - if watchtower_index >= graph.parameters.watchtower_pubkeys.len() { - bail!("Invalid watchtower index"); - } - let network = graph.parameters.instance_parameters.network; - let n_of_n_taproot_public_key = - XOnlyPublicKey::from(graph.parameters.instance_parameters.committee_agg_pubkey); - let operator_taproot_public_key = XOnlyPublicKey::from(graph.parameters.operator_pubkey); - let watchtower_taproot_public_key = graph.parameters.watchtower_pubkeys[watchtower_index]; - let hashlock = graph.parameters.hashlocks[watchtower_index]; - let watchtower_connectors = ( - WatchtowerChallengeConnector::new( - network, - &operator_taproot_public_key, - &watchtower_taproot_public_key, - ), - AckConnector::new(network, &n_of_n_taproot_public_key, &hashlock), - ); - if XOnlyPublicKey::from_keypair(watchtower_keypair).0 != watchtower_taproot_public_key { - bail!("Watchtower keypair does not match the watchtower public key"); - } - let watchtower_challenge_connector_vout = watchtower_index * 2; - let watchtower_challenge_connector_amount = graph - .watchtower_challenge_init - .tx() - .output - .get(watchtower_challenge_connector_vout) - .ok_or_else(|| anyhow::anyhow!("watchtower index out of bounds"))? - .value; - let input_0 = Input { - outpoint: OutPoint { - txid: graph.watchtower_challenge_init.tx().compute_txid(), - vout: watchtower_challenge_connector_vout as u32, - }, - amount: watchtower_challenge_connector_amount, - }; - match watchtower_challenge( - watchtower_keypair, - &watchtower_connectors, - commitment_data, - input_0, - payer_inputs, - change_address, - fee_amount, - ) { - Ok(tx) => Ok(tx), - Err(e) => bail!("Failed to build watchtower challenge transaction: {e}"), - } -} diff --git a/crates/client/src/goat_chain/chain_adaptor.rs b/crates/client/src/goat_chain/chain_adaptor.rs index bc3e7d34..b20b8ea4 100644 --- a/crates/client/src/goat_chain/chain_adaptor.rs +++ b/crates/client/src/goat_chain/chain_adaptor.rs @@ -102,7 +102,12 @@ pub trait ChainAdaptor: Send + Sync { instance_id: &[u8; 16], graph_id: &[u8; 16], ) -> anyhow::Result; - async fn gateway_cancel_withdraw(&self, graph_id: &[u8; 16]) -> anyhow::Result; + async fn gateway_cancel_withdraw( + &self, + graph_id: &[u8; 16], + nonce: U256, + committee_signs: &[Vec], + ) -> anyhow::Result; async fn gateway_process_withdraw( &self, graph_id: &[u8; 16], @@ -219,6 +224,8 @@ pub trait ChainAdaptor: Send + Sync { async fn committee_mana_is_validate_peer_id(&self, peer_id: &[u8]) -> anyhow::Result; async fn committee_mana_get_watchtowers(&self) -> anyhow::Result>; + async fn committee_mana_get_verifiers(&self) -> anyhow::Result>>; + async fn committee_mana_is_verifier(&self, peer_id: &[u8]) -> anyhow::Result; async fn committee_mana_add_watchtower( &self, watchtower: &[u8; 32], @@ -267,9 +274,6 @@ pub enum GoatNetwork { #[derive(Copy, Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Display, EnumString)] pub enum DisproveTxType { - AssertTimeout, - OperatorCommitTimeout, - OperatorNack, Disprove, QuickChallenge, ChallengeIncompleteKickoff, @@ -363,9 +367,8 @@ pub struct GraphData { pub kickoff_txid: [u8; 32], pub take1_txid: [u8; 32], pub take2_txid: [u8; 32], - pub commit_timout_txid: [u8; 32], - pub assert_timeout_txids: Vec<[u8; 32]>, - pub nack_txids: Vec<[u8; 32]>, + pub prover_assert_txid: [u8; 32], + pub disprove_txids: Vec<[u8; 32]>, } #[derive(Clone, Debug)] diff --git a/crates/client/src/goat_chain/evmchain.rs b/crates/client/src/goat_chain/evmchain.rs index 5b119c9d..b3c80e82 100644 --- a/crates/client/src/goat_chain/evmchain.rs +++ b/crates/client/src/goat_chain/evmchain.rs @@ -186,8 +186,13 @@ impl EvmChain { self.adaptor.gateway_init_withdraw(instance_id.as_bytes(), graph_id.as_bytes()).await } - pub async fn gateway_cancel_withdraw(&self, graph_id: &Uuid) -> anyhow::Result { - self.adaptor.gateway_cancel_withdraw(graph_id.as_bytes()).await + pub async fn gateway_cancel_withdraw( + &self, + graph_id: &Uuid, + nonce: U256, + committee_signs: &[Vec], + ) -> anyhow::Result { + self.adaptor.gateway_cancel_withdraw(graph_id.as_bytes(), nonce, committee_signs).await } pub async fn gateway_process_withdraw( @@ -423,6 +428,15 @@ impl EvmChain { pub async fn committee_mana_get_watchtowers(&self) -> anyhow::Result> { self.adaptor.committee_mana_get_watchtowers().await } + + pub async fn committee_mana_get_verifiers(&self) -> anyhow::Result>> { + self.adaptor.committee_mana_get_verifiers().await + } + + pub async fn committee_mana_is_verifier(&self, peer_id: &[u8]) -> anyhow::Result { + self.adaptor.committee_mana_is_verifier(peer_id).await + } + pub async fn committee_mana_add_watchtower( &self, watchtower: &[u8; 32], diff --git a/crates/client/src/goat_chain/goat_adaptor.rs b/crates/client/src/goat_chain/goat_adaptor.rs index babad5f0..e25d19a5 100644 --- a/crates/client/src/goat_chain/goat_adaptor.rs +++ b/crates/client/src/goat_chain/goat_adaptor.rs @@ -59,9 +59,6 @@ sol!( #[sol(rpc)] interface IGateway { enum DisproveTxType { - AssertTimeout, - OperatorCommitTimeout, - OperatorNack, Disprove, QuickChallenge, ChallengeIncompleteKickoff @@ -73,7 +70,7 @@ sol!( Processing, Locked, Claimed, - Discarded, + Discarded } enum WithdrawStatus { None, @@ -121,9 +118,8 @@ sol!( bytes32 kickoffTxid; bytes32 take1Txid; bytes32 take2Txid; - bytes32 commitTimoutTxid; - bytes32[] assertTimoutTxids; - bytes32[] NackTxids; + bytes32 proverAssertTxid; + bytes32[] disproveTxids; } struct BitcoinTx { @@ -147,10 +143,10 @@ sol!( uint64 public peginFeeRate; uint64 public minOperatorRewardSats; uint64 public operatorRewardRate; - uint64 public minStakeAmount; - uint64 public minChallengerReward; - uint64 public minDisproverReward; - uint64 public minSlashAmount; + uint256 public minStakeAmount; + uint256 public minChallengerReward; + uint256 public minDisproverReward; + uint256 public minSlashAmount; address public pegBTC; address public bitcoinSPV; @@ -171,19 +167,21 @@ sol!( function postGraphData(bytes16 instanceId, bytes16 graphId, GraphData calldata graphData, bytes[] calldata committeeSigs) public; function getGraphData(bytes16 graphId) external view returns (GraphData memory); function initWithdraw(bytes16 instanceId, bytes16 graphId) external; - function cancelWithdraw(bytes16 graphId) external; + function committeeCancelWithdraw(bytes16 graphId, uint256 nonce, bytes[] calldata committeeSigs) external; function proceedWithdraw(bytes16 graphId, BitcoinTx calldata rawKickoffTx, BitcoinTxProof calldata kickoffProof) external; function finishWithdrawHappyPath(bytes16 graphId, BitcoinTx calldata rawTake1Tx, BitcoinTxProof calldata take1Proof) external; function finishWithdrawUnhappyPath(bytes16 graphId, BitcoinTx calldata rawTake2Tx, BitcoinTxProof calldata take2Proof) external; function finishWithdrawDisproved(bytes16 graphId, DisproveTxType disproveTxType, uint256 txnIndex, BitcoinTx calldata rawChallengeStartTx, BitcoinTxProof calldata challengeStartTxProof, BitcoinTx calldata rawChallengeFinishTx, BitcoinTxProof calldata challengeFinishTxProof ) external; function getCommitteePubkeys(bytes16 instanceId) public view returns (bytes[] memory committeePubkeys); + function getCommitteeAddresses(bytes16 instanceId) public view returns (address[] memory committeeAddresses); + function getCommitteePubkeysUnsafe(bytes16 instanceId) public view returns (bytes[] memory committeePubkeys); function getPostGraphDigest(bytes16 instanceId, bytes16 graphId, GraphData calldata graphData) public view returns (bytes32); function getPostPeginDigest(bytes16 instanceId, bytes32 peginTxid) public view returns (bytes32); function getGraphIdsByInstanceId(bytes16 instanceId) external view returns (bytes16[] memory); + function getCancelWithdrawDigestNonced(bytes16 graphId, uint256 nonce) public view returns (bytes32); + function getUnlockStakeDigestNonced(address operator, uint256 amount, uint256 nonce) public view returns (bytes32); + function unlockOperatorStake(address operator, uint256 amount, uint256 nonce, bytes[] calldata committeeSigs) external; - // Contract is not implements this functions, do something later - function getInitializedInstanceIds() external view returns (bytes16[] memory retInstanceIds, bytes16[] memory retGraphIds); - function getInstanceIdsByPubKey(bytes32 operatorPubkey) external view returns (bytes16[] memory retInstanceIds, bytes16[] memory retGraphIds); } ); @@ -263,6 +261,8 @@ sol!( function balanceOf(address account) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); function approve(address spender, uint256 amount) external returns (bool); + function mint(address to, uint256 amount) external; + function burn(uint256 amount) external; } ); @@ -303,6 +303,9 @@ sol!( function slashStake(address operator, uint256 amount) external; function lockStake(address operator, uint256 amount) external; function unlockStake(address operator, uint256 amount) external; + function stake(uint256 amount) external; + function unstake(uint256 amount) external; + function registerPubkey(bytes32 pubkey) external; } ); @@ -315,11 +318,23 @@ sol!( function committeeSize() external view returns (uint256); function quorumSize() external view returns (uint256); function verifySignatures(bytes32 msgHash, bytes[] memory signatures) external view returns (bool); + function registerPeerId(bytes calldata peerId) external; function getCommitteePeerId(address member) external view returns (bytes); - function isValidPeerId(bytes peerId) external view returns (bool); + function isValidPeerId(bytes calldata peerId) external view returns (bool); function getWatchtowers() external view returns (bytes32[] memory); + function getVerifiers() external view returns (bytes[] memory); + function isVerifier(bytes calldata peerId) external view returns (bool); function addWatchtower(bytes32 watchtower, uint256 nonce, bytes[] memory authSignatures) external; function removeWatchtower(bytes32 watchtower, uint256 nonce, bytes[] memory authSignatures) external; + function getNoncedDigest(bytes32 msgHash, uint256 nonce) external view returns (bytes32); + function getAddWatchtowerDigestNonced(bytes32 watchtower, uint256 nonce) external view returns (bytes32); + function getRemoveWatchtowerDigestNonced(bytes32 watchtower, uint256 nonce) external view returns (bytes32); + function isAuthorizedCaller(address caller) external view returns (bool); + function addAuthorizedCaller(address caller, uint256 nonce, bytes[] memory authSignatures) external; + function removeAuthorizedCaller(address caller, uint256 nonce, bytes[] memory authSignatures) external; + function getAddAuthorizedCallerDigestNonced(address caller, uint256 nonce) external view returns (bytes32); + function getRemoveAuthorizedCallerDigestNonced(address caller, uint256 nonce) external view returns (bytes32); + function executeNoncedSignatures(bytes32 msgHash, uint256 nonce, bytes[] memory signatures) external; } ); @@ -489,7 +504,7 @@ impl GoatAdaptor { fn get_committee_management(&self) -> anyhow::Result<&CommitteeManagementInstance> { self.committee_management .as_ref() - .ok_or_else(|| anyhow::anyhow!("CommitteeMnagement not initialized")) + .ok_or_else(|| anyhow::anyhow!("CommitteeManagement not initialized")) } fn get_stake_management(&self) -> anyhow::Result<&StakeManagementInstance> { @@ -744,11 +759,6 @@ impl From for WithdrawStatus { impl From for IGateway::DisproveTxType { fn from(value: DisproveTxType) -> Self { match value { - DisproveTxType::OperatorNack => IGateway::DisproveTxType::OperatorNack, - DisproveTxType::OperatorCommitTimeout => { - IGateway::DisproveTxType::OperatorCommitTimeout - } - DisproveTxType::AssertTimeout => IGateway::DisproveTxType::AssertTimeout, DisproveTxType::Disprove => IGateway::DisproveTxType::Disprove, DisproveTxType::QuickChallenge => IGateway::DisproveTxType::QuickChallenge, DisproveTxType::ChallengeIncompleteKickoff => { @@ -807,14 +817,9 @@ impl From for IGateway::GraphData { kickoffTxid: FixedBytes::from_slice(&value.kickoff_txid), take1Txid: FixedBytes::from_slice(&value.take1_txid), take2Txid: FixedBytes::from_slice(&value.take2_txid), - commitTimoutTxid: FixedBytes::from_slice(&value.commit_timout_txid), - assertTimoutTxids: value - .assert_timeout_txids - .into_iter() - .map(|txid| FixedBytes::from_slice(&txid)) - .collect::>(), - NackTxids: value - .nack_txids + proverAssertTxid: FixedBytes::from_slice(&value.prover_assert_txid), + disproveTxids: value + .disprove_txids .into_iter() .map(|txid| FixedBytes::from_slice(&txid)) .collect::>(), @@ -831,13 +836,8 @@ impl From for GraphData { kickoff_txid: value.kickoffTxid.0, take1_txid: value.take1Txid.0, take2_txid: value.take2Txid.0, - commit_timout_txid: value.commitTimoutTxid.0, - assert_timeout_txids: value - .assertTimoutTxids - .into_iter() - .map(|txid| txid.into()) - .collect(), - nack_txids: value.NackTxids.into_iter().map(|txid| txid.into()).collect(), + prover_assert_txid: value.proverAssertTxid.0, + disprove_txids: value.disproveTxids.into_iter().map(|txid| txid.into()).collect(), } } } @@ -981,22 +981,22 @@ impl ChainAdaptor for GoatAdaptor { async fn gateway_get_min_stake_amount(&self) -> anyhow::Result { let gateway = self.get_gateway()?; - Ok(gateway.minStakeAmount().call().await?) + Ok(gateway.minStakeAmount().call().await?.try_into()?) } async fn gateway_get_min_challenger_reward(&self) -> anyhow::Result { let gateway = self.get_gateway()?; - Ok(gateway.minChallengerReward().call().await?) + Ok(gateway.minChallengerReward().call().await?.try_into()?) } async fn gateway_get_min_disprover_reward(&self) -> anyhow::Result { let gateway = self.get_gateway()?; - Ok(gateway.minDisproverReward().call().await?) + Ok(gateway.minDisproverReward().call().await?.try_into()?) } async fn gateway_get_min_slash_amount(&self) -> anyhow::Result { let gateway = self.get_gateway()?; - Ok(gateway.minSlashAmount().call().await?) + Ok(gateway.minSlashAmount().call().await?.try_into()?) } async fn gateway_get_committee_management(&self) -> anyhow::Result<[u8; 20]> { @@ -1136,29 +1136,14 @@ impl ChainAdaptor for GoatAdaptor { } async fn gateway_get_initialized_ids(&self) -> anyhow::Result> { - let gateway = self.get_gateway()?; - let ids = gateway.getInitializedInstanceIds().call().await?; - let instance_ids: Vec = - ids.retInstanceIds.iter().map(|v| Uuid::from_bytes(v.0)).collect(); - let graph_ids: Vec = - ids.retGraphIds.into_iter().map(|v| Uuid::from_bytes(v.0)).collect(); - Ok(instance_ids.into_iter().zip(graph_ids).collect()) + bail!("Gateway.getInitializedInstanceIds is not available in the gc contract interface") } async fn gateway_get_instanceids_by_pubkey( &self, - operator_pubkey: &[u8; 32], + _operator_pubkey: &[u8; 32], ) -> anyhow::Result> { - let gateway = self.get_gateway()?; - let ids = gateway - .getInstanceIdsByPubKey(FixedBytes::<32>::from_slice(operator_pubkey)) - .call() - .await?; - let instance_ids: Vec = - ids.retInstanceIds.iter().map(|v| Uuid::from_bytes(v.0)).collect(); - let graph_ids: Vec = - ids.retGraphIds.into_iter().map(|v| Uuid::from_bytes(v.0)).collect(); - Ok(instance_ids.into_iter().zip(graph_ids).collect()) + bail!("Gateway.getInstanceIdsByPubKey is not available in the gc contract interface") } async fn gateway_init_withdraw( @@ -1176,10 +1161,16 @@ impl ChainAdaptor for GoatAdaptor { Ok(tx_hash.to_string()) } - async fn gateway_cancel_withdraw(&self, graph_id: &[u8; 16]) -> anyhow::Result { + async fn gateway_cancel_withdraw( + &self, + graph_id: &[u8; 16], + nonce: U256, + committee_signs: &[Vec], + ) -> anyhow::Result { let gateway = self.get_gateway()?; + let signs: Vec = committee_signs.iter().map(|v| Bytes::copy_from_slice(v)).collect(); let tx_request = gateway - .cancelWithdraw(FixedBytes::from_slice(graph_id)) + .committeeCancelWithdraw(FixedBytes::from_slice(graph_id), nonce, signs) .from(self.get_default_signer_address()) .chain_id(self.chain_id) .into_transaction_request(); @@ -1531,6 +1522,22 @@ impl ChainAdaptor for GoatAdaptor { .collect::>()) } + async fn committee_mana_get_verifiers(&self) -> anyhow::Result>> { + let committee_management = self.get_committee_management()?; + Ok(committee_management + .getVerifiers() + .call() + .await? + .into_iter() + .map(|v| v.to_vec()) + .collect::>>()) + } + + async fn committee_mana_is_verifier(&self, peer_id: &[u8]) -> anyhow::Result { + let committee_management = self.get_committee_management()?; + Ok(committee_management.isVerifier(Bytes::copy_from_slice(peer_id)).call().await?) + } + async fn committee_mana_add_watchtower( &self, watchtower: &[u8; 32], diff --git a/crates/client/src/goat_chain/mock_goat_adaptor.rs b/crates/client/src/goat_chain/mock_goat_adaptor.rs index 7683147d..e00e1a13 100644 --- a/crates/client/src/goat_chain/mock_goat_adaptor.rs +++ b/crates/client/src/goat_chain/mock_goat_adaptor.rs @@ -266,7 +266,12 @@ impl ChainAdaptor for MockAdaptor { Ok(hex::encode(generate_random_bytes(32))) } - async fn gateway_cancel_withdraw(&self, _graph_id: &[u8; 16]) -> anyhow::Result { + async fn gateway_cancel_withdraw( + &self, + _graph_id: &[u8; 16], + _nonce: U256, + _committee_signs: &[Vec], + ) -> anyhow::Result { info!("call cancel_withdraw"); Ok(hex::encode(generate_random_bytes(32))) } @@ -461,6 +466,14 @@ impl ChainAdaptor for MockAdaptor { Ok(vec![]) } + async fn committee_mana_get_verifiers(&self) -> anyhow::Result>> { + Ok(vec![]) + } + + async fn committee_mana_is_verifier(&self, _peer_id: &[u8]) -> anyhow::Result { + Ok(false) + } + async fn committee_mana_add_watchtower( &self, _watchtower: &[u8; 32], diff --git a/crates/client/src/goat_chain/mod.rs b/crates/client/src/goat_chain/mod.rs index 67ab134f..eeb2d99c 100644 --- a/crates/client/src/goat_chain/mod.rs +++ b/crates/client/src/goat_chain/mod.rs @@ -252,8 +252,13 @@ impl GOATClient { self.chain_service.gateway_init_withdraw(instance_id, graph_id).await } - pub async fn gateway_cancel_withdraw(&self, graph_id: &Uuid) -> anyhow::Result { - self.chain_service.gateway_cancel_withdraw(graph_id).await + pub async fn gateway_cancel_withdraw( + &self, + graph_id: &Uuid, + nonce: U256, + committee_signs: &[Vec], + ) -> anyhow::Result { + self.chain_service.gateway_cancel_withdraw(graph_id, nonce, committee_signs).await } pub async fn gateway_process_withdraw( @@ -741,6 +746,15 @@ impl GOATClient { .filter_map(|v| XOnlyPublicKey::from_slice(v).ok()) .collect::>()) } + + pub async fn committee_mana_get_verifiers(&self) -> anyhow::Result>> { + self.chain_service.committee_mana_get_verifiers().await + } + + pub async fn committee_mana_is_verifier(&self, peer_id: &[u8]) -> anyhow::Result { + self.chain_service.committee_mana_is_verifier(peer_id).await + } + pub async fn committee_mana_add_watchtower( &self, watchtower: &[u8; 32], diff --git a/crates/client/src/goat_chain/utils.rs b/crates/client/src/goat_chain/utils.rs index e7fcadcb..c000a5fa 100644 --- a/crates/client/src/goat_chain/utils.rs +++ b/crates/client/src/goat_chain/utils.rs @@ -23,7 +23,7 @@ sol!( #[allow(missing_docs)] #[sol(rpc)] interface ICommitteeManagement { - function isValidPeerId(bytes peerId) external view returns (bool); + function isValidPeerId(bytes calldata peerId) external view returns (bool); } ); diff --git a/crates/commit-chain/Cargo.toml b/crates/commit-chain/Cargo.toml index 05a2c247..b981bd7c 100644 --- a/crates/commit-chain/Cargo.toml +++ b/crates/commit-chain/Cargo.toml @@ -9,11 +9,11 @@ bitcoin = { workspace = true, features = ["serde", "rand", "secp-recovery"] } bincode = "1.3.3" # workspace -guest-executor = { workspace = true } +guest-executor = { workspace = true } #header-chain = { path = "../header-chain" } # Ziren -zkm-verifier = { workspace = true } +verifier = { workspace = true } zkm-zkvm = { workspace = true } #zkm-verifier = { path = "../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } @@ -22,7 +22,7 @@ zkm-zkvm = { workspace = true } tracing = { workspace = true, features = ["max_level_trace"] } alloy-primitives = { workspace = true, features = ["sha3-keccak", "map-foldhash", "serde"] } -revm-database-interface = { workspace = true, features = ["serde"]} +revm-database-interface = { workspace = true, features = ["serde"] } #revm = { workspace = true, features = ["serde", "bn"], default-features = false } sha2 = "0.10.9" @@ -37,14 +37,14 @@ serde = { workspace = true, features = ["derive"] } #cosmos-sdk-proto = { version = "0.27", default-features = false, features = ["serde"] } tendermint = { git = "https://github.com/ProjectZKM/tendermint-rs", branch = "patch-0.40.3", default-features = false, features = ["secp256k1"] } tendermint-light-client-verifier = { git = "https://github.com/ProjectZKM/tendermint-rs", branch = "patch-0.40.3", default-features = false, features = [ - "rust-crypto", + "rust-crypto", ] } [dev-dependencies] rand = "0.8.5" serde_json = "1.0" -borsh = {version = "1.5.3", features = ["derive"] } +borsh = { version = "1.5.3", features = ["derive"] } blake3 = "1.6.1" [build-dependencies] diff --git a/crates/commit-chain/src/commit_chain.rs b/crates/commit-chain/src/commit_chain.rs index 18e3519a..bb51246e 100644 --- a/crates/commit-chain/src/commit_chain.rs +++ b/crates/commit-chain/src/commit_chain.rs @@ -9,7 +9,7 @@ pub use tendermint_light_client_verifier::{ types::{Hash, ValidatorSet}, }; -use bitcoin::{Transaction, TxOut, Witness, secp256k1::PublicKey}; +use bitcoin::{Transaction, TxOut, Witness, hashes::Hash as _, secp256k1::PublicKey}; #[derive(Serialize, Deserialize, Debug, PartialEq)] pub struct CommitInfo { @@ -99,6 +99,7 @@ pub struct CommitChainState { pub sequencers: Vec, pub publisher_public_keys: Vec, pub threshold: u16, + pub operator_vk_hash: [u8; 32], } impl CircuitCommit { @@ -114,12 +115,54 @@ impl CircuitCommit { pub const PROOF_SIZE: usize = 260; pub const PUBLIC_INPUTS_SIZE: usize = 36; pub const VK_HASH_SIZE: usize = 66; +pub const LEGACY_COMMIT_CHAIN_COMMITMENT_SIZE: usize = 64; +pub const COMMIT_CHAIN_COMMITMENT_SIZE: usize = 96; +pub const LEGACY_OPERATOR_VK_HASH: [u8; 32] = [0u8; 32]; + +#[derive(Serialize, Deserialize, PartialEq, Eq, Clone, Debug)] +pub struct CommitChainCommitment { + pub sequencer_set_hash: [u8; 32], + pub genesis_evm_block_hash: [u8; 32], + pub operator_vk_hash: [u8; 32], +} #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] pub struct CommitChainCircuitOutput { pub chain_state: CommitChainState, } +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +struct LegacyCommitChainState { + block_height: u32, + commit_txn: Transaction, + genesis_txid: [u8; 32], + sequencers: Vec, + publisher_public_keys: Vec, + threshold: u16, +} + +#[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] +struct LegacyCommitChainCircuitOutput { + chain_state: LegacyCommitChainState, +} + +impl From for CommitChainCircuitOutput { + fn from(output: LegacyCommitChainCircuitOutput) -> Self { + let chain_state = output.chain_state; + CommitChainCircuitOutput { + chain_state: CommitChainState { + block_height: chain_state.block_height, + commit_txn: chain_state.commit_txn, + genesis_txid: chain_state.genesis_txid, + sequencers: chain_state.sequencers, + publisher_public_keys: chain_state.publisher_public_keys, + threshold: chain_state.threshold, + operator_vk_hash: LEGACY_OPERATOR_VK_HASH, + }, + } + } +} + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] pub struct CommitChainCircuitInput { pub prev_proof: CommitChainPrevProofType, @@ -136,6 +179,40 @@ pub fn sequencer_hash(sequencers: &[SequencerInfo]) -> Hash { sequencer_set.hash() } +pub fn parse_commit_chain_commitment(commitment: &[u8]) -> CommitChainCommitment { + assert!( + commitment.len() == LEGACY_COMMIT_CHAIN_COMMITMENT_SIZE + || commitment.len() == COMMIT_CHAIN_COMMITMENT_SIZE, + "commit chain commitment must be 64 or 96 bytes" + ); + + let mut sequencer_set_hash = [0u8; 32]; + sequencer_set_hash.copy_from_slice(&commitment[0..32]); + let mut genesis_evm_block_hash = [0u8; 32]; + genesis_evm_block_hash.copy_from_slice(&commitment[32..64]); + let mut operator_vk_hash = LEGACY_OPERATOR_VK_HASH; + if commitment.len() == COMMIT_CHAIN_COMMITMENT_SIZE { + operator_vk_hash.copy_from_slice(&commitment[64..]); + assert_ne!( + operator_vk_hash, LEGACY_OPERATOR_VK_HASH, + "new commit chain commitment must include non-zero operator vk hash" + ); + } + + CommitChainCommitment { sequencer_set_hash, genesis_evm_block_hash, operator_vk_hash } +} + +/// Decode current or legacy commit-chain public values. +pub fn decode_commit_chain_circuit_output(public_values: &[u8]) -> CommitChainCircuitOutput { + if let Ok(output) = bincode::deserialize::(public_values) { + return output; + } + + bincode::deserialize::(public_values) + .map(Into::into) + .expect("failed to decode commit chain circuit output as current or legacy format") +} + impl CommitChainState { pub fn new(genesis_txid: [u8; 32]) -> Self { CommitChainState { @@ -150,6 +227,7 @@ impl CommitChainState { sequencers: Vec::new(), publisher_public_keys: vec![], threshold: u16::MAX, + operator_vk_hash: [0u8; 32], } } @@ -158,25 +236,34 @@ impl CommitChainState { let mut latest_commit_txn_with_wtns = commit.commit_txn.clone(); let latest_sequencers = &commit.sequencers; let (next_publisher_public_keys, next_threshold) = commit.active_publisher_set(); + let has_prev_commit = !self.commit_txn.output.is_empty(); assert_eq!(commit.genesis_txid, self.genesis_txid); + if !has_prev_commit { + assert_eq!( + latest_commit_txn_with_wtns.compute_txid().as_raw_hash().to_byte_array(), + self.genesis_txid + ); + } // calculate the commitment of latest sequencer set and check the equivalent let expected_latest_commit = extract_op_return_data(&latest_commit_txn_with_wtns.output); + let latest_commitment = parse_commit_chain_commitment(&expected_latest_commit); if let Hash::Sha256(latest_sequencer_set_hash) = sequencer_hash(latest_sequencers) { - assert_eq!(latest_sequencer_set_hash[..], expected_latest_commit[0..32]); + assert_eq!(latest_sequencer_set_hash, latest_commitment.sequencer_set_hash); } else { panic!("Invalid latest sequencer set hash"); } // check the latest txn's prev out is equals to the output of prev_txn let prev_commit_txn_value = &self.commit_txn; - if !self.sequencers.is_empty() { + if has_prev_commit { // calculate the commitment of prev sequencer set and check the equivalent let expected_prev_commit = extract_op_return_data(&prev_commit_txn_value.output); + let prev_commitment = parse_commit_chain_commitment(&expected_prev_commit); if let Hash::Sha256(prev_sequencer_set_hash) = sequencer_hash(&self.sequencers) { - assert_eq!(prev_sequencer_set_hash[..], expected_prev_commit[0..32]); + assert_eq!(prev_sequencer_set_hash, prev_commitment.sequencer_set_hash); } else { panic!("Invalid prev sequencer set hash"); } @@ -224,6 +311,7 @@ impl CommitChainState { self.publisher_public_keys = next_publisher_public_keys.to_vec(); self.threshold = next_threshold; self.block_height = commit.block_height; + self.operator_vk_hash = latest_commitment.operator_vk_hash; } } } @@ -273,7 +361,7 @@ mod tests { use crate::{ create_dummy_publisher_keys, create_sequencer_update_script, finalize, sign_partial, }; - use bitcoin::{Amount, ScriptBuf}; + use bitcoin::{Amount, ScriptBuf, script::PushBytesBuf}; use bitcoin::{ EcdsaSighashType, OutPoint, Sequence, TxIn, Witness, absolute::LockTime, transaction::Version, @@ -294,30 +382,132 @@ mod tests { assert_eq!(expected_op_data.to_vec(), op_return_data); } + fn commitment_payload( + sequencers: &[SequencerInfo], + genesis_evm_block_hash: [u8; 32], + operator_vk_hash: [u8; 32], + ) -> PushBytesBuf { + let mut payload = Vec::with_capacity(96); + if let tendermint_light_client_verifier::types::Hash::Sha256(hash) = + sequencer_hash(sequencers) + { + payload.extend_from_slice(&hash); + } else { + panic!("expected sha256 sequencer hash"); + }; + payload.extend_from_slice(&genesis_evm_block_hash); + payload.extend_from_slice(&operator_vk_hash); + PushBytesBuf::try_from(payload).expect("commitment payload is pushable") + } + #[test] - fn test_apply_commit() { - let commit_info: Vec = serde_json::from_slice(include_bytes!( - "../../../circuits/data/commit-chain/0-1.bin.commits" - )) - .unwrap(); + fn test_parse_commit_chain_commitment_splits_96_byte_payload() { + let sequencer_set_hash = [0x11u8; 32]; + let genesis_evm_block_hash = [0x22u8; 32]; + let operator_vk_hash = [0x33u8; 32]; + let mut payload = Vec::with_capacity(96); + payload.extend_from_slice(&sequencer_set_hash); + payload.extend_from_slice(&genesis_evm_block_hash); + payload.extend_from_slice(&operator_vk_hash); + + let commitment = parse_commit_chain_commitment(&payload); + + assert_eq!(commitment.sequencer_set_hash, sequencer_set_hash); + assert_eq!(commitment.genesis_evm_block_hash, genesis_evm_block_hash); + assert_eq!(commitment.operator_vk_hash, operator_vk_hash); + } - let mut chain_state = CommitChainState::new(commit_info[0].genesis_txid); - chain_state.apply_commit(commit_info.clone()); - assert_eq!(commit_info[0].genesis_txid, chain_state.genesis_txid); - assert_eq!(commit_info[0].sequencers.clone(), chain_state.sequencers.clone()); - assert_eq!(commit_info[0].commit_txn.compute_txid(), chain_state.commit_txn.compute_txid()); + #[test] + fn test_parse_commit_chain_commitment_accepts_legacy_64_byte_payload() { + let sequencer_set_hash = [0x11u8; 32]; + let genesis_evm_block_hash = [0x22u8; 32]; + let mut payload = Vec::with_capacity(64); + payload.extend_from_slice(&sequencer_set_hash); + payload.extend_from_slice(&genesis_evm_block_hash); + + let commitment = parse_commit_chain_commitment(&payload); + + assert_eq!(commitment.sequencer_set_hash, sequencer_set_hash); + assert_eq!(commitment.genesis_evm_block_hash, genesis_evm_block_hash); + assert_eq!(commitment.operator_vk_hash, LEGACY_OPERATOR_VK_HASH); + } - let commit_info2: Vec = serde_json::from_slice(include_bytes!( - "../../../circuits/data/commit-chain/1-1.bin.commits" - )) - .unwrap(); - chain_state.apply_commit(commit_info2.clone()); - assert_eq!(commit_info[0].genesis_txid, chain_state.genesis_txid); - assert_eq!(commit_info2[0].sequencers.clone(), chain_state.sequencers.clone()); - assert_eq!( - commit_info2[0].commit_txn.compute_txid(), - chain_state.commit_txn.compute_txid() - ); + #[test] + fn test_parse_commit_chain_commitment_rejects_new_payload_with_zero_operator_vk_hash() { + let mut payload = vec![0x11u8; 96]; + payload[64..].fill(0); + + let result = std::panic::catch_unwind(|| parse_commit_chain_commitment(&payload)); + + assert!(result.is_err()); + } + + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] + struct LegacyCommitChainState { + block_height: u32, + commit_txn: Transaction, + genesis_txid: [u8; 32], + sequencers: Vec, + publisher_public_keys: Vec, + threshold: u16, + } + + #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] + struct LegacyCommitChainCircuitOutput { + chain_state: LegacyCommitChainState, + } + + #[test] + fn test_decode_commit_chain_circuit_output_accepts_legacy_public_values() { + let legacy_output = LegacyCommitChainCircuitOutput { + chain_state: LegacyCommitChainState { + block_height: 7, + commit_txn: Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: vec![], + output: vec![], + }, + genesis_txid: [0x22u8; 32], + sequencers: vec![], + publisher_public_keys: vec![], + threshold: 0, + }, + }; + let public_values = bincode::serialize(&legacy_output).unwrap(); + + let decoded = decode_commit_chain_circuit_output(&public_values); + + assert_eq!(decoded.chain_state.block_height, legacy_output.chain_state.block_height); + assert_eq!(decoded.chain_state.genesis_txid, legacy_output.chain_state.genesis_txid); + assert_eq!(decoded.chain_state.operator_vk_hash, LEGACY_OPERATOR_VK_HASH); + } + + // todo: use new commit file + #[test] + fn test_apply_commit() { + // let commit_info: Vec = serde_json::from_slice(include_bytes!( + // "../../../circuits/data/commit-chain/0-1.bin.commits" + // )) + // .unwrap(); + // + // let mut chain_state = CommitChainState::new(commit_info[0].genesis_txid); + // chain_state.apply_commit(commit_info.clone()); + // assert_eq!(commit_info[0].genesis_txid, chain_state.genesis_txid); + // assert_eq!(commit_info[0].sequencers.clone(), chain_state.sequencers.clone()); + // assert_eq!(commit_info[0].commit_txn.compute_txid(), chain_state.commit_txn.compute_txid()); + // + // let commit_info2: Vec = serde_json::from_slice(include_bytes!( + // "../../../circuits/data/commit-chain/1-1.bin.commits" + // )) + // .unwrap(); + // chain_state.apply_commit(commit_info2.clone()); + // assert_eq!(commit_info[0].genesis_txid, chain_state.genesis_txid); + // assert_eq!(commit_info2[0].sequencers.clone(), chain_state.sequencers.clone()); + // assert_eq!( + // commit_info2[0].commit_txn.compute_txid(), + // chain_state.commit_txn.compute_txid() + // ); } #[test] @@ -333,13 +523,13 @@ mod tests { let next_threshold = 3u16; let final_threshold = 4u16; let empty_sequencers = vec![]; - let commit0_op_return = if let tendermint_light_client_verifier::types::Hash::Sha256(hash) = - sequencer_hash(&empty_sequencers) - { - ScriptBuf::new_op_return(&hash) - } else { - panic!("expected sha256 sequencer hash"); - }; + let genesis_evm_block_hash = [0x11u8; 32]; + let operator_vk_hash = [0x22u8; 32]; + let commit0_op_return = ScriptBuf::new_op_return(commitment_payload( + &empty_sequencers, + genesis_evm_block_hash, + operator_vk_hash, + )); let commit0 = Transaction { version: Version::TWO, lock_time: LockTime::ZERO, @@ -374,13 +564,12 @@ mod tests { let commit1_redeem_script = create_sequencer_update_script(¤t_pubkeys, current_threshold as usize); - let commit1_op_return = if let tendermint_light_client_verifier::types::Hash::Sha256(hash) = - sequencer_hash(&empty_sequencers) - { - ScriptBuf::new_op_return(&hash) - } else { - panic!("expected sha256 sequencer hash"); - }; + let commit1_operator_vk_hash = [0x33u8; 32]; + let commit1_op_return = ScriptBuf::new_op_return(commitment_payload( + &empty_sequencers, + genesis_evm_block_hash, + commit1_operator_vk_hash, + )); let mut commit1 = Transaction { version: Version::TWO, lock_time: LockTime::ZERO, @@ -431,13 +620,12 @@ mod tests { let commit2_redeem_script = create_sequencer_update_script(&next_pubkeys, next_threshold as usize); - let commit2_op_return = if let tendermint_light_client_verifier::types::Hash::Sha256(hash) = - sequencer_hash(&empty_sequencers) - { - ScriptBuf::new_op_return(&hash) - } else { - panic!("expected sha256 sequencer hash"); - }; + let commit2_operator_vk_hash = [0x44u8; 32]; + let commit2_op_return = ScriptBuf::new_op_return(commitment_payload( + &empty_sequencers, + genesis_evm_block_hash, + commit2_operator_vk_hash, + )); let mut commit2 = Transaction { version: Version::TWO, lock_time: LockTime::ZERO, @@ -498,9 +686,57 @@ mod tests { chain_state.apply_commit(vec![commit0_info]); assert_eq!(chain_state.publisher_public_keys, current_pubkeys); assert_eq!(chain_state.threshold, current_threshold); + assert_eq!(chain_state.operator_vk_hash, operator_vk_hash); chain_state.apply_commit(vec![commit1_info, commit2_info]); assert_eq!(chain_state.publisher_public_keys, final_pubkeys); assert_eq!(chain_state.threshold, final_threshold); + assert_eq!(chain_state.operator_vk_hash, commit2_operator_vk_hash); + } + + #[test] + fn test_apply_commit_tracks_genesis_operator_vk_hash() { + let next_keys = create_dummy_publisher_keys(3, bitcoin::Network::Regtest); + let next_pubkeys: Vec = next_keys.iter().map(|(_, pk)| *pk).collect(); + let empty_sequencers = vec![]; + let genesis_evm_block_hash = [0x55u8; 32]; + let operator_vk_hash = [0x66u8; 32]; + let commit_txn = Transaction { + version: Version::TWO, + lock_time: LockTime::ZERO, + input: vec![], + output: vec![ + TxOut { + value: Amount::from_sat(100_000), + script_pubkey: ScriptBuf::new_p2wsh( + &create_sequencer_update_script(&next_pubkeys, 2).wscript_hash(), + ), + }, + TxOut { + value: Amount::ZERO, + script_pubkey: ScriptBuf::new_op_return(commitment_payload( + &empty_sequencers, + genesis_evm_block_hash, + operator_vk_hash, + )), + }, + ], + }; + let genesis_txid = *commit_txn.compute_txid().as_byte_array(); + let commit = CircuitCommit { + commit_txn, + genesis_txid, + publisher_public_keys: vec![], + threshold: 0, + next_publisher_public_keys: Some(next_pubkeys), + next_threshold: Some(2), + sequencers: empty_sequencers, + block_height: 1, + }; + + let mut chain_state = CommitChainState::new(genesis_txid); + chain_state.apply_commit(vec![commit]); + + assert_eq!(chain_state.operator_vk_hash, operator_vk_hash); } } diff --git a/crates/commit-chain/src/lib.rs b/crates/commit-chain/src/lib.rs index bc9d5d37..bee13b09 100644 --- a/crates/commit-chain/src/lib.rs +++ b/crates/commit-chain/src/lib.rs @@ -2,34 +2,23 @@ mod publisher; pub use publisher::*; mod commit_chain; pub use commit_chain::*; -use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES}; - -pub const TRUSTED_COMMIT_CHAIN_ZKM_VERSION: &str = "v1.2.5"; - -/// Return the fixed `part_stark_vk` used by commit-chain's internal recursive verifier. -pub fn trusted_commit_chain_part_stark_vk() -> Vec { - Groth16Verifier::get_part_stark_vk(TRUSTED_COMMIT_CHAIN_ZKM_VERSION).to_vec() -} pub fn commit_chain_circuit(input: CommitChainCircuitInput) -> CommitChainCircuitOutput { - let current_part_stark_vk = trusted_commit_chain_part_stark_vk(); let mut chain_state = match input.prev_proof { CommitChainPrevProofType::GenesisBlock => { CommitChainState::new(input.commits[0].genesis_txid) } CommitChainPrevProofType::PrevProof(prev_proof) => { println!("verify commit chain of prev proof"); - let groth16_vk = *IMM_GROTH16_VK_BYTES; - let zkm_vk_hash = String::from_utf8(input.zkm_vk_hash.to_vec()).unwrap(); - assert_eq!(input.zkm_version, TRUSTED_COMMIT_CHAIN_ZKM_VERSION); - Groth16Verifier::verify_by_imm_groth16_vk( + verifier::verify_groth16_proof( &input.zkm_proof, &input.zkm_public_values, - &zkm_vk_hash, - groth16_vk, - ¤t_part_stark_vk, + &input.zkm_vk_hash, + &input.zkm_version, ) .unwrap(); + + // todo: read from input.zkm_public_values prev_proof.chain_state } }; diff --git a/crates/header-chain/Cargo.toml b/crates/header-chain/Cargo.toml index fed3d23d..d395bb17 100644 --- a/crates/header-chain/Cargo.toml +++ b/crates/header-chain/Cargo.toml @@ -11,7 +11,7 @@ bitcoin = { workspace = true, features = ["serde"] } serde = { workspace = true, default-features = false } crypto-bigint = { version = "0.5.5", default-features = false } zkm-zkvm = { workspace = true } -zkm-verifier = { workspace = true } +verifier = { workspace = true } [dev-dependencies] hex-literal = "1.0.0" diff --git a/crates/header-chain/src/header_chain.rs b/crates/header-chain/src/header_chain.rs index cd7f452a..9bfd2a68 100644 --- a/crates/header-chain/src/header_chain.rs +++ b/crates/header-chain/src/header_chain.rs @@ -363,7 +363,6 @@ fn calculate_work(target: &[u8; 32]) -> U256 { )] pub struct BlockHeaderCircuitOutput { pub chain_state: ChainState, - pub part_stark_vk: Vec, } /// The input proof of the header chain circuit. diff --git a/crates/header-chain/src/lib.rs b/crates/header-chain/src/lib.rs index f2ca0147..0617922f 100644 --- a/crates/header-chain/src/lib.rs +++ b/crates/header-chain/src/lib.rs @@ -12,25 +12,20 @@ pub use transaction::*; pub mod spv; pub use spv::SPV; -use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES}; /// The main entry point of the header chain circuit. pub fn header_chain_circuit(input: HeaderChainCircuitInput) -> BlockHeaderCircuitOutput { - let current_part_stark_vk = Groth16Verifier::get_part_stark_vk(&input.zkm_version).to_vec(); // println!("Detected network: {:?}", NETWORK_TYPE); // println!("NETWORK_CONSTANTS: {:?}", NETWORK_CONSTANTS); let mut chain_state = match input.prev_proof { HeaderChainPrevProofType::GenesisBlock => ChainState::new(), HeaderChainPrevProofType::PrevProof(prev_proof) => { println!("verify header chain of prev proof"); - let groth16_vk = *IMM_GROTH16_VK_BYTES; - let zkm_vk_hash = String::from_utf8(input.zkm_vk_hash.to_vec()).unwrap(); - Groth16Verifier::verify_by_imm_groth16_vk( + verifier::verify_groth16_proof( &input.zkm_proof, &input.zkm_public_values, - &zkm_vk_hash, - groth16_vk, - ¤t_part_stark_vk, + &input.zkm_vk_hash, + &input.zkm_version, ) .unwrap(); @@ -39,5 +34,5 @@ pub fn header_chain_circuit(input: HeaderChainCircuitInput) -> BlockHeaderCircui }; chain_state.apply_blocks(input.block_headers); - BlockHeaderCircuitOutput { chain_state, part_stark_vk: current_part_stark_vk } + BlockHeaderCircuitOutput { chain_state } } diff --git a/crates/state-chain/Cargo.toml b/crates/state-chain/Cargo.toml index c7a1aa50..8e98a570 100644 --- a/crates/state-chain/Cargo.toml +++ b/crates/state-chain/Cargo.toml @@ -9,11 +9,11 @@ bitcoin = { workspace = true, features = ["serde", "rand", "secp-recovery"] } bincode = "1.3.3" # workspace -guest-executor = { workspace = true } +guest-executor = { workspace = true } header-chain = { path = "../header-chain" } # Ziren -zkm-verifier = { workspace = true } +verifier = { workspace = true } zkm-zkvm = { workspace = true } #zkm-verifier = { path = "../../../Ziren/crates/verifier" } #zkm-zkvm = { path = "../../../Ziren/crates/zkvm/entrypoint", features = ["verify"] } @@ -22,7 +22,7 @@ zkm-zkvm = { workspace = true } tracing = { workspace = true, features = ["max_level_trace"] } alloy-primitives = { workspace = true, features = ["sha3-keccak", "map-foldhash", "serde"] } -revm-database-interface = { workspace = true, features = ["serde"]} +revm-database-interface = { workspace = true, features = ["serde"] } #revm = { workspace = true, features = ["serde", "bn"], default-features = false } sha2 = "0.10.9" @@ -39,7 +39,7 @@ serde_json = { workspace = true } cosmos-sdk-proto = { version = "0.27", default-features = false, features = ["serde"] } tendermint = { git = "https://github.com/ProjectZKM/tendermint-rs", branch = "patch-0.40.3", default-features = false, features = ["secp256k1"] } tendermint-light-client-verifier = { git = "https://github.com/ProjectZKM/tendermint-rs", branch = "patch-0.40.3", default-features = false, features = [ - "rust-crypto", + "rust-crypto", ] } #pbjson = "0.8" @@ -47,7 +47,7 @@ tendermint-light-client-verifier = { git = "https://github.com/ProjectZKM/tender [dev-dependencies] rand = "0.8.5" serde_json = "1.0" -borsh = {version = "1.5.3", features = ["derive"] } +borsh = { version = "1.5.3", features = ["derive"] } blake3 = "1.6.1" #zkm-sdk = { git = "https://github.com/ProjectZKM/Ziren" } diff --git a/crates/state-chain/src/lib.rs b/crates/state-chain/src/lib.rs index 49c959c5..0fe26513 100644 --- a/crates/state-chain/src/lib.rs +++ b/crates/state-chain/src/lib.rs @@ -3,10 +3,8 @@ mod state_chain; pub use cbft::*; pub use state_chain::*; -use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES}; pub fn state_chain_circuit(input: StateChainCircuitInput) -> StateChainCircuitOutput { - let current_part_stark_vk = Groth16Verifier::get_part_stark_vk(&input.zkm_version).to_vec(); let mut chain_state = match input.prev_proof { StateChainPrevProofType::GenesisBlock => { let block_hash: [u8; 32] = input.blocks[0].evm_block.current_block.hash_slow().into(); @@ -16,20 +14,18 @@ pub fn state_chain_circuit(input: StateChainCircuitInput) -> StateChainCircuitOu } StateChainPrevProofType::PrevProof(prev_proof) => { println!("verify state chain of prev proof"); - let groth16_vk = *IMM_GROTH16_VK_BYTES; - let zkm_vk_hash = String::from_utf8(input.zkm_vk_hash.to_vec()).unwrap(); - Groth16Verifier::verify_by_imm_groth16_vk( + verifier::verify_groth16_proof( &input.zkm_proof, &input.zkm_public_values, - &zkm_vk_hash, - groth16_vk, - ¤t_part_stark_vk, + &input.zkm_vk_hash, + &input.zkm_version, ) .unwrap(); + prev_proof.chain_state } }; chain_state.apply_blocks(input.blocks); - StateChainCircuitOutput { chain_state, part_stark_vk: current_part_stark_vk } + StateChainCircuitOutput { chain_state } } diff --git a/crates/state-chain/src/state_chain.rs b/crates/state-chain/src/state_chain.rs index ce735638..473db11e 100644 --- a/crates/state-chain/src/state_chain.rs +++ b/crates/state-chain/src/state_chain.rs @@ -42,7 +42,6 @@ pub struct StateChainState { #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] pub struct StateChainCircuitOutput { pub chain_state: StateChainState, - pub part_stark_vk: Vec, } #[derive(Serialize, Deserialize, PartialEq, Clone, Debug)] diff --git a/crates/store/.sqlx/query-16a993571cf490b1fa3ab7ebabd969c1a32945575a16fae6a1e0270222c73286.json b/crates/store/.sqlx/query-16a993571cf490b1fa3ab7ebabd969c1a32945575a16fae6a1e0270222c73286.json new file mode 100644 index 00000000..b5099e09 --- /dev/null +++ b/crates/store/.sqlx/query-16a993571cf490b1fa3ab7ebabd969c1a32945575a16fae6a1e0270222c73286.json @@ -0,0 +1,12 @@ +{ + "db_name": "SQLite", + "query": "INSERT OR\n REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, amount, challenge_amount,\n status, sub_status, operator_pubkey, cur_prekickoff_txid, next_prekickoff, force_skip_kickoff_txid,\n quick_challenge_txid, challenge_incomplete_kickoff_txid, pegin_txid, kickoff_txid, take1_txid,\n challenge_txid, take2_txid, watchtower_challenge_init_txid, operator_assert_txid, verifier_assert_txids, disprove_txids,\n init_withdraw_tx_hash,\n bridge_out_start_at, status_updated_at, proceed_withdraw_height, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + "describe": { + "columns": [], + "parameters": { + "Right": 30 + }, + "nullable": [] + }, + "hash": "16a993571cf490b1fa3ab7ebabd969c1a32945575a16fae6a1e0270222c73286" +} diff --git a/crates/store/.sqlx/query-a574aa126ca49083135ef445cb213379bf8df51054958887f27081ff7f8f47cc.json b/crates/store/.sqlx/query-a574aa126ca49083135ef445cb213379bf8df51054958887f27081ff7f8f47cc.json deleted file mode 100644 index a5cebc2d..00000000 --- a/crates/store/.sqlx/query-a574aa126ca49083135ef445cb213379bf8df51054958887f27081ff7f8f47cc.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "db_name": "SQLite", - "query": "INSERT OR\n REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, amount, challenge_amount,\n status, sub_status, operator_pubkey, cur_prekickoff_txid, next_prekickoff, force_skip_kickoff_txid,\n quick_challenge_txid, challenge_incomplete_kickoff_txid, pegin_txid, kickoff_txid, take1_txid,\n challenge_txid, take2_txid, disprove_txid, watchtower_challenge_init_txid, watchtower_challenge_timeout_txids, nack_txids,\n blockhash_commit_timeout_txid, assert_init_txid, assert_commit_timeout_txids, init_withdraw_tx_hash,\n bridge_out_start_at, status_updated_at, proceed_withdraw_height, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", - "describe": { - "columns": [], - "parameters": { - "Right": 33 - }, - "nullable": [] - }, - "hash": "a574aa126ca49083135ef445cb213379bf8df51054958887f27081ff7f8f47cc" -} diff --git a/crates/store/migrations/20250814113519_create_graph_table.sql b/crates/store/migrations/20250814113519_create_graph_table.sql index 94000307..7289917f 100644 --- a/crates/store/migrations/20250814113519_create_graph_table.sql +++ b/crates/store/migrations/20250814113519_create_graph_table.sql @@ -23,13 +23,10 @@ CREATE TABLE graph `take1_txid` TEXT, `challenge_txid` TEXT, `take2_txid` TEXT, - `disprove_txid` TEXT, `watchtower_challenge_init_txid` TEXT, - `watchtower_challenge_timeout_txids` TEXT NOT NULL DEFAULT '[]', - `nack_txids` TEXT NOT NULL DEFAULT '[]', - `blockhash_commit_timeout_txid` TEXT, - `assert_init_txid` TEXT, - `assert_commit_timeout_txids` TEXT NOT NULL DEFAULT '[]', + `operator_assert_txid` TEXT, + `verifier_assert_txids` TEXT NOT NULL DEFAULT '[]', + `disprove_txids` TEXT NOT NULL DEFAULT '[]', `init_withdraw_tx_hash` TEXT, `bridge_out_start_at` BIGINT NOT NULL DEFAULT 0, `zkm_version` TEXT NOT NULL DEFAULT '', diff --git a/crates/store/migrations/20260504120000_create_wrapper_proof_table.sql b/crates/store/migrations/20260504120000_create_wrapper_proof_table.sql new file mode 100644 index 00000000..6cd2adce --- /dev/null +++ b/crates/store/migrations/20260504120000_create_wrapper_proof_table.sql @@ -0,0 +1,30 @@ +CREATE TABLE IF NOT EXISTS wrapper_proof +( + `id` INTEGER PRIMARY KEY, + `operator_proof_id` BIGINT NOT NULL, + `instance_id` TEXT NOT NULL, + `graph_id` TEXT NOT NULL, + `execution_layer_block_number` BIGINT NOT NULL DEFAULT 0, + `operator_path_to_proof` TEXT NOT NULL DEFAULT '', + `path_to_proof` TEXT, + `public_value_hex` TEXT, + `operator_vk_hash` TEXT NOT NULL DEFAULT '', + `genesis_sequencer_commit_txid` TEXT NOT NULL DEFAULT '', + `operator_public_value_hex` TEXT, + `proof_size` BIGINT NOT NULL DEFAULT 0, + `cycles` BIGINT NOT NULL DEFAULT 0, + `proof_state` BIGINT NOT NULL DEFAULT 0 CHECK (proof_state IN (0, 1, 2, 3)), + `total_time_to_proof` BIGINT NOT NULL DEFAULT 0, + `proving_time` BIGINT NOT NULL DEFAULT 0, + `zkm_version` TEXT NOT NULL DEFAULT '', + `extra` TEXT, + `created_at` BIGINT NOT NULL DEFAULT 0, + `updated_at` BIGINT NOT NULL DEFAULT 0, + UNIQUE (`operator_proof_id`) +); + +CREATE INDEX IF NOT EXISTS idx_wrapper_proof_instance_graph + ON wrapper_proof (`instance_id`, `graph_id`); + +CREATE INDEX IF NOT EXISTS idx_wrapper_proof_state + ON wrapper_proof (`proof_state`); diff --git a/crates/store/migrations/20260524000000_create_pending_graph_init_table.sql b/crates/store/migrations/20260524000000_create_pending_graph_init_table.sql new file mode 100644 index 00000000..f03cb8c0 --- /dev/null +++ b/crates/store/migrations/20260524000000_create_pending_graph_init_table.sql @@ -0,0 +1,9 @@ +CREATE TABLE IF NOT EXISTS pending_graph_init +( + `instance_id` TEXT NOT NULL, + `operator_pubkey` TEXT NOT NULL, + `graph_id` TEXT NOT NULL UNIQUE, + `created_at` BIGINT NOT NULL DEFAULT 0, + `updated_at` BIGINT NOT NULL DEFAULT 0, + PRIMARY KEY (`instance_id`, `operator_pubkey`) +); diff --git a/crates/store/src/localdb.rs b/crates/store/src/localdb.rs index d80ad4b0..1cca18e1 100644 --- a/crates/store/src/localdb.rs +++ b/crates/store/src/localdb.rs @@ -2,8 +2,8 @@ use crate::utils::{QueryBuilder, QueryParam, create_place_holders}; use crate::{ BridgeOutGlobalStats, GoatTxRecord, Graph, GraphBtcTxVoutMonitor, GraphRawData, Instance, LongRunningTaskProof, Message, Node, NodesOverview, OperatorProof, PeginGraphProcessData, - PeginInstanceProcessData, SequencerSetHashChange, SequencerSetScanState, SerializableTxid, - WatchContract, WatchtowerProof, + PeginInstanceProcessData, PendingGraphInit, ProofState, SequencerSetHashChange, + SequencerSetScanState, SerializableTxid, WatchContract, WatchtowerProof, WrapperProof, }; use indexmap::IndexMap; @@ -503,7 +503,7 @@ pub struct GraphUpdate { pub status: Option, pub sub_status: Option, pub challenge_txid: Option, - pub disprove_txid: Option, + pub disprove_txids: Option>, pub bridge_out_start_at: Option, pub init_withdraw_tx_hash: Option, pub proceed_withdraw_height: Option, @@ -517,7 +517,7 @@ impl GraphUpdate { status: None, sub_status: None, challenge_txid: None, - disprove_txid: None, + disprove_txids: None, bridge_out_start_at: None, init_withdraw_tx_hash: None, proceed_withdraw_height: None, @@ -541,9 +541,9 @@ impl GraphUpdate { self } - /// Set disprove transaction ID - pub fn with_disprove_txid(mut self, disprove_txid: SerializableTxid) -> Self { - self.disprove_txid = Some(disprove_txid); + /// Set disprove transaction IDs + pub fn with_disprove_txids(mut self, disprove_txids: Vec) -> Self { + self.disprove_txids = Some(disprove_txids); self } @@ -570,7 +570,7 @@ impl GraphUpdate { self.status.is_some() || self.sub_status.is_some() || self.challenge_txid.is_some() - || self.disprove_txid.is_some() + || self.disprove_txids.is_some() || self.bridge_out_start_at.is_some() || self.init_withdraw_tx_hash.is_some() || self.proceed_withdraw_height.is_some() @@ -590,8 +590,10 @@ impl GraphUpdate { if let Some(ref challenge_txid) = self.challenge_txid { query_builder.set_field("challenge_txid", QueryParam::BTCTxid(challenge_txid.clone())); } - if let Some(ref disprove_txid) = self.disprove_txid { - query_builder.set_field("disprove_txid", QueryParam::BTCTxid(disprove_txid.clone())); + if let Some(ref disprove_txids) = self.disprove_txids { + let disprove_txids_json = + serde_json::to_string(disprove_txids).unwrap_or_else(|_| "[]".into()); + query_builder.set_field("disprove_txids", QueryParam::Text(disprove_txids_json)); } if let Some(bridge_out_start_at) = self.bridge_out_start_at { query_builder.set_field("bridge_out_start_at", QueryParam::Int(bridge_out_start_at)); @@ -1082,6 +1084,76 @@ impl<'a> StorageProcessor<'a> { Ok(res.and_then(|record| record.parameters)) } + pub async fn upsert_pending_graph_init( + &mut self, + instance_id: &Uuid, + operator_pubkey: &str, + graph_id: &Uuid, + ) -> anyhow::Result { + let current_time = get_current_timestamp_secs(); + let result = sqlx::query( + "INSERT INTO pending_graph_init + (instance_id, operator_pubkey, graph_id, created_at, updated_at) + VALUES (?, ?, ?, ?, ?) + ON CONFLICT(instance_id, operator_pubkey) DO UPDATE SET + graph_id = excluded.graph_id, + updated_at = excluded.updated_at", + ) + .bind(instance_id) + .bind(operator_pubkey) + .bind(graph_id) + .bind(current_time) + .bind(current_time) + .execute(self.conn()) + .await?; + Ok(result.rows_affected()) + } + + pub async fn find_pending_graph_init_by_instance_and_operator_pubkey( + &mut self, + instance_id: &Uuid, + operator_pubkey: &str, + ) -> anyhow::Result> { + Ok(sqlx::query_as::<_, PendingGraphInit>( + "SELECT instance_id, operator_pubkey, graph_id, created_at, updated_at + FROM pending_graph_init + WHERE instance_id = ? AND operator_pubkey = ?", + ) + .bind(instance_id) + .bind(operator_pubkey) + .fetch_optional(self.conn()) + .await?) + } + + pub async fn find_pending_graph_init_by_graph_id( + &mut self, + graph_id: &Uuid, + ) -> anyhow::Result> { + Ok(sqlx::query_as::<_, PendingGraphInit>( + "SELECT instance_id, operator_pubkey, graph_id, created_at, updated_at + FROM pending_graph_init + WHERE graph_id = ?", + ) + .bind(graph_id) + .fetch_optional(self.conn()) + .await?) + } + + pub async fn delete_pending_graph_init( + &mut self, + instance_id: &Uuid, + operator_pubkey: &str, + ) -> anyhow::Result { + let result = sqlx::query( + "DELETE FROM pending_graph_init WHERE instance_id = ? AND operator_pubkey = ?", + ) + .bind(instance_id) + .bind(operator_pubkey) + .execute(self.conn()) + .await?; + Ok(result.rows_affected()) + } + /// Insert or update a graph /// /// Performs an INSERT OR REPLACE operation on the graph table. @@ -1095,20 +1167,17 @@ impl<'a> StorageProcessor<'a> { /// - Ok(affected_rows) number of rows affected by the operation /// - Err if the operation failed pub async fn upsert_graph(&mut self, graph: &Graph) -> anyhow::Result { - let nack_txids_json = serde_json::to_string(&graph.nack_txids)?; - let watchtower_challenge_timeout_txids_json = - serde_json::to_string(&graph.watchtower_challenge_timeout_txids)?; - let assert_commit_timeout_txids_json = - serde_json::to_string(&graph.assert_commit_timeout_txids)?; + let verifier_assert_txids_json = serde_json::to_string(&graph.verifier_assert_txids)?; + let disprove_txids_json = serde_json::to_string(&graph.disprove_txids)?; let res = sqlx::query!( "INSERT OR REPLACE INTO graph (graph_id, instance_id, kickoff_index, from_addr, to_addr, amount, challenge_amount, status, sub_status, operator_pubkey, cur_prekickoff_txid, next_prekickoff, force_skip_kickoff_txid, quick_challenge_txid, challenge_incomplete_kickoff_txid, pegin_txid, kickoff_txid, take1_txid, - challenge_txid, take2_txid, disprove_txid, watchtower_challenge_init_txid, watchtower_challenge_timeout_txids, nack_txids, - blockhash_commit_timeout_txid, assert_init_txid, assert_commit_timeout_txids, init_withdraw_tx_hash, + challenge_txid, take2_txid, watchtower_challenge_init_txid, operator_assert_txid, verifier_assert_txids, disprove_txids, + init_withdraw_tx_hash, bridge_out_start_at, status_updated_at, proceed_withdraw_height, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", graph.graph_id, graph.instance_id, graph.kickoff_index, @@ -1129,13 +1198,10 @@ impl<'a> StorageProcessor<'a> { graph.take1_txid, graph.challenge_txid, graph.take2_txid, - graph.disprove_txid, graph.watchtower_challenge_init_txid, - watchtower_challenge_timeout_txids_json, - nack_txids_json, - graph.blockhash_commit_timeout_txid, - graph.assert_init_txid, - assert_commit_timeout_txids_json, + graph.operator_assert_txid, + verifier_assert_txids_json, + disprove_txids_json, graph.init_withdraw_tx_hash, graph.bridge_out_start_at, graph.status_updated_at, @@ -1212,13 +1278,10 @@ impl<'a> StorageProcessor<'a> { take1_txid, challenge_txid, take2_txid, - disprove_txid, watchtower_challenge_init_txid, - watchtower_challenge_timeout_txids, - nack_txids, - blockhash_commit_timeout_txid, - assert_init_txid, - assert_commit_timeout_txids, + operator_assert_txid, + verifier_assert_txids, + disprove_txids, init_withdraw_tx_hash, bridge_out_start_at, status_updated_at, @@ -1555,9 +1618,9 @@ impl<'a> StorageProcessor<'a> { for record in records { res.total += record.total; match record.actor.as_str() { - "Challenger" => { - (res.offline_challengers, res.online_challengers) = - (record.offline, record.online); + "Verifier" => { + res.offline_verifiers += record.offline; + res.online_verifiers += record.online; } "Operator" => { (res.offline_operators, res.online_operators) = (record.offline, record.online); @@ -2678,6 +2741,223 @@ impl<'a> StorageProcessor<'a> { Ok(res) } + pub async fn create_wrapper_proof( + &mut self, + wrapper_proof: &WrapperProof, + ) -> anyhow::Result { + let res = sqlx::query( + "INSERT INTO wrapper_proof ( + operator_proof_id, + instance_id, + graph_id, + execution_layer_block_number, + operator_path_to_proof, + path_to_proof, + public_value_hex, + operator_vk_hash, + genesis_sequencer_commit_txid, + operator_public_value_hex, + proof_size, + cycles, + proof_state, + total_time_to_proof, + proving_time, + zkm_version, + extra, + created_at, + updated_at + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + ) + .bind(wrapper_proof.operator_proof_id) + .bind(wrapper_proof.instance_id) + .bind(wrapper_proof.graph_id) + .bind(wrapper_proof.execution_layer_block_number) + .bind(&wrapper_proof.operator_path_to_proof) + .bind(&wrapper_proof.path_to_proof) + .bind(&wrapper_proof.public_value_hex) + .bind(&wrapper_proof.operator_vk_hash) + .bind(&wrapper_proof.genesis_sequencer_commit_txid) + .bind(&wrapper_proof.operator_public_value_hex) + .bind(wrapper_proof.proof_size) + .bind(wrapper_proof.cycles) + .bind(wrapper_proof.proof_state) + .bind(wrapper_proof.total_time_to_proof) + .bind(wrapper_proof.proving_time) + .bind(&wrapper_proof.zkm_version) + .bind(&wrapper_proof.extra) + .bind(wrapper_proof.created_at) + .bind(wrapper_proof.updated_at) + .execute(self.conn()) + .await?; + Ok(res.rows_affected()) + } + + pub async fn find_wrapper_proof_by_operator_proof_id( + &mut self, + operator_proof_id: i64, + ) -> anyhow::Result> { + let res = sqlx::query_as::<_, WrapperProof>( + "SELECT * FROM wrapper_proof + WHERE operator_proof_id = ? + LIMIT 1", + ) + .bind(operator_proof_id) + .fetch_optional(self.conn()) + .await?; + Ok(res) + } + + pub async fn find_wrapper_proof_by_instance_and_graph( + &mut self, + instance_id: &Uuid, + graph_id: &Uuid, + ) -> anyhow::Result> { + let res = sqlx::query_as::<_, WrapperProof>( + "SELECT * FROM wrapper_proof + WHERE instance_id = ? + AND graph_id = ? + ORDER BY id DESC + LIMIT 1", + ) + .bind(instance_id) + .bind(graph_id) + .fetch_optional(self.conn()) + .await?; + Ok(res) + } + + pub async fn find_next_wrapper_proof(&mut self) -> anyhow::Result> { + let res = sqlx::query_as::<_, WrapperProof>( + "SELECT * FROM wrapper_proof + WHERE proof_state = ? + ORDER BY id ASC + LIMIT 1", + ) + .bind(ProofState::New.to_i64()) + .fetch_optional(self.conn()) + .await?; + Ok(res) + } + + pub async fn claim_next_wrapper_proof(&mut self) -> anyhow::Result> { + let Some(candidate) = self.find_next_wrapper_proof().await? else { + return Ok(None); + }; + + let current_time = get_current_timestamp_secs(); + let updated = sqlx::query( + "UPDATE wrapper_proof + SET proof_state = ?, + updated_at = ? + WHERE id = ? + AND proof_state = ?", + ) + .bind(ProofState::Proving.to_i64()) + .bind(current_time) + .bind(candidate.id) + .bind(ProofState::New.to_i64()) + .execute(self.conn()) + .await? + .rows_affected(); + + if updated == 0 { + return Ok(None); + } + + let res = sqlx::query_as::<_, WrapperProof>( + "SELECT * FROM wrapper_proof + WHERE id = ? + LIMIT 1", + ) + .bind(candidate.id) + .fetch_optional(self.conn()) + .await?; + Ok(res) + } + + pub async fn find_proven_operator_proofs_without_wrapper( + &mut self, + limit: i64, + ) -> anyhow::Result> { + let res = sqlx::query_as::<_, OperatorProof>( + "SELECT operator_proof.* + FROM operator_proof + WHERE operator_proof.proof_state = ? + AND operator_proof.path_to_proof IS NOT NULL + AND NOT EXISTS ( + SELECT 1 + FROM wrapper_proof + WHERE wrapper_proof.operator_proof_id = operator_proof.id + ) + ORDER BY operator_proof.id ASC + LIMIT ?", + ) + .bind(ProofState::Proven.to_i64()) + .bind(limit) + .fetch_all(self.conn()) + .await?; + Ok(res) + } + + #[allow(clippy::too_many_arguments)] + pub async fn update_wrapper_proof_success( + &mut self, + id: i64, + path_to_proof: String, + public_value_hex: String, + proof_size: i64, + cycles: i64, + proving_time: i64, + zkm_version: &str, + ) -> anyhow::Result { + let current_time = get_current_timestamp_secs(); + let res = sqlx::query( + "UPDATE wrapper_proof + SET path_to_proof = ?, + public_value_hex = ?, + proof_size = ?, + cycles = ?, + proof_state = ?, + total_time_to_proof = CASE + WHEN created_at > 0 THEN (? - created_at) * 1000 + ELSE total_time_to_proof + END, + proving_time = ?, + zkm_version = ?, + updated_at = ? + WHERE id = ?", + ) + .bind(path_to_proof) + .bind(public_value_hex) + .bind(proof_size) + .bind(cycles) + .bind(ProofState::Proven.to_i64()) + .bind(current_time) + .bind(proving_time) + .bind(zkm_version) + .bind(current_time) + .bind(id) + .execute(self.conn()) + .await?; + Ok(res.rows_affected()) + } + + pub async fn update_wrapper_proof_failure(&mut self, id: i64) -> anyhow::Result { + let current_time = get_current_timestamp_secs(); + let res = sqlx::query( + "UPDATE wrapper_proof + SET proof_state = ?, + updated_at = ? + WHERE id = ?", + ) + .bind(ProofState::Failed.to_i64()) + .bind(current_time) + .bind(id) + .execute(self.conn()) + .await?; + Ok(res.rows_affected()) + } + #[allow(clippy::too_many_arguments)] pub async fn update_watchtower_proof( &mut self, @@ -2785,7 +3065,8 @@ impl<'a> StorageProcessor<'a> { "SELECT * FROM watchtower_proof WHERE instance_id = ? - AND graph_id = ?", + AND graph_id = ? + ORDER BY node_index ASC", ) .bind(instance_id) .bind(graph_id) @@ -2944,12 +3225,12 @@ impl<'a> StorageProcessor<'a> { Ok(res) } - pub async fn find_first_sequencer_set_hash_change_by_goat_block_at_or_after( + pub async fn find_first_sequencer_set_hash_change_by_goat_block_at_or_before( &mut self, goat_block_height: i64, ) -> anyhow::Result> { let res = sqlx::query_as::<_, SequencerSetHashChange>( - "SELECT * FROM sequencer_set_hash_changes WHERE goat_block_height >= ? ORDER BY goat_block_height ASC LIMIT 1", + "SELECT * FROM sequencer_set_hash_changes WHERE goat_block_height <= ? ORDER BY goat_block_height DESC LIMIT 1", ) .bind(goat_block_height) .fetch_optional(self.conn()) @@ -2957,19 +3238,6 @@ impl<'a> StorageProcessor<'a> { Ok(res) } - pub async fn find_first_sequencer_set_hash_change_by_cosmos_block_at_or_after( - &mut self, - cosmos_block_height: i64, - ) -> anyhow::Result> { - let res = sqlx::query_as::<_, SequencerSetHashChange>( - "SELECT * FROM sequencer_set_hash_changes WHERE cosmos_block_height >= ? ORDER BY cosmos_block_height ASC LIMIT 1", - ) - .bind(cosmos_block_height) - .fetch_optional(self.conn()) - .await?; - Ok(res) - } - pub async fn get_sequencer_set_scan_state( &mut self, ) -> anyhow::Result> { @@ -3057,7 +3325,7 @@ mod sequencer_set_tests { } #[tokio::test] - async fn test_find_by_goat_block_at_or_after() { + async fn test_find_by_goat_block_at_or_before() { let db = setup_db().await; let mut s = db.acquire().await.unwrap(); @@ -3067,43 +3335,25 @@ mod sequencer_set_tests { // Exact match let r = s - .find_first_sequencer_set_hash_change_by_goat_block_at_or_after(2000) + .find_first_sequencer_set_hash_change_by_goat_block_at_or_before(2000) .await .unwrap() .unwrap(); assert_eq!(r.goat_block_height, 2000); + assert_eq!(r.validators_hash, "bb"); - // Between records — should return next one + // Between records — should return previous one let r = s - .find_first_sequencer_set_hash_change_by_goat_block_at_or_after(1500) + .find_first_sequencer_set_hash_change_by_goat_block_at_or_before(2500) .await .unwrap() .unwrap(); assert_eq!(r.goat_block_height, 2000); + assert_eq!(r.validators_hash, "bb"); - // Beyond all records — should return None + // Before all records — should return None let r = - s.find_first_sequencer_set_hash_change_by_goat_block_at_or_after(4000).await.unwrap(); - assert!(r.is_none()); - } - - #[tokio::test] - async fn test_find_by_cosmos_block_at_or_after() { - let db = setup_db().await; - let mut s = db.acquire().await.unwrap(); - - s.upsert_sequencer_set_hash_change(100, 1000, "aa").await.unwrap(); - s.upsert_sequencer_set_hash_change(200, 2000, "bb").await.unwrap(); - - let r = s - .find_first_sequencer_set_hash_change_by_cosmos_block_at_or_after(150) - .await - .unwrap() - .unwrap(); - assert_eq!(r.cosmos_block_height, 200); - - let r = - s.find_first_sequencer_set_hash_change_by_cosmos_block_at_or_after(300).await.unwrap(); + s.find_first_sequencer_set_hash_change_by_goat_block_at_or_before(500).await.unwrap(); assert!(r.is_none()); } @@ -3128,23 +3378,77 @@ mod sequencer_set_tests { } #[tokio::test] - async fn test_find_returns_none_when_empty() { + async fn test_wrapper_proof_lifecycle() { let db = setup_db().await; let mut s = db.acquire().await.unwrap(); + let instance_id = Uuid::parse_str("00112233445566778899aabbccddeeff").unwrap(); + let graph_id = Uuid::parse_str("ffeeddccbbaa99887766554433221100").unwrap(); + let now = get_current_timestamp_secs(); - assert!(s.find_latest_sequencer_set_hash_change().await.unwrap().is_none()); - assert!( - s.find_first_sequencer_set_hash_change_by_goat_block_at_or_after(0) - .await - .unwrap() - .is_none() - ); - assert!( - s.find_first_sequencer_set_hash_change_by_cosmos_block_at_or_after(0) - .await - .unwrap() - .is_none() - ); - assert!(s.get_sequencer_set_scan_state().await.unwrap().is_none()); + let task = WrapperProof { + operator_proof_id: 42, + instance_id, + graph_id, + execution_layer_block_number: 9511055, + operator_path_to_proof: "operator.bin".to_string(), + operator_vk_hash: "operator-vk-hash".to_string(), + genesis_sequencer_commit_txid: + "7f7b4344adb1b8937ddb7124e4f8bba80ee9adf5e8119de76ca8736816bda246".to_string(), + operator_public_value_hex: Some("abcd".to_string()), + proof_state: ProofState::New.to_i64(), + created_at: now, + updated_at: now, + ..Default::default() + }; + + s.create_wrapper_proof(&task).await.unwrap(); + let duplicate = s.create_wrapper_proof(&task).await; + assert!(duplicate.is_err(), "operator_proof_id must be unique"); + + let found = s + .find_wrapper_proof_by_operator_proof_id(42) + .await + .unwrap() + .expect("wrapper proof should be found"); + assert_eq!(found.instance_id, instance_id); + assert_eq!(found.graph_id, graph_id); + assert_eq!(found.proof_state, ProofState::New.to_i64()); + + let claimed = s + .claim_next_wrapper_proof() + .await + .unwrap() + .expect("new wrapper proof should be claimable"); + assert_eq!(claimed.id, found.id); + assert_eq!(claimed.proof_state, ProofState::Proving.to_i64()); + + s.update_wrapper_proof_success( + claimed.id, + "wrapper.bin".to_string(), + "wrapper-public-value-hex".to_string(), + 1234, + 5678, + 90, + "v1.2.5", + ) + .await + .unwrap(); + let proven = s + .find_wrapper_proof_by_instance_and_graph(&instance_id, &graph_id) + .await + .unwrap() + .expect("wrapper proof should be found by instance and graph"); + assert_eq!(proven.proof_state, ProofState::Proven.to_i64()); + assert_eq!(proven.path_to_proof.as_deref(), Some("wrapper.bin")); + assert_eq!(proven.public_value_hex.as_deref(), Some("wrapper-public-value-hex")); + + s.update_wrapper_proof_failure(proven.id).await.unwrap(); + let failed = s + .find_wrapper_proof_by_operator_proof_id(42) + .await + .unwrap() + .expect("wrapper proof should still exist"); + assert_eq!(failed.proof_state, ProofState::Failed.to_i64()); + assert!(s.claim_next_wrapper_proof().await.unwrap().is_none()); } } diff --git a/crates/store/src/schema.rs b/crates/store/src/schema.rs index c40da0cd..f532fd47 100644 --- a/crates/store/src/schema.rs +++ b/crates/store/src/schema.rs @@ -176,8 +176,8 @@ pub struct NodesOverview { pub total: i64, pub online_operators: i64, pub offline_operators: i64, - pub online_challengers: i64, - pub offline_challengers: i64, + pub online_verifiers: i64, + pub offline_verifiers: i64, pub online_committees: i64, pub offline_committees: i64, pub online_watchtowers: i64, @@ -396,16 +396,12 @@ pub struct Graph { pub take1_txid: Option, pub challenge_txid: Option, pub take2_txid: Option, - pub disprove_txid: Option, pub watchtower_challenge_init_txid: Option, + pub operator_assert_txid: Option, #[sqlx(json)] - pub watchtower_challenge_timeout_txids: Vec, + pub verifier_assert_txids: Vec, #[sqlx(json)] - pub nack_txids: Vec, - pub blockhash_commit_timeout_txid: Option, - pub assert_init_txid: Option, - #[sqlx(json)] - pub assert_commit_timeout_txids: Vec, + pub disprove_txids: Vec, pub init_withdraw_tx_hash: Option, pub bridge_out_start_at: i64, pub status_updated_at: i64, @@ -467,12 +463,25 @@ pub struct PeginGraphProcessData { pub created_at: i64, } +#[derive(Clone, FromRow, Debug, Serialize, Deserialize, Default)] +pub struct PendingGraphInit { + pub instance_id: Uuid, + pub operator_pubkey: String, + pub graph_id: Uuid, + pub updated_at: i64, + pub created_at: i64, +} + #[derive(Debug, Clone, PartialEq, Display, EnumString)] pub enum MessageType { None, PeginRequest, CreateGraph, ConfirmInstance, + InitGraph, + GenCircuits, + CutCircuits, + SolderingProof, NonceGeneration, CommitteePresign, GraphFinalize, @@ -486,14 +495,10 @@ pub enum MessageType { ChallengeSent, WatchtowerChallengeInitSent, WatchtowerChallengeSent, - WatchtowerChallengeTimeout, - OperatorAckTimeout, - OperatorCommitBlockHashReady, - OperatorCommitBlockHashSent, - OperatorCommitBlockHashTimeout, - AssertInitReady, - AssertCommitTimeout, - DisproveReady, + AssertReady, + AssertSent, + ChallengeAssertSent, + WronglyChallengeTimeout, DisproveSent, Take1Ready, Take1Sent, @@ -710,6 +715,30 @@ pub struct OperatorProof { pub updated_at: i64, } +#[derive(Clone, FromRow, Debug, Serialize, Deserialize, Default)] +pub struct WrapperProof { + pub id: i64, + pub operator_proof_id: i64, + pub instance_id: Uuid, + pub graph_id: Uuid, + pub execution_layer_block_number: i64, + pub operator_path_to_proof: String, + pub path_to_proof: Option, + pub public_value_hex: Option, + pub operator_vk_hash: String, + pub genesis_sequencer_commit_txid: String, + pub operator_public_value_hex: Option, + pub proof_size: i64, + pub cycles: i64, + pub proof_state: i64, + pub total_time_to_proof: i64, + pub proving_time: i64, + pub zkm_version: String, + pub extra: Option, + pub created_at: i64, + pub updated_at: i64, +} + #[derive(Clone, FromRow, Debug, Serialize, Deserialize, Default)] pub struct LongRunningTaskProof { pub block_start: i64, diff --git a/crates/verifier/Cargo.toml b/crates/verifier/Cargo.toml new file mode 100644 index 00000000..9201485e --- /dev/null +++ b/crates/verifier/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "verifier" +version.workspace = true +edition.workspace = true + +[dependencies] +zkm-verifier = { workspace = true } + +[lints] +workspace = true \ No newline at end of file diff --git a/crates/verifier/src/lib.rs b/crates/verifier/src/lib.rs new file mode 100644 index 00000000..e08fde4c --- /dev/null +++ b/crates/verifier/src/lib.rs @@ -0,0 +1,23 @@ +use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES}; + +pub fn verify_groth16_proof( + proof: &[u8], + zkm_public_values: &[u8], + zkm_vk_hash: &[u8], + zkm_version: &str, +) -> Result<(), String> { + let groth16_vk = *IMM_GROTH16_VK_BYTES; + let zkm_vk_hash = String::from_utf8(zkm_vk_hash.to_vec()).map_err(|e| e.to_string())?; + let part_stark_vk = Groth16Verifier::get_part_stark_vk(zkm_version); + + match Groth16Verifier::verify_by_imm_groth16_vk( + proof, + zkm_public_values, + &zkm_vk_hash, + groth16_vk, + part_stark_vk, + ) { + Ok(_) => Ok(()), + Err(err) => Err(format!("Verify Groth16 proof, err: {err:?}")), + } +} diff --git a/deployment/regtest/proof-builder-rpc/proof-builder.toml b/deployment/regtest/proof-builder-rpc/proof-builder.toml index 663663fc..6c050856 100644 --- a/deployment/regtest/proof-builder-rpc/proof-builder.toml +++ b/deployment/regtest/proof-builder-rpc/proof-builder.toml @@ -72,4 +72,10 @@ included_watchtowers = "1" graph_id="0x00112233445566778899aabbccddeeff" execution_layer_block_number = 9511055 index = 1 -bitcoin_network = "regtest" \ No newline at end of file +bitcoin_network = "regtest" + +[wrapper] +enable = false +output = "../circuits/data/wrapper/output.bin" +genesis_sequencer_commit_txid = "5d5a29e1c426abc4b947f90b93c6b043683aafad79cb3b9a9829dfd5e51cad09" +scan_interval = 30 diff --git a/docs/part-stark-vk-attest-cli.md b/docs/part-stark-vk-attest-cli.md deleted file mode 100644 index 73cb0bf9..00000000 --- a/docs/part-stark-vk-attest-cli.md +++ /dev/null @@ -1,281 +0,0 @@ -# `part-stark-vk-attest` CLI Guide - -This document explains how to use `part-stark-vk-attest` to build the latest `part_stark_vk` attestation snapshot and add publisher signatures to that snapshot. - -## Overview - -The CLI currently provides two subcommands: - -- `build-tree` -- `sign-root` - -The workflow is fixed: - -1. Run `build-tree` first to generate the latest snapshot from the full active version set -2. Let each publisher signer run `sign-root` once -3. After the number of signatures in `manifest.json` reaches the threshold from `commit_info`, the proof side can load the attestation snapshot - -## Binary Entry - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- ... -``` - -The binary is defined in: - -- [`node/Cargo.toml`](../node/Cargo.toml) -- [`node/src/bin/part_stark_vk_attest.rs`](../node/src/bin/part_stark_vk_attest.rs) - -## Directory Model - -The attestation directory stores only one latest snapshot. Historical snapshots are not kept. - -Default output directory: - -```text -data/psv-attestations -``` - -Latest snapshot layout: - -```text -data/psv-attestations/ - manifest.json - proofs/ - v1.2.4.json - v1.2.5.json -``` - -Where: - -- `manifest.json` stores the current Merkle root, ordered versions, publisher metadata, and aggregated signatures -- `proofs/.json` stores the `part_stark_vk`, `leaf_index`, and `merkle_path` for each version - -## Version Rules - -All `--versions` inputs represent the full active version set, not incremental additions. - -The CLI will: - -1. Sort versions by semver -2. Load `part_stark_vk` for each sorted version -3. Build the Merkle tree using that sorted order - -Example: - -```text -Input: v1.2.10,v1.2.4,v1.2.5 -Sorted: v1.2.4,v1.2.5,v1.2.10 -``` - -If versions are duplicated, the command fails. - -## `build-tree` - -### Purpose - -- Build the latest Merkle tree from the full version set -- Generate Merkle proofs for every version -- Overwrite the attestation directory -- Do not write signatures - -### Command Format - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - build-tree \ - --versions \ - --attestation-dir -``` - -### Parameters - -- `--versions` - Comma-separated version list -- `--attestation-dir` - Output attestation directory; defaults to `data/psv-attestations` - -### Example - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - build-tree \ - --versions v1.2.4,v1.2.5 \ - --attestation-dir circuits/data/psv-attestations -``` - -### Output - -On success, the command prints output similar to: - -```text -built latest part_stark_vk snapshot in circuits/data/psv-attestations for versions v1.2.4,v1.2.5 -``` - -Note: - -- `build-tree` creates an unsigned snapshot -- Running only `build-tree` is not enough for the proof side to consume the attestation - -## `sign-root` - -### Purpose - -- Rebuild the Merkle tree from the same version set -- Load the current publisher set and threshold from `commit_info.active_publisher_set()` -- Sign the current root with a single publisher private key -- Merge the signature into the latest `manifest.json` - -### Command Format - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - sign-root \ - --versions \ - --commit-info-file \ - --publisher-secret-key-wif \ - --attestation-dir -``` - -### Parameters - -- `--versions` - Full active version set; must match the snapshot being signed -- `--commit-info-file` - Path to `commit_info.json` -- `--publisher-secret-key-wif` - Bitcoin WIF private key for a single publisher signer -- `--attestation-dir` - Attestation directory; defaults to `data/psv-attestations` - -### Signer Matching Rule - -The command does not require a manual signer index. - -`--publisher-secret-key-wif` is parsed as WIF, following the same Bitcoin private key format used in `sequencer-set-publish.rs`. - -The CLI will: - -1. Derive the compressed public key from the private key -2. Read the current active publisher set from `commit_info.active_publisher_set()` -3. Find that public key inside the active publisher set -4. Use the matching position as `signer_pubkey_index` - -If the private key does not belong to the current active publisher set, the command fails. - -### Your Current `commit_info` - -If you are using: - -- [`circuits/data/commit-chain/commit_info.json.0`](../circuits/data/commit-chain/commit_info.json.0) - -Then the command looks like: - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - sign-root \ - --versions v1.2.4,v1.2.5 \ - --commit-info-file circuits/data/commit-chain/commit_info.json.0 \ - --publisher-secret-key-wif \ - --attestation-dir circuits/data/psv-attestations -``` - -### Multi-Signer Aggregation - -`sign-root` handles only one signer per invocation. - -If multiple signers exist, run it once per signer: - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - sign-root \ - --versions v1.2.4,v1.2.5 \ - --commit-info-file circuits/data/commit-chain/commit_info.json.0 \ - --publisher-secret-key-wif \ - --attestation-dir circuits/data/psv-attestations - -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - sign-root \ - --versions v1.2.4,v1.2.5 \ - --commit-info-file circuits/data/commit-chain/commit_info.json.0 \ - --publisher-secret-key-wif \ - --attestation-dir circuits/data/psv-attestations - -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - sign-root \ - --versions v1.2.4,v1.2.5 \ - --commit-info-file circuits/data/commit-chain/commit_info.json.0 \ - --publisher-secret-key-wif \ - --attestation-dir circuits/data/psv-attestations -``` - -Signatures are aggregated into the same `manifest.json`. - -## Signature Invalidation Rules - -Existing signatures are cleared if any of the following changes: - -- Version list changes -- Version ordering changes -- Any resolved `part_stark_vk` bytes change -- `root` changes -- `publisher_set_id` changes - -Notes: - -- `publisher_set_id` already includes `threshold` -- A threshold change therefore appears as a `publisher_set_id` change - -## How the Proof Side Uses It - -The proof side accepts only the latest snapshot format. - -Requirements: - -- `manifest.json` must exist in the attestation directory -- Matching `proofs/.json` files must exist -- `manifest.json` must already contain enough valid signatures - -If the directory contains only the old `bundles/` files and no `manifest.json`, the proof side fails immediately. - -## Recommended Procedure - -### Step 1: Build the Latest Snapshot - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - build-tree \ - --versions v1.2.4,v1.2.5 \ - --attestation-dir circuits/data/psv-attestations -``` - -### Step 2: Let Each Signer Add One Signature - -```bash -cargo run -p bitvm2-noded --bin part-stark-vk-attest -- \ - sign-root \ - --versions v1.2.4,v1.2.5 \ - --commit-info-file circuits/data/commit-chain/commit_info.json.0 \ - --publisher-secret-key-wif \ - --attestation-dir circuits/data/psv-attestations -``` - -Repeat until the number of signatures reaches the threshold from `commit_info`. - -## Common Errors - -- `versions is empty` - No version list was provided -- `duplicate version '...'` - Duplicate version after normalization -- `failed to load part_stark_vk for version '...'` - The local verifier cannot resolve that version -- `publisher_secret_key_wif does not belong to active publisher set` - The provided WIF key does not belong to the current active publisher set -- `missing latest attestation manifest '.../manifest.json'` - The proof side is loading an attestation directory without a latest snapshot - -## Relevant Implementation Files - -- [`node/src/bin/part_stark_vk_attest.rs`](../node/src/bin/part_stark_vk_attest.rs) -- [`crates/bitcoin-light-client-circuit/src/attestation.rs`](../crates/bitcoin-light-client-circuit/src/attestation.rs) diff --git a/node/Cargo.toml b/node/Cargo.toml index 9395478f..fa4949db 100644 --- a/node/Cargo.toml +++ b/node/Cargo.toml @@ -1,10 +1,10 @@ [package] -name = "bitvm2-noded" +name = "bitvm-noded" version.workspace = true edition.workspace = true [[bin]] -name = "bitvm2-noded" +name = "bitvm-noded" path = "src/main.rs" [[bin]] @@ -36,8 +36,12 @@ name = "update-db" path = "src/bin/db_inject.rs" [[bin]] -name = "part-stark-vk-attest" -path = "src/bin/part_stark_vk_attest.rs" +name = "fetch-watchtower-xonly-pubkeys" +path = "src/bin/fetch_watchtower_xonly_pubkeys.rs" + +[[bin]] +name = "mock-rpc" +path = "src/bin/mock_rpc.rs" [dependencies] libp2p = { workspace = true, features = [ @@ -84,6 +88,8 @@ tokio-util.workspace = true http-body-util.workspace = true axum = { workspace = true, features = ["macros", "json"] } reqwest = { workspace = true, features = ["json"] } +aws-config = { version = "1.1.9", features = ["behavior-version-latest"] } +aws-sdk-s3 = "1.21.0" tower-http = { version = "0.6.2", features = ["cors", "trace"] } http = { workspace = true } stun-client = "0.1.4" @@ -111,14 +117,17 @@ dirs = "5.0.1" zkm-sdk = { workspace = true } zkm-verifier = { workspace = true, features = ["ark"] } -borsh = {version = "1.5.3", features = ["derive"] } -semver = "1.0" +zkm-recursion-core = { workspace = true } +zkm-stark = { workspace = true } +p3-bn254-fr = { workspace = true } +p3-field = { workspace = true } +borsh = { version = "1.5.3", features = ["derive"] } proof-builder = { workspace = true } util = { workspace = true } bitvm = { workspace = true } goat = { workspace = true } -bitvm2-lib = { workspace = true } +bitvm-lib = { workspace = true } store = { workspace = true } client = { workspace = true } bitcoin-light-client-circuit = { workspace = true } diff --git a/node/README.md b/node/README.md index d165f3d5..9e65ac18 100644 --- a/node/README.md +++ b/node/README.md @@ -1,6 +1,6 @@ -# BitVM2 Node +# BitVM Node -The main node implementation for GOAT Network's BitVM2 bridge protocol. This module handles P2P networking, message processing, scheduled tasks, and RPC services for secure cross-chain asset transfers between Bitcoin and GOAT L2. +The main node implementation for GOAT Network's BitVM bridge protocol. This module handles P2P networking, message processing, scheduled tasks, and RPC services for secure cross-chain asset transfers between Bitcoin and GOAT L2. ## Table of Contents @@ -24,7 +24,7 @@ The main node implementation for GOAT Network's BitVM2 bridge protocol. This mod ## Overview -The BitVM2 Node (`bitvm2-noded`) is a multi-role distributed node that participates in the BitVM2 cross-chain bridge protocol. It enables trustless Bitcoin-to-L2 transfers through a combination of: +The BitVM Node (`bitvm-noded`) is a multi-role distributed node that participates in the BitVM cross-chain bridge protocol. It enables trustless Bitcoin-to-L2 transfers through a combination of: - **Multi-signature Consensus**: Committee-based transaction presigning - **Optimistic Verification**: Watchtower monitoring with dispute resolution @@ -56,7 +56,7 @@ flowchart TB PB["Proof Builder RPC"] end - subgraph Node["bitvm2-noded"] + subgraph Node["bitvm-noded"] subgraph Input["Input Layer"] EW["Event Watch Task"] P2P["P2P Swarm (libp2p)"] @@ -108,7 +108,7 @@ flowchart LR subgraph Actors["Actor Roles"] C["Committee"] O["Operator"] - CH["Challenger"] + V["Verifier"] W["Watchtower"] R["Relayer\n(Committee + Flag)"] end @@ -116,16 +116,16 @@ flowchart LR subgraph Network["P2P Topics"] TC["/goat/topic/Committee"] TO["/goat/topic/Operator"] - TCH["/goat/topic/Challenger"] + TV["/goat/topic/Verifier"] TW["/goat/topic/Watchtower"] TA["/goat/topic/All"] end C <--> TC O <--> TO - CH <--> TCH + V <--> TV W <--> TW - C & O & CH & W & R <--> TA + C & O & V & W & R <--> TA R -.->|"SyncGraph"| TA ``` @@ -135,7 +135,7 @@ flowchart LR ## Actor System -BitVM2 employs four primary actor roles plus an optional relayer capability: +BitVM employs four primary actor roles plus an optional relayer capability: ### Fig-02-1-Actor-Roles @@ -145,7 +145,7 @@ classDiagram <> Committee Operator - Challenger + Verifier Watchtower All } @@ -164,7 +164,7 @@ classDiagram +send_take1_take2() } - class Challenger { + class Verifier { +monitor_timeouts() +submit_disprove() } @@ -183,7 +183,7 @@ classDiagram Actor <|-- Committee Actor <|-- Operator - Actor <|-- Challenger + Actor <|-- Verifier Actor <|-- Watchtower Committee <|-- Relayer : ENABLE_RELAYER=true ``` @@ -196,7 +196,7 @@ classDiagram |------|------------------|--------------| | **Committee** | Multi-sig committee member, responsible for presigning and graph endorsement | `NonceGeneration`, `CommitteePresign`, `EndorseGraph` | | **Operator** | Bridge operator, creates graphs and executes withdrawal transactions | `CreateGraph`, `KickoffSent`, `Take1Sent`, `Take2Sent` | -| **Challenger** | Dispute challenger, monitors timeouts and submits disproofs | `DisproveReady`, `DisproveSent` | +| **Verifier** | Dispute verifier, monitors timeouts and submits disproofs | `DisproveReady`, `DisproveSent` | | **Watchtower** | Chain monitor, validates block headers and submits challenges | `WatchtowerChallengeSent` | | **Relayer** | Graph data distributor, responds to sync requests | `SyncGraphRequest`, `SyncGraph` | @@ -228,7 +228,7 @@ sequenceDiagram Committee->>Committee: Validate fees and availability Committee->>Operator: ConfirmInstance - Operator->>Operator: Create SimplifiedBitvm2Graph + Operator->>Operator: Create SimplifiedbitvmGraph Operator->>DB: Store graph_raw_data (JSON) Operator->>Committee: CreateGraph (with graph data) @@ -311,7 +311,7 @@ sequenceDiagram participant GOAT as GOAT L2 participant Operator participant Watchtower - participant Challenger + participant Verifier participant BTC as Bitcoin User->>GOAT: InitWithdraw @@ -327,7 +327,7 @@ sequenceDiagram Operator->>BTC: ACK response else Operator rejects Operator->>BTC: NACK response - Challenger->>BTC: DisproveTx + Verifier->>BTC: DisproveTx end end @@ -340,7 +340,7 @@ sequenceDiagram Operator->>BTC: Take2 transaction BTC->>User: BTC transferred to user address else Challenge failed - Challenger->>BTC: DisproveTx + Verifier->>BTC: DisproveTx Note over User,BTC: User funds safe, Operator penalized end ``` @@ -392,7 +392,7 @@ flowchart TB ## Graph State Machine -Graph is the core data structure in BitVM2, representing a set of presigned Bitcoin transactions. +Graph is the core data structure in BitVM, representing a set of presigned Bitcoin transactions. ### Fig-04-1-Graph-Lifecycle @@ -579,7 +579,7 @@ sequenceDiagram | Column | Type | Description | |--------|------|-------------| | `graph_id` | UUID | Primary key (FK to graph) | -| `raw_data` | TEXT | JSON-serialized SimplifiedBitvm2Graph | +| `raw_data` | TEXT | JSON-serialized SimplifiedbitvmGraph | | `created_at`, `updated_at` | i64 | Timestamps | --- @@ -622,7 +622,7 @@ classDiagram <> Committee Operator - Challenger + Verifier Watchtower All } @@ -665,9 +665,9 @@ classDiagram | `WatchtowerChallengeInitSent` | Operator | Watchtower | WT challenge initialization | | `WatchtowerChallengeSent` | Watchtower | Operator | WT challenge submission | | `WatchtowerChallengeTimeout` | System | Operator | WT challenge timeout | -| `OperatorAckTimeout` | System | Challenger | Operator ACK timeout | -| `DisproveReady` | System | Challenger | Disprove ready | -| `DisproveSent` | Challenger | All | Disprove transaction broadcast | +| `OperatorAckTimeout` | System | Verifier | Operator ACK timeout | +| `DisproveReady` | System | Verifier | Disprove ready | +| `DisproveSent` | Verifier | All | Disprove transaction broadcast | #### Synchronization Messages @@ -758,7 +758,7 @@ node/src/ ├── metrics_service.rs # Prometheus metrics ├── rpc_service/ # REST API implementation │ ├── mod.rs # Service orchestration -│ ├── bitvm2.rs # BitVM2-specific endpoints +│ ├── bitvm.rs # BitVM-specific endpoints │ ├── routes.rs # HTTP route definitions │ ├── validation.rs # Input validation │ └── handler/ # Request handlers @@ -793,20 +793,20 @@ BITCOIN_NETWORK=regtest cargo build -r BITCOIN_NETWORK=testnet4 cargo build -r # Build only the node binary -cargo build -r -p bitvm2-noded +cargo build -r -p bitvm-noded ``` ### Generate Node Keys ```bash # Generate P2P peer key -bitvm2-noded key peer +bitvm-noded key peer # Output: # PEER_KEY= # PEER_ID= -# Generate funding address (for Operator/Challenger) -bitvm2-noded key funding-address +# Generate funding address (for Operator/Verifier) +bitvm-noded key funding-address # Output: # Funding P2WSH address: bc1q... ``` @@ -814,19 +814,41 @@ bitvm2-noded key funding-address ### Start Node ```bash -bitvm2-noded \ +bitvm-noded \ --rpc-addr 0.0.0.0:8080 \ --db-path ./node.db \ --p2p-port 4001 \ --bootnodes /ip4/x.x.x.x/tcp/4001/p2p/ ``` +### Start Local Mock RPC + +Use this when you only need to test HTTP interfaces. It starts the RPC routes without +P2P, chain watchers, or maintenance tasks, and seeds a local SQLite database with +mock nodes, instances, graphs, and overview data. + +```bash +cargo run -p bitvm-noded --bin mock-rpc -- --rpc-addr 127.0.0.1:18080 +``` + +Useful test calls: + +```bash +curl http://127.0.0.1:18080/v1/nodes/overview +curl 'http://127.0.0.1:18080/v1/instances?is_bridge_in=true' +curl http://127.0.0.1:18080/v1/graphs +``` + +The mock binary prints seeded instance and graph IDs on startup. Endpoints that +require real `graph_raw_data`, such as graph transaction hex export, still need +real graph raw data in the database. + ### CLI Options | Option | Description | Default | |--------|-------------|---------| | `--rpc-addr` | RPC service bind address | `0.0.0.0:8080` | -| `--db-path` | SQLite database path | `sqlite:/tmp/bitvm2-node.db` | +| `--db-path` | SQLite database path | `sqlite:/tmp/bitvm-node.db` | | `--p2p-port` | P2P listen port | `0` (random) | | `--bootnodes` | Bootstrap node addresses | - | | `--metrics-path` | Prometheus metrics endpoint | `/metrics` | @@ -840,7 +862,7 @@ bitvm2-noded \ | Variable | Required | Description | Default | |----------|----------|-------------|---------| -| `ACTOR` | Yes | Node role: `Committee`, `Operator`, `Challenger`, `Watchtower` | `Challenger` | +| `ACTOR` | Yes | Node role: `Committee`, `Operator`, `Verifier`, `Watchtower` | `Verifier` | | `BITCOIN_NETWORK` | Yes | Bitcoin network: `bitcoin`, `testnet4`, `signet`, `regtest` | `testnet4` | | `GOAT_NETWORK` | Yes | GOAT network: `main`, `test` | `test` | | `GOAT_CHAIN_URL` | Yes | GOAT L2 RPC endpoint | - | @@ -848,7 +870,7 @@ bitvm2-noded \ | `BITVM_SECRET` | Yes | Node private key or seed (`seed:xxx` format) | - | | `PEER_KEY` | Yes | libp2p node key (Base64 encoded) | - | | `GOAT_PRIVATE_KEY` | Conditional | GOAT chain private key (required for Committee) | - | -| `GOAT_ADDRESS` | Conditional | GOAT address (required for Operator/Challenger) | - | +| `GOAT_ADDRESS` | Conditional | GOAT address (required for Operator/Verifier) | - | | `ENABLE_RELAYER` | No | Enable relayer mode for Committee nodes | `false` | | `BTC_CHAIN_URL` | No | Bitcoin Esplora API endpoint | Public Esplora | | `MARA_SLIPSTREAM_API_URL` | No | MARA slipstream API base URL (used for non-standard tx broadcast) | mainnet: `https://slipstream.mara.com/api`; testnet4: `https://teststream.mara.com/api` | @@ -888,11 +910,11 @@ Relayer nodes should: | `/instances` | GET | List all instances | | `/instance/:id` | GET | Get instance details | | `/instances/overview` | GET | Instance statistics overview | -| `/graphs` | GET | List all graphs | -| `/graph/:id` | GET | Get graph details | -| `/graph/:id/txn` | GET | Get graph transaction list | -| `/graph/:id/tx/:txid` | GET | Get specific transaction | -| `/graph/ready_to_kickoff` | GET | Get graphs ready for kickoff | +| `/v1/graphs` | GET | List all graphs | +| `/v1/graphs/:id` | GET | Get graph details | +| `/v1/graphs/:id/txn?cursor=0` | GET | Get graph transaction list | +| `/v1/graphs/:id/tx?tx_name=cur-pre-kickoff.hex` | GET | Get specific transaction hex | +| `/v1/graphs/ready-to-kickoff` | GET | Get graphs ready for kickoff | | `/bridge_in_request` | POST | Initiate Bridge-In request | | `/bridge_out_init` | POST | Initiate Bridge-Out request | | `/challenge` | POST | Submit challenge | @@ -905,4 +927,4 @@ Relayer nodes should: - [Architecture Document](../docs/ARCHITECTURE.md) - Full system architecture - [Circuits README](../circuits/README.md) - ZK proof generation - [Deployment Guide](../deployment/README.md) - Deployment instructions -- [Incentives](../docs/incentives.md) - Incentives \ No newline at end of file +- [Incentives](../docs/incentives.md) - Incentives diff --git a/node/entrypoint.sh b/node/entrypoint.sh index 9bb31fb1..4e4d6b66 100755 --- a/node/entrypoint.sh +++ b/node/entrypoint.sh @@ -5,4 +5,4 @@ if [ -n "$BOOTNODES" ]; then bn="--bootnodes $BOOTNODES" fi -bitvm2-noded --rpc-addr 0.0.0.0:9100 --db-path /var/data/bitvm2-node-0.db --p2p-port 8443 $bn +bitvm-noded --rpc-addr 0.0.0.0:9100 --db-path /var/data/bitvm-node-0.db --p2p-port 8443 $bn diff --git a/node/src/action.rs b/node/src/action.rs index 47219f23..0c7d6f94 100644 --- a/node/src/action.rs +++ b/node/src/action.rs @@ -8,11 +8,15 @@ use crate::middleware::AllBehaviours; use crate::rpc_service::current_time_secs; use crate::utils::*; use alloy::primitives::Address as EvmAddress; -use anyhow::{Result, anyhow}; +use anyhow::{Context, Result, anyhow}; use bitcoin::{PublicKey, Txid}; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::committee::*; -use bitvm2_lib::types::{Bitvm2Graph, SimplifiedBitvm2Graph}; +use bitvm_lib::actors::Actor; +use bitvm_lib::babe_adapter::{ + BabeAssertWitness, BabeBundleBuilder, BabeChallengeAssertWitness, BabeWronglyChallengedWitness, + CACSetupPackage, +}; +use bitvm_lib::committee::*; +use bitvm_lib::types::{BitvmGcGraph, SimplifiedBitvmGcGraph}; use client::goat_chain::DisproveTxType; use client::http_client::async_client::HttpAsyncClient; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; @@ -21,6 +25,7 @@ use libp2p::{PeerId, Swarm, gossipsub}; use musig2::{PartialSignature, PubNonce}; use secp256k1::schnorr::Signature as SchnorrSignature; use serde::{Deserialize, Serialize}; +use std::sync::Arc; use store::MessageState; use store::localdb::LocalDB; use tracing::warn; @@ -32,11 +37,17 @@ pub struct GOATMessage { pub content: GOATMessageContent, } +const GOAT_MESSAGE_BIN_PREFIX: &[u8] = b"GOATBIN1"; + #[derive(Serialize, Deserialize, Clone)] pub enum GOATMessageContent { PeginRequest(PeginRequest), CreateGraph(CreateGraph), ConfirmInstance(ConfirmInstance), + InitGraph(InitGraph), + GenCircuits(GenCircuits), + CutCircuits(CutCircuits), + SolderingProofReady(SolderingProofReady), NonceGeneration(NonceGeneration), CommitteePresign(CommitteePresign), EndorseGraph(EndorseGraph), @@ -50,13 +61,10 @@ pub enum GOATMessageContent { ChallengeSent(ChallengeSent), WatchtowerChallengeInitSent(WatchtowerChallengeInitSent), WatchtowerChallengeSent(WatchtowerChallengeSent), - WatchtowerChallengeTimeout(WatchtowerChallengeTimeout), - OperatorAckTimeout(OperatorAckTimeout), - OperatorCommitBlockHashReady(OperatorCommitBlockHashReady), - OperatorCommitBlockHashTimeout(OperatorCommitBlockHashTimeout), - AssertInitReady(AssertInitReady), - AssertCommitTimeout(AssertCommitTimeout), - DisproveReady(DisproveReady), + AssertReady(AssertReady), + AssertSent(AssertSent), + ChallengeAssertSent(ChallengeAssertSent), + WronglyChallengeTimeout(WronglyChallengeTimeout), DisproveSent(DisproveSent), Take1Ready(Take1Ready), Take1Sent(Take1Sent), @@ -84,19 +92,46 @@ pub struct ConfirmInstance { pub instance_id: Uuid, } #[derive(Serialize, Deserialize, Clone)] +pub struct InitGraph { + pub instance_id: Uuid, + pub graph_id: Uuid, +} +#[derive(Serialize, Deserialize, Clone)] +pub struct GenCircuits { + pub instance_id: Uuid, + pub graph_id: Uuid, + pub verifier_pubkey: PublicKey, + pub setup_package: CACSetupPackage, +} +#[derive(Serialize, Deserialize, Clone)] +pub struct CutCircuits { + pub instance_id: Uuid, + pub graph_id: Uuid, + pub verifier_pubkey: PublicKey, + pub verifier_index: usize, + pub selected_circuit_indexes: Vec, +} +#[derive(Serialize, Deserialize, Clone)] +pub struct SolderingProofReady { + pub instance_id: Uuid, + pub graph_id: Uuid, + pub verifier_index: usize, + pub payload_hash: [u8; 32], + pub total_len: usize, +} +#[derive(Serialize, Deserialize, Clone)] pub struct CreateGraph { pub instance_id: Uuid, pub graph_id: Uuid, pub graph_nonce: u64, - pub graph: SimplifiedBitvm2Graph, + pub graph: SimplifiedBitvmGcGraph, } #[derive(Serialize, Deserialize, Clone)] pub struct NonceGeneration { pub instance_id: Uuid, pub graph_id: Uuid, pub committee_pubkey: PublicKey, - pub watchtower_num: usize, - pub assert_commit_num: usize, + pub verifier_num: usize, pub pub_nonces: CommitteePubNonces, pub nonce_sigs: CommitteeNonceSignatures, } @@ -121,7 +156,7 @@ pub struct GraphFinalize { pub instance_id: Uuid, pub graph_id: Uuid, pub graph_nonce: u64, - pub graph: SimplifiedBitvm2Graph, + pub graph: SimplifiedBitvmGcGraph, pub endorse_sigs: Vec<(PublicKey, EvmAddress, Vec)>, } #[derive(Serialize, Deserialize, Clone)] @@ -175,43 +210,35 @@ pub struct WatchtowerChallengeInitSent { pub struct WatchtowerChallengeSent { pub instance_id: Uuid, pub graph_id: Uuid, - pub watchtower_challenge_txids: Vec<(usize, Txid)>, -} -#[derive(Serialize, Deserialize, Clone)] -pub struct WatchtowerChallengeTimeout { - pub instance_id: Uuid, - pub graph_id: Uuid, - pub watchtower_indexes: Vec, -} -#[derive(Serialize, Deserialize, Clone)] -pub struct OperatorAckTimeout { - pub instance_id: Uuid, - pub graph_id: Uuid, -} -#[derive(Serialize, Deserialize, Clone)] -pub struct OperatorCommitBlockHashReady { - pub instance_id: Uuid, - pub graph_id: Uuid, + pub watchtower_index: usize, } #[derive(Serialize, Deserialize, Clone)] -pub struct OperatorCommitBlockHashTimeout { +pub struct AssertReady { pub instance_id: Uuid, pub graph_id: Uuid, } #[derive(Serialize, Deserialize, Clone)] -pub struct AssertInitReady { +pub struct AssertSent { pub instance_id: Uuid, pub graph_id: Uuid, + pub assert_txid: Txid, + pub assert_witness: Option, } #[derive(Serialize, Deserialize, Clone)] -pub struct AssertCommitTimeout { +pub struct ChallengeAssertSent { pub instance_id: Uuid, pub graph_id: Uuid, + pub challenge_assert_txid: Txid, + pub verifier_index: usize, + pub challenge_witness: Option, } #[derive(Serialize, Deserialize, Clone)] -pub struct DisproveReady { +pub struct WronglyChallengeTimeout { pub instance_id: Uuid, pub graph_id: Uuid, + pub challenge_assert_txid: Txid, + pub verifier_index: usize, + pub wrongly_challenged_witness: Option, } #[derive(Serialize, Deserialize, Clone)] pub struct DisproveSent { @@ -267,7 +294,7 @@ pub struct SyncGraphRequest { pub struct SyncGraph { pub instance_id: Uuid, pub graph_id: Uuid, - pub graph: SimplifiedBitvm2Graph, + pub graph: SimplifiedBitvmGcGraph, } #[derive(Serialize, Deserialize, Clone)] @@ -291,12 +318,32 @@ impl GOATMessage { pub async fn serialize_message(&self) -> Result> { let cloned = self.clone(); - Ok(tokio::task::spawn_blocking(move || serde_json::to_vec(&cloned)).await??) + tokio::task::spawn_blocking(move || { + if matches!(&cloned.content, GOATMessageContent::GenCircuits(_)) { + let mut encoded = bincode::serialize(&cloned) + .context("failed to serialize bincode GOATMessage")?; + let mut message = Vec::with_capacity(GOAT_MESSAGE_BIN_PREFIX.len() + encoded.len()); + message.extend_from_slice(GOAT_MESSAGE_BIN_PREFIX); + message.append(&mut encoded); + Ok(message) + } else { + serde_json::to_vec(&cloned).context("failed to serialize legacy JSON GOATMessage") + } + }) + .await? } pub async fn deserialize_message(message: &[u8]) -> Result { let cloned = message.to_vec(); - Ok(tokio::task::spawn_blocking(move || serde_json::from_slice(&cloned)).await??) + tokio::task::spawn_blocking(move || { + if let Some(encoded) = cloned.strip_prefix(GOAT_MESSAGE_BIN_PREFIX) { + bincode::deserialize(encoded).context("failed to deserialize bincode GOATMessage") + } else { + serde_json::from_slice(&cloned) + .context("failed to deserialize legacy JSON GOATMessage") + } + }) + .await? } } #[allow(clippy::too_many_arguments)] @@ -306,6 +353,7 @@ pub async fn handle_self_p2p_msg( btc_client: &BTCClient, goat_client: &GOATClient, http_client: &HttpAsyncClient, + soldering_builder: &Option>, actor: Actor, from_peer_id: PeerId, id: MessageId, @@ -332,6 +380,7 @@ pub async fn handle_self_p2p_msg( btc_client, goat_client, http_client, + soldering_builder, actor.clone(), from_peer_id, id.clone(), @@ -380,6 +429,7 @@ pub async fn recv_and_dispatch( btc_client: &BTCClient, goat_client: &GOATClient, http_client: &HttpAsyncClient, + soldering_builder: &Option>, actor: Actor, from_peer_id: PeerId, id: MessageId, @@ -397,6 +447,7 @@ pub async fn recv_and_dispatch( btc_client, goat_client, http_client, + soldering_builder, actor, from_peer_id, id, @@ -411,7 +462,7 @@ pub async fn try_finalize_graph( goat_client: &GOATClient, instance_id: Uuid, graph_id: Uuid, - graph: Option<&SimplifiedBitvm2Graph>, + graph: Option<&SimplifiedBitvmGcGraph>, broadcast_graph_finalize: bool, ) -> Result<()> { let endorsements = @@ -425,12 +476,12 @@ pub async fn try_finalize_graph( && partial_sigs.len() == committee_pubkeys.len() { let mut graph = match graph { - Some(g) => Bitvm2Graph::from_simplified(g)?, + Some(g) => BitvmGcGraph::from_simplified(g)?, None => { let g = get_graph(local_db, instance_id, graph_id) .await? .ok_or_else(|| anyhow!("Graph not found for {instance_id}:{graph_id}"))?; - Bitvm2Graph::from_simplified(&g)? + BitvmGcGraph::from_simplified(&g)? } }; let pub_nonces = pub_nonoces.into_iter().map(|(_, pn)| pn).collect::>(); @@ -498,7 +549,7 @@ pub(crate) async fn get_graph_or_defer( instance_id: Uuid, graph_id: Uuid, message: &GOATMessage, -) -> Result> { +) -> Result> { match get_graph(local_db, instance_id, graph_id).await? { Some(g) => Ok(Some(g)), None => { @@ -537,3 +588,32 @@ pub async fn try_send_sync_graph_request( send_to_peer(swarm, message).await?; Ok(()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn soldering_proof_ready_is_descriptor_only() { + let ready = SolderingProofReady { + instance_id: Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), + graph_id: Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(), + verifier_index: 3, + payload_hash: [0xabu8; 32], + total_len: 1024, + }; + + let value = serde_json::to_value(ready).unwrap(); + let object = value.as_object().unwrap(); + + assert!(object.contains_key("instance_id")); + assert!(object.contains_key("graph_id")); + assert!(object.contains_key("verifier_index")); + assert!(object.contains_key("payload_hash")); + assert!(object.contains_key("total_len")); + assert!(!object.contains_key("payload_path")); + assert!(!object.contains_key("payload")); + assert!(!object.contains_key("setup_package")); + assert!(!object.contains_key("verifier_pubkey")); + } +} diff --git a/node/src/bin/db_inject.rs b/node/src/bin/db_inject.rs index 66fa272b..601e7148 100644 --- a/node/src/bin/db_inject.rs +++ b/node/src/bin/db_inject.rs @@ -5,14 +5,14 @@ //! can process it as if it were received from the network. //! //! Key args: -//! - --db-path: local SQLite path (e.g., sqlite:/tmp/bitvm2-node.db) -//! - --actor: Committee | Operator | Challenger | Watchtower | All +//! - --db-path: local SQLite path (e.g., sqlite:/tmp/bitvm-node.db) +//! - --actor: Committee | Operator | Verifier | Watchtower | All //! - --message-json or --message-file (one required) //! - --business-id (optional; inferred from content when possible) //! //! Example: -//! - cargo run -p bitvm2-noded --bin update-db -- \ -//! --db-path sqlite:/tmp/bitvm2-node.db \ +//! - cargo run -p bitvm-noded --bin update-db -- \ +//! --db-path sqlite:/tmp/bitvm-node.db \ //! --actor Operator \ //! --message-file ./message.json use std::fs; @@ -23,9 +23,9 @@ use anyhow::{Context, Result, anyhow}; use clap::Parser; use uuid::Uuid; -use bitvm2_lib::actors::Actor; -use bitvm2_noded::action::*; -use bitvm2_noded::utils::upsert_message; +use bitvm_lib::actors::Actor; +use bitvm_noded::action::*; +use bitvm_noded::utils::upsert_message; use store::create_local_db; #[derive(Parser, Debug)] @@ -35,7 +35,7 @@ struct Args { #[arg(long)] db_path: String, - /// Target actor (Committee, Operator, Challenger, Watchtower, All) + /// Target actor (Committee, Operator, Verifier, Watchtower, All) #[arg(long, value_parser = parse_actor)] actor: Actor, @@ -80,6 +80,10 @@ fn infer_business_id(content: &GOATMessageContent) -> Option { match content { GOATMessageContent::PeginRequest(v) => Some(v.instance_id), GOATMessageContent::ConfirmInstance(v) => Some(v.instance_id), + GOATMessageContent::InitGraph(v) => Some(v.instance_id), + GOATMessageContent::GenCircuits(v) => Some(v.instance_id), + GOATMessageContent::CutCircuits(v) => Some(v.instance_id), + GOATMessageContent::SolderingProofReady(v) => Some(v.instance_id), GOATMessageContent::CreateGraph(v) => Some(v.graph_id), GOATMessageContent::NonceGeneration(v) => Some(v.graph_id), GOATMessageContent::CommitteePresign(v) => Some(v.graph_id), @@ -94,13 +98,10 @@ fn infer_business_id(content: &GOATMessageContent) -> Option { GOATMessageContent::ChallengeSent(v) => Some(v.graph_id), GOATMessageContent::WatchtowerChallengeInitSent(v) => Some(v.graph_id), GOATMessageContent::WatchtowerChallengeSent(v) => Some(v.graph_id), - GOATMessageContent::WatchtowerChallengeTimeout(v) => Some(v.graph_id), - GOATMessageContent::OperatorAckTimeout(v) => Some(v.graph_id), - GOATMessageContent::OperatorCommitBlockHashReady(v) => Some(v.graph_id), - GOATMessageContent::OperatorCommitBlockHashTimeout(v) => Some(v.graph_id), - GOATMessageContent::AssertInitReady(v) => Some(v.graph_id), - GOATMessageContent::AssertCommitTimeout(v) => Some(v.graph_id), - GOATMessageContent::DisproveReady(v) => Some(v.graph_id), + GOATMessageContent::AssertReady(v) => Some(v.graph_id), + GOATMessageContent::AssertSent(v) => Some(v.graph_id), + GOATMessageContent::ChallengeAssertSent(v) => Some(v.graph_id), + GOATMessageContent::WronglyChallengeTimeout(v) => Some(v.graph_id), GOATMessageContent::DisproveSent(v) => Some(v.graph_id), GOATMessageContent::Take1Ready(v) => Some(v.graph_id), GOATMessageContent::Take1Sent(v) => Some(v.graph_id), diff --git a/node/src/bin/fetch_watchtower_xonly_pubkeys.rs b/node/src/bin/fetch_watchtower_xonly_pubkeys.rs new file mode 100644 index 00000000..3a67094a --- /dev/null +++ b/node/src/bin/fetch_watchtower_xonly_pubkeys.rs @@ -0,0 +1,108 @@ +use alloy::primitives::Address as EvmAddress; +use anyhow::{Context, Result, anyhow}; +use bitvm_noded::env::{ + ENV_GOAT_CHAIN_URL, ENV_GOAT_GATEWAY_CONTRACT_ADDRESS, ENV_GOAT_NETWORK, + get_goat_gateway_contract_from_env, get_goat_network, goat_config_from_env, +}; +use client::goat_chain::GOATClient; +use serde::Serialize; +use sha2::{Digest, Sha256}; +use std::{ + env, fs, + path::{Path, PathBuf}, + time::{SystemTime, UNIX_EPOCH}, +}; + +#[derive(Debug, Serialize)] +struct WatchtowerSnapshot { + goat_chain_url: String, + goat_gateway_contract_address: String, + goat_network: String, + committee_management_address: String, + watchtower_xonly_public_keys: Vec, + watchtower_count: usize, + watchtower_list_hash: String, + watchtower_order_note: String, + generated_at_unix: u64, +} + +fn require_env(name: &str) -> Result { + env::var(name).map_err(|_| anyhow!("{name} must be set")) +} + +fn workspace_root() -> PathBuf { + Path::new(env!("CARGO_MANIFEST_DIR")) + .parent() + .expect("node crate is under the workspace root") + .to_path_buf() +} + +fn snapshot_path(gateway: &EvmAddress) -> PathBuf { + workspace_root() + .join("target") + .join("watchtower-snapshots") + .join(format!("{}.json", gateway.to_string().to_lowercase())) +} + +fn watchtower_list_hash(watchtower_pubkeys: &[String]) -> String { + let mut hasher = Sha256::new(); + // The hash is intentionally order-sensitive because watchtower index maps to node_index. + for key in watchtower_pubkeys { + hasher.update(key.as_bytes()); + } + format!("0x{}", hex::encode(hasher.finalize())) +} + +#[tokio::main] +async fn main() -> Result<()> { + dotenv::dotenv().ok(); + + let goat_chain_url = require_env(ENV_GOAT_CHAIN_URL)?; + let goat_gateway_contract_address = require_env(ENV_GOAT_GATEWAY_CONTRACT_ADDRESS)?; + let goat_network = env::var(ENV_GOAT_NETWORK).unwrap_or_else(|_| "test".to_string()); + + let gateway = get_goat_gateway_contract_from_env(); + let goat_client = GOATClient::new(goat_config_from_env().await, get_goat_network()); + let committee_management_address = + EvmAddress::from_slice(&goat_client.gateway_get_committee_management().await?); + // Preserve on-chain order. The operator circuit compares this list by index with graph + // watchtower_pubkeys, watchtower_challenge vouts, and included_watchtowers bitmap bits. + let watchtower_pubkeys = goat_client + .committee_mana_get_watchtowers() + .await? + .into_iter() + .map(|key| format!("0x{}", hex::encode(key.serialize()))) + .collect::>(); + + if watchtower_pubkeys.is_empty() { + return Err(anyhow!("committee management returned an empty watchtower list")); + } + + let watchtower_list_hash = watchtower_list_hash(&watchtower_pubkeys); + let snapshot = WatchtowerSnapshot { + goat_chain_url, + goat_gateway_contract_address, + goat_network, + committee_management_address: committee_management_address.to_string(), + watchtower_count: watchtower_pubkeys.len(), + watchtower_xonly_public_keys: watchtower_pubkeys.clone(), + watchtower_list_hash: watchtower_list_hash.clone(), + watchtower_order_note: + "order-sensitive: index must match graph watchtower_pubkeys/node_index".to_string(), + generated_at_unix: SystemTime::now().duration_since(UNIX_EPOCH)?.as_secs(), + }; + + let path = snapshot_path(&gateway); + let parent = path.parent().context("snapshot path has parent")?; + fs::create_dir_all(parent)?; + fs::write(&path, serde_json::to_string_pretty(&snapshot)?)?; + + eprintln!("watchtower snapshot: {}", path.display()); + eprintln!("watchtower list hash: {watchtower_list_hash}"); + eprintln!( + "before build, export FIXED_WATCHTOWER_XONLY_PUBLIC_KEYS or eval this command output" + ); + println!("export FIXED_WATCHTOWER_XONLY_PUBLIC_KEYS={}", watchtower_pubkeys.join(",")); + + Ok(()) +} diff --git a/node/src/bin/mock_rpc.rs b/node/src/bin/mock_rpc.rs new file mode 100644 index 00000000..c9e08644 --- /dev/null +++ b/node/src/bin/mock_rpc.rs @@ -0,0 +1,373 @@ +//! Local-only mock RPC service for interface testing. +//! +//! This starts the Axum RPC routes without P2P, chain watchers, or maintenance tasks, +//! and seeds SQLite with deterministic demo rows. + +use std::str::FromStr; +use std::sync::{Arc, Mutex}; +use std::time::{SystemTime, UNIX_EPOCH}; + +use alloy::primitives::U256; +use anyhow::{Context, Result}; +use bitcoin::Network; +use bitvm_lib::actors::Actor; +use bitvm_noded::env::{ + ENV_BITCOIN_NETWORK, ENV_BITVM_SECRET, ENV_GOAT_ADDRESS, ENV_GOAT_GATEWAY_CONTRACT_ADDRESS, + ENV_GOAT_NETWORK, ENV_GOAT_SWAP_CONTRACT_ADDRESS, +}; +use bitvm_noded::rpc_service::{self, AppState, current_time_secs}; +use bitvm_noded::utils::{generate_local_key, get_rand_btc_address_p2wpkh, get_rand_goat_address}; +use clap::Parser; +use client::Utxo; +use prometheus_client::registry::Registry; +use secp256k1::Secp256k1; +use store::{ + BridgeOutGlobalStats, GoatTxProcessingStatus, GoatTxRecord, GoatTxType, Graph, GraphStatus, + Instance, InstanceBridgeInStatus, InstanceBridgeOutStatus, Node, UInt64Array3, create_local_db, +}; +use tokio::signal; +use tokio_util::sync::CancellationToken; +use tracing_subscriber::EnvFilter; +use uuid::Uuid; + +const MOCK_GATEWAY_CONTRACT: &str = "0x1111111111111111111111111111111111111111"; +const MOCK_SWAP_CONTRACT: &str = "0x2222222222222222222222222222222222222222"; + +#[derive(Debug, Parser)] +#[command(author, version, about = "Start a local mock BitVM RPC service")] +struct Opts { + /// Local RPC service address. + #[arg(long, default_value = "127.0.0.1:18080")] + rpc_addr: String, + + /// SQLite database URL or file path. Defaults to a timestamped /tmp database. + #[arg(long)] + db_path: Option, + + /// Current node role exposed through AppState. + #[arg(long, default_value = "Verifier", value_parser = parse_actor)] + actor: Actor, +} + +#[derive(Debug)] +struct MockSeedSummary { + bridge_in_instance_id: Uuid, + bridge_out_instance_id: Uuid, + ready_graph_id: Uuid, + challenge_graph_id: Uuid, + operator_pubkey: String, + graph_from_addr: String, +} + +fn parse_actor(raw: &str) -> std::result::Result { + Actor::from_str(raw).map_err(|_| format!("invalid actor: {raw}")) +} + +fn default_db_path() -> String { + let ts = SystemTime::now().duration_since(UNIX_EPOCH).map(|v| v.as_secs()).unwrap_or(0); + format!("sqlite:/tmp/bitvm-node-mock-{ts}.db") +} + +fn normalize_db_path(path: Option) -> String { + let path = path.unwrap_or_else(default_db_path); + if path.starts_with("sqlite:") { path } else { format!("sqlite:{path}") } +} + +fn set_env_default(key: &str, value: &str) { + if std::env::var_os(key).is_none() { + unsafe { + std::env::set_var(key, value); + } + } +} + +fn configure_mock_env() { + set_env_default("RUST_LOG", "info"); + set_env_default(ENV_BITCOIN_NETWORK, "testnet4"); + set_env_default(ENV_GOAT_NETWORK, "test"); + set_env_default(ENV_GOAT_GATEWAY_CONTRACT_ADDRESS, MOCK_GATEWAY_CONTRACT); + set_env_default(ENV_GOAT_SWAP_CONTRACT_ADDRESS, MOCK_SWAP_CONTRACT); + set_env_default(ENV_BITVM_SECRET, "seed:mock-rpc-service"); + set_env_default(ENV_GOAT_ADDRESS, "0x3333333333333333333333333333333333333333"); +} + +fn mock_uuid(raw: &str) -> Uuid { + Uuid::parse_str(raw).expect("valid mock UUID") +} + +fn mock_tx_hash(byte: u8) -> String { + format!("0x{}", hex::encode([byte; 32])) +} + +fn mock_goat_addr(byte: u8) -> String { + format!("0x{}", hex::encode([byte; 20])) +} + +fn mock_btc_pubkey() -> String { + let (_, public_key) = Secp256k1::new().generate_keypair(&mut rand::thread_rng()); + public_key.to_string() +} + +fn mock_peer_id() -> String { + generate_local_key().public().to_peer_id().to_string() +} + +fn seeded_node( + peer_id: String, + actor: Actor, + node_name: &str, + btc_pub_key: String, + now: i64, +) -> Node { + Node { + peer_id, + actor: actor.to_string(), + node_name: node_name.to_string(), + goat_addr: get_rand_goat_address(), + btc_pub_key, + socket_addr: "127.0.0.1:18080".to_string(), + reward: "0".to_string(), + service_fee_rate: 0.001, + available_peg_btc: U256::from(4_700_000_000_000_000_000_000_000_u128).to_string(), + updated_at: now, + created_at: now, + } +} + +fn seeded_bridge_in_instance( + instance_id: Uuid, + status: InstanceBridgeInStatus, + amount: i64, + now: i64, + utxo_byte: u8, +) -> Result { + let utxo = vec![Utxo { txid: [utxo_byte; 32], vout: 0, amount_sats: amount as u64 }]; + Ok(Instance { + instance_id, + is_bridge_in: true, + network: Network::Testnet4.to_string(), + from_addr: get_rand_btc_address_p2wpkh(Network::Testnet4), + to_addr: mock_goat_addr(0x31), + amount, + fees: UInt64Array3([1_000, 2_000, 3_000]), + input_utxos: serde_json::to_string(&utxo)?, + status: status.to_string(), + goat_tx_hash: mock_tx_hash(0xa1), + goat_tx_height: 123_456, + user_change_addr: get_rand_btc_address_p2wpkh(Network::Testnet4), + user_refund_addr: get_rand_btc_address_p2wpkh(Network::Testnet4), + pegin_data_tx_hash: mock_tx_hash(0xa2), + post_pegin_txhash: Some(mock_tx_hash(0xa3)), + bridge_out_amount: "0".to_string(), + status_updated_at: now - 120, + created_at: now - 3_600, + updated_at: now, + ..Default::default() + }) +} + +fn seeded_bridge_out_instance(instance_id: Uuid, now: i64, escrow_hash: String) -> Instance { + Instance { + instance_id, + is_bridge_in: false, + network: Network::Testnet4.to_string(), + from_addr: mock_goat_addr(0x41), + to_addr: get_rand_btc_address_p2wpkh(Network::Testnet4), + amount: 0, + input_utxos: "[]".to_string(), + status: InstanceBridgeOutStatus::Initialize.to_string(), + goat_tx_hash: mock_tx_hash(0xb1), + goat_tx_height: 123_500, + escrow_hash: Some(escrow_hash), + bridge_out_amount: "25000000".to_string(), + bridge_out_lock_time: now + 3_600, + status_updated_at: now - 60, + created_at: now - 900, + updated_at: now, + ..Default::default() + } +} + +#[allow(clippy::too_many_arguments)] +fn seeded_graph( + graph_id: Uuid, + instance_id: Uuid, + kickoff_index: i64, + status: GraphStatus, + operator_pubkey: &str, + from_addr: &str, + now: i64, + init_withdraw_tx_hash: Option, + sub_status: String, +) -> Graph { + Graph { + graph_id, + instance_id, + kickoff_index, + from_addr: from_addr.to_string(), + to_addr: get_rand_btc_address_p2wpkh(Network::Testnet4), + amount: 10_000_000, + challenge_amount: 1_000_000, + status: status.to_string(), + sub_status, + operator_pubkey: operator_pubkey.to_string(), + init_withdraw_tx_hash, + bridge_out_start_at: now - 300, + status_updated_at: now - 120, + proceed_withdraw_height: 899_980, + created_at: now - 7_200, + updated_at: now, + ..Default::default() + } +} + +async fn seed_mock_data( + local_db: &store::localdb::LocalDB, + current_peer_id: &str, + actor: Actor, +) -> Result { + let now = current_time_secs(); + let operator_pubkey = mock_btc_pubkey(); + let graph_from_addr = mock_goat_addr(0x51); + let bridge_in_instance_id = mock_uuid("11111111-1111-1111-1111-111111111111"); + let bridge_in_pending_id = mock_uuid("11111111-1111-1111-1111-111111111112"); + let bridge_out_instance_id = mock_uuid("22222222-2222-2222-2222-222222222222"); + let ready_graph_id = mock_uuid("33333333-3333-3333-3333-333333333333"); + let challenge_graph_id = mock_uuid("33333333-3333-3333-3333-333333333334"); + let escrow_hash = mock_tx_hash(0xc1); + + let current_node = + seeded_node(current_peer_id.to_string(), actor, "mock-current", mock_btc_pubkey(), now); + let committee_node = + seeded_node(mock_peer_id(), Actor::Committee, "mock-committee", mock_btc_pubkey(), now); + let operator_node = + seeded_node(mock_peer_id(), Actor::Operator, "mock-operator", operator_pubkey.clone(), now); + let watchtower_node = + seeded_node(mock_peer_id(), Actor::Watchtower, "mock-watchtower", mock_btc_pubkey(), now); + + let bridge_in_success = seeded_bridge_in_instance( + bridge_in_instance_id, + InstanceBridgeInStatus::RelayerL2Minted, + 10_000_000, + now, + 0x11, + )?; + let bridge_in_pending = seeded_bridge_in_instance( + bridge_in_pending_id, + InstanceBridgeInStatus::UserInited, + 1_000_000, + now, + 0x12, + )?; + let bridge_out = seeded_bridge_out_instance(bridge_out_instance_id, now, escrow_hash.clone()); + + let none_sub_status = r#"{"watchtower_challenge_status":[],"verifier_challenge_status":[],"disprove_type":null,"disprove_index":-1}"#.to_string(); + let challenge_sub_status = r#"{"watchtower_challenge_status":[true,false],"verifier_challenge_status":["None"],"disprove_type":null,"disprove_index":-1}"#.to_string(); + let ready_graph = seeded_graph( + ready_graph_id, + bridge_in_instance_id, + 0, + GraphStatus::OperatorDataPushed, + &operator_pubkey, + &graph_from_addr, + now, + None, + none_sub_status.clone(), + ); + let challenge_graph = seeded_graph( + challenge_graph_id, + bridge_in_instance_id, + 1, + GraphStatus::Challenge, + &operator_pubkey, + &graph_from_addr, + now, + Some(mock_tx_hash(0xd1)), + challenge_sub_status, + ); + + let mut tx = local_db.start_transaction().await?; + for node in [current_node, committee_node, operator_node, watchtower_node] { + tx.upsert_node(&node).await?; + } + for instance in [bridge_in_success, bridge_in_pending, bridge_out] { + tx.upsert_instance(&instance).await?; + } + for graph in [ready_graph, challenge_graph] { + tx.upsert_graph(&graph).await?; + } + tx.upsert_goat_tx_record(&GoatTxRecord { + instance_id: bridge_out_instance_id, + graph_id: Uuid::nil(), + tx_type: GoatTxType::SwapInitialize.to_string(), + tx_hash: mock_tx_hash(0xe1), + height: 123_501, + is_local: true, + processing_status: GoatTxProcessingStatus::Processed.to_string(), + extra: Some(escrow_hash), + created_at: now, + }) + .await?; + tx.upsert_bridge_out_global_stats(&BridgeOutGlobalStats { + id: 1, + initial_txn: 1, + initial_amount: "25000000".to_string(), + claim_txn: 1, + claim_amount: "12000000".to_string(), + refund_txn: 0, + refund_amount: "0".to_string(), + created_at: now, + updated_at: now, + }) + .await?; + tx.commit().await?; + + Ok(MockSeedSummary { + bridge_in_instance_id, + bridge_out_instance_id, + ready_graph_id, + challenge_graph_id, + operator_pubkey, + graph_from_addr, + }) +} + +#[tokio::main(flavor = "current_thread")] +async fn main() -> Result<()> { + dotenv::dotenv().ok(); + configure_mock_env(); + let _ = tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).try_init(); + + let opts = Opts::parse(); + let db_path = normalize_db_path(opts.db_path); + let local_key = generate_local_key(); + let peer_id = local_key.public().to_peer_id().to_string(); + let local_db = create_local_db(&db_path).await; + let seed_summary = + seed_mock_data(&local_db, &peer_id, opts.actor.clone()).await.context("seed mock data")?; + + let registry = Arc::new(Mutex::new(Registry::default())); + let app_state = + AppState::create_arc_mock_app_state(local_db, opts.actor, peer_id, registry).await?; + let cancellation_token = CancellationToken::new(); + let shutdown_token = cancellation_token.clone(); + tokio::spawn(async move { + if signal::ctrl_c().await.is_ok() { + shutdown_token.cancel(); + } + }); + + println!("Mock RPC listening on http://{}", opts.rpc_addr); + println!("DB path: {db_path}"); + println!("Gateway contract: {MOCK_GATEWAY_CONTRACT}"); + println!("Swap contract: {MOCK_SWAP_CONTRACT}"); + println!("Bridge-in instance: {}", seed_summary.bridge_in_instance_id); + println!("Bridge-out instance: {}", seed_summary.bridge_out_instance_id); + println!("Ready graph: {}", seed_summary.ready_graph_id); + println!("Challenge graph: {}", seed_summary.challenge_graph_id); + println!("Operator pubkey: {}", seed_summary.operator_pubkey); + println!("Graph from_addr: {}", seed_summary.graph_from_addr); + + rpc_service::serve_with_app_state(opts.rpc_addr, app_state, cancellation_token).await?; + Ok(()) +} diff --git a/node/src/bin/part_stark_vk_attest.rs b/node/src/bin/part_stark_vk_attest.rs deleted file mode 100644 index 411fec7e..00000000 --- a/node/src/bin/part_stark_vk_attest.rs +++ /dev/null @@ -1,246 +0,0 @@ -use anyhow::{Context, Result, bail}; -use bitcoin::PrivateKey; -use bitcoin::secp256k1::{PublicKey, Secp256k1, SecretKey}; -use bitcoin_light_client_circuit::{ - part_stark_vk_attestation_dir, save_latest_part_stark_vk_attestation_snapshot, - sign_latest_part_stark_vk_snapshot, -}; -use clap::{Parser, Subcommand}; -use commit_chain::CommitInfo; -use semver::Version; -use std::path::PathBuf; -use std::str::FromStr; -use zkm_verifier::Groth16Verifier; - -#[derive(Debug, Parser)] -struct Cli { - #[command(subcommand)] - command: Command, -} - -#[derive(Debug, Subcommand)] -enum Command { - BuildTree { - #[arg(long, value_delimiter = ',')] - versions: Vec, - #[arg(long)] - attestation_dir: Option, - }, - SignRoot { - #[arg(long, value_delimiter = ',')] - versions: Vec, - #[arg(long)] - commit_info_file: PathBuf, - #[arg(long, value_parser = decode_publisher_secret_key_wif)] - publisher_secret_key_wif: SecretKey, - #[arg(long)] - attestation_dir: Option, - }, -} - -fn resolve_attestation_dir(attestation_dir: Option) -> PathBuf { - attestation_dir.unwrap_or_else(part_stark_vk_attestation_dir) -} - -fn normalize_version(version: &str) -> Result { - let raw_version = version.trim(); - if raw_version.is_empty() { - bail!("version cannot be empty"); - } - let normalized = raw_version - .strip_prefix('v') - .or_else(|| raw_version.strip_prefix('V')) - .unwrap_or(raw_version); - Version::parse(normalized).with_context(|| format!("invalid semver version '{version}'")) -} - -/// Sort versions by semantic version while rejecting duplicate normalized versions. -fn order_versions(versions: &[String]) -> Result> { - if versions.is_empty() { - bail!("versions is empty"); - } - - let mut keyed_versions = versions - .iter() - .map(|version| Ok((normalize_version(version)?, version.clone()))) - .collect::>>()?; - keyed_versions.sort_by(|left, right| left.0.cmp(&right.0).then(left.1.cmp(&right.1))); - - let mut ordered_versions = Vec::with_capacity(keyed_versions.len()); - let mut previous: Option = None; - for (normalized, original) in keyed_versions { - if previous.as_ref() == Some(&normalized) { - bail!("duplicate version '{original}'"); - } - previous = Some(normalized); - ordered_versions.push(original); - } - Ok(ordered_versions) -} - -fn load_publisher_set(commit_info_file: &PathBuf) -> Result<(Vec, u16)> { - let bytes = std::fs::read(commit_info_file).with_context(|| { - format!("failed to read commit_info file '{}'", commit_info_file.display()) - })?; - let commit_info: CommitInfo = serde_json::from_slice(&bytes).with_context(|| { - format!("failed to decode commit_info file '{}'", commit_info_file.display()) - })?; - let (encoded_public_keys, threshold) = commit_info.active_publisher_set(); - if encoded_public_keys.is_empty() { - bail!("commit_info publisher_public_keys is empty"); - } - if threshold == 0 { - bail!("commit_info threshold must be greater than 0"); - } - let publisher_public_keys = encoded_public_keys - .iter() - .map(|compressed_pk| { - PublicKey::from_str(compressed_pk) - .with_context(|| format!("invalid publisher public key '{}'", compressed_pk)) - }) - .collect::>>()?; - if usize::from(threshold) > publisher_public_keys.len() { - bail!( - "commit_info threshold {} exceeds publisher_public_keys length {}", - threshold, - publisher_public_keys.len() - ); - } - Ok((publisher_public_keys, threshold)) -} - -fn load_part_stark_vk_for_version(zkm_version: &str) -> Result> { - std::panic::catch_unwind(|| Groth16Verifier::get_part_stark_vk(zkm_version).to_vec()) - .map_err(|_| anyhow::anyhow!("failed to load part_stark_vk for version '{zkm_version}'")) -} - -fn load_part_stark_vks_for_versions(ordered_versions: &[String]) -> Result>> { - ordered_versions.iter().map(|version| load_part_stark_vk_for_version(version)).collect() -} - -fn decode_publisher_secret_key_wif(value: &str) -> Result { - let private_key = - PrivateKey::from_wif(value).map_err(|err| format!("invalid publisher WIF key: {err}"))?; - Ok(private_key.inner) -} - -/// Resolve the signer index by matching the secret key derived public key against the active set. -fn resolve_signer_index( - secret_key: &SecretKey, - publisher_public_keys: &[PublicKey], -) -> Result { - let signer_public_key = PublicKey::from_secret_key(&Secp256k1::new(), secret_key); - publisher_public_keys.iter().position(|public_key| *public_key == signer_public_key).ok_or_else( - || anyhow::anyhow!("publisher_secret_key_wif does not belong to active publisher set"), - ) -} - -fn main() -> Result<()> { - dotenv::dotenv().ok(); - let cli = Cli::parse(); - - match cli.command { - Command::BuildTree { versions, attestation_dir } => { - let attestation_dir = resolve_attestation_dir(attestation_dir); - let ordered_versions = order_versions(&versions)?; - let part_stark_vks = load_part_stark_vks_for_versions(&ordered_versions)?; - save_latest_part_stark_vk_attestation_snapshot( - &attestation_dir, - &ordered_versions, - &part_stark_vks, - None, - None, - None, - vec![], - ) - .map_err(anyhow::Error::msg)?; - println!( - "built latest part_stark_vk snapshot in {} for versions {}", - attestation_dir.display(), - ordered_versions.join(",") - ); - } - Command::SignRoot { - versions, - commit_info_file, - publisher_secret_key_wif, - attestation_dir, - } => { - let attestation_dir = resolve_attestation_dir(attestation_dir); - let ordered_versions = order_versions(&versions)?; - let part_stark_vks = load_part_stark_vks_for_versions(&ordered_versions)?; - let (publisher_public_keys, threshold) = load_publisher_set(&commit_info_file)?; - let signer_pubkey_index = - resolve_signer_index(&publisher_secret_key_wif, &publisher_public_keys)?; - let manifest = sign_latest_part_stark_vk_snapshot( - &attestation_dir, - &ordered_versions, - &part_stark_vks, - &publisher_public_keys, - threshold, - signer_pubkey_index, - &publisher_secret_key_wif, - ) - .map_err(anyhow::Error::msg)?; - println!("{}", serde_json::to_string_pretty(&manifest)?); - } - } - - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - use bitcoin::Network; - use bitcoin::secp256k1::Secp256k1; - - #[test] - fn test_decode_publisher_secret_key_wif_accepts_wif() { - let secret_key = - SecretKey::from_str("0101010101010101010101010101010101010101010101010101010101010101") - .unwrap(); - let private_key = PrivateKey::new(secret_key, Network::Regtest); - - let decoded = decode_publisher_secret_key_wif(&private_key.to_wif()).unwrap(); - - assert_eq!(decoded, secret_key); - } - - #[test] - fn test_order_versions_sorts_semver_with_v_prefix() { - let ordered = - order_versions(&["v1.2.10".to_string(), "v1.2.4".to_string(), "v1.2.5".to_string()]) - .unwrap(); - assert_eq!(ordered, vec!["v1.2.4", "v1.2.5", "v1.2.10"]); - } - - #[test] - fn test_order_versions_rejects_duplicate_versions() { - let err = order_versions(&["v1.2.5".to_string(), "v1.2.5".to_string()]).unwrap_err(); - assert!(err.to_string().contains("duplicate")); - } - - #[test] - fn test_resolve_signer_index_matches_active_publisher_set_order() { - let secp = Secp256k1::new(); - let secret_key_0 = - SecretKey::from_str("0101010101010101010101010101010101010101010101010101010101010101") - .unwrap(); - let secret_key_1 = - SecretKey::from_str("0202020202020202020202020202020202020202020202020202020202020202") - .unwrap(); - let secret_key_2 = - SecretKey::from_str("0303030303030303030303030303030303030303030303030303030303030303") - .unwrap(); - let publisher_public_keys = vec![ - PublicKey::from_secret_key(&secp, &secret_key_2), - PublicKey::from_secret_key(&secp, &secret_key_0), - PublicKey::from_secret_key(&secp, &secret_key_1), - ]; - - let signer_index = resolve_signer_index(&secret_key_0, &publisher_public_keys).unwrap(); - - assert_eq!(signer_index, 1); - } -} diff --git a/node/src/bin/send_bridge_out.rs b/node/src/bin/send_bridge_out.rs index d585c559..583727ab 100644 --- a/node/src/bin/send_bridge_out.rs +++ b/node/src/bin/send_bridge_out.rs @@ -12,7 +12,7 @@ use alloy::eips::BlockNumberOrTag; use alloy::primitives::{Address as EvmAddress, Bytes, U256}; use alloy::providers::{Provider, ProviderBuilder}; use anyhow::{Context, Result, anyhow, bail}; -use bitvm2_noded::env::{ +use bitvm_noded::env::{ ENV_GOAT_PRIVATE_KEY, ENV_GOAT_SWAP_CONTRACT_ADDRESS, get_goat_network, goat_config_from_env, }; use clap::{Parser, Subcommand}; diff --git a/node/src/bin/send_challenge.rs b/node/src/bin/send_challenge.rs index e70b72ca..31abeb4c 100644 --- a/node/src/bin/send_challenge.rs +++ b/node/src/bin/send_challenge.rs @@ -12,13 +12,13 @@ //! - --graph-id: target graph UUID //! //! Example: -//! - cargo run -p bitvm2-noded --bin challenge -- \ +//! - cargo run -p bitvm-noded --bin challenge -- \ //! --rpc-url http://localhost:8080 \ //! --graph-id use anyhow::{Context, Result}; -use bitvm2_noded::env::get_bitvm_key; -use bitvm2_noded::rpc_service::auth::{ +use bitvm_noded::env::get_bitvm_key; +use bitvm_noded::rpc_service::auth::{ AUTH_SIGNATURE_HEADER, AUTH_TIMESTAMP_HEADER, sign_request_auth, }; use clap::Parser; diff --git a/node/src/bin/send_pegin_request.rs b/node/src/bin/send_pegin_request.rs index a8da75e0..354af0d0 100644 --- a/node/src/bin/send_pegin_request.rs +++ b/node/src/bin/send_pegin_request.rs @@ -17,7 +17,7 @@ //! - Most args are optional; use --help for the full list. //! //! Example: -//! - cargo run -p bitvm2-noded --bin pegin-request -- request-prepare +//! - cargo run -p bitvm-noded --bin pegin-request -- request-prepare use alloy::primitives::Address as EvmAddress; use alloy::providers::{Provider, ProviderBuilder}; @@ -26,7 +26,7 @@ use bitcoin::hashes::Hash; use bitcoin::sighash::EcdsaSighashType; use bitcoin::{Address, Amount, key::Keypair}; use bitcoin::{Network, TapSighashType, XOnlyPublicKey}; -use bitvm2_lib::types::Bitvm2InstanceParameters; +use bitvm_lib::types::BitvmGcInstanceParameters; use clap::{Parser, Subcommand}; use client::btc_chain::BTCClient; use client::goat_chain::utils::get_gateway_relay_contracts; @@ -42,11 +42,11 @@ use std::time::Duration; use tokio::time::sleep; use tracing_subscriber::EnvFilter; -use bitvm2_noded::env::{ +use bitvm_noded::env::{ ENV_BITCOIN_NETWORK, ENV_BITVM_SECRET, ENV_GOAT_ADDRESS, get_goat_network, get_node_goat_address, goat_config_from_env, }; -use bitvm2_noded::utils::{ +use bitvm_noded::utils::{ broadcast_tx, get_fee_rate, get_proper_utxo_set, node_p2wsh_address, node_sign, }; @@ -355,8 +355,8 @@ async fn action_prepare( }; let user_keypair = Keypair::from_seckey_str_global(user_btc_secret)?; - let instance_params: Bitvm2InstanceParameters = - bitvm2_noded::utils::read_instance_info_from_goat(goat_client, instance_id).await?; + let instance_params: BitvmGcInstanceParameters = + bitvm_noded::utils::read_instance_info_from_goat(goat_client, instance_id).await?; // Build pegin deposit/confirm/refund transactions let (mut pegin_deposit, _confirm, _refund) = instance_params.build_pegin_tx()?; @@ -392,8 +392,8 @@ async fn action_cancel( let user_keypair = Keypair::from_seckey_str_global(user_btc_secret)?; let user_taproot_public_key = user_keypair.public_key().x_only_public_key().0; - let instance_params: Bitvm2InstanceParameters = - bitvm2_noded::utils::read_instance_info_from_goat(goat_client, instance_id).await?; + let instance_params: BitvmGcInstanceParameters = + bitvm_noded::utils::read_instance_info_from_goat(goat_client, instance_id).await?; let n_of_n_taproot_public_key = XOnlyPublicKey::from(instance_params.committee_agg_pubkey); // Build pegin deposit/confirm/refund transactions and pick the refund (cancel) diff --git a/node/src/bin/send_pegout.rs b/node/src/bin/send_pegout.rs index 67fed93e..77e9270d 100644 --- a/node/src/bin/send_pegout.rs +++ b/node/src/bin/send_pegout.rs @@ -11,12 +11,12 @@ //! - --rpc-url: node API base URL (default: http://localhost:8080) //! //! Example: -//! - cargo run -p bitvm2-noded --bin pegout -- \ +//! - cargo run -p bitvm-noded --bin pegout -- \ //! --rpc-url http://localhost:8080 once --graph-id use anyhow::{Context, Result, bail}; -use bitvm2_noded::env::get_bitvm_key; -use bitvm2_noded::rpc_service::auth::{ +use bitvm_noded::env::get_bitvm_key; +use bitvm_noded::rpc_service::auth::{ AUTH_SIGNATURE_HEADER, AUTH_TIMESTAMP_HEADER, sign_request_auth, }; use clap::{Parser, Subcommand}; diff --git a/node/src/bin/send_rbf.rs b/node/src/bin/send_rbf.rs index 05497dbf..861a37f5 100644 --- a/node/src/bin/send_rbf.rs +++ b/node/src/bin/send_rbf.rs @@ -10,7 +10,7 @@ //! - BITCOIN_NETWORK: bitcoin | testnet | testnet4 | signet | regtest (optional) //! //! Example: -//! - cargo run -p bitvm2-noded --bin send-rbf -- \ +//! - cargo run -p bitvm-noded --bin send-rbf -- \ //! --vin :0 \ //! --vin :1 \ //! --fee-amount 10000 \ @@ -31,8 +31,8 @@ use dotenv::dotenv; use goat::transactions::base::Input; use tracing_subscriber::EnvFilter; -use bitvm2_noded::env::{DUST_AMOUNT, get_bitvm_key, get_network}; -use bitvm2_noded::utils::{broadcast_tx, node_p2wsh_address, node_sign}; +use bitvm_noded::env::{DUST_AMOUNT, get_bitvm_key, get_network}; +use bitvm_noded::utils::{broadcast_tx, node_p2wsh_address, node_sign}; const DEFAULT_RBF_SEQUENCE: u32 = 0xFFFF_FFFD; diff --git a/node/src/bin/sequencer-set-publish.rs b/node/src/bin/sequencer-set-publish.rs index c6fe4f6c..1a8d2caf 100644 --- a/node/src/bin/sequencer-set-publish.rs +++ b/node/src/bin/sequencer-set-publish.rs @@ -11,13 +11,13 @@ use bitcoin::{ Address, Amount, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, absolute::LockTime, hashes::Hash, key::Keypair, transaction::Version, }; -use bitvm2_noded::env::{ +use bitvm_noded::env::{ ENV_GOAT_SEQUENCER_SET_MULTI_SIG_VERIFIER_ADDRESS, ENV_GOAT_SEQUENCER_SET_PUBLISHER_CONTRACT_ADDRESS, get_goat_address_from_env, get_network, }; -use bitvm2_noded::utils::wait_tx_confirmation; -use bitvm2_noded::utils::{broadcast_tx, get_fee_rate}; -use bitvm2_noded::utils::{node_p2wsh_address, node_sign}; +use bitvm_noded::utils::wait_tx_confirmation; +use bitvm_noded::utils::{broadcast_tx, get_fee_rate}; +use bitvm_noded::utils::{node_p2wsh_address, node_sign}; use clap::{Parser, Subcommand}; use client::btc_chain::BTCClient; use client::goat_chain::GOATClient; @@ -84,7 +84,7 @@ struct Args { #[arg(long, env = "OUTPUT_FILE", default_value = "output.data")] output_file: String, - #[arg(long, env = "DB_PATH", default_value = "sqlite:/tmp/bitvm2-node.db")] + #[arg(long, env = "DB_PATH", default_value = "sqlite:/tmp/bitvm-node.db")] db_path: String, } @@ -167,17 +167,25 @@ fn save_output(input: OutputData, output_file: &str) { } /// Query sequencer set hash from local DB. -/// If `goat_block_number` is provided, find the first record at or after that goat block. -/// If not provided, return the latest record. +/// If `goat_block_number` is provided, find the first record at or before that goat block. +/// That is, the sequencer set hash at that goat block. +/// If not provided, query the latest record. +/// For init-genesis, return the requested goat block number as the resolved height. +/// Otherwise, return the goat block number from the matched DB record. async fn get_sequencer_set_hash_from_db( db_path: &str, goat_block_number: Option, + init_genesis: bool, ) -> Result<([u8; 32], u64, u64), Box> { + if init_genesis && goat_block_number.is_none() { + return Err("init_genesis requires an explicit goat_block_number".into()); + } + let local_db = store::create_local_db(db_path).await; let mut storage = local_db.acquire().await?; let record = if let Some(goat_block_number) = goat_block_number { storage - .find_first_sequencer_set_hash_change_by_goat_block_at_or_after(i64::try_from( + .find_first_sequencer_set_hash_change_by_goat_block_at_or_before(i64::try_from( goat_block_number, )?) .await? @@ -188,8 +196,14 @@ async fn get_sequencer_set_hash_from_db( record.ok_or("No validators_hash record found in db. Start the monitor task first")?; let sequencer_set_hash = <[u8; 32]>::from_hex(record.validators_hash.trim_start_matches("0x"))?; - let goat_block_number = u64::try_from(record.goat_block_height)?; + let record_goat_block_number = u64::try_from(record.goat_block_height)?; + let goat_block_number = if init_genesis { + goat_block_number.ok_or("init_genesis requires an explicit goat_block_number")? + } else { + record_goat_block_number + }; let cosmos_block_number = u64::try_from(record.cosmos_block_height)?; + Ok((sequencer_set_hash, goat_block_number, cosmos_block_number)) } @@ -216,6 +230,8 @@ enum Commands { next_publisher_btc_pubkeys: Vec, #[arg(long, env = "GOAT_GENESIS_BLOCK_HASH", value_parser = hex_parse::<32>)] goat_genesis_block_hash: [u8; 32], + #[arg(long, env = "OPERATOR_VK_HASH", value_parser = hex_parse::<32>)] + operator_vk_hash: [u8; 32], }, PushSeq { #[arg(long, env = "OWNER_BTC_KEY_WIF")] @@ -230,6 +246,8 @@ enum Commands { init_genesis: bool, #[arg(long, env = "GOAT_GENESIS_BLOCK_HASH", value_parser = hex_parse::<32>)] goat_genesis_block_hash: [u8; 32], + #[arg(long, env = "OPERATOR_VK_HASH", value_parser = hex_parse::<32>)] + operator_vk_hash: [u8; 32], #[arg(long)] commit_info: String, }, @@ -320,9 +338,10 @@ async fn main() -> Result<(), Box> { publisher_btc_pubkeys, next_publisher_btc_pubkeys, goat_genesis_block_hash, + operator_vk_hash, } => { let (sequencer_set_hash, goat_block_number, cosmos_block_number) = - get_sequencer_set_hash_from_db(&args.db_path, goat_block_number).await?; + get_sequencer_set_hash_from_db(&args.db_path, goat_block_number, false).await?; println!( "resolved cl block number: {cosmos_block_number}, resolved el block number: {goat_block_number}" ); @@ -340,6 +359,7 @@ async fn main() -> Result<(), Box> { update_connector, sequencer_set_hash, goat_genesis_block_hash, + operator_vk_hash, goat_block_number, ) .await @@ -351,11 +371,13 @@ async fn main() -> Result<(), Box> { next_publisher_btc_pubkeys, init_genesis, goat_genesis_block_hash, + operator_vk_hash, commit_info, } => { println!("goat genesis block hash: {:#?}", hex::encode(goat_genesis_block_hash)); let (sequencer_set_hash, goat_block_number, cosmos_block_number) = - get_sequencer_set_hash_from_db(&args.db_path, goat_block_number).await?; + get_sequencer_set_hash_from_db(&args.db_path, goat_block_number, init_genesis) + .await?; let sequencers = fetch_validators(&args.cosmos_rpc_url, cosmos_block_number).await?; let fee_tx = cached_output.fee_tx; @@ -372,6 +394,7 @@ async fn main() -> Result<(), Box> { goat_block_number, sequencer_set_hash, goat_genesis_block_hash, + operator_vk_hash, output_file, ) .await?; @@ -524,6 +547,7 @@ async fn action_push_sequencer_set_update( goat_block_number: u64, sequencer_set_hash: [u8; 32], goat_genesis_block_hash: [u8; 32], + operator_vk_hash: [u8; 32], output_file: &str, ) -> Result<(), Box> { let witnesses = goat_client.ss_get_sequencer_set_update_witness(goat_block_number).await?; @@ -584,9 +608,10 @@ async fn action_push_sequencer_set_update( }; // Skip construction of the genesis tx - let mut commitment = [0u8; 64]; + let mut commitment = [0u8; 96]; commitment[0..32].copy_from_slice(&sequencer_set_hash); - commitment[32..].copy_from_slice(&goat_genesis_block_hash[0..32]); + commitment[32..64].copy_from_slice(&goat_genesis_block_hash); + commitment[64..].copy_from_slice(&operator_vk_hash); let mut sequencer_set_publish_tx = create_sequencer_update_partial_tx( commitment, &update_connector, @@ -632,6 +657,7 @@ async fn action_sign_sequencer_set_update( update_connector: Option, sequencer_set_hash: [u8; 32], goat_genesis_block_hash: [u8; 32], + operator_vk_hash: [u8; 32], goat_block_number: u64, ) -> Result<(), Box> { let total = btc_public_keys.len(); @@ -648,9 +674,10 @@ async fn action_sign_sequencer_set_update( * estimate_tx_vbytes(&[(threshold as u32, total as u32)], &[("p2wsh", 3)], 73) as f64 + RELAYER_FEE as f64; let replenish_fee = Amount::from_sat(replenish_fee.ceil() as u64); - let mut commitment = [0u8; 64]; + let mut commitment = [0u8; 96]; commitment[0..32].copy_from_slice(&sequencer_set_hash); - commitment[32..].copy_from_slice(&goat_genesis_block_hash[0..32]); + commitment[32..64].copy_from_slice(&goat_genesis_block_hash); + commitment[64..].copy_from_slice(&operator_vk_hash); let mut sequencer_set_publish_tx = create_sequencer_update_partial_tx( commitment, diff --git a/node/src/env.rs b/node/src/env.rs index dd2b8877..b923552d 100644 --- a/node/src/env.rs +++ b/node/src/env.rs @@ -6,8 +6,8 @@ use alloy::providers::{Provider, ProviderBuilder}; use alloy::signers::local::PrivateKeySigner; use base64::Engine; use bitcoin::{Network, PublicKey, key::Keypair}; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::keys::NodeMasterKey; +use bitvm_lib::actors::Actor; +use bitvm_lib::keys::NodeMasterKey; use client::goat_chain::utils::{ get_committee_management_contract, get_gateway_relay_contracts, is_validate_committee, }; @@ -16,9 +16,11 @@ use goat::constants::{CONNECTOR_Z_TIMELOCK, NUM_BLOCKS_PER_HOUR}; use libp2p::PeerId; use reqwest::Url; use sha2::{Digest, Sha256}; +use std::path::PathBuf; use std::str::FromStr; use strum::{Display, EnumString}; use tracing::{info, warn}; +use util::hex_parse; use zeroize::Zeroizing; pub const ENV_BTC_CHAIN_URL: &str = "BTC_CHAIN_URL"; @@ -76,8 +78,15 @@ pub const ENV_GOAT_NETWORK: &str = "GOAT_NETWORK"; pub const ENV_WATCHTOWER_PROOF_WAIT_SECS: &str = "WATCHTOWER_PROOF_WAIT_SECS"; pub const ENV_OPERATOR_PROOF_WAIT_SECS: &str = "OPERATOR_PROOF_WAIT_SECS"; +pub const ENV_OPERATOR_VK_HASH: &str = "OPERATOR_VK_HASH"; +pub const ENV_OPERATOR_WRAPPER_VK_HASH: &str = "OPERATOR_WRAPPER_VK_HASH"; +pub const ENV_OPERATOR_WRAPPER_ZKM_VERSION: &str = "OPERATOR_WRAPPER_ZKM_VERSION"; pub const DEFAULT_WATCHTOWER_PROOF_WAIT_SECS: usize = 60; pub const DEFAULT_OPERATOR_PROOF_WAIT_SECS: usize = 60; +pub const ENV_GC_GATES_PATH: &str = "GC_GATES_PATH"; +pub const ENV_GC_INDICES_PATH: &str = "GC_INDICES_PATH"; +pub const ENV_BABE_SETUP_STATE_DIR: &str = "BABE_SETUP_STATE_DIR"; +pub const ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH: &str = "SOLDERING_PROOF_PAYLOAD_STORE_PATH"; pub const ENV_ALWAYS_CHALLENGE: &str = "ALWAYS_CHALLENGE"; pub const ENV_GENESIS_SEQUENCER_COMMIT_TXID: &str = "GENESIS_SEQUENCER_COMMIT_TXID"; @@ -193,8 +202,8 @@ pub fn get_node_pubkey() -> anyhow::Result { } pub fn get_actor() -> Actor { - Actor::from_str(std::env::var(ENV_ACTOR).unwrap_or("Challenger".to_string()).as_str()) - .expect("Expect one of Committee, Challenger, Operator or Relayer") + Actor::from_str(std::env::var(ENV_ACTOR).unwrap_or("Verifier".to_string()).as_str()) + .expect("Expect one of Committee, Verifier, Operator or Relayer") } pub fn get_peer_key() -> String { @@ -260,10 +269,10 @@ pub async fn check_node_info() { panic!("Relayer and Committee must set goat secret key"); } let node_info = get_local_node_info(); - if [Actor::Operator.to_string(), Actor::Challenger.to_string()].contains(&node_info.actor) + if [Actor::Operator.to_string(), Actor::Verifier.to_string()].contains(&node_info.actor) && node_info.goat_addr.is_empty() { - panic!("Operator and Challenger must set goat address or goat secret key"); + panic!("Operator and Verifier must set goat address or goat secret key"); } if Actor::Committee.to_string() == node_info.actor || Actor::Operator.to_string() == node_info.actor @@ -333,24 +342,34 @@ pub fn get_committee_member_num() -> usize { COMMITTEE_MEMBER_NUMBER } -#[derive(Clone, Display, EnumString)] +#[derive(Clone, Copy, Display, EnumString)] pub enum GraphBtcTxName { - #[strum(serialize = "watchtower-challenge-init.hex")] - WatchtowerChallengeInit, - #[strum(serialize = "pre-kickoff.hex")] - PreKickoff, - #[strum(serialize = "assert-init.hex")] - AssertInit, - #[strum(serialize = "challenge.hex")] - Challenge, - #[strum(serialize = "disprove.hex")] - Disprove, - #[strum(serialize = "kickoff.hex")] - Kickoff, + #[strum(serialize = "cur-pre-kickoff.hex")] + CurPreKickoff, + #[strum(serialize = "next-pre-kickoff.hex")] + NextPreKickoff, + #[strum(serialize = "force-skip-kickoff.hex")] + ForceSkipKickoff, + #[strum(serialize = "quick-challenge.hex")] + QuickChallenge, + #[strum(serialize = "challenge-incomplete-kickoff.hex")] + ChallengeIncompleteKickoff, #[strum(serialize = "pegin.hex")] Pegin, + #[strum(serialize = "kickoff.hex")] + Kickoff, #[strum(serialize = "take1.hex")] Take1, + #[strum(serialize = "challenge.hex")] + Challenge, + #[strum(serialize = "watchtower-challenge-init.hex")] + WatchtowerChallengeInit, + #[strum(serialize = "operator-assert.hex")] + OperatorAssert, + #[strum(serialize = "verifier-assert.hex")] + VerifierAssert, + #[strum(serialize = "disprove.hex")] + Disprove, #[strum(serialize = "take2.hex")] Take2, } @@ -430,13 +449,12 @@ pub fn get_goat_swap_event_filter_gap_from_env() -> i64 { pub fn get_goat_gateway_the_graph_urls_from_env() -> String { std::env::var(ENV_GOAT_GATEWAY_EVENT_THE_GRAPH_URL) - .unwrap_or("https://graph.goat.network/subgraphs/name/bitvm2_gateway_dev".to_string()) + .unwrap_or("https://graph.goat.network/subgraphs/name/bitvm_gateway_dev".to_string()) } pub fn get_goat_swap_the_graph_urls_from_env() -> String { - std::env::var(ENV_GOAT_SWAP_EVENT_THE_GRAPH_URL).unwrap_or( - "https://graph.goat.network/subgraphs/name/bitvm2_escrow_manager_dev".to_string(), - ) + std::env::var(ENV_GOAT_SWAP_EVENT_THE_GRAPH_URL) + .unwrap_or("https://graph.goat.network/subgraphs/name/bitvm_escrow_manager_dev".to_string()) } pub async fn goat_config_from_env() -> GoatInitConfig { @@ -503,7 +521,7 @@ pub async fn goat_config_from_env() -> GoatInitConfig { } } -const DEFAULT_PROTO_NAME_BASE: &str = "bitvm2"; +const DEFAULT_PROTO_NAME_BASE: &str = "bitvm"; pub fn get_proto_base() -> String { match std::env::var("PROTO_NAME") { Ok(proto_name) => { @@ -557,6 +575,61 @@ pub fn get_operator_proof_wait_secs() -> usize { .unwrap_or(DEFAULT_OPERATOR_PROOF_WAIT_SECS) } +pub fn get_soldering_proof_payload_store_path() -> anyhow::Result { + let value = std::env::var(ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH) + .map_err(|_| anyhow::anyhow!("{ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH} needs to be set"))?; + let value = value.trim(); + if value.is_empty() { + anyhow::bail!("{ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH} cannot be empty"); + } + Ok(value.to_string()) +} + +pub fn validate_soldering_proof_payload_store_config(actor: &Actor) -> anyhow::Result<()> { + if matches!(actor, Actor::Verifier | Actor::Operator | Actor::All) { + get_soldering_proof_payload_store_path() + .map(|_| ()) + .map_err(|err| anyhow::anyhow!("{err}; required for actor {actor}")) + } else { + Ok(()) + } +} + +pub fn get_operator_vk_hash() -> anyhow::Result<[u8; 32]> { + let value = std::env::var(ENV_OPERATOR_VK_HASH) + .map_err(|_| anyhow::anyhow!("{ENV_OPERATOR_VK_HASH} needs to be set"))?; + hex_parse::<32>(&value).map_err(|err| anyhow::anyhow!("invalid {ENV_OPERATOR_VK_HASH}: {err}")) +} + +pub fn get_operator_wrapper_vk_hash() -> anyhow::Result { + std::env::var(ENV_OPERATOR_WRAPPER_VK_HASH) + .map_err(|_| anyhow::anyhow!("{ENV_OPERATOR_WRAPPER_VK_HASH} needs to be set")) +} + +pub fn get_operator_wrapper_zkm_version() -> anyhow::Result { + std::env::var(ENV_OPERATOR_WRAPPER_ZKM_VERSION) + .map_err(|_| anyhow::anyhow!("{ENV_OPERATOR_WRAPPER_ZKM_VERSION} needs to be set")) +} + +/// Returns the configured GC asset paths after checking that they are readable files. +/// TODO: maybe multi files +pub fn get_babe_gc_asset_paths() -> anyhow::Result<(PathBuf, PathBuf)> { + let gates_path = PathBuf::from( + std::env::var(ENV_GC_GATES_PATH) + .map_err(|_| anyhow::anyhow!("{ENV_GC_GATES_PATH} is missing"))?, + ); + let indices_path = PathBuf::from( + std::env::var(ENV_GC_INDICES_PATH) + .map_err(|_| anyhow::anyhow!("{ENV_GC_INDICES_PATH} is missing"))?, + ); + for (name, path) in [(ENV_GC_GATES_PATH, &gates_path), (ENV_GC_INDICES_PATH, &indices_path)] { + if !path.is_file() { + anyhow::bail!("{name} does not point to a readable file: {}", path.display()); + } + } + Ok((gates_path, indices_path)) +} + pub fn get_instance_maintenance_batch_size() -> u32 { std::env::var(ENV_INSTANCE_MAINTENANCE_BATCH_SIZE) .ok() @@ -606,3 +679,33 @@ pub fn get_genesis_sequencer_commit_id() -> [u8; 32] { let txid = bitcoin::Txid::from_str(&hexed).expect("Invalid genesis sequencer commit txid"); txid.to_byte_array() } + +#[cfg(test)] +mod tests { + use super::*; + use std::sync::Mutex; + + static ENV_LOCK: Mutex<()> = Mutex::new(()); + + #[test] + fn soldering_proof_payload_store_config_required_for_soldering_actors() { + let _guard = ENV_LOCK.lock().unwrap(); + unsafe { std::env::remove_var(ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH) }; + + assert!(validate_soldering_proof_payload_store_config(&Actor::Verifier).is_err()); + assert!(validate_soldering_proof_payload_store_config(&Actor::Operator).is_err()); + assert!(validate_soldering_proof_payload_store_config(&Actor::All).is_err()); + assert!(validate_soldering_proof_payload_store_config(&Actor::Committee).is_ok()); + assert!(validate_soldering_proof_payload_store_config(&Actor::Watchtower).is_ok()); + assert!(validate_soldering_proof_payload_store_config(&Actor::Publisher).is_ok()); + + unsafe { std::env::set_var(ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH, "/tmp/store") }; + assert!(validate_soldering_proof_payload_store_config(&Actor::Verifier).is_ok()); + assert!(validate_soldering_proof_payload_store_config(&Actor::Operator).is_ok()); + + unsafe { std::env::set_var(ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH, " ") }; + assert!(validate_soldering_proof_payload_store_config(&Actor::Verifier).is_err()); + + unsafe { std::env::remove_var(ENV_SOLDERING_PROOF_PAYLOAD_STORE_PATH) }; + } +} diff --git a/node/src/handle.rs b/node/src/handle.rs index 25f644b7..c04c1a8a 100644 --- a/node/src/handle.rs +++ b/node/src/handle.rs @@ -1,30 +1,41 @@ use crate::action::*; use crate::env::{ - COMMITTEE_INSTANCE_KEYS_DIR, get_bitvm_key, get_network, get_node_goat_address, is_relayer, + COMMITTEE_INSTANCE_KEYS_DIR, get_babe_gc_asset_paths, get_bitvm_key, get_node_goat_address, + get_soldering_proof_payload_store_path, is_relayer, }; use crate::error::SpecialError; use crate::middleware::AllBehaviours; use crate::scheduled_tasks::graph_maintenance_tasks::ChallengeSubStatus; +use crate::soldering_payload_store::{ + read_soldering_proof_store_payload, soldering_proof_payload_store_path, +}; use crate::utils::*; use anyhow::{Context, Result, anyhow, bail}; -use bitcoin::hashes::Hash; +use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use bitcoin::{OutPoint, Txid}; use bitcoin::{PublicKey, XOnlyPublicKey}; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::challenger::*; -use bitvm2_lib::committee::*; -use bitvm2_lib::keys::*; -use bitvm2_lib::operator::*; -use bitvm2_lib::types::{Bitvm2Graph, SimplifiedBitvm2Graph}; +use bitvm_lib::actors::Actor; +use bitvm_lib::babe_adapter::{ + BABE_M_CC, BABE_N_CC, BabeAssertWitness, BabeBundleBuilder, BabeChallengeAssertWitness, + BabeProverState, CACSetupPackage, CompactSolderingProofPayload, assert_wots_message, + build_assert_witness, build_real_challenge_assert_witness, build_real_setup_package, + derive_finalized_indices, expand_compact_soldering_proof_payload, extract_gc_circuit_data, + open_real_setup_and_solder, recover_real_wrongly_challenged_witness, verify_real_setup, +}; +use bitvm_lib::committee::*; +use bitvm_lib::keys::*; +use bitvm_lib::operator::*; +use bitvm_lib::types::{BitvmGcCircuitData, BitvmGcGraph, SimplifiedBitvmGcGraph}; +use bitvm_lib::verifier::*; use client::goat_chain::{DisproveTxType, PeginStatus, WithdrawStatus}; use client::http_client::async_client::HttpAsyncClient; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; use goat::connectors::connector_z::ConnectorZ; -use goat::transactions::base::{BaseTransaction, Input}; use goat::transactions::pre_signed::PreSignedTransaction; use goat::transactions::pre_signed_musig2::verify_public_nonce; use libp2p::gossipsub::MessageId; use libp2p::{PeerId, Swarm}; +use std::sync::Arc; use store::GraphStatus; use store::localdb::LocalDB; use uuid::Uuid; @@ -35,6 +46,7 @@ pub struct HandlerContext<'a> { pub btc_client: &'a BTCClient, pub goat_client: &'a GOATClient, pub http_client: &'a HttpAsyncClient, + pub soldering_builder: &'a Option>, pub actor: Actor, pub from_peer_id: PeerId, pub id: MessageId, @@ -158,6 +170,67 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent (GOATMessageContent::ConfirmInstance(ConfirmInstance { instance_id }), _) => { handle_confirm_instance_default(ctx, *instance_id).await } + (GOATMessageContent::InitGraph(InitGraph { instance_id, graph_id }), Actor::Verifier) => { + handle_init_graph_verifier(ctx, *instance_id, *graph_id).await + } + ( + GOATMessageContent::GenCircuits(GenCircuits { + instance_id, + graph_id, + verifier_pubkey, + setup_package, + }), + Actor::Operator, + ) => { + handle_gen_circuits_operator( + ctx, + *instance_id, + *graph_id, + verifier_pubkey, + setup_package, + ) + .await + } + ( + GOATMessageContent::CutCircuits(CutCircuits { + instance_id, + graph_id, + verifier_pubkey, + verifier_index, + selected_circuit_indexes, + }), + Actor::Verifier, + ) => { + handle_cut_circuits_verifier( + ctx, + *instance_id, + *graph_id, + verifier_pubkey, + *verifier_index, + selected_circuit_indexes, + ) + .await + } + ( + GOATMessageContent::SolderingProofReady(SolderingProofReady { + instance_id, + graph_id, + verifier_index, + payload_hash, + total_len, + }), + Actor::Operator, + ) => { + handle_soldering_proof_ready_operator( + ctx, + *instance_id, + *graph_id, + *verifier_index, + *payload_hash, + *total_len, + ) + .await + } ( GOATMessageContent::CreateGraph(CreateGraph { instance_id, graph_id, graph, .. }), Actor::Committee, @@ -167,8 +240,7 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent instance_id, graph_id, committee_pubkey: received_committee_pubkey, - watchtower_num, - assert_commit_num, + verifier_num, pub_nonces, nonce_sigs, }), @@ -179,8 +251,7 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent *instance_id, *graph_id, received_committee_pubkey, - *watchtower_num, - *assert_commit_num, + *verifier_num, pub_nonces, nonce_sigs, content, @@ -192,8 +263,7 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent instance_id, graph_id, committee_pubkey: received_committee_pubkey, - watchtower_num, - assert_commit_num, + verifier_num, pub_nonces, nonce_sigs, }), @@ -204,8 +274,7 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent *instance_id, *graph_id, received_committee_pubkey, - *watchtower_num, - *assert_commit_num, + *verifier_num, pub_nonces, nonce_sigs, ) @@ -343,15 +412,15 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent ) => handle_kickoff_sent_committee(ctx, *instance_id, *graph_id, content).await, ( GOATMessageContent::KickoffSent(KickoffSent { instance_id, graph_id }), - Actor::Challenger, - ) => handle_kickoff_sent_challenger(ctx, *instance_id, *graph_id, content).await, + Actor::Verifier, + ) => handle_kickoff_sent_verifier(ctx, *instance_id, *graph_id, content).await, (GOATMessageContent::KickoffSent(KickoffSent { instance_id, graph_id }), _) => { handle_kickoff_sent_default(ctx, *instance_id, *graph_id, content).await } ( GOATMessageContent::PreKickoffSent(PreKickoffSent { instance_id, graph_id }), - Actor::Challenger, - ) => handle_prekickoff_sent_challenger(ctx, *instance_id, *graph_id, content).await, + Actor::Verifier, + ) => handle_prekickoff_sent_verifier(ctx, *instance_id, *graph_id, content).await, (GOATMessageContent::PreKickoffSent(PreKickoffSent { instance_id, graph_id }), _) => { handle_prekickoff_sent_default(ctx, *instance_id, *graph_id).await } @@ -380,80 +449,63 @@ pub async fn dispatch(ctx: &mut HandlerContext<'_>, content: &GOATMessageContent .await } ( - GOATMessageContent::WatchtowerChallengeSent(WatchtowerChallengeSent { + GOATMessageContent::AssertReady(AssertReady { instance_id, graph_id }), + Actor::Operator, + ) => handle_assert_ready_operator(ctx, *instance_id, *graph_id).await, + ( + GOATMessageContent::AssertSent(AssertSent { instance_id, graph_id, - watchtower_challenge_txids, + assert_txid, + assert_witness, }), - Actor::Operator, + Actor::Verifier, ) => { - handle_watchtower_challenge_sent_operator( - ctx, - *instance_id, - *graph_id, - watchtower_challenge_txids, - content, - ) - .await + handle_assert_sent_verifier(ctx, *instance_id, *graph_id, *assert_txid, assert_witness) + .await } ( - GOATMessageContent::WatchtowerChallengeTimeout(WatchtowerChallengeTimeout { + GOATMessageContent::ChallengeAssertSent(ChallengeAssertSent { instance_id, graph_id, - watchtower_indexes, + challenge_assert_txid, + verifier_index, + challenge_witness, + .. }), Actor::Operator, ) => { - handle_watchtower_challenge_timeout_operator( + handle_challenge_assert_sent_operator( ctx, *instance_id, *graph_id, - watchtower_indexes, + *challenge_assert_txid, + *verifier_index, + challenge_witness, content, ) .await } ( - GOATMessageContent::OperatorAckTimeout(OperatorAckTimeout { instance_id, graph_id }), - Actor::Challenger, - ) => handle_operator_ack_timeout_challenger(ctx, *instance_id, *graph_id, content).await, - ( - GOATMessageContent::OperatorCommitBlockHashReady(OperatorCommitBlockHashReady { - instance_id, - graph_id, - }), - Actor::Operator, - ) => { - handle_operator_commit_blockhash_ready_operator(ctx, *instance_id, *graph_id, content) - .await - } - ( - GOATMessageContent::OperatorCommitBlockHashTimeout(OperatorCommitBlockHashTimeout { + GOATMessageContent::WronglyChallengeTimeout(WronglyChallengeTimeout { instance_id, graph_id, + challenge_assert_txid, + verifier_index, + .. }), - Actor::Challenger, + Actor::Verifier, ) => { - handle_operator_commit_blockhash_timeout_challenger( + handle_wrongly_challenge_timeout_verifier( ctx, *instance_id, *graph_id, + *challenge_assert_txid, + *verifier_index, content, ) .await } - ( - GOATMessageContent::AssertInitReady(AssertInitReady { instance_id, graph_id }), - Actor::Operator, - ) => handle_assert_init_ready_operator(ctx, *instance_id, *graph_id, content).await, - ( - GOATMessageContent::AssertCommitTimeout(AssertCommitTimeout { instance_id, graph_id }), - Actor::Challenger, - ) => handle_assert_commit_timeout_challenger(ctx, *instance_id, *graph_id, content).await, - ( - GOATMessageContent::DisproveReady(DisproveReady { instance_id, graph_id }), - Actor::Challenger, - ) => handle_disprove_ready_challenger(ctx, *instance_id, *graph_id, content).await, ( GOATMessageContent::DisproveSent(DisproveSent { instance_id, @@ -518,6 +570,170 @@ fn make_message(ctx: &HandlerContext<'_>, content: &GOATMessageContent) -> GOATM GOATMessage::new(ctx.actor.clone(), content.clone()) } +/// Freezes the first accepted Verifiers and assigns deterministic graph slots. +fn freeze_operator_candidates(state: &mut OperatorBabeSetupState) -> Result<()> { + if state.frozen_verifier_pubkeys.is_some() { + return Ok(()); + } + if state.candidates.len() < todo_funcs::verifier_num() { + bail!( + "cannot freeze {} verifier candidates before reaching target {}", + state.candidates.len(), + todo_funcs::verifier_num() + ); + } + state.candidates.truncate(todo_funcs::verifier_num()); + state.candidates.sort_by_key(|candidate| candidate.verifier_pubkey.to_bytes()); + for (verifier_index, candidate) in state.candidates.iter_mut().enumerate() { + candidate.verifier_index = Some(verifier_index); + candidate.selected_circuit_indexes = + derive_finalized_indices(&candidate.setup_package, BABE_M_CC)?; + } + state.frozen_verifier_pubkeys = + Some(state.candidates.iter().map(|candidate| candidate.verifier_pubkey).collect()); + Ok(()) +} + +/// Stores one selected Verifier slot and emits ordered graph data when complete. +fn record_candidate_gc_data( + state: &mut OperatorBabeSetupState, + verifier_pubkey: PublicKey, + verifier_index: usize, + setup_package: &CACSetupPackage, + gc_data: BitvmGcCircuitData, + prover_state: BabeProverState, +) -> Result>> { + let frozen = state + .frozen_verifier_pubkeys + .as_ref() + .ok_or_else(|| anyhow!("operator verifier membership is not frozen"))?; + let expected_pubkey = frozen + .get(verifier_index) + .ok_or_else(|| anyhow!("verifier index {verifier_index} out of frozen slot range"))?; + if expected_pubkey != &verifier_pubkey { + bail!("verifier public key does not own slot {verifier_index}"); + } + if gc_data.verifier_pubkey != verifier_pubkey { + bail!("GC slot owner does not match soldering proof verifier"); + } + let candidate = state + .candidates + .iter_mut() + .find(|candidate| candidate.verifier_pubkey == verifier_pubkey) + .ok_or_else(|| anyhow!("selected verifier candidate is missing"))?; + if candidate.verifier_index != Some(verifier_index) { + bail!("candidate verifier index does not match soldering proof slot"); + } + if candidate.setup_package != *setup_package { + bail!("soldering proof setup package does not match selected verifier candidate"); + } + if prover_state.package != *setup_package { + bail!("BABE prover state setup package does not match selected verifier candidate"); + } + if candidate.selected_circuit_indexes != prover_state.soldering.finalized_indices { + bail!("BABE prover state finalized indices do not match selected verifier cut"); + } + if prover_state.finalized.len() != BABE_M_CC || prover_state.h_msgs.len() != BABE_M_CC { + bail!("BABE prover state must contain exactly {BABE_M_CC} finalized instances and hashes"); + } + if prover_state.h_msgs != gc_data.final_msg_hashlocks { + bail!("BABE prover state hashes do not match GC slot hashes"); + } + if let Some(existing) = &candidate.gc_data + && existing != &gc_data + { + bail!("conflicting GC slot received for selected verifier"); + } + if let Some(existing) = &candidate.prover_state + && existing != &prover_state + { + bail!("conflicting BABE prover state received for selected verifier"); + } + if candidate.gc_data.is_none() { + candidate.gc_data = Some(gc_data); + } + if candidate.prover_state.is_none() { + candidate.prover_state = Some(prover_state); + } + if state.candidates.iter().any(|candidate| candidate.gc_data.is_none()) { + return Ok(None); + } + Ok(Some(state.candidates.iter().map(|candidate| candidate.gc_data.clone().unwrap()).collect())) +} + +fn validate_verifier_slot_lengths( + verifier_index: usize, + gc_data_len: usize, + verifier_asserts_len: usize, + disproves_len: usize, +) -> Result<()> { + if gc_data_len == 0 { + bail!("graph has no verifier GC slots"); + } + if gc_data_len != verifier_asserts_len || gc_data_len != disproves_len { + bail!( + "graph verifier branch lengths differ: gc_data={gc_data_len}, verifier_asserts={verifier_asserts_len}, disproves={disproves_len}" + ); + } + if verifier_index >= gc_data_len { + bail!("verifier index {verifier_index} out of range for {gc_data_len} slots"); + } + Ok(()) +} + +fn validate_verifier_slot(graph: &BitvmGcGraph, verifier_index: usize) -> Result<()> { + validate_verifier_slot_lengths( + verifier_index, + graph.parameters.gc_data.len(), + graph.verifier_asserts.len(), + graph.disproves.len(), + ) +} + +fn find_verifier_index_by_pubkey( + gc_data: &[BitvmGcCircuitData], + verifier_pubkey: &PublicKey, +) -> Result> { + let matches = gc_data + .iter() + .enumerate() + .filter_map(|(index, data)| (data.verifier_pubkey == *verifier_pubkey).then_some(index)) + .collect::>(); + match matches.as_slice() { + [] => Ok(None), + [index] => Ok(Some(*index)), + _ => bail!("graph contains duplicate slots for verifier {verifier_pubkey}"), + } +} + +fn validate_challenge_witness_index( + verifier_index: usize, + challenge_witness: &BabeChallengeAssertWitness, +) -> Result<()> { + if challenge_witness.verifier_index != verifier_index { + bail!( + "challenge witness verifier index {} does not match message verifier index {verifier_index}", + challenge_witness.verifier_index + ); + } + Ok(()) +} + +fn validate_expected_challenge_assert_txid( + graph: &BitvmGcGraph, + verifier_index: usize, + challenge_assert_txid: Txid, +) -> Result<()> { + validate_verifier_slot(graph, verifier_index)?; + let expected_txid = graph.verifier_asserts[verifier_index].tx().compute_txid(); + if challenge_assert_txid != expected_txid { + bail!( + "challenge assert txid {challenge_assert_txid} does not match graph verifier assert txid {expected_txid}" + ); + } + Ok(()) +} + fn should_ignore_invalid_pegin_request(e: &anyhow::Error, instance_id: Uuid) -> bool { if let Some(SpecialError::InvalidPeginRequest(err_msg)) = e.downcast_ref::() { tracing::warn!("Ignore PeginRequest for {instance_id}: {err_msg}"); @@ -648,11 +864,11 @@ async fn refresh_and_compensate( ctx: &HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - graph: Option<&Bitvm2Graph>, + graph: Option<&BitvmGcGraph>, scan_from_status: Option, compensate_from_status: GraphStatus, ) -> Result<(GraphStatus, Option)> { - let (graph_status, sub_status) = refresh_graph( + let (graph_status, sub_status, scan) = refresh_graph( ctx.local_db, ctx.btc_client, ctx.goat_client, @@ -670,10 +886,10 @@ async fn refresh_and_compensate( instance_id, graph_id, graph, + scan.as_ref(), scan_from_status, compensate_from_status, graph_status, - sub_status, ) .await?; Ok((graph_status, sub_status)) @@ -683,11 +899,11 @@ async fn get_graph_and_status( ctx: &HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, -) -> Result<(Bitvm2Graph, GraphStatus)> { +) -> Result<(BitvmGcGraph, GraphStatus)> { let graph = get_graph(ctx.local_db, instance_id, graph_id) .await? .ok_or_else(|| anyhow!("Graph not found for {instance_id}:{graph_id}"))?; - let graph = Bitvm2Graph::from_simplified(&graph)?; + let graph = BitvmGcGraph::from_simplified(&graph)?; let graph_start_status = get_graph_status(ctx.local_db, instance_id, graph_id) .await? .ok_or_else(|| anyhow!("Graph status not found for {instance_id}:{graph_id}"))?; @@ -699,7 +915,7 @@ async fn get_graph_and_status_or_defer( instance_id: Uuid, graph_id: Uuid, message: &GOATMessage, -) -> Result> { +) -> Result> { let graph = match get_graph_or_defer( ctx.swarm, ctx.local_db, @@ -713,7 +929,7 @@ async fn get_graph_and_status_or_defer( Some(g) => g, None => return Ok(None), }; - let graph = Bitvm2Graph::from_simplified(&graph)?; + let graph = BitvmGcGraph::from_simplified(&graph)?; let graph_start_status = get_graph_status(ctx.local_db, instance_id, graph_id) .await? .ok_or_else(|| anyhow!("Graph status not found for {instance_id}:{graph_id}"))?; @@ -726,7 +942,7 @@ async fn refresh_graph_status( graph_id: Uuid, message: Option<&GOATMessage>, compensate_from_status: GraphStatus, -) -> Result)>> { +) -> Result)>> { let (graph, graph_start_status) = match message { Some(message) => { match get_graph_and_status_or_defer(ctx, instance_id, graph_id, message).await? { @@ -857,68 +1073,25 @@ async fn handle_confirm_instance_operator( send_to_peer(ctx.swarm, msg).await?; return Ok(()); } - // 1. read & check parameters - let instance_params = match read_instance_info_from_goat(ctx.goat_client, instance_id).await { - Ok(v) => v, - Err(e) => { - if should_ignore_invalid_pegin_data(&e, instance_id) { - return Ok(()); - } - bail!(e) - } + + let pending_graph_id = { + let mut storage = ctx.local_db.acquire().await?; + storage + .find_pending_graph_init_by_instance_and_operator_pubkey( + &instance_id, + &local_operator_pubkey.to_string(), + ) + .await? + .map(|pending| pending.graph_id) }; - let pegin_deposit_txid = instance_params.build_pegin_tx()?.0.tx().compute_txid(); - if !tx_on_chain(ctx.btc_client, &pegin_deposit_txid).await? { - tracing::warn!( - "Ignore ConfirmInstance for {instance_id}: pegin deposit tx {pegin_deposit_txid} not found on chain" - ); - bail!("Invalid ConfirmInstance: pegin deposit tx {pegin_deposit_txid} not found on chain"); + if let Some(graph_id) = pending_graph_id { + tracing::info!("Resume pending graph setup for {instance_id}, graph_id: {graph_id}"); + let message_content = GOATMessageContent::InitGraph(InitGraph { instance_id, graph_id }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Verifier, message_content)).await?; + + return Ok(()); } - // 2. save the instance data to local db - store_instance_parameters(ctx.local_db, &instance_params).await?; - // 3. create & presign graph - let (graph_nonce, cur_prekickoff_tx) = - match get_current_prekickoff_tx(ctx.local_db, &local_operator_pubkey).await? { - Some(v) => v, - None => { - let genesis_prekickoff_tx = - build_genesis_prekickoff_tx(ctx.btc_client, ctx.goat_client).await?; - (0, genesis_prekickoff_tx) - } - }; - let prekickoff_params = - build_prekickoff_params(ctx.btc_client, graph_nonce, cur_prekickoff_tx).await?; - let graph_params = build_graph_params( - ctx.local_db, - ctx.goat_client, - instance_params, - prekickoff_params, - graph_nonce, - Uuid::new_v4(), - ) - .await?; - let graph_id = graph_params.graph_id; - let disprove_scripts = get_disprove_scripts(&graph_params).await?; - let mut graph = generate_bitvm_graph(graph_params, disprove_scripts)?; - operator_pre_sign(operator_master_key.master_keypair(), &mut graph)?; - store_graph(ctx.local_db, &graph.to_simplified()?).await?; - // 4. broadcast CreateGraph - let message_content = GOATMessageContent::CreateGraph(CreateGraph { - instance_id, - graph_id, - graph_nonce, - graph: graph.to_simplified()?, - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; - Ok(()) -} -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] -async fn handle_confirm_instance_default( - ctx: &mut HandlerContext<'_>, - instance_id: Uuid, -) -> Result<()> { - // triggered by PeginDeposit tx // 1. read & check parameters let instance_params = match read_instance_info_from_goat(ctx.goat_client, instance_id).await { Ok(v) => v, @@ -934,1722 +1107,1934 @@ async fn handle_confirm_instance_default( tracing::warn!( "Ignore ConfirmInstance for {instance_id}: pegin deposit tx {pegin_deposit_txid} not found on chain" ); - return Ok(()); + bail!("Invalid ConfirmInstance: pegin deposit tx {pegin_deposit_txid} not found on chain"); } + // after PeginPrepare is confirmed, broadcast InitGraph and let Verifiers generate GC. + // 2. save the instance data to local db store_instance_parameters(ctx.local_db, &instance_params).await?; + let graph_id = Uuid::new_v4(); + let mut storage = ctx.local_db.acquire().await?; + storage + .upsert_pending_graph_init(&instance_id, &local_operator_pubkey.to_string(), &graph_id) + .await?; + let message_content = GOATMessageContent::InitGraph(InitGraph { instance_id, graph_id }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Verifier, message_content)).await?; + Ok(()) } +// generate garbled circuits and broadcast GenCircuits. #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_create_graph_committee( +async fn handle_init_graph_verifier( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - graph: &SimplifiedBitvm2Graph, ) -> Result<()> { - // received from Operator - // 1. check graph data & operator stake - if let Err(e) = - todo_funcs::validate_init_graph(ctx.local_db, ctx.btc_client, ctx.goat_client, graph).await - { - if should_ignore_invalid_graph(&e, instance_id, graph_id, "CreateGraph", None) { - return Ok(()); + let verifier_master_key = VerifierMasterKey::new(get_bitvm_key()?); + let verifier_pubkey = verifier_master_key.master_keypair().public_key().into(); + + let saved_verifier_state = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .and_then(|state| state.verifier) + .filter(|state| state.verifier_pubkey == verifier_pubkey); + let verifier_state = if let Some(saved) = saved_verifier_state { + saved + } else { + get_babe_gc_asset_paths()?; + let vk = crate::vk::get_vk().await.context("load Groth16 verifying key for BABE setup")?; + let public_inputs = derive_operator_wrapper_statement(graph_id)?.public_inputs; + let (setup_package, private_state) = tokio::task::spawn_blocking(move || { + build_real_setup_package(BABE_N_CC, &vk, &public_inputs) + }) + .await + .context("real BABE setup task failed")??; + tracing::info!("Verifier setup done."); + VerifierBabeSetupState { + verifier_pubkey, + setup_package, + private_state, + verifier_index: None, + finalized_indices: vec![], + opened: vec![], + finalized: vec![], + soldering: None, } - bail!(e) }; - // 2. save the graph data to local db - store_graph(ctx.local_db, graph).await?; - // 3. generate Musig2 nonces & broadcast NonceGeneration - let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); - let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; - let (pub_nonces, _, nonce_sigs) = committee_master_key.nonces_for_graph_with_keypair( - instance_id, - graph_id, - graph.parameters.watchtower_pubkeys.len(), - graph.assert_commit_num, - instance_keypair, - ); - let local_committee_pubkey = instance_keypair.public_key().into(); - let message_content = GOATMessageContent::NonceGeneration(NonceGeneration { + + let setup_package = verifier_state.setup_package.clone(); + update_babe_setup_state(ctx.local_db, instance_id, graph_id, |state| { + state.verifier = Some(verifier_state); + })?; + + let message_content = GOATMessageContent::GenCircuits(GenCircuits { instance_id, graph_id, - committee_pubkey: local_committee_pubkey, - watchtower_num: graph.parameters.watchtower_pubkeys.len(), - assert_commit_num: graph.assert_commit_num, - pub_nonces: pub_nonces.clone(), - nonce_sigs, + verifier_pubkey, + setup_package, }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; - store_committee_pub_nonces_for_graph( - ctx.local_db, - instance_id, - graph_id, - local_committee_pubkey, - pub_nonces, - ) - .await?; - // 4. if collected enough pub_nonces, generate partial signatures & broadcast CommitteePresign - let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; - let pub_nonces_unchecked = - get_committee_pub_nonces_for_graph(ctx.local_db, instance_id, graph_id).await?; - if pub_nonces_unchecked.len() == committee_pubkeys.len() { - let mut graph = Bitvm2Graph::from_simplified(graph)?; - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = graph.assert_commit_timeout_txns.len(); - let mut pub_nonces = Vec::with_capacity(pub_nonces_unchecked.len()); - for (pk, pn) in pub_nonces_unchecked.into_iter() { - if let Err(e) = pn.validate_length(watchtower_num, assert_commit_num) { - tracing::warn!("PubNonces from {} has invalid length: {e}", pk.to_string()); - return Ok(()); - } - pub_nonces.push(pn); - } - let agg_nonces = nonces_aggregation(&pub_nonces)?; - let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); - let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; - let (_, sec_nonces, _) = committee_master_key.nonces_for_graph_with_keypair( - instance_id, - graph_id, - watchtower_num, - assert_commit_num, - instance_keypair, - ); - let committee_partial_sigs = - committee_pre_sign(instance_keypair, sec_nonces, agg_nonces.clone(), &mut graph)?; - let message_content = GOATMessageContent::CommitteePresign(CommitteePresign { - instance_id, - graph_id, - committee_pubkey: local_committee_pubkey, - committee_partial_sigs, - agg_nonces, - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; - } + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Operator, message_content)).await?; + Ok(()) } -#[allow(clippy::too_many_arguments)] +// select a subset of GC and broadcast CutCircuits. #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_nonce_generation_committee( +async fn handle_gen_circuits_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - received_committee_pubkey: &PublicKey, - watchtower_num: usize, - assert_commit_num: usize, - pub_nonces: &CommitteePubNonces, - nonce_sigs: &CommitteeNonceSignatures, - content: &GOATMessageContent, + verifier_pubkey: &PublicKey, + setup_package: &CACSetupPackage, ) -> Result<()> { - // received from Committee members - if !ensure_self_or_valid_committee( - ctx, + let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); + let local_operator_pubkey = operator_master_key.master_keypair().public_key().into(); + if !pending_graph_belongs_to_operator( + ctx.local_db, instance_id, - Some(graph_id), - received_committee_pubkey, - "NonceGeneration", + graph_id, + &local_operator_pubkey, ) .await? { - return Ok(()); - } - // 1. check pub_nonces & nonce signatures - let committee_xonly_pubkey = XOnlyPublicKey::from(*received_committee_pubkey); - if !verify_nonce_signatures( - &committee_xonly_pubkey, - pub_nonces, - nonce_sigs, - watchtower_num, - assert_commit_num, - )? { - tracing::warn!( - "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: invalid pub_nonces or nonce_sigs", - received_committee_pubkey.to_string() + tracing::debug!( + "Ignore GenCircuits for {instance_id}:{graph_id}: no local pending graph session" ); return Ok(()); } - // TODO: deal with the case that one committee member sends different pub_nonces for the same graph - // 2. save the pub_nonces to local db - store_committee_pub_nonces_for_graph( - ctx.local_db, - instance_id, - graph_id, - *received_committee_pubkey, - pub_nonces.clone(), - ) - .await?; - // 3. if received enough pub_nonces, generate partial signatures & broadcast CommitteePresign - let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; - let pub_nonces_unchecked = - get_committee_pub_nonces_for_graph(ctx.local_db, instance_id, graph_id).await?; - if pub_nonces_unchecked.len() == committee_pubkeys.len() { - let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); - let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; - let local_committee_pubkey = instance_keypair.public_key().into(); - let message = make_message(ctx, content); - let graph = match get_graph_or_defer( - ctx.swarm, - ctx.local_db, - ctx.goat_client, - instance_id, - graph_id, - &message, - ) - .await? - { - Some(g) => g, - None => return Ok(()), - }; - let mut graph = Bitvm2Graph::from_simplified(&graph)?; - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = graph.assert_commit_timeout_txns.len(); - let mut pub_nonces = Vec::with_capacity(pub_nonces_unchecked.len()); - for (pk, pn) in pub_nonces_unchecked.into_iter() { - if let Err(e) = pn.validate_length(watchtower_num, assert_commit_num) { - tracing::warn!("PubNonces from {} has invalid length: {e}", pk.to_string()); - return Ok(()); - } - pub_nonces.push(pn); + + if setup_package.commits.is_empty() { + bail!("GenCircuits setup package has no commitments"); + } + + let mut state = load_babe_setup_state(ctx.local_db, instance_id, graph_id)?.unwrap_or_default(); + let operator_state = state.operator.get_or_insert_with(|| OperatorBabeSetupState { + frozen_verifier_pubkeys: None, + candidates: vec![], + asserted_wrapper_proof: None, + }); + + let was_frozen = operator_state.frozen_verifier_pubkeys.is_some(); + if let Some(existing) = operator_state + .candidates + .iter() + .find(|candidate| candidate.verifier_pubkey == *verifier_pubkey) + { + if existing.setup_package != *setup_package { + bail!("conflicting GenCircuits setup package for verifier {verifier_pubkey}"); } - let agg_nonces = nonces_aggregation(&pub_nonces)?; - let (_, sec_nonces, _) = committee_master_key.nonces_for_graph_with_keypair( - instance_id, - graph_id, - watchtower_num, - assert_commit_num, - instance_keypair, + } else if was_frozen { + tracing::debug!( + "Ignore GenCircuits for {instance_id}:{graph_id}: verifier membership is frozen" ); - // 4. if received enough valid committee partial sigs, endorse the graph - let committee_partial_sigs = - committee_pre_sign(instance_keypair, sec_nonces, agg_nonces.clone(), &mut graph)?; - let message_content = GOATMessageContent::CommitteePresign(CommitteePresign { - instance_id, - graph_id, - committee_pubkey: local_committee_pubkey, - committee_partial_sigs: committee_partial_sigs.clone(), - agg_nonces, + + return Ok(()); + } else { + operator_state.candidates.push(OperatorVerifierCandidate { + verifier_pubkey: *verifier_pubkey, + setup_package: setup_package.clone(), + verifier_index: None, + selected_circuit_indexes: vec![], + gc_data: None, + prover_state: None, }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; - store_committee_partial_sigs_for_graph( - ctx.local_db, + } + + if operator_state.frozen_verifier_pubkeys.is_none() + && operator_state.candidates.len() < todo_funcs::verifier_num() + { + save_babe_setup_state(ctx.local_db, instance_id, graph_id, &state)?; + + return Ok(()); + } + + freeze_operator_candidates(operator_state)?; + let cut_candidates = if was_frozen { + operator_state + .candidates + .iter() + .filter(|candidate| candidate.verifier_pubkey == *verifier_pubkey) + .cloned() + .collect::>() + } else { + operator_state.candidates.clone() + }; + + save_babe_setup_state(ctx.local_db, instance_id, graph_id, &state)?; + for candidate in cut_candidates { + let message_content = GOATMessageContent::CutCircuits(CutCircuits { instance_id, graph_id, - local_committee_pubkey, - committee_partial_sigs, - ) - .await?; - let committee_partial_sigs = - get_committee_partial_sigs_for_graph(ctx.local_db, instance_id, graph_id) - .await? - .into_iter() - .map(|(_, ps)| ps) - .collect::>(); - if committee_partial_sigs.len() == committee_pubkeys.len() { - let committee_sig_for_graph = endorse_graph(ctx.goat_client, &graph).await?; - let committee_evm_address = get_node_goat_address() - .ok_or_else(|| anyhow::anyhow!("failed to get node goat address".to_string()))?; - let message_content = GOATMessageContent::EndorseGraph(EndorseGraph { - instance_id, - graph_id, - committee_pubkey: local_committee_pubkey, - committee_sig_for_graph: committee_sig_for_graph.as_bytes().to_vec(), - committee_evm_address, - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; - } + verifier_pubkey: candidate.verifier_pubkey, + verifier_index: candidate.verifier_index.unwrap(), + selected_circuit_indexes: candidate.selected_circuit_indexes, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Verifier, message_content)).await?; } + Ok(()) } -#[allow(clippy::too_many_arguments)] +// generate proofs for the chosen GC and broadcast SolderingProof. #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_nonce_generation_operator( +async fn handle_cut_circuits_verifier( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - received_committee_pubkey: &PublicKey, - watchtower_num: usize, - assert_commit_num: usize, - pub_nonces: &CommitteePubNonces, - nonce_sigs: &CommitteeNonceSignatures, + verifier_pubkey: &PublicKey, + verifier_index: usize, + selected_circuit_indexes: &Vec, ) -> Result<()> { - // received from Committee members - if !ensure_self_or_valid_committee( - ctx, - instance_id, - Some(graph_id), - received_committee_pubkey, - "NonceGeneration", - ) - .await? - { + let verifier_master_key = VerifierMasterKey::new(get_bitvm_key()?); + let local_verifier_pubkey: PublicKey = verifier_master_key.master_keypair().public_key().into(); + if &local_verifier_pubkey != verifier_pubkey { + tracing::debug!( + "Ignore CutCircuits for {instance_id}:{graph_id}: target verifier pubkey does not match local verifier" + ); + return Ok(()); } - // 1. check pub_nonces & nonce signatures - let committee_xonly_pubkey = XOnlyPublicKey::from(*received_committee_pubkey); - if !verify_nonce_signatures( - &committee_xonly_pubkey, - pub_nonces, - nonce_sigs, - watchtower_num, - assert_commit_num, - )? { + + let Some(mut verifier_state) = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .and_then(|state| state.verifier) + else { tracing::warn!( - "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: invalid pub_nonces or nonce_sigs", - received_committee_pubkey.to_string() + "Ignore CutCircuits for {instance_id}:{graph_id}: missing BABE verifier setup state" ); return Ok(()); - } - let graph = match get_graph(ctx.local_db, instance_id, graph_id).await? { - Some(g) => g, - None => { - tracing::warn!( - "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: graph not found, maybe belongs to another Operator", - received_committee_pubkey.to_string() - ); - return Ok(()); - } }; - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let assert_commit_num = graph.assert_commit_num; - if let Err(e) = pub_nonces.validate_length(watchtower_num, assert_commit_num) { + + if verifier_state.verifier_pubkey != *verifier_pubkey { tracing::warn!( - "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: invalid pub_nonces length: {e}", - received_committee_pubkey.to_string() + "Ignore CutCircuits for {instance_id}:{graph_id}: verifier pubkey does not match saved BABE setup state" ); + return Ok(()); } - // TODO: deal with the case that one committee member sends different pub_nonces for the same graph - // 2. save the pub_nonces to local db - store_committee_pub_nonces_for_graph( - ctx.local_db, - instance_id, - graph_id, - *received_committee_pubkey, - pub_nonces.clone(), - ) - .await?; - // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local db, broadcast GraphFinalize - // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order - // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them - try_finalize_graph( + + if let Some(saved_index) = verifier_state.verifier_index { + if saved_index != verifier_index { + bail!( + "CutCircuits verifier index {verifier_index} conflicts with persisted slot {saved_index}" + ); + } + if verifier_state.finalized_indices != *selected_circuit_indexes { + bail!("CutCircuits finalized indices conflict with persisted selection"); + } + if let Some(soldering) = verifier_state.soldering.clone() { + let setup_state = verifier_state; + send_soldering_proof_to_operator( + ctx.swarm, + instance_id, + graph_id, + verifier_index, + &setup_state.opened, + &setup_state.finalized, + &soldering, + ) + .await?; + + return Ok(()); + } + } + + let setup_package = verifier_state.setup_package.clone(); + get_babe_gc_asset_paths()?; + + let vk = crate::vk::get_vk().await.context("load Groth16 verifying key for BABE opening")?; + let public_inputs = derive_operator_wrapper_statement(graph_id)?.public_inputs; + let private_state = verifier_state.private_state.clone(); + let selected_indices = selected_circuit_indexes.clone(); + let package_for_opening = setup_package.clone(); + let soldering_builder = Arc::clone( + ctx.soldering_builder + .as_ref() + .context("BABE soldering builder is not initialized for Verifier")?, + ); + let (opened, finalized, soldering) = tokio::task::spawn_blocking(move || { + open_real_setup_and_solder( + &soldering_builder, + &private_state, + &package_for_opening, + &selected_indices, + &vk, + &public_inputs, + ) + }) + .await + .context("real BABE opening task failed")??; + + verifier_state.verifier_index = Some(verifier_index); + verifier_state.finalized_indices = selected_circuit_indexes.clone(); + verifier_state.opened = opened.clone(); + verifier_state.finalized = finalized.clone(); + verifier_state.soldering = Some(soldering.clone()); + + update_babe_setup_state(ctx.local_db, instance_id, graph_id, |state| { + state.verifier = Some(verifier_state); + })?; + + send_soldering_proof_to_operator( ctx.swarm, - ctx.local_db, - ctx.goat_client, instance_id, graph_id, - Some(&graph), - true, + verifier_index, + &opened, + &finalized, + &soldering, ) .await?; + Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_committee_presign_committee( +#[allow(clippy::too_many_arguments)] +async fn handle_soldering_proof_ready_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - received_committee_pubkey: &PublicKey, - committee_partial_sigs: &CommitteePartialSignatures, - _agg_nonces: &CommitteeAggNonces, - content: &GOATMessageContent, + verifier_index: usize, + payload_hash: [u8; 32], + total_len: usize, ) -> Result<()> { - // received from Committee members - if !ensure_self_or_valid_committee( - ctx, + if total_len == 0 { + bail!("SolderingProofReady total_len must be greater than zero"); + } + let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); + let local_operator_pubkey = operator_master_key.master_keypair().public_key().into(); + if !pending_graph_belongs_to_operator( + ctx.local_db, instance_id, - Some(graph_id), - received_committee_pubkey, - "CommitteePresign", + graph_id, + &local_operator_pubkey, ) .await? { + tracing::debug!( + "Ignore SolderingProofReady for {instance_id}:{graph_id}: no local pending graph session" + ); + return Ok(()); } - // 1. save the committee partial sigs to local db - // TODO: validate the partial sigs - store_committee_partial_sigs_for_graph( - ctx.local_db, + + let state = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .ok_or_else(|| anyhow!("missing BABE setup state for pending graph {graph_id}"))?; + let operator_state = state + .operator + .as_ref() + .ok_or_else(|| anyhow!("missing operator BABE setup state for pending graph {graph_id}"))?; + let frozen = operator_state + .frozen_verifier_pubkeys + .as_ref() + .ok_or_else(|| anyhow!("operator verifier membership is not frozen"))?; + let verifier_pubkey = frozen.get(verifier_index).ok_or_else(|| { + anyhow!("SolderingProofReady verifier index {verifier_index} out of range") + })?; + let candidate = operator_state + .candidates + .iter() + .find(|candidate| candidate.verifier_pubkey == *verifier_pubkey) + .ok_or_else(|| anyhow!("selected verifier candidate is missing"))?; + if candidate.verifier_index != Some(verifier_index) { + bail!("selected verifier candidate index does not match SolderingProofReady slot"); + } + + let store_base_path = get_soldering_proof_payload_store_path()?; + let payload_path = soldering_proof_payload_store_path( + &store_base_path, instance_id, graph_id, - *received_committee_pubkey, - committee_partial_sigs.clone(), + verifier_index, + &payload_hash, + )?; + tracing::info!( + from_peer_id = %ctx.from_peer_id, + instance_id = %instance_id, + graph_id = %graph_id, + verifier_index, + total_len, + payload_hash = %soldering_payload_hash_hex(&payload_hash), + payload_path = %payload_path, + "received SolderingProofReady; reading payload from store" + ); + let payload = match read_soldering_proof_store_payload(&payload_path).await { + Ok(payload) => payload, + Err(err) => { + tracing::error!( + instance_id = %instance_id, + graph_id = %graph_id, + verifier_index, + payload_path = %payload_path, + error = %err, + "failed to read soldering proof payload from store" + ); + return Err(err).context("read soldering proof payload from store"); + } + }; + tracing::info!( + instance_id = %instance_id, + graph_id = %graph_id, + verifier_index, + bytes = payload.len(), + total_len, + payload_hash = %soldering_payload_hash_hex(&payload_hash), + payload_path = %payload_path, + "read soldering proof payload from store" + ); + tracing::info!( + instance_id = %instance_id, + graph_id = %graph_id, + verifier_index, + total_len, + payload_hash = %soldering_payload_hash_hex(&payload_hash), + "start processing soldering proof payload" + ); + handle_soldering_proof_payload_operator( + ctx, + instance_id, + graph_id, + verifier_index, + payload_hash, + total_len, + &payload, ) - .await?; - // 2. if received enough valid committee partial sigs, endorse the graph - let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; - let committee_partial_sigs = - get_committee_partial_sigs_for_graph(ctx.local_db, instance_id, graph_id) - .await? - .into_iter() - .map(|(_, ps)| ps) - .collect::>(); - if committee_partial_sigs.len() == committee_pubkeys.len() { - let message = make_message(ctx, content); - let graph = match get_graph_or_defer( - ctx.swarm, - ctx.local_db, - ctx.goat_client, - instance_id, - graph_id, - &message, - ) - .await? - { - Some(g) => g, - None => return Ok(()), - }; - let graph = Bitvm2Graph::from_simplified(&graph)?; - let committee_sig_for_graph = endorse_graph(ctx.goat_client, &graph).await?; - let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); - let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; - let local_committee_pubkey = instance_keypair.public_key().into(); - let committee_evm_address = get_node_goat_address() - .ok_or_else(|| anyhow::anyhow!("failed to get node goat address".to_string()))?; - let message_content = GOATMessageContent::EndorseGraph(EndorseGraph { - instance_id, - graph_id, - committee_pubkey: local_committee_pubkey, - committee_sig_for_graph: committee_sig_for_graph.as_bytes().to_vec(), - committee_evm_address, - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; + .await +} + +#[allow(clippy::too_many_arguments)] +pub(crate) async fn handle_soldering_proof_payload_operator( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + graph_id: Uuid, + verifier_index: usize, + payload_hash: [u8; 32], + total_len: usize, + payload: &[u8], +) -> Result<()> { + if payload.len() != total_len { + tracing::warn!( + instance_id = %instance_id, + graph_id = %graph_id, + verifier_index, + actual_len = payload.len(), + total_len, + payload_hash = %soldering_payload_hash_hex(&payload_hash), + "SolderingProof payload length mismatch" + ); + bail!( + "SolderingProof payload length {} does not match total_len {total_len}", + payload.len() + ); } - Ok(()) + let actual_hash = soldering_payload_hash(payload); + if actual_hash != payload_hash { + tracing::warn!( + instance_id = %instance_id, + graph_id = %graph_id, + verifier_index, + expected_hash = %soldering_payload_hash_hex(&payload_hash), + actual_hash = %soldering_payload_hash_hex(&actual_hash), + "SolderingProof payload hash mismatch" + ); + bail!("SolderingProof payload hash mismatch"); + } + let payload: CompactSolderingProofPayload = + bincode::deserialize(payload).context("deserialize compact soldering proof payload")?; + handle_compact_soldering_proof_operator(ctx, instance_id, graph_id, verifier_index, payload) + .await } +// verify Verifier SolderingProof, build Graph and broadcast CreateGraph. #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_committee_presign_operator( +async fn handle_compact_soldering_proof_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - received_committee_pubkey: &PublicKey, - committee_partial_sigs: &CommitteePartialSignatures, - _agg_nonces: &CommitteeAggNonces, + verifier_index: usize, + payload: CompactSolderingProofPayload, ) -> Result<()> { - // received from Committee members - if !ensure_self_or_valid_committee( - ctx, + let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); + let local_operator_pubkey = operator_master_key.master_keypair().public_key().into(); + if !pending_graph_belongs_to_operator( + ctx.local_db, instance_id, - Some(graph_id), - received_committee_pubkey, - "CommitteePresign", + graph_id, + &local_operator_pubkey, ) .await? { + tracing::debug!( + "Ignore SolderingProof for {instance_id}:{graph_id}: no local pending graph session" + ); + return Ok(()); } - // 1. save the committee partial sigs to local db - // TODO: validate the partial sigs - store_committee_partial_sigs_for_graph( + + let mut state = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .ok_or_else(|| anyhow!("missing BABE setup state for pending graph {graph_id}"))?; + let operator_state = state + .operator + .as_mut() + .ok_or_else(|| anyhow!("missing operator BABE setup state for pending graph {graph_id}"))?; + let frozen = operator_state + .frozen_verifier_pubkeys + .as_ref() + .ok_or_else(|| anyhow!("operator verifier membership is not frozen"))?; + let verifier_pubkey = *frozen + .get(verifier_index) + .ok_or_else(|| anyhow!("SolderingProof verifier index {verifier_index} out of range"))?; + + let candidate = operator_state + .candidates + .iter() + .find(|candidate| candidate.verifier_pubkey == verifier_pubkey) + .ok_or_else(|| anyhow!("selected verifier candidate is missing"))?; + if candidate.verifier_index != Some(verifier_index) { + bail!("selected verifier candidate index does not match SolderingProof slot"); + } + let setup_package = candidate.setup_package.clone(); + let (opened, finalized, soldering) = + expand_compact_soldering_proof_payload(&setup_package, payload) + .context("expand compact soldering proof payload")?; + + let vk = crate::vk::get_vk().await.context("load Groth16 verifying key for BABE validation")?; + let public_inputs = derive_operator_wrapper_statement(graph_id)?.public_inputs; + let package_for_validation = setup_package.clone(); + let opened_for_validation = opened.clone(); + let finalized_for_validation = finalized.clone(); + let soldering_for_validation = soldering.clone(); + let soldering_builder = Arc::clone( + ctx.soldering_builder + .as_ref() + .context("BABE soldering builder is not initialized for Operator")?, + ); + + tokio::task::spawn_blocking(move || { + verify_real_setup( + &soldering_builder, + &package_for_validation, + &opened_for_validation, + &finalized_for_validation, + &soldering_for_validation, + &vk, + &public_inputs, + ) + }) + .await + .context("real BABE setup verification task failed")??; + + let gc_data = extract_gc_circuit_data(&finalized, &soldering, verifier_pubkey)?; + let prover_state = BabeProverState { + package: setup_package.clone(), + finalized, + soldering, + h_msgs: gc_data.final_msg_hashlocks.clone(), + }; + let Some(bitvm_gc_circuit_datas) = record_candidate_gc_data( + operator_state, + verifier_pubkey, + verifier_index, + &setup_package, + gc_data, + prover_state, + )? + else { + save_babe_setup_state(ctx.local_db, instance_id, graph_id, &state)?; + return Ok(()); + }; + save_babe_setup_state(ctx.local_db, instance_id, graph_id, &state)?; + + let instance_params = get_instance_parameters(ctx.local_db, instance_id) + .await? + .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; + + let (graph_nonce, cur_prekickoff_txn) = + match get_current_prekickoff_tx(ctx.local_db, &local_operator_pubkey).await? { + Some((graph_nonce, prekickoff_tx)) => (graph_nonce, prekickoff_tx), + None => (0, build_genesis_prekickoff_tx(ctx.btc_client, ctx.goat_client).await?), + }; + let prekickoff_params = + build_prekickoff_params(ctx.btc_client, graph_nonce, cur_prekickoff_txn).await?; + + let graph_params = build_graph_params( ctx.local_db, - instance_id, + ctx.goat_client, + instance_params, + prekickoff_params, + bitvm_gc_circuit_datas, + graph_nonce, graph_id, - *received_committee_pubkey, - committee_partial_sigs.clone(), ) .await?; - // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local database, broadcast GraphFinalize - // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order - // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them - try_finalize_graph(ctx.swarm, ctx.local_db, ctx.goat_client, instance_id, graph_id, None, true) - .await?; + + let mut graph = generate_bitvm_graph(graph_params)?; + operator_pre_sign(operator_master_key.master_keypair(), &mut graph)?; + + let graph = graph.to_simplified()?; + store_graph(ctx.local_db, &graph).await?; + + let mut storage = ctx.local_db.acquire().await?; + storage.delete_pending_graph_init(&instance_id, &local_operator_pubkey.to_string()).await?; + + let message_content = + GOATMessageContent::CreateGraph(CreateGraph { instance_id, graph_id, graph_nonce, graph }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; + Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_endorse_graph_operator( +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] +async fn handle_confirm_instance_default( ctx: &mut HandlerContext<'_>, instance_id: Uuid, - graph_id: Uuid, - received_committee_pubkey: &PublicKey, - committee_sig_for_graph: &[u8], - committee_evm_address: &alloy::primitives::Address, ) -> Result<()> { - // received from Committee members - if !ensure_self_or_valid_committee_with_evm( - ctx, - instance_id, - Some(graph_id), - received_committee_pubkey, - committee_evm_address, - "EndorseGraph", - ) - .await? - { - return Ok(()); - } - // 1. check endorsement signature - let graph = match get_graph(ctx.local_db, instance_id, graph_id).await? { - Some(g) => g, - None => { - tracing::warn!( - "Ignore EndorseGraph for {instance_id}:{graph_id} from {}: graph not found, maybe belongs to another Operator", - received_committee_pubkey.to_string() - ); - return Ok(()); + // triggered by PeginDeposit tx + // 1. read & check parameters + let instance_params = match read_instance_info_from_goat(ctx.goat_client, instance_id).await { + Ok(v) => v, + Err(e) => { + if should_ignore_invalid_pegin_data(&e, instance_id) { + return Ok(()); + } + bail!(e) } }; - let full_graph = Bitvm2Graph::from_simplified(&graph)?; - if let Err(e) = verify_graph_endorsement( - ctx.goat_client, - committee_evm_address, - &full_graph, - committee_sig_for_graph, - ) - .await - { + let pegin_deposit_txid = instance_params.build_pegin_tx()?.0.tx().compute_txid(); + if !tx_on_chain(ctx.btc_client, &pegin_deposit_txid).await? { tracing::warn!( - "Ignore EndorseGraph for {instance_id}:{graph_id} from {}: invalid endorsement signature: {e}", - received_committee_pubkey.to_string() + "Ignore ConfirmInstance for {instance_id}: pegin deposit tx {pegin_deposit_txid} not found on chain" ); return Ok(()); } - // 2. save the endorsement signature to local db - store_committee_endorsement_for_graph( - ctx.local_db, - instance_id, - graph_id, - *received_committee_pubkey, - *committee_evm_address, - committee_sig_for_graph.to_owned(), - ) - .await?; - // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local database, broadcast GraphFinalize - // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order - // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them - try_finalize_graph( - ctx.swarm, - ctx.local_db, - ctx.goat_client, - instance_id, - graph_id, - Some(&graph), - true, - ) - .await?; + // 2. save the instance data to local db + store_instance_parameters(ctx.local_db, &instance_params).await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_graph_finalize_committee( +async fn handle_create_graph_committee( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - graph: &SimplifiedBitvm2Graph, - endorse_sigs: &[(PublicKey, alloy::primitives::Address, Vec)], + graph: &SimplifiedBitvmGcGraph, ) -> Result<()> { // received from Operator - // 1. check graph data - if let Err(e) = todo_funcs::validate_finalized_graph(ctx.goat_client, graph, endorse_sigs).await + // 1. check graph data & operator stake + if let Err(e) = + todo_funcs::validate_init_graph(ctx.local_db, ctx.btc_client, ctx.goat_client, graph).await { - if should_ignore_invalid_graph( - &e, - instance_id, - graph_id, - "GraphFinalize", - Some(&ctx.from_peer_id), - ) { + if should_ignore_invalid_graph(&e, instance_id, graph_id, "CreateGraph", None) { return Ok(()); } bail!(e) - } + }; // 2. save the graph data to local db store_graph(ctx.local_db, graph).await?; - store_committee_endorsements_for_graph( + // 3. generate Musig2 nonces & broadcast NonceGeneration + let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); + let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; + let verifier_num = graph.parameters.gc_data.len(); + let (pub_nonces, _, nonce_sigs) = committee_master_key.nonces_for_graph_with_keypair( + instance_id, + graph_id, + verifier_num, + instance_keypair, + ); + let local_committee_pubkey = instance_keypair.public_key().into(); + let message_content = GOATMessageContent::NonceGeneration(NonceGeneration { + instance_id, + graph_id, + committee_pubkey: local_committee_pubkey, + verifier_num, + pub_nonces: pub_nonces.clone(), + nonce_sigs, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; + store_committee_pub_nonces_for_graph( ctx.local_db, instance_id, graph_id, - endorse_sigs.to_owned(), + local_committee_pubkey, + pub_nonces, ) .await?; - // After storing, mark the graph as endorsed - mark_graph_as_endorsed(ctx.local_db, instance_id, graph_id).await?; - // 3. if endorsed graph count >= threshold, generate & broadcast PeginConfirmNonce - if get_endorsed_graph_count(ctx.local_db, instance_id).await? - >= todo_funcs::min_required_operator() - { + // 4. if collected enough pub_nonces, generate partial signatures & broadcast CommitteePresign + let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; + let pub_nonces_unchecked = + get_committee_pub_nonces_for_graph(ctx.local_db, instance_id, graph_id).await?; + if pub_nonces_unchecked.len() == committee_pubkeys.len() { + let mut graph = BitvmGcGraph::from_simplified(graph)?; + let verifier_num = graph.verifier_asserts.len(); + let mut pub_nonces = Vec::with_capacity(pub_nonces_unchecked.len()); + for (pk, pn) in pub_nonces_unchecked.into_iter() { + if let Err(e) = pn.validate_length(verifier_num) { + tracing::warn!("PubNonces from {} has invalid length: {e}", pk.to_string()); + return Ok(()); + } + pub_nonces.push(pn); + } + let agg_nonces = nonces_aggregation(&pub_nonces)?; let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; - let local_committee_pubkey = instance_keypair.public_key().into(); - let stored_pub_nonce = get_committee_pub_nonce_for_instance( - ctx.local_db, + let (_, sec_nonces, _) = committee_master_key.nonces_for_graph_with_keypair( instance_id, - &local_committee_pubkey, - ) - .await?; - if stored_pub_nonce.is_none() { - let (_, pub_nonce, nonce_sig) = - committee_master_key.nonce_for_instance_with_keypair(instance_id, instance_keypair); - let message_content = GOATMessageContent::PeginConfirmNonce(PeginConfirmNonce { - instance_id, - committee_pubkey: local_committee_pubkey, - pub_nonce: pub_nonce.clone(), - nonce_sig, - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::Committee, message_content)).await?; - store_committee_pub_nonce_for_instance( - ctx.local_db, - instance_id, - local_committee_pubkey, - pub_nonce, - ) - .await?; - } - } - // 4. (Relayer) try to call Gateway.postGraphData - // GraphFinalize may come after PostReady, so we need to check it here - if is_relayer() { - let pegin_data = ctx.goat_client.gateway_get_pegin_data(&instance_id).await?; - if pegin_data.status != PeginStatus::Withdrawable { - // pegin not posted yet - return Ok(()); - } - let graph_data = ctx.goat_client.gateway_get_graph_data(&graph_id).await?; - if graph_data.operator_pubkey != [0u8; 32] { - // already posted - return Ok(()); - } - let graph = Bitvm2Graph::from_simplified(graph)?; - let graph_data = build_graph_data(&graph)?; - let endorse_sigs = endorse_sigs.iter().map(|(_, _, sig)| sig.clone()).collect::>(); - ctx.goat_client - .gateway_post_graph_data(&instance_id, &graph_id, &graph_data, &endorse_sigs) - .await?; - } - Ok(()) -} - -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_graph_finalize_default( - ctx: &mut HandlerContext<'_>, - instance_id: Uuid, - graph_id: Uuid, - graph: &SimplifiedBitvm2Graph, - endorse_sigs: &[(PublicKey, alloy::primitives::Address, Vec)], -) -> Result<()> { - // received from Operator - // 1. check graph data - if let Err(e) = todo_funcs::validate_finalized_graph(ctx.goat_client, graph, endorse_sigs).await - { - if should_ignore_invalid_graph( - &e, + graph_id, + verifier_num, + instance_keypair, + ); + let committee_partial_sigs = + committee_pre_sign(instance_keypair, sec_nonces, agg_nonces.clone(), &mut graph)?; + let message_content = GOATMessageContent::CommitteePresign(CommitteePresign { instance_id, graph_id, - "GraphFinalize", - Some(&ctx.from_peer_id), - ) { - return Ok(()); - } - bail!(e) + committee_pubkey: local_committee_pubkey, + committee_partial_sigs, + agg_nonces, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; } - // 2. save the graph data to local db - store_graph(ctx.local_db, graph).await?; Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] -async fn handle_pegin_confirm_nonce_committee( +#[allow(clippy::too_many_arguments)] +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_nonce_generation_committee( ctx: &mut HandlerContext<'_>, instance_id: Uuid, + graph_id: Uuid, received_committee_pubkey: &PublicKey, - pub_nonce: &musig2::PubNonce, - nonce_sig: &secp256k1::schnorr::Signature, + verifier_num: usize, + pub_nonces: &CommitteePubNonces, + nonce_sigs: &CommitteeNonceSignatures, + content: &GOATMessageContent, ) -> Result<()> { // received from Committee members if !ensure_self_or_valid_committee( ctx, instance_id, - None, + Some(graph_id), received_committee_pubkey, - "PeginConfirmNonce", + "NonceGeneration", ) .await? { return Ok(()); } - // 1. check pub_nonce - if !verify_public_nonce(nonce_sig, pub_nonce, &XOnlyPublicKey::from(*received_committee_pubkey)) - { + // 1. check pub_nonces & nonce signatures + let committee_xonly_pubkey = XOnlyPublicKey::from(*received_committee_pubkey); + if !verify_nonce_signatures(&committee_xonly_pubkey, pub_nonces, nonce_sigs, verifier_num)? { tracing::warn!( - "Ignore PeginConfirmNonce for {instance_id} from {}: invalid pub_nonce or nonce_sig", + "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: invalid pub_nonces or nonce_sigs", received_committee_pubkey.to_string() ); return Ok(()); } - // 2. save the pub_nonce to local db - store_committee_pub_nonce_for_instance( + // TODO: deal with the case that one committee member sends different pub_nonces for the same graph + // 2. save the pub_nonces to local db + store_committee_pub_nonces_for_graph( ctx.local_db, instance_id, + graph_id, *received_committee_pubkey, - pub_nonce.clone(), + pub_nonces.clone(), ) .await?; - // 3. if received enough pub_nonces, generate partial signature & broadcast PeginConfirmPartialSig + // 3. if received enough pub_nonces, generate partial signatures & broadcast CommitteePresign let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; - let pub_nonces = get_committee_pub_nonces_for_instance(ctx.local_db, instance_id).await?; - if pub_nonces.len() == committee_pubkeys.len() { + let pub_nonces_unchecked = + get_committee_pub_nonces_for_graph(ctx.local_db, instance_id, graph_id).await?; + if pub_nonces_unchecked.len() == committee_pubkeys.len() { let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; let local_committee_pubkey = instance_keypair.public_key().into(); - let (sec_nonce, _, _) = - committee_master_key.nonce_for_instance_with_keypair(instance_id, instance_keypair); - let agg_nonce = - nonce_aggregation(&pub_nonces.iter().map(|(_, pn)| pn.clone()).collect::>()); - let instance_params = get_instance_parameters(ctx.local_db, instance_id) - .await? - .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; - let mut pegin_confirm = instance_params.build_pegin_tx()?.1; - let context = instance_params.get_verifier_context(instance_keypair)?; - let partial_sig = pegin_confirm - .sign_input_0_musig2(&context, &sec_nonce, &agg_nonce) - .map_err(|e| anyhow!("Failed to sign pegin confirm for {instance_id}: {e}"))?; - let endorse_sig = - endorse_pegin(ctx.goat_client, instance_id, &pegin_confirm.tx().compute_txid()).await?; - let message_content = GOATMessageContent::PeginConfirmPartialSig(PeginConfirmPartialSig { - instance_id, - committee_pubkey: local_committee_pubkey, - partial_sig, - endorse_sig: endorse_sig.as_bytes().to_vec(), - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::Committee, message_content)).await?; - store_committee_partial_sig_for_instance( + let message = make_message(ctx, content); + let graph = match get_graph_or_defer( + ctx.swarm, ctx.local_db, + ctx.goat_client, instance_id, - local_committee_pubkey, - partial_sig, + graph_id, + &message, ) - .await?; - store_committee_endorse_sig_for_pegin( + .await? + { + Some(g) => g, + None => return Ok(()), + }; + let mut graph = BitvmGcGraph::from_simplified(&graph)?; + let verifier_num = graph.verifier_asserts.len(); + let mut pub_nonces = Vec::with_capacity(pub_nonces_unchecked.len()); + for (pk, pn) in pub_nonces_unchecked.into_iter() { + if let Err(e) = pn.validate_length(verifier_num) { + tracing::warn!("PubNonces from {} has invalid length: {e}", pk.to_string()); + return Ok(()); + } + pub_nonces.push(pn); + } + let agg_nonces = nonces_aggregation(&pub_nonces)?; + let (_, sec_nonces, _) = committee_master_key.nonces_for_graph_with_keypair( + instance_id, + graph_id, + verifier_num, + instance_keypair, + ); + // 4. if received enough valid committee partial sigs, endorse the graph + let committee_partial_sigs = + committee_pre_sign(instance_keypair, sec_nonces, agg_nonces.clone(), &mut graph)?; + let message_content = GOATMessageContent::CommitteePresign(CommitteePresign { + instance_id, + graph_id, + committee_pubkey: local_committee_pubkey, + committee_partial_sigs: committee_partial_sigs.clone(), + agg_nonces, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; + store_committee_partial_sigs_for_graph( ctx.local_db, instance_id, + graph_id, local_committee_pubkey, - endorse_sig.as_bytes().to_vec(), + committee_partial_sigs, ) .await?; - // 4. (Relayer) if received enough partial signatures, aggregate the sigs - if is_relayer() { - let partial_sigs = get_committee_partial_sigs_for_instance(ctx.local_db, instance_id) + let committee_partial_sigs = + get_committee_partial_sigs_for_graph(ctx.local_db, instance_id, graph_id) .await? .into_iter() .map(|(_, ps)| ps) .collect::>(); - let context = instance_params.get_base_context(); - if partial_sigs.len() == committee_pubkeys.len() { - let full_sig = pegin_confirm - .aggregate_input_0_musig2_signatures(&context, partial_sigs, &agg_nonce) - .map_err(|e| { - anyhow!( - "Failed to aggregate Pegin-Confirm's signatures for {instance_id}: {e}" - ) - })?; - let connector_z = ConnectorZ::new( - context.network, - &context.n_of_n_taproot_public_key, - &instance_params.user_info.user_xonly_pubkey, - ); - pegin_confirm.push_input_0_signature(&connector_z, full_sig); - broadcast_tx(ctx.btc_client, pegin_confirm.tx()).await?; - } + if committee_partial_sigs.len() == committee_pubkeys.len() { + let committee_sig_for_graph = endorse_graph(ctx.goat_client, &graph).await?; + let committee_evm_address = get_node_goat_address() + .ok_or_else(|| anyhow::anyhow!("failed to get node goat address".to_string()))?; + let message_content = GOATMessageContent::EndorseGraph(EndorseGraph { + instance_id, + graph_id, + committee_pubkey: local_committee_pubkey, + committee_sig_for_graph: committee_sig_for_graph.as_bytes().to_vec(), + committee_evm_address, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; } } Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] -async fn handle_pegin_confirm_partial_sig_committee( +#[allow(clippy::too_many_arguments)] +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_nonce_generation_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, + graph_id: Uuid, received_committee_pubkey: &PublicKey, - partial_sig: &musig2::PartialSignature, - endorse_sig: &[u8], + verifier_num: usize, + pub_nonces: &CommitteePubNonces, + nonce_sigs: &CommitteeNonceSignatures, ) -> Result<()> { // received from Committee members if !ensure_self_or_valid_committee( ctx, instance_id, - None, + Some(graph_id), received_committee_pubkey, - "PeginConfirmPartialSig", + "NonceGeneration", ) .await? { return Ok(()); } - // 1. save the partial signature & endorsement signature to local db - // partial sigs will be validated when aggregating - store_committee_partial_sig_for_instance( + // 1. check pub_nonces & nonce signatures + let committee_xonly_pubkey = XOnlyPublicKey::from(*received_committee_pubkey); + if !verify_nonce_signatures(&committee_xonly_pubkey, pub_nonces, nonce_sigs, verifier_num)? { + tracing::warn!( + "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: invalid pub_nonces or nonce_sigs", + received_committee_pubkey.to_string() + ); + return Ok(()); + } + let graph = match get_graph(ctx.local_db, instance_id, graph_id).await? { + Some(g) => g, + None => { + tracing::warn!( + "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: graph not found, maybe belongs to another Operator", + received_committee_pubkey.to_string() + ); + return Ok(()); + } + }; + let verifier_num = graph.parameters.gc_data.len(); + if let Err(e) = pub_nonces.validate_length(verifier_num) { + tracing::warn!( + "Ignore NonceGeneration for {instance_id}:{graph_id} from {}: invalid pub_nonces length: {e}", + received_committee_pubkey.to_string() + ); + return Ok(()); + } + // TODO: deal with the case that one committee member sends different pub_nonces for the same graph + // 2. save the pub_nonces to local db + store_committee_pub_nonces_for_graph( ctx.local_db, instance_id, + graph_id, *received_committee_pubkey, - *partial_sig, + pub_nonces.clone(), ) .await?; - store_committee_endorse_sig_for_pegin( + // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local db, broadcast GraphFinalize + // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order + // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them + try_finalize_graph( + ctx.swarm, ctx.local_db, + ctx.goat_client, instance_id, - *received_committee_pubkey, - endorse_sig.to_owned(), + graph_id, + Some(&graph), + true, ) .await?; - // 3. (Relayer) if received enough partial signatures, aggregate the sigs - if is_relayer() { - let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; - let pub_nonces = get_committee_pub_nonces_for_instance(ctx.local_db, instance_id).await?; - let partial_sigs = get_committee_partial_sigs_for_instance(ctx.local_db, instance_id) - .await? - .into_iter() - .map(|(_, ps)| ps) - .collect::>(); - if pub_nonces.len() == committee_pubkeys.len() - && partial_sigs.len() == committee_pubkeys.len() - { - let instance_params = get_instance_parameters(ctx.local_db, instance_id) - .await? - .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; - let mut pegin_confirm = instance_params.build_pegin_tx()?.1; - let agg_nonce = - nonce_aggregation(&pub_nonces.iter().map(|(_, pn)| pn.clone()).collect::>()); - let context = instance_params.get_base_context(); - let full_sig = pegin_confirm - .aggregate_input_0_musig2_signatures(&context, partial_sigs, &agg_nonce) - .map_err(|e| { - anyhow!("Failed to aggregate Pegin-Confirm's signatures for {instance_id}: {e}") - })?; - let connector_z = ConnectorZ::new( - context.network, - &context.n_of_n_taproot_public_key, - &instance_params.user_info.user_xonly_pubkey, - ); - pegin_confirm.push_input_0_signature(&connector_z, full_sig); - broadcast_tx(ctx.btc_client, pegin_confirm.tx()).await?; - } - } Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] -async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> Result<()> { - // triggered by PeginConfirm tx - if !is_relayer() { +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_committee_presign_committee( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + graph_id: Uuid, + received_committee_pubkey: &PublicKey, + committee_partial_sigs: &CommitteePartialSignatures, + _agg_nonces: &CommitteeAggNonces, + content: &GOATMessageContent, +) -> Result<()> { + // received from Committee members + if !ensure_self_or_valid_committee( + ctx, + instance_id, + Some(graph_id), + received_committee_pubkey, + "CommitteePresign", + ) + .await? + { return Ok(()); } - // 1. (Relayer)call Gateway.postPeginData on GoatChain + // 1. save the committee partial sigs to local db + // TODO: validate the partial sigs + store_committee_partial_sigs_for_graph( + ctx.local_db, + instance_id, + graph_id, + *received_committee_pubkey, + committee_partial_sigs.clone(), + ) + .await?; + // 2. if received enough valid committee partial sigs, endorse the graph let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; - let pegin_data = ctx.goat_client.gateway_get_pegin_data(&instance_id).await?; - if pegin_data.status == PeginStatus::None { - tracing::warn!("Ignore PostReady for {instance_id}: not a pending pegin request"); - return Ok(()); - } else if pegin_data.status == PeginStatus::Pending { - let instance_params = get_instance_parameters(ctx.local_db, instance_id) - .await? - .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; - let pegin_confirm = instance_params.build_pegin_tx()?.1; - let pegin_txid = pegin_confirm.tx().compute_txid(); - let pegin_tx = match ctx.btc_client.get_tx(&pegin_txid).await? { - Some(tx) => tx, - None => { - tracing::warn!( - "Ignore PostReady for {instance_id}: Pegin-Confirm transaction not found on Bitcoin: {pegin_txid}" - ); - return Ok(()); - } - }; - let endorse_sigs = get_committee_endorse_sigs_for_pegin(ctx.local_db, instance_id) + let committee_partial_sigs = + get_committee_partial_sigs_for_graph(ctx.local_db, instance_id, graph_id) .await? .into_iter() - .map(|(_, es)| es) + .map(|(_, ps)| ps) .collect::>(); - if endorse_sigs.len() != committee_pubkeys.len() { - tracing::warn!( - "Ignore PostReady for {instance_id}: not enough endorse sigs for pegin confirm tx: {}", - endorse_sigs.len() - ); - return Ok(()); - } - let pegin_height = match ctx.btc_client.get_tx_status(&pegin_txid).await?.block_height { - Some(height) => height as u64, - None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); - let message = GOATMessage::new( - ctx.actor.clone(), - GOATMessageContent::PostReady(PostReady { instance_id }), - ); - push_local_unhandled_messages( - ctx.local_db, - instance_id, - &message, - delay_secs as usize, - ) - .await?; - tracing::info!( - "Retry postPeginData later for {instance_id}: pegin confirm tx not confirmed on btc yet" - ); - return Ok(()); - } + if committee_partial_sigs.len() == committee_pubkeys.len() { + let message = make_message(ctx, content); + let graph = match get_graph_or_defer( + ctx.swarm, + ctx.local_db, + ctx.goat_client, + instance_id, + graph_id, + &message, + ) + .await? + { + Some(g) => g, + None => return Ok(()), }; - let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; - if goat_confirmed_height < pegin_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) - * (pegin_height - goat_confirmed_height); - let message = GOATMessage::new( - ctx.actor.clone(), - GOATMessageContent::PostReady(PostReady { instance_id }), - ); - push_local_unhandled_messages(ctx.local_db, instance_id, &message, delay_secs as usize) - .await?; - tracing::info!( - "Retry postPeginData later for {instance_id}: pegin confirm tx block not posted to goat spv contract yet" - ); - return Ok(()); - } - ctx.goat_client - .gateway_post_pegin_data(ctx.btc_client, &instance_id, &pegin_tx, &endorse_sigs) - .await?; - } else { - // already posted - } - // 2. (Relayer)call Gateway.postGraphData on GoatChain - let graph_ids = get_graph_ids_for_instance(ctx.local_db, instance_id).await?; - for graph_id in &graph_ids { - let graph_data = ctx.goat_client.gateway_get_graph_data(graph_id).await?; - if graph_data.operator_pubkey != [0u8; 32] { - // already posted - continue; - } - let endorsement_sigs = - get_committee_endorsements_for_graph(ctx.local_db, instance_id, *graph_id) - .await? - .into_iter() - .map(|(_, _, sig)| sig) - .collect::>(); - if endorsement_sigs.len() != committee_pubkeys.len() { - tracing::warn!( - "Ignore postGraphData for {instance_id}:{graph_id}: not enough endorse sigs for graph: {}", - endorsement_sigs.len() - ); - continue; - } - let graph = get_graph(ctx.local_db, instance_id, *graph_id) - .await? - .ok_or_else(|| anyhow!("Graph not found for {instance_id}:{graph_id}"))?; - let graph = Bitvm2Graph::from_simplified(&graph)?; - let graph_data = build_graph_data(&graph)?; - ctx.goat_client - .gateway_post_graph_data(&instance_id, graph_id, &graph_data, &endorsement_sigs) - .await?; + let graph = BitvmGcGraph::from_simplified(&graph)?; + let committee_sig_for_graph = endorse_graph(ctx.goat_client, &graph).await?; + let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); + let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; + let local_committee_pubkey = instance_keypair.public_key().into(); + let committee_evm_address = get_node_goat_address() + .ok_or_else(|| anyhow::anyhow!("failed to get node goat address".to_string()))?; + let message_content = GOATMessageContent::EndorseGraph(EndorseGraph { + instance_id, + graph_id, + committee_pubkey: local_committee_pubkey, + committee_sig_for_graph: committee_sig_for_graph.as_bytes().to_vec(), + committee_evm_address, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; } Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_kickoff_ready_operator( +async fn handle_committee_presign_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - content: &GOATMessageContent, + received_committee_pubkey: &PublicKey, + committee_partial_sigs: &CommitteePartialSignatures, + _agg_nonces: &CommitteeAggNonces, ) -> Result<()> { - // triggered by InitWithdraw event from GoatChain - let message = make_message(ctx, content); - let graph = match get_graph_or_defer( - ctx.swarm, + // received from Committee members + if !ensure_self_or_valid_committee( + ctx, + instance_id, + Some(graph_id), + received_committee_pubkey, + "CommitteePresign", + ) + .await? + { + return Ok(()); + } + // 1. save the committee partial sigs to local db + // TODO: validate the partial sigs + store_committee_partial_sigs_for_graph( ctx.local_db, - ctx.goat_client, instance_id, graph_id, - &message, + *received_committee_pubkey, + committee_partial_sigs.clone(), + ) + .await?; + // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local database, broadcast GraphFinalize + // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order + // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them + try_finalize_graph(ctx.swarm, ctx.local_db, ctx.goat_client, instance_id, graph_id, None, true) + .await?; + Ok(()) +} + +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_endorse_graph_operator( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + graph_id: Uuid, + received_committee_pubkey: &PublicKey, + committee_sig_for_graph: &[u8], + committee_evm_address: &alloy::primitives::Address, +) -> Result<()> { + // received from Committee members + if !ensure_self_or_valid_committee_with_evm( + ctx, + instance_id, + Some(graph_id), + received_committee_pubkey, + committee_evm_address, + "EndorseGraph", ) .await? { - Some(g) => g, - None => return Ok(()), - }; - let mut graph = Bitvm2Graph::from_simplified(&graph)?; - let operator_pubkey = graph.parameters.operator_pubkey; - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let node_pubkey: PublicKey = operator_master_key.master_keypair().public_key().into(); - if node_pubkey != operator_pubkey { - tracing::warn!("Ignore KickoffReady for {instance_id}:{graph_id}: not my graph"); return Ok(()); } - // 1. check the withdraw status on GoatChain - let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; - if withdraw_status != WithdrawStatus::Initialized { - tracing::warn!( - "Ignore KickoffReady for {instance_id}:{graph_id}: invalid withdraw status: {withdraw_status:?}" - ); - return Ok(()); - } - // 2. check prekickoff nonce & broadcast previous pre-kickoff if needed - let start_nonce = - match get_latest_pegout_finalized_graph(ctx.local_db, &operator_pubkey).await? { - Some((n, _)) => n + 1, - None => 0, - }; - for current_nonce in start_nonce..graph.parameters.graph_nonce { - let (current_instance_id, current_graph_id) = match get_graph_id_by_nonce( - ctx.local_db, - current_nonce, - &operator_pubkey, - ) - .await? - { - Some(v) => v, - None => { - tracing::warn!( - "Ignore KickoffReady for {instance_id}:{graph_id}: missing previous graph {current_nonce}" - ); - return Ok(()); - } - }; - let current_graph = match get_graph_or_defer( - ctx.swarm, - ctx.local_db, - ctx.goat_client, - current_instance_id, - current_graph_id, - &message, - ) - .await? - { - Some(g) => g, - None => return Ok(()), - }; - let mut current_graph = Bitvm2Graph::from_simplified(¤t_graph)?; - let current_graph_start_status = - get_graph_status(ctx.local_db, current_instance_id, current_graph_id) - .await? - .ok_or_else(|| { - anyhow!("Graph status not found for {current_instance_id}:{current_graph_id}") - })?; - let (current_graph_status, current_graph_sub_status) = refresh_graph( - ctx.local_db, - ctx.btc_client, - ctx.goat_client, - current_instance_id, - current_graph_id, - Some(¤t_graph), - Some(current_graph_start_status), - None, - ) - .await?; - compensate_graph_events( - ctx.local_db, - ctx.btc_client, - current_instance_id, - current_graph_id, - Some(¤t_graph), - Some(current_graph_start_status), - current_graph_start_status, - current_graph_status, - current_graph_sub_status, - ) - .await?; - if current_graph_status.is_closed() { - continue; - } else if current_graph_status.is_pegout_started() { + // 1. check endorsement signature + let graph = match get_graph(ctx.local_db, instance_id, graph_id).await? { + Some(g) => g, + None => { tracing::warn!( - "Ignore KickoffReady for {instance_id}:{graph_id}: previous graph {current_graph_id} already started pegout" - ); - let nonce_interval = - graph.parameters.graph_nonce - current_graph.parameters.graph_nonce; - let min_pegout_time_secs = take1_timelock(ctx.btc_client.network()) as u64 - * todo_funcs::avg_block_time_secs(ctx.btc_client.network()); - let delay_secs = min_pegout_time_secs * nonce_interval; - push_local_unhandled_messages( - ctx.local_db, - current_graph_id, - &message, - delay_secs as usize, - ) - .await?; - return Ok(()); - } else if current_graph_status.is_obsoleted() { - operator_skip_graph(ctx.btc_client, &mut current_graph).await?; - tracing::info!( - "Operator {operator_pubkey} skipped obsoleted graph {current_instance_id}:{current_graph_id}" + "Ignore EndorseGraph for {instance_id}:{graph_id} from {}: graph not found, maybe belongs to another Operator", + received_committee_pubkey.to_string() ); - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks - push_local_unhandled_messages( - ctx.local_db, - current_graph_id, - &message, - delay_secs as usize, - ) - .await?; return Ok(()); - } else { - let graph_data_on_goat = - ctx.goat_client.gateway_get_graph_data(¤t_graph_id).await?; - if graph_data_on_goat.operator_pubkey != [0u8; 32] { - tracing::warn!( - "Ignore KickoffReady for {instance_id}:{graph_id}: previous available graph exists for Operator {operator_pubkey}: {current_instance_id}:{current_graph_id}, please withdraw it first" - ); - let nonce_interval = - graph.parameters.graph_nonce - current_graph.parameters.graph_nonce; - let min_pegout_time_secs = take1_timelock(ctx.btc_client.network()) as u64 - * todo_funcs::avg_block_time_secs(ctx.btc_client.network()); - let delay_secs = min_pegout_time_secs * nonce_interval; - push_local_unhandled_messages( - ctx.local_db, - current_graph_id, - &message, - delay_secs as usize, - ) - .await?; - return Ok(()); - } else { - operator_skip_graph(ctx.btc_client, &mut current_graph).await?; - tracing::info!( - "Operator {operator_pubkey} skipped non-posted graph {current_instance_id}:{current_graph_id}" - ); - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks - push_local_unhandled_messages( - ctx.local_db, - current_graph_id, - &message, - delay_secs as usize, - ) - .await?; - return Ok(()); - } } + }; + let full_graph = BitvmGcGraph::from_simplified(&graph)?; + if let Err(e) = verify_graph_endorsement( + ctx.goat_client, + committee_evm_address, + &full_graph, + committee_sig_for_graph, + ) + .await + { + tracing::warn!( + "Ignore EndorseGraph for {instance_id}:{graph_id} from {}: invalid endorsement signature: {e}", + received_committee_pubkey.to_string() + ); + return Ok(()); } - // 3. sign & broadcast prekickoff & kickoff txns - operator_kickoff(ctx.btc_client, &mut graph).await?; + // 2. save the endorsement signature to local db + store_committee_endorsement_for_graph( + ctx.local_db, + instance_id, + graph_id, + *received_committee_pubkey, + *committee_evm_address, + committee_sig_for_graph.to_owned(), + ) + .await?; + // 3. if received enough endorsement signatures, mark the graph as endorsed, send the graph to local database, broadcast GraphFinalize + // Operator may receive EndorseGraph, CommitteePresign or NonceGeneration messages in any order + // So we need to check if we have collected enough endorsements, pub_nonces and partial_sigs every time we receive them + try_finalize_graph( + ctx.swarm, + ctx.local_db, + ctx.goat_client, + instance_id, + graph_id, + Some(&graph), + true, + ) + .await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_kickoff_sent_committee( +async fn handle_graph_finalize_committee( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - content: &GOATMessageContent, + graph: &SimplifiedBitvmGcGraph, + endorse_sigs: &[(PublicKey, alloy::primitives::Address, Vec)], ) -> Result<()> { - // triggered by Kickoff tx - // 1. update status - let (graph, _graph_status, _graph_sub_status) = - match refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::OperatorKickOff) - .await? - { - Some(v) => v, - None => return Ok(()), - }; - if !is_relayer() { - return Ok(()); + // received from Operator + // 1. check graph data + if let Err(e) = todo_funcs::validate_finalized_graph(ctx.goat_client, graph, endorse_sigs).await + { + if should_ignore_invalid_graph( + &e, + instance_id, + graph_id, + "GraphFinalize", + Some(&ctx.from_peer_id), + ) { + return Ok(()); + } + bail!(e) } - // 2. (Relayer) try to call Gateway.proceedWithdraw - let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; - if withdraw_status != WithdrawStatus::Initialized { - tracing::warn!( - "Ignore KickoffSent for {instance_id}:{graph_id}: invalid withdraw status: {withdraw_status:?}" - ); - return Ok(()); + // 2. save the graph data to local db + store_graph(ctx.local_db, graph).await?; + store_committee_endorsements_for_graph( + ctx.local_db, + instance_id, + graph_id, + endorse_sigs.to_owned(), + ) + .await?; + // After storing, mark the graph as endorsed + mark_graph_as_endorsed(ctx.local_db, instance_id, graph_id).await?; + // 3. if endorsed graph count >= threshold, generate & broadcast PeginConfirmNonce + if get_endorsed_graph_count(ctx.local_db, instance_id).await? + >= todo_funcs::min_required_operator() + { + let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); + let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; + let local_committee_pubkey = instance_keypair.public_key().into(); + let stored_pub_nonce = get_committee_pub_nonce_for_instance( + ctx.local_db, + instance_id, + &local_committee_pubkey, + ) + .await?; + if stored_pub_nonce.is_none() { + let (_, pub_nonce, nonce_sig) = + committee_master_key.nonce_for_instance_with_keypair(instance_id, instance_keypair); + let message_content = GOATMessageContent::PeginConfirmNonce(PeginConfirmNonce { + instance_id, + committee_pubkey: local_committee_pubkey, + pub_nonce: pub_nonce.clone(), + nonce_sig, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Committee, message_content)).await?; + store_committee_pub_nonce_for_instance( + ctx.local_db, + instance_id, + local_committee_pubkey, + pub_nonce, + ) + .await?; + } } - let kickoff_txid = graph.kickoff.tx().compute_txid(); - let kickoff_tx = match ctx.btc_client.get_tx(&kickoff_txid).await? { - Some(tx) => tx, - None => { - tracing::warn!( - "Ignore KickoffSent for {instance_id}:{graph_id}: kickoff tx not found on chain: {kickoff_txid}" - ); + // 4. (Relayer) try to call Gateway.postGraphData + // GraphFinalize may come after PostReady, so we need to check it here + if is_relayer() { + let pegin_data = ctx.goat_client.gateway_get_pegin_data(&instance_id).await?; + if pegin_data.status != PeginStatus::Withdrawable { + // pegin not posted yet return Ok(()); } - }; - let kickoff_height = match ctx.btc_client.get_tx_status(&kickoff_txid).await?.block_height { - Some(height) => height as u64, - None => { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); - let message = make_message(ctx, content); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) - .await?; - tracing::info!( - "Retry proceedWithdraw later for {instance_id}:{graph_id}: kickoff tx not confirmed on btc yet" - ); + let graph_data = ctx.goat_client.gateway_get_graph_data(&graph_id).await?; + if graph_data.operator_pubkey != [0u8; 32] { + // already posted return Ok(()); } - }; - let goat_confirmed_btc_height = ctx.goat_client.btc_spv_latest_height().await?; - if goat_confirmed_btc_height < kickoff_height { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) - * (kickoff_height - goat_confirmed_btc_height); - let message = make_message(ctx, content); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) + let graph = BitvmGcGraph::from_simplified(graph)?; + let graph_data = build_graph_data(&graph)?; + let endorse_sigs = endorse_sigs.iter().map(|(_, _, sig)| sig.clone()).collect::>(); + ctx.goat_client + .gateway_post_graph_data(&instance_id, &graph_id, &graph_data, &endorse_sigs) .await?; - tracing::info!( - "Retry proceedWithdraw later for {instance_id}:{graph_id}: kickoff tx block not posted to goat spv contract yet" - ); - return Ok(()); } - ctx.goat_client.gateway_process_withdraw(ctx.btc_client, &graph_id, &kickoff_tx).await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_kickoff_sent_challenger( +async fn handle_graph_finalize_default( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - content: &GOATMessageContent, + graph: &SimplifiedBitvmGcGraph, + endorse_sigs: &[(PublicKey, alloy::primitives::Address, Vec)], ) -> Result<()> { - // triggered by Kickoff tx - let message = make_message(ctx, content); - let (graph, _graph_status, _graph_sub_status) = match refresh_graph_status( - ctx, - instance_id, - graph_id, - Some(&message), - GraphStatus::OperatorKickOff, - ) - .await? + // received from Operator + // 1. check graph data + if let Err(e) = todo_funcs::validate_finalized_graph(ctx.goat_client, graph, endorse_sigs).await { - Some(v) => v, - None => return Ok(()), - }; - // 1. check kickoff tx status on Bitcoin chain - let kickoff_txid = graph.kickoff.tx().compute_txid(); - let kickoff_height = match ctx.btc_client.get_tx_status(&kickoff_txid).await?.block_height { - Some(height) => height, - None => { - tracing::warn!( - "Ignore KickoffSent for {instance_id}:{graph_id}: kickoff tx not confirmed yet" - ); + if should_ignore_invalid_graph( + &e, + instance_id, + graph_id, + "GraphFinalize", + Some(&ctx.from_peer_id), + ) { return Ok(()); } - }; - let take1_txid = graph.take1.tx().compute_txid(); - let (challenge_tx, _) = export_challenge_tx(&graph).unwrap(); - let kickoff_challenge_outpoint = challenge_tx.input[0].previous_output; - if let Some(spent_txid) = outpoint_spent_txid( - ctx.btc_client, - &kickoff_challenge_outpoint.txid, - kickoff_challenge_outpoint.vout as u64, + bail!(e) + } + // 2. save the graph data to local db + store_graph(ctx.local_db, graph).await?; + Ok(()) +} + +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] +async fn handle_pegin_confirm_nonce_committee( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + received_committee_pubkey: &PublicKey, + pub_nonce: &musig2::PubNonce, + nonce_sig: &secp256k1::schnorr::Signature, +) -> Result<()> { + // received from Committee members + if !ensure_self_or_valid_committee( + ctx, + instance_id, + None, + received_committee_pubkey, + "PeginConfirmNonce", ) .await? { - let spent_tx_name = if spent_txid == take1_txid { "Take1" } else { "Challenge" }; + return Ok(()); + } + // 1. check pub_nonce + if !verify_public_nonce(nonce_sig, pub_nonce, &XOnlyPublicKey::from(*received_committee_pubkey)) + { tracing::warn!( - "Ignore KickoffSent for {instance_id}:{graph_id}: challenge connector already spent by {spent_tx_name} tx: {spent_txid}" + "Ignore PeginConfirmNonce for {instance_id} from {}: invalid pub_nonce or nonce_sig", + received_committee_pubkey.to_string() ); return Ok(()); } - // 2. check withdraw status, if it's invalid, sign & broadcast challenge txn - let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; - let goat_confirmed_btc_height = ctx.goat_client.btc_spv_latest_height().await? as u32; - if [WithdrawStatus::None, WithdrawStatus::Canceled].contains(&withdraw_status) { - if kickoff_height >= goat_confirmed_btc_height { - tracing::warn!( - "Ignore KickoffSent for {instance_id}:{graph_id}: kickoff tx not confirmed by goat spv yet" - ); - return Ok(()); - } else { - let (challenge_tx, _) = export_challenge_tx(&graph).unwrap(); - let challenge_txid = challenge_tx.compute_txid(); - if ctx.btc_client.get_tx(&challenge_txid).await?.is_none() { - let challenge_txid = send_challenge_tx(ctx.btc_client, &graph).await?; - let message_content = GOATMessageContent::ChallengeSent(ChallengeSent { - instance_id, - graph_id, - challenge_txid, - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; + // 2. save the pub_nonce to local db + store_committee_pub_nonce_for_instance( + ctx.local_db, + instance_id, + *received_committee_pubkey, + pub_nonce.clone(), + ) + .await?; + // 3. if received enough pub_nonces, generate partial signature & broadcast PeginConfirmPartialSig + let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; + let pub_nonces = get_committee_pub_nonces_for_instance(ctx.local_db, instance_id).await?; + if pub_nonces.len() == committee_pubkeys.len() { + let committee_master_key = CommitteeMasterKey::new(get_bitvm_key()?); + let instance_keypair = load_committee_instance_keypair(&committee_master_key, instance_id)?; + let local_committee_pubkey = instance_keypair.public_key().into(); + let (sec_nonce, _, _) = + committee_master_key.nonce_for_instance_with_keypair(instance_id, instance_keypair); + let agg_nonce = + nonce_aggregation(&pub_nonces.iter().map(|(_, pn)| pn.clone()).collect::>()); + let instance_params = get_instance_parameters(ctx.local_db, instance_id) + .await? + .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; + let mut pegin_confirm = instance_params.build_pegin_tx()?.1; + let context = instance_params.get_committee_context(instance_keypair)?; + let partial_sig = pegin_confirm + .sign_input_0_musig2(&context, &sec_nonce, &agg_nonce) + .map_err(|e| anyhow!("Failed to sign pegin confirm for {instance_id}: {e}"))?; + let endorse_sig = + endorse_pegin(ctx.goat_client, instance_id, &pegin_confirm.tx().compute_txid()).await?; + let message_content = GOATMessageContent::PeginConfirmPartialSig(PeginConfirmPartialSig { + instance_id, + committee_pubkey: local_committee_pubkey, + partial_sig, + endorse_sig: endorse_sig.as_bytes().to_vec(), + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Committee, message_content)).await?; + store_committee_partial_sig_for_instance( + ctx.local_db, + instance_id, + local_committee_pubkey, + partial_sig, + ) + .await?; + store_committee_endorse_sig_for_pegin( + ctx.local_db, + instance_id, + local_committee_pubkey, + endorse_sig.as_bytes().to_vec(), + ) + .await?; + // 4. (Relayer) if received enough partial signatures, aggregate the sigs + if is_relayer() { + let partial_sigs = get_committee_partial_sigs_for_instance(ctx.local_db, instance_id) + .await? + .into_iter() + .map(|(_, ps)| ps) + .collect::>(); + let context = instance_params.get_base_context(); + if partial_sigs.len() == committee_pubkeys.len() { + let full_sig = pegin_confirm + .aggregate_input_0_musig2_signatures(&context, partial_sigs, &agg_nonce) + .map_err(|e| { + anyhow!( + "Failed to aggregate Pegin-Confirm's signatures for {instance_id}: {e}" + ) + })?; + let connector_z = ConnectorZ::new( + context.network, + &context.n_of_n_taproot_public_key, + &instance_params.user_info.user_xonly_pubkey, + ); + pegin_confirm.push_input_0_signature(&connector_z, full_sig); + broadcast_tx(ctx.btc_client, pegin_confirm.tx()).await?; } } - } else { - tracing::info!( - "Ignore KickoffSent for {instance_id}:{graph_id}: withdraw already initiated, status: {withdraw_status:?}" - ); } Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_kickoff_sent_default( - ctx: &mut HandlerContext<'_>, - instance_id: Uuid, - graph_id: Uuid, - content: &GOATMessageContent, -) -> Result<()> { - // triggered by Kickoff tx - let message = make_message(ctx, content); - let _graph = refresh_graph_status( - ctx, - instance_id, - graph_id, - Some(&message), - GraphStatus::OperatorKickOff, - ) - .await?; - Ok(()) -} - -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_prekickoff_sent_challenger( +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] +async fn handle_pegin_confirm_partial_sig_committee( ctx: &mut HandlerContext<'_>, instance_id: Uuid, - graph_id: Uuid, - content: &GOATMessageContent, + received_committee_pubkey: &PublicKey, + partial_sig: &musig2::PartialSignature, + endorse_sig: &[u8], ) -> Result<()> { - // triggered by PreKickoff tx - let message = make_message(ctx, content); - let (graph, _graph_status, _graph_sub_status) = match refresh_graph_status( + // received from Committee members + if !ensure_self_or_valid_committee( ctx, instance_id, - graph_id, - Some(&message), - GraphStatus::PreKickoff, - ) - .await? - { - Some(v) => v, - None => return Ok(()), - }; - // 1. check the previous graph status - if !tx_on_chain( - ctx.btc_client, - &graph.parameters.prekickoff_parameters.cur_prekickoff_txn.tx().compute_txid(), + None, + received_committee_pubkey, + "PeginConfirmPartialSig", ) .await? { - tracing::warn!( - "Ignore PreKickoffSent for {instance_id}:{graph_id}: prekickoff tx not on chain" - ); return Ok(()); } - let graph_nonce = graph.parameters.graph_nonce; - if graph_nonce == 0 { - return Ok(()); - } - let (prev_instance_id, prev_graph_id) = - get_graph_id_by_nonce(ctx.local_db, graph_nonce - 1, &graph.parameters.operator_pubkey) - .await? - .ok_or_else(|| anyhow!("Prev graph not found for {instance_id}:{graph_id}"))?; - let prev_graph = match get_graph_or_defer( - ctx.swarm, + // 1. save the partial signature & endorsement signature to local db + // partial sigs will be validated when aggregating + store_committee_partial_sig_for_instance( ctx.local_db, - ctx.goat_client, - prev_instance_id, - prev_graph_id, - &message, + instance_id, + *received_committee_pubkey, + *partial_sig, ) - .await? - { - Some(g) => g, - None => return Ok(()), - }; - let prev_graph = Bitvm2Graph::from_simplified(&prev_graph)?; - let prev_graph_start_status = get_graph_status(ctx.local_db, prev_instance_id, prev_graph_id) - .await? - .ok_or_else(|| anyhow!("Graph status not found for {prev_instance_id}:{prev_graph_id}"))?; - let (prev_graph_status, _prev_graph_sub_status) = refresh_and_compensate( - ctx, - prev_instance_id, - prev_graph_id, - Some(&prev_graph), - Some(prev_graph_start_status), - prev_graph_start_status, + .await?; + store_committee_endorse_sig_for_pegin( + ctx.local_db, + instance_id, + *received_committee_pubkey, + endorse_sig.to_owned(), ) .await?; - if !tx_on_chain(ctx.btc_client, &prev_graph.kickoff.tx().compute_txid()).await? { - // 2. if previous kickoff not started, broadcast force-skip-kickoff txn - challenger_force_skip_kickoff(ctx.btc_client, &prev_graph).await?; - } else if !prev_graph_status.is_closed() { - // 3. if previous kickoff is not closed, broadcast quick-challenge/challenge-incomplete-kickoff txn - challenger_quick_challenge(ctx.btc_client, &prev_graph).await?; + // 3. (Relayer) if received enough partial signatures, aggregate the sigs + if is_relayer() { + let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; + let pub_nonces = get_committee_pub_nonces_for_instance(ctx.local_db, instance_id).await?; + let partial_sigs = get_committee_partial_sigs_for_instance(ctx.local_db, instance_id) + .await? + .into_iter() + .map(|(_, ps)| ps) + .collect::>(); + if pub_nonces.len() == committee_pubkeys.len() + && partial_sigs.len() == committee_pubkeys.len() + { + let instance_params = get_instance_parameters(ctx.local_db, instance_id) + .await? + .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; + let mut pegin_confirm = instance_params.build_pegin_tx()?.1; + let agg_nonce = + nonce_aggregation(&pub_nonces.iter().map(|(_, pn)| pn.clone()).collect::>()); + let context = instance_params.get_base_context(); + let full_sig = pegin_confirm + .aggregate_input_0_musig2_signatures(&context, partial_sigs, &agg_nonce) + .map_err(|e| { + anyhow!("Failed to aggregate Pegin-Confirm's signatures for {instance_id}: {e}") + })?; + let connector_z = ConnectorZ::new( + context.network, + &context.n_of_n_taproot_public_key, + &instance_params.user_info.user_xonly_pubkey, + ); + pegin_confirm.push_input_0_signature(&connector_z, full_sig); + broadcast_tx(ctx.btc_client, pegin_confirm.tx()).await?; + } + } + Ok(()) +} + +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id))] +async fn handle_post_ready(ctx: &mut HandlerContext<'_>, instance_id: Uuid) -> Result<()> { + // triggered by PeginConfirm tx + if !is_relayer() { + return Ok(()); + } + // 1. (Relayer)call Gateway.postPeginData on GoatChain + let committee_pubkeys = ctx.goat_client.gateway_get_committee_pubkeys(&instance_id).await?; + let pegin_data = ctx.goat_client.gateway_get_pegin_data(&instance_id).await?; + if pegin_data.status == PeginStatus::None { + tracing::warn!("Ignore PostReady for {instance_id}: not a pending pegin request"); + return Ok(()); + } else if pegin_data.status == PeginStatus::Pending { + let instance_params = get_instance_parameters(ctx.local_db, instance_id) + .await? + .ok_or_else(|| anyhow!("Instance parameters not found for {instance_id}"))?; + let pegin_confirm = instance_params.build_pegin_tx()?.1; + let pegin_txid = pegin_confirm.tx().compute_txid(); + let pegin_tx = match ctx.btc_client.get_tx(&pegin_txid).await? { + Some(tx) => tx, + None => { + tracing::warn!( + "Ignore PostReady for {instance_id}: Pegin-Confirm transaction not found on Bitcoin: {pegin_txid}" + ); + return Ok(()); + } + }; + let endorse_sigs = get_committee_endorse_sigs_for_pegin(ctx.local_db, instance_id) + .await? + .into_iter() + .map(|(_, es)| es) + .collect::>(); + if endorse_sigs.len() != committee_pubkeys.len() { + tracing::warn!( + "Ignore PostReady for {instance_id}: not enough endorse sigs for pegin confirm tx: {}", + endorse_sigs.len() + ); + return Ok(()); + } + let pegin_height = match ctx.btc_client.get_tx_status(&pegin_txid).await?.block_height { + Some(height) => height as u64, + None => { + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let message = GOATMessage::new( + ctx.actor.clone(), + GOATMessageContent::PostReady(PostReady { instance_id }), + ); + push_local_unhandled_messages( + ctx.local_db, + instance_id, + &message, + delay_secs as usize, + ) + .await?; + tracing::info!( + "Retry postPeginData later for {instance_id}: pegin confirm tx not confirmed on btc yet" + ); + return Ok(()); + } + }; + let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; + if goat_confirmed_height < pegin_height { + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) + * (pegin_height - goat_confirmed_height); + let message = GOATMessage::new( + ctx.actor.clone(), + GOATMessageContent::PostReady(PostReady { instance_id }), + ); + push_local_unhandled_messages(ctx.local_db, instance_id, &message, delay_secs as usize) + .await?; + tracing::info!( + "Retry postPeginData later for {instance_id}: pegin confirm tx block not posted to goat spv contract yet" + ); + return Ok(()); + } + ctx.goat_client + .gateway_post_pegin_data(ctx.btc_client, &instance_id, &pegin_tx, &endorse_sigs) + .await?; + } else { + // already posted + } + // 2. (Relayer)call Gateway.postGraphData on GoatChain + let graph_ids = get_graph_ids_for_instance(ctx.local_db, instance_id).await?; + for graph_id in &graph_ids { + let graph_data = ctx.goat_client.gateway_get_graph_data(graph_id).await?; + if graph_data.operator_pubkey != [0u8; 32] { + // already posted + continue; + } + let endorsement_sigs = + get_committee_endorsements_for_graph(ctx.local_db, instance_id, *graph_id) + .await? + .into_iter() + .map(|(_, _, sig)| sig) + .collect::>(); + if endorsement_sigs.len() != committee_pubkeys.len() { + tracing::warn!( + "Ignore postGraphData for {instance_id}:{graph_id}: not enough endorse sigs for graph: {}", + endorsement_sigs.len() + ); + continue; + } + let graph = get_graph(ctx.local_db, instance_id, *graph_id) + .await? + .ok_or_else(|| anyhow!("Graph not found for {instance_id}:{graph_id}"))?; + let graph = BitvmGcGraph::from_simplified(&graph)?; + let graph_data = build_graph_data(&graph)?; + ctx.goat_client + .gateway_post_graph_data(&instance_id, graph_id, &graph_data, &endorsement_sigs) + .await?; } Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_prekickoff_sent_default( - ctx: &mut HandlerContext<'_>, - instance_id: Uuid, - graph_id: Uuid, -) -> Result<()> { - // triggered by PreKickoff tx - let _graph = - refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::PreKickoff).await?; - Ok(()) -} - -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_challenge_sent_operator( +async fn handle_kickoff_ready_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - challenge_txid: Txid, content: &GOATMessageContent, ) -> Result<()> { - // triggered by Challenge tx + // triggered by InitWithdraw event from GoatChain let message = make_message(ctx, content); - let (mut graph, _graph_status, _graph_sub_status) = match refresh_graph_status( - ctx, + let graph = match get_graph_or_defer( + ctx.swarm, + ctx.local_db, + ctx.goat_client, instance_id, graph_id, - Some(&message), - GraphStatus::Challenge, + &message, ) .await? { - Some(v) => v, + Some(g) => g, None => return Ok(()), }; - // 1. check the challenge tx status on Bitcoin chain - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - if tx_on_chain(ctx.btc_client, &watchtower_challenge_init_txid).await? { + let mut graph = BitvmGcGraph::from_simplified(&graph)?; + let operator_pubkey = graph.parameters.operator_pubkey; + let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); + let node_pubkey: PublicKey = operator_master_key.master_keypair().public_key().into(); + if node_pubkey != operator_pubkey { + tracing::warn!("Ignore KickoffReady for {instance_id}:{graph_id}: not my graph"); + return Ok(()); + } + // 1. check the withdraw status on GoatChain + let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; + if withdraw_status != WithdrawStatus::Initialized { tracing::warn!( - "Ignore ChallengeSent for {instance_id}:{graph_id}: watchtower challenge init already sent" + "Ignore KickoffReady for {instance_id}:{graph_id}: invalid withdraw status: {withdraw_status:?}" ); return Ok(()); } - let kickoff_txid = graph.kickoff.tx().compute_txid(); - if let Some(challenge_tx) = ctx.btc_client.get_tx(&challenge_txid).await? { - let challenge_outpoint = OutPoint { txid: kickoff_txid, vout: 0 }; - if challenge_tx.input[0].previous_output != challenge_outpoint { + // 2. check prekickoff nonce & broadcast previous pre-kickoff if needed + let start_nonce = + match get_latest_pegout_finalized_graph(ctx.local_db, &operator_pubkey).await? { + Some((n, _)) => n + 1, + None => 0, + }; + for current_nonce in start_nonce..graph.parameters.graph_nonce { + let (current_instance_id, current_graph_id) = match get_graph_id_by_nonce( + ctx.local_db, + current_nonce, + &operator_pubkey, + ) + .await? + { + Some(v) => v, + None => { + tracing::warn!( + "Ignore KickoffReady for {instance_id}:{graph_id}: missing previous graph {current_nonce}" + ); + return Ok(()); + } + }; + let current_graph = match get_graph_or_defer( + ctx.swarm, + ctx.local_db, + ctx.goat_client, + current_instance_id, + current_graph_id, + &message, + ) + .await? + { + Some(g) => g, + None => return Ok(()), + }; + let mut current_graph = BitvmGcGraph::from_simplified(¤t_graph)?; + let current_graph_start_status = + get_graph_status(ctx.local_db, current_instance_id, current_graph_id) + .await? + .ok_or_else(|| { + anyhow!("Graph status not found for {current_instance_id}:{current_graph_id}") + })?; + let (current_graph_status, _current_graph_sub_status, current_graph_scan) = refresh_graph( + ctx.local_db, + ctx.btc_client, + ctx.goat_client, + current_instance_id, + current_graph_id, + Some(¤t_graph), + Some(current_graph_start_status), + None, + ) + .await?; + compensate_graph_events( + ctx.local_db, + ctx.btc_client, + current_instance_id, + current_graph_id, + Some(¤t_graph), + current_graph_scan.as_ref(), + Some(current_graph_start_status), + current_graph_start_status, + current_graph_status, + ) + .await?; + if current_graph_status.is_closed() { + continue; + } else if current_graph_status.is_pegout_started() { tracing::warn!( - "Ignore ChallengeSent for {instance_id}:{graph_id}: invalid challenge tx input" + "Ignore KickoffReady for {instance_id}:{graph_id}: previous graph {current_graph_id} already started pegout" ); + let nonce_interval = + graph.parameters.graph_nonce - current_graph.parameters.graph_nonce; + let min_pegout_time_secs = take1_timelock(ctx.btc_client.network()) as u64 + * todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = min_pegout_time_secs * nonce_interval; + push_local_unhandled_messages( + ctx.local_db, + current_graph_id, + &message, + delay_secs as usize, + ) + .await?; + return Ok(()); + } else if current_graph_status.is_obsoleted() { + operator_skip_graph(ctx.btc_client, &mut current_graph).await?; + tracing::info!( + "Operator {operator_pubkey} skipped obsoleted graph {current_instance_id}:{current_graph_id}" + ); + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks + push_local_unhandled_messages( + ctx.local_db, + current_graph_id, + &message, + delay_secs as usize, + ) + .await?; return Ok(()); + } else { + let graph_data_on_goat = + ctx.goat_client.gateway_get_graph_data(¤t_graph_id).await?; + if graph_data_on_goat.operator_pubkey != [0u8; 32] { + tracing::warn!( + "Ignore KickoffReady for {instance_id}:{graph_id}: previous available graph exists for Operator {operator_pubkey}: {current_instance_id}:{current_graph_id}, please withdraw it first" + ); + let nonce_interval = + graph.parameters.graph_nonce - current_graph.parameters.graph_nonce; + let min_pegout_time_secs = take1_timelock(ctx.btc_client.network()) as u64 + * todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let delay_secs = min_pegout_time_secs * nonce_interval; + push_local_unhandled_messages( + ctx.local_db, + current_graph_id, + &message, + delay_secs as usize, + ) + .await?; + return Ok(()); + } else { + operator_skip_graph(ctx.btc_client, &mut current_graph).await?; + tracing::info!( + "Operator {operator_pubkey} skipped non-posted graph {current_instance_id}:{current_graph_id}" + ); + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); // wait for 1 blocks + push_local_unhandled_messages( + ctx.local_db, + current_graph_id, + &message, + delay_secs as usize, + ) + .await?; + return Ok(()); + } } - } else { - tracing::warn!( - "Ignore ChallengeSent for {instance_id}:{graph_id}: challenge tx not found on chain" - ); - return Ok(()); } - // 2. if the challenge is confirmed, sign & broadcast watchtower-challenge-init txn - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let watchtower_challenge_init_tx = - operator_sign_watchtower_challenge_init(operator_master_key.master_keypair(), &mut graph)?; - let anchor_vout = watchtower_challenge_init_tx.output.len() as u64 - 1; - let watchtower_challenge_init_tx_total_input_amount = - graph.watchtower_challenge_init.prev_outs().iter().map(|o| o.value).sum(); - let child_tx = build_cpfp_txns( - ctx.btc_client, - &watchtower_challenge_init_tx, - anchor_vout, - watchtower_challenge_init_tx_total_input_amount, - ) - .await?; - match child_tx { - Some(tx) => { - broadcast_package(ctx.btc_client, &[watchtower_challenge_init_tx, tx], true).await? - } - None => broadcast_tx(ctx.btc_client, &watchtower_challenge_init_tx).await?, - }; + // 3. sign & broadcast prekickoff & kickoff txns + operator_kickoff(ctx.btc_client, &mut graph).await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_challenge_sent_default( +async fn handle_kickoff_sent_committee( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, + content: &GOATMessageContent, ) -> Result<()> { - // triggered by Challenge tx - let _graph = - refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::Challenge).await?; + // triggered by Kickoff tx + // 1. update status + let (graph, _graph_status, _graph_sub_status) = + match refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::OperatorKickOff) + .await? + { + Some(v) => v, + None => return Ok(()), + }; + if !is_relayer() { + return Ok(()); + } + // 2. (Relayer) try to call Gateway.proceedWithdraw + let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; + if withdraw_status != WithdrawStatus::Initialized { + tracing::warn!( + "Ignore KickoffSent for {instance_id}:{graph_id}: invalid withdraw status: {withdraw_status:?}" + ); + return Ok(()); + } + let kickoff_txid = graph.kickoff.tx().compute_txid(); + let kickoff_tx = match ctx.btc_client.get_tx(&kickoff_txid).await? { + Some(tx) => tx, + None => { + tracing::warn!( + "Ignore KickoffSent for {instance_id}:{graph_id}: kickoff tx not found on chain: {kickoff_txid}" + ); + return Ok(()); + } + }; + let kickoff_height = match ctx.btc_client.get_tx_status(&kickoff_txid).await?.block_height { + Some(height) => height as u64, + None => { + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let message = make_message(ctx, content); + push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) + .await?; + tracing::info!( + "Retry proceedWithdraw later for {instance_id}:{graph_id}: kickoff tx not confirmed on btc yet" + ); + return Ok(()); + } + }; + let goat_confirmed_btc_height = ctx.goat_client.btc_spv_latest_height().await?; + if goat_confirmed_btc_height < kickoff_height { + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) + * (kickoff_height - goat_confirmed_btc_height); + let message = make_message(ctx, content); + push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) + .await?; + tracing::info!( + "Retry proceedWithdraw later for {instance_id}:{graph_id}: kickoff tx block not posted to goat spv contract yet" + ); + return Ok(()); + } + ctx.goat_client.gateway_process_withdraw(ctx.btc_client, &graph_id, &kickoff_tx).await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_watchtower_challenge_init_sent_watchtower( +async fn handle_kickoff_sent_verifier( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, content: &GOATMessageContent, ) -> Result<()> { - // triggered by WatchtowerChallengeInit tx + // triggered by Kickoff tx let message = make_message(ctx, content); - let (graph, graph_status, _graph_sub_status) = match refresh_graph_status( + let (graph, _graph_status, _graph_sub_status) = match refresh_graph_status( ctx, instance_id, graph_id, Some(&message), - GraphStatus::Challenge, + GraphStatus::OperatorKickOff, ) .await? { Some(v) => v, None => return Ok(()), }; - if graph_status != GraphStatus::Challenge { - tracing::warn!( - "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: graph status is {graph_status:?}" - ); - return Ok(()); - } - let watchtower_keypair = WatchtowerMasterKey::new(get_bitvm_key()?).master_keypair(); - let node_index = match graph - .parameters - .watchtower_pubkeys - .iter() - .position(|pk| *pk == watchtower_keypair.public_key().x_only_public_key().0) - { - Some(index) => index, + // 1. check kickoff tx status on Bitcoin chain + let kickoff_txid = graph.kickoff.tx().compute_txid(); + let kickoff_height = match ctx.btc_client.get_tx_status(&kickoff_txid).await?.block_height { + Some(height) => height, None => { tracing::warn!( - "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: node not a watchtower" + "Ignore KickoffSent for {instance_id}:{graph_id}: kickoff tx not confirmed yet" ); return Ok(()); } }; - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - if !tx_on_chain(ctx.btc_client, &watchtower_challenge_init_txid).await? { - tracing::warn!( - "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: watchtower challenge init not on chain" - ); - return Ok(()); - } - if outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, 2 * node_index as u64) - .await? - .is_some() + let take1_txid = graph.take1.tx().compute_txid(); + let (challenge_tx, _) = export_challenge_tx(&graph).unwrap(); + let kickoff_challenge_outpoint = challenge_tx.input[0].previous_output; + if let Some(spent_txid) = outpoint_spent_txid( + ctx.btc_client, + &kickoff_challenge_outpoint.txid, + kickoff_challenge_outpoint.vout as u64, + ) + .await? { + let spent_tx_name = if spent_txid == take1_txid { "Take1" } else { "Challenge" }; tracing::warn!( - "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: watchtower challenge already spent" + "Ignore KickoffSent for {instance_id}:{graph_id}: challenge connector already spent by {spent_tx_name} tx: {spent_txid}" ); return Ok(()); } - // 1. check the withdraw status on GoatChain, if the withdraw is invalid, sign & broadcast watchtower-challenge txn + // 2. check withdraw status, if it's invalid, sign & broadcast challenge txn let withdraw_status = ctx.goat_client.gateway_get_withdraw_data(&graph_id).await?.status; - if crate::env::should_always_challenge() - || [WithdrawStatus::None, WithdrawStatus::Canceled].contains(&withdraw_status) - { - let watchtower_proof = match get_watchtower_commitment( - ctx.local_db, - ctx.btc_client, - ctx.http_client, - instance_id, - graph_id, - ) - .await? - { - (Some(p), _) => p, - (None, wait_secs) => { - tracing::warn!( - "Retry WatchtowerChallengeInitSent for {instance_id}:{graph_id} later: watchtower proof not ready, retry after {wait_secs} seconds" - ); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, wait_secs).await?; - return Ok(()); - } - }; - let watchtower_challenge_txid = match send_watchtower_challenge_tx( - ctx.btc_client, - &graph, - node_index, - watchtower_proof, - ) - .await - { - Ok(txid) => txid, - Err(e) => { - tracing::warn!( - "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: failed to send watchtower challenge tx: {e}" - ); - return Ok(()); + let goat_confirmed_btc_height = ctx.goat_client.btc_spv_latest_height().await? as u32; + if [WithdrawStatus::None, WithdrawStatus::Canceled].contains(&withdraw_status) { + if kickoff_height >= goat_confirmed_btc_height { + tracing::warn!( + "Ignore KickoffSent for {instance_id}:{graph_id}: kickoff tx not confirmed by goat spv yet" + ); + return Ok(()); + } else { + let (challenge_tx, _) = export_challenge_tx(&graph).unwrap(); + let challenge_txid = challenge_tx.compute_txid(); + if ctx.btc_client.get_tx(&challenge_txid).await?.is_none() { + let challenge_txid = send_challenge_tx(ctx.btc_client, &graph).await?; + let message_content = GOATMessageContent::ChallengeSent(ChallengeSent { + instance_id, + graph_id, + challenge_txid, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; } - }; - let message_content = - GOATMessageContent::WatchtowerChallengeSent(WatchtowerChallengeSent { - instance_id, - graph_id, - watchtower_challenge_txids: vec![(node_index, watchtower_challenge_txid)], - }); - send_to_peer(ctx.swarm, GOATMessage::new(Actor::All, message_content)).await?; + } + } else { + tracing::info!( + "Ignore KickoffSent for {instance_id}:{graph_id}: withdraw already initiated, status: {withdraw_status:?}" + ); } Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_watchtower_challenge_sent_operator( +async fn handle_kickoff_sent_default( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - watchtower_challenge_txids: &Vec<(usize, Txid)>, content: &GOATMessageContent, ) -> Result<()> { - // triggered by WatchtowerChallenge tx + // triggered by Kickoff tx let message = make_message(ctx, content); - // 1. check the watchtower-challenge tx status on Bitcoin chain, if watchtower challenge tx is confirmed, sign & broadcast operator-ack txn - let (mut graph, graph_status, _graph_sub_status) = match refresh_graph_status( + let _graph = refresh_graph_status( ctx, instance_id, graph_id, Some(&message), - GraphStatus::Challenge, + GraphStatus::OperatorKickOff, ) - .await? - { - Some(v) => v, - None => return Ok(()), - }; - if graph_status != GraphStatus::Challenge { - tracing::warn!( - "Ignore WatchtowerChallengeSent for {instance_id}:{graph_id}: graph status is {graph_status:?}" - ); - return Ok(()); - } - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let operator_graph_keypair = operator_master_key.master_keypair(); - let operator_master_keypair = operator_master_key.master_keypair(); - for (watchtower_index, watchtower_challenge_txid) in watchtower_challenge_txids { - tracing::info!( - "Handle WatchtowerChallengeSent for {instance_id}:{graph_id}, watchtower index: {watchtower_index}, watchtower challenge txid: {watchtower_challenge_txid}" - ); - let watchtower_challenge_tx = match ctx.btc_client.get_tx(watchtower_challenge_txid).await? - { - Some(tx) => tx, - None => { - tracing::warn!( - "Ignore WatchtowerChallengeSent for {instance_id}:{graph_id}: watchtower challenge tx not found on chain: {watchtower_challenge_txid}" - ); - continue; - } - }; - let watchtower_challenge_outpoint = - OutPoint { txid: watchtower_challenge_init_txid, vout: 2 * *watchtower_index as u32 }; - if watchtower_challenge_tx.input[0].previous_output != watchtower_challenge_outpoint { - tracing::warn!( - "Ignore WatchtowerChallengeSent for {instance_id}:{graph_id}: invalid watchtower challenge tx input" - ); - continue; - } - let preimage = - todo_funcs::get_preimage(ctx.local_db, instance_id, graph_id, *watchtower_index) - .await?; - let (ack_txin, ack_txin_amount) = - operator_sign_ack(operator_graph_keypair, &mut graph, *watchtower_index, &preimage)?; - build_sign_and_broadcast_tx( - ctx.btc_client, - operator_master_keypair, - vec![ack_txin], - ack_txin_amount, - vec![], - ) - .await?; - } + .await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_watchtower_challenge_timeout_operator( +async fn handle_prekickoff_sent_verifier( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - watchtower_indexes: &Vec, content: &GOATMessageContent, ) -> Result<()> { - // triggered by timeout task + // triggered by PreKickoff tx let message = make_message(ctx, content); - let (mut graph, graph_status, _graph_sub_status) = match refresh_graph_status( + let (graph, _graph_status, _graph_sub_status) = match refresh_graph_status( ctx, instance_id, graph_id, Some(&message), - GraphStatus::Challenge, + GraphStatus::PreKickoff, ) .await? { Some(v) => v, None => return Ok(()), }; - if graph_status != GraphStatus::Challenge { + // 1. check the previous graph status + if !tx_on_chain( + ctx.btc_client, + &graph.parameters.prekickoff_parameters.cur_prekickoff_txn.tx().compute_txid(), + ) + .await? + { tracing::warn!( - "Ignore WatchtowerChallengeTimeout for {instance_id}:{graph_id}: graph status is {graph_status:?}" + "Ignore PreKickoffSent for {instance_id}:{graph_id}: prekickoff tx not on chain" ); return Ok(()); } - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let watchtower_challenge_init_height = match ctx - .btc_client - .get_tx_status(&watchtower_challenge_init_txid) - .await? - .block_height - { - Some(height) => height, - None => { - tracing::warn!( - "Ignore WatchtowerChallengeTimeout for {instance_id}:{graph_id}: watchtower challenge init tx not confirmed yet" - ); - return Ok(()); - } - }; - let current_height = ctx.btc_client.get_height().await?; - if current_height - < watchtower_challenge_init_height + watchtower_challenge_timeout_timelock(get_network()) - { - tracing::warn!( - "Ignore WatchtowerChallengeTimeout for {instance_id}:{graph_id}: watchtower challenge timelock not expired yet" - ); + let graph_nonce = graph.parameters.graph_nonce; + if graph_nonce == 0 { return Ok(()); } - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let operator_master_keypair = operator_master_key.master_keypair(); - // 1. sign & broadcast watchtower-challenge-timeout txn - for watchtower_index in watchtower_indexes { - tracing::info!( - "Handle WatchtowerChallengeTimeout for {instance_id}:{graph_id}, watchtower index: {watchtower_index}" - ); - let watchtower_challenge_vout = 2 * *watchtower_index as u64; - if outpoint_spent_txid( - ctx.btc_client, - &watchtower_challenge_init_txid, - watchtower_challenge_vout, - ) + let (prev_instance_id, prev_graph_id) = + get_graph_id_by_nonce(ctx.local_db, graph_nonce - 1, &graph.parameters.operator_pubkey) + .await? + .ok_or_else(|| anyhow!("Prev graph not found for {instance_id}:{graph_id}"))?; + let prev_graph = match get_graph_or_defer( + ctx.swarm, + ctx.local_db, + ctx.goat_client, + prev_instance_id, + prev_graph_id, + &message, + ) + .await? + { + Some(g) => g, + None => return Ok(()), + }; + let prev_graph = BitvmGcGraph::from_simplified(&prev_graph)?; + let prev_graph_start_status = get_graph_status(ctx.local_db, prev_instance_id, prev_graph_id) .await? - .is_some() - { - tracing::warn!( - "Ignore WatchtowerChallengeTimeout for {instance_id}:{graph_id}: watchtower challenge already spent" - ); - continue; - } - let watchtower_challenge_timeout_tx = operator_sign_watchtower_challenge_timeout( - operator_master_keypair, - &mut graph, - *watchtower_index, - )?; - let anchor_vout = watchtower_challenge_timeout_tx.output.len() as u64 - 1; - let watchtower_challenge_timeout_tx_total_input_amount = graph - .watchtower_challenge_timeout_txns - .get(*watchtower_index) - .ok_or_else(|| anyhow!("WatchtowerChallengeTimeout txn not found for {instance_id}:{graph_id}:{watchtower_index}"))? - .prev_outs() - .iter() - .map(|o| o.value) - .sum(); - let child_tx = build_cpfp_txns( - ctx.btc_client, - &watchtower_challenge_timeout_tx, - anchor_vout, - watchtower_challenge_timeout_tx_total_input_amount, - ) - .await?; - match child_tx { - Some(tx) => { - broadcast_package(ctx.btc_client, &[watchtower_challenge_timeout_tx, tx], true) - .await? - } - None => broadcast_tx(ctx.btc_client, &watchtower_challenge_timeout_tx).await?, - }; + .ok_or_else(|| anyhow!("Graph status not found for {prev_instance_id}:{prev_graph_id}"))?; + let (prev_graph_status, _prev_graph_sub_status) = refresh_and_compensate( + ctx, + prev_instance_id, + prev_graph_id, + Some(&prev_graph), + Some(prev_graph_start_status), + prev_graph_start_status, + ) + .await?; + if !tx_on_chain(ctx.btc_client, &prev_graph.kickoff.tx().compute_txid()).await? { + // 2. if previous kickoff not started, broadcast force-skip-kickoff txn + verifier_force_skip_kickoff(ctx.btc_client, &prev_graph).await?; + } else if !prev_graph_status.is_closed() { + // 3. if previous kickoff is not closed, broadcast quick-challenge/challenge-incomplete-kickoff txn + verifier_quick_challenge(ctx.btc_client, &prev_graph).await?; } Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_operator_ack_timeout_challenger( +async fn handle_prekickoff_sent_default( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + graph_id: Uuid, +) -> Result<()> { + // triggered by PreKickoff tx + let _graph = + refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::PreKickoff).await?; + Ok(()) +} + +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_challenge_sent_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, + challenge_txid: Txid, content: &GOATMessageContent, ) -> Result<()> { - // triggered by timeout task + // triggered by Challenge tx let message = make_message(ctx, content); - let (graph, graph_status, _graph_sub_status) = match refresh_graph_status( + let (mut graph, _graph_status, _graph_sub_status) = match refresh_graph_status( ctx, instance_id, graph_id, @@ -2661,164 +3046,78 @@ async fn handle_operator_ack_timeout_challenger( Some(v) => v, None => return Ok(()), }; - if graph_status != GraphStatus::Challenge { - tracing::warn!( - "Ignore AckTimeout for {instance_id}:{graph_id}: graph status is {graph_status:?}" - ); - return Ok(()); - } + // 1. check the challenge tx status on Bitcoin chain let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let connector_f_vout = 1 + 2 * graph.parameters.watchtower_pubkeys.len() as u64; - if outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, connector_f_vout) - .await? - .is_some() - { + if tx_on_chain(ctx.btc_client, &watchtower_challenge_init_txid).await? { tracing::warn!( - "Ignore OperatorAckTimeout for {instance_id}:{graph_id}: connector_F already spent" + "Ignore ChallengeSent for {instance_id}:{graph_id}: watchtower challenge init already sent" ); return Ok(()); } - let current_height = ctx.btc_client.get_height().await?; - let watchtower_challenge_init_height = match ctx - .btc_client - .get_tx_status(&watchtower_challenge_init_txid) - .await? - .block_height - { - Some(height) => height, - None => { + let kickoff_txid = graph.kickoff.tx().compute_txid(); + if let Some(challenge_tx) = ctx.btc_client.get_tx(&challenge_txid).await? { + let challenge_outpoint = OutPoint { txid: kickoff_txid, vout: 0 }; + if challenge_tx.input[0].previous_output != challenge_outpoint { tracing::warn!( - "Ignore OperatorAckTimeout for {instance_id}:{graph_id}: watchtower challenge init tx not confirmed yet" + "Ignore ChallengeSent for {instance_id}:{graph_id}: invalid challenge tx input" ); return Ok(()); } - }; - if current_height < watchtower_challenge_init_height + nack_timelock(get_network()) { + } else { tracing::warn!( - "Ignore OperatorAckTimeout for {instance_id}:{graph_id}: nack timelock not expired yet" + "Ignore ChallengeSent for {instance_id}:{graph_id}: challenge tx not found on chain" ); return Ok(()); } - let mut nack_index = None; - for watchtower_index in 0..graph.parameters.watchtower_pubkeys.len() { - let ack_vout = 1 + 2 * watchtower_index as u64; - if outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, ack_vout) - .await? - .is_none() - { - nack_index = Some(watchtower_index); - break; - } - } - let nack_index = match nack_index { - Some(index) => index, - None => { - tracing::warn!( - "Ignore OperatorAckTimeout for {instance_id}:{graph_id}: all ack connectors already spent" - ); - return Ok(()); - } - }; - // 1. broadcast Nack txn - let nack_tx = graph - .nack_txns - .get(nack_index) - .ok_or_else(|| anyhow!("Nack txn not found for {instance_id}:{graph_id}:{nack_index}"))? - .finalize(); - let anchor_vout = nack_tx.output.len() as u64 - 1; - let nack_tx_total_input_amount = - graph.nack_txns[nack_index].prev_outs().iter().map(|o| o.value).sum(); - let child_tx = - build_cpfp_txns(ctx.btc_client, &nack_tx, anchor_vout, nack_tx_total_input_amount).await?; + // 2. if the challenge is confirmed, sign & broadcast watchtower-challenge-init txn + let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); + let watchtower_challenge_init_tx = + operator_sign_watchtower_challenge_init(operator_master_key.master_keypair(), &mut graph)?; + let anchor_vout = watchtower_challenge_init_tx.output.len() as u64 - 1; + let watchtower_challenge_init_tx_total_input_amount = + graph.watchtower_challenge_init.prev_outs().iter().map(|o| o.value).sum(); + let child_tx = build_cpfp_txns( + ctx.btc_client, + &watchtower_challenge_init_tx, + anchor_vout, + watchtower_challenge_init_tx_total_input_amount, + ) + .await?; match child_tx { - Some(tx) => broadcast_package(ctx.btc_client, &[nack_tx, tx], true).await?, - None => broadcast_package(ctx.btc_client, &[nack_tx], true).await?, + Some(tx) => { + broadcast_package(ctx.btc_client, &[watchtower_challenge_init_tx, tx], true).await? + } + None => broadcast_tx(ctx.btc_client, &watchtower_challenge_init_tx).await?, }; + let message_content = + GOATMessageContent::WatchtowerChallengeInitSent(WatchtowerChallengeInitSent { + instance_id, + graph_id, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Watchtower, message_content)).await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_operator_commit_blockhash_ready_operator( +async fn handle_challenge_sent_default( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - content: &GOATMessageContent, ) -> Result<()> { - // triggered by timeout task - let message = make_message(ctx, content); - let (mut graph, graph_status, _graph_sub_status) = match refresh_graph_status( - ctx, - instance_id, - graph_id, - Some(&message), - GraphStatus::Challenge, - ) - .await? - { - Some(v) => v, - None => return Ok(()), - }; - if graph_status != GraphStatus::Challenge { - tracing::warn!( - "Ignore CommitBlockHashReady for {instance_id}:{graph_id}: graph status is {graph_status:?}" - ); - return Ok(()); - } - // 1. check that all WatchtowerChallenge Connectors are spent - let largest_watchtower_challenge_block_hash = match get_largest_watchtower_challenge_block( - &graph, - ctx.btc_client, - ) - .await - { - Ok(d) => d, - Err(e) => { - tracing::warn!( - "Retry OperatorCommitBlockHashReady later for {instance_id}:{graph_id}: failed to get - largest watchtower challenge block, error: {e:?}" - ); - push_local_unhandled_messages( - ctx.local_db, - graph_id, - &message, - todo_funcs::avg_block_time_secs(ctx.btc_client.network()) as usize, - ) - .await?; - return Ok(()); - } - }; - // 2. sign & broadcast commit-blockhash txn - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let operator_graph_keypair = operator_master_key.master_keypair(); - let operator_master_keypair = operator_master_key.master_keypair(); - let wots_secret_keys = operator_master_key.wots_keypair_for_graph(graph.parameters.graph_id).0; - let blockhash_wots_secret_key = &wots_secret_keys[0]; - let (operator_commit_blockhash_txin, operator_commit_blockhash_txin_amount) = - operator_sign_blockhash_commit( - operator_graph_keypair, - &mut graph, - &largest_watchtower_challenge_block_hash.to_byte_array(), - blockhash_wots_secret_key, - )?; - build_sign_and_broadcast_tx( - ctx.btc_client, - operator_master_keypair, - vec![operator_commit_blockhash_txin], - operator_commit_blockhash_txin_amount, - vec![], - ) - .await?; + // triggered by Challenge tx + let _graph = + refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::Challenge).await?; Ok(()) } #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_operator_commit_blockhash_timeout_challenger( +async fn handle_watchtower_challenge_init_sent_watchtower( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, content: &GOATMessageContent, ) -> Result<()> { - // triggered by timeout task + // triggered by WatchtowerChallengeInit tx let message = make_message(ctx, content); let (graph, graph_status, _graph_sub_status) = match refresh_graph_status( ctx, @@ -2834,484 +3133,473 @@ async fn handle_operator_commit_blockhash_timeout_challenger( }; if graph_status != GraphStatus::Challenge { tracing::warn!( - "Ignore CommitBlockHashTimeout for {instance_id}:{graph_id}: graph status is {graph_status:?}" - ); - return Ok(()); - } - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let connector_f_vout = 1 + 2 * graph.parameters.watchtower_pubkeys.len() as u64; - if outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, connector_f_vout) - .await? - .is_some() - { - tracing::warn!( - "Ignore OperatorCommitBlockHashTimeout for {instance_id}:{graph_id}: connector_F already spent" + "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: graph status is {graph_status:?}" ); return Ok(()); } - let watchtower_challenge_init_height = match ctx - .btc_client - .get_tx_status(&watchtower_challenge_init_txid) - .await? - .block_height + let watchtower_keypair = WatchtowerMasterKey::new(get_bitvm_key()?).master_keypair(); + let node_index = match graph + .parameters + .watchtower_pubkeys + .iter() + .position(|pk| *pk == watchtower_keypair.public_key().x_only_public_key().0) { - Some(height) => height, + Some(index) => index, None => { tracing::warn!( - "Ignore OperatorCommitBlockHashTimeout for {instance_id}:{graph_id}: watchtower challenge init tx not confirmed yet" + "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: node not a watchtower" ); return Ok(()); } }; - let current_height = ctx.btc_client.get_height().await?; - if current_height - < watchtower_challenge_init_height + commit_blockhash_timeout_timelock(get_network()) - { + let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); + if !tx_on_chain(ctx.btc_client, &watchtower_challenge_init_txid).await? { tracing::warn!( - "Ignore OperatorCommitBlockHashTimeout for {instance_id}:{graph_id}: commit-blockhash timelock not expired yet" + "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: watchtower challenge init not on chain" ); return Ok(()); } - let connector_g_vout = 2 * graph.parameters.watchtower_pubkeys.len() as u64; - if outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, connector_g_vout) + if outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, node_index as u64) .await? .is_some() { tracing::warn!( - "Ignore OperatorCommitBlockHashTimeout for {instance_id}:{graph_id}: connector_G already spent" + "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: watchtower challenge already spent" ); return Ok(()); } - // 1. broadcast OperatorCommitBlockHashTimeout txn - let blockhash_commit_timeout_tx = graph.blockhash_commit_timeout.finalize(); - let anchor_vout = blockhash_commit_timeout_tx.output.len() as u64 - 1; - let blockhash_commit_timeout_tx_total_input_amount = - graph.blockhash_commit_timeout.prev_outs().iter().map(|o| o.value).sum(); - let child_tx = build_cpfp_txns( + // watchtower should always challenge + let watchtower_proof = match get_watchtower_commitment( + ctx.local_db, ctx.btc_client, - &blockhash_commit_timeout_tx, - anchor_vout, - blockhash_commit_timeout_tx_total_input_amount, + ctx.http_client, + instance_id, + graph_id, ) - .await?; - match child_tx { - Some(tx) => { - broadcast_package(ctx.btc_client, &[blockhash_commit_timeout_tx, tx], true).await? + .await? + { + (Some(p), _) => p, + (None, wait_secs) => { + tracing::warn!( + "Retry WatchtowerChallengeInitSent for {instance_id}:{graph_id} later: watchtower proof not ready, retry after {wait_secs} seconds" + ); + push_local_unhandled_messages(ctx.local_db, graph_id, &message, wait_secs).await?; + return Ok(()); + } + }; + let watchtower_challenge_txid = match send_watchtower_challenge_tx( + ctx.btc_client, + &graph, + node_index, + watchtower_proof, + ) + .await + { + Ok(txid) => txid, + Err(e) => { + tracing::warn!( + "Ignore WatchtowerChallengeInitSent for {instance_id}:{graph_id}: failed to send watchtower challenge tx: {e}" + ); + return Ok(()); } - None => broadcast_tx(ctx.btc_client, &blockhash_commit_timeout_tx).await?, }; + tracing::info!( + "WatchtowerChallengeSent for {instance_id}:{graph_id}: watchtower_index={node_index}, txid={watchtower_challenge_txid}" + ); + let message_content = GOATMessageContent::WatchtowerChallengeSent(WatchtowerChallengeSent { + instance_id, + graph_id, + watchtower_index: node_index, + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Operator, message_content)).await?; Ok(()) } +// after the watchtower challenge flow is complete, build proof and broadcast Assert transaction. #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_assert_init_ready_operator( +async fn handle_assert_ready_operator( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - content: &GOATMessageContent, ) -> Result<()> { - // triggered by timeout task - let message = make_message(ctx, content); - let (mut graph, graph_status, graph_sub_status) = match refresh_graph_status( - ctx, + let message = GOATMessage::new( + Actor::Operator, + GOATMessageContent::AssertReady(AssertReady { instance_id, graph_id }), + ); + let (mut graph, _graph_status, _graph_sub_status) = + match refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::Challenge).await? + { + Some(v) => v, + None => return Ok(()), + }; + let (operator_wrapper_proof, wait_secs) = get_operator_wrapper_proof( + ctx.local_db, + ctx.http_client, + &graph, + ctx.btc_client, instance_id, graph_id, - Some(&message), - GraphStatus::Challenge, ) - .await? + .await?; + + if wait_secs > 0 { + tracing::info!( + "Retry AssertReady later for {instance_id}:{graph_id}: operator proof is not ready" + ); + push_local_unhandled_messages(ctx.local_db, graph_id, &message, wait_secs).await?; + return Ok(()); + } + + let Some(operator_wrapper_proof) = operator_wrapper_proof else { + return Ok(()); + }; + let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); + let assert_secret_key = operator_master_key.wots_keypair_for_graph(graph_id).0; + let assert_witness = build_assert_witness(&operator_wrapper_proof.proof, &assert_secret_key)?; + let assert_message = assert_wots_message(&assert_witness)?; + let mut asserted_wrapper_proof = Vec::new(); + operator_wrapper_proof.proof.serialize_compressed(&mut asserted_wrapper_proof)?; + let mut setup_state = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .ok_or_else(|| anyhow!("missing operator BABE setup state for graph {graph_id}"))?; + let operator_state = setup_state + .operator + .as_mut() + .ok_or_else(|| anyhow!("missing operator BABE setup state for graph {graph_id}"))?; + if let Some(existing) = &operator_state.asserted_wrapper_proof + && existing != &asserted_wrapper_proof { - Some(v) => v, - None => return Ok(()), + bail!("operator assertion wrapper proof conflicts with persisted proof"); + } + operator_state.asserted_wrapper_proof = Some(asserted_wrapper_proof); + save_babe_setup_state(ctx.local_db, instance_id, graph_id, &setup_state)?; + + let assert_tx = operator_sign_assert(&mut graph, &assert_secret_key, &assert_message)?; + broadcast_nonstandard_tx(ctx.btc_client, &assert_tx).await?; + + let message_content = GOATMessageContent::AssertSent(AssertSent { + instance_id, + graph_id, + assert_txid: assert_tx.compute_txid(), + assert_witness: Some(assert_witness), + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Verifier, message_content)).await?; + + Ok(()) +} + +// verify Operator DynamicPublicInput and Proof; broadcast PubinDisprove or ChallengeAssert as needed. +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_assert_sent_verifier( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + graph_id: Uuid, + assert_txid: Txid, + assert_witness: &Option, +) -> Result<()> { + let (graph, _graph_status, _graph_sub_status) = + match refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::Challenge).await? + { + Some(v) => v, + None => return Ok(()), + }; + + let Some(assert_tx) = ctx.btc_client.get_tx(&assert_txid).await? else { + tracing::warn!( + "Ignore AssertSent for {instance_id}:{graph_id}: assert tx {assert_txid} not found on chain" + ); + return Ok(()); }; - if graph_status != GraphStatus::Challenge { + let Some(assert_witness) = assert_witness else { tracing::warn!( - "Ignore AssertInitReady for {instance_id}:{graph_id}: graph status is {graph_status:?}" + "Ignore AssertSent for {instance_id}:{graph_id}: assert witness is not available in message" + ); + return Ok(()); + }; + + let verifier_master_key = VerifierMasterKey::new(get_bitvm_key()?); + let verifier_pubkey = verifier_master_key.master_keypair().public_key().into(); + let Some(verifier_index) = + find_verifier_index_by_pubkey(&graph.parameters.gc_data, &verifier_pubkey)? + else { + tracing::debug!( + "Ignore AssertSent for {instance_id}:{graph_id}: local verifier has no graph slot" + ); + return Ok(()); + }; + validate_verifier_slot(&graph, verifier_index)?; + + let Some(saved_verifier_state) = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .and_then(|state| state.verifier) + else { + tracing::warn!( + "Ignore AssertSent for {instance_id}:{graph_id}: missing BABE verifier setup state" + ); + return Ok(()); + }; + if saved_verifier_state.verifier_index != Some(verifier_index) { + tracing::warn!( + "Ignore AssertSent for {instance_id}:{graph_id}: local setup slot does not match graph owner slot" ); return Ok(()); } - match graph_sub_status { - Some(sub_status) => { - if !sub_status.is_watchtower_challenge_success() { - tracing::warn!( - "Ignore AssertInitReady for {instance_id}:{graph_id}: watchtower challenge not finished yet, sub-status {sub_status:?}" - ); - return Ok(()); - } - } - None => { - tracing::warn!( - "Ignore AssertInitReady for {instance_id}:{graph_id}: graph sub-status is None" - ); - return Ok(()); - } - } - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let operator_graph_keypair = operator_master_key.master_keypair(); - let assert_init_txid = graph.assert_init.tx().compute_txid(); - // uncomment the following lines to check operator proof before sending assert-init txn - // let (proof_opt, wait_secs) = - // get_operator_proof(local_db, http_client, instance_id, graph_id).await?; - // if proof_opt.is_none() { - // tracing::warn!( - // "Retry AssertInitReady for {instance_id}:{graph_id} later: operator proof not ready, retry after {wait_secs} seconds" - // ); - // push_local_unhandled_messages(local_db, graph_id, &message, wait_secs).await?; - // return Ok(()); - // } - // 1. sign & broadcast assert-init txn - if !tx_on_chain(ctx.btc_client, &assert_init_txid).await? { - let assert_init_tx = operator_sign_assert_init(operator_graph_keypair, &mut graph)?; - let anchor_vout = assert_init_tx.output.len() as u64 - 1; - let assert_init_tx_total_input_amount = - graph.assert_init.prev_outs().iter().map(|o| o.value).sum(); - let child_tx = build_cpfp_txns( - ctx.btc_client, - &assert_init_tx, - anchor_vout, - assert_init_tx_total_input_amount, - ) - .await?; - match child_tx { - Some(tx) => broadcast_package(ctx.btc_client, &[assert_init_tx, tx], true).await?, - None => broadcast_tx(ctx.btc_client, &assert_init_tx).await?, + let vk = crate::vk::get_vk().await.context("load Groth16 verifying key for BABE challenge")?; + let public_inputs = derive_operator_wrapper_statement(graph_id)?.public_inputs; + let challenge_witness = build_real_challenge_assert_witness( + &saved_verifier_state.private_state, + &saved_verifier_state.setup_package, + &saved_verifier_state.finalized_indices, + &vk, + &public_inputs, + &graph.parameters.operator_wots_pubkeys, + assert_witness, + verifier_index, + )?; + let labels: [Vec; goat::assert_scripts::INPUT_WIRE_NUM] = challenge_witness + .input_labels + .iter() + .map(|label| label.to_vec()) + .collect::>() + .try_into() + .map_err(|labels: Vec>| { + anyhow!( + "BABE challenge witness exposes {} labels; expected {}", + labels.len(), + goat::assert_scripts::INPUT_WIRE_NUM + ) + })?; + let operator_assert_txin = assert_tx + .input + .first() + .cloned() + .ok_or_else(|| anyhow!("operator assert transaction has no input"))?; + let challenge_assert_tx = + build_verifier_assert_tx(&graph, operator_assert_txin, verifier_index, labels)?; + broadcast_nonstandard_tx(ctx.btc_client, &challenge_assert_tx).await?; + let message_content = GOATMessageContent::ChallengeAssertSent(ChallengeAssertSent { + instance_id, + graph_id, + challenge_assert_txid: challenge_assert_tx.compute_txid(), + verifier_index, + challenge_witness: Some(challenge_witness), + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Operator, message_content)).await?; + + Ok(()) +} + +// compute msg after ChallengeAssert is broadcast and broadcast WronglyChallenge transaction. +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +async fn handle_challenge_assert_sent_operator( + ctx: &mut HandlerContext<'_>, + instance_id: Uuid, + graph_id: Uuid, + challenge_assert_txid: Txid, + verifier_index: usize, + challenge_witness: &Option, + content: &GOATMessageContent, +) -> Result<()> { + let (graph, _graph_status, _graph_sub_status) = + match refresh_graph_status(ctx, instance_id, graph_id, None, GraphStatus::Challenge).await? + { + Some(v) => v, + None => return Ok(()), }; - // assert-commit should be broadcasted after assert-init is confirmed (wait for 1 block) - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) - .await?; - return Ok(()); - } - let connector_d_vout = graph.assert_commit_timeout_txns.len() as u64; - if outpoint_spent_txid(ctx.btc_client, &assert_init_txid, connector_d_vout).await?.is_some() { + let Some(challenge_witness) = challenge_witness else { tracing::warn!( - "Ignore AssertInitReady for {instance_id}:{graph_id}: connector_D already spent" + "Ignore ChallengeAssertSent for {instance_id}:{graph_id}: challenge witness is not available in message" ); return Ok(()); - } - // 2. sign & broadcast assert-commit txns - if !tx_confirmed(ctx.btc_client, &assert_init_txid).await? { - // assert-commit should be broadcasted after assert-init is confirmed (wait for 1 block) + }; + + validate_verifier_slot(&graph, verifier_index)?; + validate_challenge_witness_index(verifier_index, challenge_witness)?; + validate_expected_challenge_assert_txid(&graph, verifier_index, challenge_assert_txid)?; + + let Some(challenge_assert_tx) = ctx.btc_client.get_tx(&challenge_assert_txid).await? else { let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let message = make_message(ctx, content); push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) .await?; + tracing::info!( + "Retry ChallengeAssertSent later for {instance_id}:{graph_id}: challenge assert tx {challenge_assert_txid} not found on chain" + ); return Ok(()); - } else { - let (split_txid_opt, has_pending_fee_input, proof_wait_secs_opt) = - operator_send_assert_commit(ctx.local_db, ctx.btc_client, ctx.http_client, &mut graph) - .await?; - if let Some(wait_secs) = proof_wait_secs_opt { - tracing::warn!( - "Retry AssertInitReady for {instance_id}:{graph_id} later: operator proof not ready, retry after {wait_secs} seconds" - ); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, wait_secs).await?; - return Ok(()); - } else if let Some(split_txid) = split_txid_opt { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) * 2; - tracing::warn!( - "Retry AssertInitReady for {instance_id}:{graph_id} later: fee_inputs_split_tx {split_txid} broadcasted, retry after {delay_secs} seconds" - ); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) - .await?; - } else if has_pending_fee_input { - let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) * 2; - tracing::warn!( - "Retry AssertInitReady for {instance_id}:{graph_id} later: some fee inputs are pending, retry after {delay_secs} seconds", - ); - push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) - .await?; - } + }; + + let labels: [Vec; goat::assert_scripts::INPUT_WIRE_NUM] = challenge_witness + .input_labels + .iter() + .map(|label| label.to_vec()) + .collect::>() + .try_into() + .map_err(|labels: Vec>| { + anyhow!( + "BABE challenge witness exposes {} labels; expected {}", + labels.len(), + goat::assert_scripts::INPUT_WIRE_NUM + ) + })?; + let operator_assert_txid = challenge_assert_tx + .input + .first() + .ok_or_else(|| anyhow!("published verifier assert transaction has no input"))? + .previous_output + .txid; + let operator_assert_tx = + ctx.btc_client.get_tx(&operator_assert_txid).await?.ok_or_else(|| { + anyhow!("published operator assert transaction {operator_assert_txid} is unavailable") + })?; + let operator_assert_txin = operator_assert_tx + .input + .first() + .cloned() + .ok_or_else(|| anyhow!("published operator assert transaction has no input"))?; + let expected_challenge_assert = + build_verifier_assert_tx(&graph, operator_assert_txin, verifier_index, labels)?; + if expected_challenge_assert.input.first().map(|input| &input.witness) + != challenge_assert_tx.input.first().map(|input| &input.witness) + { + bail!("published verifier assert witness does not match BABE challenge labels"); } + + let setup_state = load_babe_setup_state(ctx.local_db, instance_id, graph_id)? + .ok_or_else(|| anyhow!("missing operator BABE setup state for graph {graph_id}"))?; + let operator_state = setup_state + .operator + .ok_or_else(|| anyhow!("missing operator BABE setup state for graph {graph_id}"))?; + let candidate = operator_state + .candidates + .iter() + .find(|candidate| candidate.verifier_index == Some(verifier_index)) + .ok_or_else(|| anyhow!("missing BABE prover state for verifier slot {verifier_index}"))?; + let prover_state = candidate + .prover_state + .as_ref() + .ok_or_else(|| anyhow!("missing BABE prover state for verifier slot {verifier_index}"))?; + let proof_bytes = operator_state + .asserted_wrapper_proof + .as_ref() + .ok_or_else(|| anyhow!("missing asserted wrapper proof for graph {graph_id}"))?; + let proof = Groth16Proof::deserialize_compressed(proof_bytes.as_slice()) + .context("deserialize asserted wrapper proof")?; + let wrongly_challenged_witness = + recover_real_wrongly_challenged_witness(prover_state, challenge_witness, &proof)?; + let (wrongly_challenged_input, _amount) = operator_sign_wrongly_challenged( + &graph, + verifier_index, + &wrongly_challenged_witness.final_msgs, + )?; + let wrongly_challenged_tx = bitcoin::Transaction { + version: bitcoin::transaction::Version(2), + lock_time: bitcoin::absolute::LockTime::ZERO, + input: vec![wrongly_challenged_input], + output: vec![goat::scripts::p2a_output()], + }; + broadcast_nonstandard_tx(ctx.btc_client, &wrongly_challenged_tx).await?; + Ok(()) } +// broadcast NoWithdraw after the ChallengeAssert timelock expires. #[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_assert_commit_timeout_challenger( +async fn handle_wrongly_challenge_timeout_verifier( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, + challenge_assert_txid: Txid, + verifier_index: usize, content: &GOATMessageContent, ) -> Result<()> { - // triggered by timeout task - let message = make_message(ctx, content); - let (graph, graph_status, _graph_sub_status) = match refresh_graph_status( + let (graph, _graph_status, _graph_sub_status) = match refresh_graph_status( ctx, instance_id, graph_id, - Some(&message), - GraphStatus::Challenge, + None, + GraphStatus::Disprove, ) .await? { Some(v) => v, None => return Ok(()), }; - if graph_status != GraphStatus::Challenge { - tracing::warn!( - "Ignore AssertCommitTimeout for {instance_id}:{graph_id}: graph status is {graph_status:?}" + validate_verifier_slot(&graph, verifier_index)?; + validate_expected_challenge_assert_txid(&graph, verifier_index, challenge_assert_txid)?; + let verifier_master_key = VerifierMasterKey::new(get_bitvm_key()?); + let local_verifier_pubkey: PublicKey = verifier_master_key.master_keypair().public_key().into(); + if graph.parameters.gc_data[verifier_index].verifier_pubkey != local_verifier_pubkey { + tracing::debug!( + "Ignore WronglyChallengeTimeout for {instance_id}:{graph_id}: local verifier does not own slot {verifier_index}" ); return Ok(()); } - let assert_init_txid = graph.assert_init.tx().compute_txid(); - let connector_d_vout = graph.assert_commit_timeout_txns.len() as u64; - if outpoint_spent_txid(ctx.btc_client, &assert_init_txid, connector_d_vout).await?.is_some() { - tracing::warn!( - "Ignore AssertCommitTimeout for {instance_id}:{graph_id}: connector_D already spent" + + let delay_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()); + let message = make_message(ctx, content); + if ctx.btc_client.get_tx(&challenge_assert_txid).await?.is_none() { + push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) + .await?; + tracing::info!( + "Retry WronglyChallengeTimeout later for {instance_id}:{graph_id}: challenge assert tx {challenge_assert_txid} not found on chain" ); return Ok(()); } - let assert_init_height = match ctx + + let challenge_assert_height = match ctx .btc_client - .get_tx_status(&assert_init_txid) + .get_tx_status(&challenge_assert_txid) .await? .block_height { - Some(height) => height, + Some(height) => height as u64, None => { - tracing::warn!( - "Ignore AssertCommitTimeout for {instance_id}:{graph_id}: assert init tx not confirmed yet" + push_local_unhandled_messages(ctx.local_db, graph_id, &message, delay_secs as usize) + .await?; + tracing::info!( + "Retry WronglyChallengeTimeout later for {instance_id}:{graph_id}: challenge assert tx {challenge_assert_txid} is not confirmed" ); return Ok(()); } }; - let current_height = ctx.btc_client.get_height().await?; - if current_height < assert_init_height + assert_commit_timeout_timelock(get_network()) { - tracing::warn!( - "Ignore AssertCommitTimeout for {instance_id}:{graph_id}: assert init tx timelock not expired yet" + + let disprove_height = challenge_assert_height + + disprove_timelock(graph.parameters.instance_parameters.network) as u64; + let goat_confirmed_height = ctx.goat_client.btc_spv_latest_height().await?; + if goat_confirmed_height < disprove_height { + let retry_secs = todo_funcs::avg_block_time_secs(ctx.btc_client.network()) + * (disprove_height - goat_confirmed_height); + push_local_unhandled_messages(ctx.local_db, graph_id, &message, retry_secs as usize) + .await?; + tracing::info!( + "Retry WronglyChallengeTimeout later for {instance_id}:{graph_id}: disprove timelock has not expired" ); return Ok(()); } - let mut commit_index = None; - for i in 0..graph.assert_commit_timeout_txns.len() { - let assert_commit_vout = i as u64; - if outpoint_spent_txid(ctx.btc_client, &assert_init_txid, assert_commit_vout) - .await? - .is_none() - { - commit_index = Some(i); - break; - } - } - let commit_index = match commit_index { - Some(index) => index, - None => { - tracing::warn!( - "Ignore AssertCommitTimeout for {instance_id}:{graph_id}: all assert commit connectors already spent" - ); - return Ok(()); - } - }; - // 1. broadcast AssertCommitTimeout txn - let assert_commit_timeout_tx = graph - .assert_commit_timeout_txns - .get(commit_index) - .ok_or_else(|| { - anyhow!("AssertCommitTimeout txn not found for {instance_id}:{graph_id}:{commit_index}") - })? - .finalize(); - let anchor_vout = assert_commit_timeout_tx.output.len() as u64 - 1; - let assert_commit_timeout_tx_total_input_amount = - graph.assert_commit_timeout_txns[commit_index].prev_outs().iter().map(|o| o.value).sum(); - let child_tx = build_cpfp_txns( - ctx.btc_client, - &assert_commit_timeout_tx, - anchor_vout, - assert_commit_timeout_tx_total_input_amount, - ) - .await?; - match child_tx { - Some(tx) => { - broadcast_package(ctx.btc_client, &[assert_commit_timeout_tx, tx], true).await? - } - None => broadcast_tx(ctx.btc_client, &assert_commit_timeout_tx).await?, - }; - Ok(()) -} - -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] -async fn handle_disprove_ready_challenger( - ctx: &mut HandlerContext<'_>, - instance_id: Uuid, - graph_id: Uuid, - content: &GOATMessageContent, -) -> Result<()> { - // triggered by AssertCommit tx or OperatorCommitBlockHash tx - let message = make_message(ctx, content); - let (graph, graph_status, _graph_sub_status) = match refresh_graph_status( - ctx, - instance_id, - graph_id, - Some(&message), - GraphStatus::Challenge, - ) - .await? + if let Some(spent_txid) = outpoint_spent_txid(ctx.btc_client, &challenge_assert_txid, 0).await? { - Some(v) => v, - None => return Ok(()), - }; - if graph_status != GraphStatus::Challenge { - tracing::warn!( - "Ignore DisproveReady for {instance_id}:{graph_id}: graph status is {graph_status:?}" + tracing::info!( + "Skip Disprove for {instance_id}:{graph_id} slot {verifier_index}: verifier assertion output already spent by {spent_txid}" ); return Ok(()); } - // 1. get assertions committed by Operator from Bitcoin chain - let operator_commit_blockhash_txin = { - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let connector_g_vout = 2 * graph.parameters.watchtower_pubkeys.len() as u64; - let commit_blockhash_timeout_txid = graph.blockhash_commit_timeout.tx().compute_txid(); - match outpoint_spent_txin(ctx.btc_client, &watchtower_challenge_init_txid, connector_g_vout) - .await? - { - Some((spent_txid, _, txin)) => { - if spent_txid == commit_blockhash_timeout_txid { - tracing::warn!( - "Ignore DisproveReady for {instance_id}:{graph_id}: graph already challenged by CommitBlockHashTimeout: {spent_txid}" - ); - return Ok(()); - } - txin - } - None => { - tracing::warn!( - "Ignore DisproveReady for {instance_id}:{graph_id}: operator-commit-blockhash not sent yet" - ); - return Ok(()); - } - } - }; - let operator_assert_commit_txins = { - let assert_init_txid = graph.assert_init.tx().compute_txid(); - let mut txins = vec![]; - for i in 0..graph.assert_commit_timeout_txns.len() { - let assert_commit_vout = i as u64; - match outpoint_spent_txin(ctx.btc_client, &assert_init_txid, assert_commit_vout).await? - { - Some((spent_txid, _, txin)) => { - let assert_commit_timeout_txid = - graph.assert_commit_timeout_txns[i].tx().compute_txid(); - if spent_txid == assert_commit_timeout_txid { - tracing::warn!( - "Ignore DisproveReady for {instance_id}:{graph_id}: graph already challenged by AssertCommitTimeout[{i}]: {spent_txid}" - ); - return Ok(()); - } - txins.push(txin); - } - None => { - tracing::warn!( - "Ignore DisproveReady for {instance_id}:{graph_id}: assert-commit {i} not sent yet" - ); - return Ok(()); - } - } - } - txins - }; - let operator_ack_txins = { - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let mut txins = vec![]; - for watchtower_index in 0..graph.parameters.watchtower_pubkeys.len() { - let ack_vout = 1 + 2 * watchtower_index as u64; - if let Some((spent_txid, _, txin)) = - outpoint_spent_txin(ctx.btc_client, &watchtower_challenge_init_txid, ack_vout) - .await? - { - let nack_txid = graph.nack_txns[watchtower_index].tx().compute_txid(); - if spent_txid != nack_txid { - txins.push(txin); - } - } - } - txins - }; - // 2. check assertions committed by Operator, if any assertion is invalid, sign & broadcast disprove txn - let vk = crate::vk::get_vk().await?; - let disprove_scripts = get_disprove_scripts(&graph.parameters).await?; - let disprove_scripts = - disprove_scripts.try_into().map_err(|_| anyhow!("Mismatch disprove scripts num"))?; - if let Some(disprove_witness) = verify_operator_commits( - operator_commit_blockhash_txin, - operator_assert_commit_txins, - operator_ack_txins, - graph.parameters.watchtower_pubkeys.len(), - &vk, - &disprove_scripts, - )? { - let disprover_evm_address = get_node_goat_address() - .ok_or_else(|| anyhow::anyhow!("failed to get node goat address".to_string()))?; - let connector_e_input = Input { - outpoint: OutPoint { txid: graph.kickoff.tx().compute_txid(), vout: 3 }, - amount: graph.kickoff.tx().output[3].value, - }; - let disprove_tx = sign_disprove( - &graph, - &connector_e_input, - disprove_witness, - disprove_scripts.to_vec(), - Some(*disprover_evm_address.as_ref()), - )?; - let challenger_master_key = ChallengerMasterKey::new(get_bitvm_key()?); - let challenger_disprove_keypair = challenger_master_key.keypair_for_nst_disprove(); - if let Err(e) = build_sign_and_broadcast_non_standard_tx( - ctx.btc_client, - challenger_disprove_keypair, - disprove_tx.clone(), - connector_e_input.amount, - ) - .await - { - if e.downcast_ref::() - .is_some_and(|se| matches!(se, SpecialError::InsufficientBalance(_))) - { - let disprove_address = node_p2wsh_address( - get_network(), - &challenger_disprove_keypair.public_key().into(), - ); - let disprove_balance = ctx - .btc_client - .get_address_utxo(disprove_address.clone()) - .await? - .iter() - .map(|u| u.value) - .sum::(); - let fee_rate = get_fee_rate(ctx.btc_client).await?; - let est_fee_sat = - ((disprove_tx.weight().to_vbytes_ceil() + 200) as f64 * fee_rate).ceil() as u64; - let target_balance_sat = est_fee_sat + 20_000; - let shortfall_sat = target_balance_sat.saturating_sub(disprove_balance.to_sat()); - if shortfall_sat > 0 { - tracing::info!( - "Top up nst-disprove p2wsh address for {instance_id}:{graph_id}: shortfall={} sats", - shortfall_sat - ); - fund_address( - ctx.btc_client, - challenger_master_key.master_keypair(), - disprove_address, - bitcoin::Amount::from_sat(shortfall_sat), - ) - .await?; - } - build_sign_and_broadcast_non_standard_tx( - ctx.btc_client, - challenger_disprove_keypair, - disprove_tx, - connector_e_input.amount, - ) - .await?; - } else { - return Err(e); - } - } - } else { - tracing::info!("All assertions valid for {instance_id}:{graph_id}, no need to disprove"); - return Ok(()); - } + + let disprove_tx = build_disprove_tx(&graph, verifier_index, None)?; + broadcast_nonstandard_tx(ctx.btc_client, &disprove_tx).await?; + + let message_content = GOATMessageContent::DisproveSent(DisproveSent { + instance_id, + graph_id, + disprove_type: DisproveTxType::Disprove, + index: verifier_index, + challenge_start_txid: Some(challenge_assert_txid), + challenge_finish_txid: disprove_tx.compute_txid(), + }); + send_to_peer(ctx.swarm, GOATMessage::new(Actor::Committee, message_content)).await?; + Ok(()) } -#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id))] +#[tracing::instrument(level = "info", skip_all, fields(instance_id = %instance_id, graph_id = %graph_id +))] async fn handle_disprove_sent_committee( ctx: &mut HandlerContext<'_>, instance_id: Uuid, @@ -3372,79 +3660,6 @@ async fn handle_disprove_sent_committee( return Ok(()); } }; - match disprove_type { - DisproveTxType::AssertTimeout => { - if challenge_finish_txid - != graph - .assert_commit_timeout_txns - .get(index) - .ok_or_else(|| { - anyhow!( - "AssertCommitTimeout txn not found for {instance_id}:{graph_id}:{index}" - ) - })? - .tx() - .compute_txid() - { - tracing::warn!( - "Ignore DisproveSent for {instance_id}:{graph_id}: challenge finish txid does not match assert commit timeout txn" - ); - return Ok(()); - } - } - DisproveTxType::OperatorCommitTimeout => { - if challenge_finish_txid != graph.blockhash_commit_timeout.tx().compute_txid() { - tracing::warn!( - "Ignore DisproveSent for {instance_id}:{graph_id}: challenge finish txid does not match operator commit timeout txn" - ); - return Ok(()); - } - } - DisproveTxType::OperatorNack => { - if challenge_finish_txid - != graph - .nack_txns - .get(index) - .ok_or_else(|| { - anyhow!("Nack txn not found for {instance_id}:{graph_id}:{index}") - })? - .tx() - .compute_txid() - { - tracing::warn!( - "Ignore DisproveSent for {instance_id}:{graph_id}: challenge finish txid does not match nack txn" - ); - return Ok(()); - } - } - DisproveTxType::Disprove => { - let connector_e_input = OutPoint { txid: kickoff_txid, vout: 3 }; - if challenge_finish_tx.input[0].previous_output != connector_e_input { - tracing::warn!( - "Ignore DisproveSent for {instance_id}:{graph_id}: challenge finish tx is not a disprove txn" - ); - return Ok(()); - } - } - DisproveTxType::QuickChallenge => { - let guardian_connector_input = OutPoint { txid: kickoff_txid, vout: 4 }; - if challenge_finish_tx.input[0].previous_output != guardian_connector_input { - tracing::warn!( - "Ignore DisproveSent for {instance_id}:{graph_id}: challenge finish tx is not a quick challenge txn" - ); - return Ok(()); - } - } - DisproveTxType::ChallengeIncompleteKickoff => { - let guardian_connector_input = OutPoint { txid: kickoff_txid, vout: 4 }; - if challenge_finish_tx.input[0].previous_output != guardian_connector_input { - tracing::warn!( - "Ignore DisproveSent for {instance_id}:{graph_id}: challenge finish tx is not a challenge incomplete kickoff txn" - ); - return Ok(()); - } - } - } let challenge_finish_height = match ctx .btc_client .get_tx_status(&challenge_finish_txid) @@ -3527,7 +3742,7 @@ async fn handle_take1_ready_operator( } let kickoff_txid = graph.kickoff.tx().compute_txid(); let connector_a_vout = 0; - let guardian_connector_vout = 4; + let guardian_connector_vout = 3; if outpoint_spent_txid(ctx.btc_client, &kickoff_txid, connector_a_vout).await?.is_some() || outpoint_spent_txid(ctx.btc_client, &kickoff_txid, guardian_connector_vout) .await? @@ -3684,62 +3899,40 @@ async fn handle_take2_ready_operator( ); return Ok(()); } - let kickoff_txid = graph.kickoff.tx().compute_txid(); - let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); - let assert_init_txid = graph.assert_init.tx().compute_txid(); - let connector_d_vout = graph.assert_commit_timeout_txns.len() as u64; - let connector_e_vout = 3; - let connector_f_vout = 1 + 2 * graph.parameters.watchtower_pubkeys.len() as u64; - let guardian_connector_vout = 4; - // check if connector_E, connector_F, connector_D, guardian_connector are all unspent - if outpoint_spent_txid(ctx.btc_client, &kickoff_txid, connector_e_vout).await?.is_some() - || outpoint_spent_txid(ctx.btc_client, &watchtower_challenge_init_txid, connector_f_vout) - .await? - .is_some() - || outpoint_spent_txid(ctx.btc_client, &assert_init_txid, connector_d_vout).await?.is_some() - || outpoint_spent_txid(ctx.btc_client, &kickoff_txid, guardian_connector_vout) - .await? - .is_some() - { - tracing::warn!("Ignore Take2Ready for {instance_id}:{graph_id}: connectors already spent"); - return Ok(()); - } - // check if assert-init tx and watchtower-challenge-init tx are both confirmed and timelock expired - let assert_init_height = match ctx - .btc_client - .get_tx_status(&assert_init_txid) - .await? - .block_height - { - Some(height) => height, - None => { + let operator_assert_txid = graph.operator_assert.tx().compute_txid(); + // Take2 spends Connector-0, Connector-D, and Guardian Connector. Recheck every input before + // signing so a stale Take2Ready cannot race an already-spent connector. + let take2_inputs = + graph.take2.tx().input.iter().map(|txin| txin.previous_output).collect::>(); + for previous_output in take2_inputs { + if let Some(spent_txid) = + outpoint_spent_txid(ctx.btc_client, &previous_output.txid, previous_output.vout as u64) + .await? + { tracing::warn!( - "Ignore Take2Ready for {instance_id}:{graph_id}: assert init tx not confirmed yet" + "Ignore Take2Ready for {instance_id}:{graph_id}: take2 input {}:{} already spent by {}", + previous_output.txid, + previous_output.vout, + spent_txid ); return Ok(()); } - }; - let watchtower_challenge_init_height = match ctx + } + let operator_assert_height = match ctx .btc_client - .get_tx_status(&watchtower_challenge_init_txid) + .get_tx_status(&operator_assert_txid) .await? .block_height { Some(height) => height, None => { tracing::warn!( - "Ignore Take2Ready for {instance_id}:{graph_id}: watchtower challenge init tx not confirmed yet" + "Ignore Take2Ready for {instance_id}:{graph_id}: assert init tx not confirmed yet" ); return Ok(()); } }; - if !is_take2_timelock_expired( - ctx.btc_client, - watchtower_challenge_init_height, - assert_init_height, - ) - .await? - { + if !is_take2_timelock_expired(ctx.btc_client, operator_assert_height).await? { tracing::warn!( "Ignore Take2Ready for {instance_id}:{graph_id}: take2 timelock not expired yet" ); @@ -3880,7 +4073,7 @@ async fn handle_sync_graph( ctx: &mut HandlerContext<'_>, instance_id: Uuid, graph_id: Uuid, - graph: &SimplifiedBitvm2Graph, + graph: &SimplifiedBitvmGcGraph, ) -> Result<()> { // sent by relayer nodes in response to SyncGraphRequest if graph_exists(ctx.local_db, instance_id, graph_id).await? { @@ -3895,7 +4088,7 @@ async fn handle_sync_graph( ) })?; store_graph(ctx.local_db, graph).await?; - let graph = Bitvm2Graph::from_simplified(graph)?; + let graph = BitvmGcGraph::from_simplified(graph)?; refresh_and_compensate( ctx, instance_id, @@ -3925,3 +4118,127 @@ async fn handle_response_node_info( save_node_info(ctx.local_db, node_info).await?; Ok(()) } + +#[cfg(test)] +mod tests { + use std::str::FromStr; + + use bitvm_lib::babe_adapter::{ + BABE_M_CC, BabeProverState, build_setup_package, open_and_solder, + }; + + use super::*; + + fn verifier_pubkey() -> PublicKey { + PublicKey::from_str("0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798") + .unwrap() + } + + fn gc_submission() -> (CACSetupPackage, BitvmGcCircuitData, BabeProverState) { + let package = build_setup_package(BABE_M_CC + 1).unwrap(); + let selected = (0..BABE_M_CC).collect::>(); + let (_, finalized, soldering) = open_and_solder(&package, &selected).unwrap(); + let gc_data = extract_gc_circuit_data(&finalized, &soldering, verifier_pubkey()).unwrap(); + let prover_state = BabeProverState { + package: package.clone(), + finalized, + soldering, + h_msgs: gc_data.final_msg_hashlocks.clone(), + }; + (package, gc_data, prover_state) + } + + fn operator_state(package: CACSetupPackage) -> OperatorBabeSetupState { + OperatorBabeSetupState { + frozen_verifier_pubkeys: Some(vec![verifier_pubkey()]), + candidates: vec![OperatorVerifierCandidate { + verifier_pubkey: verifier_pubkey(), + setup_package: package, + verifier_index: Some(0), + selected_circuit_indexes: (0..BABE_M_CC).collect(), + gc_data: None, + prover_state: None, + }], + asserted_wrapper_proof: None, + } + } + + #[test] + fn freeze_operator_candidate_selects_protocol_finalized_count() { + let package = build_setup_package(BABE_M_CC + 1).unwrap(); + let mut state = OperatorBabeSetupState { + frozen_verifier_pubkeys: None, + candidates: vec![OperatorVerifierCandidate { + verifier_pubkey: verifier_pubkey(), + setup_package: package, + verifier_index: None, + selected_circuit_indexes: vec![], + gc_data: None, + prover_state: None, + }], + asserted_wrapper_proof: None, + }; + + freeze_operator_candidates(&mut state).unwrap(); + + assert_eq!(state.candidates[0].selected_circuit_indexes.len(), BABE_M_CC); + } + + #[test] + fn candidate_records_one_slot_and_full_prover_state_idempotently() { + let (package, gc_data, prover_state) = gc_submission(); + let mut state = operator_state(package.clone()); + + let graph_data = record_candidate_gc_data( + &mut state, + verifier_pubkey(), + 0, + &package, + gc_data.clone(), + prover_state.clone(), + ) + .unwrap() + .unwrap(); + + assert_eq!(graph_data.len(), 1); + assert_eq!(graph_data[0].final_msg_hashlocks.len(), BABE_M_CC); + assert_eq!(state.candidates[0].prover_state.as_ref().unwrap().finalized.len(), BABE_M_CC); + + let duplicate = record_candidate_gc_data( + &mut state, + verifier_pubkey(), + 0, + &package, + gc_data.clone(), + prover_state.clone(), + ) + .unwrap() + .unwrap(); + assert_eq!(duplicate.len(), 1); + + let mut conflict = prover_state; + conflict.h_msgs[0][0] ^= 1; + assert!( + record_candidate_gc_data( + &mut state, + verifier_pubkey(), + 0, + &package, + gc_data, + conflict, + ) + .is_err() + ); + } + + #[test] + fn legacy_operator_candidate_without_prover_state_deserializes() { + let (package, _, _) = gc_submission(); + let mut value = serde_json::to_value(operator_state(package)).unwrap(); + value["candidates"][0].as_object_mut().unwrap().remove("prover_state"); + + let restored: OperatorBabeSetupState = serde_json::from_value(value).unwrap(); + + assert!(restored.candidates[0].prover_state.is_none()); + } +} diff --git a/node/src/lib.rs b/node/src/lib.rs index 9ecafd89..fe677b09 100644 --- a/node/src/lib.rs +++ b/node/src/lib.rs @@ -7,6 +7,7 @@ pub mod p2p_msg_handler; pub mod rpc_service; mod scheduled_tasks; +mod soldering_payload_store; pub mod utils; pub use scheduled_tasks::{ run_maintenance_tasks, run_sequencer_set_hash_monitor_task, run_watch_event_task, @@ -14,200 +15,3 @@ pub use scheduled_tasks::{ mod error; mod vk; - -mod dbg { - #![allow(unused)] - use core::panic; - use std::str::FromStr; - - use crate::action::*; - use crate::utils::*; - use bitcoin::{Address, Amount, Network, Witness}; - use bitvm2_lib::actors::Actor; - use bitvm2_lib::committee::*; - use bitvm2_lib::keys::*; - use bitvm2_lib::operator::*; - use bitvm2_lib::types::*; - use goat::connectors::connector_e::ConnectorE; - use goat::connectors::kickoff_connectors::*; - use goat::contexts::base::generate_n_of_n_public_key; - use goat::disprove_scripts::hash160; - use goat::scripts::p2a_script; - use goat::transactions::base::Input; - use goat::transactions::prekickoff::PrekickoffTransaction; - use secp256k1::{Keypair, Secp256k1, SecretKey, rand}; - use serde::Deserialize; - use serde::Serialize; - use uuid::Uuid; - - fn dbg_network() -> Network { - Network::Testnet4 - } - fn dbg_keypair() -> Keypair { - let secp = Secp256k1::new(); - Keypair::new(&secp, &mut rand::thread_rng()) - } - fn dbg_input() -> Input { - Input { - outpoint: bitcoin::OutPoint { - txid: bitcoin::Txid::from_str( - "b2b18acfdd358369d9a1e8370cfd9eb4ee123507a0321ad608de500cc54740d8", - ) - .unwrap(), - vout: 0, - }, - amount: Amount::from_sat(500000000), - } - } - fn dbg_address() -> Address { - node_p2wsh_address(dbg_network(), &dbg_keypair().public_key().into()) - } - fn build_dbg_instance_parameters() -> Bitvm2InstanceParameters { - let user_info = UserInfo { - depositor_evm_address: [1u8; 20], - txn_fees: [1000u64; 3], - inputs: vec![dbg_input()], - user_xonly_pubkey: dbg_keypair().public_key().x_only_public_key().0, - user_change_address: dbg_address(), - user_refund_address: dbg_address(), - }; - let committee_pubkeys = - vec![dbg_keypair().public_key().into(), dbg_keypair().public_key().into()]; - let committee_agg_pubkey = generate_n_of_n_public_key(&committee_pubkeys).0; - Bitvm2InstanceParameters { - network: dbg_network(), - instance_id: Uuid::new_v4(), - user_info, - pegin_amount: Amount::from_sat(1000000), - committee_pubkeys, - committee_agg_pubkey, - } - } - fn build_dbg_prekickoff_parameters() -> PrekickoffParameters { - let xonly_pubkey = dbg_keypair().public_key().x_only_public_key().0; - let force_skip_connector = ForceSkipConnector::new(dbg_network(), &xonly_pubkey); - let kickoff_connector = KickoffConnector::new(dbg_network(), &xonly_pubkey); - let prekickoff_connector = PrekickoffConnector::new(dbg_network(), &xonly_pubkey); - let cur_prekickoff_txn = PrekickoffTransaction::new_for_validation( - &prekickoff_connector, - &force_skip_connector, - &kickoff_connector, - &prekickoff_connector, - dbg_input(), - vec![], - vec![], - 1000, - 2, - 50, - ) - .unwrap(); - PrekickoffParameters { - cur_prekickoff_txn, - replenish_fee_inputs: vec![], - replenish_fee_prev_outs: vec![], - fee_amount: 1000, - } - } - fn build_dbg_graph_parameters() -> Bitvm2GraphParameters { - let graph_id = Uuid::new_v4(); - let instance_parameters = build_dbg_instance_parameters(); - let prekickoff_parameters = build_dbg_prekickoff_parameters(); - let operator_master_key = OperatorMasterKey::new(dbg_keypair()); - let operator_master_keypair = operator_master_key.master_keypair(); - let operator_pubkey = operator_master_keypair.public_key().into(); - let operator_wots_pubkeys = operator_master_key.wots_keypair_for_graph(graph_id).1; - let watchtower_pubkeys = vec![ - dbg_keypair().public_key().x_only_public_key().0, - dbg_keypair().public_key().x_only_public_key().0, - ]; - let mut hashlocks = vec![]; - for index in 0..watchtower_pubkeys.len() { - let preimage = b"preimage".to_vec(); - let hashlock = hash160(&preimage); - hashlocks.push(hashlock); - } - let instance_id = instance_parameters.instance_id; - Bitvm2GraphParameters { - instance_parameters, - prekickoff_parameters, - graph_id, - graph_nonce: 1, - challenge_amount: todo_funcs::challenge_amount(), - operator_pubkey, - operator_wots_pubkeys, - operator_receive_address: dbg_address(), - watchtower_pubkeys, - hashlocks, - guest_constant_value: [3u8; 32], - } - } - fn build_dbg_simplified_graph() -> SimplifiedBitvm2Graph { - let disprove_scripts = vec![p2a_script()]; - let graph = generate_bitvm_graph(build_dbg_graph_parameters(), disprove_scripts).unwrap(); - graph.to_simplified().unwrap() - } - - #[tokio::test] - async fn dbg_serde() { - let graph: SimplifiedBitvm2Graph = build_dbg_simplified_graph(); - let message_content = GOATMessageContent::CreateGraph(CreateGraph { - instance_id: graph.parameters.instance_parameters.instance_id, - graph_id: graph.parameters.graph_id, - graph_nonce: graph.parameters.graph_nonce, - graph: graph.clone(), - }); - let msg = GOATMessage::new(Actor::All, message_content); - let msg_se = msg.serialize_message().await.unwrap(); - let msg_de = GOATMessage::deserialize_message(&msg_se).await.unwrap(); - // check graph id - assert_eq!( - graph.parameters.graph_id, - match msg_de.content { - GOATMessageContent::CreateGraph(ref cg) => cg.graph_id, - _ => Uuid::nil(), - } - ); - } - - #[test] - fn dbg_serde_ignores_legacy_graph_zkm_version() { - let graph = build_dbg_simplified_graph(); - let mut value = serde_json::to_value(&graph).unwrap(); - value - .get_mut("parameters") - .and_then(serde_json::Value::as_object_mut) - .unwrap() - .insert("zkm_version".to_string(), serde_json::Value::String("v1.2.5".to_string())); - - let decoded: SimplifiedBitvm2Graph = serde_json::from_value(value).unwrap(); - assert_eq!(decoded.parameters.graph_id, graph.parameters.graph_id); - assert_eq!(decoded.parameters.graph_nonce, graph.parameters.graph_nonce); - } - - #[tokio::test] - #[ignore = "requires local db"] - async fn dbg_serde_from_db() { - let dbg_path = "/home/ubuntu/bitvm2-noded-test/operator_0/bitvm2-node.db"; - let instance_id = uuid::Uuid::parse_str("A4DB2DD03EEA43FB9601D60236EBAD90").unwrap(); - let graph_id = uuid::Uuid::parse_str("044285C328EE4D2EAE06718659EFDC34").unwrap(); - let local_db = store::create_local_db(dbg_path).await; - let graph = get_graph(&local_db, instance_id, graph_id).await.unwrap().unwrap(); - let message_content = GOATMessageContent::CreateGraph(CreateGraph { - instance_id: graph.parameters.instance_parameters.instance_id, - graph_id: graph.parameters.graph_id, - graph_nonce: graph.parameters.graph_nonce, - graph, - }); - let msg = GOATMessage::new(Actor::All, message_content); - let msg_se = msg.serialize_message().await.unwrap(); - let msg_de = GOATMessage::deserialize_message(&msg_se).await.unwrap(); - // check graph id - assert_eq!( - graph_id, - match msg_de.content { - GOATMessageContent::CreateGraph(ref cg) => cg.graph_id, - _ => Uuid::nil(), - } - ); - } -} diff --git a/node/src/main.rs b/node/src/main.rs index 9945701f..4a87fb48 100644 --- a/node/src/main.rs +++ b/node/src/main.rs @@ -1,9 +1,11 @@ #![feature(trivial_bounds)] use base64::Engine; -use bitvm2_lib::actors::Actor; -use bitvm2_noded::env::{ +use bitvm_lib::actors::Actor; +use bitvm_lib::babe_adapter::BabeBundleBuilder; +use bitvm_noded::env::{ self, ENV_PEER_KEY, SEQUENCER_SET_MONITOR_INTERVAL_SECS, check_node_info, get_btc_url_from_env, get_goat_network, get_network, get_node_pubkey, goat_config_from_env, + validate_soldering_proof_payload_store_config, }; use clap::{Parser, Subcommand}; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; @@ -13,16 +15,16 @@ use std::error::Error; use std::sync::{Arc, Mutex}; use tracing_subscriber::EnvFilter; -use bitvm2_noded::utils::{ +use bitvm_noded::utils::{ self, generate_local_key, save_local_info, set_node_external_socket_addr_env, }; -use bitvm2_noded::{ +use bitvm_noded::{ rpc_service, run_maintenance_tasks, run_sequencer_set_hash_monitor_task, run_watch_event_task, }; use anyhow::Result; -use bitvm2_noded::middleware::swarm::{Bitvm2SwarmConfig, BitvmNetworkManager}; -use bitvm2_noded::p2p_msg_handler::BitvmNodeProcessor; +use bitvm_noded::middleware::swarm::{BitvmNetworkManager, BitvmSwarmConfig}; +use bitvm_noded::p2p_msg_handler::BitvmNodeProcessor; use client::http_client::async_client::HttpAsyncClient; use futures::future; use tokio::signal; @@ -41,7 +43,7 @@ struct Opts { pub rpc_addr: String, /// Local Sqlite database file path - #[arg(long, default_value = "sqlite:/tmp/bitvm2-node.db")] + #[arg(long, default_value = "sqlite:/tmp/bitvm-node.db")] pub db_path: String, /// Peer nodes as the bootnodes @@ -52,7 +54,7 @@ struct Opts { #[arg(long, default_value = "/metrics")] metrics_path: String, - /// Whether to run the libp2p Kademlia protocol and join the BitVM2 DHT. + /// Whether to run the libp2p Kademlia protocol and join the BitVM DHT. #[arg(long, default_value = "true")] enable_kademlia: bool, @@ -114,12 +116,13 @@ async fn main() -> Result<(), Box> { KeyCommands::FundingAddress => { let public_key = get_node_pubkey()?; let p2wsh_addr = utils::node_p2wsh_address(get_network(), &public_key); - println!("Funding P2WSH address (for operator and challenger): {p2wsh_addr}"); + println!("Funding P2WSH address (for operator and verifier): {p2wsh_addr}"); } } return Ok(()); } let _ = tracing_subscriber::fmt().with_env_filter(EnvFilter::from_default_env()).try_init(); + validate_soldering_proof_payload_store_config(&actor)?; let is_publisher = actor == Actor::Publisher || actor == Actor::All; let sequencer_set_monitor_start_cosmos_block = @@ -135,15 +138,15 @@ async fn main() -> Result<(), Box> { // Create cancellation token for graceful shutdown let cancellation_token = CancellationToken::new(); let mut task_handles: Vec>> = vec![]; - // init bitvm2swarm + // init bitvmswarm let bitvm_network_manager = BitvmNetworkManager::new( - Bitvm2SwarmConfig { + BitvmSwarmConfig { local_key: env::get_peer_key(), p2p_port: opt.p2p_port, bootnodes: opt.bootnodes, topic_names: vec![ Actor::Committee.to_string(), - Actor::Challenger.to_string(), + Actor::Verifier.to_string(), Actor::Operator.to_string(), Actor::Watchtower.to_string(), Actor::All.to_string(), @@ -160,6 +163,8 @@ async fn main() -> Result<(), Box> { btc_client: BTCClient::new(get_network(), get_btc_url_from_env().as_deref()), goat_client: GOATClient::new(env::goat_config_from_env().await, env::get_goat_network()), http_client: HttpAsyncClient::new(None), + soldering_builder: matches!(actor, Actor::Verifier | Actor::Operator) + .then(|| Arc::new(BabeBundleBuilder::new())), }; let actor_clone1 = actor.clone(); diff --git a/node/src/middleware/behaviour.rs b/node/src/middleware/behaviour.rs index 7080e0cc..f7773fe7 100644 --- a/node/src/middleware/behaviour.rs +++ b/node/src/middleware/behaviour.rs @@ -1,4 +1,3 @@ -// #![feature(trivial_bounds)] use libp2p::identity::Keypair; use libp2p::{gossipsub, kad, kad::store::MemoryStore, swarm::StreamProtocol}; use libp2p_swarm_derive::NetworkBehaviour; diff --git a/node/src/middleware/swarm.rs b/node/src/middleware/swarm.rs index d4e7d57f..ee9b5ae4 100644 --- a/node/src/middleware/swarm.rs +++ b/node/src/middleware/swarm.rs @@ -3,7 +3,7 @@ use crate::middleware::{AllBehaviours, split_topic_name}; use crate::{env, middleware}; use anyhow::bail; use base64::Engine; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; use futures::StreamExt; use libp2p::gossipsub::MessageId; use libp2p::multiaddr::Protocol; @@ -92,7 +92,7 @@ pub trait P2pMessageHandler { } #[derive(Clone, Debug)] -pub struct Bitvm2SwarmConfig { +pub struct BitvmSwarmConfig { pub local_key: String, pub p2p_port: u16, pub bootnodes: Vec, @@ -102,13 +102,13 @@ pub struct Bitvm2SwarmConfig { } pub struct BitvmNetworkManager { - config: Bitvm2SwarmConfig, + config: BitvmSwarmConfig, peer_id: PeerId, swarm: BitvmSwarmWrapper, } impl BitvmNetworkManager { pub fn new( - config: Bitvm2SwarmConfig, + config: BitvmSwarmConfig, metric_registry: &mut Registry, ) -> anyhow::Result { let key_pair = libp2p::identity::Keypair::from_protobuf_encoding(&Zeroizing::new( @@ -204,13 +204,27 @@ impl BitvmNetworkManager { match event { SwarmEvent::NewListenAddr { address, .. } => tracing::debug!("Listening on {address:?}"), SwarmEvent::Behaviour(AllBehavioursEvent::Gossipsub(gossipsub::Event::Message { - propagation_source: _peer_id, + propagation_source, message_id: id, message, })) => { + let source = message.source.unwrap_or(propagation_source); + let data_prefix = hex::encode(&message.data[..message.data.len().min(16)]); + let data_starts_with_goatbin = message.data.starts_with(b"GOATBIN1"); match msg_handler.recv_and_dispatch(&mut self.swarm, actor.clone(), - message.source.expect("empty message source"), id, &message.data).await { - Ok(_) => {},Err(e) => { tracing::error!("Fail to handle p2p message, error: {e:?}") } + source, id.clone(), &message.data).await { + Ok(_) => {},Err(e) => { + tracing::error!( + error = ?e, + from_peer_id = %source, + message_id = ?id, + topic = %message.topic, + data_len = message.data.len(), + data_prefix, + data_starts_with_goatbin, + "Fail to handle p2p message" + ) + } } } SwarmEvent::Behaviour(AllBehavioursEvent::Gossipsub(gossipsub::Event::Subscribed { peer_id, topic})) => { diff --git a/node/src/p2p_msg_handler.rs b/node/src/p2p_msg_handler.rs index 868f3ed5..be31343a 100644 --- a/node/src/p2p_msg_handler.rs +++ b/node/src/p2p_msg_handler.rs @@ -4,11 +4,13 @@ use crate::action::{ use crate::env::get_local_node_info; use crate::middleware::swarm::{BitvmSwarmWrapper, P2pMessageHandler, TickMessageType}; use crate::utils::detect_heart_beat; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; +use bitvm_lib::babe_adapter::BabeBundleBuilder; use client::http_client::async_client::HttpAsyncClient; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; use libp2p::PeerId; use libp2p::gossipsub::MessageId; +use std::sync::Arc; use store::localdb::LocalDB; pub struct BitvmNodeProcessor { @@ -16,6 +18,7 @@ pub struct BitvmNodeProcessor { pub btc_client: BTCClient, pub goat_client: GOATClient, pub http_client: HttpAsyncClient, + pub soldering_builder: Option>, } impl P2pMessageHandler for BitvmNodeProcessor { async fn recv_and_dispatch( @@ -32,6 +35,7 @@ impl P2pMessageHandler for BitvmNodeProcessor { &self.btc_client, &self.goat_client, &self.http_client, + &self.soldering_builder, actor, from_peer_id, id, @@ -71,6 +75,7 @@ impl P2pMessageHandler for BitvmNodeProcessor { &self.btc_client, &self.goat_client, &self.http_client, + &self.soldering_builder, actor, peer_id, GOATMessage::default_message_id(), @@ -105,12 +110,12 @@ mod tests { use crate::action::{GOATMessage, GOATMessageContent, NodeInfo, send_to_peer}; use crate::env::get_rpc_support_actors; use crate::middleware::swarm::{ - Bitvm2SwarmConfig, BitvmNetworkManager, BitvmSwarmWrapper, P2pMessageHandler, + BitvmNetworkManager, BitvmSwarmConfig, BitvmSwarmWrapper, P2pMessageHandler, TickMessageType, }; use crate::utils::{generate_local_key, save_node_info}; use base64::Engine; - use bitvm2_lib::actors::Actor; + use bitvm_lib::actors::Actor; use libp2p::PeerId; use libp2p::gossipsub::MessageId; use prometheus_client::registry::Registry; @@ -147,13 +152,13 @@ mod tests { local_key }; let mut bitvm_network_manager = BitvmNetworkManager::new( - Bitvm2SwarmConfig { + BitvmSwarmConfig { local_key, p2p_port, bootnodes, topic_names: vec![ Actor::Committee.to_string(), - Actor::Challenger.to_string(), + Actor::Verifier.to_string(), Actor::Operator.to_string(), Actor::Watchtower.to_string(), Actor::All.to_string(), @@ -163,7 +168,7 @@ mod tests { }, &mut metric_registry, ) - .expect("create bitvm2 swarm"); + .expect("create bitvm swarm"); let local_db = if let Some(local_db) = local_db { local_db diff --git a/node/src/rpc_service/auth.rs b/node/src/rpc_service/auth.rs index 25d47067..4d5db476 100644 --- a/node/src/rpc_service/auth.rs +++ b/node/src/rpc_service/auth.rs @@ -9,7 +9,7 @@ use sha2::{Digest, Sha256}; pub const AUTH_TIMESTAMP_HEADER: &str = "x-auth-timestamp"; pub const AUTH_SIGNATURE_HEADER: &str = "x-auth-signature"; const AUTH_WINDOW_SECS: i64 = 300; -const AUTH_DOMAIN: &[u8] = b"bitvm2-auth"; +const AUTH_DOMAIN: &[u8] = b"bitvm-auth"; /// Verify that the request carries a valid Schnorr signature produced by the /// same `BITVM_SECRET` this node is configured with. @@ -17,7 +17,7 @@ const AUTH_DOMAIN: &[u8] = b"bitvm2-auth"; /// Expected headers: /// - `X-Auth-Timestamp`: current unix epoch seconds (string) /// - `X-Auth-Signature`: hex-encoded 64-byte Schnorr signature of -/// `SHA256(b"bitvm2-auth" || timestamp_str)` +/// `SHA256(b"bitvm-auth" || timestamp_str)` pub fn verify_request_auth(headers: &HeaderMap) -> Result<(), (StatusCode, Json)> { let timestamp_str = headers .get(AUTH_TIMESTAMP_HEADER) @@ -65,7 +65,7 @@ pub fn verify_request_auth(headers: &HeaderMap) -> Result<(), (StatusCode, Json< /// Build the `(x-auth-timestamp, x-auth-signature)` header values for a request. /// -/// Signs `SHA256(b"bitvm2-auth" || timestamp_str)` with the provided keypair using +/// Signs `SHA256(b"bitvm-auth" || timestamp_str)` with the provided keypair using /// Schnorr and returns `(timestamp_str, hex_signature)`. pub fn sign_request_auth(keypair: &Keypair) -> (String, String) { let timestamp = std::time::SystemTime::now() diff --git a/node/src/rpc_service/bitvm2.rs b/node/src/rpc_service/bitvm.rs similarity index 97% rename from node/src/rpc_service/bitvm2.rs rename to node/src/rpc_service/bitvm.rs index 5dee94df..15f0fc72 100644 --- a/node/src/rpc_service/bitvm2.rs +++ b/node/src/rpc_service/bitvm.rs @@ -1,7 +1,7 @@ use super::utils::{deserialize_u256, serialize_u256}; use crate::rpc_service::current_time_secs; use crate::scheduled_tasks::graph_maintenance_tasks::{ - AssertCommitStatus, ChallengeSubStatus, WatchtowerChallengeStatus, + ChallengeSubStatus, VerifierChallengeStatus, }; use crate::utils::{check_bridge_in_uxto_available_or_self_spent, reflect_goat_address}; use alloy::hex::ToHexExt; @@ -22,14 +22,22 @@ use strum::{Display, EnumString}; use tracing::warn; use uuid::Uuid; +#[allow(dead_code)] pub const WATCHTOWER_CHALLENGE_STEP_INIT: &str = "Watchtower Challenge init"; +#[allow(dead_code)] pub const WATCHTOWER_CHALLENGE_STEP_CHALLENGE: &str = "Watchtower Challenge"; +#[allow(dead_code)] pub const WATCHTOWER_CHALLENGE_STEP_CHALLENGE_TIMEOUT: &str = "Watchtower Challenge Timeout"; +#[allow(dead_code)] pub const WATCHTOWER_CHALLENGE_STEP_ACK: &str = "Operator Challenge ACK"; +#[allow(dead_code)] pub const WATCHTOWER_CHALLENGE_STEP_COMMIT_BLOCKHASH: &str = "Operator Commit BlockHash"; +#[allow(dead_code)] pub const WATCHTOWER_CHALLENGE_STEP_COMMIT_BLOCKHASH_TIMEOUT: &str = "Operator Commit BlockHash Timeout"; +#[allow(dead_code)] pub const ASSERT_STEP_INIT: &str = "Assert init"; +#[allow(dead_code)] pub const ASSERT_STEP_COMMIT: &str = "Assert Commit"; const BRIDGE_IN_FAIL_AS_UTXO_BEEN_SPENT: &str = "Your UTXO has already been spent."; @@ -112,6 +120,7 @@ pub struct PegoutResponse { #[derive(Debug, Deserialize)] pub struct GraphTxGetParams { pub tx_name: String, + pub index: Option, } #[derive(Debug, Deserialize)] @@ -486,14 +495,19 @@ pub type GraphGetResponse = GraphExtended; #[derive(Deserialize, Serialize, Default)] pub struct GraphTxnGetResponse { - pub assert_init: BtcTxData, - pub watchtower_challenge_init: BtcTxData, - pub pre_kickoff: BtcTxData, - pub challenge: BtcTxData, - pub disprove: BtcTxData, - pub kickoff: BtcTxData, + pub cur_prekickoff: BtcTxData, + pub next_prekickoff: BtcTxData, + pub force_skip_kickoff: BtcTxData, + pub quick_challenge: BtcTxData, + pub challenge_incomplete_kickoff: BtcTxData, pub pegin: BtcTxData, + pub kickoff: BtcTxData, pub take1: BtcTxData, + pub challenge: BtcTxData, + pub watchtower_challenge_init: BtcTxData, + pub operator_assert: BtcTxData, + pub verifier_asserts: Vec, + pub disproves: Vec, pub take2: BtcTxData, } #[derive(Deserialize, Serialize, Default)] @@ -658,9 +672,12 @@ impl GraphExtended { let challenge_sub_status = match serde_json::from_str::(&graph.sub_status) { Ok(v) => { - if v.assert_commit_status != AssertCommitStatus::None { + if v.verifier_challenge_status + .iter() + .any(|status| *status != VerifierChallengeStatus::None) + { SimpleChallengeSubStatus::Assert - } else if v.watchtower_challenge_status != WatchtowerChallengeStatus::None { + } else if v.watchtower_challenge_status.iter().any(|status| *status) { SimpleChallengeSubStatus::WatchtowerChallenge } else { SimpleChallengeSubStatus::None diff --git a/node/src/rpc_service/handler/bitvm2_handler.rs b/node/src/rpc_service/handler/bitvm2_handler.rs index 86448b72..56496111 100644 --- a/node/src/rpc_service/handler/bitvm2_handler.rs +++ b/node/src/rpc_service/handler/bitvm2_handler.rs @@ -4,16 +4,13 @@ use crate::env::{ get_node_goat_address, get_node_pubkey, }; use crate::rpc_service::auth::verify_request_auth; -use crate::rpc_service::bitvm2::*; +use crate::rpc_service::bitvm::*; use crate::rpc_service::node::ALIVE_TIME_JUDGE_THRESHOLD; use crate::rpc_service::response::{ ApiErrorExt, ApiResult, ErrorResponse, error_response, ok_response, }; use crate::rpc_service::validation::InputValidator; use crate::rpc_service::{AppState, current_time_secs}; -use crate::scheduled_tasks::graph_maintenance_tasks::{ - AssertInitTxVoutMonitorData, ChallengeSubStatus, WTInitTxVoutMonitorData, -}; use crate::utils::{ find_instances_by_escrow_hash, gen_instance_parameters_local, get_bridge_out_global_stats, parse_graph_raw_data, send_challenge_tx, @@ -22,8 +19,8 @@ use alloy::primitives::{Address, U256}; use axum::Json; use axum::extract::{Path, Query, State}; use bitcoin::consensus::encode::serialize_hex; -use bitvm2_lib::types::{Bitvm2Graph, SimplifiedBitvm2Graph}; -use client::goat_chain::{DisproveTxType, PeginStatus, WithdrawStatus}; +use bitvm_lib::types::{BitvmGcGraph, SimplifiedBitvmGcGraph}; +use client::goat_chain::{PeginStatus, WithdrawStatus}; use goat::transactions::pre_signed::PreSignedTransaction; use http::{HeaderMap, StatusCode}; use sha2::{Digest, Sha256}; @@ -61,6 +58,20 @@ fn bridge_out_retry_jitter_ms(attempt: u32) -> u64 { base + (now % 25) } +fn graph_tx_index_error( + tx_name: GraphBtcTxName, + index: usize, + len: usize, +) -> (StatusCode, Json) { + ( + StatusCode::BAD_REQUEST, + Json(ErrorResponse { + error: "INVALID_GRAPH_TX_INDEX".to_string(), + message: format!("Invalid index {index} for {tx_name}. Available count: {len}"), + }), + ) +} + /// Get instance settings /// /// Returns bridge-in amount configuration information for frontend display of available bridge amount options. @@ -781,12 +792,12 @@ pub async fn get_instances_overview( /// Get graph by ID /// -/// Returns detailed information for a specific BitVM2 graph including transaction status +/// Returns detailed information for a specific BitVM graph including transaction status /// and waiting time information. /// /// # Path Parameters /// -/// - `graph_id`: UUID of the BitVM2 graph to retrieve +/// - `graph_id`: UUID of the BitVM graph to retrieve /// /// # Returns /// @@ -796,7 +807,7 @@ pub async fn get_instances_overview( /// /// # Use Case /// -/// Applications use this to retrieve detailed information about a specific BitVM2 graph, +/// Applications use this to retrieve detailed information about a specific BitVM graph, /// including its current status and estimated waiting time. /// /// # Example @@ -829,13 +840,10 @@ pub async fn get_instances_overview( /// "take1_txid": null, /// "challenge_txid": null, /// "take2_txid": null, -/// "disprove_txid": null, /// "watchtower_challenge_init_txid": null, -/// "watchtower_challenge_timeout_txids": [], -/// "nack_txids": [], -/// "blockhash_commit_timeout_txid": null, -/// "assert_init_txid": null, -/// "assert_commit_timeout_txids": [], +/// "operator_assert_txid": null, +/// "verifier_assert_txids": [], +/// "disprove_txids": [], /// "init_withdraw_tx_hash": null, /// "bridge_out_start_at": 1699123456, /// "status_updated_at": 1699123456, @@ -921,13 +929,10 @@ pub async fn get_graph( /// "take1_txid": null, /// "challenge_txid": null, /// "take2_txid": null, -/// "disprove_txid": null, /// "watchtower_challenge_init_txid": null, -/// "watchtower_challenge_timeout_txids": [], -/// "nack_txids": [], -/// "blockhash_commit_timeout_txid": null, -/// "assert_init_txid": null, -/// "assert_commit_timeout_txids": [], +/// "operator_assert_txid": null, +/// "verifier_assert_txids": [], +/// "disprove_txids": [], /// "init_withdraw_tx_hash": null, /// "bridge_out_start_at": 1699123456, /// "status_updated_at": 1699123456, @@ -973,7 +978,7 @@ pub async fn get_graphs( /// Get ready to kickoff graph /// -/// Returns a BitVM2 graph that is ready for the operator to kickoff. This endpoint is used by +/// Returns a BitVM graph that is ready for the operator to kickoff. This endpoint is used by /// operators to query available graphs that need kickoff processing. /// /// # Query Parameters @@ -1023,13 +1028,10 @@ pub async fn get_graphs( /// "take1_txid": null, /// "challenge_txid": null, /// "take2_txid": null, -/// "disprove_txid": null, /// "watchtower_challenge_init_txid": null, -/// "watchtower_challenge_timeout_txids": [], -/// "nack_txids": [], -/// "blockhash_commit_timeout_txid": null, -/// "assert_init_txid": null, -/// "assert_commit_timeout_txids": [], +/// "operator_assert_txid": null, +/// "verifier_assert_txids": [], +/// "disprove_txids": [], /// "init_withdraw_tx_hash": null, /// "bridge_out_start_at": 0, /// "status_updated_at": 1699123456, @@ -1114,181 +1116,42 @@ pub async fn get_ready_to_kickoff_graph( /// Get graph Bitcoin transaction progress data /// -/// Helper function to retrieve progress tracking data for specific BitVM2 graph transactions. +/// Helper function to retrieve progress tracking data for specific BitVM graph transactions. /// This function monitors transaction vout status and extracts progress information for /// watchtower challenges and assert operations. /// /// # Parameters /// /// - `storage_processor`: Database storage processor for querying transaction monitoring data -/// - `btc_tx_name`: The type of Bitcoin transaction to query (WatchtowerChallengeInit or AssertInit) +/// - `btc_tx_name`: The type of Bitcoin transaction to query /// - `graph`: The graph instance containing transaction IDs /// /// # Returns /// /// - `Ok((progress_data_vec, fail_reason))`: Tuple of progress data steps and optional failure reason -/// - `Err`: Error if database query or JSON deserialization fails -/// -/// # Features -/// -/// - For WatchtowerChallengeInit: Tracks init, challenge, challenge timeout, NACK, commit blockhash, and timeout steps -/// - For AssertInit: Tracks init and commit steps -/// - Returns empty progress data for other transaction types -/// -/// # Note -/// -/// Progress data includes current/total counts for each step in multi-stage transaction processes. +/// - `Err`: Error if database query fails pub(crate) async fn get_graph_btc_tx_process_data<'a>( - storage_processor: &mut StorageProcessor<'a>, - btc_tx_name: GraphBtcTxName, - graph: &Graph, + _storage_processor: &mut StorageProcessor<'a>, + _btc_tx_name: GraphBtcTxName, + _graph: &Graph, ) -> anyhow::Result<(Vec, Option)> { - let mut progress_datas: Vec = vec![]; - // todo update fail reason - let mut fail_reason: Option = None; - - match btc_tx_name { - GraphBtcTxName::WatchtowerChallengeInit => { - if let Some(tx) = graph.watchtower_challenge_init_txid.clone() - && let Some(vout_monitor) = - storage_processor.find_graph_btc_tx_vout_monitor(&graph.graph_id, &tx).await? - && let Ok(monitor_data) = - serde_json::from_str::(&vout_monitor.monitor_data) - && let Ok(challenge_status) = - serde_json::from_str::(&graph.sub_status) - { - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_INIT.to_string(), - current: 1, - total: 1, - }); - let (challenge_current, challenge_total) = - monitor_data.get_challenge_process_desc(); - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_CHALLENGE.to_string(), - current: challenge_current, - total: challenge_total, - }); - - let (challenge_timeout_current, challenge_timeout_total) = - monitor_data.get_challenge_timeout_process_desc(); - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_CHALLENGE_TIMEOUT.to_string(), - current: challenge_timeout_current, - total: challenge_timeout_total, - }); - - let (ack_current, ack_total) = monitor_data.get_ack_process_desc(); - if ack_total > 0 { - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_ACK.to_string(), - current: ack_current, - total: ack_total, - }); - } - - let (block_hash_current, block_hash_total) = - monitor_data.get_commit_block_hash_desc(); - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_COMMIT_BLOCKHASH.to_string(), - current: block_hash_current, - total: block_hash_total, - }); - - let (current, total) = monitor_data.get_commit_block_hash_timeout_desc(); - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_COMMIT_BLOCKHASH_TIMEOUT.to_string(), - current, - total, - }); - - if let Some(disprove_type) = challenge_status.disprove_type { - match disprove_type { - DisproveTxType::OperatorCommitTimeout => { - fail_reason = - Some("The operator commits blockhash timeout".to_string()); - } - DisproveTxType::OperatorNack => { - let (challenge_timeout_num, nack_num) = ( - challenge_timeout_total - challenge_timeout_current, - ack_total - ack_current, - ); - - fail_reason = match (challenge_timeout_num > 0, nack_num > 0) { - (true, true) => Some(format!( - "The operator has {challenge_timeout_num} unsent Challenge-Timeout transactions, and {nack_num} unsent Ack transactions" - )), - (false, true) => Some(format!( - "The operator has {nack_num} unsent Ack transactions" - )), - (true, false) => Some(format!( - "The operator has {challenge_timeout_num} unsent Challenge-Timeout transactions" - )), - (false, false) => None, - }; - } - _ => {} - } - } - } else { - progress_datas.push(ProgressData { - name: WATCHTOWER_CHALLENGE_STEP_INIT.to_string(), - current: 0, - total: 1, - }); - } - } - GraphBtcTxName::AssertInit => { - if let Some(tx) = graph.assert_init_txid.clone() - && let Some(vout_monitor) = - storage_processor.find_graph_btc_tx_vout_monitor(&graph.graph_id, &tx).await? - && let Ok(monitor_data) = - serde_json::from_str::(&vout_monitor.monitor_data) - && let Ok(challenge_status) = - serde_json::from_str::(&graph.sub_status) - { - progress_datas.push(ProgressData { - name: ASSERT_STEP_INIT.to_string(), - current: 1, - total: 1, - }); - let (current, total) = monitor_data.get_commit_process_desc(); - progress_datas.push(ProgressData { - name: ASSERT_STEP_COMMIT.to_string(), - current, - total, - }); - - if let Some(DisproveTxType::AssertTimeout) = challenge_status.disprove_type { - fail_reason = - Some(format!("The operator has {} unsent assertions", total - current)); - } - } else { - progress_datas.push(ProgressData { - name: ASSERT_STEP_INIT.to_string(), - current: 0, - total: 1, - }); - } - } - _ => {} - } - Ok((progress_datas, fail_reason)) + Ok((vec![], None)) } /// Get graph transaction by name /// /// Returns raw Bitcoin transaction data and progress information for a specific transaction -/// within a BitVM2 graph. Supports querying various transaction types including kickoff, +/// within a BitVM graph. Supports querying various transaction types including kickoff, /// challenge, and take transactions. /// /// # Path Parameters /// -/// - `graph_id`: UUID of the BitVM2 graph +/// - `graph_id`: UUID of the BitVM graph /// /// # Query Parameters /// -/// - `tx_name`: Name of the transaction to retrieve (e.g., "kickoff", "challenge", "take1", etc.) +/// - `tx_name`: Name of the transaction to retrieve, using the `*.hex` names from `GraphBtcTxName` +/// - `index`: Optional verifier index for `verifier-assert.hex` and `disprove.hex` /// /// # Returns /// @@ -1312,38 +1175,7 @@ pub(crate) async fn get_graph_btc_tx_process_data<'a>( /// { /// "btc_tx_data": { /// "raw_data": "020000000001...", -/// "progresses": [ -/// { -/// "name": "Watchtower Challenge init", -/// "current": 1, -/// "total": 1 -/// }, -/// { -/// "name": "Watchtower Challenge", -/// "current": 3, -/// "total": 5 -/// }, -/// { -/// "name": "Watchtower Challenge Timeout", -/// "current": 0, -/// "total": 2 -/// }, -/// { -/// "name": "Operator Challenge NACK", -/// "current": 2, -/// "total": 3 -/// }, -/// { -/// "name": "Operator Commit BlockHash", -/// "current": 1, -/// "total": 4 -/// }, -/// { -/// "name": "Operator Commit BlockHash Timeout", -/// "current": 0, -/// "total": 1 -/// } -/// ], +/// "progresses": [], /// "fail_reason": null /// } /// } @@ -1369,46 +1201,56 @@ pub async fn get_graph_tx( if let (Some(graph_raw_data), Some(graph)) = (graph_raw_data, graph) { let (progresses, fail_reason) = - get_graph_btc_tx_process_data(&mut storage_process, tx_name.clone(), &graph) + get_graph_btc_tx_process_data(&mut storage_process, tx_name, &graph) .await .api_error("GET_GRAPH_TX_ERROR")?; - let simplified_bitvm2_graph: SimplifiedBitvm2Graph = + let simplified_bitvm_graph: SimplifiedBitvmGcGraph = parse_graph_raw_data(graph_raw_data.raw_data.clone(), graph_id_uuid) .await .api_error("GET_GRAPH_TXN_ERROR")?; - let bitvm2_graph: Bitvm2Graph = Bitvm2Graph::from_simplified(&simplified_bitvm2_graph) + let bitvm_graph: BitvmGcGraph = BitvmGcGraph::from_simplified(&simplified_bitvm_graph) .api_error("GET_GRAPH_TX_ERROR")?; + let tx_index = params.index.unwrap_or(0); let raw_data = match tx_name { - GraphBtcTxName::AssertInit => serialize_hex(bitvm2_graph.assert_init.tx()), - GraphBtcTxName::PreKickoff => serialize_hex(bitvm2_graph.cur_prekickoff.tx()), - GraphBtcTxName::Kickoff => serialize_hex(bitvm2_graph.kickoff.tx()), - GraphBtcTxName::Pegin => serialize_hex(bitvm2_graph.pegin.tx()), - GraphBtcTxName::Take1 => serialize_hex(bitvm2_graph.take1.tx()), - GraphBtcTxName::Take2 => serialize_hex(bitvm2_graph.take2.tx()), - GraphBtcTxName::WatchtowerChallengeInit => { - serialize_hex(bitvm2_graph.watchtower_challenge_init.tx()) + GraphBtcTxName::CurPreKickoff => serialize_hex(bitvm_graph.cur_prekickoff.tx()), + GraphBtcTxName::NextPreKickoff => serialize_hex(bitvm_graph.next_prekickoff.tx()), + GraphBtcTxName::ForceSkipKickoff => serialize_hex(bitvm_graph.force_skip_kickoff.tx()), + GraphBtcTxName::QuickChallenge => serialize_hex(bitvm_graph.quick_challenge.tx()), + GraphBtcTxName::ChallengeIncompleteKickoff => { + serialize_hex(bitvm_graph.challenge_incomplete_kickoff.tx()) } + GraphBtcTxName::Pegin => serialize_hex(bitvm_graph.pegin.tx()), + GraphBtcTxName::Kickoff => serialize_hex(bitvm_graph.kickoff.tx()), + GraphBtcTxName::Take1 => serialize_hex(bitvm_graph.take1.tx()), GraphBtcTxName::Challenge => { if let Some(challenge_txid) = graph.challenge_txid && let Ok(Some(tx)) = app_state.btc_client.get_tx(&challenge_txid.0).await { serialize_hex(&tx) } else { - serialize_hex(bitvm2_graph.challenge.tx()) + serialize_hex(bitvm_graph.challenge.tx()) } } + GraphBtcTxName::WatchtowerChallengeInit => { + serialize_hex(bitvm_graph.watchtower_challenge_init.tx()) + } + GraphBtcTxName::OperatorAssert => serialize_hex(bitvm_graph.operator_assert.tx()), + GraphBtcTxName::VerifierAssert => { + let tx = bitvm_graph.verifier_asserts.get(tx_index).ok_or_else(|| { + graph_tx_index_error(tx_name, tx_index, bitvm_graph.verifier_asserts.len()) + })?; + serialize_hex(tx.tx()) + } GraphBtcTxName::Disprove => { - if let Some(disprove_txid) = graph.disprove_txid - && let Ok(Some(tx)) = app_state.btc_client.get_tx(&disprove_txid.0).await - { - serialize_hex(&tx) - } else { - "".to_string() - } + let tx = bitvm_graph.disproves.get(tx_index).ok_or_else(|| { + graph_tx_index_error(tx_name, tx_index, bitvm_graph.disproves.len()) + })?; + serialize_hex(tx.tx()) } + GraphBtcTxName::Take2 => serialize_hex(bitvm_graph.take2.tx()), }; ok_response(GraphTxGetResponse { @@ -1429,12 +1271,12 @@ pub async fn get_graph_tx( /// Get all graph transactions /// /// Returns raw Bitcoin transaction data and progress information for all transactions -/// in a BitVM2 graph. This includes all transaction types: assert_init, watchtower_challenge_init, -/// pre_kickoff, challenge, disprove, kickoff, pegin, take1, and take2. +/// in a BitVM graph. This includes all transaction types currently present in `BitvmGcGraph`, +/// including verifier assert and disprove vectors. /// /// # Path Parameters /// -/// - `graph_id`: UUID of the BitVM2 graph +/// - `graph_id`: UUID of the BitVM graph /// /// # Query Parameters /// @@ -1449,7 +1291,7 @@ pub async fn get_graph_tx( /// # Use Case /// /// Applications use this to retrieve all transaction details from a graph in a single request, -/// which is useful for displaying the complete transaction flow and status of a BitVM2 graph. +/// which is useful for displaying the complete transaction flow and status of a BitVM graph. /// /// # Example /// @@ -1460,82 +1302,20 @@ pub async fn get_graph_tx( /// Response example: /// ```json /// { -/// "assert_init": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "watchtower_challenge_init": { -/// "raw_data": "020000000001...", -/// "progresses": [ -/// { -/// "name": "Watchtower Challenge init", -/// "current": 1, -/// "total": 1 -/// }, -/// { -/// "name": "Watchtower Challenge", -/// "current": 3, -/// "total": 5 -/// }, -/// { -/// "name": "Watchtower Challenge Timeout", -/// "current": 0, -/// "total": 2 -/// }, -/// { -/// "name": "Operator Challenge NACK", -/// "current": 2, -/// "total": 3 -/// }, -/// { -/// "name": "Operator Commit BlockHash", -/// "current": 1, -/// "total": 4 -/// }, -/// { -/// "name": "Operator Commit BlockHash Timeout", -/// "current": 0, -/// "total": 1 -/// } -/// ], -/// "fail_reason": null -/// }, -/// "pre_kickoff": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "challenge": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "disprove": { -/// "raw_data": "", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "kickoff": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "pegin": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "take1": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// }, -/// "take2": { -/// "raw_data": "020000000001...", -/// "progresses": [], -/// "fail_reason": null -/// } +/// "cur_prekickoff": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "next_prekickoff": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "force_skip_kickoff": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "quick_challenge": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "challenge_incomplete_kickoff": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "pegin": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "kickoff": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "take1": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "challenge": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "watchtower_challenge_init": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "operator_assert": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null }, +/// "verifier_asserts": [{ "raw_data": "020000000001...", "progresses": [], "fail_reason": null }], +/// "disproves": [{ "raw_data": "020000000001...", "progresses": [], "fail_reason": null }], +/// "take2": { "raw_data": "020000000001...", "progresses": [], "fail_reason": null } /// } /// ``` #[axum::debug_handler] @@ -1595,11 +1375,11 @@ pub async fn get_graph_txn( }), ) })?; - let simplified_bitvm2_graph: SimplifiedBitvm2Graph = - parse_graph_raw_data(graph_raw_data.raw_data.clone(), graph_id_uuid) + let simplified_bitvm_graph: SimplifiedBitvmGcGraph = + parse_graph_raw_data(graph_raw_data.raw_data.clone(), graph.graph_id) .await .api_error("GET_GRAPH_TXN_ERROR")?; - let bitvm2_graph: Bitvm2Graph = Bitvm2Graph::from_simplified(&simplified_bitvm2_graph) + let bitvm_graph: BitvmGcGraph = BitvmGcGraph::from_simplified(&simplified_bitvm_graph) .api_error("GET_GRAPH_TXN_ERROR")?; let (wt_progresses, wt_fail_reason) = get_graph_btc_tx_process_data( @@ -1612,28 +1392,43 @@ pub async fn get_graph_txn( let (assert_progresses, assert_fail_reason) = get_graph_btc_tx_process_data( &mut storage_processor, - GraphBtcTxName::AssertInit, + GraphBtcTxName::OperatorAssert, &graph, ) .await .api_error("GET_GRAPH_TXN_ERROR")?; let mut resp = GraphTxnGetResponse { - assert_init: BtcTxData::new(serialize_hex(bitvm2_graph.assert_init.tx())) - .with_progresses(assert_progresses) - .with_fail_reason(assert_fail_reason), + cur_prekickoff: BtcTxData::new(serialize_hex(bitvm_graph.cur_prekickoff.tx())), + next_prekickoff: BtcTxData::new(serialize_hex(bitvm_graph.next_prekickoff.tx())), + force_skip_kickoff: BtcTxData::new(serialize_hex(bitvm_graph.force_skip_kickoff.tx())), + quick_challenge: BtcTxData::new(serialize_hex(bitvm_graph.quick_challenge.tx())), + challenge_incomplete_kickoff: BtcTxData::new(serialize_hex( + bitvm_graph.challenge_incomplete_kickoff.tx(), + )), + pegin: BtcTxData::new(serialize_hex(bitvm_graph.pegin.tx())), + kickoff: BtcTxData::new(serialize_hex(bitvm_graph.kickoff.tx())), + take1: BtcTxData::new(serialize_hex(bitvm_graph.take1.tx())), + challenge: BtcTxData::new(serialize_hex(bitvm_graph.challenge.tx())), watchtower_challenge_init: BtcTxData::new(serialize_hex( - bitvm2_graph.watchtower_challenge_init.tx(), + bitvm_graph.watchtower_challenge_init.tx(), )) .with_progresses(wt_progresses) .with_fail_reason(wt_fail_reason), - pre_kickoff: BtcTxData::new(serialize_hex(bitvm2_graph.cur_prekickoff.tx())), - challenge: BtcTxData::new(serialize_hex(bitvm2_graph.challenge.tx())), - disprove: Default::default(), - kickoff: BtcTxData::new(serialize_hex(bitvm2_graph.kickoff.tx())), - pegin: BtcTxData::new(serialize_hex(bitvm2_graph.pegin.tx())), - take1: BtcTxData::new(serialize_hex(bitvm2_graph.take1.tx())), - take2: BtcTxData::new(serialize_hex(bitvm2_graph.take2.tx())), + operator_assert: BtcTxData::new(serialize_hex(bitvm_graph.operator_assert.tx())) + .with_progresses(assert_progresses) + .with_fail_reason(assert_fail_reason), + verifier_asserts: bitvm_graph + .verifier_asserts + .iter() + .map(|tx| BtcTxData::new(serialize_hex(tx.tx()))) + .collect(), + disproves: bitvm_graph + .disproves + .iter() + .map(|tx| BtcTxData::new(serialize_hex(tx.tx()))) + .collect(), + take2: BtcTxData::new(serialize_hex(bitvm_graph.take2.tx())), }; if let Some(challenge_txid) = graph.challenge_txid @@ -1641,11 +1436,6 @@ pub async fn get_graph_txn( { resp.challenge.raw_data = serialize_hex(&tx); } - if let Some(disprove_txid) = graph.disprove_txid - && let Ok(Some(tx)) = app_state.btc_client.get_tx(&disprove_txid.0).await - { - resp.disprove.raw_data = serialize_hex(&tx); - } ok_response(resp) } else { @@ -1670,7 +1460,7 @@ pub async fn get_graph_txn( /// /// # Path Parameters /// -/// - `graph_id`: UUID of the current BitVM2 graph +/// - `graph_id`: UUID of the current BitVM graph /// /// # Returns /// @@ -1727,7 +1517,7 @@ pub async fn get_graph_neighbor_ids( /// /// # Path Parameters /// -/// - `instance_id`: UUID of the BitVM2 instance +/// - `instance_id`: UUID of the BitVM instance /// /// # Returns /// @@ -1794,12 +1584,12 @@ pub async fn get_unsigned_pegin_txn( /// Send challenge transaction for a graph /// -/// Loads the graph from DB, rebuilds the full BitVM2 graph, and broadcasts +/// Loads the graph from DB, rebuilds the full BitVM graph, and broadcasts /// the Challenge transaction on Bitcoin. /// /// # Path Parameters /// -/// - `graph_id`: UUID of the BitVM2 graph to challenge +/// - `graph_id`: UUID of the BitVM graph to challenge /// /// # Returns /// @@ -1831,15 +1621,15 @@ pub async fn send_challenge( ) })?; - let simplified_bitvm2_graph: SimplifiedBitvm2Graph = + let simplified_bitvm_graph: SimplifiedBitvmGcGraph = parse_graph_raw_data(graph_raw_data.raw_data, graph_id_uuid) .await .api_error("SEND_CHALLENGE_ERROR")?; - let bitvm2_graph: Bitvm2Graph = - Bitvm2Graph::from_simplified(&simplified_bitvm2_graph).api_error("SEND_CHALLENGE_ERROR")?; + let bitvm_graph: BitvmGcGraph = + BitvmGcGraph::from_simplified(&simplified_bitvm_graph).api_error("SEND_CHALLENGE_ERROR")?; - let txid = send_challenge_tx(&app_state.btc_client, &bitvm2_graph) + let txid = send_challenge_tx(&app_state.btc_client, &bitvm_graph) .await .api_error("SEND_CHALLENGE_ERROR")?; diff --git a/node/src/rpc_service/handler/node_handler.rs b/node/src/rpc_service/handler/node_handler.rs index 7f1473b3..40d7fb30 100644 --- a/node/src/rpc_service/handler/node_handler.rs +++ b/node/src/rpc_service/handler/node_handler.rs @@ -7,13 +7,13 @@ use crate::rpc_service::validation::InputValidator; use crate::rpc_service::{AppState, current_time_secs}; use crate::utils::reflect_goat_address; use axum::extract::{Path, Query, State}; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; use std::sync::Arc; use store::localdb::NodeQuery; /// Get node list /// -/// Returns a paginated list of BitVM2 network nodes based on query parameters. Supports filtering by +/// Returns a paginated list of BitVM network nodes based on query parameters. Supports filtering by /// GOAT address, actor type, and online status. Automatically updates the current node's timestamp. /// /// # Query Parameters @@ -32,7 +32,7 @@ use store::localdb::NodeQuery; /// /// # Use Case /// -/// Frontend applications use this to display the list of active nodes in the BitVM2 network, +/// Frontend applications use this to display the list of active nodes in the BitVM network, /// showing their roles, availability, and connection information. /// /// # Example @@ -113,7 +113,7 @@ pub async fn get_nodes( /// Get nodes overview statistics /// -/// Returns statistical overview of all nodes in the BitVM2 network, including counts by actor type +/// Returns statistical overview of all nodes in the BitVM network, including counts by actor type /// and online/offline status. Automatically updates the current node's timestamp. /// /// # Returns @@ -140,8 +140,8 @@ pub async fn get_nodes( /// "total": 18, /// "online_operators": 8, /// "offline_operators": 2, -/// "online_challengers": 3, -/// "offline_challengers": 1, +/// "online_verifiers": 3, +/// "offline_verifiers": 1, /// "online_committees": 2, /// "offline_committees": 0, /// "online_watchtowers": 2, diff --git a/node/src/rpc_service/handler/proof_handler.rs b/node/src/rpc_service/handler/proof_handler.rs index 5d0d3c62..c5ed3d7e 100644 --- a/node/src/rpc_service/handler/proof_handler.rs +++ b/node/src/rpc_service/handler/proof_handler.rs @@ -132,7 +132,7 @@ async fn handle_proof_desc_forwarding( /// # Use Case /// /// Applications use this to retrieve proof generation status and metadata for different chain proof types -/// in the BitVM2 network. The endpoint supports forwarding requests to dedicated proof builder services +/// in the BitVM network. The endpoint supports forwarding requests to dedicated proof builder services /// when configured via environment variables. /// /// # Example @@ -195,7 +195,7 @@ pub async fn get_chain_proof_desc( /// /// # Query Parameters /// -/// - `instance_id`: Instance ID (required) - the identifier of the BitVM2 instance +/// - `instance_id`: Instance ID (required) - the identifier of the BitVM instance /// - `graph_id`: Graph ID (required) - the identifier of the graph within the instance /// /// # Returns @@ -213,7 +213,7 @@ pub async fn get_chain_proof_desc( /// # Use Case /// /// Applications use this to retrieve proof generation status and metadata for operator proofs -/// in the BitVM2 network. The endpoint supports forwarding requests to dedicated proof builder services +/// in the BitVM network. The endpoint supports forwarding requests to dedicated proof builder services /// when configured via environment variables. /// /// # Example diff --git a/node/src/rpc_service/mod.rs b/node/src/rpc_service/mod.rs index b2a67541..a7d9974e 100644 --- a/node/src/rpc_service/mod.rs +++ b/node/src/rpc_service/mod.rs @@ -1,5 +1,5 @@ pub mod auth; -mod bitvm2; +mod bitvm; mod cors_config; pub mod handler; mod node; @@ -27,7 +27,7 @@ use axum::{ Router, middleware, routing::{get, post}, }; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; use client::btc_chain::BTCClient; use client::goat_chain::GOATClient; use client::http_client::async_client::HttpAsyncClient; @@ -93,6 +93,30 @@ impl AppState { http_client, })) } + + pub async fn create_arc_mock_app_state( + local_db: LocalDB, + actor: Actor, + peer_id: String, + registry: Arc>, + ) -> anyhow::Result> { + let (btc_client, btc_mock_adaptor) = BTCClient::new_mock_client(); + btc_mock_adaptor.set_height(900_000); + + let (goat_client, goat_mock_adaptor) = GOATClient::new_mock_client(); + goat_mock_adaptor.set_latest_block_number(1_000_000); + goat_mock_adaptor.set_finalized_block_number(999_990); + + Ok(Arc::new(AppState { + local_db, + btc_client, + goat_client, + metrics_state: MetricsState::new(registry), + actor, + peer_id, + http_client: HttpAsyncClient::new(None), + })) + } } /// Root path handler @@ -115,15 +139,11 @@ async fn root() -> &'static str { "Hello, World!" } -pub async fn serve( +pub async fn serve_with_app_state( addr: String, - local_db: LocalDB, - actor: Actor, - peer_id: String, - registry: Arc>, + app_state: Arc, cancellation_token: CancellationToken, ) -> anyhow::Result { - let app_state = AppState::create_arc_app_state(local_db, actor, peer_id, registry).await?; let server = Router::new() .route(routes::ROOT, get(root)) .route(routes::v1::NODES_BASE, get(get_nodes)) @@ -201,6 +221,18 @@ pub async fn serve( } } +pub async fn serve( + addr: String, + local_db: LocalDB, + actor: Actor, + peer_id: String, + registry: Arc>, + cancellation_token: CancellationToken, +) -> anyhow::Result { + let app_state = AppState::create_arc_app_state(local_db, actor, peer_id, registry).await?; + serve_with_app_state(addr, app_state, cancellation_token).await +} + /// This method introduces performance overhead and is temporarily used for debugging with the frontend. /// It will be removed afterwards. async fn print_req_and_resp_detail( @@ -239,7 +271,7 @@ mod tests { use crate::env::{ ENV_GOAT_CHAIN_URL, ENV_GOAT_GATEWAY_CONTRACT_ADDRESS, ENV_PROOF_SEVER_URL, get_network, }; - use crate::rpc_service::bitvm2::{ + use crate::rpc_service::bitvm::{ BRIDGE_IN_AMOUNTS, GraphGetResponse, GraphListResponse, InstanceGetResponse, InstanceListResponse, InstanceOverviewResponse, InstanceSettingResponse, }; @@ -389,7 +421,7 @@ mod tests { let mut nodes = Vec::::new(); let (_, public_key) = Secp256k1::new().generate_keypair(&mut rand::thread_rng()); let pub_key = public_key.to_string(); - let actor = Actor::Challenger; + let actor = Actor::Verifier; nodes.push(Node { peer_id: generate_local_key().public().to_peer_id().to_string(), actor: actor.to_string(), @@ -427,7 +459,7 @@ mod tests { tokio::spawn(rpc_service::serve( addr.clone(), local_db, - Actor::Challenger, + Actor::Verifier, generate_local_key().public().to_peer_id().to_string(), Arc::new(Mutex::new(Registry::default())), CancellationToken::new(), @@ -456,7 +488,7 @@ mod tests { resp_validation: Some(Box::new(|text| -> bool { matches!( serde_json::from_str::(&text), - Ok(node_overview) if node_overview.nodes_overview.online_challengers == 1 && + Ok(node_overview) if node_overview.nodes_overview.online_verifiers == 1 && node_overview.nodes_overview.online_committees == 1 ) })), @@ -467,10 +499,10 @@ mod tests { } #[tokio::test(flavor = "multi_thread")] - async fn test_bitvm2_api() -> Result<(), Box> { + async fn test_bitvm_api() -> Result<(), Box> { init(None); let addr = available_addr(); - let actor = Actor::Challenger; + let actor = Actor::Verifier; let local_key = generate_local_key(); let peer_id = local_key.public().to_peer_id().to_string(); let local_db = create_local_db(&temp_sqlite_db_path()).await; @@ -572,13 +604,10 @@ mod tests { take1_txid: None, challenge_txid: None, take2_txid: None, - disprove_txid: None, + operator_assert_txid: None, + verifier_assert_txids: vec![], + disprove_txids: vec![], watchtower_challenge_init_txid: None, - watchtower_challenge_timeout_txids: vec![], - nack_txids: vec![], - blockhash_commit_timeout_txid: None, - assert_init_txid: None, - assert_commit_timeout_txids: vec![], init_withdraw_tx_hash: Some(format!("0x{}", hex::encode(generate_random_bytes(32)))), bridge_out_start_at: current_time_secs() + 100, status_updated_at: current_time_secs(), @@ -607,13 +636,10 @@ mod tests { take1_txid: None, challenge_txid: None, take2_txid: None, - disprove_txid: None, + operator_assert_txid: None, + verifier_assert_txids: vec![], + disprove_txids: vec![], watchtower_challenge_init_txid: None, - watchtower_challenge_timeout_txids: vec![], - nack_txids: vec![], - blockhash_commit_timeout_txid: None, - assert_init_txid: None, - assert_commit_timeout_txids: vec![], init_withdraw_tx_hash: None, bridge_out_start_at: 0, status_updated_at: current_time_secs(), @@ -765,7 +791,7 @@ mod tests { })), }, ]; - do_batch_tests("bitvm2 apis", &Client::new(), &api_test_items).await?; + do_batch_tests("bitvm apis", &Client::new(), &api_test_items).await?; Ok(()) } diff --git a/node/src/rpc_service/node.rs b/node/src/rpc_service/node.rs index 30982ef1..7e1748a4 100644 --- a/node/src/rpc_service/node.rs +++ b/node/src/rpc_service/node.rs @@ -58,6 +58,7 @@ pub struct NodeDesc { pub trait ToNodeDesc { fn to_node_desc(self, time_threshold: i64, current_peer_id: &str) -> NodeDesc; } + impl ToNodeDesc for Node { fn to_node_desc(self, time_threshold: i64, current_peer_id: &str) -> NodeDesc { let status = if self.updated_at >= time_threshold || self.peer_id == current_peer_id { diff --git a/node/src/rpc_service/routes.rs b/node/src/rpc_service/routes.rs index a6463f5f..901b4bda 100644 --- a/node/src/rpc_service/routes.rs +++ b/node/src/rpc_service/routes.rs @@ -25,8 +25,11 @@ pub(crate) mod v1 { // pub const PROOFS_BASE: &str = "/v1/proofs"; pub const PROOFS_CHAIN_PROOFS_DESC: &str = "/v1/proofs/chain_proofs_desc"; pub const NODES_WATCHTOWER_BASE: &str = "/v1/proofs/watchtower_proofs"; + #[allow(dead_code)] pub const NODES_OPERATOR_BASE: &str = "/v1/proofs/operator_proofs"; + pub const PROOFS_WRAPPER_PROOF: &str = "/v1/proofs/wrapper_proofs"; pub const PROOFS_OPERATOR_PROOF_DESC: &str = "/v1/proofs/operator_proofs_desc"; pub const PROOFS_WATCHTOWER_PROOF_TIMEOUT: &str = "/v1/proofs/watchtower_proofs_timeout"; + #[allow(dead_code)] pub const PROOFS_OPERATOR_PROOF_TIMEOUT: &str = "/v1/proofs/operator_proofs_timeout"; } diff --git a/node/src/rpc_service/validation.rs b/node/src/rpc_service/validation.rs index d662dc65..ee284346 100644 --- a/node/src/rpc_service/validation.rs +++ b/node/src/rpc_service/validation.rs @@ -5,7 +5,7 @@ use alloy::primitives::Address as EvmAddress; use axum::Json; use axum::http::StatusCode; use bitcoin::{Address, AddressType, Network, PublicKey}; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; use libp2p::PeerId; use std::str::FromStr; use uuid::Uuid; @@ -182,8 +182,10 @@ impl InputValidator { Json(ErrorResponse { error: "INVALID_TX_NAME_TYPE".to_string(), message: format!( - "Invalid tx_name: {tx_name}. Valid values are: pegin.hex, kickoff.hex, pre-kickoff.hex, \ - watchtower-challenge-init.hex, assert-init.hex, challenge.hex, take1.hex, take2.hex, disprove.hex" + "Invalid tx_name: {tx_name}. Valid values are: cur-pre-kickoff.hex, next-pre-kickoff.hex, \ + force-skip-kickoff.hex, quick-challenge.hex, challenge-incomplete-kickoff.hex, pegin.hex, \ + kickoff.hex, take1.hex, challenge.hex, watchtower-challenge-init.hex, operator-assert.hex, \ + verifier-assert.hex, disprove.hex, take2.hex. Use index for verifier-assert.hex and disprove.hex." ), }), )), diff --git a/node/src/scheduled_tasks/event_watch_task.rs b/node/src/scheduled_tasks/event_watch_task.rs index ed58abe7..ca8c344e 100644 --- a/node/src/scheduled_tasks/event_watch_task.rs +++ b/node/src/scheduled_tasks/event_watch_task.rs @@ -19,8 +19,8 @@ use alloy::sol_types::{SolType, SolValue}; use bitcoin::address::NetworkUnchecked; use bitcoin::hashes::Hash; use bitcoin::{Address, Amount, OutPoint, Txid}; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::types::UserInfo; +use bitvm_lib::actors::Actor; +use bitvm_lib::types::UserInfo; use client::btc_chain::BTCClient; use client::goat_chain::{GOATClient, GoatInitConfig}; use client::graphs::GraphQueryClient; @@ -454,10 +454,10 @@ async fn handle_withdraw_disproved_events<'a>( ) -> anyhow::Result<()> { for event in withdraw_disproved_events { let graph_id = Uuid::from_str(&strip_hex_prefix_owned(&event.graph_id))?; - let (flag, challenger_addr) = reflect_goat_address(Some(event.challenger_addr.clone())); + let (flag, verifier_addr) = reflect_goat_address(Some(event.challenger_addr.clone())); if !flag { warn!( - "handle_withdraw_disproved_events failed as cast challenger address failed, detail: {}, {}", + "handle_withdraw_disproved_events failed as cast verifier address failed, detail: {}, {}", event.transaction_hash, event.challenger_addr ); continue; @@ -473,7 +473,7 @@ async fn handle_withdraw_disproved_events<'a>( add_node_reward( storage_processor, - &challenger_addr.unwrap(), + &verifier_addr.unwrap(), U256::from_str(&event.challenger_amount_sats).unwrap_or_default(), ) .await?; @@ -1230,7 +1230,25 @@ pub async fn run_watch_event_task( })], ), ( - Actor::Challenger, + Actor::Verifier, + vec![WatchEventConfig::Gateway(TheGraphConfig { + address: gateway_contract, + the_graph_url: get_goat_gateway_the_graph_urls_from_env(), + event_entities: vec![ + GatewayEventEntity::InitWithdraws, + GatewayEventEntity::CancelWithdraws, + GatewayEventEntity::ProceedWithdraws, + GatewayEventEntity::WithdrawHappyPaths, + GatewayEventEntity::WithdrawUnhappyPaths, + GatewayEventEntity::WithdrawDisproveds, + GatewayEventEntity::BridgeInRequests, + GatewayEventEntity::BridgeIns, + GatewayEventEntity::PostGraphDatas, + ], + })], + ), + ( + Actor::Verifier, vec![WatchEventConfig::Gateway(TheGraphConfig { address: gateway_contract, the_graph_url: get_goat_gateway_the_graph_urls_from_env(), diff --git a/node/src/scheduled_tasks/graph_maintenance_tasks.rs b/node/src/scheduled_tasks/graph_maintenance_tasks.rs index f9838e26..779b8be1 100644 --- a/node/src/scheduled_tasks/graph_maintenance_tasks.rs +++ b/node/src/scheduled_tasks/graph_maintenance_tasks.rs @@ -1,25 +1,18 @@ use crate::action::{ - AssertCommitTimeout, AssertInitReady, ChallengeSent, DisproveReady, DisproveSent, - GOATMessageContent, KickoffReady, KickoffSent, OperatorAckTimeout, - OperatorCommitBlockHashReady, OperatorCommitBlockHashTimeout, PreKickoffSent, Take1Ready, - Take1Sent, Take2Ready, Take2Sent, WatchtowerChallengeInitSent, WatchtowerChallengeSent, - WatchtowerChallengeTimeout, + AssertReady, ChallengeSent, DisproveSent, GOATMessageContent, KickoffReady, KickoffSent, + PreKickoffSent, Take1Ready, Take1Sent, Take2Ready, Take2Sent, WronglyChallengeTimeout, }; use crate::env::get_network; use crate::rpc_service::current_time_secs; use crate::scheduled_tasks::fetch_on_turn_graph_by_status; +use crate::utils::todo_funcs::min_required_watchtower; use crate::utils::{SELF_SENDER, outpoint_spent_txid, upsert_message}; use bitcoin::Txid; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::challenger::{ - assert_commit_timeout_timelock, commit_blockhash_timeout_timelock, nack_timelock, -}; -use bitvm2_lib::operator::{ - take1_timelock, take2_timelocks, watchtower_challenge_timeout_timelock, -}; +use bitvm_lib::actors::Actor; +use bitvm_lib::operator::{take1_timelock, take2_timelock}; +use bitvm_lib::verifier::disprove_timelock; use client::btc_chain::BTCClient; use client::goat_chain::DisproveTxType; -use indexmap::IndexMap; use serde::{Deserialize, Serialize}; use store::localdb::{LocalDB, StorageProcessor}; use store::{ @@ -29,48 +22,23 @@ use strum::{Display, EnumString}; use tracing::{info, trace, warn}; use uuid::Uuid; -const CONNECTOR_G_MARGIN: i64 = 3; // const CONNECTOR_D_MARGIN: u64 = 2; -// const CONNECTOR_F_MARGIN: u64 = 2; const CONNECTOR_GUARDIAN_MARGIN: u64 = 2; const MONITE_BTC_TX_NAME_KICKOFF: &str = "kickoff"; const MONITE_BTC_TX_NAME_WATCHTOWER_INIT: &str = "watchtower_init"; -const MONITE_BTC_TX_NAME_ASSERT_INIT: &str = "assert_init"; - -#[derive(Clone, Debug)] -pub struct ChallengeTimeLockConfig { - pub watchtower_challenge_timelock: i64, - pub watchtower_ack_timelock: i64, - pub watchtower_blockhash_commit_timelock: i64, - pub assert_commit_timelock: i64, -} - -fn get_challenge_timelock_config() -> ChallengeTimeLockConfig { - ChallengeTimeLockConfig { - watchtower_challenge_timelock: watchtower_challenge_timeout_timelock(get_network()) as i64, - watchtower_ack_timelock: nack_timelock(get_network()) as i64, - watchtower_blockhash_commit_timelock: commit_blockhash_timeout_timelock(get_network()) - as i64, - assert_commit_timelock: assert_commit_timeout_timelock(get_network()) as i64, - } -} +const MONITE_BTC_TX_NAME_PROVER_ASSERT: &str = "prover_assert"; fn get_take1_timelock_config() -> i64 { take1_timelock(get_network()) as i64 } -pub struct Take2TimeLockConfig { - pub assert_init_out_timelock: i64, - pub watchtower_challenge_init_out_timelock: i64, +fn get_take2_timelock_config() -> i64 { + take2_timelock(get_network()) as i64 } -fn get_take2_timelock_config() -> Take2TimeLockConfig { - let (watchtower_challenge_init_out_timelock, assert_init_out_timelock) = - take2_timelocks(get_network()); - Take2TimeLockConfig { - assert_init_out_timelock: assert_init_out_timelock as i64, - watchtower_challenge_init_out_timelock: watchtower_challenge_init_out_timelock as i64, - } + +fn get_disprove_timelock_config() -> i64 { + disprove_timelock(get_network()) as i64 } #[derive(Clone, Debug, Eq, PartialEq, Display, EnumString)] enum OperatorWithdrawType { @@ -78,373 +46,38 @@ enum OperatorWithdrawType { Take2, } -/// Watchtower init tx vout item status watchtower processed -#[derive( - Copy, Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, Display, EnumString, -)] -pub enum CommitBlockHashStatus { - #[default] - None, - WatchtowerChallengeProcessed, // watchtower challenge processed, but commit blockhash not detected yet - OperatorCommit, // commit blockhash detected - OperatorCommitTimeout, // commit blockhash not detected, but timelock expired -} - #[derive( Copy, Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, Display, EnumString, )] -pub enum AssertCommitStatus { +pub enum VerifierChallengeStatus { #[default] None, - OperatorInit, // assert init vout detected, but some assert commit not detected yet - OperatorCommit, // all assert commit sent - OperatorCommitTimeout, // some assert commit not sent, but timelock expired + VerifierAsserted, + ProverAnswered, + Disproved, } -#[derive( - Copy, Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, Display, EnumString, -)] -pub enum WatchtowerChallengeStatus { - #[default] - None, - OperatorInit, // watchtower init detected, but no challenge detected yet - WatchtowerChallenge, // Some Watchtower challenge, and timelock not expired - WatchtowerChallengeTimeout, // Some Watchtower did not challenge, and timelock expired - OperatorACKTimeout, // Operator did not send ACK for some Watchtower, and timelock expired - WatchtowerChallengeNormalFinished, // Normal Finished, TODO: rename it - WatchtowerChallengeDisproveFinished, // Disproved Finished -} - -#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize, Default)] pub struct ChallengeSubStatus { - pub watchtower_challenge_status: WatchtowerChallengeStatus, - pub commit_blockhash_status: CommitBlockHashStatus, - pub assert_commit_status: AssertCommitStatus, + pub watchtower_challenge_status: Vec, // true for challenge connector spend + pub verifier_challenge_status: Vec, pub disprove_type: Option, pub disprove_index: i32, } impl ChallengeSubStatus { - pub fn is_watchtower_challenge_success(&self) -> bool { + pub fn is_watchtower_challenge_success(&self, required_watchtower_num: usize) -> bool { self.watchtower_challenge_status - == WatchtowerChallengeStatus::WatchtowerChallengeNormalFinished - && self.commit_blockhash_status == CommitBlockHashStatus::OperatorCommit + .iter() + .filter(|&&status| status) + .take(required_watchtower_num) + .count() + == required_watchtower_num } pub fn is_disproved(&self) -> bool { self.disprove_type.is_some() } - - pub fn is_all_commit_success(&self) -> bool { - self.is_watchtower_challenge_success() && self.is_assert_commit_success() - } - - pub fn is_assert_commit_success(&self) -> bool { - self.assert_commit_status == AssertCommitStatus::OperatorCommit - } -} - -#[derive(Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, Display, EnumString)] -pub enum WatchtowerChallengeItemStatus { - #[default] - None, - OperatorInit, - Challenge, - ChallengeTimeout, - OperatorACK, - OperatorNACK, -} - -/// Watchtower init tx vout data -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct WTInitTxVoutMonitorData { - pub data_map: IndexMap, - pub require_disproved_indexes: Vec, - pub commit_blockhash_status: CommitBlockHashStatus, -} - -impl WTInitTxVoutMonitorData { - pub fn new(index_size: i32) -> Self { - let mut data_map: IndexMap = IndexMap::new(); - for i in 0..index_size { - data_map.insert(i, WatchtowerChallengeItemStatus::OperatorInit); - } - Self { - data_map, - require_disproved_indexes: vec![], - commit_blockhash_status: CommitBlockHashStatus::None, - } - } - pub async fn monitor_vout( - &mut self, - btc_client: &BTCClient, - txid: &Txid, - input_challenge_timeout_txids: &[SerializableTxid], - nack_txids: &[SerializableTxid], - commit_blockhash_timeout_txid: &SerializableTxid, - ) -> anyhow::Result<(Vec<(usize, Txid)>, Vec<(usize, Txid)>, Vec<(usize, Txid)>)> { - let mut challenge_txids: Vec<(usize, Txid)> = Vec::new(); - let mut challenge_timeout_txids: Vec<(usize, Txid)> = Vec::new(); - let mut ack_txids: Vec<(usize, Txid)> = Vec::new(); - for (k, status) in self.data_map.iter_mut() { - let index = *k; - if *status == WatchtowerChallengeItemStatus::OperatorInit - && let Some(spend_txid) = - outpoint_spent_txid(btc_client, txid, (index * 2) as u64).await? - { - if input_challenge_timeout_txids.iter().any(|v| v.0 == spend_txid) { - *status = WatchtowerChallengeItemStatus::ChallengeTimeout; - challenge_timeout_txids.push((index as usize, spend_txid)); - } else { - *status = WatchtowerChallengeItemStatus::Challenge; - challenge_txids.push((index as usize, spend_txid)); - } - } - - if *status == WatchtowerChallengeItemStatus::Challenge - && let Some(spend_txid) = - outpoint_spent_txid(btc_client, txid, (index * 2 + 1) as u64).await? - { - if nack_txids.iter().any(|v| v.0 == spend_txid) { - *status = WatchtowerChallengeItemStatus::OperatorNACK; - } else { - *status = WatchtowerChallengeItemStatus::OperatorACK; - ack_txids.push((index as usize, spend_txid)); - } - } - } - let connector_g_vout = self.data_map.len() as i32 * 2; - if matches!( - self.commit_blockhash_status, - CommitBlockHashStatus::None | CommitBlockHashStatus::WatchtowerChallengeProcessed - ) && let Some(spend_txid) = - outpoint_spent_txid(btc_client, txid, connector_g_vout as u64).await? - { - if commit_blockhash_timeout_txid.0 == spend_txid { - self.commit_blockhash_status = CommitBlockHashStatus::OperatorCommitTimeout; - } else { - self.commit_blockhash_status = CommitBlockHashStatus::OperatorCommit; - } - } else if self.commit_blockhash_status == CommitBlockHashStatus::None - && self.is_commit_blockhash_ready() - { - self.commit_blockhash_status = CommitBlockHashStatus::WatchtowerChallengeProcessed; - } - Ok((challenge_txids, challenge_timeout_txids, ack_txids)) - } - - fn update_disprove_indexes(&mut self) { - self.require_disproved_indexes = vec![]; - for (index, status) in self.data_map.iter() { - if matches!( - *status, - WatchtowerChallengeItemStatus::OperatorInit - | WatchtowerChallengeItemStatus::Challenge - ) { - self.require_disproved_indexes.push(*index as usize); - } - } - } - - fn get_require_disproved_string(&self) -> String { - format!( - "[{}]", - self.require_disproved_indexes - .iter() - .map(|v| v.to_string()) - .collect::>() - .join("_") - ) - } - - pub fn get_challenge_process_desc(&self) -> (usize, usize) { - ( - self.data_map - .iter() - .filter(|(_, v)| { - matches!( - **v, - WatchtowerChallengeItemStatus::Challenge - | WatchtowerChallengeItemStatus::OperatorACK - ) - }) - .count(), - self.data_map.len(), - ) - } - - pub fn get_challenge_timeout_process_desc(&self) -> (usize, usize) { - ( - self.data_map - .iter() - .filter(|(_, v)| **v == WatchtowerChallengeItemStatus::ChallengeTimeout) - .count(), - self.data_map.len(), - ) - } - - pub fn get_commit_block_hash_desc(&self) -> (usize, usize) { - match self.commit_blockhash_status { - CommitBlockHashStatus::OperatorCommit => (1, 1), - _ => (0, 1), - } - } - - pub fn get_commit_block_hash_timeout_desc(&self) -> (usize, usize) { - match self.commit_blockhash_status { - CommitBlockHashStatus::OperatorCommitTimeout => (1, 1), - _ => (0, 1), - } - } - - pub fn get_ack_process_desc(&self) -> (usize, usize) { - ( - self.data_map - .iter() - .filter(|(_, v)| **v == WatchtowerChallengeItemStatus::OperatorACK) - .count(), - self.data_map - .iter() - .filter(|(_, v)| { - matches!( - **v, - WatchtowerChallengeItemStatus::Challenge - | WatchtowerChallengeItemStatus::OperatorACK - | WatchtowerChallengeItemStatus::OperatorNACK - ) - }) - .count(), - ) - } - - pub fn is_watchtower_challenge_success(&self) -> bool { - self.data_map.values().all(|status| { - matches!( - status, - WatchtowerChallengeItemStatus::OperatorACK - | WatchtowerChallengeItemStatus::ChallengeTimeout - ) - }) && self.commit_blockhash_status == CommitBlockHashStatus::OperatorCommit - } - - pub fn is_commit_blockhash_processed(&self) -> bool { - matches!( - self.commit_blockhash_status, - CommitBlockHashStatus::OperatorCommit | CommitBlockHashStatus::OperatorCommitTimeout - ) - } - - pub fn is_commit_blockhash_ready(&self) -> bool { - self.data_map.values().all(|status| { - matches!( - status, - WatchtowerChallengeItemStatus::OperatorACK - | WatchtowerChallengeItemStatus::ChallengeTimeout - | WatchtowerChallengeItemStatus::Challenge - ) - }) - } - - pub fn is_disproved(&self) -> bool { - self.data_map.values().any(|status| *status == WatchtowerChallengeItemStatus::OperatorNACK) - || self.commit_blockhash_status == CommitBlockHashStatus::OperatorCommitTimeout - } -} - -/// Assert init tx vout item status -#[derive(Clone, Debug, Serialize, Deserialize, Default, Eq, PartialEq, Display, EnumString)] -pub enum AssertCommitItemStatus { - #[default] - None, - OperatorInit, - OperatorCommit, - OperatorCommitTimeout, -} -/// Assert init tx vout data -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct AssertInitTxVoutMonitorData { - pub data_map: IndexMap, - pub require_disproved_indexes: Vec, -} - -impl AssertInitTxVoutMonitorData { - pub fn new(index_size: i32) -> Self { - let mut data_map: IndexMap = IndexMap::new(); - for i in 0..index_size { - data_map.insert(i, AssertCommitItemStatus::OperatorInit); - } - Self { data_map, require_disproved_indexes: vec![] } - } - pub async fn monitor_vout( - &mut self, - btc_client: &BTCClient, - txid: &Txid, - committ_timeout_txids: &[SerializableTxid], - ) -> anyhow::Result { - let mut vout_spent_detect = 0; - for (k, status) in self.data_map.iter_mut() { - if *status == AssertCommitItemStatus::OperatorInit - && let Some(spend_txid) = outpoint_spent_txid(btc_client, txid, *k as u64).await? - { - if committ_timeout_txids.iter().any(|v| v.0 == spend_txid) { - *status = AssertCommitItemStatus::OperatorCommitTimeout; - } else { - *status = AssertCommitItemStatus::OperatorCommit; - } - vout_spent_detect += 1 - } - } - Ok(vout_spent_detect) - } - - pub fn is_assert_success(&self) -> bool { - self.data_map.values().all(|status| *status == AssertCommitItemStatus::OperatorCommit) - } - - pub fn get_commit_process_desc(&self) -> (usize, usize) { - ( - self.data_map - .iter() - .filter(|(_, v)| **v == AssertCommitItemStatus::OperatorCommit) - .count(), - self.data_map.len(), - ) - } - - fn update_disprove_indexes(&mut self) { - self.require_disproved_indexes = vec![]; - for (index, status) in self.data_map.iter() { - if *status == AssertCommitItemStatus::OperatorInit { - self.require_disproved_indexes.push(*index as usize); - } - } - } - - fn get_require_disproved_string(&self) -> String { - format!( - "[{}]", - self.require_disproved_indexes - .iter() - .map(|v| v.to_string()) - .collect::>() - .join("_") - ) - } - - fn is_disproved(&self) -> bool { - self.data_map - .values() - .any(|status| *status == AssertCommitItemStatus::OperatorCommitTimeout) - } -} - -/// Parse monitor data from JSON string -fn parse_monitor_data(monitor_data: &str) -> anyhow::Result -where - T: serde::de::DeserializeOwned, -{ - serde_json::from_str(monitor_data) - .map_err(|e| anyhow::anyhow!("Failed to parse monitor data: {e}")) } pub async fn get_user_init_withdraw_graphs<'a>( @@ -606,164 +239,150 @@ pub async fn detect_take1_or_challenge( Ok(()) } +/// TBD: Currently, the challenge flow is driven by P2P messages; it should be changed to monitoring-driven in the future. #[tracing::instrument(level = "info", skip(local_db, btc_client))] pub async fn process_graph_challenge( local_db: &LocalDB, btc_client: &BTCClient, ) -> anyhow::Result<()> { + trace!("start tick action: process_graph_challenge"); + let graphs = { let mut storage_processor = local_db.acquire().await?; fetch_on_turn_graph_by_status(&mut storage_processor, &GraphStatus::Challenge.to_string()) .await? }; let current_height = btc_client.get_height().await? as i64; + info!( + "start tick action: process_graph_challenge, graphs: {}, current_height: {current_height}", + graphs.len() + ); + for graph in graphs { - // if detect_kickoff_ref_disprove_tx(btc_client, local_db, &graph).await? { - // warn!( - // "process_graph_challenge detect_kickoff_ref_disprove_tx happened at graph:{}", - // graph.graph_id - // ); - // continue; - // } - let mut sub_status: ChallengeSubStatus = match serde_json::from_str(&graph.sub_status) { - Ok(sub_status) => sub_status, - Err(_) => { - warn!( - "failed to deserialize sub_status at graph:{}, reset to default, old sub_status {}", - graph.graph_id, graph.sub_status - ); - ChallengeSubStatus::default() - } - }; - let mut is_watchtower_challenge_success = sub_status.is_watchtower_challenge_success(); - let mut is_assert_commit_success = sub_status.is_assert_commit_success(); - let mut is_all_commit_success = is_watchtower_challenge_success && is_assert_commit_success; - if !sub_status.is_disproved() && !is_all_commit_success { - trace!("graph:{} is not disproved", graph.graph_id); - if !is_watchtower_challenge_success { - info!("graph:{} watchtower challenge is processing", graph.graph_id); - // process_watchtower_challenge_monitoring may trigger: WatchtowerChallengeSent, WatchtowerChallengeTimeout, OperatorAckTimeout, DisproveSent(OperatorCommitTimeout/OperatorNack), OperatorCommitBlockHashReady, OperatorCommitBlockHashTimeout - is_watchtower_challenge_success = process_watchtower_challenge_monitoring( - btc_client, - local_db, - &graph, - &mut sub_status, - current_height, - ) - .await?; - } - if is_watchtower_challenge_success && !is_assert_commit_success { - info!("graph:{} assert commit is processing", graph.graph_id); - // upsert AssertInitReady message whenever watchtower challenge is finished normally, repeated inserts are idempotent - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Operator, - GOATMessageContent::AssertInitReady(AssertInitReady { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), - 0, - 0, - ) - .await?; - // process_assert_commit_monitoring may trigger: DisproveSent(AssertTimeout), AssertCommitTimeout - is_assert_commit_success = process_assert_commit_monitoring( - btc_client, - local_db, - &graph, - &mut sub_status, - current_height, - ) - .await?; - } - is_all_commit_success = is_watchtower_challenge_success && is_assert_commit_success; + if let Some((actor, message_content)) = + detect_watchtower_challenge(btc_client, local_db, &graph).await? + { + info!("process_graph_challenge detect enough watchtower challenges"); + let mut storage_processor = local_db.acquire().await?; + upsert_message( + &mut storage_processor, + false, + graph.graph_id, + None, + SELF_SENDER.to_string(), + actor, + message_content, + 0, + 0, + ) + .await?; + } + + if let Some((actor, message_content, sub_type)) = + detect_assert_disprove_ready(btc_client, local_db, &graph, current_height).await? + { + info!("process_graph_challenge detect assert disprove ready"); + let mut storage_processor = local_db.acquire().await?; + upsert_message( + &mut storage_processor, + false, + graph.graph_id, + sub_type, + SELF_SENDER.to_string(), + actor, + message_content, + 0, + 0, + ) + .await?; } - if !sub_status.is_disproved() && is_all_commit_success { + + // take2 monitor + if let Some((actor, message_content)) = + detect_take2(btc_client, local_db, &graph, current_height).await? + { + info!("process_graph_challenge detect take2 ready or take2 sent or disprove sent"); let mut storage_processor = local_db.acquire().await?; - info!("graph:{} watchtower challenge and assert commit is finished", graph.graph_id); - // upsert DisproveReady whenever both watchtower-challenge and assert is finished normally, repeated inserts are idempotent upsert_message( &mut storage_processor, false, graph.graph_id, None, SELF_SENDER.to_string(), - Actor::Challenger, - GOATMessageContent::DisproveReady(DisproveReady { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), + actor, + message_content, 0, 0, ) .await?; - // detect_take2 may return: Take2Ready, Take2Sent, DisproveSent(Disprove) - if let Some((actor, message_content)) = - detect_take2(btc_client, local_db, &graph, current_height).await? - { - let mut storage_processor = local_db.acquire().await?; - upsert_message( - &mut storage_processor, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - actor, - message_content, - 0, - 0, - ) - .await?; - } } } - Ok(()) } -/// Check if Take1Ready Take2Ready message needs to be sent -async fn check_operator_withdraw_ready_condition( +/// may trigger: AssertReady +async fn detect_watchtower_challenge( btc_client: &BTCClient, local_db: &LocalDB, - graph_id: Uuid, - check_tx_items: Vec<(Txid, String, OperatorWithdrawType, i64, i64)>, // (txid, tag, height, lock_blocks) - current_height: i64, -) -> anyhow::Result { - info!( - "check_operator_withdraw_ready_condition for graph_id: {graph_id}, check tx size: {}, detail:{check_tx_items:?}", - check_tx_items.len() - ); - let mut ready = true; - for (txid, tx_name, operator_withdraw_type, height, lock_blocks) in check_tx_items { - let height = if height <= 0 { + graph: &Graph, +) -> anyhow::Result> { + let watchtower_challenge_init_txid: Txid = match graph.watchtower_challenge_init_txid.clone() { + Some(txid) => txid.into(), + None => { + warn!( + "detect_watchtower_challenge graph_id:{} watchtower_challenge_init_txid is none", + graph.graph_id + ); + return Ok(None); + } + }; + let required_watchtower_num = min_required_watchtower(); + + let monitor = { + let mut storage_processor = local_db.acquire().await?; + storage_processor + .find_graph_btc_tx_vout_monitor( + &graph.graph_id, + &SerializableTxid::from(watchtower_challenge_init_txid), + ) + .await? + }; + let (_height, vout_len) = match monitor { + Some(monitor) if monitor.height > 0 && monitor.vout_len > 0 => { + (monitor.height, monitor.vout_len) + } + monitor => { + let Some(tx_info) = btc_client.get_tx_info(&watchtower_challenge_init_txid).await? + else { + trace!( + "detect_watchtower_challenge graph_id:{} watchtower challenge init txid {} not on chain", + graph.graph_id, watchtower_challenge_init_txid + ); + return Ok(None); + }; + let height = tx_info.status.block_height.unwrap_or_default() as i64; + if height <= 0 { + trace!( + "detect_watchtower_challenge graph_id:{} watchtower challenge init txid {} not confirmed", + graph.graph_id, watchtower_challenge_init_txid + ); + return Ok(None); + } + let current_times = current_time_secs(); - let (height, vout_len) = match btc_client.get_tx_info(&txid).await? { - Some(tx_info) => ( - tx_info.status.block_height.unwrap_or_default() as i64, - tx_info.vout.len() as i64, - ), + let (monitor_data, created_at, tx_name) = match monitor { + Some(monitor) => (monitor.monitor_data, monitor.created_at, monitor.tx_name), None => { - info!("graph_id:{graph_id}, {operator_withdraw_type} txid {txid} not on chain",); - return Ok(false); + (String::new(), current_times, MONITE_BTC_TX_NAME_WATCHTOWER_INIT.to_string()) } }; - let txid_serial: SerializableTxid = txid.into(); + let vout_len = tx_info.vout.len() as i64; let mut storage_processor = local_db.acquire().await?; - let existing = - storage_processor.find_graph_btc_tx_vout_monitor(&graph_id, &txid_serial).await?; - let (monitor_data, created_at, tx_name_to_use) = match existing { - Some(existing) => (existing.monitor_data, existing.created_at, existing.tx_name), - None => ("".to_string(), current_times, tx_name.clone()), - }; storage_processor .upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { - graph_id, - tx_name: tx_name_to_use, - txid: txid_serial, + graph_id: graph.graph_id, + tx_name, + txid: SerializableTxid::from(watchtower_challenge_init_txid), height, vout_len, monitor_data, @@ -771,22 +390,184 @@ async fn check_operator_withdraw_ready_condition( updated_at: current_times, }) .await?; - height - } else { - height - }; - - info!( - "graph_id:{graph_id}, {operator_withdraw_type} txid {txid} at height {height} lock_blocks {lock_blocks}, current height: {current_height} ", - ); - - if height == 0 || height > 0 && height + lock_blocks > current_height { - ready = false; - break; + (height, vout_len) } - } - Ok(ready) -} + }; + + let mut spent_challenge_connector_num = 0; + for vout in 0..vout_len as u64 { + if outpoint_spent_txid(btc_client, &watchtower_challenge_init_txid, vout).await?.is_some() { + spent_challenge_connector_num += 1; + if spent_challenge_connector_num >= required_watchtower_num { + return Ok(Some(( + Actor::Operator, + GOATMessageContent::AssertReady(AssertReady { + instance_id: graph.instance_id, + graph_id: graph.graph_id, + }), + ))); + } + } + } + Ok(None) +} + +// may trigger disprove ready +async fn detect_assert_disprove_ready( + btc_client: &BTCClient, + local_db: &LocalDB, + graph: &Graph, + current_height: i64, +) -> anyhow::Result)>> { + let operator_assert_txid = match graph.operator_assert_txid.clone() { + Some(operator_assert_txid) => operator_assert_txid.into(), + None => { + warn!( + "detect_assert_disprove_ready graph_id:{} operator_assert_txid has none value", + graph.graph_id + ); + return Ok(None); + } + }; + if graph.verifier_assert_txids.is_empty() { + return Ok(None); + } + + let connector_d_vout = graph.verifier_assert_txids.len() as u64; + if outpoint_spent_txid(btc_client, &operator_assert_txid, connector_d_vout).await?.is_some() { + trace!( + "detect_assert_disprove_ready graph_id:{} connector_d already spent", + graph.graph_id + ); + return Ok(None); + } + + for (index, verifier_assert_txid) in graph.verifier_assert_txids.iter().enumerate() { + let verifier_assert_txid: Txid = verifier_assert_txid.clone().into(); + if outpoint_spent_txid(btc_client, &verifier_assert_txid, 0).await?.is_some() { + continue; + } + + let height = { + let mut storage_processor = local_db.acquire().await?; + storage_processor + .find_graph_btc_tx_vout_monitor(&graph.graph_id, &verifier_assert_txid.into()) + .await? + .unwrap_or_default() + .height + }; + let height = if height <= 0 { + let Some(tx_info) = btc_client.get_tx_info(&verifier_assert_txid).await? else { + continue; + }; + let height = tx_info.status.block_height.unwrap_or_default() as i64; + if height <= 0 { + continue; + } + + let current_times = current_time_secs(); + let mut storage_processor = local_db.acquire().await?; + storage_processor + .upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { + graph_id: graph.graph_id, + tx_name: format!("verifier_assert_{index}"), + txid: verifier_assert_txid.into(), + height, + vout_len: tx_info.vout.len() as i64, + monitor_data: String::new(), + created_at: current_times, + updated_at: current_times, + }) + .await?; + height + } else { + height + }; + + if height + get_disprove_timelock_config() <= current_height { + info!( + "detect_assert_disprove_ready graph_id:{} verifier_assert index:{} is ready to disprove", + graph.graph_id, index + ); + return Ok(Some(( + Actor::Verifier, + GOATMessageContent::WronglyChallengeTimeout(WronglyChallengeTimeout { + instance_id: graph.instance_id, + graph_id: graph.graph_id, + challenge_assert_txid: verifier_assert_txid, + verifier_index: index, + wrongly_challenged_witness: None, + }), + Some(index.to_string()), + ))); + } + } + + Ok(None) +} + +/// Check if Take1Ready Take2Ready message needs to be sent +async fn check_operator_withdraw_ready_condition( + btc_client: &BTCClient, + local_db: &LocalDB, + graph_id: Uuid, + check_tx_items: Vec<(Txid, String, OperatorWithdrawType, i64, i64)>, // (txid, tag, height, lock_blocks) + current_height: i64, +) -> anyhow::Result { + info!( + "check_operator_withdraw_ready_condition for graph_id: {graph_id}, check tx size: {}, detail:{check_tx_items:?}", + check_tx_items.len() + ); + let mut ready = true; + for (txid, tx_name, operator_withdraw_type, height, lock_blocks) in check_tx_items { + let height = if height <= 0 { + let current_times = current_time_secs(); + let (height, vout_len) = match btc_client.get_tx_info(&txid).await? { + Some(tx_info) => ( + tx_info.status.block_height.unwrap_or_default() as i64, + tx_info.vout.len() as i64, + ), + None => { + info!("graph_id:{graph_id}, {operator_withdraw_type} txid {txid} not on chain",); + return Ok(false); + } + }; + let txid_serial: SerializableTxid = txid.into(); + let mut storage_processor = local_db.acquire().await?; + let existing = + storage_processor.find_graph_btc_tx_vout_monitor(&graph_id, &txid_serial).await?; + let (monitor_data, created_at, tx_name_to_use) = match existing { + Some(existing) => (existing.monitor_data, existing.created_at, existing.tx_name), + None => ("".to_string(), current_times, tx_name.clone()), + }; + storage_processor + .upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { + graph_id, + tx_name: tx_name_to_use, + txid: txid_serial, + height, + vout_len, + monitor_data, + created_at, + updated_at: current_times, + }) + .await?; + height + } else { + height + }; + + info!( + "graph_id:{graph_id}, {operator_withdraw_type} txid {txid} at height {height} lock_blocks {lock_blocks}, current height: {current_height} ", + ); + + if height == 0 || height > 0 && height + lock_blocks > current_height { + ready = false; + break; + } + } + Ok(ready) +} /// Process graph data in KickOff status /// may return: Take1Ready, Take1Sent, ChallengeSent @@ -880,315 +661,6 @@ async fn process_kickoff_graph( } } -/// Process watchtower challenge monitoring -/// may trigger: WatchtowerChallengeSent, WatchtowerChallengeTimeout, OperatorAckTimeout, DisproveSent(OperatorCommitTimeout/OperatorNack), OperatorCommitBlockHashReady, OperatorCommitBlockHashTimeout -/// return Ok(true) if watchtower challenge is success -#[tracing::instrument(level = "info", skip(btc_client, local_db))] -async fn process_watchtower_challenge_monitoring( - btc_client: &BTCClient, - local_db: &LocalDB, - graph: &Graph, - _sub_status: &mut ChallengeSubStatus, - current_height: i64, -) -> anyhow::Result { - // WatchtowerChallengeInitSent will be pushed inside refresh_watchtower_challenge_monitor_data - let (mut vout_monitor_data, watchtower_challenge_init_height, monitor_result) = - match refresh_watchtower_challenge_monitor_data(local_db, btc_client, graph).await? { - Some((data, init_height, monitor_result)) => (data, init_height, monitor_result), - None => { - warn!( - "graph_id {} fail to get vout monitor data, maybe watchtower-challenge-init-tx not confirmed yet", - graph.graph_id - ); - return Ok(false); - } - }; - - let timelock_config = get_challenge_timelock_config(); - info!("timelock_config: {timelock_config:?}"); - let is_challenge_timeout = watchtower_challenge_init_height - + timelock_config.watchtower_challenge_timelock - < current_height; - let is_ack_timeout = - watchtower_challenge_init_height + timelock_config.watchtower_ack_timelock < current_height; - let is_blockhash_commit_timeout = watchtower_challenge_init_height - + timelock_config.watchtower_blockhash_commit_timelock - < current_height; - info!( - "is_ack_timeout_{is_ack_timeout}, is_challenge_timeout_{is_challenge_timeout}, \ - is_blockhash_commit_timeout_{is_blockhash_commit_timeout}, watchtower_challenge_init_height:{watchtower_challenge_init_height}, current_height:{current_height} ", - ); - if vout_monitor_data.is_watchtower_challenge_success() { - info!("graph_id {} watchtower challenge is success", graph.graph_id); - return Ok(true); - } - if vout_monitor_data.is_disproved() { - let challenge_start_txid = graph.challenge_txid.clone().map(|v| v.into()); - let (disprove_type, index, challenge_finish_txid) = if vout_monitor_data - .commit_blockhash_status - == CommitBlockHashStatus::OperatorCommitTimeout - { - let blockhash_commit_timeout_txid = graph.blockhash_commit_timeout_txid.clone().ok_or_else(|| anyhow::anyhow!( - "graph_id {} is disproved by OperatorCommitTimeout but blockhash_commit_timeout_txid is none", - graph.graph_id - ))?; - (DisproveTxType::OperatorCommitTimeout, 0, blockhash_commit_timeout_txid.into()) - } else if let Some((&index, _)) = vout_monitor_data - .data_map - .iter() - .find(|(_, status)| **status == WatchtowerChallengeItemStatus::OperatorNACK) - { - let nack_txid = graph.nack_txids.get(index as usize).cloned().ok_or_else(|| { - anyhow::anyhow!( - "graph_id {} is disproved by OperatorNACK at index {} but nack_txid is none", - graph.graph_id, - index - ) - })?; - (DisproveTxType::OperatorNack, index as usize, nack_txid.into()) - } else { - anyhow::bail!( - "graph_id {} vout monitor data is disproved but no OperatorNACK or OperatorCommitBlockHashTimeout found", - graph.graph_id - ) - }; - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Operator, - GOATMessageContent::DisproveSent(DisproveSent { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - disprove_type, - index, - challenge_start_txid, - challenge_finish_txid, - }), - 0, - 0, - ) - .await?; - return Ok(false); // already disproved, no need to process further - } - if !vout_monitor_data.is_commit_blockhash_processed() - && vout_monitor_data.is_commit_blockhash_ready() - { - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Operator, - GOATMessageContent::OperatorCommitBlockHashReady(OperatorCommitBlockHashReady { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), - 0, - 0, - ) - .await?; - } - if !vout_monitor_data.is_commit_blockhash_processed() && is_blockhash_commit_timeout { - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Challenger, - GOATMessageContent::OperatorCommitBlockHashTimeout(OperatorCommitBlockHashTimeout { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), - 0, - 0, - ) - .await?; - } - if vout_monitor_data - .data_map - .values() - .any(|status| *status == WatchtowerChallengeItemStatus::Challenge) - { - let watchtower_challenge_txids = monitor_result.0; - if watchtower_challenge_txids.is_empty() { - warn!( - "graph_id {} watchtower challenge txids is empty when some vout status is Challenge", - graph.graph_id - ); - } else { - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Operator, - GOATMessageContent::WatchtowerChallengeSent(WatchtowerChallengeSent { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - watchtower_challenge_txids, - }), - 0, - 0, - ) - .await?; - } - } - if is_ack_timeout { - // Re-sends an OperatorAckTimeout message whenever the disproved indexes change. - vout_monitor_data.update_disprove_indexes(); - if !vout_monitor_data.require_disproved_indexes.is_empty() { - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - Some(vout_monitor_data.get_require_disproved_string()), - SELF_SENDER.to_string(), - Actor::Challenger, - GOATMessageContent::OperatorAckTimeout(OperatorAckTimeout { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), - 0, - 0, - ) - .await?; - } - } - if is_challenge_timeout { - // Re-sends an WatchtowerChallengeTimeout message whenever the watchtower indexes change. - let watchtower_indexes: Vec = vout_monitor_data - .data_map - .iter() - .filter_map(|(&index, status)| match status { - WatchtowerChallengeItemStatus::OperatorInit => Some(index as usize), - _ => None, - }) - .collect(); - if !watchtower_indexes.is_empty() { - let sub_type = format!( - "[{}]", - watchtower_indexes.iter().map(|v| v.to_string()).collect::>().join("_") - ); - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - Some(sub_type), - SELF_SENDER.to_string(), - Actor::Operator, - GOATMessageContent::WatchtowerChallengeTimeout(WatchtowerChallengeTimeout { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - watchtower_indexes, - }), - 0, - 0, - ) - .await?; - } - } - Ok(false) -} - -/// Process assert commit monitoring -/// may trigger: DisproveSent(AssertTimeout), AssertCommitTimeout -/// return Ok(true) if assert commit is success -async fn process_assert_commit_monitoring( - btc_client: &BTCClient, - local_db: &LocalDB, - graph: &Graph, - _sub_status: &mut ChallengeSubStatus, - current_height: i64, -) -> anyhow::Result { - let (mut vout_monitor_data, assert_init_height, _monitor_result) = - match refresh_assert_monitor_data(local_db, btc_client, graph).await? { - Some((data, init_height, monitor_result)) => (data, init_height, monitor_result), - None => { - warn!( - "graph_id {} fail to get vout monitor data, maybe assert-init-tx not confirmed yet", - graph.graph_id - ); - return Ok(false); - } - }; - - let timelock_config = get_challenge_timelock_config(); - let is_assert_commit_timeout = - assert_init_height + timelock_config.assert_commit_timelock < current_height; - info!( - "process_assert_commit_monitoring: graph id :{} is_assert_commit_timeout:{is_assert_commit_timeout},\ - assert_init_height:{assert_init_height}, timelock_config.assert_commit_timelock:{}, current_height:{current_height}", - graph.graph_id, timelock_config.assert_commit_timelock - ); - if vout_monitor_data.is_assert_success() { - info!("graph_id {} assert commit is success", graph.graph_id); - return Ok(true); - } - if vout_monitor_data.is_disproved() { - let challenge_start_txid = graph.challenge_txid.clone().map(|v| v.into()); - let disprove_type = DisproveTxType::AssertTimeout; - let index = vout_monitor_data - .data_map - .iter() - .find(|(_, status)| **status == AssertCommitItemStatus::OperatorCommitTimeout) - .map(|(&index, _)| index as usize) - .ok_or_else(|| anyhow::anyhow!("graph_id {} assert vout monitor data is disproved but no OperatorCommitTimeout found", graph.graph_id))?; - let challenge_finish_txid = graph.assert_commit_timeout_txids.get(index).cloned().ok_or_else(|| anyhow::anyhow!( - "graph_id {} is disproved by OperatorCommitTimeout at index {} but assert_commit_timeout_txid is none", - graph.graph_id, - index - ))?.into(); - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Operator, - GOATMessageContent::DisproveSent(DisproveSent { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - disprove_type, - index, - challenge_start_txid, - challenge_finish_txid, - }), - 0, - 0, - ) - .await?; - return Ok(false); // already disproved, no need to process further - } - if is_assert_commit_timeout { - vout_monitor_data.update_disprove_indexes(); - if !vout_monitor_data.require_disproved_indexes.is_empty() { - upsert_message( - &mut local_db.acquire().await?, - false, - graph.graph_id, - Some(vout_monitor_data.get_require_disproved_string()), - SELF_SENDER.to_string(), - Actor::Challenger, - GOATMessageContent::AssertCommitTimeout(AssertCommitTimeout { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), - 0, - 0, - ) - .await?; - } - } - - Ok(false) -} - /// may trigger: DisproveSent(QuickChallenge/ChallengeIncompleteKickoff) async fn detect_kickoff_ref_disprove_tx( btc_client: &BTCClient, @@ -1279,7 +751,6 @@ async fn detect_kickoff_ref_disprove_tx( Ok(detected) } -/// Process graph data in Watchtower Assert Normal status /// may trigger: Take2Ready, Take2Sent, DisproveSent(Disprove) async fn detect_take2( btc_client: &BTCClient, @@ -1287,126 +758,112 @@ async fn detect_take2( graph: &Graph, current_height: i64, ) -> anyhow::Result> { - trace!("detect_take2 graph_id {}", graph.graph_id); - let timelock_config = get_take2_timelock_config(); - let (kickoff_txid, watchtower_challenge_init_txid, assert_init_txid, take2_txid): ( - Txid, - Txid, - Txid, - Txid, - ) = match ( + let (kickoff_txid, operator_assert_txid, take2_txid) = match ( graph.kickoff_txid.clone(), - graph.watchtower_challenge_init_txid.clone(), - graph.assert_init_txid.clone(), + graph.operator_assert_txid.clone(), graph.take2_txid.clone(), ) { - ( - Some(kickoff_txid), - Some(watchtower_challenge_init_txid), - Some(assert_init_txid), - Some(take2_txid), - ) => ( - kickoff_txid.into(), - watchtower_challenge_init_txid.into(), - assert_init_txid.into(), - take2_txid.into(), - ), + (Some(kickoff_txid), Some(operator_assert_txid), Some(take2_txid)) => { + (kickoff_txid.into(), operator_assert_txid.into(), take2_txid.into()) + } _ => { warn!( - "detect_take2 graph_id:{}, kickoff_txid, watchtower_challenge_init_txid, assert_init_txid or take2_txid is none", + "detect_take2 graph_id:{} kickoff_txid/operator_assert_txid/take2_txid has none value", graph.graph_id ); return Ok(None); } }; - let spent_txid = match outpoint_spent_txid(btc_client, &kickoff_txid, 3).await? { - Some(txid) => txid, - None => { - trace!("detect_take2 graph_id {} take2 or disprove is not on chain", graph.graph_id); - let mut storage_processor = local_db.acquire().await?; - let watchtower_init_height = storage_processor - .find_graph_btc_tx_vout_monitor( - &graph.graph_id, - &watchtower_challenge_init_txid.into(), - ) - .await? - .unwrap_or_default() - .height; - - let assert_init_height = storage_processor - .find_graph_btc_tx_vout_monitor(&graph.graph_id, &assert_init_txid.into()) - .await? - .unwrap_or_default() - .height; - let ready = check_operator_withdraw_ready_condition( - btc_client, - local_db, - graph.graph_id, - vec![ - ( - watchtower_challenge_init_txid, - MONITE_BTC_TX_NAME_WATCHTOWER_INIT.to_string(), - OperatorWithdrawType::Take2, - watchtower_init_height, - timelock_config.watchtower_challenge_init_out_timelock, - ), - ( - assert_init_txid, - MONITE_BTC_TX_NAME_ASSERT_INIT.to_string(), - OperatorWithdrawType::Take2, - assert_init_height, - timelock_config.assert_init_out_timelock, - ), - ], - current_height, - ) - .await?; + let connector_d_vout = graph.verifier_assert_txids.len() as u64; + if let Some(spend_txid) = + outpoint_spent_txid(btc_client, &operator_assert_txid, connector_d_vout).await? + { + if spend_txid == take2_txid { + info!("detect_take2 graph_id:{} take2 is on chain", graph.graph_id); + return Ok(Some(( + Actor::Committee, + GOATMessageContent::Take2Sent(Take2Sent { + instance_id: graph.instance_id, + graph_id: graph.graph_id, + }), + ))); + } - if ready { + if let Some(tx) = btc_client.get_tx(&spend_txid).await? + && tx.input.len() == 2 + { + let verifier_assert_txid = tx.input[0].previous_output.txid; + if let Some(index) = graph + .verifier_assert_txids + .iter() + .position(|txid| Txid::from(txid.clone()) == verifier_assert_txid) + { info!( - "detect_take2 graph_id {} take2 is ready to send to btc chain", - graph.graph_id + "detect_take2 graph_id:{} disprove is on chain, spent txid:{}, index:{}", + graph.graph_id, spend_txid, index ); return Ok(Some(( - Actor::Operator, - GOATMessageContent::Take2Ready(Take2Ready { + Actor::Committee, + GOATMessageContent::DisproveSent(DisproveSent { instance_id: graph.instance_id, graph_id: graph.graph_id, + disprove_type: DisproveTxType::Disprove, + index, + challenge_start_txid: graph.challenge_txid.clone().map(|v| v.into()), + challenge_finish_txid: spend_txid, }), ))); } - return Ok(None); } - }; - if spent_txid == take2_txid { - info!( - "detect_take2 graph_id {} take2:{} is on btc chain", - graph.graph_id, - spent_txid.to_string() + warn!( + "detect_take2 graph_id:{} connector_d spent by unknown txid:{}", + graph.graph_id, spend_txid ); + return Ok(None); + } + + let guardian_connector_vout = 3; + if outpoint_spent_txid(btc_client, &kickoff_txid, guardian_connector_vout).await?.is_some() { + trace!("detect_take2 graph_id:{} guardian connector already spent", graph.graph_id); + return Ok(None); + } + + let height = { + let mut storage_processor = local_db.acquire().await?; + storage_processor + .find_graph_btc_tx_vout_monitor(&graph.graph_id, &operator_assert_txid.into()) + .await? + .unwrap_or_default() + .height + }; + if check_operator_withdraw_ready_condition( + btc_client, + local_db, + graph.graph_id, + vec![( + operator_assert_txid, + MONITE_BTC_TX_NAME_PROVER_ASSERT.to_string(), + OperatorWithdrawType::Take2, + height, + get_take2_timelock_config(), + )], + current_height, + ) + .await? + { + info!("detect_take2 graph_id:{} take2 is ready to send to btc chain", graph.graph_id); Ok(Some(( - Actor::All, - GOATMessageContent::Take2Sent(Take2Sent { + Actor::Operator, + GOATMessageContent::Take2Ready(Take2Ready { instance_id: graph.instance_id, graph_id: graph.graph_id, }), ))) } else { - let disprove_type = DisproveTxType::Disprove; - let challenge_start_txid = graph.challenge_txid.clone().map(|v| v.into()); - Ok(Some(( - Actor::All, - GOATMessageContent::DisproveSent(DisproveSent { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - disprove_type, - index: 0, - challenge_start_txid, - challenge_finish_txid: spent_txid, - }), - ))) + trace!("detect_take2 graph_id:{} take2 not ready", graph.graph_id); + Ok(None) } } @@ -1448,7 +905,7 @@ async fn check_pre_kickoff_sent( graph_id, None, SELF_SENDER.to_string(), - Actor::Challenger, + Actor::Verifier, GOATMessageContent::PreKickoffSent(PreKickoffSent { instance_id, graph_id }), 0, 0, @@ -1459,302 +916,3 @@ async fn check_pre_kickoff_sent( } Ok(pre_sents) } - -pub(crate) async fn refresh_watchtower_challenge_monitor_data( - local_db: &LocalDB, - btc_client: &BTCClient, - graph: &Graph, -) -> anyhow::Result< - Option<( - WTInitTxVoutMonitorData, - i64, - (Vec<(usize, Txid)>, Vec<(usize, Txid)>, Vec<(usize, Txid)>), - )>, -> { - let watchtower_challenge_init_txid = - graph.watchtower_challenge_init_txid.clone().ok_or_else(|| { - anyhow::anyhow!("graph_id:{} watchtower_challenge_init_txid is none", graph.graph_id) - })?; - let out_monitor = { - let mut storage_processor = local_db.acquire().await?; - storage_processor - .find_graph_btc_tx_vout_monitor(&graph.graph_id, &watchtower_challenge_init_txid) - .await? - }; - - let mut init_height; - let mut monitor_meta_update: Option<(i64, i64)> = None; - let mut existing_meta: Option<(String, i64)> = None; - let mut vout_monitor_data = if let Some(out_monitor) = out_monitor { - existing_meta = Some((out_monitor.tx_name.clone(), out_monitor.created_at)); - init_height = out_monitor.height; - match parse_monitor_data::(&out_monitor.monitor_data) { - Ok(vout_monitor_data) => vout_monitor_data, - Err(err) => { - warn!( - "graph_id:{} fail to parse watchtower monitor_data, rebuild default: {err}", - graph.graph_id - ); - let mut new_height = out_monitor.height; - let mut new_vout_len = out_monitor.vout_len; - if new_height <= 0 || new_vout_len <= 0 { - let txid: Txid = watchtower_challenge_init_txid.clone().into(); - if let Some(tx) = btc_client.get_tx_info(&txid).await? { - if new_height <= 0 { - new_height = tx.status.block_height.unwrap_or_default() as i64; - } - if new_vout_len <= 0 { - new_vout_len = tx.vout.len() as i64; - } - } - } - init_height = new_height; - let index_size = (new_vout_len as i32 - CONNECTOR_G_MARGIN as i32) / 2; - if index_size <= 0 { - warn!( - "graph_id:{} watchtower_challenge_init_txid {} invalid index_size {index_size}, skip refresh", - graph.graph_id, watchtower_challenge_init_txid.0 - ); - return Ok(None); - } - if new_height != out_monitor.height || new_vout_len != out_monitor.vout_len { - monitor_meta_update = Some((new_height, new_vout_len)); - } - WTInitTxVoutMonitorData::new(index_size) - } - } - } else { - let txid: Txid = watchtower_challenge_init_txid.clone().into(); - let watchtower_challenge_init_tx = match btc_client - .get_tx_info(&txid) - .await? - .filter(|tx| tx.status.block_height.unwrap_or_default() > 0) - { - Some(tx) => tx, - None => { - warn!( - "graph_id:{} watchtower_challenge_init_txid not on chain, skip refresh", - graph.graph_id - ); - return Ok(None); - } - }; - - init_height = watchtower_challenge_init_tx.status.block_height.unwrap_or_default() as i64; - let vout_monitor_data = WTInitTxVoutMonitorData::new( - (watchtower_challenge_init_tx.vout.len() as i32 - CONNECTOR_G_MARGIN as i32) / 2, - ); - - let current_times = current_time_secs(); - let mut tx = local_db.start_transaction().await?; - tx.upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { - graph_id: graph.graph_id, - tx_name: MONITE_BTC_TX_NAME_WATCHTOWER_INIT.to_string(), - txid: watchtower_challenge_init_txid.clone(), - height: watchtower_challenge_init_tx.status.block_height.unwrap_or_default() as i64, - vout_len: watchtower_challenge_init_tx.vout.len() as i64, - monitor_data: serde_json::to_string(&vout_monitor_data)?, - created_at: current_times, - updated_at: current_times, - }) - .await?; - tx.commit().await?; - - vout_monitor_data - }; - - if init_height <= 0 { - warn!( - "graph_id:{} watchtower_challenge_init_txid {} height is not confirmed, skip refresh", - graph.graph_id, watchtower_challenge_init_txid.0 - ); - return Ok(None); - } - let block_hash_commit_timeout_txid = - graph.blockhash_commit_timeout_txid.clone().ok_or_else(|| { - anyhow::anyhow!("graph_id:{} blockhash_commit_timeout_txid is empty", graph.graph_id) - })?; - let monitor_result = vout_monitor_data - .monitor_vout( - btc_client, - &watchtower_challenge_init_txid.clone().into(), - &graph.watchtower_challenge_timeout_txids, - &graph.nack_txids, - &block_hash_commit_timeout_txid, - ) - .await?; - let mut tx = local_db.start_transaction().await?; - if let (Some((height, vout_len)), Some((tx_name, created_at))) = - (monitor_meta_update, existing_meta) - { - tx.upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { - graph_id: graph.graph_id, - tx_name, - txid: watchtower_challenge_init_txid.clone(), - height, - vout_len, - monitor_data: serde_json::to_string(&vout_monitor_data)?, - created_at, - updated_at: current_time_secs(), - }) - .await?; - } else { - tx.update_graph_btc_tx_vout_monitor_data( - &graph.graph_id, - &watchtower_challenge_init_txid, - serde_json::to_string(&vout_monitor_data)?, - ) - .await?; - } - // Always insert WatchtowerChallengeInitSent to avoid missing; repeated inserts are idempotent - upsert_message( - &mut tx, - false, - graph.graph_id, - None, - SELF_SENDER.to_string(), - Actor::Watchtower, - GOATMessageContent::WatchtowerChallengeInitSent(WatchtowerChallengeInitSent { - instance_id: graph.instance_id, - graph_id: graph.graph_id, - }), - 0, - 0, - ) - .await?; - tx.commit().await?; - - Ok(Some((vout_monitor_data, init_height, monitor_result))) -} - -pub(crate) async fn refresh_assert_monitor_data( - local_db: &LocalDB, - btc_client: &BTCClient, - graph: &Graph, -) -> anyhow::Result> { - let assert_init_txid = graph - .assert_init_txid - .clone() - .ok_or_else(|| anyhow::anyhow!("graph_id:{} assert_init_txid is none", graph.graph_id))?; - let out_monitor = { - let mut storage_processor = local_db.acquire().await?; - storage_processor.find_graph_btc_tx_vout_monitor(&graph.graph_id, &assert_init_txid).await? - }; - - let mut init_height; - let mut monitor_meta_update: Option<(i64, i64)> = None; - let mut existing_meta: Option<(String, i64)> = None; - let mut vout_monitor_data = if let Some(out_monitor) = out_monitor { - existing_meta = Some((out_monitor.tx_name.clone(), out_monitor.created_at)); - init_height = out_monitor.height; - match parse_monitor_data::(&out_monitor.monitor_data) { - Ok(vout_monitor_data) => vout_monitor_data, - Err(err) => { - warn!( - "graph_id:{} fail to parse assert monitor_data, rebuild default: {err}", - graph.graph_id - ); - let mut new_height = out_monitor.height; - let mut new_vout_len = out_monitor.vout_len; - if new_height <= 0 || new_vout_len <= 0 { - let txid: Txid = assert_init_txid.clone().into(); - if let Some(tx) = btc_client.get_tx_info(&txid).await? { - if new_height <= 0 { - new_height = tx.status.block_height.unwrap_or_default() as i64; - } - if new_vout_len <= 0 { - new_vout_len = tx.vout.len() as i64; - } - } - } - init_height = new_height; - let index_size = new_vout_len as i32 - 2; - if index_size <= 0 { - warn!( - "graph_id:{} assert_init_txid {} invalid index_size {index_size}, skip refresh", - graph.graph_id, assert_init_txid.0 - ); - return Ok(None); - } - if new_height != out_monitor.height || new_vout_len != out_monitor.vout_len { - monitor_meta_update = Some((new_height, new_vout_len)); - } - AssertInitTxVoutMonitorData::new(index_size) - } - } - } else { - let txid: Txid = assert_init_txid.clone().into(); - let assert_init_tx = match btc_client - .get_tx_info(&txid) - .await? - .filter(|tx| tx.status.block_height.unwrap_or_default() > 0) - { - Some(tx) => tx, - None => { - warn!("graph_id:{} assert_init_txid not on chain, skip refresh", graph.graph_id); - return Ok(None); - } - }; - - init_height = assert_init_tx.status.block_height.unwrap_or_default() as i64; - let vout_monitor_data = - AssertInitTxVoutMonitorData::new(assert_init_tx.vout.len() as i32 - 2); - let current_times = current_time_secs(); - let mut tx = local_db.start_transaction().await?; - tx.upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { - graph_id: graph.graph_id, - tx_name: MONITE_BTC_TX_NAME_ASSERT_INIT.to_string(), - txid: assert_init_txid.clone(), - height: assert_init_tx.status.block_height.unwrap_or_default() as i64, - vout_len: assert_init_tx.vout.len() as i64, - monitor_data: serde_json::to_string(&vout_monitor_data)?, - created_at: current_times, - updated_at: current_times, - }) - .await?; - tx.commit().await?; - - vout_monitor_data - }; - - if init_height <= 0 { - warn!( - "graph_id:{} assert_init_txid {} height is not confirmed, skip refresh", - graph.graph_id, assert_init_txid.0 - ); - return Ok(None); - } - let monitor_result = vout_monitor_data - .monitor_vout( - btc_client, - &assert_init_txid.clone().into(), - &graph.assert_commit_timeout_txids, - ) - .await?; - let mut tx = local_db.start_transaction().await?; - if let (Some((height, vout_len)), Some((tx_name, created_at))) = - (monitor_meta_update, existing_meta) - { - tx.upsert_graph_btc_tx_vout_monitor(&GraphBtcTxVoutMonitor { - graph_id: graph.graph_id, - tx_name, - txid: assert_init_txid.clone(), - height, - vout_len, - monitor_data: serde_json::to_string(&vout_monitor_data)?, - created_at, - updated_at: current_time_secs(), - }) - .await?; - } else { - tx.update_graph_btc_tx_vout_monitor_data( - &graph.graph_id, - &assert_init_txid, - serde_json::to_string(&vout_monitor_data)?, - ) - .await?; - } - tx.commit().await?; - - Ok(Some((vout_monitor_data, init_height, monitor_result))) -} diff --git a/node/src/scheduled_tasks/instance_maintenance_tasks.rs b/node/src/scheduled_tasks/instance_maintenance_tasks.rs index f2bd4071..c7dd23b7 100644 --- a/node/src/scheduled_tasks/instance_maintenance_tasks.rs +++ b/node/src/scheduled_tasks/instance_maintenance_tasks.rs @@ -13,10 +13,10 @@ use crate::utils::{ upsert_message, }; use alloy::sol_types::SolType; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::constants::CONNECTOR_Z_TIMELOCK; -use bitvm2_lib::keys::CommitteeMasterKey; -use bitvm2_lib::transactions::base::BaseTransaction; +use bitvm_lib::actors::Actor; +use bitvm_lib::constants::CONNECTOR_Z_TIMELOCK; +use bitvm_lib::keys::CommitteeMasterKey; +use bitvm_lib::transactions::base::BaseTransaction; use client::Utxo; use client::btc_chain::BTCClient; use client::goat_chain::GOATClient; diff --git a/node/src/scheduled_tasks/mod.rs b/node/src/scheduled_tasks/mod.rs index 710bd88c..9f1cc918 100644 --- a/node/src/scheduled_tasks/mod.rs +++ b/node/src/scheduled_tasks/mod.rs @@ -17,7 +17,7 @@ use crate::scheduled_tasks::instance_maintenance_tasks::{ }; use crate::scheduled_tasks::node_maintenance_tasks::node_available_pbtc_update_monitor; use crate::scheduled_tasks::spv_maintenance_tasks::spv_header_hash_update; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; use client::btc_chain::BTCClient; use client::goat_chain::GOATClient; pub use event_watch_task::{is_processing_gateway_history_events, run_watch_event_task}; @@ -159,6 +159,10 @@ pub fn get_goat_message_content_type(content: &GOATMessageContent) -> MessageTyp GOATMessageContent::PeginRequest(_) => MessageType::PeginRequest, GOATMessageContent::CreateGraph(_) => MessageType::CreateGraph, GOATMessageContent::ConfirmInstance(_) => MessageType::ConfirmInstance, + GOATMessageContent::InitGraph(_) => MessageType::InitGraph, + GOATMessageContent::GenCircuits(_) => MessageType::GenCircuits, + GOATMessageContent::CutCircuits(_) => MessageType::CutCircuits, + GOATMessageContent::SolderingProofReady(_) => MessageType::SolderingProof, GOATMessageContent::NonceGeneration(_) => MessageType::NonceGeneration, GOATMessageContent::CommitteePresign(_) => MessageType::CommitteePresign, GOATMessageContent::GraphFinalize(_) => MessageType::GraphFinalize, @@ -174,19 +178,10 @@ pub fn get_goat_message_content_type(content: &GOATMessageContent) -> MessageTyp MessageType::WatchtowerChallengeInitSent } GOATMessageContent::WatchtowerChallengeSent(_) => MessageType::WatchtowerChallengeSent, - GOATMessageContent::WatchtowerChallengeTimeout(_) => { - MessageType::WatchtowerChallengeTimeout - } - GOATMessageContent::OperatorAckTimeout(_) => MessageType::OperatorAckTimeout, - GOATMessageContent::OperatorCommitBlockHashReady(_) => { - MessageType::OperatorCommitBlockHashReady - } - GOATMessageContent::OperatorCommitBlockHashTimeout(_) => { - MessageType::OperatorCommitBlockHashTimeout - } - GOATMessageContent::AssertInitReady(_) => MessageType::AssertInitReady, - GOATMessageContent::AssertCommitTimeout(_) => MessageType::AssertCommitTimeout, - GOATMessageContent::DisproveReady(_) => MessageType::DisproveReady, + GOATMessageContent::AssertReady(_) => MessageType::AssertReady, + GOATMessageContent::AssertSent(_) => MessageType::AssertSent, + GOATMessageContent::ChallengeAssertSent(_) => MessageType::ChallengeAssertSent, + GOATMessageContent::WronglyChallengeTimeout(_) => MessageType::WronglyChallengeTimeout, GOATMessageContent::DisproveSent(_) => MessageType::DisproveSent, GOATMessageContent::Take1Ready(_) => MessageType::Take1Ready, GOATMessageContent::Take1Sent(_) => MessageType::Take1Sent, diff --git a/node/src/scheduled_tasks/node_maintenance_tasks.rs b/node/src/scheduled_tasks/node_maintenance_tasks.rs index 293480f6..f49ea95d 100644 --- a/node/src/scheduled_tasks/node_maintenance_tasks.rs +++ b/node/src/scheduled_tasks/node_maintenance_tasks.rs @@ -1,5 +1,5 @@ use alloy::primitives::Address; -use bitvm2_lib::actors::Actor; +use bitvm_lib::actors::Actor; use client::goat_chain::GOATClient; use std::str::FromStr; use store::localdb::{LocalDB, NodeQuery}; diff --git a/node/src/scheduled_tasks/sequencer_set_hash_monitor_task.rs b/node/src/scheduled_tasks/sequencer_set_hash_monitor_task.rs index c323a997..4231c5d6 100644 --- a/node/src/scheduled_tasks/sequencer_set_hash_monitor_task.rs +++ b/node/src/scheduled_tasks/sequencer_set_hash_monitor_task.rs @@ -293,21 +293,17 @@ mod tests { sync_sequencer_set_hash_changes(&db, &rpc, 10).await.unwrap(); - let mut s = db.acquire().await.unwrap(); // Should have 2 hash change records: one at block 10 (init), one at block 13 (change) + let mut s = db.acquire().await.unwrap(); let first = s - .find_first_sequencer_set_hash_change_by_cosmos_block_at_or_after(10) + .find_first_sequencer_set_hash_change_by_goat_block_at_or_before(1012) .await .unwrap() .unwrap(); assert_eq!(first.cosmos_block_height, 10); assert_eq!(first.validators_hash, hex::encode(make_hash(0xAA))); - let second = s - .find_first_sequencer_set_hash_change_by_cosmos_block_at_or_after(11) - .await - .unwrap() - .unwrap(); + let second = s.find_latest_sequencer_set_hash_change().await.unwrap().unwrap(); assert_eq!(second.cosmos_block_height, 13); assert_eq!(second.validators_hash, hex::encode(make_hash(0xBB))); } diff --git a/node/src/soldering_payload_store.rs b/node/src/soldering_payload_store.rs new file mode 100644 index 00000000..fae3edc6 --- /dev/null +++ b/node/src/soldering_payload_store.rs @@ -0,0 +1,175 @@ +use anyhow::{Context, Result, anyhow, bail}; +use aws_config::BehaviorVersion; +use aws_sdk_s3::primitives::ByteStream; +use std::path::{Path, PathBuf}; +use tokio::io::AsyncReadExt; +use uuid::Uuid; + +pub(crate) fn is_soldering_proof_s3_path(path: &str) -> bool { + path.trim().starts_with("s3://") +} + +pub(crate) fn soldering_proof_payload_store_path( + base_path: &str, + instance_id: Uuid, + graph_id: Uuid, + verifier_index: usize, + payload_hash: &[u8; 32], +) -> Result { + let base_path = base_path.trim(); + if base_path.is_empty() { + bail!("soldering proof payload store path cannot be empty"); + } + let payload_name = format!("{graph_id}-{verifier_index}-{}.bin", hex::encode(payload_hash)); + if is_soldering_proof_s3_path(base_path) { + Ok(format!("{}/{}/{}", base_path.trim_end_matches('/'), instance_id, payload_name)) + } else { + Ok(PathBuf::from(base_path) + .join(instance_id.to_string()) + .join(payload_name) + .to_string_lossy() + .to_string()) + } +} + +fn parse_soldering_proof_s3_path(path: &str) -> Result<(String, String)> { + let path = path.trim(); + let path = path + .strip_prefix("s3://") + .ok_or_else(|| anyhow!("soldering proof payload path is not an s3 path"))?; + let (bucket, key) = path + .split_once('/') + .ok_or_else(|| anyhow!("soldering proof s3 path must include bucket and key"))?; + if bucket.is_empty() { + bail!("soldering proof s3 path bucket cannot be empty"); + } + if key.is_empty() { + bail!("soldering proof s3 path key cannot be empty"); + } + Ok((bucket.to_string(), key.to_string())) +} + +pub(crate) async fn write_soldering_proof_store_payload(path: &str, payload: &[u8]) -> Result<()> { + if is_soldering_proof_s3_path(path) { + let (bucket, key) = parse_soldering_proof_s3_path(path)?; + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = aws_sdk_s3::Client::new(&config); + client + .put_object() + .bucket(&bucket) + .key(&key) + .body(ByteStream::from(payload.to_vec())) + .send() + .await + .with_context(|| format!("write soldering proof payload to {path}"))?; + return Ok(()); + } + + let path = Path::new(path.trim()); + if let Some(parent) = path.parent() { + tokio::fs::create_dir_all(parent) + .await + .with_context(|| format!("create soldering proof payload dir {}", parent.display()))?; + } + let mut temp_path = path.as_os_str().to_os_string(); + temp_path.push(".tmp"); + let temp_path = PathBuf::from(temp_path); + tokio::fs::write(&temp_path, payload) + .await + .with_context(|| format!("write soldering proof payload {}", temp_path.display()))?; + tokio::fs::rename(&temp_path, path).await.with_context(|| { + format!("move soldering proof payload {} to {}", temp_path.display(), path.display()) + })?; + Ok(()) +} + +pub(crate) async fn read_soldering_proof_store_payload(path: &str) -> Result> { + if is_soldering_proof_s3_path(path) { + let (bucket, key) = parse_soldering_proof_s3_path(path)?; + let config = aws_config::load_defaults(BehaviorVersion::latest()).await; + let client = aws_sdk_s3::Client::new(&config); + let object = client + .get_object() + .bucket(&bucket) + .key(&key) + .send() + .await + .with_context(|| format!("read soldering proof payload from {path}"))?; + let mut reader = object.body.into_async_read(); + let mut payload = Vec::new(); + reader + .read_to_end(&mut payload) + .await + .with_context(|| format!("read soldering proof s3 body from {path}"))?; + return Ok(payload); + } + + tokio::fs::read(path.trim()) + .await + .with_context(|| format!("read soldering proof payload {}", path.trim())) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn store_test_ids() -> (Uuid, Uuid, [u8; 32]) { + ( + Uuid::parse_str("11111111-1111-1111-1111-111111111111").unwrap(), + Uuid::parse_str("22222222-2222-2222-2222-222222222222").unwrap(), + [0xabu8; 32], + ) + } + + #[test] + fn soldering_payload_store_detects_s3_paths() { + assert!(is_soldering_proof_s3_path("s3://bucket/prefix")); + assert!(!is_soldering_proof_s3_path("/tmp/soldering")); + assert!(!is_soldering_proof_s3_path("relative/soldering")); + } + + #[test] + fn soldering_payload_store_builds_deterministic_paths() { + let (instance_id, graph_id, payload_hash) = store_test_ids(); + let s3_path = soldering_proof_payload_store_path( + "s3://bucket/prefix/", + instance_id, + graph_id, + 7, + &payload_hash, + ) + .unwrap(); + assert_eq!( + s3_path, + "s3://bucket/prefix/11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222-7-abababababababababababababababababababababababababababababababab.bin" + ); + + let local_path = soldering_proof_payload_store_path( + "/tmp/soldering/", + instance_id, + graph_id, + 7, + &payload_hash, + ) + .unwrap(); + assert_eq!( + local_path, + "/tmp/soldering/11111111-1111-1111-1111-111111111111/22222222-2222-2222-2222-222222222222-7-abababababababababababababababababababababababababababababababab.bin" + ); + } + + #[tokio::test] + async fn soldering_payload_store_local_write_read_round_trip() { + let (instance_id, graph_id, payload_hash) = store_test_ids(); + let temp_dir = tempfile::tempdir().unwrap(); + let base = temp_dir.path().to_string_lossy(); + let path = + soldering_proof_payload_store_path(&base, instance_id, graph_id, 0, &payload_hash) + .unwrap(); + let payload = b"compact soldering proof".to_vec(); + + write_soldering_proof_store_payload(&path, &payload).await.unwrap(); + let restored = read_soldering_proof_store_payload(&path).await.unwrap(); + assert_eq!(restored, payload); + } +} diff --git a/node/src/utils.rs b/node/src/utils.rs index 33dd76eb..3b2d6f3b 100644 --- a/node/src/utils.rs +++ b/node/src/utils.rs @@ -1,15 +1,20 @@ use crate::action::{ - GOATMessage, GOATMessageContent, NodeInfo, push_local_unhandled_messages, send_to_peer, + ChallengeSent, DisproveSent, GOATMessage, GOATMessageContent, KickoffSent, NodeInfo, + PreKickoffSent, SolderingProofReady, Take1Sent, Take2Sent, push_local_unhandled_messages, + send_to_peer, }; use crate::env::*; use crate::error::SpecialError; use crate::middleware::AllBehaviours; use crate::rpc_service::current_time_secs; -use crate::vk::get_vk; +use crate::soldering_payload_store::{ + is_soldering_proof_s3_path, soldering_proof_payload_store_path, + write_soldering_proof_store_payload, +}; use alloy::primitives::{Address as EvmAddress, Signature as EvmSignature}; use alloy::signers::Signer; use alloy::signers::local::PrivateKeySigner; -use anyhow::{Result, anyhow, bail}; +use anyhow::{Context, Result, anyhow, bail}; use bitcoin::address::NetworkUnchecked; use bitcoin::consensus::encode::{deserialize, serialize}; use bitcoin::hashes::Hash; @@ -19,28 +24,28 @@ use bitcoin::{ PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Txid, Witness, XOnlyPublicKey, }; -use bitcoin_light_client_circuit::{VK_HASH_SIZE, build_watchtower_commitment}; +use bitcoin_light_client_circuit::{ + VK_HASH_SIZE, build_watchtower_commitment, wrapper_public_values, +}; use bitvm::treepp::*; -use bitvm2_lib::actors::Actor; -use bitvm2_lib::challenger::*; -use bitvm2_lib::committee::*; -use bitvm2_lib::keys::{ChallengerMasterKey, OperatorMasterKey, WatchtowerMasterKey}; -use bitvm2_lib::operator::*; -use bitvm2_lib::types::{ - Bitvm2Graph, Bitvm2GraphParameters, Bitvm2InstanceParameters, Groth16Proof, GuestInputs, - PrekickoffParameters, PublicInputs, SimplifiedBitvm2Graph, UserInfo, VerifyingKey, +use bitvm_lib::actors::Actor; +use bitvm_lib::committee::*; +use bitvm_lib::keys::{OperatorMasterKey, VerifierMasterKey, WatchtowerMasterKey}; +use bitvm_lib::operator::*; +use bitvm_lib::types::{ + BitvmGcCircuitData, BitvmGcGraph, BitvmGcGraphParameters, BitvmGcInstanceParameters, + PrekickoffParameters, SimplifiedBitvmGcGraph, UserInfo, }; -use bitvm2_lib::watchtower::*; +use bitvm_lib::verifier::*; +use bitvm_lib::watchtower::*; use client::Utxo as ClientUtxo; use client::{btc_chain::BTCClient, goat_chain::GOATClient}; use esplora_client::Utxo; -use futures::future::try_join_all; use goat::connectors::{ base::TaprootConnector, kickoff_connectors::{ForceSkipConnector, KickoffConnector, PrekickoffConnector}, }; use goat::contexts::base::generate_n_of_n_public_key; -use goat::disprove_scripts::hash160; use goat::scripts::generate_opreturn_script; use goat::transactions::base::Input; use goat::transactions::pre_signed::PreSignedTransaction; @@ -49,12 +54,13 @@ use goat::transactions::signing::populate_p2wsh_witness; use indexmap::IndexMap; use libp2p::{PeerId, Swarm}; use musig2::{PartialSignature, PubNonce}; +use p3_bn254_fr::Bn254Fr; +use p3_field::{FieldAlgebra, PrimeField}; use rand::Rng; use reqwest::Url; use secp256k1::Secp256k1; use serde::{Deserialize, Serialize}; -use std::fs::{self, File}; -use std::io::{BufReader, BufWriter}; +use sha2::{Digest, Sha256}; use std::net::SocketAddr; use std::panic::{AssertUnwindSafe, catch_unwind}; use std::path::{Path, PathBuf}; @@ -65,47 +71,74 @@ use std::time::{SystemTime, UNIX_EPOCH}; use store::localdb::{ GraphQuery, GraphUpdate, InstanceQuery, InstanceUpdate, LocalDB, StorageProcessor, }; -use store::{ - BridgeOutGlobalStats, ByteArray32, Graph, GraphRawData, GraphStatus, Instance, - InstanceBridgeInStatus, Message, MessageState, MessageType, Node, PeginGraphProcessData, - PeginInstanceProcessData, SerializableTxid, UInt64Array3, -}; -use stun_client::{Attribute, Class, Client}; -use zkm_sdk::ZKMProofWithPublicValues; -use zkm_verifier::{Groth16Verifier, IMM_GROTH16_VK_BYTES, convert_ark_imm_wrap_vk}; use crate::env; use crate::rpc_service::routes::v1::{ - NODES_OPERATOR_BASE, NODES_WATCHTOWER_BASE, PROOFS_OPERATOR_PROOF_TIMEOUT, - PROOFS_WATCHTOWER_PROOF_TIMEOUT, + NODES_OPERATOR_BASE, NODES_WATCHTOWER_BASE, PROOFS_WATCHTOWER_PROOF_TIMEOUT, + PROOFS_WRAPPER_PROOF, }; use crate::scheduled_tasks::get_goat_message_content_type; use crate::scheduled_tasks::graph_maintenance_tasks::{ - AssertCommitItemStatus, AssertCommitStatus, ChallengeSubStatus, CommitBlockHashStatus, - WatchtowerChallengeItemStatus, WatchtowerChallengeStatus, refresh_assert_monitor_data, - refresh_watchtower_challenge_monitor_data, + ChallengeSubStatus, VerifierChallengeStatus, }; use bitcoin_light_client_circuit::hash_operator_constant; -use bitvm2_lib::transactions::base::BaseTransaction; +use bitvm_lib::babe_adapter::{ + BabeProverState, BabeVerifierPrivateState, CACSetupPackage, FinalizedInstanceData, + SolderingData, compact_soldering_proof_payload, +}; +use bitvm_lib::transactions::base::BaseTransaction; use client::goat_chain::{DisproveTxType, GraphData, PeginStatus, WithdrawStatus}; use client::http_client::async_client::HttpAsyncClient; use proof_builder::{ - OperatorProofRequest, OperatorProofResponse, OperatorProofTimeoutUpdateRequest, - OperatorProofTimeoutUpdateResponse, ProofData, WatchtowerProofRequest, WatchtowerProofResponse, - WatchtowerProofTimeoutUpdateRequest, WatchtowerProofTimeoutUpdateResponse, + OperatorProofRequest, OperatorProofResponse, ProofData, WatchtowerProofRequest, + WatchtowerProofResponse, WatchtowerProofTimeoutUpdateRequest, + WatchtowerProofTimeoutUpdateResponse, WrapperProofResponse, +}; +use store::{ + BridgeOutGlobalStats, ByteArray32, Graph, GraphRawData, GraphStatus, Instance, + InstanceBridgeInStatus, Message, MessageState, MessageType, Node, PeginGraphProcessData, + PeginInstanceProcessData, SerializableTxid, UInt64Array3, }; +use stun_client::{Attribute, Class, Client}; use tracing::{error, info, warn}; use uuid::Uuid; +use zkm_recursion_core::stark::KoalaBearPoseidon2Outer; +use zkm_sdk::ZKMProofWithPublicValues; +use zkm_stark::PartStarkVerifyingKey; +use zkm_verifier::{ + Groth16Verifier, IMM_GROTH16_VK_BYTES, convert_ark_imm_wrap_vk, decode_zkm_vkey_hash, + hash_public_inputs, load_ark_public_inputs_from_bytes, +}; + pub(crate) const BRIDGE_OUT_GLOBAL_STATS_ID: i64 = 1; + +pub type VerifyingKey = ark_groth16::VerifyingKey; +pub type Groth16Proof = ark_groth16::Proof; +pub type PublicInputs = Vec; + +#[derive(Clone)] +pub struct ValidatedOperatorWrapperProof { + pub proof: Groth16Proof, + pub public_inputs: PublicInputs, + pub verifying_key: VerifyingKey, + pub public_values: Vec, + pub wrapper_vk_hash: String, + pub zkm_version: String, +} + +#[derive(Clone)] +pub struct OperatorWrapperStatement { + pub public_values: Vec, + pub public_inputs: PublicInputs, + pub wrapper_vk_hash: String, + pub zkm_version: String, +} + pub mod todo_funcs { #![allow(dead_code, unreachable_code, unused_variables)] use super::*; - use bitvm::chunk::api::{NUM_HASH, NUM_PUBS, NUM_U256}; - use bitvm2_lib::types::SimplifiedBitvm2Graph; - use goat::{ - connectors::assert_connectors::chunk_assert_commit, disprove_scripts::NUM_GUEST_PUBS_ASSERT, - }; + use bitvm_lib::types::SimplifiedBitvmGcGraph; // other operations pub fn avg_block_time_secs(network: Network) -> u64 { @@ -118,12 +151,6 @@ pub mod todo_funcs { // _ => 600, // default to 10 minutes } } - pub fn assert_commmit_num() -> usize { - let use_compact = false; - let wots32_num = NUM_GUEST_PUBS_ASSERT + NUM_PUBS + NUM_U256; - let wots16_num = NUM_HASH; - chunk_assert_commit(wots32_num, wots16_num, use_compact).len() - } pub fn min_required_operator() -> usize { // todo!("get min required operator number") 1 @@ -132,16 +159,22 @@ pub mod todo_funcs { // todo!("get min required watchtower number") 1 } + #[inline] + pub fn verifier_num() -> usize { + // todo!("get verifier num") + 1 + } + pub async fn validate_init_graph( local_db: &LocalDB, btc_client: &BTCClient, goat_client: &GOATClient, - graph: &SimplifiedBitvm2Graph, + graph: &SimplifiedBitvmGcGraph, ) -> Result<()> { // Basic structural and on-chain consistency checks for an incoming graph proposal. // Return SpecialError::InvalidGraph on any validation failure. // 1) Rebuild full graph (ensures signatures present if flags are set and tx graph is coherent) - let full_graph = Bitvm2Graph::from_simplified(graph) + let full_graph = BitvmGcGraph::from_simplified(graph) .map_err(|e| SpecialError::InvalidGraph(format!("invalid graph structure: {e}")))?; // 2) Network must match local node network @@ -169,20 +202,13 @@ pub mod todo_funcs { if graph.parameters.challenge_amount != super::todo_funcs::challenge_amount() { bail!(SpecialError::InvalidGraph("unexpected challenge amount".to_string())); } - if graph.assert_commit_num != super::todo_funcs::assert_commmit_num() { - bail!(SpecialError::InvalidGraph("unexpected assert_commit_num".to_string())); - } // 5) Watchtower config sanity: number of watchtowers should match number of hashlocks and registry size let watchtowers_on_chain = goat_client.committee_mana_get_watchtowers().await.map_err(|e| { SpecialError::InvalidGraph(format!("failed to load watchtowers from chain: {e}")) })?; - if graph.parameters.watchtower_pubkeys.len() != graph.parameters.hashlocks.len() { - bail!(SpecialError::InvalidGraph( - "watchtower_pubkeys and hashlocks length mismatch".to_string() - )); - } + // deduplicate watchtower pubkeys: reject graphs that contain duplicate watchtower entries { use std::collections::HashSet; @@ -238,11 +264,11 @@ pub mod todo_funcs { } pub async fn validate_finalized_graph( goat_client: &GOATClient, - graph: &SimplifiedBitvm2Graph, + graph: &SimplifiedBitvmGcGraph, endorse_sigs: &[(PublicKey, EvmAddress, Vec)], ) -> Result<()> { // 1) Rebuild full graph to ensure structure is coherent and txns derivable - let full_graph = Bitvm2Graph::from_simplified(graph) + let full_graph = BitvmGcGraph::from_simplified(graph) .map_err(|e| SpecialError::InvalidGraph(format!("invalid graph structure: {e}")))?; // 2) Repeat key static checks (network, committee set, counts) @@ -263,17 +289,9 @@ pub mod todo_funcs { "committee pubkeys mismatch with GoatChain".to_string() )); } - if graph.parameters.watchtower_pubkeys.len() != graph.parameters.hashlocks.len() { - bail!(SpecialError::InvalidGraph( - "watchtower_pubkeys and hashlocks length mismatch".to_string() - )); - } if graph.parameters.challenge_amount != super::todo_funcs::challenge_amount() { bail!(SpecialError::InvalidGraph("unexpected challenge amount".to_string())); } - if graph.assert_commit_num != super::todo_funcs::assert_commmit_num() { - bail!(SpecialError::InvalidGraph("unexpected assert_commit_num".to_string())); - } // 3) Validate endorsements: unique, from legitimate committee members, and signatures recover to the provided EVM address use std::collections::HashSet; @@ -347,15 +365,6 @@ pub mod todo_funcs { + (replenish_fee_inputs_num as u64 * CHEKSIG_P2WSH_INPUT_VBYTES); Amount::from_sat(tx_vbytes) } - pub async fn get_preimage( - local_db: &LocalDB, - instance_id: Uuid, - graph_id: Uuid, - index: usize, - ) -> Result> { - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - Ok(operator_master_key.preimage_for_graph(graph_id, index)) - } } pub mod evm_swap_utils { @@ -772,31 +781,189 @@ pub(crate) async fn refresh_graph( goat_client: &GOATClient, instance_id: Uuid, graph_id: Uuid, - graph: Option<&Bitvm2Graph>, + graph: Option<&BitvmGcGraph>, scan_from_status: Option, scan_from_sub_status: Option, -) -> Result<(GraphStatus, Option)> { - let graph = match graph { - Some(g) => g, - None => { - let g = get_graph(local_db, instance_id, graph_id).await?; - match g { - Some(g) => &Bitvm2Graph::from_simplified(&g)?, - None => bail!("Graph {graph_id} not found in local db"), - } - } +) -> Result<(GraphStatus, Option, Option)> { + let Some(graph) = graph else { + let status = scan_from_status.unwrap_or(GraphStatus::OperatorPresigned); + return Ok((status, scan_from_sub_status, None)); + }; + + let scan = scan_graph_chain_state( + btc_client, + goat_client, + graph, + scan_from_status, + scan_from_sub_status, + ) + .await?; + + if let Some(challenge_txid) = scan.challenge_txid { + update_graph_challenge_txid_if_needed(local_db, graph_id, challenge_txid).await?; + } + + update_graph_status( + local_db, + instance_id, + graph_id, + scan.status, + Some(scan.sub_status.clone()), + ) + .await?; + + Ok((scan.status, Some(scan.sub_status.clone()), Some(scan))) +} + +#[derive(Clone, Debug)] +struct DetectedDisprove { + disprove_type: DisproveTxType, + index: usize, + challenge_start_txid: Option, + challenge_finish_txid: Txid, +} + +#[derive(Clone, Debug)] +pub(crate) struct GraphChainScan { + status: GraphStatus, + sub_status: ChallengeSubStatus, + challenge_txid: Option, + #[allow(dead_code)] + watchtower_challenge_init_on_chain: bool, + #[allow(dead_code)] + operator_assert_on_chain: bool, + disprove: Option, +} + +fn normalize_challenge_sub_status( + mut sub_status: ChallengeSubStatus, + watchtower_num: usize, + verifier_num: usize, +) -> ChallengeSubStatus { + sub_status.watchtower_challenge_status.resize(watchtower_num, false); + sub_status.verifier_challenge_status.resize(verifier_num, VerifierChallengeStatus::None); + sub_status +} + +fn connector_a_outpoint(graph: &BitvmGcGraph) -> Result { + graph + .challenge + .tx() + .input + .first() + .map(|input| input.previous_output) + .ok_or_else(|| anyhow!("graph challenge tx has no inputs")) +} + +#[allow(dead_code)] +fn connector_d_outpoint(graph: &BitvmGcGraph) -> Result { + graph + .take2 + .tx() + .input + .get(1) + .map(|input| input.previous_output) + .ok_or_else(|| anyhow!("graph take2 tx has no connector-d input")) +} + +#[allow(dead_code)] +fn guardian_connector_outpoint(graph: &BitvmGcGraph) -> Result { + graph + .take2 + .tx() + .input + .get(2) + .map(|input| input.previous_output) + .ok_or_else(|| anyhow!("graph take2 tx has no guardian connector input")) +} + +async fn update_graph_challenge_txid_if_needed( + local_db: &LocalDB, + graph_id: Uuid, + challenge_txid: Txid, +) -> Result<()> { + let mut storage_processor = local_db.acquire().await?; + let Some(graph) = storage_processor.find_graph(&graph_id).await? else { + warn!("graph: {graph_id} not found, skip updating challenge txid"); + return Ok(()); }; + if graph.challenge_txid.as_ref().map(|txid| txid.0) != Some(challenge_txid) { + storage_processor + .update_graph(&GraphUpdate::new(graph_id).with_challenge_txid(challenge_txid.into())) + .await?; + } + Ok(()) +} + +#[allow(dead_code)] +async fn detect_guardian_disprove( + btc_client: &BTCClient, + graph: &BitvmGcGraph, + challenge_start_txid: Option, +) -> Result> { + let quick_challenge_txid = graph.quick_challenge.tx().compute_txid(); + if tx_on_chain(btc_client, &quick_challenge_txid).await? { + return Ok(Some(DetectedDisprove { + disprove_type: DisproveTxType::QuickChallenge, + index: 0, + challenge_start_txid, + challenge_finish_txid: quick_challenge_txid, + })); + } + + let challenge_incomplete_kickoff_txid = graph.challenge_incomplete_kickoff.tx().compute_txid(); + if tx_on_chain(btc_client, &challenge_incomplete_kickoff_txid).await? { + return Ok(Some(DetectedDisprove { + disprove_type: DisproveTxType::ChallengeIncompleteKickoff, + index: 0, + challenge_start_txid, + challenge_finish_txid: challenge_incomplete_kickoff_txid, + })); + } + Ok(None) +} + +async fn scan_graph_chain_state( + btc_client: &BTCClient, + goat_client: &GOATClient, + graph: &BitvmGcGraph, + scan_from_status: Option, + scan_from_sub_status: Option, +) -> Result { + let instance_id = graph.parameters.instance_parameters.instance_id; + let graph_id = graph.parameters.graph_id; + let watchtower_num = graph.parameters.watchtower_pubkeys.len(); + let verifier_num = graph.verifier_asserts.len(); + let mut sub_status = normalize_challenge_sub_status( + scan_from_sub_status.unwrap_or_default(), + watchtower_num, + verifier_num, + ); let mut current_status = match scan_from_status { Some(s) => s, None => { if graph.committee_pre_signed() { GraphStatus::CommitteePresigned } else { - return Ok((GraphStatus::OperatorPresigned, None)); + return Ok(GraphChainScan { + status: GraphStatus::OperatorPresigned, + sub_status, + challenge_txid: None, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: None, + }); } } }; - let mut sub_status = scan_from_sub_status.unwrap_or_default(); + + let prekickoff_txid = graph.cur_prekickoff.tx().compute_txid(); + let kickoff_txid = graph.kickoff.tx().compute_txid(); + let take1_txid = graph.take1.tx().compute_txid(); + let take2_txid = graph.take2.tx().compute_txid(); + let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); + let operator_assert_txid = graph.operator_assert.tx().compute_txid(); + // check if Graph has been posted on GoatChain if current_status == GraphStatus::CommitteePresigned { let graph_data_on_goat = goat_client.gateway_get_graph_data(&graph_id).await?; @@ -816,7 +983,6 @@ pub(crate) async fn refresh_graph( } } // check Prekickoff - let prekickoff_txid = graph.cur_prekickoff.tx().compute_txid(); if matches!( current_status, GraphStatus::OperatorPresigned @@ -825,8 +991,14 @@ pub(crate) async fn refresh_graph( | GraphStatus::Obsoleted ) { if !tx_on_chain(btc_client, &prekickoff_txid).await? { - update_graph_status(local_db, instance_id, graph_id, current_status, None).await?; - return Ok((current_status, None)); + return Ok(GraphChainScan { + status: current_status, + sub_status, + challenge_txid: None, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: None, + }); } else { current_status = if current_status == GraphStatus::OperatorDataPushed { GraphStatus::PreKickoff @@ -843,264 +1015,209 @@ pub(crate) async fn refresh_graph( } } // check Kickoff/SkipKickoff - let kickoff_txid = graph.kickoff.tx().compute_txid(); if matches!(current_status, GraphStatus::PreKickoff | GraphStatus::Obsoleted) { let kickoff_connector_vout = 1; if let Some(spent_txid) = outpoint_spent_txid(btc_client, &prekickoff_txid, kickoff_connector_vout).await? { if spent_txid != kickoff_txid { - update_graph_status(local_db, instance_id, graph_id, GraphStatus::Skipped, None) - .await?; - return Ok((GraphStatus::Skipped, None)); + return Ok(GraphChainScan { + status: GraphStatus::Skipped, + sub_status, + challenge_txid: None, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: None, + }); } else { current_status = GraphStatus::OperatorKickOff; } } else { - update_graph_status(local_db, instance_id, graph_id, current_status, None).await?; - return Ok((current_status, None)); + return Ok(GraphChainScan { + status: current_status, + sub_status, + challenge_txid: None, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: None, + }); } } - // check Take1/Challenge - let take1_txid = graph.take1.tx().compute_txid(); - let connector_a_vout = 0; + + let mut challenge_txid = None; + if current_status == GraphStatus::OperatorKickOff + && let Some(disprove) = detect_guardian_disprove(btc_client, graph, challenge_txid).await? + { + sub_status.disprove_type = Some(disprove.disprove_type); + sub_status.disprove_index = disprove.index as i32; + return Ok(GraphChainScan { + status: GraphStatus::Disprove, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: Some(disprove), + }); + } if current_status == GraphStatus::OperatorKickOff { + let connector_a = connector_a_outpoint(graph)?; if let Some(spent_txid) = - outpoint_spent_txid(btc_client, &kickoff_txid, connector_a_vout).await? + outpoint_spent_txid(btc_client, &connector_a.txid, connector_a.vout as u64).await? { if spent_txid != take1_txid { current_status = GraphStatus::Challenge; + challenge_txid = Some(spent_txid); } else { - update_graph_status( - local_db, - instance_id, - graph_id, - GraphStatus::OperatorTake1, - None, - ) - .await?; - return Ok((GraphStatus::OperatorTake1, None)); + return Ok(GraphChainScan { + status: GraphStatus::OperatorTake1, + sub_status, + challenge_txid: None, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: None, + }); } } else { - update_graph_status( - local_db, - instance_id, - graph_id, - GraphStatus::OperatorKickOff, - None, - ) - .await?; - return Ok((GraphStatus::OperatorKickOff, None)); + return Ok(GraphChainScan { + status: GraphStatus::OperatorKickOff, + sub_status, + challenge_txid: None, + watchtower_challenge_init_on_chain: false, + operator_assert_on_chain: false, + disprove: None, + }); } } - try_update_graph_challenge_txid( - btc_client, - local_db, - graph_id, - kickoff_txid, - connector_a_vout, - take1_txid, - ) - .await?; - // check Take2/Disprove - let take2_txid = graph.take2.tx().compute_txid(); + + let mut watchtower_challenge_init_on_chain = false; + let mut operator_assert_on_chain = false; if current_status == GraphStatus::Challenge { - let connector_e_vout = 3; - if let Some(spent_txid) = - outpoint_spent_txid(btc_client, &kickoff_txid, connector_e_vout).await? - { - let (current_status, sub_status) = if spent_txid != take2_txid { - sub_status.disprove_type = Some(DisproveTxType::Disprove); - (GraphStatus::Disprove, Some(sub_status)) - } else { - (GraphStatus::OperatorTake2, None) - }; - update_graph_status(local_db, instance_id, graph_id, current_status, sub_status) - .await?; - return Ok((current_status, sub_status)); + if let Some(disprove) = detect_guardian_disprove(btc_client, graph, challenge_txid).await? { + sub_status.disprove_type = Some(disprove.disprove_type); + sub_status.disprove_index = disprove.index as i32; + return Ok(GraphChainScan { + status: GraphStatus::Disprove, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain, + disprove: Some(disprove), + }); } - } - // check Watchtower-Challenge & Assert-Commit process - if current_status == GraphStatus::Challenge { - let network = get_network(); - let current_height = btc_client.get_height().await? as i64; - let db_graph = convert_graph(graph, current_time_secs()); - // check Watchtower Challenge process - let (vout_monitor_data, watchtower_challenge_init_height, _) = - match refresh_watchtower_challenge_monitor_data(local_db, btc_client, &db_graph).await? - { - Some(data) => data, - None => { - update_graph_status( - local_db, - instance_id, - graph_id, - current_status, - Some(sub_status), - ) - .await?; - return Ok((current_status, Some(sub_status))); - } - }; - let is_challenge_timeout = watchtower_challenge_init_height - + (watchtower_challenge_timeout_timelock(network) as i64) - < current_height; - let is_ack_timeout = - watchtower_challenge_init_height + (nack_timelock(network) as i64) < current_height; - let is_blockhash_commit_timeout = watchtower_challenge_init_height - + (commit_blockhash_timeout_timelock(network) as i64) - < current_height; - // 1) check watchtower challenge status - if let Some((&index, _)) = vout_monitor_data - .data_map - .iter() - .find(|(_, status)| **status == WatchtowerChallengeItemStatus::OperatorNACK) - { - // 1.1) WatchtowerChallengeDisproveFinished - sub_status.watchtower_challenge_status = - WatchtowerChallengeStatus::WatchtowerChallengeDisproveFinished; - sub_status.disprove_index = index; - sub_status.disprove_type = Some(DisproveTxType::OperatorNack); - current_status = GraphStatus::Disprove; - update_graph_status(local_db, instance_id, graph_id, current_status, Some(sub_status)) - .await?; - // no further check is needed if Disproved - return Ok((current_status, Some(sub_status))); - } else if vout_monitor_data.data_map.values().all(|status| { - matches!( - status, - WatchtowerChallengeItemStatus::OperatorACK - | WatchtowerChallengeItemStatus::ChallengeTimeout - ) - }) { - // 1.2) WatchtowerChallengeNormalFinished - sub_status.watchtower_challenge_status = - WatchtowerChallengeStatus::WatchtowerChallengeNormalFinished; - } else if vout_monitor_data.data_map.values().any(|status| { - matches!( - status, - WatchtowerChallengeItemStatus::OperatorInit - | WatchtowerChallengeItemStatus::Challenge - ) - }) && is_ack_timeout - { - // 1.3) OperatorACKTimeout - sub_status.watchtower_challenge_status = WatchtowerChallengeStatus::OperatorACKTimeout; - } else if vout_monitor_data - .data_map - .values() - .any(|status| *status == WatchtowerChallengeItemStatus::OperatorInit) - && is_challenge_timeout - { - // 1.4) WatchtowerChallengeTimeout - sub_status.watchtower_challenge_status = - WatchtowerChallengeStatus::WatchtowerChallengeTimeout; - } else if vout_monitor_data - .data_map - .values() - .any(|status| *status == WatchtowerChallengeItemStatus::Challenge) - { - // 1.5) WatchtowerChallenge - sub_status.watchtower_challenge_status = WatchtowerChallengeStatus::WatchtowerChallenge; - } else if vout_monitor_data - .data_map - .values() - .any(|status| *status == WatchtowerChallengeItemStatus::OperatorInit) - { - // 1.6) OperatorInit - sub_status.watchtower_challenge_status = WatchtowerChallengeStatus::OperatorInit; - } else { - // 1.7) None - sub_status.watchtower_challenge_status = WatchtowerChallengeStatus::None; + + watchtower_challenge_init_on_chain = + tx_on_chain(btc_client, &watchtower_challenge_init_txid).await?; + if !watchtower_challenge_init_on_chain { + return Ok(GraphChainScan { + status: current_status, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain: false, + disprove: None, + }); } - // 2) check commit blockhash status - if vout_monitor_data.commit_blockhash_status == CommitBlockHashStatus::OperatorCommitTimeout - { - // 2.1) Disproved by OperatorCommitTimeout - sub_status.commit_blockhash_status = CommitBlockHashStatus::OperatorCommitTimeout; - sub_status.disprove_index = 0; - sub_status.disprove_type = Some(DisproveTxType::OperatorCommitTimeout); - current_status = GraphStatus::Disprove; - update_graph_status(local_db, instance_id, graph_id, current_status, Some(sub_status)) - .await?; - return Ok((current_status, Some(sub_status))); - } else if vout_monitor_data.commit_blockhash_status == CommitBlockHashStatus::OperatorCommit - { - // 2.2) OperatorCommit - sub_status.commit_blockhash_status = CommitBlockHashStatus::OperatorCommit; - } else if sub_status.watchtower_challenge_status - == WatchtowerChallengeStatus::WatchtowerChallengeNormalFinished - { - if is_blockhash_commit_timeout { - // 2.3) WatchtowerChallengeCommitTimeout - sub_status.commit_blockhash_status = CommitBlockHashStatus::OperatorCommitTimeout; - } else { - // 2.4) WatchtowerChallengeProcessed - sub_status.commit_blockhash_status = - CommitBlockHashStatus::WatchtowerChallengeProcessed; + for watchtower_index in 0..watchtower_num { + if outpoint_spent_txid( + btc_client, + &watchtower_challenge_init_txid, + watchtower_index as u64, + ) + .await? + .is_some() + { + sub_status.watchtower_challenge_status[watchtower_index] = true; } - } else { - // 2.5) None - sub_status.commit_blockhash_status = CommitBlockHashStatus::None; } - // 3) check Assert Commit process - let (vout_monitor_data, assert_init_height, _) = - match refresh_assert_monitor_data(local_db, btc_client, &db_graph).await? { - Some(data) => data, - None => { - update_graph_status( - local_db, - instance_id, - graph_id, - current_status, - Some(sub_status), - ) - .await?; - return Ok((current_status, Some(sub_status))); + operator_assert_on_chain = tx_on_chain(btc_client, &operator_assert_txid).await?; + if !operator_assert_on_chain { + return Ok(GraphChainScan { + status: current_status, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain, + disprove: None, + }); + } + // TBD: add GraphStatus::Assert + } + + if current_status == GraphStatus::Challenge { + if let Some(disprove) = detect_guardian_disprove(btc_client, graph, challenge_txid).await? { + sub_status.disprove_type = Some(disprove.disprove_type); + sub_status.disprove_index = disprove.index as i32; + return Ok(GraphChainScan { + status: GraphStatus::Disprove, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain, + disprove: Some(disprove), + }); + } + + // TBD: add GraphStatus::Assert + let mut detected_disprove = None; + for verifier_index in 0..verifier_num { + let verifier_assert_txid = graph.verifier_asserts[verifier_index].tx().compute_txid(); + let disprove_txid = graph.disproves[verifier_index].tx().compute_txid(); + if tx_on_chain(btc_client, &verifier_assert_txid).await? { + if let Some(spent_txid) = + outpoint_spent_txid(btc_client, &verifier_assert_txid, 0).await? + { + if spent_txid == disprove_txid { + sub_status.verifier_challenge_status[verifier_index] = + VerifierChallengeStatus::Disproved; + sub_status.disprove_type = Some(DisproveTxType::Disprove); + sub_status.disprove_index = verifier_index as i32; + detected_disprove = Some(DetectedDisprove { + disprove_type: DisproveTxType::Disprove, + index: verifier_index, + challenge_start_txid: challenge_txid, + challenge_finish_txid: spent_txid, + }); + current_status = GraphStatus::Disprove; + } else { + sub_status.verifier_challenge_status[verifier_index] = + VerifierChallengeStatus::ProverAnswered; + } + } else { + sub_status.verifier_challenge_status[verifier_index] = + VerifierChallengeStatus::VerifierAsserted; } - }; - let is_assert_commit_timeout = - assert_init_height + (assert_commit_timeout_timelock(network) as i64) < current_height; - if let Some((&index, _)) = vout_monitor_data - .data_map - .iter() - .find(|(_, status)| **status == AssertCommitItemStatus::OperatorCommitTimeout) - { - // 3.1) Disproved by OperatorCommitTimeout - sub_status.assert_commit_status = AssertCommitStatus::OperatorCommitTimeout; - sub_status.disprove_index = index; - sub_status.disprove_type = Some(DisproveTxType::AssertTimeout); - current_status = GraphStatus::Disprove; - update_graph_status(local_db, instance_id, graph_id, current_status, Some(sub_status)) - .await?; - // no further check is needed if Disproved - return Ok((current_status, Some(sub_status))); - } else if vout_monitor_data - .data_map - .values() - .all(|status| *status == AssertCommitItemStatus::OperatorCommit) - { - // 3.2) OperatorCommit - sub_status.assert_commit_status = AssertCommitStatus::OperatorCommit; - } else if vout_monitor_data - .data_map - .values() - .any(|status| *status == AssertCommitItemStatus::OperatorInit) - { - if is_assert_commit_timeout { - // 3.3) AssertCommitTimeout - sub_status.assert_commit_status = AssertCommitStatus::OperatorCommitTimeout; - } else { - // 3.4) OperatorInit - sub_status.assert_commit_status = AssertCommitStatus::OperatorInit; } - } else { - // 3.5) None - sub_status.assert_commit_status = AssertCommitStatus::None; + } + if current_status == GraphStatus::Disprove { + return Ok(GraphChainScan { + status: current_status, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain, + disprove: detected_disprove, + }); + } + if tx_on_chain(btc_client, &take2_txid).await? { + return Ok(GraphChainScan { + status: GraphStatus::OperatorTake2, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain, + disprove: None, + }); } } - update_graph_status(local_db, instance_id, graph_id, current_status, Some(sub_status)).await?; - Ok((current_status, Some(sub_status))) + + Ok(GraphChainScan { + status: current_status, + sub_status, + challenge_txid, + watchtower_challenge_init_on_chain, + operator_assert_on_chain, + disprove: None, + }) } #[allow(clippy::enum_variant_names)] @@ -1122,238 +1239,184 @@ fn map_transition_to_event(from: GraphStatus, to: GraphStatus) -> Option Some(GraphCompensateEventKind::Take1Sent), (OperatorKickOff, Challenge) => Some(GraphCompensateEventKind::ChallengeSent), (Challenge, Disprove) => Some(GraphCompensateEventKind::DisproveSent), + (OperatorKickOff, Disprove) => Some(GraphCompensateEventKind::DisproveSent), (Challenge, OperatorTake2) => Some(GraphCompensateEventKind::Take2Sent), _ => None, } } +async fn upsert_graph_compensate_message( + local_db: &LocalDB, + graph_id: Uuid, + sub_type: Option, + actor: Actor, + message_content: GOATMessageContent, +) -> Result<()> { + let mut storage_processor = local_db.acquire().await?; + upsert_message( + &mut storage_processor, + false, + graph_id, + sub_type, + SELF_SENDER.to_string(), + actor, + message_content, + 0, + 0, + ) + .await +} + +async fn push_graph_compensate_message( + local_db: &LocalDB, + graph_id: Uuid, + actor: Actor, + message_content: GOATMessageContent, +) -> Result<()> { + let message = GOATMessage::new(actor, message_content); + push_local_unhandled_messages(local_db, graph_id, &message, 0).await +} + +#[allow(dead_code)] +async fn should_emit_wrongly_challenge_timeout( + btc_client: &BTCClient, + challenge_assert_txid: Txid, +) -> Result { + let status = btc_client.get_tx_status(&challenge_assert_txid).await?; + let Some(block_height) = status.block_height else { + return Ok(false); + }; + let current_height = btc_client.get_height().await?; + Ok(current_height >= block_height + disprove_timelock(get_network())) +} + #[allow(clippy::too_many_arguments)] pub(crate) async fn compensate_graph_events( local_db: &LocalDB, - btc_client: &BTCClient, + _btc_client: &BTCClient, instance_id: Uuid, graph_id: Uuid, - graph: Option<&Bitvm2Graph>, + _graph: Option<&BitvmGcGraph>, + scan: Option<&GraphChainScan>, scan_from_status: Option, compensate_from_status: GraphStatus, final_status: GraphStatus, - final_sub_status: Option, ) -> Result<()> { - use GOATMessageContent::*; + let Some(scan) = scan else { + tracing::debug!( + "Skip graph compensation for {instance_id}:{graph_id}: chain scan result is missing" + ); + return Ok(()); + }; let scan_start = scan_from_status.unwrap_or(compensate_from_status); - let effective_from = if scan_start.is_after(&compensate_from_status) { scan_start } else { compensate_from_status }; - if !effective_from.is_before(&final_status) { tracing::debug!( - "Skip compensating graph events: effective_from {effective_from:?} is not before final_status {final_status:?}", + "Skip graph compensation for {instance_id}:{graph_id}: effective_from={effective_from:?}, final_status={final_status:?}" ); return Ok(()); } - let mut rev_path = Vec::new(); - let mut cur = final_status; - loop { - rev_path.push(cur); - if cur == effective_from { - break; - } - cur = match cur.get_previous_status() { - Some(prev) => prev, - None => { - tracing::debug!( - "Stop compensating graph events early: no previous status for {cur:?} while targeting {effective_from:?}", - ); - return Ok(()); - } + let mut rev_path = vec![final_status]; + let mut cursor = final_status; + while cursor != effective_from { + let Some(prev) = cursor.get_previous_status() else { + tracing::debug!( + "Skip graph compensation for {instance_id}:{graph_id}: cannot walk from {final_status:?} back to {effective_from:?}" + ); + return Ok(()); }; + rev_path.push(prev); + cursor = prev; } rev_path.reverse(); for window in rev_path.windows(2) { - let s_from = window[0]; - let s_to = window[1]; - - if let Some(kind) = map_transition_to_event(s_from, s_to) { - match kind { - GraphCompensateEventKind::PreKickoffSent => { - let prekickoff_sent = - PreKickoffSent(crate::action::PreKickoffSent { instance_id, graph_id }); - let message = GOATMessage::new(Actor::All, prekickoff_sent); - push_local_unhandled_messages(local_db, graph_id, &message, 0).await?; - } - GraphCompensateEventKind::KickoffSent => { - let kickoff_sent = - KickoffSent(crate::action::KickoffSent { instance_id, graph_id }); - let message = GOATMessage::new(Actor::All, kickoff_sent); - push_local_unhandled_messages(local_db, graph_id, &message, 0).await?; - } - GraphCompensateEventKind::Take1Sent => { - let take1_sent = Take1Sent(crate::action::Take1Sent { instance_id, graph_id }); - let message = GOATMessage::new(Actor::All, take1_sent); - push_local_unhandled_messages(local_db, graph_id, &message, 0).await?; - } - GraphCompensateEventKind::ChallengeSent => { - let graph = match graph { - Some(g) => g, - None => { - let g = get_graph(local_db, instance_id, graph_id).await?; - match g { - Some(g) => &Bitvm2Graph::from_simplified(&g)?, - None => bail!("Graph {graph_id} not found in local db"), - } - } - }; - let kickoff_txid = graph.kickoff.tx().compute_txid(); - let take1_txid = graph.take1.tx().compute_txid(); - let connector_a_vout = 0; - if let Some(challenge_txid) = - outpoint_spent_txid(btc_client, &kickoff_txid, connector_a_vout).await? - && challenge_txid != take1_txid - { - let challenge_sent = ChallengeSent(crate::action::ChallengeSent { + let [from, to] = window else { + continue; + }; + let Some(event) = map_transition_to_event(*from, *to) else { + continue; + }; + + match event { + GraphCompensateEventKind::PreKickoffSent => { + push_graph_compensate_message( + local_db, + graph_id, + Actor::Verifier, + GOATMessageContent::PreKickoffSent(PreKickoffSent { instance_id, graph_id }), + ) + .await?; + } + GraphCompensateEventKind::KickoffSent => { + push_graph_compensate_message( + local_db, + graph_id, + Actor::All, + GOATMessageContent::KickoffSent(KickoffSent { instance_id, graph_id }), + ) + .await?; + } + GraphCompensateEventKind::Take1Sent => { + push_graph_compensate_message( + local_db, + graph_id, + Actor::Committee, + GOATMessageContent::Take1Sent(Take1Sent { instance_id, graph_id }), + ) + .await?; + } + GraphCompensateEventKind::ChallengeSent => { + if let Some(challenge_txid) = scan.challenge_txid { + push_graph_compensate_message( + local_db, + graph_id, + Actor::Operator, + GOATMessageContent::ChallengeSent(ChallengeSent { instance_id, graph_id, challenge_txid, - }); - let message = GOATMessage::new(Actor::All, challenge_sent); - push_local_unhandled_messages(local_db, graph_id, &message, 0).await?; - } + }), + ) + .await?; } - GraphCompensateEventKind::DisproveSent => { - let sub_status = match final_sub_status { - Some(ref s) => s, - None => { - tracing::error!( - "No final_sub_status provided for DisproveSent compensation!" - ); - continue; - } - }; - let disprove_type = match sub_status.disprove_type { - Some(t) => t, - None => { - tracing::error!( - "No disprove_type in final_sub_status for DisproveSent compensation!" - ); - continue; - } - }; - let graph = match graph { - Some(g) => g, - None => { - let g = get_graph(local_db, instance_id, graph_id).await?; - match g { - Some(g) => &Bitvm2Graph::from_simplified(&g)?, - None => bail!("Graph {graph_id} not found in local db"), - } - } - }; - let kickoff_txid = graph.kickoff.tx().compute_txid(); - let take1_txid = graph.take1.tx().compute_txid(); - let connector_a_vout = 0; - let challenge_start_txid = if let Some(challenge_txid) = - outpoint_spent_txid(btc_client, &kickoff_txid, connector_a_vout).await? - { - if challenge_txid == take1_txid { - tracing::error!("Take1 found for DisproveSent compensation!"); - continue; - } - Some(challenge_txid) - } else { - None - }; - let challenge_finish_txid = match disprove_type { - DisproveTxType::Disprove => { - let connector_e_vout = 3; - outpoint_spent_txid(btc_client, &kickoff_txid, connector_e_vout) - .await? - .ok_or(anyhow!( - "No Disprove txn found for DisproveSent compensation!" - ))? - } - DisproveTxType::OperatorCommitTimeout => { - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let connector_f_vout = watchtower_num * 2 + 1; - let watchtower_challenge_init_txid = - graph.watchtower_challenge_init.tx().compute_txid(); - outpoint_spent_txid( - btc_client, - &watchtower_challenge_init_txid, - connector_f_vout as u64, - ) - .await? - .ok_or(anyhow!( - "No OperatorCommitTimeout txn found for DisproveSent compensation!" - ))? - } - DisproveTxType::OperatorNack => { - let watchtower_num = graph.parameters.watchtower_pubkeys.len(); - let connector_f_vout = watchtower_num * 2 + 1; - let watchtower_challenge_init_txid = - graph.watchtower_challenge_init.tx().compute_txid(); - outpoint_spent_txid( - btc_client, - &watchtower_challenge_init_txid, - connector_f_vout as u64, - ) - .await? - .ok_or(anyhow!( - "No OperatorNack txn found for DisproveSent compensation!" - ))? - } - DisproveTxType::AssertTimeout => { - let assert_commit_num = graph.assert_commit_timeout_txns.len(); - let connector_d_vout = assert_commit_num; - let assert_init_txid = graph.assert_init.tx().compute_txid(); - outpoint_spent_txid( - btc_client, - &assert_init_txid, - connector_d_vout as u64, - ) - .await? - .ok_or(anyhow!( - "No AssertTimeout txn found for DisproveSent compensation!" - ))? - } - DisproveTxType::QuickChallenge => { - let guardian_connector_vout = 4; - outpoint_spent_txid(btc_client, &kickoff_txid, guardian_connector_vout) - .await? - .ok_or(anyhow!( - "No QuickChallenge txn found for DisproveSent compensation!" - ))? - } - DisproveTxType::ChallengeIncompleteKickoff => { - let guardian_connector_vout = 4; - outpoint_spent_txid(btc_client, &kickoff_txid, guardian_connector_vout) - .await? - .ok_or( - anyhow!("No ChallengeIncompleteKickoff txn found for DisproveSent compensation!") - )? - } - }; - let disprove_sent = DisproveSent(crate::action::DisproveSent { + } + GraphCompensateEventKind::DisproveSent => { + let disprove = scan.disprove.clone().ok_or_else(|| { + anyhow!( + "Graph {instance_id}:{graph_id} reached Disprove but no disprove transaction was detected" + ) + })?; + upsert_graph_compensate_message( + local_db, + graph_id, + Some(disprove.index.to_string()), + Actor::Committee, + GOATMessageContent::DisproveSent(DisproveSent { instance_id, graph_id, - disprove_type, - index: sub_status.disprove_index as usize, - challenge_start_txid, - challenge_finish_txid, - }); - let message = GOATMessage::new(Actor::All, disprove_sent); - push_local_unhandled_messages(local_db, graph_id, &message, 0).await?; - } - GraphCompensateEventKind::Take2Sent => { - let take2_sent = Take2Sent(crate::action::Take2Sent { instance_id, graph_id }); - let message = GOATMessage::new(Actor::All, take2_sent); - push_local_unhandled_messages(local_db, graph_id, &message, 0).await?; - } + disprove_type: disprove.disprove_type, + index: disprove.index, + challenge_start_txid: disprove.challenge_start_txid, + challenge_finish_txid: disprove.challenge_finish_txid, + }), + ) + .await?; + } + GraphCompensateEventKind::Take2Sent => { + push_graph_compensate_message( + local_db, + graph_id, + Actor::Committee, + GOATMessageContent::Take2Sent(Take2Sent { instance_id, graph_id }), + ) + .await?; } } } @@ -1361,7 +1424,7 @@ pub(crate) async fn compensate_graph_events( Ok(()) } -pub fn build_graph_data(graph: &Bitvm2Graph) -> Result { +pub fn build_graph_data(graph: &BitvmGcGraph) -> Result { // operator pubkey: first byte is prefix, next 32 bytes are key let op_pk_bytes = graph.parameters.operator_pubkey.to_bytes(); let operator_pubkey_prefix = op_pk_bytes[0]; @@ -1373,15 +1436,9 @@ pub fn build_graph_data(graph: &Bitvm2Graph) -> Result { let kickoff_txid = graph.kickoff.finalize().compute_txid().to_byte_array(); let take1_txid = graph.take1.finalize().compute_txid().to_byte_array(); let take2_txid = graph.take2.finalize().compute_txid().to_byte_array(); - let commit_timout_txid = - graph.blockhash_commit_timeout.finalize().compute_txid().to_byte_array(); - let assert_timeout_txids: Vec<[u8; 32]> = graph - .assert_commit_timeout_txns - .iter() - .map(|tx| tx.finalize().compute_txid().to_byte_array()) - .collect(); - let nack_txids: Vec<[u8; 32]> = - graph.nack_txns.iter().map(|tx| tx.finalize().compute_txid().to_byte_array()).collect(); + let prover_assert_txid = graph.operator_assert.finalize().compute_txid().to_byte_array(); + let disprove_txids: Vec<[u8; 32]> = + graph.disproves.iter().map(|tx| tx.finalize().compute_txid().to_byte_array()).collect(); Ok(GraphData { operator_pubkey_prefix, @@ -1390,13 +1447,12 @@ pub fn build_graph_data(graph: &Bitvm2Graph) -> Result { kickoff_txid, take1_txid, take2_txid, - commit_timout_txid, - assert_timeout_txids, - nack_txids, + prover_assert_txid, + disprove_txids, }) } -pub async fn get_graph_digest(goat_client: &GOATClient, graph: &Bitvm2Graph) -> Result<[u8; 32]> { +pub async fn get_graph_digest(goat_client: &GOATClient, graph: &BitvmGcGraph) -> Result<[u8; 32]> { let instance_id = graph.parameters.instance_parameters.instance_id; let graph_id = graph.parameters.graph_id; let graph_data = build_graph_data(graph)?; @@ -1535,7 +1591,7 @@ pub async fn read_pegin_request( pub async fn read_instance_info_from_goat( goat_client: &GOATClient, instance_id: Uuid, -) -> Result { +) -> Result { let pegin_data = goat_client.gateway_get_pegin_data(&instance_id).await?; let network = get_network(); let user_change_address = Address::from_str(&pegin_data.user_change_addr) @@ -1586,7 +1642,7 @@ pub async fn read_instance_info_from_goat( } }; let committee_agg_pubkey = generate_n_of_n_public_key(&committee_pubkeys).0; - Ok(Bitvm2InstanceParameters { + Ok(BitvmGcInstanceParameters { network, instance_id, user_info, @@ -1604,46 +1660,11 @@ pub async fn is_take1_timelock_expired(client: &BTCClient, kickoff_height: u32) pub async fn is_take2_timelock_expired( client: &BTCClient, - watchtower_challenge_init_height: u32, - assert_init_height: u32, + operator_assert_height: u32, ) -> Result { - let lock_blocks = take2_timelocks(get_network()); + let lock_blocks = take2_timelock(get_network()); let current_height = client.get_height().await?; - Ok(current_height >= watchtower_challenge_init_height + lock_blocks.0 - || current_height >= assert_init_height + lock_blocks.1) -} - -/// Loads partial scripts from a local cache file. -/// If cache file does not exist, generate partial scripts by vk an cache it -pub async fn get_partial_scripts() -> Result> { - let scripts_cache_path = format!("{SCRIPT_CACHE_FILE_NAME}.bin"); - if Path::new(&scripts_cache_path).exists() { - let file = File::open(scripts_cache_path)?; - let reader = BufReader::new(file); - let scripts_bytes: Vec = bincode::deserialize_from(reader)?; - Ok(scripts_bytes) - } else { - let partial_scripts = generate_partial_scripts(&get_vk().await?); - if let Some(parent) = Path::new(&scripts_cache_path).parent() { - fs::create_dir_all(parent)?; - }; - let file = File::create(&scripts_cache_path)?; - let writer = BufWriter::new(file); - bincode::serialize_into(writer, &partial_scripts)?; - Ok(partial_scripts) - } -} - -pub async fn get_disprove_scripts(graph_params: &Bitvm2GraphParameters) -> Result> { - let partial_scripts = get_partial_scripts().await?; - let (mut disprove_scripts, disprove_scripts_1) = generate_disprove_scripts( - &partial_scripts, - graph_params.operator_wots_pubkeys.clone(), - &graph_params.guest_constant_value, - &graph_params.hashlocks, - ); - disprove_scripts.extend(disprove_scripts_1); - Ok(disprove_scripts) + Ok(current_height >= operator_assert_height + lock_blocks) } pub async fn get_fee_rate(client: &BTCClient) -> Result { @@ -1752,27 +1773,27 @@ pub async fn broadcast_package( fn gen_watchtower_commitment(graph_id: Uuid, proof_data: ProofData) -> Result> { let graph_id = graph_id.as_bytes(); - let proof = proof_data.proof.as_slice(); + let proof = + proof_data.proof.as_slice().try_into().map_err(|_| anyhow!("invalid proof length"))?; + let public_inputs = proof_data + .public_inputs + .as_slice() + .try_into() + .map_err(|_| anyhow!("invalid public inputs length"))?; if proof_data.vk.len() != VK_HASH_SIZE { bail!("invalid vk_hash length"); } - if proof_data.proof_part_stark_vk.is_empty() { - bail!("missing proof_part_stark_vk"); + if proof_data.zkm_version.is_empty() { + bail!("missing zkm_version"); } - build_watchtower_commitment( + Ok(build_watchtower_commitment( graph_id, proof, - &proof_data.public_inputs, + &public_inputs, &proof_data.vk, - &proof_data.proof_part_stark_vk, - ) - .map_err(|e| anyhow!("failed to build watchtower commitment: {e}")) -} - -fn load_part_stark_vk_for_zkm_version(zkm_version: &str) -> Result> { - catch_unwind(AssertUnwindSafe(|| Groth16Verifier::get_part_stark_vk(zkm_version).to_vec())) - .map_err(|_| anyhow!("failed to load part_stark_vk for zkm_version {zkm_version}")) + &proof_data.zkm_version, + )) } // proof network @@ -1817,276 +1838,246 @@ pub async fn get_watchtower_commitment( .await?; match response.proof_data { Some(proof_data) => { - let watchtower_commitment = gen_watchtower_commitment(graph_id, proof_data)?; - Ok((Some(watchtower_commitment), 0)) - } - None => Ok((None, get_watchtower_proof_wait_secs())), - } - } else { - warn!("graph:{graph_id} not found or related txn is none",); - bail!("No graph in db"); - } -} - -#[tracing::instrument(name = "get_watchtower_challenge_info", skip(btc_client))] -pub async fn get_watchtower_challenge_info( - btc_client: &BTCClient, - watchtower_challenge_init_txid: &SerializableTxid, - num_challenger: usize, -) -> Result<(Vec, Vec)> { - let challenge_finish_txids: Vec> = try_join_all((0..num_challenger).map(|i| { - let connector_vout = i as u32 * 2; - outpoint_spent_txid(btc_client, &watchtower_challenge_init_txid.0, connector_vout.into()) - })) - .await?; - - if challenge_finish_txids.iter().any(|txid| txid.is_none()) { - bail!( - "No enough watchtower challenge tx found for graph, expected {num_challenger} but got {}. Waiting for watchtower challenge to be submitted", - challenge_finish_txids.iter().filter(|txid| txid.is_some()).count() - ); - } - - let challenge_timeout_txids: Vec> = try_join_all((0..num_challenger).map(|i| { - let connector_vout = i as u32 * 2 + 1; - outpoint_spent_txid(btc_client, &watchtower_challenge_init_txid.0, connector_vout.into()) - })) - .await?; - - // calculate challenge txid and included watchtower map - let mut included_watchtowers: Vec = vec![false; num_challenger]; - let watchtower_challenge_txids: Vec = - (included_watchtowers.iter_mut().zip(challenge_finish_txids.iter())) - .zip(challenge_timeout_txids.iter()) - .map(|((included, finish_txid), timeout_txid)| { - if finish_txid.is_some() && timeout_txid.is_some() && finish_txid != timeout_txid { - *included = true; - } - finish_txid.unwrap().to_string() - }) - .collect(); - - if watchtower_challenge_txids.len() != num_challenger { - bail!( - "challenge txids length mismatch with num_challenger, expected {num_challenger} but got {}", - watchtower_challenge_txids.len() - ); - } - Ok((watchtower_challenge_txids, included_watchtowers)) -} -/// Returns: -/// - `Ok(Some(OperatorProof), _)` if operator proof is available -/// - `Ok(None, wait_secs)` if operator proof is not yet available, with suggested wait time -pub async fn get_operator_proof( - local_db: &LocalDB, - http_client: &HttpAsyncClient, - bitvm_graph: &Bitvm2Graph, - btc_client: &BTCClient, - instance_id: Uuid, - graph_id: Uuid, - operator_committed_blockhash: String, -) -> Result<(Option<(GuestInputs, Groth16Proof, PublicInputs, VerifyingKey)>, usize)> { - let mut storage_processor = local_db.acquire().await?; - if let Some(graph) = storage_processor.find_graph(&graph_id).await? { - if graph.proceed_withdraw_height <= 0 { - warn!("graph {graph_id} proceed_withdraw_height <= 0, waiting to been updated"); - return Ok((None, get_operator_proof_wait_secs())); - } - - let watchtower_challenge_init_txid = graph - .watchtower_challenge_init_txid - .ok_or_else(|| anyhow::anyhow!("watchtower_challenge_init_txid is none"))?; - let num_challenger = bitvm_graph.parameters.watchtower_pubkeys.len(); - let (watchtower_challenge_txids, included_watchtowers) = - match get_watchtower_challenge_info( - btc_client, - &watchtower_challenge_init_txid, - num_challenger, - ) - .await - { - Ok(info) => info, - Err(e) => { - warn!("Failed to get watchtower challenge info: {e}"); - return Ok((None, get_operator_proof_wait_secs())); - } - }; - let base_url = Url::parse( - &get_proof_build_rpc_host() - .ok_or_else(|| anyhow::anyhow!("failed to get proof_build_rpc_host"))?, - )?; - let url = base_url.join(NODES_OPERATOR_BASE)?; - - let response = http_client - .post_response_json::( - url.as_str(), - &OperatorProofRequest { - instance_id: instance_id.to_string(), - graph_id: graph_id.to_string(), - operator_committed_blockhash, - execution_layer_block_number: graph.proceed_withdraw_height, - watchtower_challenge_txids, - included_watchtowers, - watchtower_challenge_init_txid: watchtower_challenge_init_txid.0.to_string(), - watchtower_challenge_pubkeys: bitvm_graph - .parameters - .watchtower_pubkeys - .iter() - .map(|pk| pk.public_key(secp256k1::Parity::Even).to_string()) - .collect(), - }, - ) - .await?; - - match response.proof_data { - Some(proof_data) => { - info!("get_operator_proof get proof successfully"); - let proof: ZKMProofWithPublicValues = - bincode::deserialize(proof_data.proof.as_slice()).unwrap(); - let proof_part_stark_vk = load_part_stark_vk_for_zkm_version(&proof.zkm_version)?; - let output: bitcoin_light_client_circuit::OperatorPublicOutputs = - proof.public_values.clone().read(); - // TODO: additionally check constant and included_watchtower with included_watchtowers. - //proof.public_values.head(); - info!("get_operator_proof parse proof successfully"); - let ark_proof = convert_ark_imm_wrap_vk( - &proof, - &proof_data.vk, - &IMM_GROTH16_VK_BYTES, - &proof_part_stark_vk, - ) - .map_err(|e| anyhow!("failed to convert operator proof to ark format: {e}"))?; - info!("get_operator_proof parse proof successfully"); - - Ok(( - Some(( - [output.constant, output.included_watchtowers], - ark_proof.proof.clone(), - ark_proof.public_inputs.into(), - ark_proof.groth16_vk.into(), - )), - 0, - )) + let watchtower_commitment = gen_watchtower_commitment(graph_id, proof_data)?; + Ok((Some(watchtower_commitment), 0)) } - None => Ok((None, get_operator_proof_wait_secs())), + None => Ok((None, get_watchtower_proof_wait_secs())), } } else { - warn!("graph:{graph_id} not found"); + warn!("graph:{graph_id} not found or related txn is none",); bail!("No graph in db"); } } -const ASSERT_COMMIT_CACHE_VERSION: u32 = 1; -#[derive(Serialize, Deserialize)] -struct CachedAssertCommitInput { - txin: Vec, - amount_sat: u64, +#[tracing::instrument(name = "get_watchtower_challenge_info", skip(btc_client))] +pub async fn get_watchtower_challenge_info( + btc_client: &BTCClient, + watchtower_challenge_init_txid: &SerializableTxid, + num_watchtowers: usize, +) -> Result<(Vec>, Vec)> { + let mut challenge_txids = Vec::with_capacity(num_watchtowers); + let mut included_watchtowers = Vec::with_capacity(num_watchtowers); + for index in 0..num_watchtowers { + let spent_txid = + outpoint_spent_txid(btc_client, &watchtower_challenge_init_txid.0, index as u64) + .await?; + match spent_txid { + Some(txid) => { + let status = btc_client.get_tx_status(&txid).await?; + if !status.confirmed { + bail!("watchtower challenge tx {txid} at index {index} is not confirmed yet"); + } + challenge_txids.push(Some(txid.to_string())); + included_watchtowers.push(true); + } + None => { + challenge_txids.push(None); + included_watchtowers.push(false); + } + } + } + Ok((challenge_txids, included_watchtowers)) } - -#[derive(Serialize, Deserialize)] -struct CachedAssertCommitInputs { - version: u32, - inputs: Vec, +fn load_part_stark_vk_for_zkm_version(zkm_version: &str) -> Result> { + catch_unwind(AssertUnwindSafe(|| Groth16Verifier::get_part_stark_vk(zkm_version).to_vec())) + .map_err(|_| anyhow!("failed to load part_stark_vk for zkm_version {zkm_version}")) } -fn embed_txin_into_dummy_tx(txin: &TxIn) -> Transaction { - Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![txin.clone()], - output: vec![], +fn combined_wrapper_vk_hash(wrapper_vk_hash: &str, zkm_version: &str) -> Result<[u8; 32]> { + if !wrapper_vk_hash.starts_with("0x") { + bail!("configured wrapper vk hash must use 0x-prefixed Ziren encoding"); + } + let raw_vk_hash = decode_zkm_vkey_hash(wrapper_vk_hash) + .map_err(|e| anyhow!("invalid configured wrapper vk hash: {e:?}"))?; + let part_vk: PartStarkVerifyingKey = + bincode::deserialize(&load_part_stark_vk_for_zkm_version(zkm_version)?) + .context("deserialize configured wrapper partial STARK verifying key")?; + let base = Bn254Fr::from_canonical_u32(256); + let mut field_hash = Bn254Fr::ZERO; + for byte in raw_vk_hash { + field_hash = field_hash * base + Bn254Fr::from_canonical_u32(byte as u32); + } + let combined = zkm_recursion_core::hash_vkey_with_part_vk(&part_vk, field_hash); + let bytes = combined.as_canonical_biguint().to_bytes_be(); + if bytes.len() > 32 { + bail!("combined wrapper verifying key hash exceeds BN254 field encoding"); } + let mut encoded = [0u8; 32]; + encoded[32 - bytes.len()..].copy_from_slice(&bytes); + Ok(encoded) } -fn extract_txin_from_dummy_tx(tx: &Transaction) -> TxIn { - tx.input[0].clone() +pub fn derive_operator_wrapper_statement(graph_id: Uuid) -> Result { + let public_values = wrapper_public_values( + get_operator_vk_hash()?, + *graph_id.as_bytes(), + get_genesis_sequencer_commit_id(), + ) + .to_vec(); + let wrapper_vk_hash = get_operator_wrapper_vk_hash()?; + let zkm_version = get_operator_wrapper_zkm_version()?; + let public_inputs = load_ark_public_inputs_from_bytes( + &combined_wrapper_vk_hash(&wrapper_vk_hash, &zkm_version)?, + &hash_public_inputs(&public_values), + ) + .to_vec(); + Ok(OperatorWrapperStatement { public_values, public_inputs, wrapper_vk_hash, zkm_version }) } -fn assert_commit_cache_path(graph_id: Uuid) -> PathBuf { - Path::new(ASSERT_COMMITS_CACHE_DIR).join(format!("{graph_id}.json")) -} +/// Returns: +/// - `Ok(Some(WrapperProof), _)` if wrapper proof is available +/// - `Ok(None, wait_secs)` if operator or wrapper proof is not yet available +pub async fn get_operator_wrapper_proof( + local_db: &LocalDB, + http_client: &HttpAsyncClient, + bitvm_graph: &BitvmGcGraph, + btc_client: &BTCClient, + instance_id: Uuid, + graph_id: Uuid, +) -> Result<(Option, usize)> { + let mut storage_processor = local_db.acquire().await?; + let Some(graph) = storage_processor.find_graph(&graph_id).await? else { + warn!("graph:{graph_id} not found"); + bail!("No graph in db"); + }; + drop(storage_processor); -fn load_assert_commit_inputs_from_cache(graph_id: Uuid) -> Option> { - let path = assert_commit_cache_path(graph_id); - if !path.exists() { - return None; + if graph.proceed_withdraw_height <= 0 { + warn!("graph {graph_id} proceed_withdraw_height <= 0, waiting to been updated"); + return Ok((None, get_operator_proof_wait_secs())); } - let file = match File::open(&path) { - Ok(file) => file, - Err(err) => { - warn!("failed to open assert-commit cache {path:?}: {err:?}"); - return None; - } - }; - let reader = BufReader::new(file); - let cached: CachedAssertCommitInputs = match serde_json::from_reader(reader) { - Ok(data) => data, - Err(err) => { - warn!("failed to deserialize assert-commit cache {path:?}: {err:?}"); - return None; + + let watchtower_challenge_init_txid = graph + .watchtower_challenge_init_txid + .ok_or_else(|| anyhow::anyhow!("watchtower_challenge_init_txid is none"))?; + let num_challenger = bitvm_graph.parameters.watchtower_pubkeys.len(); + let (watchtower_challenge_txids, included_watchtowers) = match get_watchtower_challenge_info( + btc_client, + &watchtower_challenge_init_txid, + num_challenger, + ) + .await + { + Ok(info) => info, + Err(e) => { + warn!("Failed to get watchtower challenge info: {e}"); + return Ok((None, get_operator_proof_wait_secs())); } }; - if cached.version != ASSERT_COMMIT_CACHE_VERSION { - warn!( - "assert-commit cache version mismatch for {path:?}, expecting {ASSERT_COMMIT_CACHE_VERSION}, got {}", - cached.version - ); - return None; - } - let mut inputs = Vec::with_capacity(cached.inputs.len()); - for item in cached.inputs { - match deserialize::(&item.txin) { - Ok(txin_embed_tx) => inputs.push(( - extract_txin_from_dummy_tx(&txin_embed_tx), - Amount::from_sat(item.amount_sat), - )), - Err(err) => { - warn!("failed to decode txin from cache {path:?}: {err:?}"); - return None; + let operator_committed_blockhash = { + let mut largest: Option<(u32, BlockHash)> = None; + for txid in watchtower_challenge_txids.iter().flatten() { + let status = btc_client.get_tx_status(&Txid::from_str(txid)?).await?; + let (height, hash) = match (status.block_height, status.block_hash) { + (Some(height), Some(hash)) => (height, hash), + _ => bail!("watchtower challenge tx {txid} is not confirmed yet"), + }; + if largest.is_none_or(|(largest_height, _)| height > largest_height) { + largest = Some((height, hash)); } } + largest + .map(|(_, hash)| hash.to_string()) + .ok_or_else(|| anyhow!("no confirmed watchtower challenge tx is available"))? + }; + + let base_url = Url::parse( + &get_proof_build_rpc_host() + .ok_or_else(|| anyhow::anyhow!("failed to get proof_build_rpc_host"))?, + )?; + let operator_url = base_url.join(NODES_OPERATOR_BASE)?; + let operator_response = http_client + .post_response_json::( + operator_url.as_str(), + &OperatorProofRequest { + instance_id: instance_id.to_string(), + graph_id: graph_id.to_string(), + operator_committed_blockhash, + execution_layer_block_number: graph.proceed_withdraw_height, + watchtower_challenge_txids, + included_watchtowers, + watchtower_challenge_init_txid: watchtower_challenge_init_txid.0.to_string(), + watchtower_challenge_pubkeys: bitvm_graph + .parameters + .watchtower_pubkeys + .iter() + .map(|pk| pk.public_key(secp256k1::Parity::Even).to_string()) + .collect(), + }, + ) + .await?; + + if operator_response.proof_data.is_none() { + return Ok((None, get_operator_proof_wait_secs())); } - Some(inputs) -} -fn store_assert_commit_inputs_in_cache(graph_id: Uuid, inputs: &[(TxIn, Amount)]) -> Result<()> { - fs::create_dir_all(ASSERT_COMMITS_CACHE_DIR)?; - let path = assert_commit_cache_path(graph_id); - let file = File::create(&path)?; - let writer = BufWriter::new(file); - let payload = CachedAssertCommitInputs { - version: ASSERT_COMMIT_CACHE_VERSION, - inputs: inputs - .iter() - .map(|(txin, amount)| CachedAssertCommitInput { - txin: serialize(&embed_txin_into_dummy_tx(txin)), - amount_sat: amount.to_sat(), - }) - .collect(), + let statement = derive_operator_wrapper_statement(graph_id)?; + let expected_public_values = statement.public_values.clone(); + let genesis_txid_text = std::env::var(ENV_GENESIS_SEQUENCER_COMMIT_TXID) + .map_err(|_| anyhow!("{ENV_GENESIS_SEQUENCER_COMMIT_TXID} needs to be set"))?; + + let mut wrapper_url = base_url.join(PROOFS_WRAPPER_PROOF)?; + wrapper_url + .query_pairs_mut() + .append_pair("instance_id", &instance_id.to_string()) + .append_pair("graph_id", &graph_id.to_string()) + .append_pair("genesis_sequencer_commit_txid", &genesis_txid_text); + let wrapper_response = + http_client.get_response_json::(wrapper_url.as_str()).await?; + + let Some(proof_data) = wrapper_response.proof_data else { + if let Some(error) = wrapper_response.error { + info!("operator wrapper proof is not ready for graph_id:{graph_id}: {error}"); + } + return Ok((None, get_operator_proof_wait_secs())); }; - serde_json::to_writer(writer, &payload)?; - Ok(()) -} -fn cleanup_assert_commit_cache(graph_id: Uuid) -> Result<()> { - let path = assert_commit_cache_path(graph_id); - if path.exists() { - fs::remove_file(path)?; + let proof: ZKMProofWithPublicValues = bincode::deserialize(proof_data.proof.as_slice()) + .map_err(|err| anyhow!("failed to deserialize operator wrapper proof: {err}"))?; + let proof_public_values = proof.public_values.to_vec(); + if proof_public_values != expected_public_values { + bail!("operator wrapper proof public values do not match graph challenge inputs"); + } + if !proof_data.public_inputs.is_empty() && proof_data.public_inputs != expected_public_values { + bail!("operator wrapper proof public input sidecar does not match proof"); + } + let expected_wrapper_vk_hash = statement.wrapper_vk_hash.clone(); + if proof_data.vk != expected_wrapper_vk_hash { + bail!("operator wrapper proof vk hash does not match configured wrapper identity"); + } + let expected_zkm_version = statement.zkm_version.clone(); + if proof.zkm_version != expected_zkm_version || proof_data.zkm_version != expected_zkm_version { + bail!("operator wrapper proof Ziren version does not match configured wrapper identity"); } - Ok(()) -} -pub async fn challenger_force_skip_kickoff( - client: &BTCClient, - graph: &Bitvm2Graph, -) -> Result { - let challenger_master_key = ChallengerMasterKey::new(get_bitvm_key()?); - let challenger_master_keypair = challenger_master_key.master_keypair(); - let challenger_receive_address = - node_p2wsh_address(get_network(), &challenger_master_keypair.public_key().into()); + let part_stark_vk = load_part_stark_vk_for_zkm_version(&proof.zkm_version)?; + let ark_proof = + convert_ark_imm_wrap_vk(&proof, &proof_data.vk, &IMM_GROTH16_VK_BYTES, &part_stark_vk) + .map_err(|e| anyhow!("failed to convert operator wrapper proof to ark format: {e}"))?; + if ark_proof.public_inputs.as_slice() != statement.public_inputs.as_slice() { + bail!("operator wrapper proof public inputs do not match graph setup statement"); + } + + Ok(( + Some(ValidatedOperatorWrapperProof { + proof: ark_proof.proof.clone(), + public_inputs: ark_proof.public_inputs.into(), + verifying_key: ark_proof.groth16_vk.into(), + public_values: expected_public_values, + wrapper_vk_hash: expected_wrapper_vk_hash, + zkm_version: expected_zkm_version, + }), + 0, + )) +} +pub async fn verifier_force_skip_kickoff(client: &BTCClient, graph: &BitvmGcGraph) -> Result { + let verifier_master_key = VerifierMasterKey::new(get_bitvm_key()?); + let verifier_master_keypair = verifier_master_key.master_keypair(); + let verifier_receive_address = + node_p2wsh_address(get_network(), &verifier_master_keypair.public_key().into()); let fee_rate = get_fee_rate(client).await?; let (force_skip_kickoff_tx, anchor_added) = - build_force_skip_kickoff_tx(graph, challenger_receive_address, fee_rate)?; + build_force_skip_kickoff_tx(graph, verifier_receive_address, fee_rate)?; if anchor_added { let anchor_vout = force_skip_kickoff_tx.output.len() as u64 - 1; let force_skip_kickoff_tx_total_input_amount = @@ -2110,14 +2101,14 @@ pub async fn challenger_force_skip_kickoff( Ok(force_skip_kickoff_tx.compute_txid()) } -pub async fn challenger_quick_challenge(client: &BTCClient, graph: &Bitvm2Graph) -> Result { - let challenger_master_key = ChallengerMasterKey::new(get_bitvm_key()?); - let challenger_master_keypair = challenger_master_key.master_keypair(); - let challenger_receive_address = - node_p2wsh_address(get_network(), &challenger_master_keypair.public_key().into()); +pub async fn verifier_quick_challenge(client: &BTCClient, graph: &BitvmGcGraph) -> Result { + let verifier_master_key = VerifierMasterKey::new(get_bitvm_key()?); + let verifier_master_keypair = verifier_master_key.master_keypair(); + let verifier_receive_address = + node_p2wsh_address(get_network(), &verifier_master_keypair.public_key().into()); let fee_rate = get_fee_rate(client).await?; let (quick_challenge_tx, anchor_added) = - build_quick_challenge_tx(graph, challenger_receive_address, fee_rate)?; + build_quick_challenge_tx(graph, verifier_receive_address, fee_rate)?; if anchor_added { let anchor_vout = quick_challenge_tx.output.len() as u64 - 1; let quick_challenge_tx_total_input_amount = @@ -2573,8 +2564,8 @@ pub async fn build_genesis_prekickoff_tx( btc_client: &BTCClient, goat_client: &GOATClient, ) -> Result { - let assert_commit_num = todo_funcs::assert_commmit_num(); let watchtower_num = goat_client.committee_mana_get_watchtowers().await?.len(); + let verifier_num = todo_funcs::verifier_num(); let network = get_network(); let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); let node_keypair = operator_master_key.master_keypair(); @@ -2605,7 +2596,7 @@ pub async fn build_genesis_prekickoff_tx( vec![], fee_amount.to_sat(), watchtower_num, - assert_commit_num, + verifier_num, ) .map_err(|e| anyhow::anyhow!("failed to create pre-kickoff txn: {e}")) } @@ -2665,29 +2656,24 @@ pub async fn build_prekickoff_params( } pub async fn build_graph_params( - local_db: &LocalDB, + _local_db: &LocalDB, goat_client: &GOATClient, - instance_parameters: Bitvm2InstanceParameters, + instance_parameters: BitvmGcInstanceParameters, prekickoff_parameters: PrekickoffParameters, + bitvm_gc_circuit_datas: Vec, graph_nonce: u64, graph_id: Uuid, -) -> Result { +) -> Result { let instance_id = instance_parameters.instance_id; let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); let operator_master_keypair = operator_master_key.master_keypair(); let operator_pubkey = operator_master_keypair.public_key().into(); let operator_receive_address = node_p2wsh_address(instance_parameters.network, &operator_pubkey); - let operator_wots_pubkeys = operator_master_key.wots_keypair_for_graph(graph_id).1; + let (_, operator_wots_pubkeys) = operator_master_key.wots_keypair_for_graph(graph_id); let watchtower_pubkeys = goat_client.committee_mana_get_watchtowers().await?; - let mut hashlocks = vec![]; - for index in 0..watchtower_pubkeys.len() { - let preimage = todo_funcs::get_preimage(local_db, instance_id, graph_id, index).await?; - let hashlock = hash160(&preimage); - hashlocks.push(hashlock); - } - let guest_constant_value = get_guest_constant_value(instance_id, graph_id)?; - Ok(Bitvm2GraphParameters { + let _guest_constant_value = get_guest_constant_value(instance_id, graph_id)?; + Ok(BitvmGcGraphParameters { instance_parameters, prekickoff_parameters, graph_id, @@ -2697,12 +2683,11 @@ pub async fn build_graph_params( operator_wots_pubkeys, operator_receive_address, watchtower_pubkeys, - hashlocks, - guest_constant_value, + gc_data: bitvm_gc_circuit_datas, }) } -pub async fn operator_skip_graph(btc_client: &BTCClient, graph: &mut Bitvm2Graph) -> Result<()> { +pub async fn operator_skip_graph(btc_client: &BTCClient, graph: &mut BitvmGcGraph) -> Result<()> { let graph_nonce = graph.parameters.graph_nonce; let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); let operator_master_keypair = operator_master_key.master_keypair(); @@ -2767,7 +2752,7 @@ pub async fn operator_skip_graph(btc_client: &BTCClient, graph: &mut Bitvm2Graph Ok(()) } -pub async fn operator_kickoff(btc_client: &BTCClient, graph: &mut Bitvm2Graph) -> Result<()> { +pub async fn operator_kickoff(btc_client: &BTCClient, graph: &mut BitvmGcGraph) -> Result<()> { let graph_nonce = graph.parameters.graph_nonce; let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); let operator_graph_keypair = operator_master_key.master_keypair(); @@ -2837,215 +2822,14 @@ pub async fn operator_kickoff(btc_client: &BTCClient, graph: &mut Bitvm2Graph) - Ok(()) } -/// Return values: (Some(split_txid), has_pending_fee_inputs, Some(wait_proof_gen_secs)) -/// - `split_txid`: If a split transaction was broadcasted to consolidate UTXOs for fees, its txid is returned here. -/// - `has_pending_fee_inputs`: Indicates whether some of UTXOs for fees are still pending. -/// - `wait_proof_gen_secs`: If proof generation is still in progress, this returns the estimated time in seconds to wait before retrying. -pub async fn operator_send_assert_commit( - local_db: &LocalDB, - btc_client: &BTCClient, - http_client: &HttpAsyncClient, - graph: &mut Bitvm2Graph, -) -> Result<(Option, bool, Option)> { - // Prepare keys and proof materials - let instance_id = graph.parameters.instance_parameters.instance_id; - let graph_id = graph.parameters.graph_id; - let operator_master_key = OperatorMasterKey::new(get_bitvm_key()?); - let node_keypair = operator_master_key.master_keypair(); - let node_public_key: PublicKey = node_keypair.public_key().into(); - let node_address = node_p2wsh_address(get_network(), &node_public_key); - let fee_rate = get_fee_rate(btc_client).await?; - - // Ensure assert-init is confirmed before sending commits - let assert_init_txid = graph.assert_init.tx().compute_txid(); - if !tx_confirmed(btc_client, &assert_init_txid).await? { - bail!("assert-init not confirmed yet, skip assert-commit broadcast"); - } - - // Build or load signed inputs for each assert-commit connector - let assert_commit_inputs = if let Some(inputs) = load_assert_commit_inputs_from_cache(graph_id) - { - tracing::info!("loaded assert-commit inputs from cache for graph_id:{graph_id}"); - inputs - } else { - let wots_secret_keys = operator_master_key.wots_keypair_for_graph(graph_id).0; - let operator_committed_blockhash = - get_largest_watchtower_challenge_block(graph, btc_client).await?; - let (guest_inputs, proof, groth16_pubin, vk) = match get_operator_proof( - local_db, - http_client, - graph, - btc_client, - instance_id, - graph_id, - operator_committed_blockhash.to_string(), - ) - .await? - { - (Some(proof_data), _) => proof_data, - (None, wait_secs) => { - tracing::info!( - "operator proof generation in progress for graph_id:{graph_id}, wait and retry {wait_secs}s later" - ); - return Ok((None, false, Some(wait_secs))); - } - }; - info!("operator_send_assert_commit start operator_sign_assert_commit"); - let inputs = operator_sign_assert_commit( - node_keypair, - graph, - &wots_secret_keys, - guest_inputs, - proof, - groth16_pubin, - &vk, - )?; - - info!("operator_send_assert_commit start store_assert_commit_inputs_in_cache"); - if let Err(err) = store_assert_commit_inputs_in_cache(graph_id, &inputs) { - tracing::warn!("failed to write assert-commit cache for graph_id:{graph_id}: {err:?}"); - } - inputs - }; - - fn estimate_fee_funding_amount(txin: &TxIn, fee_rate: f64) -> Amount { - let sample_tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![txin.clone()], - output: vec![TxOut { - value: Amount::ZERO, - script_pubkey: generate_opreturn_script(vec![]), - }], - }; - let base_vbytes = sample_tx.weight().to_vbytes_ceil(); - let est_vbytes = base_vbytes + CHEKSIG_P2WSH_INPUT_VBYTES + P2WSH_OUTPUT_VBYTES; - let est_fee = (est_vbytes as f64 * fee_rate).ceil() as u64; - Amount::from_sat(est_fee + DUST_AMOUNT + 1_000) - } - - // filter out already-spent assert-commit connectors - let mut pending_assert_commit_txins: Vec<(usize, TxIn, Amount)> = vec![]; - let mut required_fees: Vec = vec![]; - for (i, (txin, amount)) in assert_commit_inputs.into_iter().enumerate() { - if outpoint_spent_txid(btc_client, &assert_init_txid, i as u64).await?.is_none() { - let est_fee = estimate_fee_funding_amount(&txin, fee_rate); - pending_assert_commit_txins.push((i, txin, amount)); - required_fees.push(est_fee); - } - } - if pending_assert_commit_txins.is_empty() { - tracing::info!("no assert-commit inputs to send (all spent)"); - if let Err(err) = cleanup_assert_commit_cache(graph_id) { - tracing::warn!( - "failed to cleanup assert-commit cache for graph_id:{graph_id}: {err:?}" - ); - } - return Ok((None, false, None)); - } - - // get available fee UTXOs from node address - let (utxo_sets, split_tx) = - get_proper_utxo_sets(btc_client, node_address.clone(), required_fees.clone(), fee_rate) - .await?; - - // broadcast split tx if needed - if let Some((mut split_tx, txin_amounts)) = split_tx { - for (i, amount) in txin_amounts.iter().enumerate().take(split_tx.input.len()) { - node_sign(&mut split_tx, i, *amount, EcdsaSighashType::All, &node_keypair)?; - } - let split_txid = split_tx.compute_txid(); - broadcast_tx(btc_client, &split_tx).await?; - return Ok((Some(split_txid), false, None)); - } else if utxo_sets.is_empty() { - let current_balance = btc_client - .get_address_utxo(node_address) - .await? - .iter() - .map(|u| u.value) - .sum::(); - let required_total_fee: Amount = required_fees.into_iter().sum(); - bail!(SpecialError::InsufficientBalance(format!( - "Not enough balance to complete the transaction, current_balance: {current_balance}, required: {required_total_fee}" - ))); - }; - - // build, sign and broadcast assert-commit txns - let mut has_pending_fee_input = false; - for (i, (origin_index, assert_commit_txin, _assert_commit_input_amount)) in - pending_assert_commit_txins.into_iter().enumerate() - { - let fee_inputs = &utxo_sets[i]; - let fee_inputs_total = fee_inputs.iter().map(|input| input.amount).sum::(); - let mut current_has_pending_fee_input = false; - for inputs in fee_inputs.iter() { - if !tx_confirmed(btc_client, &inputs.outpoint.txid).await? { - current_has_pending_fee_input = true; - break; - } - } - if current_has_pending_fee_input { - has_pending_fee_input = true; - continue; - } - - let mut tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![], - output: vec![], - }; - tx.input.push(assert_commit_txin); - for input in fee_inputs { - tx.input.push(TxIn { - previous_output: input.outpoint, - script_sig: ScriptBuf::new(), - sequence: Sequence::MAX, - witness: Witness::default(), - }); - } - - let fee = required_fees[i]; - let change_value = fee_inputs_total - fee; - if change_value > Amount::from_sat(DUST_AMOUNT) { - tx.output - .push(TxOut { value: change_value, script_pubkey: node_address.script_pubkey() }); - } else { - let op_return_script = generate_opreturn_script( - format!("assert-commit-{origin_index}").as_bytes().to_vec(), - ); - tx.output.push(TxOut { value: Amount::ZERO, script_pubkey: op_return_script }); - } - - for (fee_index, fee_input) in fee_inputs.iter().enumerate() { - let input_index = 1 + fee_index; - node_sign( - &mut tx, - input_index, - fee_input.amount, - EcdsaSighashType::All, - &node_keypair, - )?; - } - - broadcast_tx(btc_client, &tx).await?; - } - - if !has_pending_fee_input && let Err(err) = cleanup_assert_commit_cache(graph_id) { - warn!("failed to cleanup assert-commit cache for graph_id:{graph_id}: {err:?}"); - } - - Ok((None, has_pending_fee_input, None)) -} - -pub async fn send_challenge_tx(btc_client: &BTCClient, graph: &Bitvm2Graph) -> Result { +pub async fn send_challenge_tx(btc_client: &BTCClient, graph: &BitvmGcGraph) -> Result { let (mut challenge_tx, _) = export_challenge_tx(graph)?; - let challenge_keypair = ChallengerMasterKey::new(get_bitvm_key()?).master_keypair(); - let challenger_evm_address = get_node_goat_address() + let challenge_keypair = VerifierMasterKey::new(get_bitvm_key()?).master_keypair(); + let verifier_evm_address = get_node_goat_address() .ok_or_else(|| anyhow::anyhow!("failed to get node goat address".to_string()))?; challenge_tx.output.push(bitcoin::TxOut { value: Amount::ZERO, - script_pubkey: generate_opreturn_script(challenger_evm_address.to_vec()), + script_pubkey: generate_opreturn_script(verifier_evm_address.to_vec()), }); build_sign_and_broadcast_tx( btc_client, @@ -3059,7 +2843,7 @@ pub async fn send_challenge_tx(btc_client: &BTCClient, graph: &Bitvm2Graph) -> R pub async fn send_watchtower_challenge_tx( btc_client: &BTCClient, - graph: &Bitvm2Graph, + graph: &BitvmGcGraph, watchtower_index: usize, commitment_data: Vec, ) -> Result { @@ -3114,7 +2898,7 @@ pub async fn send_watchtower_challenge_tx( } } -pub async fn endorse_graph(goat_client: &GOATClient, graph: &Bitvm2Graph) -> Result { +pub async fn endorse_graph(goat_client: &GOATClient, graph: &BitvmGcGraph) -> Result { let signer = PrivateKeySigner::from_str(&get_node_goat_private_key()?)?; let graph_digest = get_graph_digest(goat_client, graph).await?; let sig = signer.sign_hash(&graph_digest.into()).await?; @@ -3135,7 +2919,7 @@ pub async fn endorse_pegin( pub async fn verify_graph_endorsement( goat_client: &GOATClient, evm_address: &EvmAddress, - graph: &Bitvm2Graph, + graph: &BitvmGcGraph, signature: &[u8], ) -> Result { let graph_digest = get_graph_digest(goat_client, graph).await?; @@ -3221,10 +3005,7 @@ pub async fn upsert_message( let message_id = generate_message_id(business_id, msg_type.to_string().clone(), sub_type); if is_update || storage_processor.find_messages_by_id(&message_id).await?.is_none() { if let Some(cancel_msg_type) = match msg_type { - MessageType::WatchtowerChallengeTimeout => { - Some(MessageType::WatchtowerChallengeInitSent) - } - MessageType::AssertCommitTimeout => Some(MessageType::AssertInitReady), + MessageType::AssertSent => Some(MessageType::WatchtowerChallengeInitSent), _ => None, } { notify_to_cancel_proof_task(storage_processor, business_id, cancel_msg_type).await?; @@ -3256,8 +3037,8 @@ pub async fn notify_to_cancel_proof_task( business_id: Uuid, msg_type: MessageType, ) -> Result<()> { - if !matches!(msg_type, MessageType::WatchtowerChallengeInitSent | MessageType::AssertInitReady) - { + // AssertInitSent is removed; update related logic if needed; + if !matches!(msg_type, MessageType::WatchtowerChallengeInitSent) { warn!("notify_to_cancel_proof_task: input wrong message type:{msg_type}"); return Ok(()); } @@ -3301,20 +3082,6 @@ pub async fn notify_to_cancel_proof_task( info!("call {}, response:{:?}", PROOFS_WATCHTOWER_PROOF_TIMEOUT, response); response.data.is_some() } - MessageType::AssertInitReady => { - let url = host.join(PROOFS_OPERATOR_PROOF_TIMEOUT)?; - let response = http_client - .post_response_json::( - url.as_str(), - &OperatorProofTimeoutUpdateRequest { - instance_id: graph.instance_id.to_string(), - graph_id: graph.graph_id.to_string(), - }, - ) - .await?; - info!("call {}, response:{:?}", PROOFS_OPERATOR_PROOF_TIMEOUT, response); - response.data.is_some() - } _ => false, }; if notify_result { @@ -3336,11 +3103,11 @@ pub async fn notify_to_cancel_proof_task( } /// store new graph, graph_raw_data, and update instance_id -pub async fn get_bitvm2_graph_from_db( +pub async fn get_bitvm_graph_from_db( _local_db: &LocalDB, _instance_id: Uuid, graph_id: Uuid, -) -> Result { +) -> Result { Err(anyhow!("graph:{graph_id} not found")) } @@ -3559,7 +3326,7 @@ pub fn strip_hex_prefix_owned(s: &str) -> String { /// Retrieve the server's public IP via NAT protocol and combine it with /// the configured RPC monitoring port`rpc_addr` to generate the external RPC service address. pub async fn set_node_external_socket_addr_env(rpc_addr: &str) -> Result<()> { - if get_proof_server_url().is_some() { + if get_proof_server_url().is_some() || std::env::var(ENV_EXTERNAL_SOCKET_ADDR).is_ok() { // not provide proof server return Ok(()); } @@ -3687,7 +3454,7 @@ pub async fn get_current_prekickoff_tx( Ok(Some(( (graphs[0].kickoff_index + 1) as u64, - Bitvm2Graph::from_simplified(&simplified_graph)?.next_prekickoff, + BitvmGcGraph::from_simplified(&simplified_graph)?.next_prekickoff, ))) } else { Ok(None) @@ -3779,7 +3546,7 @@ pub async fn store_pegin_request( pub async fn store_instance_parameters( local_db: &LocalDB, - instance_params: &Bitvm2InstanceParameters, + instance_params: &BitvmGcInstanceParameters, ) -> Result<()> { let mut storage_processor = local_db.acquire().await?; storage_processor @@ -3793,7 +3560,7 @@ pub async fn store_instance_parameters( pub async fn get_instance_parameters( local_db: &LocalDB, instance_id: Uuid, -) -> Result> { +) -> Result> { let mut storage_processor = local_db.acquire().await?; if let Some(instance) = storage_processor.find_instance(&instance_id).await? { Ok(if let Some(parameters) = instance.parameters { @@ -3806,57 +3573,48 @@ pub async fn get_instance_parameters( } } -fn convert_graph(bitvm2_graph: &Bitvm2Graph, current_time: i64) -> Graph { +fn convert_graph(bitvm_graph: &BitvmGcGraph, current_time: i64) -> Graph { let mut status = GraphStatus::OperatorPresigned.to_string(); - if bitvm2_graph.committee_pre_signed() { + if bitvm_graph.committee_pre_signed() { status = GraphStatus::CommitteePresigned.to_string(); } Graph { - graph_id: bitvm2_graph.parameters.graph_id, - instance_id: bitvm2_graph.parameters.instance_parameters.instance_id, - kickoff_index: bitvm2_graph.parameters.graph_nonce as i64, + graph_id: bitvm_graph.parameters.graph_id, + instance_id: bitvm_graph.parameters.instance_parameters.instance_id, + kickoff_index: bitvm_graph.parameters.graph_nonce as i64, from_addr: "".to_string(), to_addr: "".to_string(), - amount: bitvm2_graph.parameters.instance_parameters.pegin_amount.to_sat() as i64, - challenge_amount: bitvm2_graph.parameters.challenge_amount.to_sat() as i64, + amount: bitvm_graph.parameters.instance_parameters.pegin_amount.to_sat() as i64, + challenge_amount: bitvm_graph.parameters.challenge_amount.to_sat() as i64, status, sub_status: "".to_string(), - operator_pubkey: bitvm2_graph.parameters.operator_pubkey.to_string(), - cur_prekickoff_txid: Some(bitvm2_graph.cur_prekickoff.finalize().compute_txid().into()), - next_prekickoff: Some(bitvm2_graph.next_prekickoff.finalize().compute_txid().into()), + operator_pubkey: bitvm_graph.parameters.operator_pubkey.to_string(), + cur_prekickoff_txid: Some(bitvm_graph.cur_prekickoff.finalize().compute_txid().into()), + next_prekickoff: Some(bitvm_graph.next_prekickoff.finalize().compute_txid().into()), force_skip_kickoff_txid: Some( - bitvm2_graph.force_skip_kickoff.finalize().compute_txid().into(), + bitvm_graph.force_skip_kickoff.finalize().compute_txid().into(), ), - quick_challenge_txid: Some(bitvm2_graph.quick_challenge.finalize().compute_txid().into()), + quick_challenge_txid: Some(bitvm_graph.quick_challenge.finalize().compute_txid().into()), challenge_incomplete_kickoff_txid: Some( - bitvm2_graph.challenge_incomplete_kickoff.finalize().compute_txid().into(), + bitvm_graph.challenge_incomplete_kickoff.finalize().compute_txid().into(), ), - pegin_txid: Some(bitvm2_graph.pegin.finalize().compute_txid().into()), - kickoff_txid: Some(bitvm2_graph.kickoff.finalize().compute_txid().into()), - take1_txid: Some(bitvm2_graph.take1.finalize().compute_txid().into()), + pegin_txid: Some(bitvm_graph.pegin.finalize().compute_txid().into()), + kickoff_txid: Some(bitvm_graph.kickoff.finalize().compute_txid().into()), + take1_txid: Some(bitvm_graph.take1.finalize().compute_txid().into()), challenge_txid: None, - take2_txid: Some(bitvm2_graph.take2.finalize().compute_txid().into()), - disprove_txid: None, + take2_txid: Some(bitvm_graph.take2.finalize().compute_txid().into()), watchtower_challenge_init_txid: Some( - bitvm2_graph.watchtower_challenge_init.finalize().compute_txid().into(), + bitvm_graph.watchtower_challenge_init.finalize().compute_txid().into(), ), - watchtower_challenge_timeout_txids: bitvm2_graph - .watchtower_challenge_timeout_txns - .iter() - .map(|tx| tx.finalize().compute_txid().into()) - .collect(), - nack_txids: bitvm2_graph - .nack_txns + operator_assert_txid: Some(bitvm_graph.operator_assert.finalize().compute_txid().into()), + verifier_assert_txids: bitvm_graph + .verifier_asserts .iter() .map(|tx| tx.finalize().compute_txid().into()) .collect(), - blockhash_commit_timeout_txid: Some( - bitvm2_graph.blockhash_commit_timeout.finalize().compute_txid().into(), - ), - assert_init_txid: Some(bitvm2_graph.assert_init.finalize().compute_txid().into()), - assert_commit_timeout_txids: bitvm2_graph - .assert_commit_timeout_txns + disprove_txids: bitvm_graph + .disproves .iter() .map(|tx| tx.finalize().compute_txid().into()) .collect(), @@ -3869,24 +3627,24 @@ fn convert_graph(bitvm2_graph: &Bitvm2Graph, current_time: i64) -> Graph { } } -pub async fn store_graph(local_db: &LocalDB, simple_graph: &SimplifiedBitvm2Graph) -> Result<()> { +pub async fn store_graph(local_db: &LocalDB, simple_graph: &SimplifiedBitvmGcGraph) -> Result<()> { let mut tx = local_db.start_transaction().await?; - let bitvm2_graph: Bitvm2Graph = Bitvm2Graph::from_simplified(simple_graph)?; + let bitvm_graph: BitvmGcGraph = BitvmGcGraph::from_simplified(simple_graph)?; let graph_id = simple_graph.parameters.graph_id; let instance_id = simple_graph.parameters.instance_parameters.instance_id; let current_time = current_time_secs(); - let mut graph = convert_graph(&bitvm2_graph, current_time); + let mut graph = convert_graph(&bitvm_graph, current_time); if let Some(node_info) = - tx.get_node_by_btc_pub_key(&bitvm2_graph.parameters.operator_pubkey.to_string()).await? + tx.get_node_by_btc_pub_key(&bitvm_graph.parameters.operator_pubkey.to_string()).await? { graph.from_addr = node_info.goat_addr.clone(); graph.to_addr = - node_p2wsh_address(get_network(), &bitvm2_graph.parameters.operator_pubkey).to_string(); + node_p2wsh_address(get_network(), &bitvm_graph.parameters.operator_pubkey).to_string(); } tx.upsert_graph(&graph).await?; - if bitvm2_graph.committee_pre_signed() { + if bitvm_graph.committee_pre_signed() { tx.update_instance( &InstanceUpdate::new_with_instance_id(instance_id) .with_status(InstanceBridgeInStatus::Presigned.to_string()), @@ -3907,16 +3665,16 @@ pub async fn store_graph(local_db: &LocalDB, simple_graph: &SimplifiedBitvm2Grap Ok(()) } -/// Parse raw graph data JSON string to SimplifiedBitvm2Graph using spawn_blocking +/// Parse raw graph data JSON string to SimplifiedBitvmGcGraph using spawn_blocking /// to handle large data and potential stack overflow issues pub async fn parse_graph_raw_data( raw_data: String, graph_id: Uuid, -) -> Result { +) -> Result { let raw_data_len = raw_data.len(); let raw_data_clone = raw_data.clone(); let parse_result = tokio::task::spawn_blocking(move || { - serde_json::from_str::(&raw_data_clone) + serde_json::from_str::(&raw_data_clone) }) .await; @@ -3946,10 +3704,10 @@ pub async fn parse_graph_raw_data( } } -/// Serialize SimplifiedBitvm2Graph to JSON string using spawn_blocking +/// Serialize SimplifiedBitvmGcGraph to JSON string using spawn_blocking /// to handle large data and potential stack overflow issues pub async fn serialize_graph_raw_data( - graph: &SimplifiedBitvm2Graph, + graph: &SimplifiedBitvmGcGraph, graph_id: Uuid, ) -> Result { let graph_clone = graph.clone(); @@ -3984,7 +3742,7 @@ pub async fn get_graph( local_db: &LocalDB, _instance_id: Uuid, graph_id: Uuid, -) -> Result> { +) -> Result> { let mut storage_process = local_db.acquire().await?; if let Some(graph_raw_data) = storage_process.find_graph_raw_data(&graph_id).await? && let Ok(simplified_graph) = parse_graph_raw_data(graph_raw_data.raw_data, graph_id).await @@ -3999,7 +3757,7 @@ pub async fn get_graph_by_instance_id_and_operator_pubkey( local_db: &LocalDB, instance_id: Uuid, operator_pubkey: &PublicKey, -) -> Result> { +) -> Result> { let mut storage_process = local_db.acquire().await?; if let Some(graph_id) = storage_process .get_graph_id_by_instance_id_and_operator_pubkey(&instance_id, &operator_pubkey.to_string()) @@ -4480,8 +4238,8 @@ pub async fn update_graph_status( match storage_processor.find_graph(&graph_id).await? { Some(graph) => { if graph.status == new_status.to_string() - && let Some(sub_status) = sub_status - && sub_status == ChallengeSubStatus::default() + && let Some(ref sub_status) = sub_status + && *sub_status == ChallengeSubStatus::default() { warn!( "graph: {graph_id}, new_status: {new_status} is equal old status and ChallengeSubStatus is None, so not update" @@ -4523,7 +4281,7 @@ pub async fn get_graph_ids_for_instance( pub fn gen_instance_parameters_local( instance: &Instance, -) -> anyhow::Result { +) -> anyhow::Result { let network = Network::from_str(&instance.network)?; let committee_pubkeys: Vec = instance .committees_answers @@ -4533,7 +4291,7 @@ pub fn gen_instance_parameters_local( let committee_agg_pubkey = generate_n_of_n_public_key(&committee_pubkeys).0; let utxos: Vec = serde_json::from_str(&instance.input_utxos)?; - Ok(Bitvm2InstanceParameters { + Ok(BitvmGcInstanceParameters { network, instance_id: instance.instance_id, user_info: gen_user_info( @@ -4639,7 +4397,7 @@ pub(crate) async fn get_bridge_out_global_stats<'a>( } pub async fn get_largest_watchtower_challenge_block( - graph: &Bitvm2Graph, + graph: &BitvmGcGraph, btc_client: &BTCClient, ) -> anyhow::Result { let watchtower_challenge_init_txid = graph.watchtower_challenge_init.tx().compute_txid(); @@ -4647,7 +4405,7 @@ pub async fn get_largest_watchtower_challenge_block( let mut largest_watchtower_challenge_block_hash: BlockHash = BlockHash::from_slice(&[0u8; 32]).unwrap(); for watchtower_index in 0..graph.parameters.watchtower_pubkeys.len() { - let watchtower_challenge_vout = 2 * watchtower_index as u32; + let watchtower_challenge_vout = watchtower_index as u32; match outpoint_spent_txid( btc_client, &watchtower_challenge_init_txid, @@ -4686,165 +4444,187 @@ pub async fn get_largest_watchtower_challenge_block( Ok(largest_watchtower_challenge_block_hash) } -#[cfg(test)] -mod tests { - use super::*; - use std::time::Duration; - - async fn build_large_nonstandard_tx_for_debug( - btc_client: &BTCClient, - node_keypair: &Keypair, - payload_size: usize, - ) -> Result { - let mut msg = "large_opreturn_msg".to_string().as_bytes().to_vec(); - msg.extend(vec![0u8; payload_size - msg.len()]); - let scr = script! { - OP_RETURN - {msg} - } - .compile(); - let mut tx = Transaction { - version: bitcoin::transaction::Version(2), - lock_time: bitcoin::absolute::LockTime::ZERO, - input: vec![], - output: vec![TxOut { value: Amount::ZERO, script_pubkey: scr }], - }; - - let node_pubkey: PublicKey = node_keypair.public_key().into(); - let node_address = node_p2wsh_address(get_network(), &node_pubkey); - - let (inputs, _, change_amount) = get_proper_utxo_set( - btc_client, - tx.weight().to_vbytes_ceil(), - node_address.clone(), - Amount::ZERO, - 1.0, - ) - .await? - .ok_or_else(|| { - anyhow!("insufficient UTXOs on {} for debug nonstandard tx test", node_address) - })?; - - for input in &inputs { - tx.input.push(TxIn { - previous_output: input.outpoint, - script_sig: ScriptBuf::new(), - sequence: Sequence::MAX, - witness: Witness::default(), - }); - } - - if change_amount > Amount::from_sat(DUST_AMOUNT) { - tx.output - .push(TxOut { script_pubkey: node_address.script_pubkey(), value: change_amount }); - } +pub(crate) fn babe_setup_state_root(local_db: &LocalDB) -> PathBuf { + std::env::var_os(ENV_BABE_SETUP_STATE_DIR) + .filter(|path| !path.as_os_str().is_empty()) + .map(PathBuf::from) + .unwrap_or_else(|| { + let db_path = local_db + .path + .strip_prefix("sqlite://") + .or_else(|| local_db.path.strip_prefix("sqlite:")) + .unwrap_or(&local_db.path); + PathBuf::from(db_path) + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .map(Path::to_path_buf) + .unwrap_or_else(|| PathBuf::from(".")) + .join(".bitvm-babe-state") + }) +} - for (i, input) in inputs.iter().enumerate() { - node_sign(&mut tx, i, input.amount, EcdsaSighashType::All, node_keypair)?; - } +fn babe_setup_state_path(local_db: &LocalDB, instance_id: Uuid, graph_id: Uuid) -> PathBuf { + babe_setup_state_root(local_db).join(instance_id.to_string()).join(format!("{graph_id}.json")) +} - Ok(tx) - } +pub(crate) fn soldering_payload_hash(payload: &[u8]) -> [u8; 32] { + Sha256::digest(payload).into() +} - #[tokio::test] - #[ignore] - async fn debug_test_broadcast_nonstandard_tx_and_check_mempool_visibility() -> Result<()> { - if std::env::var(ENV_BITVM_SECRET).is_err() { - bail!("BITVM_SECRET is required and should correspond to a funded testnet key"); - } +pub(crate) fn soldering_payload_hash_hex(payload_hash: &[u8; 32]) -> String { + payload_hash.iter().map(|byte| format!("{byte:02x}")).collect() +} - let network = get_network(); - if network != Network::Testnet4 { - bail!("refuse to run this debug test on non-testnet4 network: {network}"); - } +pub(crate) async fn pending_graph_belongs_to_operator( + local_db: &LocalDB, + instance_id: Uuid, + graph_id: Uuid, + operator_pubkey: &PublicKey, +) -> Result { + let mut storage = local_db.acquire().await?; + Ok(storage.find_pending_graph_init_by_graph_id(&graph_id).await?.is_some_and(|pending| { + pending.instance_id == instance_id && pending.operator_pubkey == operator_pubkey.to_string() + })) +} - let btc_url = - get_btc_url_from_env().unwrap_or_else(|| "https://mempool.space/testnet4".to_string()); - let btc_client = BTCClient::new(network, Some(&btc_url)); - let node_keypair = get_bitvm_key()?; - - // Use an oversized OP_RETURN payload to make tx non-standard for regular mempool relay. - let tx = build_large_nonstandard_tx_for_debug(&btc_client, &node_keypair, 200_000).await?; - let txid = tx.compute_txid(); - println!( - "debug nonstandard tx built: txid={}, vbytes={}", - txid, - tx.weight().to_vbytes_ceil() +#[allow(clippy::too_many_arguments)] +pub(crate) async fn send_soldering_proof_to_operator( + swarm: &mut Swarm, + instance_id: Uuid, + graph_id: Uuid, + verifier_index: usize, + opened: &[(usize, u64)], + finalized: &[FinalizedInstanceData], + soldering: &SolderingData, +) -> Result<()> { + let compact_payload = compact_soldering_proof_payload(opened, finalized, soldering)?; + let payload = bincode::serialize(&compact_payload) + .context("serialize compact soldering proof payload")?; + let payload_hash = soldering_payload_hash(&payload); + let total_len = payload.len(); + let store_base_path = get_soldering_proof_payload_store_path()?; + let payload_path = soldering_proof_payload_store_path( + &store_base_path, + instance_id, + graph_id, + verifier_index, + &payload_hash, + )?; + let store_mode = if is_soldering_proof_s3_path(&store_base_path) { "s3" } else { "local" }; + if let Err(err) = + write_soldering_proof_store_payload(&payload_path, &payload).await.with_context(|| { + format!("write soldering proof payload to configured store path {payload_path}") + }) + { + tracing::error!( + store_mode, + total_len, + payload_hash = %soldering_payload_hash_hex(&payload_hash), + payload_path = %payload_path, + error = %err, + "failed to write soldering proof payload to store" ); + return Err(err); + } + tracing::info!( + store_mode, + total_len, + payload_hash = %soldering_payload_hash_hex(&payload_hash), + payload_path = %payload_path, + "send compact soldering proof ready from payload store" + ); + let message_content = GOATMessageContent::SolderingProofReady(SolderingProofReady { + instance_id, + graph_id, + verifier_index, + payload_hash, + total_len, + }); + send_to_peer(swarm, GOATMessage::new(Actor::Operator, message_content)).await?; - let normal_err = broadcast_tx(&btc_client, &tx).await.err().ok_or_else(|| { - anyhow!("expected normal mempool broadcast to fail for non-standard tx") - })?; - println!("normal broadcast failed as expected: {normal_err:#}"); - - broadcast_nonstandard_tx(&btc_client, &tx).await?; - println!("broadcast_nonstandard_tx succeeded for non-standard tx"); - - let mut found_in_mempool = false; - for _ in 0..10 { - if btc_client.get_tx(&txid).await?.is_some() { - found_in_mempool = true; - break; - } - tokio::time::sleep(Duration::from_secs(1)).await; - } - - if found_in_mempool { - println!( - "successfully found non-standard tx in mempool after broadcast_nonstandard_tx" - ); - } else { - println!("failed to find non-standard tx in mempool after broadcast_nonstandard_tx"); - } + Ok(()) +} - Ok(()) +fn load_babe_setup_state_from_path(path: &Path) -> Result> { + match std::fs::read(path) { + Ok(bytes) => Ok(Some(serde_json::from_slice(&bytes)?)), + Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None), + Err(err) => Err(err).with_context(|| format!("read BABE setup state {}", path.display())), } +} - #[tokio::test] - #[ignore = "test on regtest"] - async fn test_get_watchtower_challenge_info() { - let init_txid = - Txid::from_str("2bb03cb075c95d94c298d139242bcd42c366b7105df34591e77e3ac11ac29386") - .unwrap(); - let init_txid = SerializableTxid(init_txid); - let number_challenge = 2; - let esplora_url = "http://localhost:13002".to_string(); - let btc_client = BTCClient::new(get_network(), Some(&esplora_url)); - - let result = - get_watchtower_challenge_info(&btc_client, &init_txid, number_challenge).await.unwrap(); - println!("result: {result:#?}"); +fn save_babe_setup_state_to_path(path: &Path, state: &BabeSetupState) -> Result<()> { + if let Some(parent) = path.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("create BABE setup state dir {}", parent.display()))?; } + let bytes = serde_json::to_vec_pretty(state)?; + std::fs::write(path, bytes) + .with_context(|| format!("write BABE setup state {}", path.display())) +} - #[test] - fn test_operator_proof_request_url() { - unsafe { - std::env::set_var(ENV_PROOF_BUILD_URL, "http://127.0.0.1:8900"); - } - - let base_url = Url::parse( - &get_proof_build_rpc_host() - .ok_or_else(|| anyhow::anyhow!("failed to get proof_build_rpc_host")) - .unwrap(), - ) - .unwrap(); - unsafe { - std::env::remove_var(ENV_PROOF_BUILD_URL); - } - let url = base_url.join(NODES_OPERATOR_BASE).unwrap(); - - assert_eq!(url.as_str(), "http://127.0.0.1:8900/v1/proofs/operator_proofs"); - } +pub(crate) fn load_babe_setup_state( + local_db: &LocalDB, + instance_id: Uuid, + graph_id: Uuid, +) -> Result> { + load_babe_setup_state_from_path(&babe_setup_state_path(local_db, instance_id, graph_id)) +} - #[test] - fn test_load_part_stark_vk_for_zkm_version_accepts_known_version() { - let part_stark_vk = load_part_stark_vk_for_zkm_version("v1.2.4").unwrap(); - assert!(!part_stark_vk.is_empty()); - } +pub(crate) fn save_babe_setup_state( + local_db: &LocalDB, + instance_id: Uuid, + graph_id: Uuid, + state: &BabeSetupState, +) -> Result<()> { + save_babe_setup_state_to_path(&babe_setup_state_path(local_db, instance_id, graph_id), state) +} - #[test] - fn test_load_part_stark_vk_for_zkm_version_rejects_unknown_version_without_panic() { - let err = load_part_stark_vk_for_zkm_version("v0.0.0-test").unwrap_err(); - assert!(err.to_string().contains("failed to load part_stark_vk")); - } +pub(crate) fn update_babe_setup_state( + local_db: &LocalDB, + instance_id: Uuid, + graph_id: Uuid, + update: impl FnOnce(&mut BabeSetupState), +) -> Result { + let mut state = load_babe_setup_state(local_db, instance_id, graph_id)?.unwrap_or_default(); + update(&mut state); + save_babe_setup_state(local_db, instance_id, graph_id, &state)?; + Ok(state) +} + +#[derive(Clone, Default, Serialize, Deserialize)] +pub struct BabeSetupState { + pub verifier: Option, + pub operator: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct VerifierBabeSetupState { + pub verifier_pubkey: PublicKey, + pub setup_package: CACSetupPackage, + pub private_state: BabeVerifierPrivateState, + pub verifier_index: Option, + pub finalized_indices: Vec, + pub opened: Vec<(usize, u64)>, + pub finalized: Vec, + pub soldering: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct OperatorVerifierCandidate { + pub verifier_pubkey: PublicKey, + pub setup_package: CACSetupPackage, + pub verifier_index: Option, + pub selected_circuit_indexes: Vec, + pub gc_data: Option, + #[serde(default)] + pub prover_state: Option, +} + +#[derive(Clone, Serialize, Deserialize)] +pub struct OperatorBabeSetupState { + pub frozen_verifier_pubkeys: Option>, + pub candidates: Vec, + #[serde(default)] + pub asserted_wrapper_proof: Option>, } diff --git a/node/src/vk.rs b/node/src/vk.rs index 708b3b6b..5e5667ce 100644 --- a/node/src/vk.rs +++ b/node/src/vk.rs @@ -1,5 +1,6 @@ +#![allow(dead_code)] + use anyhow::Result; -use bitvm2_lib::types::VerifyingKey; use std::fs; use std::path::PathBuf; use zkm_sdk::install::CIRCUIT_ARTIFACTS_URL_BASE; @@ -11,6 +12,8 @@ use { std::{cmp::min, process::Command}, }; +pub type VerifyingKey = ark_groth16::VerifyingKey; + pub(crate) fn block_on(fut: impl std::future::Future) -> T { use tokio::task::block_in_place; diff --git a/node/ssp-ci.sh b/node/ssp-ci.sh index 7aa3c259..90f035e7 100644 --- a/node/ssp-ci.sh +++ b/node/ssp-ci.sh @@ -26,16 +26,16 @@ $CMD fund $CMD payfee --total 5 echo -e "publish genisis sign sequencer set" -$CMD push-seq --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --init-genesis --commit-info="${DIR}/../circuits/data/commit-chain/commit_info.json.0" +$CMD push-seq --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --operator-vk-hash $OPERATOR_VK_HASH --init-genesis --commit-info="${DIR}/../circuits/data/commit-chain/commit_info.json.0" echo -e "set the new publisher set: publishers: ${PUBLISHER_BTC_PUBKEYS} => next_publishers: ${NEXT_PUBLISHER_BTC_PUBKEYS}" $CMD payfee --total 5 -$CMD sign-seq --owner-btc-key-wif cMceqPhHedrhbcR9eXgzmfWy7kRqLyAxMYwFT6ABDWsiwUp9Nsq9 --goat-block-number $GOAT_BLOCK_NUMBER -$CMD sign-seq --owner-btc-key-wif cMec2DGaTXkYJYfi7x3ZGjRXkeqmAvYAoWzMAcWj5fdLaqudWsNi --goat-block-number $GOAT_BLOCK_NUMBER -$CMD sign-seq --owner-btc-key-wif cMgZD2qsGReP1UvGbNQ7moL6PZFgzsuPFV3St8sGwpNxED4hqkEM --goat-block-number $GOAT_BLOCK_NUMBER -$CMD sign-seq --owner-btc-key-wif cMiWPrRA5KYDiRAq4nkgGsEf2TfcpqGbhT6YbfDpoy8ZsaAHiDeo --goat-block-number $GOAT_BLOCK_NUMBER -$CMD sign-seq --owner-btc-key-wif cMkTafzStDS4RMRPYD7Emw9DfN5Yendp9R9eKBaNg7tBWwGU43fD --goat-block-number $GOAT_BLOCK_NUMBER +$CMD sign-seq --owner-btc-key-wif cMceqPhHedrhbcR9eXgzmfWy7kRqLyAxMYwFT6ABDWsiwUp9Nsq9 --goat-block-number $GOAT_BLOCK_NUMBER --operator-vk-hash $OPERATOR_VK_HASH +$CMD sign-seq --owner-btc-key-wif cMec2DGaTXkYJYfi7x3ZGjRXkeqmAvYAoWzMAcWj5fdLaqudWsNi --goat-block-number $GOAT_BLOCK_NUMBER --operator-vk-hash $OPERATOR_VK_HASH +$CMD sign-seq --owner-btc-key-wif cMgZD2qsGReP1UvGbNQ7moL6PZFgzsuPFV3St8sGwpNxED4hqkEM --goat-block-number $GOAT_BLOCK_NUMBER --operator-vk-hash $OPERATOR_VK_HASH +$CMD sign-seq --owner-btc-key-wif cMiWPrRA5KYDiRAq4nkgGsEf2TfcpqGbhT6YbfDpoy8ZsaAHiDeo --goat-block-number $GOAT_BLOCK_NUMBER --operator-vk-hash $OPERATOR_VK_HASH +$CMD sign-seq --owner-btc-key-wif cMkTafzStDS4RMRPYD7Emw9DfN5Yendp9R9eKBaNg7tBWwGU43fD --goat-block-number $GOAT_BLOCK_NUMBER --operator-vk-hash $OPERATOR_VK_HASH # broadcast publisher changes to Bitcoin $CMD push-seq --goat-block-number $GOAT_BLOCK_NUMBER --commit-info="${DIR}/../circuits/data/commit-chain/commit_info.json.1" @@ -44,10 +44,10 @@ GOAT_BLOCK_NUMBER=$(($GOAT_BLOCK_NUMBER+1)) echo -e "recover the publisher set: next_publishers => publishers" $CMD payfee --total 3 -$CMD sign-seq --owner-btc-key-wif cMceqPhHedrhbcR9eXgzmfWy7kRqLyAxMYwFT6ABDWsiwUp9Nsq9 --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS -$CMD sign-seq --owner-btc-key-wif cMec2DGaTXkYJYfi7x3ZGjRXkeqmAvYAoWzMAcWj5fdLaqudWsNi --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS -$CMD sign-seq --owner-btc-key-wif cMgZD2qsGReP1UvGbNQ7moL6PZFgzsuPFV3St8sGwpNxED4hqkEM --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS +$CMD sign-seq --owner-btc-key-wif cMceqPhHedrhbcR9eXgzmfWy7kRqLyAxMYwFT6ABDWsiwUp9Nsq9 --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS --operator-vk-hash $OPERATOR_VK_HASH +$CMD sign-seq --owner-btc-key-wif cMec2DGaTXkYJYfi7x3ZGjRXkeqmAvYAoWzMAcWj5fdLaqudWsNi --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS --operator-vk-hash $OPERATOR_VK_HASH +$CMD sign-seq --owner-btc-key-wif cMgZD2qsGReP1UvGbNQ7moL6PZFgzsuPFV3St8sGwpNxED4hqkEM --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS --operator-vk-hash $OPERATOR_VK_HASH # broadcast publisher changes to Bitcoin -$CMD push-seq --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS --commit-info="${DIR}/../circuits/data/commit-chain/commit_info.json.2" +$CMD push-seq --goat-block-number $GOAT_BLOCK_NUMBER --next-publisher-btc-pubkeys=$PUBLISHER_BTC_PUBKEYS --publisher-btc-pubkeys=$NEXT_PUBLISHER_BTC_PUBKEYS --operator-vk-hash $OPERATOR_VK_HASH --commit-info="${DIR}/../circuits/data/commit-chain/commit_info.json.2" diff --git a/proof-builder-rpc/Cargo.toml b/proof-builder-rpc/Cargo.toml index bec395f7..68e9b5b3 100644 --- a/proof-builder-rpc/Cargo.toml +++ b/proof-builder-rpc/Cargo.toml @@ -8,6 +8,7 @@ name = "proof-builder-rpc" path = "src/main.rs" [dependencies] +zkm-sdk = { workspace = true } clap = { workspace = true } futures = { workspace = true } dotenv = { workspace = true } @@ -35,6 +36,7 @@ commit-chain = { workspace = true } state-chain-proof = { workspace = true } watchtower-proof = { workspace = true } operator-proof = { workspace = true } +operator-wrapper-proof = { workspace = true } util = { workspace = true } client = { workspace = true } bitcoin-light-client-circuit.workspace = true diff --git a/proof-builder-rpc/README.md b/proof-builder-rpc/README.md index 3d79be87..afcb67ec 100644 --- a/proof-builder-rpc/README.md +++ b/proof-builder-rpc/README.md @@ -26,7 +26,7 @@ Long-running proof tasks (stored in the `long_running_task_proof` table) — suc GOAT_BLOCK_NUMBER=${THE_GOAT_BLOCK_NUMBER} bash -x scp.sh ``` -Example `scp.sh` used for Regtest integration tests: +Example `scp.sh` used for Regtest integration tests, remember to set the `OPERATOR_VK_HASH` environment variable before running: ```bash #!/bin/bash diff --git a/proof-builder-rpc/proof-builder.toml.example b/proof-builder-rpc/proof-builder.toml.example index 96dce504..c90145c6 100644 --- a/proof-builder-rpc/proof-builder.toml.example +++ b/proof-builder-rpc/proof-builder.toml.example @@ -73,3 +73,9 @@ graph_id="86F23E650F6D447885CA320E6BA27ABD" execution_layer_block_number = 10321027 index = 1 bitcoin_network = "regtest" + +[wrapper] +enable = false +output = "../circuits/data/wrapper/output.bin" +genesis_sequencer_commit_txid = "5d5a29e1c426abc4b947f90b93c6b043683aafad79cb3b9a9829dfd5e51cad09" +scan_interval = 30 diff --git a/proof-builder-rpc/src/api/mod.rs b/proof-builder-rpc/src/api/mod.rs index 8ad9cfa0..c1f5d999 100644 --- a/proof-builder-rpc/src/api/mod.rs +++ b/proof-builder-rpc/src/api/mod.rs @@ -6,9 +6,9 @@ mod validation; use crate::api::metrics_service::{ApiMetricsState, metrics_handler, metrics_middleware}; use crate::api::proof_handler::{ - get_chain_proof_task_desc, get_operator_proof_task_desc, post_operator_proof_task, - post_watchtower_proof_task, update_operator_proof_task_timeout, - update_watchtower_proof_task_timeout, + get_chain_proof_task_desc, get_operator_proof_task_desc, get_wrapper_proof_task, + get_wrapper_proof_task_desc, post_operator_proof_task, post_watchtower_proof_task, + update_operator_proof_task_timeout, update_watchtower_proof_task_timeout, }; use axum::http::Method; use axum::routing::{get, post}; @@ -48,6 +48,8 @@ pub(crate) async fn serve( .route(routes::v1::PROOFS_OPERATOR_PROOF, post(post_operator_proof_task)) .route(routes::v1::PROOFS_OPERATOR_PROOF_TIMEOUT, post(update_operator_proof_task_timeout)) .route(routes::v1::PROOFS_OPERATOR_PROOF_DESC, get(get_operator_proof_task_desc)) + .route(routes::v1::PROOFS_WRAPPER_PROOF, get(get_wrapper_proof_task)) + .route(routes::v1::PROOFS_WRAPPER_PROOF_DESC, get(get_wrapper_proof_task_desc)) .layer(middleware::from_fn_with_state(api_state.clone(), metrics_middleware)) .layer(CorsLayer::new().allow_headers(Any).allow_origin(Any).allow_methods(vec![ Method::GET, diff --git a/proof-builder-rpc/src/api/proof_handler.rs b/proof-builder-rpc/src/api/proof_handler.rs index 37e72da4..dde156e8 100644 --- a/proof-builder-rpc/src/api/proof_handler.rs +++ b/proof-builder-rpc/src/api/proof_handler.rs @@ -1,20 +1,22 @@ use crate::api::ApiState; -use crate::api::response::{ApiErrorExt, ApiResult, ok_response}; +use crate::api::response::{ApiErrorExt, ApiResult, ErrorResponse, ok_response}; use crate::api::validation::InputValidator; use crate::task::{ - add_operator_task, add_watchtower_task, find_operator_task, find_watchtower_task, - update_operator_task_state, update_watchtower_task_state, + add_operator_task, add_watchtower_task, create_missing_wrapper_tasks, find_operator_task, + find_watchtower_task, update_operator_task_state, update_watchtower_task_state, }; use axum::Json; use axum::extract::{Query, State}; +use axum::http::StatusCode; use proof_builder::{ ChainProofDescRequest, OperatorProofDescRequest, OperatorProofRequest, OperatorProofResponse, OperatorProofTimeoutUpdateRequest, OperatorProofTimeoutUpdateResponse, ProofData, ProofDesc, ProofDescResponse, ProofType, WatchtowerProofRequest, WatchtowerProofResponse, WatchtowerProofTimeoutUpdateRequest, WatchtowerProofTimeoutUpdateResponse, + WrapperProofDescRequest, WrapperProofMetadata, WrapperProofResponse, }; use std::sync::Arc; -use store::ProofState; +use store::{ProofState, WrapperProof}; use tracing::info; #[axum::debug_handler] @@ -185,6 +187,63 @@ pub(super) async fn post_operator_proof_task( } } } + +#[axum::debug_handler] +pub(super) async fn get_wrapper_proof_task( + State(api_state): State>, + Query(payload): Query, +) -> ApiResult { + let wrapper_proof = find_wrapper_proof_by_request(&api_state, &payload).await?; + match wrapper_proof { + Some(wrapper_proof) + if wrapper_proof.proof_state == ProofState::Proven.to_i64() + && wrapper_proof.path_to_proof.is_some() => + { + ok_response(WrapperProofResponse { + proof_data: Some(ProofData::load_proof_data( + wrapper_proof.path_to_proof.as_deref().unwrap(), + ProofType::Wrapper, + )), + metadata: Some(wrapper_metadata(&wrapper_proof)), + error: None, + }) + } + Some(wrapper_proof) => ok_response(WrapperProofResponse { + proof_data: None, + metadata: Some(wrapper_metadata(&wrapper_proof)), + error: Some(format!( + "The wrapper proof is not ready, state {}, path:{:?}", + wrapper_proof.proof_state, wrapper_proof.path_to_proof + )), + }), + None => ok_response(WrapperProofResponse { + proof_data: None, + metadata: None, + error: Some("No wrapper proof found".to_string()), + }), + } +} + +#[axum::debug_handler] +pub(super) async fn get_wrapper_proof_task_desc( + State(api_state): State>, + Query(payload): Query, +) -> ApiResult { + let wrapper_proof = find_wrapper_proof_by_request(&api_state, &payload).await?; + match wrapper_proof { + Some(wrapper_proof) => ok_response(WrapperProofResponse { + proof_data: None, + metadata: Some(wrapper_metadata(&wrapper_proof)), + error: None, + }), + None => ok_response(WrapperProofResponse { + proof_data: None, + metadata: None, + error: Some("No wrapper proof found".to_string()), + }), + } +} + #[axum::debug_handler] pub(super) async fn update_operator_proof_task_timeout( State(api_state): State>, @@ -216,6 +275,78 @@ pub(super) async fn update_operator_proof_task_timeout( } } +async fn find_wrapper_proof_by_request( + api_state: &Arc, + payload: &WrapperProofDescRequest, +) -> Result, (StatusCode, Json)> { + if let Some(operator_proof_id) = payload.operator_proof_id { + let mut storage_process = + api_state.local_db.acquire().await.api_error("GET_WRAPPER_PROOF_ERROR")?; + return storage_process + .find_wrapper_proof_by_operator_proof_id(operator_proof_id) + .await + .api_error("GET_WRAPPER_PROOF_ERROR"); + } + + let instance_id = InputValidator::validate_uuid( + payload.instance_id.as_deref().unwrap_or_default(), + "instance_id", + )?; + let graph_id = + InputValidator::validate_uuid(payload.graph_id.as_deref().unwrap_or_default(), "graph_id")?; + + let wrapper_proof = { + let mut storage_process = + api_state.local_db.acquire().await.api_error("GET_WRAPPER_PROOF_ERROR")?; + storage_process + .find_wrapper_proof_by_instance_and_graph(&instance_id, &graph_id) + .await + .api_error("GET_WRAPPER_PROOF_ERROR")? + }; + + if wrapper_proof.is_some() { + return Ok(wrapper_proof); + } + + if let Some(genesis_txid) = payload.genesis_sequencer_commit_txid.as_deref() + && !genesis_txid.is_empty() + { + create_missing_wrapper_tasks(&api_state.local_db, genesis_txid) + .await + .api_error("GET_WRAPPER_PROOF_ERROR")?; + } + + let mut storage_process = + api_state.local_db.acquire().await.api_error("GET_WRAPPER_PROOF_ERROR")?; + storage_process + .find_wrapper_proof_by_instance_and_graph(&instance_id, &graph_id) + .await + .api_error("GET_WRAPPER_PROOF_ERROR") +} + +fn wrapper_metadata(wrapper_proof: &WrapperProof) -> WrapperProofMetadata { + WrapperProofMetadata { + id: wrapper_proof.id, + operator_proof_id: wrapper_proof.operator_proof_id, + instance_id: wrapper_proof.instance_id.to_string(), + graph_id: wrapper_proof.graph_id.to_string(), + operator_path_to_proof: wrapper_proof.operator_path_to_proof.clone(), + path_to_proof: wrapper_proof.path_to_proof.clone(), + public_value_hex: wrapper_proof.public_value_hex.clone(), + operator_vk_hash: wrapper_proof.operator_vk_hash.clone(), + genesis_sequencer_commit_txid: wrapper_proof.genesis_sequencer_commit_txid.clone(), + operator_public_value_hex: wrapper_proof.operator_public_value_hex.clone(), + proof_state: wrapper_proof.proof_state, + proof_size: wrapper_proof.proof_size, + cycles: wrapper_proof.cycles, + total_time_to_proof: wrapper_proof.total_time_to_proof, + proving_time: wrapper_proof.proving_time, + zkm_version: wrapper_proof.zkm_version.clone(), + created_at: wrapper_proof.created_at, + updated_at: wrapper_proof.updated_at, + } +} + #[axum::debug_handler] pub(super) async fn post_watchtower_proof_task( State(api_state): State>, @@ -226,14 +357,6 @@ pub(super) async fn post_watchtower_proof_task( let challenge_init_txid = InputValidator::validate_btc_txid(&payload.challenge_init_txid, "challenge_init_txid")? .to_string(); - let mut storage_process = - api_state.local_db.acquire().await.api_error("POST_WATCHTOWER_PROOF_TASK_ERROR")?; - storage_process - .find_graph(&graph_id) - .await - .api_error("POST_WATCHTOWER_PROOF_TASK_ERROR")? - .ok_or_else(|| anyhow::anyhow!("graph {graph_id} not found")) - .api_error("POST_WATCHTOWER_PROOF_TASK_ERROR")?; let watchtower_proof = find_watchtower_task(&api_state.local_db, instance_id, graph_id, &payload.public_key) diff --git a/proof-builder-rpc/src/api/routes.rs b/proof-builder-rpc/src/api/routes.rs index fea597ee..df37f6c7 100644 --- a/proof-builder-rpc/src/api/routes.rs +++ b/proof-builder-rpc/src/api/routes.rs @@ -7,4 +7,6 @@ pub(crate) mod v1 { pub(crate) const PROOFS_OPERATOR_PROOF: &str = "/v1/proofs/operator_proofs"; pub(crate) const PROOFS_OPERATOR_PROOF_TIMEOUT: &str = "/v1/proofs/operator_proofs_timeout"; pub(crate) const PROOFS_OPERATOR_PROOF_DESC: &str = "/v1/proofs/operator_proofs_desc"; + pub(crate) const PROOFS_WRAPPER_PROOF: &str = "/v1/proofs/wrapper_proofs"; + pub(crate) const PROOFS_WRAPPER_PROOF_DESC: &str = "/v1/proofs/wrapper_proofs_desc"; } diff --git a/proof-builder-rpc/src/config.rs b/proof-builder-rpc/src/config.rs index 7c137c5b..c47ff1ce 100644 --- a/proof-builder-rpc/src/config.rs +++ b/proof-builder-rpc/src/config.rs @@ -9,6 +9,8 @@ pub(crate) struct ProofBuilderConfig { pub state_chain: state_chain_proof::Args, pub watchtower: watchtower_proof::Args, pub operator: operator_proof::Args, + #[serde(default)] + pub wrapper: operator_wrapper_proof::Args, } impl ProofBuilderConfig { diff --git a/proof-builder-rpc/src/task/mod.rs b/proof-builder-rpc/src/task/mod.rs index e58fcb03..6241a103 100644 --- a/proof-builder-rpc/src/task/mod.rs +++ b/proof-builder-rpc/src/task/mod.rs @@ -3,20 +3,24 @@ mod header_chain_proof; mod operator_proof; mod state_chain_proof; mod watchtower_proof; +mod wrapper_proof; use crate::config::ProofBuilderConfig; use crate::task::{ commit_chain_proof::spawn_commit_chain_proof_task, header_chain_proof::spawn_header_chain_proof_task, operator_proof::spawn_operator_proof_task, state_chain_proof::spawn_state_chain_proof_task, watchtower_proof::spawn_watchtower_proof_task, + wrapper_proof::spawn_wrapper_proof_task, }; use ::commit_chain_proof::CommitChainProofBuilder; use ::header_chain_proof::HeaderChainProofBuilder; use ::state_chain_proof::StateChainProofBuilder; use bitcoin::{BlockHash, Network, Txid}; use client::btc_chain::BTCClient; +use std::collections::HashSet; use std::str::FromStr; use std::time::UNIX_EPOCH; use uuid::Uuid; +pub(crate) use wrapper_proof::create_missing_wrapper_tasks; use futures::future::Either; use proof_builder::{OnDemandTask, ProofBuilder}; @@ -31,6 +35,7 @@ pub(crate) fn is_start_generate_proof_tasks(cfg: &ProofBuilderConfig) -> bool { || cfg.state_chain.enable || cfg.watchtower.enable || cfg.operator.enable + || cfg.wrapper.enable } pub(crate) async fn run_generate_proof_tasks( @@ -99,6 +104,18 @@ pub(crate) async fn run_generate_proof_tasks( Either::Right(std::future::pending::, tokio::task::JoinError>>()) }; + let wrapper_proof_future = if cfg.wrapper.enable { + Either::Left(spawn_wrapper_proof_task( + cfg.wrapper.clone(), + local_db.clone(), + interval, + interval, + cancellation_token.clone(), + )) + } else { + Either::Right(std::future::pending::, tokio::task::JoinError>>()) + }; + tokio::select! { result = header_chain_proof_future => { match result { @@ -175,6 +192,21 @@ pub(crate) async fn run_generate_proof_tasks( } } } + result = wrapper_proof_future => { + match result { + Ok(Ok(_)) => { + info!("Wrapper proof generate task completed successfully"); + } + Ok(Err(e)) => { + error!("Wrapper proof generate task error: {}", e); + return Err(e); + } + Err(e) => { + error!("Wrapper proof generate task panic: {:?}", e); + anyhow::bail!("Wrapper proof generate task panic: {:?}", e); + } + } + } } Ok("tasks_completed".to_string()) @@ -214,7 +246,7 @@ async fn read_watchtower_challenge_details<'a>( i64, i64, Option, - Vec, + Vec>, Vec, Vec, Option, @@ -234,8 +266,8 @@ async fn read_watchtower_challenge_details<'a>( Some(task) => { tracing::info!("watchtower task: {task:?}"); // NOTE: we use watchtower challenge init txid to calculate the height - let watchtower_challenge_txids: Vec = - vec![task.challenge_init_txid.0.to_string()]; + let watchtower_challenge_txids: Vec> = + vec![Some(task.challenge_init_txid.0.to_string())]; let pubkeys: Vec = vec![task.public_key.clone()]; ( task.id, @@ -281,8 +313,10 @@ async fn read_watchtower_challenge_details<'a>( ); } } - let challenge_txids: Vec = - watchtower_info.iter().map(|w| w.challenge_txid.0.to_string()).collect::>(); + let challenge_txids: Vec> = watchtower_info + .iter() + .map(|w| w.included.then(|| w.challenge_txid.0.to_string())) + .collect::>(); let challenge_public_keys: Vec = watchtower_info.iter().map(|w| w.public_key.clone()).collect::>(); let included_watchtowers: Vec = @@ -350,7 +384,7 @@ pub(crate) async fn fetch_on_demand_task( let mut largest_btc_block_height = 0; let btc_client = BTCClient::new(bitcoin_network, Some(esplora_url)); - for txid in &watchtower_challenge_txids { + for txid in watchtower_challenge_txids.iter().flatten() { let txid = Txid::from_str(txid)?; let block_height = match btc_client.get_tx_status(&txid).await { Ok(tx_status) => { @@ -741,18 +775,23 @@ pub(crate) async fn add_operator_task( graph_id: Uuid, operator_committed_blockhash: String, execution_layer_block_number: i64, - watchtower_challenge_txids: Vec, + watchtower_challenge_txids: Vec>, included_watchtowers: Vec, watchtower_challenge_init_txid: String, watchtower_challenge_pubkeys: Vec, ) -> anyhow::Result { + validate_indexed_watchtower_challenges( + &watchtower_challenge_txids, + &included_watchtowers, + &watchtower_challenge_pubkeys, + )?; let mut storage_processor = local_db.start_transaction().await?; let existing_watchtower_proof_task = storage_processor .find_watchtower_proof_by_instance_and_graph(&instance_id, &graph_id) .await?; tracing::info!("existing_watchtower_proof_task: {:?}", existing_watchtower_proof_task); - let timeout_watchtower_challenge_txids: Vec = watchtower_challenge_txids + let missing_watchtower_indices: Vec = watchtower_challenge_txids .iter() .enumerate() .filter(|(node_index, _)| { @@ -760,40 +799,44 @@ pub(crate) async fn add_operator_task( .iter() .any(|task| task.node_index as usize == *node_index) }) - .map(|(_, txid)| txid.clone()) + .map(|(node_index, _)| node_index) .collect(); - tracing::info!("timeout_watchtower_challenge_txids: {:?}", timeout_watchtower_challenge_txids); + tracing::info!("missing_watchtower_indices: {:?}", missing_watchtower_indices); // insert timeout watchtower proof task with failed state. - for txid in timeout_watchtower_challenge_txids.iter() { - let node_index = watchtower_challenge_txids.iter().position(|t| t == txid).unwrap() as i32; + for node_index in missing_watchtower_indices { + let stored_txid = watchtower_challenge_txids[node_index] + .as_deref() + .unwrap_or(&watchtower_challenge_init_txid); let task = WatchtowerProof { id: 1, instance_id, graph_id, challenge_init_txid: Txid::from_str(&watchtower_challenge_init_txid)?.into(), - challenge_txid: Txid::from_str(txid)?.into(), + challenge_txid: Txid::from_str(stored_txid)?.into(), proof_state: ProofState::Failed.to_i64(), created_at: current_time_secs(), updated_at: current_time_secs(), execution_layer_block_number, - public_key: watchtower_challenge_pubkeys[node_index as usize].clone(), // Note: this is a fake public key. - node_index, + public_key: watchtower_challenge_pubkeys[node_index].clone(), // Note: this is a fake public key. + node_index: node_index as i32, + included: included_watchtowers[node_index], ..Default::default() }; let affected = storage_processor.create_watchtower_proof(&task).await?; tracing::info!( - "mark timeout watchtower proof as failed, txid: {txid}, affected rows: {affected}", + "mark timeout watchtower proof as failed, node_index: {node_index}, affected rows: {affected}", ); } // update watchtower's challenge txid. for (i, txid) in watchtower_challenge_txids.iter().enumerate() { + let stored_txid = txid.as_deref().unwrap_or(&watchtower_challenge_init_txid); let affected = storage_processor .update_watchtower_proof_challenge_txid( &instance_id, &graph_id, i as i32, - txid, + stored_txid, included_watchtowers[i], ) .await?; @@ -818,6 +861,39 @@ pub(crate) async fn add_operator_task( Ok(affected_rows) } +fn validate_indexed_watchtower_challenges( + challenge_txids: &[Option], + included_watchtowers: &[bool], + challenge_pubkeys: &[String], +) -> anyhow::Result<()> { + if challenge_txids.len() != included_watchtowers.len() + || challenge_txids.len() != challenge_pubkeys.len() + { + anyhow::bail!( + "watchtower challenge txids, included bitmap, and public keys must have equal lengths" + ); + } + + let mut seen = HashSet::new(); + for (index, (txid, included)) in challenge_txids.iter().zip(included_watchtowers).enumerate() { + match (txid, included) { + (Some(txid), true) => { + Txid::from_str(txid)?; + if !seen.insert(txid) { + anyhow::bail!("duplicate watchtower challenge txid at index {index}"); + } + } + (None, false) => {} + (Some(_), false) | (None, true) => { + anyhow::bail!( + "watchtower challenge txid and included flag disagree at index {index}" + ); + } + } + } + Ok(()) +} + pub(crate) async fn find_operator_task( local_db: &LocalDB, instance_id: Uuid, @@ -919,8 +995,8 @@ mod tests { let graph_id = Uuid::from_str("00112233445566778899aabbccddeeff").unwrap(); let number = 9511055; let watchtower_challenge_txids = vec![ - "4506cf35cd70b3006fe3ce4a87ca1f9b0a76f348cfb529423e2d4c163c28d604".to_string(), - "f16286f143430a229c6d068798cd9ba751e83202de5045cc788aab227114cdb2".to_string(), + Some("4506cf35cd70b3006fe3ce4a87ca1f9b0a76f348cfb529423e2d4c163c28d604".to_string()), + None, ]; let operator_committed_blockhash = "7f7b4344adb1b8937ddb7124e4f8bba80ee9adf5e8119de76ca8736816bda246".to_string(); diff --git a/proof-builder-rpc/src/task/operator_proof.rs b/proof-builder-rpc/src/task/operator_proof.rs index 6717b063..bf42a9b2 100644 --- a/proof-builder-rpc/src/task/operator_proof.rs +++ b/proof-builder-rpc/src/task/operator_proof.rs @@ -11,6 +11,7 @@ use tokio::task::JoinHandle; use tokio_util::sync::CancellationToken; use tracing::info; use util::hex_parse; +use zkm_sdk::HashableKey; #[tracing::instrument(level = "info", skip(local_db, cancellation_token))] pub(crate) fn spawn_operator_proof_task( @@ -30,6 +31,7 @@ pub(crate) fn spawn_operator_proof_task( } let builder = OperatorProofBuilder::new(); + info!("operator vk hash {:?}", builder.vk().bytes32()); loop { tokio::select! { _ = tokio::time::sleep(Duration::from_secs(interval)) => { @@ -50,8 +52,24 @@ pub(crate) fn spawn_operator_proof_task( args.graph_id ); args.watchtower_challenge_init_txid = next_task.watchtower_challenge_init_txid.unwrap().clone(); - args.watchtower_challenge_txids = next_task.watchtower_challenge_txids.join(","); - args.watchtower_public_keys = next_task.watchtower_public_keys.join(","); + let included_challenges: Vec<_> = next_task + .watchtower_challenge_txids + .iter() + .zip(&next_task.watchtower_public_keys) + .filter_map(|(txid, public_key)| { + txid.as_ref().map(|txid| (txid.as_str(), public_key.as_str())) + }) + .collect(); + args.watchtower_challenge_txids = included_challenges + .iter() + .map(|(txid, _)| *txid) + .collect::>() + .join(","); + args.watchtower_public_keys = included_challenges + .iter() + .map(|(_, public_key)| *public_key) + .collect::>() + .join(","); // LE array to string, e.g. [1, 1, 1, 0] => 7 args.included_watchtowers = le_bits_to_u256(&next_task.included_watchtowers).to_string(); task_index = next_task.task_index; diff --git a/proof-builder-rpc/src/task/wrapper_proof.rs b/proof-builder-rpc/src/task/wrapper_proof.rs new file mode 100644 index 00000000..2078820c --- /dev/null +++ b/proof-builder-rpc/src/task/wrapper_proof.rs @@ -0,0 +1,259 @@ +use crate::task::current_time_secs; +use operator_wrapper_proof::OperatorWrapperProofBuilder; +use proof_builder::{ProofBuilder, ProofRequest}; +use std::path::Path; +use std::time::Duration; +use store::localdb::LocalDB; +use store::{ProofState, WrapperProof}; +use tokio::task::JoinHandle; +use tokio_util::sync::CancellationToken; +use zkm_sdk::HashableKey; + +#[tracing::instrument(level = "info", skip(local_db))] +pub(crate) async fn create_missing_wrapper_tasks( + local_db: &LocalDB, + genesis_sequencer_commit_txid: &str, +) -> anyhow::Result { + let mut storage_processor = local_db.acquire().await?; + let operator_proofs = storage_processor.find_proven_operator_proofs_without_wrapper(32).await?; + let mut created = 0u64; + + for operator_proof in operator_proofs { + let Some(operator_path_to_proof) = operator_proof.path_to_proof.clone() else { + continue; + }; + let operator_vk_hash = + read_utf8_sidecar(&format!("{operator_path_to_proof}.vk_hash.bin")).unwrap_or_default(); + let now = current_time_secs(); + let wrapper_proof = WrapperProof { + operator_proof_id: operator_proof.id, + instance_id: operator_proof.instance_id, + graph_id: operator_proof.graph_id, + execution_layer_block_number: operator_proof.execution_layer_block_number, + operator_path_to_proof, + operator_vk_hash, + genesis_sequencer_commit_txid: genesis_sequencer_commit_txid.to_string(), + operator_public_value_hex: operator_proof.public_value_hex.clone(), + proof_state: ProofState::New.to_i64(), + created_at: now, + updated_at: now, + ..Default::default() + }; + + match storage_processor.create_wrapper_proof(&wrapper_proof).await { + Ok(rows) => created += rows, + Err(err) if err.to_string().contains("UNIQUE") => { + tracing::debug!( + operator_proof_id = operator_proof.id, + "wrapper task already exists" + ); + } + Err(err) => return Err(err), + } + } + + Ok(created) +} + +#[tracing::instrument(level = "info", skip(local_db, cancellation_token))] +pub(crate) fn spawn_wrapper_proof_task( + args: operator_wrapper_proof::Args, + local_db: LocalDB, + interval: u64, + initial_delay: u64, + cancellation_token: CancellationToken, +) -> JoinHandle> { + let args = args.clone(); + tokio::spawn(async move { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(initial_delay)) => {} + _ = cancellation_token.cancelled() => { + anyhow::bail!("Wrapper proof generate task cancelled"); + } + } + + let builder = OperatorWrapperProofBuilder::new(); + tracing::info!("operator wrapper vk hash {:?}", builder.vk().bytes32()); + let scan_interval = if args.scan_interval == 0 { interval } else { args.scan_interval }; + loop { + tokio::select! { + _ = tokio::time::sleep(Duration::from_secs(scan_interval)) => { + if args.genesis_sequencer_commit_txid.is_empty() { + tracing::warn!("Wrapper proof task skipped: genesis_sequencer_commit_txid is empty"); + continue; + } + + let created = create_missing_wrapper_tasks( + &local_db, + &args.genesis_sequencer_commit_txid, + ).await?; + if created > 0 { + tracing::info!("created {created} wrapper proof tasks"); + } + + let wrapper_task = { + let mut storage_processor = local_db.acquire().await?; + storage_processor + .claim_next_wrapper_proof() + .await? + }; + let Some(wrapper_task) = wrapper_task else { + tracing::debug!("No wrapper proof task found"); + continue; + }; + + let output = wrapper_output_path(&args.output, &wrapper_task); + let ctx = ProofRequest::WrapperProofRequest { + operator_proof_id: wrapper_task.operator_proof_id, + operator_input_proof: wrapper_task.operator_path_to_proof.clone(), + graph_id: *wrapper_task.graph_id.as_bytes(), + genesis_sequencer_commit_txid: wrapper_task.genesis_sequencer_commit_txid.clone(), + output: output.clone(), + }; + + let proving_start = tokio::time::Instant::now(); + let result = builder.build_proof(&ctx).and_then(|(input, proof, cycles, proving_time)| { + let zkm_version = proof.zkm_version.clone(); + let (public_value_hex, proof_size) = + builder.save_proof(&ctx, &input, cycles, proof)?; + Ok((cycles, proving_time, public_value_hex, proof_size, zkm_version)) + }); + + match result { + Ok((cycles, proving_time, public_value_hex, proof_size, zkm_version)) => { + let proving_duration = proving_start.elapsed().as_secs_f32() * 1000.0; + let mut storage_processor = local_db.acquire().await?; + let affected = storage_processor + .update_wrapper_proof_success( + wrapper_task.id, + output, + public_value_hex, + proof_size as i64, + cycles as i64, + proving_time as i64, + &zkm_version, + ) + .await?; + tracing::info!( + wrapper_proof_id = wrapper_task.id, + operator_proof_id = wrapper_task.operator_proof_id, + proving_duration_ms = proving_duration as i64, + affected, + "wrapper proof generated" + ); + } + Err(err) => { + let mut storage_processor = local_db.acquire().await?; + storage_processor + .update_wrapper_proof_failure(wrapper_task.id) + .await?; + tracing::warn!( + wrapper_proof_id = wrapper_task.id, + operator_proof_id = wrapper_task.operator_proof_id, + error = %err, + "wrapper proof generation failed" + ); + } + } + } + _ = cancellation_token.cancelled() => { + anyhow::bail!("Wrapper proof generate task cancelled"); + } + } + } + }) +} + +fn wrapper_output_path(output: &str, wrapper_task: &WrapperProof) -> String { + let path = Path::new(output); + let output_dir = path + .parent() + .filter(|parent| !parent.as_os_str().is_empty()) + .unwrap_or_else(|| Path::new(".")); + output_dir + .join(format!( + "{}-{}.bin", + wrapper_task.graph_id.as_simple(), + wrapper_task.operator_proof_id + )) + .to_string_lossy() + .to_string() +} + +fn read_utf8_sidecar(path: &str) -> anyhow::Result { + Ok(String::from_utf8(std::fs::read(path)?)?) +} + +#[cfg(test)] +mod tests { + use super::*; + use store::{OperatorProof, create_local_db}; + use uuid::Uuid; + + #[tokio::test] + async fn create_missing_wrapper_tasks_is_unique_and_only_consumes_proven_operators() { + let local_db = create_local_db("sqlite::memory:").await; + let instance_id = Uuid::parse_str("00112233445566778899aabbccddeeff").unwrap(); + let graph_id = Uuid::parse_str("ffeeddccbbaa99887766554433221100").unwrap(); + let new_graph_id = Uuid::parse_str("11111111111111111111111111111111").unwrap(); + let now = current_time_secs(); + + { + let mut storage_processor = local_db.acquire().await.unwrap(); + storage_processor + .create_operator_proof(&OperatorProof { + instance_id, + graph_id, + execution_layer_block_number: 9511055, + path_to_proof: Some("operator-proof.bin".to_string()), + public_value_hex: Some("operator-public".to_string()), + proof_state: ProofState::Proven.to_i64(), + created_at: now, + updated_at: now, + operator_committed_blockhash: + "7f7b4344adb1b8937ddb7124e4f8bba80ee9adf5e8119de76ca8736816bda246" + .to_string(), + ..Default::default() + }) + .await + .unwrap(); + storage_processor + .create_operator_proof(&OperatorProof { + instance_id, + graph_id: new_graph_id, + execution_layer_block_number: 9511056, + path_to_proof: Some("operator-new.bin".to_string()), + proof_state: ProofState::New.to_i64(), + created_at: now, + updated_at: now, + operator_committed_blockhash: + "7f7b4344adb1b8937ddb7124e4f8bba80ee9adf5e8119de76ca8736816bda246" + .to_string(), + ..Default::default() + }) + .await + .unwrap(); + } + + let genesis_txid = "7f7b4344adb1b8937ddb7124e4f8bba80ee9adf5e8119de76ca8736816bda246"; + assert_eq!(create_missing_wrapper_tasks(&local_db, genesis_txid).await.unwrap(), 1); + assert_eq!(create_missing_wrapper_tasks(&local_db, genesis_txid).await.unwrap(), 0); + + let mut storage_processor = local_db.acquire().await.unwrap(); + let wrapper_proof = storage_processor + .find_wrapper_proof_by_instance_and_graph(&instance_id, &graph_id) + .await + .unwrap() + .expect("wrapper proof should exist"); + assert_eq!(wrapper_proof.operator_path_to_proof, "operator-proof.bin"); + assert_eq!(wrapper_proof.operator_public_value_hex.as_deref(), Some("operator-public")); + assert_eq!(wrapper_proof.proof_state, ProofState::New.to_i64()); + assert!( + storage_processor + .find_wrapper_proof_by_instance_and_graph(&instance_id, &new_graph_id) + .await + .unwrap() + .is_none() + ); + } +}