From 7bc208fa20a3a073c0b6d5fe0b7758b11fa38453 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sat, 8 Aug 2026 16:24:02 +0800 Subject: [PATCH 1/5] chore: bump poseidon 3.1.0, plonky2 1.5.5, crystals 4.1.0 Wormhole stack temporarily from qp-zk-circuits PR branch until crates.io release pins match. Single lockfile version of each new crate. --- quantus_sdk/rust/Cargo.lock | 67 ++++++++++++++++++------------------- quantus_sdk/rust/Cargo.toml | 20 +++++------ 2 files changed, 42 insertions(+), 45 deletions(-) diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock index 9307a4cd0..36c25ca70 100644 --- a/quantus_sdk/rust/Cargo.lock +++ b/quantus_sdk/rust/Cargo.lock @@ -151,9 +151,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.100" +version = "1.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a23eb6b1614318a8071c9b2521f36b424b2c83db5eb3a0fead4a6c0809af6e61" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" [[package]] name = "ark-bls12-377" @@ -545,6 +545,7 @@ dependencies = [ "rand_core 0.6.4", "serde", "unicode-normalization", + "zeroize", ] [[package]] @@ -2862,9 +2863,9 @@ dependencies = [ [[package]] name = "qp-plonky2" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff530eb14bc9389c7c5dad8fc3b1a90a6a5784f34daf74a34b963a562b73b0d0" +checksum = "8fd331d489a309f88e2d0e35a2b996932c7d92038b91ccc656a0a8e6b11b6977" dependencies = [ "ahash", "anyhow", @@ -2890,9 +2891,9 @@ dependencies = [ [[package]] name = "qp-plonky2-core" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7da3052436ca37dbb0540280e37846da1e826bd026745798853b6ffbb3b5e798" +checksum = "b81a3a9fce99f7bd45b8578f8d9b6a33507d34c2eb2c47c969b464db1ad601d3" dependencies = [ "ahash", "anyhow", @@ -2911,9 +2912,9 @@ dependencies = [ [[package]] name = "qp-plonky2-field" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "518a71f954222caa0d87fd331254a1651b5ee09d8e21881899478b0a1c2cc42e" +checksum = "1630d418ddce9feba18d3364596711d07074851757301350de313eef0a31af4f" dependencies = [ "anyhow", "itertools 0.13.0", @@ -2928,9 +2929,9 @@ dependencies = [ [[package]] name = "qp-plonky2-verifier" -version = "1.5.4" +version = "1.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f4d3c30ed97690849cf92eb0fdd1d4e6765ecc9e6be6b0a0e9b4dc4accb5145f" +checksum = "944da5dec21ee476d561f6c38caddf45e829f3cc5fccbc76f6ece03660378dbe" dependencies = [ "ahash", "anyhow", @@ -2965,30 +2966,29 @@ dependencies = [ [[package]] name = "qp-poseidon-core" -version = "3.0.2" +version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55e32db7b3a5d70086f82f198ecfc021f17cb7ec70a416512dba3488f4a116c1" +checksum = "5872607e25ea4ee5fb37e64bf1462168e1a36a4e719cdc8a105533c708253918" [[package]] name = "qp-rusty-crystals-dilithium" -version = "3.0.1" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0f6eb664c03395b3892f7f18d4f08bff0e256a967bb0146598a0f771d5e99ba" +checksum = "2facf5349ce149731e6d21dc338f847c78e4a3a65e92e086e2cee244e7cf0457" dependencies = [ "zeroize", ] [[package]] name = "qp-rusty-crystals-hdwallet" -version = "3.0.1" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9a9d975e8bfa2b6726f9668f32bd134a2556ccf65f4d336fed941658e4db67" +checksum = "40119eb0c9a085722330c15ce17b71ca3aaecf5fa5a44891a47c1c9ec2c3109c" dependencies = [ "bip39", "getrandom 0.2.17", "hex", "hex-literal", - "hmac", "qp-poseidon-core", "qp-rusty-crystals-dilithium", "serde", @@ -3001,9 +3001,8 @@ dependencies = [ [[package]] name = "qp-wormhole-aggregator" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6c3fb3ef7ce2fe519cab6f10e206bbbace8e3c7739318e1cfbe1eb16779a51d" +version = "4.1.0" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" dependencies = [ "anyhow", "hex", @@ -3019,22 +3018,21 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da895295e488eae1d5fbf63a94ccf388e580595b5607ef51b3e057ac0bef0539" +version = "4.1.0" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" dependencies = [ "anyhow", "hex", "qp-plonky2", "qp-wormhole-inputs", "qp-zk-circuits-common", + "zeroize", ] [[package]] name = "qp-wormhole-circuit-builder" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a65d2f36b1f6903679180d0a2cfb5a4147960229b8b74034779f0a9f4644930d" +version = "4.1.0" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" dependencies = [ "anyhow", "clap", @@ -3042,22 +3040,21 @@ dependencies = [ "qp-wormhole-aggregator", "qp-wormhole-circuit", "qp-zk-circuits-common", + "rand 0.8.6", ] [[package]] name = "qp-wormhole-inputs" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06b7675995701fc2c229846055fcfdb860ea1118c46437357f3cf6f7c15b73aa" +version = "4.1.0" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" dependencies = [ "anyhow", ] [[package]] name = "qp-wormhole-prover" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f105bc62dbab0b4573ef43d4a7c87e6482ea2b7c8b99f18a1b3240c22956fb7f" +version = "4.1.0" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" dependencies = [ "anyhow", "qp-plonky2", @@ -3067,9 +3064,8 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ac5d2eb980e702b7db1458601fbb7a2d5fc86c6713c5c324ecc0e28061fa955" +version = "4.1.0" +source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" dependencies = [ "anyhow", "qp-plonky2", @@ -3078,6 +3074,7 @@ dependencies = [ "qp-wormhole-inputs", "rand 0.8.6", "serde", + "serde_json", ] [[package]] diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml index b30406b61..3914ecd20 100644 --- a/quantus_sdk/rust/Cargo.toml +++ b/quantus_sdk/rust/Cargo.toml @@ -8,9 +8,9 @@ crate-type = ["cdylib", "staticlib", "rlib"] [dependencies] # NOTE: Quantus chain dependencies. -qp-poseidon-core = "3.0.2" -qp-rusty-crystals-dilithium = { version = "3.0.1", default-features = false } -qp-rusty-crystals-hdwallet = { version = "3.0.1" } +qp-poseidon-core = "3.1.0" +qp-rusty-crystals-dilithium = { version = "4.1.0", default-features = false } +qp-rusty-crystals-hdwallet = { version = "4.1.0" } flutter_rust_bridge = "=2.12.0" hex = "0.4.3" @@ -22,13 +22,13 @@ quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = # ZK proof generation (aligned with quantus-cli / chain) anyhow = "1.0" blake3 = "1.8" -qp-plonky2 = { version = "1.5.4", default-features = false, features = ["rand", "std"] } -qp-wormhole-circuit = { version = "3.1.0", default-features = false, features = ["std"] } -qp-wormhole-prover = { version = "3.1.0", default-features = false, features = ["std"] } -qp-wormhole-aggregator = { version = "3.1.0", default-features = false, features = ["std"] } -qp-wormhole-inputs = { version = "3.1.0", default-features = false, features = ["std"] } -qp-zk-circuits-common = { version = "3.1.0", default-features = false, features = ["std"] } -qp-wormhole-circuit-builder = { version = "3.1.0" } +qp-plonky2 = { version = "1.5.5", default-features = false, features = ["rand", "std"] } +qp-wormhole-circuit = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } +qp-wormhole-prover = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } +qp-wormhole-aggregator = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } +qp-wormhole-inputs = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } +qp-zk-circuits-common = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } +qp-wormhole-circuit-builder = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08" } # Circuit generation is CPU-intensive; without this it takes ~10min instead of ~30s. [profile.dev.build-override] From e3cfac3b1f6ba9216f24ff938f13089e7a737d95 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Sat, 8 Aug 2026 19:35:53 +0800 Subject: [PATCH 2/5] chore: use crates.io wormhole 4.2.0 (drop zk-circuits git) Replace temporary git pins with published qp-wormhole-* / qp-zk-circuits-common 4.2.0. --- quantus_sdk/rust/Cargo.lock | 214 ++++-------------------------------- quantus_sdk/rust/Cargo.toml | 12 +- 2 files changed, 26 insertions(+), 200 deletions(-) diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock index 36c25ca70..c0173c6c1 100644 --- a/quantus_sdk/rust/Cargo.lock +++ b/quantus_sdk/rust/Cargo.lock @@ -1884,15 +1884,6 @@ 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" @@ -2366,122 +2357,6 @@ dependencies = [ "log", ] -[[package]] -name = "p3-dft" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3b2764a3982d22d62aa933c8de6f9d71d8a474c9110b69e675dea1887bdeffc" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-matrix", - "p3-maybe-rayon", - "p3-util", - "tracing", -] - -[[package]] -name = "p3-field" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc13a73509fe09c67b339951ca8d4cc6e61c9bf08c130dbc90dda52452918cc2" -dependencies = [ - "itertools 0.14.0", - "num-bigint", - "p3-maybe-rayon", - "p3-util", - "paste", - "rand 0.9.5", - "serde", - "tracing", -] - -[[package]] -name = "p3-goldilocks" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552849f6309ffde34af0d31aa9a2d0a549cb0ec138d9792bfbf4a17800742362" -dependencies = [ - "num-bigint", - "p3-dft", - "p3-field", - "p3-mds", - "p3-poseidon2", - "p3-symmetric", - "p3-util", - "paste", - "rand 0.9.5", - "serde", -] - -[[package]] -name = "p3-matrix" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e1e9f69c2fe15768b3ceb2915edb88c47398aa22c485d8163deab2a47fe194" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "p3-maybe-rayon", - "p3-util", - "rand 0.9.5", - "serde", - "tracing", - "transpose", -] - -[[package]] -name = "p3-maybe-rayon" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f765046b763d046728b3246b690f81dfa7ccd7523b7a1582c74f616fbce6a0" - -[[package]] -name = "p3-mds" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c90541c6056712daf2ee69ec328db8b5605ae8dbafe60226c8eb75eaac0e1f9" -dependencies = [ - "p3-dft", - "p3-field", - "p3-symmetric", - "p3-util", - "rand 0.9.5", -] - -[[package]] -name = "p3-poseidon2" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88e9f053f120a78ad27e9c1991a0ea547777328ca24025c42364d6ee2667d59a" -dependencies = [ - "p3-field", - "p3-mds", - "p3-symmetric", - "p3-util", - "rand 0.9.5", -] - -[[package]] -name = "p3-symmetric" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72d5db8f05a26d706dfd8aaf7aa4272ca4f3e7a075db897ec7108f24fad78759" -dependencies = [ - "itertools 0.14.0", - "p3-field", - "serde", -] - -[[package]] -name = "p3-util" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dfee67245d9ce78a15176728da2280032f0a84b5819a39a953e7ec03cfd9bd7" -dependencies = [ - "serde", -] - [[package]] name = "parity-scale-codec" version = "3.7.5" @@ -2951,19 +2826,6 @@ dependencies = [ "unroll", ] -[[package]] -name = "qp-poseidon-constants" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300d8b01e4a492b202e739ab5f73215b261905911020cc0f74a22109365e3bd7" -dependencies = [ - "p3-field", - "p3-goldilocks", - "p3-poseidon2", - "rand 0.9.5", - "rand_chacha 0.9.0", -] - [[package]] name = "qp-poseidon-core" version = "3.1.0" @@ -3001,8 +2863,9 @@ dependencies = [ [[package]] name = "qp-wormhole-aggregator" -version = "4.1.0" -source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d36c1e817cba09384f092f6f1cda8016cc39637c878c1b80788d8c7d5bd75b40" dependencies = [ "anyhow", "hex", @@ -3018,8 +2881,9 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit" -version = "4.1.0" -source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418648e0835bb47c7e6fa908bce246e9e9fce042f857b15e652b2591ff869e76" dependencies = [ "anyhow", "hex", @@ -3031,8 +2895,9 @@ dependencies = [ [[package]] name = "qp-wormhole-circuit-builder" -version = "4.1.0" -source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0716238af4fa0c0efdf06f30ccbff90534c0d9e700c3d90aa9d3be79fb10d2fe" dependencies = [ "anyhow", "clap", @@ -3045,16 +2910,18 @@ dependencies = [ [[package]] name = "qp-wormhole-inputs" -version = "4.1.0" -source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9385fee220d58083a3c833ff9ee87c2346f77c7e869f35bbe12ecd8dad3d076f" dependencies = [ "anyhow", ] [[package]] name = "qp-wormhole-prover" -version = "4.1.0" -source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c90421378b57eeb08f3e60f426aa861b2ad741942dcd795c43b5665171026a" dependencies = [ "anyhow", "qp-plonky2", @@ -3064,12 +2931,12 @@ dependencies = [ [[package]] name = "qp-zk-circuits-common" -version = "4.1.0" -source = "git+https://github.com/Quantus-Network/qp-zk-circuits?branch=chore%2Fbump-crypto-deps-2026-08#14d81950e4c091fc9a27781b4c3414150a619a93" +version = "4.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78477b533829319a41b946bf8bdc1bc778f9ed1806f156eb584aa3a6ff764b3b" dependencies = [ "anyhow", "qp-plonky2", - "qp-poseidon-constants", "qp-poseidon-core", "qp-wormhole-inputs", "rand 0.8.6", @@ -3122,19 +2989,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ca0ecfa931c29007047d1bc58e623ab12e5590e8c7cc53200d5202b69266d8a" dependencies = [ "libc", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" -dependencies = [ - "rand_core 0.9.5", -] - [[package]] name = "rand" version = "0.10.1" @@ -3156,16 +3014,6 @@ dependencies = [ "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" @@ -3175,12 +3023,6 @@ 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" - [[package]] name = "rand_core" version = "0.10.1" @@ -4015,12 +3857,6 @@ 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" @@ -4439,16 +4275,6 @@ dependencies = [ "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 = "trie-db" version = "0.31.0" @@ -4614,7 +4440,7 @@ dependencies = [ "arrayref", "digest 0.10.7", "rand 0.8.6", - "rand_chacha 0.3.1", + "rand_chacha", "rand_core 0.6.4", "sha2 0.10.9", "sha3", diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml index 3914ecd20..a6fa8ff06 100644 --- a/quantus_sdk/rust/Cargo.toml +++ b/quantus_sdk/rust/Cargo.toml @@ -23,12 +23,12 @@ quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = anyhow = "1.0" blake3 = "1.8" qp-plonky2 = { version = "1.5.5", default-features = false, features = ["rand", "std"] } -qp-wormhole-circuit = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } -qp-wormhole-prover = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } -qp-wormhole-aggregator = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } -qp-wormhole-inputs = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } -qp-zk-circuits-common = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08", default-features = false, features = ["std"] } -qp-wormhole-circuit-builder = { git = "https://github.com/Quantus-Network/qp-zk-circuits", branch = "chore/bump-crypto-deps-2026-08" } +qp-wormhole-circuit = { version = "4.2.0", default-features = false, features = ["std"] } +qp-wormhole-prover = { version = "4.2.0", default-features = false, features = ["std"] } +qp-wormhole-aggregator = { version = "4.2.0", default-features = false, features = ["std"] } +qp-wormhole-inputs = { version = "4.2.0", default-features = false, features = ["std"] } +qp-zk-circuits-common = { version = "4.2.0", default-features = false, features = ["std"] } +qp-wormhole-circuit-builder = { version = "4.2.0" } # Circuit generation is CPU-intensive; without this it takes ~10min instead of ~30s. [profile.dev.build-override] From f06aafdb8faf36816e5ddcacd1302ae8208fe236 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Mon, 10 Aug 2026 13:13:11 +0800 Subject: [PATCH 3/5] fix: adapt rust SDK to bumped crypto/wormhole APIs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - dilithium 4.1.0: Keypair fields are private (use accessors and Keypair::from_parts), generate() takes &mut SensitiveBytes32, mnemonic_to_seed writes into a caller-owned SensitiveBytes64, sign() hedge is Option<&SensitiveBytes32> - hdwallet 4.1.0: WormholePair fields are private (use accessors) - wormhole 4.2.0: PrivateCircuitInputs.secret is Secret, hash_node_presorted returns Result - artifact check: drop private_batch_prover.bin from required files — 4.2.x emits no prover binaries, so the check never passed and forced full circuit regeneration on every call --- quantus_sdk/rust/src/api/crypto.rs | 39 +++++++++++++++------------- quantus_sdk/rust/src/api/wormhole.rs | 22 ++++++++-------- 2 files changed, 32 insertions(+), 29 deletions(-) diff --git a/quantus_sdk/rust/src/api/crypto.rs b/quantus_sdk/rust/src/api/crypto.rs index 4271d5047..af7440ca1 100644 --- a/quantus_sdk/rust/src/api/crypto.rs +++ b/quantus_sdk/rust/src/api/crypto.rs @@ -25,18 +25,17 @@ pub struct Keypair { impl Keypair { fn from_ml_dsa(ml_dsa_keypair: MlDsaKeypair) -> Self { Keypair { - public_key: ml_dsa_keypair.public.to_bytes().to_vec(), - secret_key: ml_dsa_keypair.secret.to_bytes().to_vec(), + public_key: ml_dsa_keypair.public().to_bytes().to_vec(), + secret_key: ml_dsa_keypair.secret().to_bytes().to_vec(), } } fn to_ml_dsa(&self) -> MlDsaKeypair { - MlDsaKeypair { - secret: ml_dsa_87::SecretKey::from_bytes(&self.secret_key) - .expect("Failed to parse secret key"), - public: ml_dsa_87::PublicKey::from_bytes(&self.public_key) - .expect("Failed to parse public key"), - } + let secret = + ml_dsa_87::SecretKey::from_bytes(&self.secret_key).expect("Failed to parse secret key"); + let public = + ml_dsa_87::PublicKey::from_bytes(&self.public_key).expect("Failed to parse public key"); + MlDsaKeypair::from_parts(secret, public).expect("Keypair halves do not correspond") } } @@ -66,11 +65,11 @@ pub fn ss58_to_account_id(s: &str) -> Result, String> { #[flutter_rust_bridge::frb(sync)] pub fn generate_keypair(mnemonic_str: String) -> Result { - let mut seed64 = mnemonic_to_seed(mnemonic_str, None)?; - let mut seed_for_pair = [0u8; 32]; - seed_for_pair.copy_from_slice(&seed64[..32]); - let _ = SensitiveBytes64::from(&mut seed64); - let ml_dsa_keypair = MlDsaKeypair::generate(SensitiveBytes32::new(&mut seed_for_pair)); + let mut seed64 = SensitiveBytes64::zeroed(); + mnemonic_to_seed(mnemonic_str, None, &mut seed64)?; + let mut entropy = SensitiveBytes32::zeroed(); + entropy.as_mut_bytes().copy_from_slice(&seed64.as_bytes()[..32]); + let ml_dsa_keypair = MlDsaKeypair::generate(&mut entropy); Ok(Keypair::from_ml_dsa(ml_dsa_keypair)) } @@ -89,11 +88,11 @@ pub struct WormholeResult { #[flutter_rust_bridge::frb(sync)] pub fn derive_wormhole(mnemonic_str: String, path: &str) -> Result { let pair = derive_wormhole_from_mnemonic(&mnemonic_str, None, path)?; - let account = AccountId32::new(pair.address); + let account = AccountId32::new(*pair.address()); Ok(WormholeResult { address: account.to_ss58check(), - first_hash: pair.first_hash.to_vec(), - secret: pair.secret.as_bytes().to_vec(), + first_hash: pair.first_hash().to_vec(), + secret: pair.secret().as_bytes().to_vec(), }) } @@ -121,14 +120,18 @@ pub fn first_hash_to_address(first_hash_hex: String) -> Result { #[flutter_rust_bridge::frb(sync)] pub fn generate_keypair_from_seed(seed: Vec) -> Keypair { let mut seed_array: [u8; 32] = seed.try_into().expect("Seed must be 32 bytes"); - let ml_dsa_keypair = MlDsaKeypair::generate(SensitiveBytes32::new(&mut seed_array)); + let mut entropy = SensitiveBytes32::new(&mut seed_array); + let ml_dsa_keypair = MlDsaKeypair::generate(&mut entropy); Keypair::from_ml_dsa(ml_dsa_keypair) } #[flutter_rust_bridge::frb(sync)] pub fn sign_message(keypair: &Keypair, message: &[u8], entropy: Option<[u8; 32]>) -> Vec { let ml_dsa_keypair = keypair.to_ml_dsa(); - let signature = ml_dsa_keypair.sign(message, None, entropy) + let mut entropy = entropy; + let hedge = entropy.as_mut().map(SensitiveBytes32::new); + let signature = ml_dsa_keypair + .sign(message, None, hedge.as_ref()) .expect("Signing failed"); signature.to_vec() } diff --git a/quantus_sdk/rust/src/api/wormhole.rs b/quantus_sdk/rust/src/api/wormhole.rs index 501e28abd..045d0b1cb 100644 --- a/quantus_sdk/rust/src/api/wormhole.rs +++ b/quantus_sdk/rust/src/api/wormhole.rs @@ -1,6 +1,7 @@ use qp_wormhole_circuit::{ inputs::{CircuitInputs, PrivateCircuitInputs}, nullifier::Nullifier, + sensitive::Secret, }; use qp_wormhole_inputs::{BytesDigest, PublicCircuitInputs}; use qp_zk_circuits_common::{ @@ -201,7 +202,8 @@ pub fn compute_merkle_positions( } } - current_hash = hash_node_presorted(&all_four); + current_hash = hash_node_presorted(&all_four) + .map_err(|e| format!("Failed to hash merkle node at level: {}", e))?; } Ok(MerkleProcessed { @@ -242,21 +244,19 @@ pub fn ensure_circuit_binaries(bins_dir: String) -> Result { } fn all_required_files_exist(dir: &Path) -> bool { - // Must match the artifacts produced by `generate_all_circuit_binaries` and - // consumed by `PrivateBatchProver::new_from_binaries_dir` in qp-wormhole-* - // 3.1.x. Two things changed vs 3.0.x: the leaf circuit no longer emits a - // `prover.bin` (the leaf prover is always built from source), and the - // aggregation artifacts were renamed `aggregated_*` -> `private_batch_*`. - // If this list still names the old files, a stale 3.0.x circuits directory - // satisfies the check, generation is skipped, and proving later fails with - // "Failed to read aggregated prover file .../private_batch_prover.bin". + // Must match the artifacts produced by `generate_all_circuit_binaries` in + // qp-wormhole-* 4.2.x. No circuit emits a prover binary anymore (provers + // always rebuild their circuits from source), so `private_batch_prover.bin` + // is gone; `PrivateBatchProver::new_from_binaries_dir` now loads only + // common.bin, verifier.bin, dummy_proof.bin and config.json. Keeping the + // removed prover file in this list makes the check never pass, forcing a + // full (expensive) circuit regeneration on every call. const REQUIRED: &[&str] = &[ "common.bin", "verifier.bin", "dummy_proof.bin", "private_batch_common.bin", "private_batch_verifier.bin", - "private_batch_prover.bin", "config.json", ]; REQUIRED.iter().all(|f| dir.join(f).exists()) @@ -312,7 +312,7 @@ pub fn generate_proof( } let private = PrivateCircuitInputs { - secret: secret_digest, + secret: Secret::from(secret_digest), transfer_count: input.transfer_count, unspendable_account: unspendable_bytes, parent_hash: vec_to_digest(&input.parent_hash, "parent_hash")?, From f3af25d78974ba63c8195b1f125631824cba6a15 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 11 Aug 2026 12:01:00 +0800 Subject: [PATCH 4/5] versioned prover files added --- .../lib/src/services/circuit_manager.dart | 4 - quantus_sdk/rust/Cargo.lock | 5 +- quantus_sdk/rust/Cargo.toml | 5 +- quantus_sdk/rust/src/api/wormhole.rs | 262 +++++++++++++++++- 4 files changed, 259 insertions(+), 17 deletions(-) diff --git a/quantus_sdk/lib/src/services/circuit_manager.dart b/quantus_sdk/lib/src/services/circuit_manager.dart index 092997544..5c9488e19 100644 --- a/quantus_sdk/lib/src/services/circuit_manager.dart +++ b/quantus_sdk/lib/src/services/circuit_manager.dart @@ -33,16 +33,12 @@ class CircuitStatus { /// Circuit binaries are bundled as SDK assets and extracted to the app's /// support directory on first use. Rust FFI requires file paths. class CircuitManager { - // Must match the artifacts generated by the Rust `ensure_circuit_binaries` - // FFI (qp-wormhole-* 3.1.x): the leaf circuit no longer emits `prover.bin`, - // and the aggregation artifacts are `private_batch_*`, not `aggregated_*`. static const List requiredFiles = [ 'common.bin', 'verifier.bin', 'dummy_proof.bin', 'private_batch_common.bin', 'private_batch_verifier.bin', - 'private_batch_prover.bin', 'config.json', ]; diff --git a/quantus_sdk/rust/Cargo.lock b/quantus_sdk/rust/Cargo.lock index c0173c6c1..cd1b9c756 100644 --- a/quantus_sdk/rust/Cargo.lock +++ b/quantus_sdk/rust/Cargo.lock @@ -2947,7 +2947,7 @@ dependencies = [ [[package]] name = "quantus_ur" version = "0.1.0" -source = "git+https://github.com/Quantus-Network/quantus_ur.git?tag=1.4.0#f3929da023e9c8081b8e9944b5b665871980fa79" +source = "git+https://github.com/Quantus-Network/quantus_ur.git?tag=1.6.0#0b9bcc586a6ccb845c4d476ffa0880b0478b284a" dependencies = [ "hex", "keystone-ur", @@ -3154,6 +3154,7 @@ dependencies = [ "quantus_ur", "sp-core", "sp-runtime", + "tempfile", ] [[package]] @@ -3965,7 +3966,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ "fastrand", - "getrandom 0.4.3", + "getrandom 0.3.4", "once_cell", "rustix 1.1.4", "windows-sys 0.61.2", diff --git a/quantus_sdk/rust/Cargo.toml b/quantus_sdk/rust/Cargo.toml index a6fa8ff06..0446228dc 100644 --- a/quantus_sdk/rust/Cargo.toml +++ b/quantus_sdk/rust/Cargo.toml @@ -17,7 +17,7 @@ hex = "0.4.3" sp-core = { version = "43.0.0", default-features = true } parity-scale-codec = { version = "3.7.5", default-features = false, features = ["std"] } sp-runtime = { version = "48.0.0", default-features = false, features = ["std"] } -quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = "1.4.0" } +quantus_ur = { git = "https://github.com/Quantus-Network/quantus_ur.git", tag = "1.6.0" } # ZK proof generation (aligned with quantus-cli / chain) anyhow = "1.0" @@ -30,6 +30,9 @@ qp-wormhole-inputs = { version = "4.2.0", default-features = false, features = [ qp-zk-circuits-common = { version = "4.2.0", default-features = false, features = ["std"] } qp-wormhole-circuit-builder = { version = "4.2.0" } +[dev-dependencies] +tempfile = "3" + # Circuit generation is CPU-intensive; without this it takes ~10min instead of ~30s. [profile.dev.build-override] opt-level = 3 diff --git a/quantus_sdk/rust/src/api/wormhole.rs b/quantus_sdk/rust/src/api/wormhole.rs index 045d0b1cb..d96b9d537 100644 --- a/quantus_sdk/rust/src/api/wormhole.rs +++ b/quantus_sdk/rust/src/api/wormhole.rs @@ -8,7 +8,57 @@ use qp_zk_circuits_common::{ utils::digest_to_bytes, zk_merkle::{hash_node_presorted, SIBLINGS_PER_LEVEL}, }; -use std::path::Path; +use std::path::{Path, PathBuf}; + +pub const ZK_CIRCUITS_VERSION: &str = "4.2.0"; + +fn versioned_bins_dir(base: &Path) -> PathBuf { + base.join(format!("v{}", ZK_CIRCUITS_VERSION)) +} + +const LEGACY_FILES: &[&str] = &[ + "common.bin", + "verifier.bin", + "dummy_proof.bin", + "private_batch_common.bin", + "private_batch_verifier.bin", + "private_batch_prover.bin", + "prover.bin", + "config.json", +]; + +fn cleanup_stale_circuit_dirs(base: &Path) { + for name in LEGACY_FILES { + let path = base.join(name); + if path.exists() { + eprintln!("[circuits] removing legacy file: {}", path.display()); + if let Err(e) = std::fs::remove_file(&path) { + eprintln!("[circuits] failed to remove {}: {}", path.display(), e); + } + } + } + + let current = format!("v{}", ZK_CIRCUITS_VERSION); + let entries = match std::fs::read_dir(base) { + Ok(e) => e, + Err(_) => return, + }; + for entry in entries.flatten() { + let name = entry.file_name(); + let name_str = name.to_string_lossy(); + if name_str.starts_with('v') && name_str != current && entry.path().is_dir() { + eprintln!("[circuits] removing old version dir: {}", entry.path().display()); + if let Err(e) = std::fs::remove_dir_all(entry.path()) { + eprintln!("[circuits] failed to remove {}: {}", entry.path().display(), e); + } + } + } +} + +#[flutter_rust_bridge::frb(sync)] +pub fn zk_circuits_version() -> String { + ZK_CIRCUITS_VERSION.to_string() +} #[flutter_rust_bridge::frb(sync)] pub fn compute_address_hash_hex(raw_address: Vec) -> Result { @@ -213,15 +263,19 @@ pub fn compute_merkle_positions( } pub fn ensure_circuit_binaries(bins_dir: String) -> Result { - let dir = Path::new(&bins_dir); - std::fs::create_dir_all(dir) + let base = Path::new(&bins_dir); + std::fs::create_dir_all(base) .map_err(|e| format!("Failed to create bins directory {}: {}", bins_dir, e))?; + cleanup_stale_circuit_dirs(base); + + let dir = versioned_bins_dir(base); + std::fs::create_dir_all(&dir) + .map_err(|e| format!("Failed to create versioned bins directory {}: {}", dir.display(), e))?; + let config_path = dir.join("config.json"); - if all_required_files_exist(dir) { - // Reuse existing binaries only if they were generated with the current - // batch size; otherwise regenerate (e.g. after the chain's 16 -> 7 change). - match qp_wormhole_circuit_builder::CircuitBinsConfig::load(dir) { + if all_required_files_exist(&dir) { + match qp_wormhole_circuit_builder::CircuitBinsConfig::load(&dir) { Ok(config) if config.num_leaf_proofs == DEFAULT_NUM_LEAF_PROOFS => { return std::fs::read_to_string(&config_path) .map_err(|e| format!("Failed to read config.json: {}", e)); @@ -231,7 +285,7 @@ pub fn ensure_circuit_binaries(bins_dir: String) -> Result { } qp_wormhole_circuit_builder::generate_all_circuit_binaries( - dir, + &dir, true, DEFAULT_NUM_LEAF_PROOFS, None, @@ -360,7 +414,7 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res use qp_wormhole_aggregator::private_batch::prover::PrivateBatchProver; use qp_zk_circuits_common::circuit::{C, D, F}; - let bins_path = Path::new(&bins_dir); + let bins_path = versioned_bins_dir(Path::new(&bins_dir)); let leaf_prover = qp_wormhole_prover::build_fresh(); let common_data = &leaf_prover.circuit_data.common; @@ -374,7 +428,7 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res }) .collect::>()?; - let prover = PrivateBatchProver::new_from_binaries_dir(bins_path) + let prover = PrivateBatchProver::new_from_binaries_dir(&bins_path) .map_err(|e| format!("Failed to create private-batch prover: {}", e))?; let aggregated = prover @@ -383,3 +437,191 @@ pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Res Ok(aggregated.to_bytes()) } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn versioned_bins_dir_appends_version() { + let base = Path::new("/tmp/circuits"); + let result = versioned_bins_dir(base); + assert_eq!(result, PathBuf::from(format!("/tmp/circuits/v{}", ZK_CIRCUITS_VERSION))); + } + + #[test] + fn zk_circuits_version_matches_const() { + assert_eq!(zk_circuits_version(), ZK_CIRCUITS_VERSION); + } + + #[test] + fn cleanup_removes_legacy_files() { + let tmp = tempfile::tempdir().unwrap(); + let base = tmp.path(); + for name in LEGACY_FILES { + std::fs::write(base.join(name), b"x").unwrap(); + } + cleanup_stale_circuit_dirs(base); + for name in LEGACY_FILES { + assert!(!base.join(name).exists(), "{} should have been removed", name); + } + } + + #[test] + fn cleanup_removes_old_version_dirs() { + let tmp = tempfile::tempdir().unwrap(); + let base = tmp.path(); + std::fs::create_dir(base.join("v1.0.0")).unwrap(); + std::fs::create_dir(base.join("v3.9.0")).unwrap(); + let current = format!("v{}", ZK_CIRCUITS_VERSION); + std::fs::create_dir(base.join(¤t)).unwrap(); + + cleanup_stale_circuit_dirs(base); + + assert!(!base.join("v1.0.0").exists()); + assert!(!base.join("v3.9.0").exists()); + assert!(base.join(¤t).exists(), "current version dir must survive"); + } + + #[test] + fn cleanup_ignores_non_version_entries() { + let tmp = tempfile::tempdir().unwrap(); + let base = tmp.path(); + std::fs::create_dir(base.join("other_dir")).unwrap(); + std::fs::write(base.join("notes.txt"), b"keep").unwrap(); + + cleanup_stale_circuit_dirs(base); + + assert!(base.join("other_dir").exists()); + assert!(base.join("notes.txt").exists()); + } + + #[test] + fn cleanup_on_empty_dir_is_noop() { + let tmp = tempfile::tempdir().unwrap(); + cleanup_stale_circuit_dirs(tmp.path()); + } + + #[test] + fn all_required_files_exist_true_when_complete() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path(); + for name in &["common.bin", "verifier.bin", "dummy_proof.bin", + "private_batch_common.bin", "private_batch_verifier.bin", "config.json"] { + std::fs::write(dir.join(name), b"x").unwrap(); + } + assert!(all_required_files_exist(dir)); + } + + #[test] + fn all_required_files_exist_false_when_missing() { + let tmp = tempfile::tempdir().unwrap(); + let dir = tmp.path(); + std::fs::write(dir.join("common.bin"), b"x").unwrap(); + assert!(!all_required_files_exist(dir)); + } + + #[test] + fn compute_address_hash_hex_deterministic() { + let addr = vec![42u8; 32]; + let h1 = compute_address_hash_hex(addr.clone()).unwrap(); + let h2 = compute_address_hash_hex(addr).unwrap(); + assert_eq!(h1, h2); + assert_eq!(h1.len(), 64); + } + + #[test] + fn compute_address_hash_hex_rejects_wrong_length() { + assert!(compute_address_hash_hex(vec![0u8; 31]).is_err()); + assert!(compute_address_hash_hex(vec![0u8; 33]).is_err()); + } + + #[test] + fn output_amount_with_10bps_fee() { + let out = wormhole_compute_output_amount(10_000, 10).unwrap(); + assert_eq!(out, 9990); + } + + #[test] + fn output_amount_zero_fee() { + let out = wormhole_compute_output_amount(10_000, 0).unwrap(); + assert_eq!(out, 10_000); + } + + #[test] + fn output_amount_full_fee() { + let out = wormhole_compute_output_amount(10_000, 10_000).unwrap(); + assert_eq!(out, 0); + } + + #[test] + fn output_amount_rejects_excessive_fee() { + assert!(wormhole_compute_output_amount(1, 10_001).is_err()); + } + + #[test] + fn decode_leaf_amount_valid() { + let mut data = vec![0u8; 60]; + let amount: u128 = 500 * SCALE_DOWN_FACTOR; + data[44..60].copy_from_slice(&amount.to_le_bytes()); + assert_eq!(decode_leaf_amount(data).unwrap(), 500); + } + + #[test] + fn decode_leaf_amount_too_short() { + assert!(decode_leaf_amount(vec![0u8; 59]).is_err()); + } + + #[test] + fn decode_leaf_transfer_count_valid() { + let mut data = vec![0u8; 40]; + data[32..40].copy_from_slice(&77u64.to_le_bytes()); + assert_eq!(decode_leaf_transfer_count(data).unwrap(), 77); + } + + #[test] + fn decode_leaf_transfer_count_too_short() { + assert!(decode_leaf_transfer_count(vec![0u8; 39]).is_err()); + } + + #[test] + fn decode_leaf_to_account_valid() { + let mut data = vec![0u8; 64]; + data[0..32].copy_from_slice(&[0xAB; 32]); + assert_eq!(decode_leaf_to_account(data).unwrap(), vec![0xAB; 32]); + } + + #[test] + fn decode_leaf_to_account_too_short() { + assert!(decode_leaf_to_account(vec![0u8; 31]).is_err()); + } + + #[test] + fn compute_nullifier_deterministic() { + let secret = vec![1u8; 32]; + let n1 = compute_nullifier(secret.clone(), 0).unwrap(); + let n2 = compute_nullifier(secret.clone(), 0).unwrap(); + assert_eq!(n1, n2); + let n3 = compute_nullifier(secret, 1).unwrap(); + assert_ne!(n1, n3); + } + + #[test] + fn compute_nullifier_rejects_bad_length() { + assert!(compute_nullifier(vec![0u8; 16], 0).is_err()); + } + + #[test] + fn compute_wormhole_address_deterministic() { + let secret = vec![7u8; 32]; + let a1 = compute_wormhole_address(secret.clone()).unwrap(); + let a2 = compute_wormhole_address(secret).unwrap(); + assert_eq!(a1, a2); + assert_eq!(a1.len(), 32); + } + + #[test] + fn compute_wormhole_address_rejects_bad_length() { + assert!(compute_wormhole_address(vec![0u8; 10]).is_err()); + } +} From 77093ae42581d3bb04edfa01f0c14ed253aa7788 Mon Sep 17 00:00:00 2001 From: Nikolaus Heger Date: Tue, 11 Aug 2026 12:29:28 +0800 Subject: [PATCH 5/5] fix: remove full 3.0/3.1 circuit artifact set on upgrade LEGACY_FILES omitted dummy_private_batch_proof.bin and aggregated_*.bin, so wallet upgrades left large stale proving files. Cover both historical manifests and test them independently of LEGACY_FILES. Also rustfmt the Rust API sources. --- quantus_sdk/rust/src/api/crypto.rs | 20 ++++- quantus_sdk/rust/src/api/mod.rs | 2 +- quantus_sdk/rust/src/api/multisig.rs | 37 +++------ quantus_sdk/rust/src/api/ur.rs | 62 ++++++++------ quantus_sdk/rust/src/api/wormhole.rs | 119 +++++++++++++++++++++++---- 5 files changed, 170 insertions(+), 70 deletions(-) diff --git a/quantus_sdk/rust/src/api/crypto.rs b/quantus_sdk/rust/src/api/crypto.rs index af7440ca1..80c2edba8 100644 --- a/quantus_sdk/rust/src/api/crypto.rs +++ b/quantus_sdk/rust/src/api/crypto.rs @@ -1,7 +1,10 @@ use qp_poseidon_core::{hash_bytes, hash_to_bytes, serialization::bytes_to_digest}; use qp_rusty_crystals_dilithium::ml_dsa_87; -use qp_rusty_crystals_hdwallet::{derive_key_from_mnemonic, derive_wormhole_from_mnemonic, mnemonic_to_seed, SensitiveBytes32, SensitiveBytes64}; pub use qp_rusty_crystals_hdwallet::HDLatticeError; +use qp_rusty_crystals_hdwallet::{ + derive_key_from_mnemonic, derive_wormhole_from_mnemonic, mnemonic_to_seed, SensitiveBytes32, + SensitiveBytes64, +}; use sp_core::crypto::{AccountId32, Ss58Codec}; use std::convert::AsRef; @@ -68,13 +71,18 @@ pub fn generate_keypair(mnemonic_str: String) -> Result let mut seed64 = SensitiveBytes64::zeroed(); mnemonic_to_seed(mnemonic_str, None, &mut seed64)?; let mut entropy = SensitiveBytes32::zeroed(); - entropy.as_mut_bytes().copy_from_slice(&seed64.as_bytes()[..32]); + entropy + .as_mut_bytes() + .copy_from_slice(&seed64.as_bytes()[..32]); let ml_dsa_keypair = MlDsaKeypair::generate(&mut entropy); Ok(Keypair::from_ml_dsa(ml_dsa_keypair)) } #[flutter_rust_bridge::frb(sync)] -pub fn generate_derived_keypair(mnemonic_str: String, path: &str) -> Result { +pub fn generate_derived_keypair( + mnemonic_str: String, + path: &str, +) -> Result { derive_key_from_mnemonic(&mnemonic_str, None, path).map(Keypair::from_ml_dsa) } @@ -137,7 +145,11 @@ pub fn sign_message(keypair: &Keypair, message: &[u8], entropy: Option<[u8; 32]> } #[flutter_rust_bridge::frb(sync)] -pub fn sign_message_with_pubkey(keypair: &Keypair, message: &[u8], entropy: Option<[u8; 32]>) -> Vec { +pub fn sign_message_with_pubkey( + keypair: &Keypair, + message: &[u8], + entropy: Option<[u8; 32]>, +) -> Vec { let signature = sign_message(keypair, message, entropy); let mut result = Vec::with_capacity(signature.len() + keypair.public_key.len()); result.extend_from_slice(&signature); diff --git a/quantus_sdk/rust/src/api/mod.rs b/quantus_sdk/rust/src/api/mod.rs index 7f2be9b5d..a03eae7f8 100644 --- a/quantus_sdk/rust/src/api/mod.rs +++ b/quantus_sdk/rust/src/api/mod.rs @@ -1,4 +1,4 @@ pub mod crypto; pub mod multisig; pub mod ur; -pub mod wormhole; \ No newline at end of file +pub mod wormhole; diff --git a/quantus_sdk/rust/src/api/multisig.rs b/quantus_sdk/rust/src/api/multisig.rs index e7c10077e..8393c3469 100644 --- a/quantus_sdk/rust/src/api/multisig.rs +++ b/quantus_sdk/rust/src/api/multisig.rs @@ -5,11 +5,7 @@ use sp_runtime::traits::{BlakeTwo256, Hash as HashT, TrailingZeroInput}; const PALLET_ID: [u8; 8] = *b"py/mltsg"; const SS58_PREFIX: u16 = 189; -fn derive_multisig_address( - signers: Vec, - threshold: u32, - nonce: u64, -) -> AccountId32 { +fn derive_multisig_address(signers: Vec, threshold: u32, nonce: u64) -> AccountId32 { let mut sorted_signers = signers; sorted_signers.sort(); @@ -36,15 +32,13 @@ pub fn predict_multisig_address( let account_ids: Vec = signers .iter() .map(|s| { - AccountId32::from_ss58check(s) - .map_err(|e| format!("Invalid SS58 address '{s}': {e}")) + AccountId32::from_ss58check(s).map_err(|e| format!("Invalid SS58 address '{s}': {e}")) }) .collect::>()?; let account_id = derive_multisig_address(account_ids, threshold, nonce); - Ok(account_id.to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom( - SS58_PREFIX, - ))) + Ok(account_id + .to_ss58check_with_version(sp_core::crypto::Ss58AddressFormat::custom(SS58_PREFIX))) } #[cfg(test)] @@ -56,18 +50,12 @@ mod tests { let signer_a = "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY"; let signer_b = "5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty"; - let forward = predict_multisig_address( - vec![signer_a.to_string(), signer_b.to_string()], - 2, - 0, - ) - .expect("forward prediction should succeed"); - let reverse = predict_multisig_address( - vec![signer_b.to_string(), signer_a.to_string()], - 2, - 0, - ) - .expect("reverse prediction should succeed"); + let forward = + predict_multisig_address(vec![signer_a.to_string(), signer_b.to_string()], 2, 0) + .expect("forward prediction should succeed"); + let reverse = + predict_multisig_address(vec![signer_b.to_string(), signer_a.to_string()], 2, 0) + .expect("reverse prediction should succeed"); assert_eq!(forward, reverse); } @@ -84,10 +72,7 @@ mod tests { ) .expect("golden vector prediction should succeed"); - assert_eq!( - address, - "qzkvQ2YBa7RrvYKNCXxJw7hJZPBSi6GzZxXGN2fN6DddaH5HJ" - ); + assert_eq!(address, "qzkvQ2YBa7RrvYKNCXxJw7hJZPBSi6GzZxXGN2fN6DddaH5HJ"); } #[test] diff --git a/quantus_sdk/rust/src/api/ur.rs b/quantus_sdk/rust/src/api/ur.rs index a8dbbc19a..b63cd8525 100644 --- a/quantus_sdk/rust/src/api/ur.rs +++ b/quantus_sdk/rust/src/api/ur.rs @@ -2,13 +2,13 @@ /// use quantus_ur::{decode_bytes, encode_bytes, is_complete}; -// Note decode_ur takes the list of QR Codes in any order and assembles them correctly. -// It also deals with the weird elements that are created in the UR standard when we exceed the number -// of segments. -// For example if you have 3 segments, and the scanner scans all 3 but doesn't succeed, subsequent parts +// Note decode_ur takes the list of QR Codes in any order and assembles them correctly. +// It also deals with the weird elements that are created in the UR standard when we exceed the number +// of segments. +// For example if you have 3 segments, and the scanner scans all 3 but doesn't succeed, subsequent parts // are sent with strange numbers like /412-3/ which are encoded with pieces of the previous segments so that -// the algorithm recovers faster than just repeating the segments over and over. This is described in the UR -// standard. FYI. +// the algorithm recovers faster than just repeating the segments over and over. This is described in the UR +// standard. FYI. #[flutter_rust_bridge::frb(sync)] pub fn decode_ur(ur_parts: Vec) -> Result, String> { decode_bytes(&ur_parts).map_err(|e| e.to_string()) @@ -32,10 +32,10 @@ mod tests { fn test_single_part_roundtrip() { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000"; let payload_bytes = hex::decode(hex_payload).expect("Hex decode failed"); - + let encoded_parts = encode_ur(payload_bytes.clone()).expect("Encoding failed"); assert_eq!(encoded_parts.len(), 1, "Should be single part"); - + let decoded_bytes = decode_ur(encoded_parts).expect("Decoding failed"); assert_eq!(decoded_bytes, payload_bytes); } @@ -44,10 +44,10 @@ mod tests { fn test_multi_part_roundtrip() { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); - + let encoded_parts = encode_ur(payload_bytes.clone()).expect("Encoding failed"); assert!(encoded_parts.len() > 1, "Should be multiple parts"); - + let decoded_bytes = decode_ur(encoded_parts).expect("Decoding failed"); assert_eq!(decoded_bytes, payload_bytes); } @@ -57,8 +57,11 @@ mod tests { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000"; let payload_bytes = hex::decode(hex_payload).expect("Hex decode failed"); let encoded_parts = encode_ur(payload_bytes).expect("Encoding failed"); - - assert!(is_complete_ur(encoded_parts), "Single part should be complete"); + + assert!( + is_complete_ur(encoded_parts), + "Single part should be complete" + ); } #[test] @@ -66,8 +69,11 @@ mod tests { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); let encoded_parts = encode_ur(payload_bytes).expect("Encoding failed"); - - assert!(is_complete_ur(encoded_parts), "All parts should be complete"); + + assert!( + is_complete_ur(encoded_parts), + "All parts should be complete" + ); } #[test] @@ -75,11 +81,14 @@ mod tests { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); let encoded_parts = encode_ur(payload_bytes).expect("Encoding failed"); - + assert!(encoded_parts.len() > 1, "Should have multiple parts"); - + let incomplete_parts = vec![encoded_parts[0].clone()]; - assert!(!is_complete_ur(incomplete_parts), "Incomplete parts should return false"); + assert!( + !is_complete_ur(incomplete_parts), + "Incomplete parts should return false" + ); } #[test] @@ -87,18 +96,23 @@ mod tests { let hex_payload = "0200007416854906f03a9dff66e3270a736c44e15970ac03a638471523a03069f276ca0700e876481755010000007400000002000000".repeat(10); let payload_bytes = hex::decode(&hex_payload).expect("Hex decode failed"); let encoded_parts = encode_ur(payload_bytes.clone()).expect("Encoding failed"); - + assert!(encoded_parts.len() > 1, "Should be multiple parts"); - + let mut scrambled_parts = encoded_parts.clone(); scrambled_parts.reverse(); let mid = scrambled_parts.len() / 2; scrambled_parts.swap(0, mid); - + let decoded_bytes = decode_ur(scrambled_parts.clone()).expect("Decoding failed"); - assert_eq!(decoded_bytes, payload_bytes, "Decoding should work regardless of part order"); - - assert!(is_complete_ur(scrambled_parts), "Scrambled parts should still be complete"); - } + assert_eq!( + decoded_bytes, payload_bytes, + "Decoding should work regardless of part order" + ); + assert!( + is_complete_ur(scrambled_parts), + "Scrambled parts should still be complete" + ); + } } diff --git a/quantus_sdk/rust/src/api/wormhole.rs b/quantus_sdk/rust/src/api/wormhole.rs index d96b9d537..713c5b849 100644 --- a/quantus_sdk/rust/src/api/wormhole.rs +++ b/quantus_sdk/rust/src/api/wormhole.rs @@ -16,15 +16,22 @@ fn versioned_bins_dir(base: &Path) -> PathBuf { base.join(format!("v{}", ZK_CIRCUITS_VERSION)) } +// Flat-dir artifacts from pre-versioned installs. Union of 3.0 (`aggregated_*`) +// and 3.1 (`private_batch_*` + `dummy_private_batch_proof.bin` from +// `generate_all_circuit_binaries(..., true, ...)`). const LEGACY_FILES: &[&str] = &[ "common.bin", "verifier.bin", "dummy_proof.bin", + "prover.bin", + "config.json", "private_batch_common.bin", "private_batch_verifier.bin", "private_batch_prover.bin", - "prover.bin", - "config.json", + "dummy_private_batch_proof.bin", + "aggregated_prover.bin", + "aggregated_verifier.bin", + "aggregated_common.bin", ]; fn cleanup_stale_circuit_dirs(base: &Path) { @@ -47,9 +54,16 @@ fn cleanup_stale_circuit_dirs(base: &Path) { let name = entry.file_name(); let name_str = name.to_string_lossy(); if name_str.starts_with('v') && name_str != current && entry.path().is_dir() { - eprintln!("[circuits] removing old version dir: {}", entry.path().display()); + eprintln!( + "[circuits] removing old version dir: {}", + entry.path().display() + ); if let Err(e) = std::fs::remove_dir_all(entry.path()) { - eprintln!("[circuits] failed to remove {}: {}", entry.path().display(), e); + eprintln!( + "[circuits] failed to remove {}: {}", + entry.path().display(), + e + ); } } } @@ -270,8 +284,13 @@ pub fn ensure_circuit_binaries(bins_dir: String) -> Result { cleanup_stale_circuit_dirs(base); let dir = versioned_bins_dir(base); - std::fs::create_dir_all(&dir) - .map_err(|e| format!("Failed to create versioned bins directory {}: {}", dir.display(), e))?; + std::fs::create_dir_all(&dir).map_err(|e| { + format!( + "Failed to create versioned bins directory {}: {}", + dir.display(), + e + ) + })?; let config_path = dir.join("config.json"); if all_required_files_exist(&dir) { @@ -409,7 +428,10 @@ pub fn generate_proof( }) } -pub fn aggregate_proofs(proof_bytes_list: Vec>, bins_dir: String) -> Result, String> { +pub fn aggregate_proofs( + proof_bytes_list: Vec>, + bins_dir: String, +) -> Result, String> { use plonky2::plonk::proof::ProofWithPublicInputs; use qp_wormhole_aggregator::private_batch::prover::PrivateBatchProver; use qp_zk_circuits_common::circuit::{C, D, F}; @@ -446,7 +468,10 @@ mod tests { fn versioned_bins_dir_appends_version() { let base = Path::new("/tmp/circuits"); let result = versioned_bins_dir(base); - assert_eq!(result, PathBuf::from(format!("/tmp/circuits/v{}", ZK_CIRCUITS_VERSION))); + assert_eq!( + result, + PathBuf::from(format!("/tmp/circuits/v{}", ZK_CIRCUITS_VERSION)) + ); } #[test] @@ -454,16 +479,71 @@ mod tests { assert_eq!(zk_circuits_version(), ZK_CIRCUITS_VERSION); } + // Independent of LEGACY_FILES so omissions in that list fail the tests. + const V3_0_MANIFEST: &[&str] = &[ + "prover.bin", + "verifier.bin", + "common.bin", + "aggregated_prover.bin", + "aggregated_verifier.bin", + "aggregated_common.bin", + "dummy_proof.bin", + "config.json", + ]; + + const V3_1_MANIFEST: &[&str] = &[ + "common.bin", + "verifier.bin", + "dummy_proof.bin", + "private_batch_common.bin", + "private_batch_verifier.bin", + "private_batch_prover.bin", + "dummy_private_batch_proof.bin", + "config.json", + ]; + + #[test] + fn legacy_files_covers_historical_manifests() { + for name in V3_0_MANIFEST.iter().chain(V3_1_MANIFEST.iter()) { + assert!( + LEGACY_FILES.contains(name), + "LEGACY_FILES missing historical artifact {}", + name + ); + } + } + + #[test] + fn cleanup_removes_v3_0_manifest() { + let tmp = tempfile::tempdir().unwrap(); + let base = tmp.path(); + for name in V3_0_MANIFEST { + std::fs::write(base.join(name), b"x").unwrap(); + } + cleanup_stale_circuit_dirs(base); + for name in V3_0_MANIFEST { + assert!( + !base.join(name).exists(), + "{} should have been removed", + name + ); + } + } + #[test] - fn cleanup_removes_legacy_files() { + fn cleanup_removes_v3_1_manifest() { let tmp = tempfile::tempdir().unwrap(); let base = tmp.path(); - for name in LEGACY_FILES { + for name in V3_1_MANIFEST { std::fs::write(base.join(name), b"x").unwrap(); } cleanup_stale_circuit_dirs(base); - for name in LEGACY_FILES { - assert!(!base.join(name).exists(), "{} should have been removed", name); + for name in V3_1_MANIFEST { + assert!( + !base.join(name).exists(), + "{} should have been removed", + name + ); } } @@ -480,7 +560,10 @@ mod tests { assert!(!base.join("v1.0.0").exists()); assert!(!base.join("v3.9.0").exists()); - assert!(base.join(¤t).exists(), "current version dir must survive"); + assert!( + base.join(¤t).exists(), + "current version dir must survive" + ); } #[test] @@ -506,8 +589,14 @@ mod tests { fn all_required_files_exist_true_when_complete() { let tmp = tempfile::tempdir().unwrap(); let dir = tmp.path(); - for name in &["common.bin", "verifier.bin", "dummy_proof.bin", - "private_batch_common.bin", "private_batch_verifier.bin", "config.json"] { + for name in &[ + "common.bin", + "verifier.bin", + "dummy_proof.bin", + "private_batch_common.bin", + "private_batch_verifier.bin", + "config.json", + ] { std::fs::write(dir.join(name), b"x").unwrap(); } assert!(all_required_files_exist(dir));