diff --git a/Cargo.lock b/Cargo.lock index 61d6367942..6edd5abf52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,18 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.21.0" +version = "0.25.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" +checksum = "1b5d307320b3181d6d7954e663bd7c774a838b8220fe0593c86d9fb09f498b4b" dependencies = [ "gimli", ] [[package]] -name = "adler" -version = "1.0.2" +name = "adler2" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" [[package]] name = "aead" @@ -23,7 +23,7 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ - "crypto-common 0.1.6", + "crypto-common 0.1.7", "generic-array", ] @@ -41,12 +41,12 @@ dependencies = [ [[package]] name = "ahash" -version = "0.8.11" +version = "0.8.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" dependencies = [ "cfg-if", - "getrandom 0.2.15", + "getrandom 0.3.4", "once_cell", "version_check", "zerocopy", @@ -54,9 +54,9 @@ dependencies = [ [[package]] name = "aho-corasick" -version = "1.1.3" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" dependencies = [ "memchr", ] @@ -72,9 +72,9 @@ dependencies = [ [[package]] name = "ambassador" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b27ba24e4d8a188489d5a03c7fabc167a60809a383cdb4d15feb37479cd2a48" +checksum = "e68de4cdc6006162265d0957edb4a860fe4e711b1dc17a5746fd95f952f08285" dependencies = [ "itertools 0.10.5", "proc-macro2", @@ -84,21 +84,23 @@ dependencies = [ [[package]] name = "amplify" -version = "4.6.0" +version = "4.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e711289a6cb28171b4f0e6c8019c69ff9476050508dc082167575d458ff74d0" +checksum = "3f7fb4ac7c881e54a8e7015e399b6112a2a5bc958b6c89ac510840ff20273b31" dependencies = [ "amplify_derive", "amplify_num", "ascii", + "getrandom 0.2.17", + "getrandom 0.3.4", "wasm-bindgen", ] [[package]] name = "amplify_derive" -version = "4.0.0" +version = "4.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "759dcbfaf94d838367a86d493ec34ccc8aa6fe365cb7880d6bf89006de24d9c1" +checksum = "2a6309e6b8d89b36b9f959b7a8fa093583b94922a0f6438a24fb08936de4d428" dependencies = [ "amplify_syn", "proc-macro2", @@ -108,9 +110,9 @@ dependencies = [ [[package]] name = "amplify_num" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04c009c5c4de814911b177e2ea59e4930bb918978ed3cce4900d846a6ceb0838" +checksum = "99bcb75a2982047f733547042fc3968c0f460dfcf7d90b90dea3b2744580e9ad" dependencies = [ "wasm-bindgen", ] @@ -126,12 +128,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -147,29 +143,82 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[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.8" +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 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] [[package]] name = "anyhow" -version = "1.0.83" +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 = "25bdb32cbbdce2b519a9cd7df3a678443100e265d5e25ca763b7572a5104f5f3" +checksum = "6a3a1fd6f75306b68087b831f025c712524bcb19aad54e557b1129cfa0a2b207" +dependencies = [ + "rustversion", +] [[package]] name = "arrayref" -version = "0.3.7" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b4930d2cb77ce62f89ee5d5289b4ac049559b1c45539271f5ed4fdc7db34545" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" [[package]] name = "arrayvec" -version = "0.7.4" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" [[package]] name = "arti-client" @@ -179,7 +228,7 @@ checksum = "0a79ca5ce63b36033a5ccbfbcc7f919cbd93db61708543aa5e2e4917856205e7" dependencies = [ "async-trait", "cfg-if", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "educe", @@ -194,7 +243,7 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", "tor-async-utils", "tor-basic-utils", @@ -238,7 +287,7 @@ dependencies = [ "nom", "num-traits", "rusticata-macros", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -249,7 +298,7 @@ checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", "synstructure", ] @@ -261,7 +310,7 @@ checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -272,29 +321,47 @@ checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" [[package]] name = "async-compression" -version = "0.4.11" +version = "0.4.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd066d0b4ef8ecb03a55319dc13aa6910616d0f44008a045bb1835af830abff5" +checksum = "d0f9ee0f6e02ffd7ad5816e9464499fba7b3effd01123b515c41d1697c43dad1" dependencies = [ - "flate2", - "futures-core", + "compression-codecs", + "compression-core", "futures-io", - "memchr", "pin-project-lite", - "xz2", - "zstd", - "zstd-safe", +] + +[[package]] +name = "async-stream" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b5a71a6f37880a80d1d7f19efd781e4b5de42c88f0722cc13bcb6cc2cfe8476" +dependencies = [ + "async-stream-impl", + "futures-core", + "pin-project-lite", +] + +[[package]] +name = "async-stream-impl" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7c24de15d275a1ecfd47a380fb4d5ec9bfe0933f309ed5e705b775596a3574d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "async-trait" -version = "0.1.81" +version = "0.1.89" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e0c28dcc82d7c8ead5cb13beb15405b57b8546e93215673ff8ca0349a028107" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -333,9 +400,9 @@ checksum = "c59bdb34bc650a32731b31bd8f0829cc15d24a708ee31559e0bb34f2bc320cba" [[package]] name = "atomic" -version = "0.6.0" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994" +checksum = "a89cbf775b137e9b968e67227ef7f775587cde3fd31b0d8599dbd0f598a48340" dependencies = [ "bytemuck", ] @@ -348,23 +415,78 @@ checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" + +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower 0.5.3", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "axum-core" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", + "tracing", +] [[package]] name = "backtrace" -version = "0.3.71" +version = "0.3.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +checksum = "bb531853791a215d7c62a30daf0dde835f381ab5de4589cfe7c649d2cbe92bd6" dependencies = [ "addr2line", - "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", + "windows-link 0.2.1", ] [[package]] @@ -381,15 +503,15 @@ checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "base64ct" -version = "1.8.0" +version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55248b47b0caf0546f7988906588779981c43bb1bc9d0c44087278f80cdb44ba" +checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" [[package]] name = "bech32" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d965446196e3b7decd44aa7ee49e31d630118f90ef12f97900f262eb915c951d" +checksum = "32637268377fc7b10a8c6d51de3e7fba1ce5dd371a96e342b34e6078db558e7f" [[package]] name = "bellman" @@ -431,7 +553,7 @@ dependencies = [ "hmac 0.12.1", "pbkdf2", "rand 0.8.5", - "sha2 0.10.8", + "sha2 0.10.9", "unicode-normalization", "zeroize", ] @@ -454,18 +576,18 @@ dependencies = [ [[package]] name = "bit-set" -version = "0.5.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" dependencies = [ "bit-vec", ] [[package]] name = "bit-vec" -version = "0.6.3" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" [[package]] name = "bitflags" @@ -475,9 +597,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.0" +version = "2.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" +checksum = "843867be96c8daad0d758b57df9392b6d8d271134fce549de6ce169ff98a92af" [[package]] name = "bitvec" @@ -493,9 +615,9 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23285ad32269793932e830392f2fe2f83e26488fd3ec778883a93c8323735780" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" dependencies = [ "arrayref", "arrayvec", @@ -504,9 +626,9 @@ dependencies = [ [[package]] name = "blake2s_simd" -version = "1.0.2" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94230421e395b9920d23df13ea5d77a20e1725331f90fbbf6df6040b33f756ae" +checksum = "ee29928bad1e3f94c9d1528da29e07a1d3d04817ae8332de1e8b846c8439f4b3" dependencies = [ "arrayref", "arrayvec", @@ -521,7 +643,7 @@ checksum = "e0b121a9fe0df916e362fb3271088d071159cdf11db0e4182d02152850756eff" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -561,7 +683,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09dc0086e469182132244e9b8d313a0742e1132da43a08c24b9dd3c18e0faf3a" dependencies = [ - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -570,26 +692,26 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" dependencies = [ - "sha2 0.10.8", + "sha2 0.10.9", "tinyvec", ] [[package]] name = "bstr" -version = "1.11.3" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "531a9155a481e2ee699d4f98f43c0ca4ff8ee1bfd55c31e9e98fb29d2b176fe0" +checksum = "63044e1ae8e69f3b5a92c736ca6269b8d12fa7efe39bf34ddb06d102cf0e2cab" dependencies = [ "memchr", - "regex-automata 0.4.9", + "regex-automata", "serde", ] [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.20.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb" [[package]] name = "by_address" @@ -599,9 +721,9 @@ checksum = "64fa3c856b712db6612c019f14756e64e4bcea13337a6b33b696333a9eaa2d06" [[package]] name = "bytemuck" -version = "1.16.3" +version = "1.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "102087e286b4677862ea56cf8fc58bb2cdfa8725c40ffb80fe3a008eb7f2fc83" +checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec" [[package]] name = "byteorder" @@ -611,9 +733,9 @@ checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "bytes" -version = "1.10.1" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" +checksum = "1e748733b7cbc798e1434b6ac524f0c1ff2ab456fe201501e6497c8417a4fc33" [[package]] name = "caret" @@ -638,10 +760,11 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.17" +version = "1.2.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fcb57c740ae1daf453ae85f16e37396f672b039e00d9d866e07ddb24e328e3a" +checksum = "b7a4d3ec6524d28a329fc53654bbadc9bdd7b0431f5d65f1a56ffb28a1ee5283" dependencies = [ + "find-msvc-tools", "jobserver", "libc", "shlex", @@ -649,9 +772,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "chacha20" @@ -677,17 +800,31 @@ dependencies = [ "zeroize", ] +[[package]] +name = "chain-ingest" +version = "0.1.0" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "pir-types", + "prost 0.13.5", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tonic 0.12.3", + "tonic-build 0.12.3", + "tracing", +] + [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "c673075a2e0e5f4a1dde27ce9dee1ea4558c7ffe648f576438a20ca1d2acc4b0" dependencies = [ - "android-tzdata", "iana-time-zone", "num-traits", "serde", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -723,41 +860,56 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ - "crypto-common 0.1.6", + "crypto-common 0.1.7", "inout", "zeroize", ] [[package]] name = "clap" -version = "4.5.20" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b97f376d85a664d5837dbae44bf546e6477a679ff6610010f17276f686d867e8" +checksum = "b193af5b67834b676abd72466a96c1024e6a6ad978a1f484bd90b85c94041351" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] name = "clap_builder" -version = "4.5.20" +version = "4.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19bc80abd44e4bed93ca373a0704ccbd1b710dc5749406201bb018272808dc54" +checksum = "714a53001bf66416adb0e2ef5ac857140e7dc3a0c48fb28b2f10762fc4b5069f" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim 0.11.1", +] + +[[package]] +name = "clap_derive" +version = "4.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1110bd8a634a1ab8cb04345d8d878267d57c3cf1b38d91b71af6686408bbca6a" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] name = "clap_lex" -version = "0.7.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "coarsetime" -version = "0.1.34" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13b3839cf01bb7960114be3ccf2340f541b6d0c81f8690b007b2b39f750f7e5d" +checksum = "e58eb270476aa4fc7843849f8a35063e8743b4dbcdf6dd0f8ea0886980c204c2" dependencies = [ "libc", "wasix", @@ -766,9 +918,64 @@ dependencies = [ [[package]] name = "cobs" -version = "0.2.3" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa961b519f0b462e3a3b4a34b64d119eeaca1d59af726fe450bbba07a9fc0a1" +dependencies = [ + "thiserror 2.0.18", +] + +[[package]] +name = "colorchoice" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570" + +[[package]] +name = "commitment-ingest" +version = "0.1.0" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "chain-ingest", + "decryption-types", + "pir-types", + "thiserror 2.0.18", + "tokio", + "tracing", + "witness-types", +] + +[[package]] +name = "commitment-tree-db" +version = "0.1.0" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "incrementalmerkletree", + "orchard 0.12.0", + "thiserror 2.0.18", + "tracing", + "witness-types", + "xxhash-rust", +] + +[[package]] +name = "compression-codecs" +version = "0.4.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb7b51a7d9c967fc26773061ba86150f19c50c0d65c887cb1fbe295fd16619b7" +dependencies = [ + "compression-core", + "flate2", + "liblzma", + "zstd", + "zstd-safe", +] + +[[package]] +name = "compression-core" +version = "0.4.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" +checksum = "75984efb6ed102a0d42db99afb6c1948f0380d1d91808d5529916e6c08b49d8d" [[package]] name = "concurrent-queue" @@ -787,15 +994,15 @@ checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" [[package]] name = "constant_time_eq" -version = "0.3.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f7144d30dcf0fafbce74250a3963025d8d52177934239851c917d29f1df280c2" +checksum = "3d52eff69cd5e647efe296129160853a42795992097e8af39800e1060caeea9b" [[package]] name = "convert_case" -version = "0.7.1" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +checksum = "633458d4ef8c78b72454de2d54fd6ab2e60f9e02be22f3c6104cdc8a4e0fceb9" dependencies = [ "unicode-segmentation", ] @@ -809,11 +1016,31 @@ dependencies = [ "futures", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation-sys" -version = "0.8.6" +version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] name = "core2" @@ -826,27 +1053,27 @@ dependencies = [ [[package]] name = "cpp_demangle" -version = "0.4.4" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96e58d342ad113c2b878f16d5d034c03be492ae460cdbc02b7f0f2284d310c7d" +checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def" dependencies = [ "cfg-if", ] [[package]] name = "cpufeatures" -version = "0.2.12" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crc32fast" -version = "1.4.2" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" dependencies = [ "cfg-if", ] @@ -941,9 +1168,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -960,25 +1187,24 @@ dependencies = [ [[package]] name = "crossbeam-queue" -version = "0.3.8" +version = "0.3.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cfb3ea8a53f37c40dea2c7bedcbd88bdfae54f5e2175d6ecaff1c988353add" +checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115" dependencies = [ - "cfg-if", "crossbeam-utils", ] [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" [[package]] name = "crypto-bigint" @@ -994,9 +1220,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.1.6" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" dependencies = [ "generic-array", "rand_core 0.6.4", @@ -1045,16 +1271,16 @@ checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "daggy" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91a9304e55e9d601a39ae4deaba85406d5c0980e106f65afcf0460e9af1e7602" +checksum = "70def8d72740e44d9f676d8dab2c933a236663d86dd24319b57a2bed4d694774" dependencies = [ - "petgraph", + "petgraph 0.7.1", ] [[package]] @@ -1069,12 +1295,12 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "9cdf337090841a411e2a7f3deb9187445851f91b309c0c0a29e05f74a00a48c0" dependencies = [ - "darling_core 0.20.10", - "darling_macro 0.20.10", + "darling_core 0.21.3", + "darling_macro 0.21.3", ] [[package]] @@ -1093,16 +1319,16 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "1247195ecd7e3c85f83c8d2a366e4210d588e802133e1e355180a9870b517ea4" dependencies = [ "fnv", "ident_case", "proc-macro2", "quote", "strsim 0.11.1", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1118,20 +1344,20 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "d38308df82d1080de0afee5d069fa14b0326a88c14f15c5ccda35b4a6c414c81" dependencies = [ - "darling_core 0.20.10", + "darling_core 0.21.3", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "data-encoding" -version = "2.6.0" +version = "2.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8566979429cf69b49a5c740c60791108e86440e8be149bbea4fe54d2c32d6e2" +checksum = "d7a1e2f27636f116493b8b860f5546edb47c8d8f8ea73e1d2a20be88e28d1fea" [[package]] name = "debugid" @@ -1142,11 +1368,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "decryption-types" +version = "0.1.0" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "serde", + "witness-types", +] + [[package]] name = "der" -version = "0.7.9" +version = "0.7.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "e7c1832837b905bbfb5101e07cc24c8deddf52f93225eee6ead5f4d63d53ddcb" dependencies = [ "const-oid", "pem-rfc7468", @@ -1177,42 +1412,14 @@ dependencies = [ "serde", ] -[[package]] -name = "derive-deftly" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72f9bc3564f74be6c35d49a7efee54380d7946ccc631323067f33fabb9246027" -dependencies = [ - "derive-deftly-macros 0.14.2", - "heck", -] - [[package]] name = "derive-deftly" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d308ebe4b10924331bd079044b418da7b227d724d3e2408567a47ad7c3da2a0" dependencies = [ - "derive-deftly-macros 1.3.0", - "heck", -] - -[[package]] -name = "derive-deftly-macros" -version = "0.14.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1b84d32b18d9a256d81e4fec2e4cfd0ab6dde5e5ff49be1713ae0adbd0060c2" -dependencies = [ + "derive-deftly-macros", "heck", - "indexmap 2.12.0", - "itertools 0.13.0", - "proc-macro-crate", - "proc-macro2", - "quote", - "sha3", - "strum 0.26.3", - "syn 2.0.100", - "void", ] [[package]] @@ -1222,14 +1429,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dd5f2b7218a51c827a11d22d1439b598121fac94bf9b99452e4afffe512d78c9" dependencies = [ "heck", - "indexmap 2.12.0", + "indexmap 2.13.1", "itertools 0.14.0", "proc-macro-crate", "proc-macro2", "quote", "sha3", - "strum 0.27.2", - "syn 2.0.100", + "strum", + "syn 2.0.117", "void", ] @@ -1266,23 +1473,24 @@ dependencies = [ [[package]] name = "derive_more" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" dependencies = [ "derive_more-impl", ] [[package]] name = "derive_more-impl" -version = "2.0.1" +version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ "convert_case", "proc-macro2", "quote", - "syn 2.0.100", + "rustc_version", + "syn 2.0.117", "unicode-xid", ] @@ -1294,7 +1502,7 @@ checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer 0.10.4", "const-oid", - "crypto-common 0.1.6", + "crypto-common 0.1.7", "subtle", ] @@ -1315,37 +1523,16 @@ version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ - "dirs-sys 0.5.0", + "dirs-sys", ] [[package]] name = "dirs" -version = "5.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "44c45a9d03d6676652bcb5e724c7e988de1acad23a711b5217ab9cbecbec2225" -dependencies = [ - "dirs-sys 0.4.1", -] - -[[package]] -name = "dirs" -version = "6.0.0" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" dependencies = [ - "dirs-sys 0.5.0", -] - -[[package]] -name = "dirs-sys" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "520f05a5cbd335fae5a99ff7a6ab8627577660ee5cfd6a94a6a929b52ff0321c" -dependencies = [ - "libc", - "option-ext", - "redox_users 0.4.5", - "windows-sys 0.48.0", + "dirs-sys", ] [[package]] @@ -1356,41 +1543,41 @@ checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ "libc", "option-ext", - "redox_users 0.5.0", - "windows-sys 0.60.2", + "redox_users", + "windows-sys 0.61.2", ] [[package]] name = "displaydoc" -version = "0.2.4" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "487585f4d0c6655fe74905e2504d8ad6908e4db67f744eb140876906c2f3175d" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "document-features" -version = "0.2.8" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef5282ad69563b5fc40319526ba27e0e7363d552a896f0297d54f767717f9b95" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" dependencies = [ "litrs", ] [[package]] name = "downcast-rs" -version = "2.0.1" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" +checksum = "117240f60069e65410b3ae1bb213295bd828f707b5bec6596a1afc8793ce0cbc" [[package]] name = "dyn-clone" -version = "1.0.17" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" [[package]] name = "dynosaur" @@ -1409,7 +1596,7 @@ checksum = "0b0713d5c1d52e774c5cd7bb8b043d7c0fc4f921abfb678556140bfbe6ab2364" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1447,7 +1634,7 @@ dependencies = [ "merlin", "rand_core 0.6.4", "serde", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", "zeroize", ] @@ -1466,9 +1653,9 @@ dependencies = [ [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "elliptic-curve" @@ -1501,6 +1688,15 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "enum-ordinalize" version = "3.1.15" @@ -1511,7 +1707,7 @@ dependencies = [ "num-traits", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1523,15 +1719,9 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] -[[package]] -name = "env_home" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7f84e12ccf0a7ddc17a6c41c93326024c42920d7ee630d04950e6926645c0fe" - [[package]] name = "equator" version = "0.4.2" @@ -1549,7 +1739,7 @@ checksum = "44f23cf4b44bfce11a86ace86f8a73ffdec849c9fd00a386a53d278bd9e81fb3" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1565,9 +1755,9 @@ dependencies = [ [[package]] name = "equivalent" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" @@ -1576,14 +1766,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] name = "event-listener" -version = "5.3.1" +version = "5.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6032be9bd27023a771701cc49f9f053c751055f71efb2e0ae5c15809093675ba" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" dependencies = [ "concurrent-queue", "parking", @@ -1613,15 +1803,15 @@ checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" [[package]] name = "fastrand" -version = "2.1.0" +version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" [[package]] name = "ff" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ded41244b729663b1e574f1b4fb731469f69f79c17667b5d776b16cda0479449" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" dependencies = [ "bitvec", "rand_core 0.6.4", @@ -1640,25 +1830,30 @@ version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8cb01cd46b0cf372153850f4c6c272d9cbea2da513e07538405148f95bd789f3" dependencies = [ - "atomic 0.6.0", + "atomic 0.6.1", "serde", - "toml 0.8.19", + "toml 0.8.23", "uncased", "version_check", ] [[package]] name = "filetime" -version = "0.2.25" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "35c0522e981e68cbfa8c3f978441a5f34b30b96e146b33cd3359176b50fe8586" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.59.0", ] +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + [[package]] name = "findshlibs" version = "0.10.2" @@ -1682,15 +1877,15 @@ dependencies = [ [[package]] name = "fixedbitset" -version = "0.4.2" +version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80" +checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "flate2" -version = "1.0.30" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" dependencies = [ "crc32fast", "miniz_oxide", @@ -1714,6 +1909,30 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[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 = "fpe" version = "0.6.1" @@ -1735,11 +1954,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a157b06319bb4868718fd20177a0d3373d465e429d89cd0ee493d9f5918902" dependencies = [ "derive_builder_fork_arti", - "dirs 6.0.0", + "dirs", "libc", "pwd-grp", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "walkdir", ] @@ -1761,9 +1980,9 @@ checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] name = "futures" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "645c6916888f6cb6350d2550b80fb63e734897a8498abe35cfb732b6487804b0" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" dependencies = [ "futures-channel", "futures-core", @@ -1776,9 +1995,9 @@ dependencies = [ [[package]] name = "futures-channel" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" +checksum = "07bbe89c50d7a535e539b8c17bc0b49bdb77747034daa8087407d655f3f7cc1d" dependencies = [ "futures-core", "futures-sink", @@ -1786,15 +2005,15 @@ dependencies = [ [[package]] name = "futures-core" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" [[package]] name = "futures-executor" -version = "0.3.30" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a576fc72ae164fca6b9db127eaa9a9dda0d61316034f33a0a0d4eda41f02b01d" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" dependencies = [ "futures-core", "futures-task", @@ -1803,19 +2022,19 @@ dependencies = [ [[package]] name = "futures-io" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" +checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" [[package]] name = "futures-macro" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +checksum = "e835b70203e41293343137df5c0664546da5745f82ec9b84d40be8336958447b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -1825,27 +2044,27 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" dependencies = [ "futures-io", - "rustls 0.23.25", + "rustls 0.23.37", "rustls-pki-types", ] [[package]] name = "futures-sink" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" +checksum = "c39754e157331b013978ec91992bde1ac089843443c49cbc7f46150b0fad0893" [[package]] name = "futures-task" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" [[package]] name = "futures-util" -version = "0.3.31" +version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ "futures-channel", "futures-core", @@ -1855,7 +2074,6 @@ dependencies = [ "futures-task", "memchr", "pin-project-lite", - "pin-utils", "slab", ] @@ -1872,13 +2090,15 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.15" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -1886,11 +2106,26 @@ name = "getrandom" version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 5.3.0", + "wasip2", + "wasm-bindgen", +] + +[[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", + "r-efi 6.0.0", "wasip2", + "wasip3", ] [[package]] @@ -1902,14 +2137,14 @@ dependencies = [ "proc-macro-error2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "gimli" -version = "0.28.1" +version = "0.32.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +checksum = "e629b9b98ef3dd8afe6ca2bd0f89306cec16d43d907889945bc5d6687f2f13c7" [[package]] name = "glob-match" @@ -1951,9 +2186,9 @@ dependencies = [ [[package]] name = "h2" -version = "0.4.5" +version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" +checksum = "2f44da3a8150a6703ed5d34e164b875fd14c2cdab9af1252a9a1020bde2bdc54" dependencies = [ "atomic-waker", "bytes", @@ -1961,7 +2196,7 @@ dependencies = [ "futures-core", "futures-sink", "http", - "indexmap 2.12.0", + "indexmap 2.13.1", "slab", "tokio", "tokio-util", @@ -1970,11 +2205,13 @@ dependencies = [ [[package]] name = "half" -version = "2.2.1" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02b4af3693f1b705df946e9fe5631932443781d0aabb423b62fcd4d73f6d2fd0" +checksum = "6ea2d84b969582b4b1864a92dc5d27cd2b77b622a8d79306834f1be5ba20d84b" dependencies = [ + "cfg-if", "crunchy", + "zerocopy", ] [[package]] @@ -2017,14 +2254,15 @@ dependencies = [ [[package]] name = "halo2_proofs" -version = "0.3.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b867a8d9bbb85fca76fff60652b5cd19b853a1c4d0665cb89bee68b18d2caf0" +checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" dependencies = [ "blake2b_simd", "ff", "group", "halo2_legacy_pdqsort", + "indexmap 1.9.3", "maybe-rayon", "pasta_curves", "rand_core 0.6.4", @@ -2039,18 +2277,18 @@ checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" [[package]] name = "hashbrown" -version = "0.15.2" +version = "0.15.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "foldhash", ] [[package]] name = "hashbrown" -version = "0.16.0" +version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" [[package]] name = "hashlink" @@ -2058,7 +2296,7 @@ version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1" dependencies = [ - "hashbrown 0.15.2", + "hashbrown 0.15.5", ] [[package]] @@ -2069,9 +2307,9 @@ checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" -version = "0.3.9" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hex" @@ -2108,11 +2346,11 @@ dependencies = [ [[package]] name = "home" -version = "0.5.9" +version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +checksum = "cc627f471c528ff0c4a49e1d5e60450c8f6461dd6d10ba9dcd3a61d3dff7728d" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -2123,12 +2361,11 @@ checksum = "f558a64ac9af88b5ba400d99b579451af0d39c6d360980045b91aac966d705e2" [[package]] name = "http" -version = "1.1.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" +checksum = "e3ba2a386d7f85a81f119ad7498ebe444d2e22c2af0b86b069416ace48b3311a" dependencies = [ "bytes", - "fnv", "itoa", ] @@ -2144,12 +2381,12 @@ dependencies = [ [[package]] name = "http-body-util" -version = "0.1.2" +version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "pin-project-lite", @@ -2169,9 +2406,9 @@ checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" [[package]] name = "humantime" -version = "2.1.0" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" +checksum = "135b12329e5e3ce057a9f972339ea52bc954fe1e9358ef27f95e89716fbc5424" [[package]] name = "humantime-serde" @@ -2194,13 +2431,14 @@ dependencies = [ [[package]] name = "hyper" -version = "1.6.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +checksum = "6299f016b246a94207e63da54dbe807655bf9e00044f73ded42c3ac5305fbcca" dependencies = [ + "atomic-waker", "bytes", "futures-channel", - "futures-util", + "futures-core", "h2", "http", "http-body", @@ -2213,11 +2451,27 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls 0.23.37", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + [[package]] name = "hyper-timeout" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3203a961e5c83b6f5498933e78b6b263e208c197b63e9c6c53cc82ffd3f63793" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" dependencies = [ "hyper", "hyper-util", @@ -2226,38 +2480,60 @@ dependencies = [ "tower-service", ] +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" dependencies = [ + "base64", "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", + "ipnet", "libc", + "percent-encoding", "pin-project-lite", - "socket2 0.5.9", + "socket2 0.6.3", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.65" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", + "log", "wasm-bindgen", - "windows-core 0.52.0", + "windows-core 0.62.2", ] [[package]] @@ -2269,12 +2545,121 @@ 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.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "incrementalmerkletree" version = "0.8.2" @@ -2310,12 +2695,12 @@ dependencies = [ [[package]] name = "indexmap" -version = "2.12.0" +version = "2.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +checksum = "45a8a2b9cb3e0b0c1803dbb0758ffac5de2f425b23c28f518faabd9d805342ff" dependencies = [ "equivalent", - "hashbrown 0.16.0", + "hashbrown 0.16.1", "serde", "serde_core", ] @@ -2327,7 +2712,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "232929e1d75fe899576a3d5c7416ad0d88dbfbb3c3d6aa00873a7408a50ddb88" dependencies = [ "ahash", - "indexmap 2.12.0", + "indexmap 2.13.1", "is-terminal", "itoa", "log", @@ -2340,11 +2725,11 @@ dependencies = [ [[package]] name = "inotify" -version = "0.11.0" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f37dccff2791ab604f9babef0ba14fbe0be30bd368dc541e2b08d07c8aa908f3" +checksum = "bd5b3eaf1a28b758ac0faa5a4254e8ab2705605496f1b1f3fbbc3988ad73d199" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "inotify-sys", "libc", ] @@ -2360,30 +2745,55 @@ dependencies = [ [[package]] name = "inout" -version = "0.1.3" +version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" dependencies = [ "generic-array", ] [[package]] name = "inventory" -version = "0.3.15" +version = "0.3.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4f0c30c76f2f4ccee3fe55a2435f691ca00c0e4bd87abe4f4a851b1d4dac39b" +dependencies = [ + "rustversion", +] + +[[package]] +name = "ipnet" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98f6fed1fde3f8c21bc40a1abb88dd75e67924f9cffc3ef95607bad8017f8e2" + +[[package]] +name = "iri-string" +version = "0.7.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f958d3d68f4167080a18141e10381e7634563984a537f2a49a30fd8e53ac5767" +checksum = "25e659a4bb38e810ebc252e53b5814ff908a8c58c2a9ce2fae1bbec24cbf4e20" +dependencies = [ + "memchr", + "serde", +] [[package]] name = "is-terminal" -version = "0.4.12" +version = "0.4.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f23ff5ef2b80d608d61efee834934d862cd92461afc0560dedf493e4c033738b" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] +[[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" @@ -2413,25 +2823,29 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" [[package]] name = "jobserver" -version = "0.1.31" +version = "0.1.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2b099aaa34a9751c5bf0878add70444e1ed2dd73f347be99003d4577277de6e" +checksum = "9afb3de4395d6b3e67a780b6de64b51c978ecf11cb9a462c66be7d4ca9039d33" dependencies = [ + "getrandom 0.3.4", "libc", ] [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "2e04e2ef80ce82e13552136fabeef8a5ed1f985a96805761cbb9a2c34e7664d9" dependencies = [ + "cfg-if", + "futures-util", + "once_cell", "wasm-bindgen", ] @@ -2451,27 +2865,27 @@ dependencies = [ [[package]] name = "keccak" -version = "0.1.4" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f6d5ed8676d904364de097082f4e7d240b571b67989ced0240f08b7f966f940" +checksum = "cb26cec98cce3a3d96cbb7bced3c4b16e3d13f27ec56dbd62cbc8f39cfb9d653" dependencies = [ "cpufeatures", ] [[package]] name = "known-folders" -version = "1.1.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4397c789f2709d23cfcb703b316e0766a8d4b17db2d47b0ab096ef6047cae1d8" +checksum = "7a1886916523694cd6ea3d175f03a1e5010699a2a4cc13696d83d7bea1d80638" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] name = "kqueue" -version = "1.0.8" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" +checksum = "eac30106d7dce88daf4a3fcb4879ea939476d5074a9b7ddd0fb97fa4bed5596a" dependencies = [ "kqueue-sys", "libc", @@ -2496,27 +2910,54 @@ dependencies = [ "spin 0.9.8", ] +[[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.177" +version = "0.2.184" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48f5d2a454e16a5ea0f4ced81bd44e4cfc7bd3a507b61887c99fd3538b28e4af" + +[[package]] +name = "liblzma" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6033b77c21d1f56deeae8014eb9fbe7bdf1765185a6c508b5ca82eeaed7f899" +dependencies = [ + "liblzma-sys", +] + +[[package]] +name = "liblzma-sys" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "1a60851d15cd8c5346eca4ab8babff585be2ae4bc8097c067291d3ffe2add3b6" +dependencies = [ + "cc", + "libc", + "pkg-config", +] [[package]] name = "libm" -version = "0.2.8" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] name = "libredox" -version = "0.1.3" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" +checksum = "7ddbf48fd451246b1f8c2610bd3b4ac0cc6e149d89832867093ab69a17194f08" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "libc", - "redox_syscall", + "plain", + "redox_syscall 0.7.3", ] [[package]] @@ -2532,57 +2973,51 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" [[package]] -name = "linux-raw-sys" -version = "0.11.0" +name = "litemap" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" [[package]] name = "litrs" -version = "0.4.1" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" [[package]] name = "lock_api" -version = "0.4.12" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" dependencies = [ - "autocfg", "scopeguard", ] [[package]] name = "log" -version = "0.4.21" +version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] -name = "lzma-sys" -version = "0.1.20" +name = "matchers" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5fda04ab3764e6cde78b9974eec4f779acaba7c4e84b36eca3cf77c581b85d27" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" dependencies = [ - "cc", - "libc", - "pkg-config", + "regex-automata", ] [[package]] -name = "matchers" -version = "0.1.0" +name = "matchit" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8263075bb86c5a1b1427b5ae862e8889656f126e9f77c484496e8b47cf5c5558" -dependencies = [ - "regex-automata 0.1.10", -] +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" [[package]] name = "maybe-rayon" @@ -2596,15 +3031,15 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.4" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" [[package]] name = "memmap2" -version = "0.9.4" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" +checksum = "714098028fe011992e1c3962653c96b2d578c4b4bce9036e15ff220319b1e0e3" dependencies = [ "libc", ] @@ -2627,6 +3062,12 @@ dependencies = [ "zeroize", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + [[package]] name = "minimal-lexical" version = "0.2.1" @@ -2635,21 +3076,20 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" dependencies = [ - "adler", + "adler2", + "simd-adler32", ] [[package]] name = "minreq" -version = "2.12.0" +version = "2.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "763d142cdff44aaadd9268bebddb156ef6c65a0e13486bb81673cf2d8739f9b0" +checksum = "05015102dad0f7d61691ca347e9d9d9006685a64aefb3d79eecf62665de2153d" dependencies = [ - "log", - "once_cell", "rustls 0.21.12", "rustls-webpki 0.101.7", "webpki-roots 0.25.4", @@ -2657,33 +3097,39 @@ dependencies = [ [[package]] name = "mio" -version = "0.8.11" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" +checksum = "50b7e5b27aa02a74bac8c3f23f448f8d87ff11f92d3aac1a6ed369ee08cc56c1" dependencies = [ "libc", + "log", "wasi", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] -name = "mio" -version = "1.0.3" +name = "multimap" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d87ecb2933e8aeadb3e3a02b828fed80a7528047e68b4f424523a0981a3a084" + +[[package]] +name = "native-tls" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ "libc", "log", - "wasi", - "windows-sys 0.52.0", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] -[[package]] -name = "multimap" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a" - [[package]] name = "nix" version = "0.26.4" @@ -2719,52 +3165,53 @@ checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" [[package]] name = "notify" -version = "8.0.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fee8403b3d66ac7b26aee6e40a897d85dc5ce26f44da36b8b73e987cc52e943" +checksum = "4d3d07927151ff8575b7087f245456e549fea62edf0ec4e565a5ee50c8402bc3" dependencies = [ - "bitflags 2.9.0", - "filetime", + "bitflags 2.11.0", "inotify", "kqueue", "libc", "log", - "mio 1.0.3", + "mio", "notify-types", "walkdir", - "windows-sys 0.59.0", + "windows-sys 0.60.2", ] [[package]] name = "notify-types" -version = "2.0.0" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e0826a989adedc2a244799e823aece04662b66609d96af8dff7ac6df9a8925d" +checksum = "42b8cfee0e339a0337359f3c88165702ac6e600dc01c0cc9579a92d62b08477a" +dependencies = [ + "bitflags 2.11.0", +] [[package]] name = "ntapi" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8a3895c6391c39d7fe7ebc444a87eb2991b2a0bc718fdabd071eec617fc68e4" +checksum = "c3b335231dfd352ffb0f8017f3b6027a4917f7df785ea2143d8af2adc66980ae" dependencies = [ "winapi", ] [[package]] name = "nu-ansi-term" -version = "0.46.0" +version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "overload", - "winapi", + "windows-sys 0.61.2", ] [[package]] name = "num-bigint" -version = "0.4.5" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c165a9ab64cf766f73521c0dd2cfdff64f488b8f0b3e621face3462d3db536d7" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ "num-integer", "num-traits", @@ -2772,11 +3219,10 @@ dependencies = [ [[package]] name = "num-bigint-dig" -version = "0.8.4" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" +checksum = "e661dda6640fad38e827a6d4a310ff4763082116fe217f279885c97f511bb0b7" dependencies = [ - "byteorder", "lazy_static", "libm", "num-integer", @@ -2835,9 +3281,9 @@ dependencies = [ [[package]] name = "num_cpus" -version = "1.16.0" +version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" dependencies = [ "hermit-abi", "libc", @@ -2845,23 +3291,24 @@ dependencies = [ [[package]] name = "num_enum" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" dependencies = [ "num_enum_derive", + "rustversion", ] [[package]] name = "num_enum_derive" -version = "0.7.2" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -2870,7 +3317,7 @@ version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", ] [[package]] @@ -2885,18 +3332,24 @@ dependencies = [ [[package]] name = "object" -version = "0.32.2" +version = "0.37.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" dependencies = [ "memchr", ] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" [[package]] name = "oneshot-fused-workaround" @@ -2909,9 +3362,9 @@ dependencies = [ [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "opaque-debug" @@ -2919,6 +3372,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "951c002c75e16ea2c65b8c7e4d3d51d5530d8dfa7d060b4776828c88cfb18ecf" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + +[[package]] +name = "openssl-probe" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c87def4c32ab89d880effc9e097653c8da5d6ef28e6b539d313baaacfbafcbe" + +[[package]] +name = "openssl-sys" +version = "0.9.112" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57d55af3b3e226502be1526dfdba67ab0e9c96fc293004e79576b2b9edb0dbdb" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2961,6 +3458,40 @@ dependencies = [ "zip32", ] +[[package]] +name = "orchard" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c01cd4ea711aab5f263f2b7aa6966687a2d6c7df4f78eb1b97a66a7a4e78e3b" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "core2", + "ff", + "fpe", + "getset", + "group", + "halo2_poseidon", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.5", + "rand_core 0.6.4", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "ordered-float" version = "2.10.1" @@ -2979,12 +3510,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "overload" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39" - [[package]] name = "p256" version = "0.13.2" @@ -2994,19 +3519,19 @@ dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] name = "p384" -version = "0.13.0" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70786f51bcc69f6a4c0360e063a4cac5419ef7c5cd5b3c99ad70f3be5ba79209" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" dependencies = [ "ecdsa", "elliptic-curve", "primeorder", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -3020,7 +3545,7 @@ dependencies = [ "elliptic-curve", "primeorder", "rand_core 0.6.4", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] @@ -3034,15 +3559,15 @@ dependencies = [ [[package]] name = "parking" -version = "2.2.0" +version = "2.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" [[package]] name = "parking_lot" -version = "0.12.3" +version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" dependencies = [ "lock_api", "parking_lot_core", @@ -3050,15 +3575,15 @@ dependencies = [ [[package]] name = "parking_lot_core" -version = "0.9.10" +version = "0.9.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" dependencies = [ "cfg-if", "libc", - "redox_syscall", + "redox_syscall 0.5.18", "smallvec", - "windows-targets 0.52.6", + "windows-link 0.2.1", ] [[package]] @@ -3115,7 +3640,7 @@ dependencies = [ "incrementalmerkletree", "jubjub", "nonempty", - "orchard", + "orchard 0.11.0", "pasta_curves", "postcard", "rand_core 0.6.4", @@ -3125,7 +3650,7 @@ dependencies = [ "secp256k1", "serde", "serde_with", - "sha2 0.10.8", + "sha2 0.10.9", "shardtree", "zcash_note_encryption", "zcash_primitives", @@ -3147,18 +3672,29 @@ dependencies = [ [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "petgraph" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "3672b37090dbd86368a4145bc067582552b29c27377cad4e0a306c97f9bd7772" +dependencies = [ + "fixedbitset", + "indexmap 2.13.1", +] [[package]] name = "petgraph" -version = "0.6.5" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" +checksum = "8701b58ea97060d5e5b155d383a69952a60943f0e6dfe30b04c287beb0b27455" dependencies = [ "fixedbitset", - "indexmap 2.12.0", + "hashbrown 0.15.5", + "indexmap 2.13.1", ] [[package]] @@ -3192,7 +3728,7 @@ dependencies = [ "phf_shared", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -3206,35 +3742,37 @@ dependencies = [ [[package]] name = "pin-project" -version = "1.1.5" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" +checksum = "f1749c7ed4bcaf4c3d0a3efc28538844fb29bcdd7d2b67b2be7e20ba861ff517" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" -version = "1.1.5" +version = "1.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" +checksum = "d9b20ed30f105399776b9c883e68e536ef602a16ae6f596d2c473591d6ad64c6" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" [[package]] -name = "pin-utils" +name = "pir-types" version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "serde", +] [[package]] name = "pkcs1" @@ -3259,15 +3797,21 @@ dependencies = [ [[package]] name = "pkg-config" -version = "0.3.30" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" [[package]] name = "plotters" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2c224ba00d7cadd4d5c660deaf2098e5e80e07846537c51f9cfa4be50c1fd45" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -3278,15 +3822,15 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e76628b4d3a7581389a35d5b6e2139607ad7c75b17aed325f210aa91f4a9609" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.5" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38f6d39893cca0701371e3c27294f09797214b86f1fb951b89ade8ec04e2abab" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] @@ -3314,14 +3858,14 @@ dependencies = [ "parking_lot", "pin-project", "static_assertions", - "thiserror 1.0.63", + "thiserror 1.0.69", ] [[package]] name = "postcard" -version = "1.1.1" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "170a2601f67cc9dba8edd8c4870b15f71a6a2dc196daec8c83f72b59dff628a8" +checksum = "6764c3b5dd454e283a30e6dfe78e9b31096d9e32036b5d1eaac7a6119ccb9a24" dependencies = [ "cobs", "embedded-io 0.4.0", @@ -3329,6 +3873,15 @@ dependencies = [ "serde", ] +[[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" @@ -3355,23 +3908,26 @@ dependencies = [ "spin 0.10.0", "symbolic-demangle", "tempfile", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "prettyplease" -version = "0.2.20" +version = "0.2.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f12335488a2f3b0a83b14edad48dca9879ce89b2edd10e80237e4e852dd645e" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" dependencies = [ "proc-macro2", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -3395,23 +3951,22 @@ dependencies = [ [[package]] name = "priority-queue" -version = "2.1.1" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "714c75db297bc88a63783ffc6ab9f830698a6705aa0201416931759ef4c8183d" +checksum = "93980406f12d9f8140ed5abe7155acb10bb1e69ea55c88960b9c2f117445ef96" dependencies = [ - "autocfg", "equivalent", - "indexmap 2.12.0", + "indexmap 2.13.1", + "serde", ] [[package]] name = "proc-macro-crate" -version = "1.3.1" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" dependencies = [ - "once_cell", - "toml_edit 0.19.15", + "toml_edit 0.25.11+spec-1.1.0", ] [[package]] @@ -3433,33 +3988,33 @@ dependencies = [ "proc-macro-error-attr2", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "proc-macro2" -version = "1.0.103" +version = "1.0.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ee95bc4ef87b8d5ba32e8b7714ccc834865276eab0aed5c9958d00ec45f49e8" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" dependencies = [ "unicode-ident", ] [[package]] name = "proptest" -version = "1.4.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "31b476131c3c86cb68032fdc5cb6d5a1045e3e42d96b69fa599fd77701e1f5bf" +checksum = "14cae93065090804185d3b75f0bf93b8eeda30c7a9b4a33d3bdb3988d6229e50" dependencies = [ "bit-set", "bit-vec", - "bitflags 2.9.0", + "bitflags 2.11.0", "lazy_static", "num-traits", "rand 0.8.5", "rand_chacha 0.3.1", "rand_xorshift", - "regex-syntax 0.8.5", + "regex-syntax", "rusty-fork", "tempfile", "unarray", @@ -3467,66 +4022,117 @@ dependencies = [ [[package]] name = "prost" -version = "0.14.1" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" +dependencies = [ + "bytes", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7231bd9b3d3d33c86b58adbac74b5ec0ad9f496b19d22801d773636feaa95f3d" +checksum = "d2ea70524a2f82d518bce41317d0fae74151505651af45faf1ffbd6fd33f0568" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.14.3", ] [[package]] name = "prost-build" -version = "0.14.1" +version = "0.13.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac6c3320f9abac597dcbc668774ef006702672474aad53c6d596b62e487b40b1" +checksum = "be769465445e8c1474e9c5dac2018218498557af32d9ed057325ec9a41ae81bf" dependencies = [ "heck", "itertools 0.14.0", "log", "multimap", "once_cell", - "petgraph", + "petgraph 0.7.1", + "prettyplease", + "prost 0.13.5", + "prost-types 0.13.5", + "regex", + "syn 2.0.117", + "tempfile", +] + +[[package]] +name = "prost-build" +version = "0.14.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "343d3bd7056eda839b03204e68deff7d1b13aba7af2b2fd16890697274262ee7" +dependencies = [ + "heck", + "itertools 0.14.0", + "log", + "multimap", + "petgraph 0.8.3", "prettyplease", - "prost", - "prost-types", + "prost 0.14.3", + "prost-types 0.14.3", "regex", - "syn 2.0.100", + "syn 2.0.117", "tempfile", ] [[package]] name = "prost-derive" -version = "0.14.1" +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-derive" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9120690fafc389a67ba3803df527d0ec9cbbc9cc45e4cc20b332996dfb672425" +checksum = "27c6023962132f4b30eb4c172c91ce92d933da334c59c23cddee82358ddafb0b" dependencies = [ "anyhow", "itertools 0.14.0", "proc-macro2", "quote", - "syn 2.0.100", + "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 0.13.5", ] [[package]] name = "prost-types" -version = "0.14.1" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b4db3d6da204ed77bb26ba83b6122a73aeb2e87e25fbf7ad2e84c4ccbf8f72" +checksum = "8991c4cbdb8bc5b11f0b074ffe286c30e523de90fee5ba8132f1399f23cb3dd7" dependencies = [ - "prost", + "prost 0.14.3", ] [[package]] name = "pwd-grp" -version = "1.0.0" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b94fdf3867b7f2889a736f0022ea9386766280d2cca4bdbe41629ada9e4f3b8f" +checksum = "0e2023f41b5fcb7c30eb5300a5733edfaa9e0e0d502d51b586f65633fd39e40c" dependencies = [ - "derive-deftly 0.14.2", + "derive-deftly", "libc", "paste", - "thiserror 1.0.63", + "thiserror 2.0.18", ] [[package]] @@ -3546,9 +4152,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.36" +version = "1.0.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924" dependencies = [ "proc-macro2", ] @@ -3559,6 +4165,12 @@ 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" @@ -3583,7 +4195,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" dependencies = [ "rand_chacha 0.9.0", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3603,7 +4215,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", - "rand_core 0.9.3", + "rand_core 0.9.5", ] [[package]] @@ -3612,14 +4224,14 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", ] [[package]] name = "rand_core" -version = "0.9.3" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" dependencies = [ "getrandom 0.3.4", ] @@ -3641,7 +4253,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b16df48f071248e67b8fc5e866d9448d45c08ad8b672baaaf796e2f15e606ff0" dependencies = [ "libc", - "rand_core 0.9.3", + "rand_core 0.9.5", "winapi", ] @@ -3656,9 +4268,9 @@ dependencies = [ [[package]] name = "rayon" -version = "1.10.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" dependencies = [ "either", "rayon-core", @@ -3666,9 +4278,9 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.12.1" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" dependencies = [ "crossbeam-deque", "crossbeam-utils", @@ -3697,7 +4309,7 @@ dependencies = [ "pasta_curves", "rand_core 0.6.4", "serde", - "thiserror 1.0.63", + "thiserror 1.0.69", "zeroize", ] @@ -3709,106 +4321,149 @@ checksum = "89b0ac1bc6bb3696d2c6f52cff8fba57238b81da8c0214ee6cd146eb8fde364e" dependencies = [ "rand_core 0.6.4", "reddsa", - "thiserror 1.0.63", + "thiserror 1.0.69", "zeroize", ] [[package]] name = "redox_syscall" -version = "0.5.1" +version = "0.5.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "469052894dcb553421e483e4209ee581a45100d31b4018de03e5a7ad86374a7e" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", ] [[package]] -name = "redox_users" -version = "0.4.5" +name = "redox_syscall" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd283d9651eeda4b2a83a43c1c91b266c40fd76ecd39a50a8c630ae69dc72891" +checksum = "6ce70a74e890531977d37e532c34d45e9055d2409ed08ddba14529471ed0be16" dependencies = [ - "getrandom 0.2.15", - "libredox", - "thiserror 1.0.63", + "bitflags 2.11.0", ] [[package]] name = "redox_users" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd6f9d3d47bdd2ad6945c5015a226ec6155d0bcdfd8f7cd29f86b71f8de99d2b" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ - "getrandom 0.2.15", + "getrandom 0.2.17", "libredox", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] -name = "regex" -version = "1.11.1" +name = "ref-cast" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" dependencies = [ - "aho-corasick", - "memchr", - "regex-automata 0.4.9", - "regex-syntax 0.8.5", + "ref-cast-impl", ] [[package]] -name = "regex-automata" -version = "0.1.10" +name = "ref-cast-impl" +version = "1.0.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" dependencies = [ - "regex-syntax 0.6.29", + "proc-macro2", + "quote", + "syn 2.0.117", ] [[package]] -name = "regex-automata" -version = "0.4.9" +name = "regex" +version = "1.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276" dependencies = [ "aho-corasick", "memchr", - "regex-syntax 0.8.5", + "regex-automata", + "regex-syntax", ] [[package]] -name = "regex-syntax" -version = "0.6.29" +name = "regex-automata" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] [[package]] name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "retry-error" -version = "0.8.0" +version = "0.8.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b295404fa4a9e1e63537ccbd4e4b6309d9688bd70608ddc16d3b8af0389a673a" +checksum = "dc897dd8d9e8bd1ed8cdad82b5966c3e0ecae09fb1907d58efaa013543185d0a" [[package]] -name = "rfc6979" -version = "0.4.0" +name = "reqwest" +version = "0.12.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ - "hmac 0.12.1", + "base64", + "bytes", + "encoding_rs", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "mime", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tower 0.5.3", + "tower-http 0.6.8", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "retry-error" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b295404fa4a9e1e63537ccbd4e4b6309d9688bd70608ddc16d3b8af0389a673a" + +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac 0.12.1", "subtle", ] [[package]] name = "rgb" -version = "0.8.50" +version = "0.8.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a" +checksum = "47b34b781b31e5d73e9fbc8689c70551fd1ade9a19e3e28cfec8580a79290cc4" dependencies = [ "bytemuck", ] @@ -3821,7 +4476,7 @@ checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" dependencies = [ "cc", "cfg-if", - "getrandom 0.2.15", + "getrandom 0.2.17", "libc", "untrusted", "windows-sys 0.52.0", @@ -3847,9 +4502,9 @@ dependencies = [ [[package]] name = "rsa" -version = "0.9.6" +version = "0.9.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e5124fcb30e76a7e79bfee683a2746db83784b86289f6251b54b7950a0dfc" +checksum = "b8573f03f5883dcaebdfcf4725caa1ecb9c15b2ef50c43a07b816e06799bb12d" dependencies = [ "const-oid", "digest 0.10.7", @@ -3859,7 +4514,7 @@ dependencies = [ "pkcs1", "pkcs8", "rand_core 0.6.4", - "sha2 0.10.8", + "sha2 0.10.9", "signature", "spki", "subtle", @@ -3872,7 +4527,7 @@ version = "0.37.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "fallible-iterator", "fallible-streaming-iterator", "hashlink", @@ -3884,26 +4539,27 @@ dependencies = [ [[package]] name = "rust_decimal" -version = "1.35.0" +version = "1.41.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1790d1c4c0ca81211399e0e0af16333276f375209e71a37b67698a373db5b47a" +checksum = "2ce901f9a19d251159075a4c37af514c3b8ef99c22e02dd8c19161cf397ee94a" dependencies = [ "arrayvec", "num-traits", "serde", + "wasm-bindgen", ] [[package]] name = "rustc-demangle" -version = "0.1.24" +version = "0.1.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "b50b8869d9fc858ce7266cce0194bd74df58b9d0e3f6df3a9fc8eb470d95c09d" [[package]] name = "rustc_version" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" dependencies = [ "semver", ] @@ -3919,28 +4575,15 @@ dependencies = [ [[package]] name = "rustix" -version = "0.38.34" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" -dependencies = [ - "bitflags 2.9.0", - "errno", - "libc", - "linux-raw-sys 0.4.13", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustix" -version = "1.1.2" +version = "1.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd15f8a2c5551a84d56efdc1cd049089e409ac19a3072d5037a17fd70719ff3e" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" dependencies = [ - "bitflags 2.9.0", + "bitflags 2.11.0", "errno", "libc", - "linux-raw-sys 0.11.0", - "windows-sys 0.60.2", + "linux-raw-sys", + "windows-sys 0.61.2", ] [[package]] @@ -3957,24 +4600,48 @@ dependencies = [ [[package]] name = "rustls" -version = "0.23.25" +version = "0.23.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +checksum = "758025cb5fccfd3bc2fd74708fd4682be41d99e5dff73c377c0646c6012c73a4" dependencies = [ "log", "once_cell", "ring", "rustls-pki-types", - "rustls-webpki 0.103.1", + "rustls-webpki 0.103.10", "subtle", "zeroize", ] +[[package]] +name = "rustls-native-certs" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "612460d5f7bea540c490b2b6395d8e34a953e52b491accd6c86c8164c5932a63" +dependencies = [ + "openssl-probe", + "rustls-pki-types", + "schannel", + "security-framework", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + [[package]] name = "rustls-pki-types" -version = "1.11.0" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" +dependencies = [ + "zeroize", +] [[package]] name = "rustls-webpki" @@ -3988,9 +4655,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.1" +version = "0.103.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +checksum = "df33b2b81ac578cabaf06b89b0631153a3f416b0a886e8a7a1707fb51abbd1ef" dependencies = [ "ring", "rustls-pki-types", @@ -3999,15 +4666,15 @@ dependencies = [ [[package]] name = "rustversion" -version = "1.0.17" +version = "1.0.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" +checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d" [[package]] name = "rusty-fork" -version = "0.3.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +checksum = "cc6bf79ff24e648f6da1f8d1f011e9cac26491b619e6b9280f2b47f1774e6ee2" dependencies = [ "fnv", "quick-error", @@ -4017,9 +4684,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" [[package]] name = "safelog" @@ -4031,7 +4698,7 @@ dependencies = [ "educe", "either", "fluid-let", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -4086,6 +4753,39 @@ dependencies = [ "zip32", ] +[[package]] +name = "schannel" +version = "0.1.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91c1b7e4904c873ef0710c1f407dde2e6287de2bebc1bbbf7d430bb7cbffd939" +dependencies = [ + "windows-sys 0.61.2", +] + +[[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 = "schemerz" version = "0.2.0" @@ -4095,7 +4795,7 @@ dependencies = [ "daggy", "indexmap 1.9.3", "log", - "thiserror 1.0.63", + "thiserror 1.0.69", "uuid", ] @@ -4168,11 +4868,34 @@ dependencies = [ "zeroize", ] +[[package]] +name = "security-framework" +version = "3.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f4bc775c73d9a02cde8bf7b2ec4c9d12743edf609006c7facc23998404cd1d" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.10.1", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2691df843ecc5d231c0b14ece2acc3efb62c0a398c7e1d875f3983ce020e3" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "semver" -version = "1.0.23" +version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" [[package]] name = "serde" @@ -4211,60 +4934,87 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "serde_ignored" -version = "0.1.10" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8e319a36d1b52126a0d608f24e93b2d81297091818cd70625fcf50a15d84ddf" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" dependencies = [ "serde", + "serde_core", ] [[package]] name = "serde_json" -version = "1.0.120" +version = "1.0.149" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +checksum = "83fc039473c5595ace860d8c4fafa220ff474b3fc6bfdb4293327f1a37e94d86" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" dependencies = [ "itoa", - "ryu", "serde", + "serde_core", ] [[package]] name = "serde_spanned" -version = "0.6.8" +version = "0.6.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" dependencies = [ "serde", ] [[package]] name = "serde_spanned" -version = "1.0.3" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e24345aa0fe688594e73770a5f6d1b216508b4f93484c0026d521acd30134392" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" dependencies = [ "serde_core", ] +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + [[package]] name = "serde_with" -version = "3.8.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad483d2ab0149d5a5ebcd9972a3852711e0153d863bf5a5d0391d28883c4a20" +checksum = "381b283ce7bc6b476d903296fb59d0d36633652b633b27f64db4fb46dcbfc3b9" dependencies = [ "base64", "chrono", "hex", "indexmap 1.9.3", - "indexmap 2.12.0", - "serde", - "serde_derive", + "indexmap 2.13.1", + "schemars 0.9.0", + "schemars 1.2.1", + "serde_core", "serde_json", "serde_with_macros", "time", @@ -4272,21 +5022,21 @@ dependencies = [ [[package]] name = "serde_with_macros" -version = "3.8.1" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65569b702f41443e8bc8bbb1c5779bd0450bbe723b56198980e80ec45780bce2" +checksum = "a6d4e30573c8cb306ed6ab1dca8423eec9a463ea0e155f45399455e0368b27e0" dependencies = [ - "darling 0.20.10", + "darling 0.21.3", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "sha1" -version = "0.10.5" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f04293dc80c3993519f2d7f6f511707ee7094fe0c6d3406feb330cdb3540eba3" +checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", @@ -4295,9 +5045,9 @@ dependencies = [ [[package]] name = "sha2" -version = "0.10.8" +version = "0.10.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" dependencies = [ "cfg-if", "cpufeatures", @@ -4341,7 +5091,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "359e552886ae54d1642091645980d83f7db465fd9b5b0248e3680713c1773388" dependencies = [ "assert_matches", - "bitflags 2.9.0", + "bitflags 2.11.0", "either", "incrementalmerkletree", "incrementalmerkletree-testing", @@ -4351,12 +5101,12 @@ dependencies = [ [[package]] name = "shellexpand" -version = "3.1.0" +version = "3.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da03fa3b94cc19e3ebfc88c4229c49d8f08cdbd1228870a45f0ffdf84988e14b" +checksum = "32824fab5e16e6c4d86dc1ba84489390419a39f97699852b66480bb87d297ed8" dependencies = [ "bstr", - "dirs 5.0.1", + "dirs", "os_str_bytes", ] @@ -4368,10 +5118,11 @@ checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] name = "signal-hook-registry" -version = "1.4.1" +version = "1.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8229b473baa5980ac72ef434c4415e70c4b5e71b423043adb4ba059f89c99a1" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" dependencies = [ + "errno", "libc", ] @@ -4385,6 +5136,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "sinsemilla" version = "0.1.0" @@ -4398,24 +5155,21 @@ dependencies = [ [[package]] name = "siphasher" -version = "1.0.1" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" +checksum = "b2aa850e253778c88a04c3d7323b043aeda9d3e30d5971937c1855769763678e" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" [[package]] name = "slotmap" -version = "1.0.7" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" +checksum = "bdd58c3c93c3d278ca835519292445cb4b0d4dc59ccfdf7ceadaab3f8aeb4038" dependencies = [ "serde", "version_check", @@ -4430,21 +5184,21 @@ dependencies = [ "paste", "serde", "slotmap", - "thiserror 2.0.12", + "thiserror 2.0.18", "void", ] [[package]] name = "smallvec" -version = "1.13.2" +version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "socket2" -version = "0.5.9" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" dependencies = [ "libc", "windows-sys 0.52.0", @@ -4452,12 +5206,12 @@ dependencies = [ [[package]] name = "socket2" -version = "0.6.1" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17129e116933cf371d018bb80ae557e889637989d8638274fb25622827b03881" +checksum = "3a766e1110788c36f4fa1c2b71b387a7815aa65f88ce0229841826633d93723e" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4503,14 +5257,14 @@ checksum = "eb9242b9ef4108a78e8cd1a2c98e193ef372437f8c22be363075233321dd4a15" dependencies = [ "base64ct", "pem-rfc7468", - "sha2 0.10.8", + "sha2 0.10.9", ] [[package]] name = "ssh-key" -version = "0.6.6" +version = "0.6.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ca9b366a80cf18bb6406f4cf4d10aebfb46140a8c0c33f666a144c5c76ecbafc" +checksum = "3b86f5297f0f04d08cabaa0f6bff7cb6aec4d9c3b49d87990d63da9d9156a8c3" dependencies = [ "num-bigint-dig", "p256", @@ -4519,7 +5273,7 @@ dependencies = [ "rand_core 0.6.4", "rsa", "sec1", - "sha2 0.10.8", + "sha2 0.10.9", "signature", "ssh-cipher", "ssh-encoding", @@ -4529,9 +5283,9 @@ dependencies = [ [[package]] name = "stable_deref_trait" -version = "1.2.0" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" [[package]] name = "static_assertions" @@ -4557,35 +5311,13 @@ 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.100", + "strum_macros", ] [[package]] @@ -4597,7 +5329,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -4608,9 +5340,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "symbolic-common" -version = "12.13.3" +version = "12.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13a4dfe4bbeef59c1f32fc7524ae7c95b9e1de5e79a43ce1604e181081d71b0c" +checksum = "52ca086c1eb5c7ee74b151ba83c6487d5d33f8c08ad991b86f3f58f6629e68d5" dependencies = [ "debugid", "memmap2", @@ -4620,9 +5352,9 @@ dependencies = [ [[package]] name = "symbolic-demangle" -version = "12.13.3" +version = "12.17.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cf6a95abff97de4d7ff3473f33cacd38f1ddccad5c1feab435d6760300e3b6" +checksum = "baa911a28a62823aaf2cc2e074212492a3ee69d0d926cc8f5b12b4a108ff5c0c" dependencies = [ "cpp_demangle", "rustc-demangle", @@ -4642,9 +5374,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.100" +version = "2.0.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99" dependencies = [ "proc-macro2", "quote", @@ -4656,16 +5388,19 @@ name = "sync_wrapper" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] [[package]] name = "synstructure" -version = "0.13.1" +version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -4682,6 +5417,27 @@ dependencies = [ "windows", ] +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.11.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "tap" version = "1.0.1" @@ -4690,71 +5446,71 @@ checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tempfile" -version = "3.10.1" +version = "3.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" dependencies = [ - "cfg-if", "fastrand", - "rustix 0.38.34", - "windows-sys 0.52.0", + "getrandom 0.4.2", + "once_cell", + "rustix", + "windows-sys 0.61.2", ] [[package]] name = "thiserror" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0342370b38b6a11b6cc11d6a805569958d54cfa061a29969c3b5ce2ea405724" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" dependencies = [ - "thiserror-impl 1.0.63", + "thiserror-impl 1.0.69", ] [[package]] name = "thiserror" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.12", + "thiserror-impl 2.0.18", ] [[package]] name = "thiserror-impl" -version = "1.0.63" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4558b58466b9ad7ca0f102865eccc95938dca1a74a856f2b57b6629050da261" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "thiserror-impl" -version = "2.0.12" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "thread_local" -version = "1.1.8" +version = "1.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" dependencies = [ "cfg-if", - "once_cell", ] [[package]] name = "time" -version = "0.3.36" +version = "0.3.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +checksum = "35e7868883861bd0e56d9ac6efcaaca0d6d5d82a2a7ec8209ff492c07cf37b21" dependencies = [ "deranged", "itoa", @@ -4773,9 +5529,9 @@ checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" [[package]] name = "time-macros" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +checksum = "2834e6017e3e5e4b9834939793b282bc03b37a3336245fa820e35e233e2a85de" dependencies = [ "num-conv", "time-core", @@ -4783,11 +5539,12 @@ dependencies = [ [[package]] name = "tinystr" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d4f6d1145dcb577acf783d4e601bc1d76a13337bb54e6233add580b07344c8b" +checksum = "c8323304221c2a851516f22236c5722a72eaa19749016521d6dff0824447d96d" dependencies = [ "displaydoc", + "serde_core", "zerovec", ] @@ -4803,9 +5560,9 @@ dependencies = [ [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "3e61e67053d25a4e82c844e8424039d9745781b3fc4f32b8d55ed50f5f667ef3" dependencies = [ "tinyvec_macros", ] @@ -4818,48 +5575,57 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.37.0" +version = "1.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" +checksum = "2bd1c4c0fc4a7ab90fc15ef6daaa3ec3b893f004f915f2392557ed23237820cd" dependencies = [ - "backtrace", "bytes", "libc", - "mio 0.8.11", - "num_cpus", + "mio", + "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.5.9", + "socket2 0.6.3", "tokio-macros", - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] name = "tokio-macros" -version = "2.2.0" +version = "2.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", ] [[package]] name = "tokio-rustls" -version = "0.26.2" +version = "0.26.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" dependencies = [ - "rustls 0.23.25", + "rustls 0.23.37", "tokio", ] [[package]] name = "tokio-stream" -version = "0.1.17" +version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" dependencies = [ "futures-core", "pin-project-lite", @@ -4868,9 +5634,9 @@ dependencies = [ [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "9ae9cec805b01e8fc3fd2fe289f89149a9b66dd16786abd8b19cfa7b48cb0098" dependencies = [ "bytes", "futures-core", @@ -4882,93 +5648,143 @@ dependencies = [ [[package]] name = "toml" -version = "0.8.19" +version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +checksum = "dc1beb996b9d83529a9e75c17a1686767d148d70663143c7854d8b4a09ced362" dependencies = [ "serde", - "serde_spanned 0.6.8", - "toml_datetime 0.6.8", - "toml_edit 0.22.22", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_edit 0.22.27", ] [[package]] name = "toml" -version = "0.9.8" +version = "0.9.12+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0dc8b1fb61449e27716ec0e1bdf0f6b8f3e8f6b05391e8497b8b6d7804ea6d8" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.1", "serde_core", - "serde_spanned 1.0.3", - "toml_datetime 0.7.3", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", "toml_parser", "toml_writer", - "winnow 0.7.13", + "winnow 0.7.15", ] [[package]] name = "toml_datetime" -version = "0.6.8" +version = "0.6.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +checksum = "22cddaf88f4fbc13c51aebbf5f8eceb5c7c5a9da2ac40a13519eb5b0a0e8f11c" dependencies = [ "serde", ] [[package]] name = "toml_datetime" -version = "0.7.3" +version = "0.7.5+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2cdb639ebbc97961c51720f858597f7f24c4fc295327923af55b74c3c724533" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" dependencies = [ "serde_core", ] [[package]] -name = "toml_edit" -version = "0.19.15" +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" dependencies = [ - "indexmap 2.12.0", - "toml_datetime 0.6.8", - "winnow 0.5.40", + "serde_core", ] [[package]] name = "toml_edit" -version = "0.22.22" +version = "0.22.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +checksum = "41fe8c660ae4257887cf66394862d21dbca4a6ddd26f04a3560410406a2f819a" dependencies = [ - "indexmap 2.12.0", + "indexmap 2.13.1", "serde", - "serde_spanned 0.6.8", - "toml_datetime 0.6.8", - "winnow 0.6.20", + "serde_spanned 0.6.9", + "toml_datetime 0.6.11", + "toml_write", + "winnow 0.7.15", +] + +[[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.13.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.1", ] [[package]] name = "toml_parser" -version = "1.0.4" +version = "1.1.2+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0cbe268d35bdb4bb5a56a2de88d0ad0eb70af5384a99d648cd4b3d04039800e" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" dependencies = [ - "winnow 0.7.13", + "winnow 1.0.1", ] +[[package]] +name = "toml_write" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" + [[package]] name = "toml_writer" -version = "1.0.4" +version = "1.1.1+spec-1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df8b2b54733674ad286d16267dcfc7a71ed5c776e4ac7aa3c3e2561f7c637bf2" +checksum = "756daf9b1013ebe47a8776667b466417e2d4c5679d441c26230efd9ef78692db" + +[[package]] +name = "tonic" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877c5b330756d856ffcc4553ab34a5684481ade925ecc54bcd1bf02b1d0d4d52" +dependencies = [ + "async-stream", + "async-trait", + "axum", + "base64", + "bytes", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "prost 0.13.5", + "rustls-native-certs", + "rustls-pemfile", + "socket2 0.5.10", + "tokio", + "tokio-rustls", + "tokio-stream", + "tower 0.4.13", + "tower-layer", + "tower-service", + "tracing", +] [[package]] name = "tonic" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eb7613188ce9f7df5bfe185db26c5814347d110db17920415cf2fbcad85e7203" +checksum = "fec7c61a0695dc1887c1b53952990f3ad2e3a31453e1f49f10e75424943a93ec" dependencies = [ "async-trait", "base64", @@ -4982,55 +5798,69 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "socket2 0.6.1", + "socket2 0.6.3", "sync_wrapper", "tokio", "tokio-rustls", "tokio-stream", - "tower", + "tower 0.5.3", "tower-layer", "tower-service", "tracing", - "webpki-roots 1.0.3", + "webpki-roots 1.0.6", +] + +[[package]] +name = "tonic-build" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9557ce109ea773b399c9b9e5dca39294110b74f1f342cb347a80d1fce8c26a11" +dependencies = [ + "prettyplease", + "proc-macro2", + "prost-build 0.13.5", + "prost-types 0.13.5", + "quote", + "syn 2.0.117", ] [[package]] name = "tonic-build" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c40aaccc9f9eccf2cd82ebc111adc13030d23e887244bc9cfa5d1d636049de3" +checksum = "1882ac3bf5ef12877d7ed57aad87e75154c11931c2ba7e6cde5e22d63522c734" dependencies = [ "prettyplease", "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "tonic-prost" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "66bd50ad6ce1252d87ef024b3d64fe4c3cf54a86fb9ef4c631fdd0ded7aeaa67" +checksum = "a55376a0bbaa4975a3f10d009ad763d8f4108f067c7c2e74f3001fb49778d309" dependencies = [ "bytes", - "prost", - "tonic", + "prost 0.14.3", + "tonic 0.14.5", ] [[package]] name = "tonic-prost-build" -version = "0.14.2" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4a16cba4043dc3ff43fcb3f96b4c5c154c64cbd18ca8dce2ab2c6a451d058a2" +checksum = "f3144df636917574672e93d0f56d7edec49f90305749c668df5101751bb8f95a" dependencies = [ "prettyplease", "proc-macro2", - "prost-build", - "prost-types", + "prost-build 0.14.3", + "prost-types 0.14.3", "quote", - "syn 2.0.100", + "syn 2.0.117", "tempfile", - "tonic-build", + "tonic-build 0.14.5", ] [[package]] @@ -5039,13 +5869,13 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cad5e568ad4e025a68aa0395a146247609dd5b6d8c2141255f5e4f367e7fda8a" dependencies = [ - "derive-deftly 1.3.0", + "derive-deftly", "educe", "futures", "oneshot-fused-workaround", "pin-project", "postage", - "thiserror 2.0.12", + "thiserror 2.0.18", "void", ] @@ -5065,7 +5895,7 @@ dependencies = [ "serde", "slab", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", ] [[package]] @@ -5075,12 +5905,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fc7fb465ba671ee1486d8bd1e0a8f546887c2ce034004c4c9b03a6227e1c381" dependencies = [ "bytes", - "derive-deftly 1.3.0", + "derive-deftly", "digest 0.10.7", "educe", "getrandom 0.3.4", "safelog", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-error", "tor-llcrypto", "zeroize", @@ -5093,17 +5923,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79ba1b43f22fab2daee3e0c902f1455b3aed8e086b2d83d8c60b36523b173d25" dependencies = [ "amplify", - "bitflags 2.9.0", + "bitflags 2.11.0", "bytes", "caret", - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "educe", "itertools 0.14.0", "paste", "rand 0.9.2", "smallvec", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-cert", @@ -5126,7 +5956,7 @@ dependencies = [ "derive_builder_fork_arti", "derive_more", "digest 0.10.7", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-bytes", "tor-checkable", "tor-llcrypto", @@ -5149,7 +5979,7 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-cell", @@ -5176,7 +6006,7 @@ checksum = "7c9839e9bb302f17447c350e290bb107084aca86c640882a91522f2059f6a686" dependencies = [ "humantime", "signature", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-llcrypto", ] @@ -5189,7 +6019,7 @@ dependencies = [ "amplify", "async-trait", "cfg-if", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "downcast-rs", @@ -5205,7 +6035,7 @@ dependencies = [ "retry-error", "safelog", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-cell", @@ -5237,7 +6067,7 @@ checksum = "cb15df773842025010d885fbe862062ebaa342b799f9716273eaf733b92f2f45" dependencies = [ "amplify", "cfg-if", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "educe", "either", @@ -5252,9 +6082,9 @@ dependencies = [ "serde", "serde-value", "serde_ignored", - "strum 0.27.2", - "thiserror 2.0.12", - "toml 0.9.8", + "strum", + "thiserror 2.0.18", + "toml 0.9.12+spec-1.1.0", "tor-basic-utils", "tor-error", "tor-rtcompat", @@ -5271,7 +6101,7 @@ dependencies = [ "directories", "serde", "shellexpand", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-error", "tor-general-addr", ] @@ -5284,7 +6114,7 @@ checksum = "c1690438c1fc778fc7c89c132e529365b1430d6afe03aeecbc2508324807bf0b" dependencies = [ "digest 0.10.7", "hex", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-llcrypto", ] @@ -5304,7 +6134,7 @@ dependencies = [ "httpdate", "itertools 0.14.0", "memchr", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-circmgr", "tor-error", "tor-linkspec", @@ -5368,8 +6198,8 @@ dependencies = [ "serde_json", "signature", "static_assertions", - "strum 0.27.2", - "thiserror 2.0.12", + "strum", + "thiserror 2.0.18", "time", "tor-async-utils", "tor-basic-utils", @@ -5402,8 +6232,8 @@ dependencies = [ "paste", "retry-error", "static_assertions", - "strum 0.27.2", - "thiserror 2.0.12", + "strum", + "thiserror 2.0.18", "tracing", "void", ] @@ -5415,7 +6245,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c42cb5b5aec0584db2fba4a88c4e08fb09535ef61e4ef5674315a89e69ec31a2" dependencies = [ "derive_more", - "thiserror 2.0.12", + "thiserror 2.0.18", "void", ] @@ -5427,7 +6257,7 @@ checksum = "0585a83a4c56b4f31f6fa2965e2f9c490c9f4d29fba2fedb5a9ee71009f793c0" dependencies = [ "amplify", "base64ct", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "dyn-clone", @@ -5443,8 +6273,8 @@ dependencies = [ "rand 0.9.2", "safelog", "serde", - "strum 0.27.2", - "thiserror 2.0.12", + "strum", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-config", @@ -5469,7 +6299,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf9ee6e0dbec9ba11c3d046181a42dd4759e108de38e2b5927689edbdc458a51" dependencies = [ "data-encoding", - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "digest 0.10.7", "hex", @@ -5481,7 +6311,7 @@ dependencies = [ "serde", "signature", "subtle", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-error", @@ -5497,7 +6327,7 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5aa30066b80ade55a1b88a82b5320dfc50d1724918ad614ded8ecb4820c32062" dependencies = [ - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "downcast-rs", "paste", @@ -5505,7 +6335,7 @@ dependencies = [ "rsa", "signature", "ssh-key", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-bytes", "tor-cert", "tor-checkable", @@ -5522,7 +6352,7 @@ dependencies = [ "amplify", "arrayvec", "cfg-if", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "downcast-rs", @@ -5537,7 +6367,7 @@ dependencies = [ "serde", "signature", "ssh-key", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-config", @@ -5562,7 +6392,7 @@ dependencies = [ "base64ct", "by_address", "caret", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "hex", @@ -5570,8 +6400,8 @@ dependencies = [ "safelog", "serde", "serde_with", - "strum 0.27.2", - "thiserror 2.0.12", + "strum", + "thiserror 2.0.18", "tor-basic-utils", "tor-bytes", "tor-config", @@ -5591,7 +6421,7 @@ dependencies = [ "ctr", "curve25519-dalek", "der-parser", - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "digest 0.10.7", "ed25519-dalek", @@ -5601,18 +6431,18 @@ dependencies = [ "rand 0.9.2", "rand_chacha 0.9.0", "rand_core 0.6.4", - "rand_core 0.9.3", + "rand_core 0.9.5", "rand_jitter", "rdrand", "rsa", "safelog", "serde", "sha1", - "sha2 0.10.8", + "sha2 0.10.9", "sha3", "signature", "subtle", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-error", "tor-memquota", "visibility", @@ -5628,7 +6458,7 @@ checksum = "845d65304be6a614198027c4b2d1b35aaf073335c26df619d17e5f4027f2657f" dependencies = [ "futures", "humantime", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-error", "tor-rtcompat", "tracing", @@ -5642,7 +6472,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef375c3442a4ea74f0b6bf91a3eed660d55301b2e2f59b366aba4849b2321a6f" dependencies = [ "cfg-if", - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "dyn-clone", "educe", @@ -5654,7 +6484,7 @@ dependencies = [ "slotmap-careful", "static_assertions", "sysinfo", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-async-utils", "tor-basic-utils", "tor-config", @@ -5672,7 +6502,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "638b4e6507e3786488859d3c463fa73addbad4f788806c6972603727e527672e" dependencies = [ "async-trait", - "bitflags 2.9.0", + "bitflags 2.11.0", "derive_more", "futures", "humantime", @@ -5680,8 +6510,8 @@ dependencies = [ "num_enum", "rand 0.9.2", "serde", - "strum 0.27.2", - "thiserror 2.0.12", + "strum", + "thiserror 2.0.18", "tor-basic-utils", "tor-error", "tor-linkspec", @@ -5701,9 +6531,9 @@ checksum = "1dbc32d89e7ea2e2799168d0c453061647a727e39fc66f52e1bcb4c38c8dc433" dependencies = [ "amplify", "base64ct", - "bitflags 2.9.0", + "bitflags 2.11.0", "cipher", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "digest 0.10.7", @@ -5718,9 +6548,9 @@ dependencies = [ "serde_with", "signature", "smallvec", - "strum 0.27.2", + "strum", "subtle", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", "tinystr", "tor-basic-utils", @@ -5742,7 +6572,7 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59e41aea027686b05f21e0ad75aa2c0c9681a87f2f3130b6d6f7a7a8c06edd7b" dependencies = [ - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "filetime", "fs-mistrust", @@ -5754,7 +6584,7 @@ dependencies = [ "sanitize-filename", "serde", "serde_json", - "thiserror 2.0.12", + "thiserror 2.0.18", "time", "tor-async-utils", "tor-basic-utils", @@ -5778,7 +6608,7 @@ dependencies = [ "cipher", "coarsetime", "criterion-cycles-per-byte", - "derive-deftly 1.3.0", + "derive-deftly", "derive_builder_fork_arti", "derive_more", "digest 0.10.7", @@ -5794,14 +6624,14 @@ dependencies = [ "pin-project", "postage", "rand 0.9.2", - "rand_core 0.9.3", + "rand_core 0.9.5", "safelog", "slotmap-careful", "smallvec", "static_assertions", "subtle", "sync_wrapper", - "thiserror 2.0.12", + "thiserror 2.0.18", "tokio", "tokio-util", "tor-async-utils", @@ -5836,7 +6666,7 @@ dependencies = [ "caret", "paste", "serde_with", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-bytes", ] @@ -5874,8 +6704,8 @@ dependencies = [ "paste", "pin-project", "rustls-pki-types", - "rustls-webpki 0.103.1", - "thiserror 2.0.12", + "rustls-webpki 0.103.10", + "thiserror 2.0.18", "tokio", "tokio-util", "tor-error", @@ -5893,7 +6723,7 @@ dependencies = [ "amplify", "assert_matches", "async-trait", - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "educe", "futures", @@ -5903,8 +6733,8 @@ dependencies = [ "pin-project", "priority-queue", "slotmap-careful", - "strum 0.27.2", - "thiserror 2.0.12", + "strum", + "thiserror 2.0.18", "tor-error", "tor-general-addr", "tor-rtcompat", @@ -5921,11 +6751,11 @@ checksum = "0dbb9b68d9cf8e07eeafbca91ac11b7d9c4be1e674cb59830edfbac153333e7f" dependencies = [ "amplify", "caret", - "derive-deftly 1.3.0", + "derive-deftly", "educe", "safelog", "subtle", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-bytes", "tor-error", ] @@ -5936,22 +6766,42 @@ version = "0.35.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48139f001dd6f409325b7c190ebcea1033b27f09042543946ab7aa4ad286257b" dependencies = [ - "derive-deftly 1.3.0", + "derive-deftly", "derive_more", "serde", - "thiserror 2.0.12", + "thiserror 2.0.18", "tor-memquota", ] [[package]] name = "tower" -version = "0.5.2" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" +dependencies = [ + "futures-core", + "futures-util", + "indexmap 1.9.3", + "pin-project", + "pin-project-lite", + "rand 0.8.5", + "slab", + "tokio", + "tokio-util", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", - "indexmap 2.12.0", + "indexmap 2.13.1", "pin-project-lite", "slab", "sync_wrapper", @@ -5962,6 +6812,41 @@ dependencies = [ "tracing", ] +[[package]] +name = "tower-http" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9cd434a998747dd2c4276bc96ee2e0c7a2eadf3cae88e52be55a05fa9053f5" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "http", + "http-body", + "http-body-util", + "pin-project-lite", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tower-http" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4e6559d53cc268e5031cd8429d05415bc4cb4aefc4aa5d6cc35fbf5b924a1f8" +dependencies = [ + "bitflags 2.11.0", + "bytes", + "futures-util", + "http", + "http-body", + "iri-string", + "pin-project-lite", + "tower 0.5.3", + "tower-layer", + "tower-service", +] + [[package]] name = "tower-layer" version = "0.3.3" @@ -5976,10 +6861,11 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] name = "tracing" -version = "0.1.41" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ + "log", "pin-project-lite", "tracing-attributes", "tracing-core", @@ -5987,20 +6873,20 @@ dependencies = [ [[package]] name = "tracing-attributes" -version = "0.1.30" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81383ab64e72a7a8b8e13130c49e3dab29def6d0c7d76a03087b3cf71c5c6903" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "tracing-core" -version = "0.1.34" +version = "0.1.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9d12581f227e93f094d3af2ae690a574abb8a2b9b7a96e7cfe9647b2b617678" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" dependencies = [ "once_cell", "valuable", @@ -6019,14 +6905,14 @@ dependencies = [ [[package]] name = "tracing-subscriber" -version = "0.3.18" +version = "0.3.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad0f048c97dbd9faa9b7df56362b8ebcaa52adb06b498c050d2f4e32f90a7a8b" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" dependencies = [ "matchers", "nu-ansi-term", "once_cell", - "regex", + "regex-automata", "sharded-slab", "smallvec", "thread_local", @@ -6037,9 +6923,9 @@ dependencies = [ [[package]] name = "tracing-test" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "557b891436fe0d5e0e363427fc7f217abf9ccd510d5136549847bdcbcd011d68" +checksum = "19a4c448db514d4f24c5ddb9f73f2ee71bfb24c526cf0c570ba142d1119e0051" dependencies = [ "tracing-core", "tracing-subscriber", @@ -6048,12 +6934,12 @@ dependencies = [ [[package]] name = "tracing-test-macro" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04659ddb06c87d233c566112c1c9c5b9e98256d9af50ec3bc9c8327f873a7568" +checksum = "ad06847b7afb65c7866a36664b75c40b895e318cea4f71299f013fb22965329d" dependencies = [ "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -6064,7 +6950,7 @@ checksum = "70977707304198400eb4835a78f6a9f928bf41bba420deb8fdb175cd965d77a7" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -6075,9 +6961,9 @@ checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "typed-index-collections" -version = "3.3.0" +version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fd393dbd1e7b23e0cab7396570309b4068aa504e9dac2cd41d827583b4e9ab7" +checksum = "898160f1dfd383b4e92e17f0512a7d62f3c51c44937b23b6ffc3a1614a8eaccd" dependencies = [ "bincode", "serde", @@ -6085,9 +6971,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.17.0" +version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" [[package]] name = "uint" @@ -6118,24 +7004,24 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" [[package]] name = "unicode-normalization" -version = "0.1.24" +version = "0.1.25" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" dependencies = [ "tinyvec", ] [[package]] name = "unicode-segmentation" -version = "1.12.0" +version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +checksum = "9629274872b2bfaf8d66f5f15725007f635594914870f65218920345aa11aa8c" [[package]] name = "unicode-xid" @@ -6149,7 +7035,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" dependencies = [ - "crypto-common 0.1.6", + "crypto-common 0.1.7", "subtle", ] @@ -6157,29 +7043,55 @@ dependencies = [ name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "unty" +version = "0.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" + +[[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", + "serde", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "unty" -version = "0.0.4" +name = "utf8parse" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "uuid" -version = "1.8.0" +version = "1.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a183cf7feeba97b4dd1c0d46788634f6221d87fa961b305bed08c851829efcc0" +checksum = "5ac8b6f42ead25368cf5b098aeb3dc8a1a2c05a3eee8a9a1a68c640edbfc79d9" dependencies = [ - "getrandom 0.2.15", - "serde", + "getrandom 0.4.2", + "js-sys", + "serde_core", + "wasm-bindgen", ] [[package]] name = "valuable" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "vcpkg" @@ -6189,9 +7101,9 @@ checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "version_check" -version = "0.9.4" +version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "visibility" @@ -6201,7 +7113,7 @@ checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -6262,9 +7174,9 @@ checksum = "a7b6d5a78adc3e8f198e9cd730f219a695431467f7ec29dcfc63ade885feebe1" [[package]] name = "wait-timeout" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +checksum = "09ac3b126d3914f9849036f826e054cbabdc8519970b8998ddaf3b5bd3c65f11" dependencies = [ "libc", ] @@ -6290,58 +7202,66 @@ dependencies = [ [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" +version = "0.11.1+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" +dependencies = [ + "wit-bindgen", +] + +[[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", ] [[package]] name = "wasix" -version = "0.12.21" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1fbb4ef9bbca0c1170e0b00dd28abc9e3b68669821600cad1caaed606583c6d" +checksum = "1757e0d1f8456693c7e5c6c629bdb54884e032aa0bb53c155f6a39f94440d332" dependencies = [ "wasi", ] [[package]] name = "wasm-bindgen" -version = "0.2.92" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "0551fc1bb415591e3372d0bc4780db7e587d84e2a7e79da121051c5c4b89d0b0" dependencies = [ "cfg-if", + "once_cell", + "rustversion", + "serde", "wasm-bindgen-macro", + "wasm-bindgen-shared", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "wasm-bindgen-futures" +version = "0.4.67" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "03623de6905b7206edd0a75f69f747f134b7f0a2323392d664448bf2d3c5d87e" dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.100", - "wasm-bindgen-shared", + "js-sys", + "wasm-bindgen", ] [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "7fbdf9a35adf44786aecd5ff89b4563a90325f9da0923236f6104e603c7e86be" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -6349,22 +7269,47 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.117" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "dca9693ef2bab6d4e6707234500350d8dad079eb508dca05530c85dc3a529ff2" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn 2.0.100", - "wasm-bindgen-backend", + "syn 2.0.117", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.117" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39129a682a6d2d841b6c429d0c51e5cb0ed1a03829d8b3d1e69a011e62cb3d3b" +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 = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "bb0e353e6a2fbdc176932bbaab493762eb1255a7900fe0fea1a2f96c296cc909" +dependencies = [ + "anyhow", + "indexmap 2.13.1", + "wasm-encoder", + "wasmparser", +] [[package]] name = "wasm_sync" @@ -6377,6 +7322,18 @@ dependencies = [ "web-sys", ] +[[package]] +name = "wasmparser" +version = "0.244.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47b807c72e1bac69382b3a6fb3dbe8ea4c0ed87ff5629b8685ae6b9a611028fe" +dependencies = [ + "bitflags 2.11.0", + "hashbrown 0.15.5", + "indexmap 2.13.1", + "semver", +] + [[package]] name = "weak-table" version = "0.3.2" @@ -6385,9 +7342,9 @@ checksum = "323f4da9523e9a669e1eaf9c6e763892769b1d38c623913647bfdc1532fe4549" [[package]] name = "web-sys" -version = "0.3.69" +version = "0.3.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" +checksum = "cd70027e39b12f0849461e08ffc50b9cd7688d942c1c8e3c7b22273236b4dd0a" dependencies = [ "js-sys", "wasm-bindgen", @@ -6401,22 +7358,20 @@ checksum = "5f20c57d8d7db6d3b86154206ae5d8fba62dd39573114de97c2cb0578251f8e1" [[package]] name = "webpki-roots" -version = "1.0.3" +version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32b130c0d2d49f8b6889abc456e795e82525204f27c42cf767cf0d7734e089b8" +checksum = "22cfaf3c063993ff62e73cb4311efde4db1efb31ab78a3e5c457939ad5cc0bed" dependencies = [ "rustls-pki-types", ] [[package]] name = "which" -version = "8.0.0" +version = "8.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3fabb953106c3c8eea8306e4393700d7657561cb43122571b172bbfb7c7ba1d" +checksum = "81995fafaaaf6ae47a7d0cc83c67caf92aeb7e5331650ae6ff856f7c0c60c459" dependencies = [ - "env_home", - "rustix 1.1.2", - "winsafe", + "libc", ] [[package]] @@ -6437,11 +7392,11 @@ checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" [[package]] name = "winapi-util" -version = "0.1.8" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -6474,24 +7429,28 @@ dependencies = [ [[package]] name = "windows-core" -version = "0.52.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ - "windows-targets 0.52.6", + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", ] [[package]] name = "windows-core" -version = "0.61.2" +version = "0.62.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" dependencies = [ "windows-implement", "windows-interface", - "windows-link 0.1.3", - "windows-result", - "windows-strings", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", ] [[package]] @@ -6513,7 +7472,7 @@ checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -6524,7 +7483,7 @@ checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] @@ -6549,6 +7508,17 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -6558,6 +7528,15 @@ dependencies = [ "windows-link 0.1.3", ] +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + [[package]] name = "windows-strings" version = "0.4.2" @@ -6568,12 +7547,12 @@ dependencies = [ ] [[package]] -name = "windows-sys" -version = "0.48.0" +name = "windows-strings" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" dependencies = [ - "windows-targets 0.48.5", + "windows-link 0.2.1", ] [[package]] @@ -6585,15 +7564,6 @@ dependencies = [ "windows-targets 0.52.6", ] -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - [[package]] name = "windows-sys" version = "0.60.2" @@ -6604,18 +7574,12 @@ dependencies = [ ] [[package]] -name = "windows-targets" -version = "0.48.5" +name = "windows-sys" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows-link 0.2.1", ] [[package]] @@ -6660,12 +7624,6 @@ dependencies = [ "windows-link 0.1.3", ] -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" @@ -6678,12 +7636,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a9d8416fa8b42f5c947f8482c43e7d89e73a173cead56d044f6a56104a6d1b53" -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - [[package]] name = "windows_aarch64_msvc" version = "0.52.6" @@ -6696,12 +7648,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9d782e804c2f632e395708e99a94275910eb9100b2114651e04744e9b125006" -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - [[package]] name = "windows_i686_gnu" version = "0.52.6" @@ -6726,12 +7672,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa7359d10048f68ab8b09fa71c3daccfb0e9b559aed648a8f95469c27057180c" -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - [[package]] name = "windows_i686_msvc" version = "0.52.6" @@ -6744,12 +7684,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e7ac75179f18232fe9c285163565a57ef8d3c89254a30685b57d83a38d326c2" -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - [[package]] name = "windows_x86_64_gnu" version = "0.52.6" @@ -6762,12 +7696,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c3842cdd74a865a8066ab39c8a7a473c0778a3f29370b5fd6b4b9aa7df4a499" -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" @@ -6780,12 +7708,6 @@ version = "0.53.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ffa179e2d07eee8ad8f57493436566c7cc30ac536a3379fdf008f47f6bb7ae1" -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - [[package]] name = "windows_x86_64_msvc" version = "0.52.6" @@ -6800,39 +7722,146 @@ checksum = "d6bbff5f0aada427a1e5a6da5f1f98158182f26556f345ac9e04d36d0ebed650" [[package]] name = "winnow" -version = "0.5.40" +version = "0.7.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" dependencies = [ "memchr", ] [[package]] name = "winnow" -version = "0.6.20" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +checksum = "09dac053f1cd375980747450bfc7250c264eaae0583872e845c0c7cd578872b5" dependencies = [ "memchr", ] [[package]] -name = "winnow" -version = "0.7.13" +name = "wit-bindgen" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" +dependencies = [ + "wit-bindgen-rust-macro", +] [[package]] -name = "winsafe" -version = "0.0.19" +name = "wit-bindgen-core" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904" +checksum = "ea61de684c3ea68cb082b7a88508a8b27fcc8b797d738bfc99a82facf1d752dc" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] [[package]] -name = "wit-bindgen" -version = "0.46.0" +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.13.1", + "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 2.11.0", + "indexmap 2.13.1", + "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.13.1", + "log", + "semver", + "serde", + "serde_derive", + "serde_json", + "unicode-xid", + "wasmparser", +] + +[[package]] +name = "witness-server" +version = "0.1.0" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "arc-swap", + "axum", + "chain-ingest", + "clap", + "commitment-ingest", + "commitment-tree-db", + "pir-types", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio", + "tower-http 0.5.2", + "tracing", + "tracing-subscriber", + "witness-types", +] + +[[package]] +name = "witness-types" +version = "0.1.0" +source = "git+https://github.com/valargroup/spendability-pir.git?rev=3b33092a2988cfdd28edd65837b9ad03b0003fd1#3b33092a2988cfdd28edd65837b9ad03b0003fd1" +dependencies = [ + "pir-types", + "serde", +] + +[[package]] +name = "writeable" +version = "0.6.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "1ffae5123b2d3fc086436f8834ae3ab053a283cfac8fe0a0b8eaae044768a4c4" [[package]] name = "wyz" @@ -6862,12 +7891,32 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" [[package]] -name = "xz2" -version = "0.1.7" +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + +[[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 = "388c44dc09d76f1536602ead6d325eb532f5c122f17782bd57fb47baeeb767e2" +checksum = "de844c262c8848816172cef550288e7dc6c7b7814b4ee56b3e1553f275f1858e" dependencies = [ - "lzma-sys", + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", ] [[package]] @@ -6922,13 +7971,13 @@ dependencies = [ "jubjub", "memuse", "nonempty", - "orchard", + "orchard 0.11.0", "pasta_curves", "pczt", "percent-encoding", "postcard", "proptest", - "prost", + "prost 0.14.3", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", @@ -6946,14 +7995,14 @@ dependencies = [ "time-core", "tokio", "tokio-rustls", - "tonic", + "tonic 0.14.5", "tonic-prost", "tonic-prost-build", "tor-rtcompat", - "tower", + "tower 0.5.3", "tracing", "trait-variant", - "webpki-roots 1.0.3", + "webpki-roots 1.0.6", "which", "zcash_address", "zcash_encoding", @@ -6982,11 +8031,11 @@ dependencies = [ "incrementalmerkletree", "jubjub", "nonempty", - "orchard", + "orchard 0.11.0", "postcard", "proptest", - "prost", - "prost-build", + "prost 0.14.3", + "prost-build 0.14.3", "rayon", "sapling-crypto", "secp256k1", @@ -6996,7 +8045,7 @@ dependencies = [ "shardtree", "static_assertions", "subtle", - "thiserror 1.0.63", + "thiserror 1.0.69", "time", "tokio", "tracing", @@ -7019,11 +8068,13 @@ version = "0.19.5" dependencies = [ "ambassador", "assert_matches", + "axum", "bip32", - "bitflags 2.9.0", + "bitflags 2.11.0", "bls12_381", "bs58", "byteorder", + "commitment-tree-db", "document-features", "group", "hex", @@ -7032,15 +8083,17 @@ dependencies = [ "jubjub", "maybe-rayon", "nonempty", - "orchard", + "orchard 0.11.0", "pasta_curves", + "pir-types", "proptest", - "prost", + "prost 0.14.3", "rand 0.8.5", "rand_chacha 0.3.1", "rand_core 0.6.4", "rand_distr", "regex", + "reqwest", "rusqlite", "sapling-crypto", "schemerz", @@ -7048,13 +8101,16 @@ dependencies = [ "secp256k1", "secrecy", "serde", + "serde_json", "shardtree", "static_assertions", "subtle", "tempfile", "time", + "tokio", "tracing", "uuid", + "witness-server", "zcash_address", "zcash_client_backend", "zcash_encoding", @@ -7084,7 +8140,7 @@ dependencies = [ "blake2b_simd", "ff", "jubjub", - "orchard", + "orchard 0.11.0", "rand_core 0.6.4", "sapling-crypto", "zcash_address", @@ -7124,7 +8180,7 @@ dependencies = [ "jubjub", "memuse", "nonempty", - "orchard", + "orchard 0.11.0", "proptest", "rand 0.8.5", "rand_chacha 0.3.1", @@ -7181,7 +8237,7 @@ dependencies = [ "jubjub", "memuse", "nonempty", - "orchard", + "orchard 0.11.0", "pprof", "proptest", "rand 0.8.5", @@ -7191,7 +8247,7 @@ dependencies = [ "ripemd 0.1.3", "sapling-crypto", "secp256k1", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", "tracing", "zcash_address", @@ -7242,19 +8298,19 @@ dependencies = [ [[package]] name = "zcash_script" -version = "0.4.2" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bed6cf5b2b4361105d4ea06b2752f0c8af4641756c7fbc9858a80af186c234f" +checksum = "c6ef9d04e0434a80b62ad06c5a610557be358ef60a98afa5dbc8ecaf19ad72e7" dependencies = [ "bip32", - "bitflags 2.9.0", + "bitflags 2.11.0", "bounded-vec", "hex", "ripemd 0.1.3", "secp256k1", "sha1", - "sha2 0.10.8", - "thiserror 2.0.12", + "sha2 0.10.9", + "thiserror 2.0.18", ] [[package]] @@ -7280,7 +8336,7 @@ dependencies = [ "proptest", "ripemd 0.1.3", "secp256k1", - "sha2 0.10.8", + "sha2 0.10.9", "subtle", "zcash_address", "zcash_encoding", @@ -7292,65 +8348,106 @@ dependencies = [ [[package]] name = "zerocopy" -version = "0.7.34" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" +checksum = "eed437bf9d6692032087e337407a86f04cd8d6a16a37199ed57949d415bd68e9" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.34" +version = "0.8.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" +checksum = "70e3cd084b1788766f53af483dd21f93881ff30d7320490ec3ef7526d203bad4" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "syn 2.0.117", ] [[package]] name = "zerofrom" -version = "0.1.6" +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 = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +checksum = "11532158c46691caf0f2593ea8358fed6bbf68a0315e80aae9bd41fbade684a1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", + "synstructure", +] [[package]] name = "zeroize" -version = "1.7.0" +version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "85a5b4158499876c763cb03bc4e49185d3cccbabb15b33c627f7884f43db852e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.100", + "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.1" +version = "0.11.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94e62113720e311984f461c56b00457ae9981c0bc7859d22306cc2ae2f95571c" +checksum = "90f911cbc359ab6af17377d242225f4d75119aec87ea711a880987b18cd7b239" dependencies = [ + "serde", + "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 = "zip32" -version = "0.2.0" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13ff9ea444cdbce820211f91e6aa3d3a56bde7202d3c0961b7c38f793abf5637" +checksum = "b64bf5186a8916f7a48f2a98ef599bf9c099e2458b36b819e393db1c0e768c4b" dependencies = [ + "bech32", "blake2b_simd", "memuse", "subtle", @@ -7369,29 +8466,35 @@ dependencies = [ "zcash_protocol", ] +[[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.1" +version = "0.13.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d789b1514203a1120ad2429eae43a7bd32b90976a7bb8a05f7ec02fa88cc23a" +checksum = "e91ee311a569c327171651566e07972200e76fcfe2242a4fa446149a3881c08a" dependencies = [ "zstd-safe", ] [[package]] name = "zstd-safe" -version = "7.1.0" +version = "7.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd99b45c6bc03a018c8b8a86025678c87e55526064e38f9df301989dce7ec0a" +checksum = "8f49c4d5f0abb602a93fb8736af2a4f4dd9512e36f7f570d66e65ff867ed3b9d" dependencies = [ "zstd-sys", ] [[package]] name = "zstd-sys" -version = "2.0.10+zstd.1.5.6" +version = "2.0.16+zstd.1.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c253a4914af5bafc8fa8c86ee400827e83cf6ec01195ec1f1ed8441bf00d65aa" +checksum = "91e19ebc2adc8f83e43039e79776e3fda8ca919132d68a1fed6a5faca2683748" dependencies = [ "cc", "pkg-config", diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index c52b828938..ed1c64e4f0 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -10,6 +10,13 @@ workspace. ## [Unreleased] +### Added +- `zcash_client_backend::data_api::WalletCommitmentTrees::get_pir_orchard_merkle_path` + default trait method (behind `orchard` + `spendability-pir` feature flags) for + retrieving PIR-provided Orchard Merkle authentication paths. +- `spendability-pir` feature flag, enabling PIR-based Orchard note spendability + via witness construction from an external PIR server. + ## [0.21.2] - 2026-03-10 - The following APIs no longer crash in certain regtest mode configurations with fewer NUs active: diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 037e89236f..364b9ec410 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -266,6 +266,10 @@ non-standard-fees = ["zcash_primitives/non-standard-fees"] #! ### Experimental features +## Enables PIR-based Orchard note spendability: nullifier spend detection and +## witness construction via Private Information Retrieval. +spendability-pir = ["orchard"] + ## Exposes unstable APIs. Their behaviour may change at any time. unstable = ["dep:byteorder", "zcash_keys/unstable"] diff --git a/zcash_client_backend/src/data_api.rs b/zcash_client_backend/src/data_api.rs index 95a3290170..9b15821f2b 100644 --- a/zcash_client_backend/src/data_api.rs +++ b/zcash_client_backend/src/data_api.rs @@ -3105,6 +3105,14 @@ pub trait WalletWrite: WalletRead { } } +/// The result of a PIR Orchard witness lookup: Merkle path, anchor height, and anchor root. +#[cfg(all(feature = "orchard", feature = "spendability-pir"))] +pub type PirOrchardWitness = ( + incrementalmerkletree::MerklePath, + u64, + [u8; 32], +); + /// This trait describes a capability for manipulating wallet note commitment trees. #[cfg_attr(feature = "test-dependencies", delegatable_trait)] pub trait WalletCommitmentTrees { @@ -3168,4 +3176,19 @@ pub trait WalletCommitmentTrees { start_index: u64, roots: &[CommitmentTreeRoot], ) -> Result<(), ShardTreeError>; + + /// Retrieves a PIR-provided Orchard Merkle authentication path for the note at the + /// given commitment tree position. Returns the path, anchor height, and anchor root. + /// + /// The default implementation returns `Ok(None)`, indicating no PIR witness is + /// available. See [`zcash_client_sqlite::WalletDb`] for the production implementation + /// backed by the `pir_witness_data` table. + #[cfg(all(feature = "orchard", feature = "spendability-pir"))] + fn get_pir_orchard_merkle_path( + &self, + position: incrementalmerkletree::Position, + ) -> Result, Self::Error> { + let _ = position; + Ok(None) + } } diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 0330a018fb..b3a72b7858 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -1082,46 +1082,111 @@ where }; #[cfg(feature = "orchard")] - let (orchard_anchor, orchard_inputs) = if proposal_step - .involves(PoolType::Shielded(ShieldedProtocol::Orchard)) - { - proposal_step.shielded_inputs().map_or_else( - || Ok((Some(orchard::Anchor::empty_tree()), vec![])), - |inputs| { - wallet_db.with_orchard_tree_mut::<_, _, Error<_, _, _, _, _, _>>(|orchard_tree| { - let anchor = orchard_tree - .root_at_checkpoint_id(&inputs.anchor_height())? - .ok_or(ProposalError::AnchorNotFound(inputs.anchor_height()))? - .into(); + let orchard_tree_result: Result<_, CreateErrT> = + if proposal_step.involves(PoolType::Shielded(ShieldedProtocol::Orchard)) { + proposal_step.shielded_inputs().map_or_else( + || Ok((Some(orchard::Anchor::empty_tree()), vec![])), + |inputs| { + wallet_db.with_orchard_tree_mut::<_, _, Error<_, _, _, _, _, _>>( + |orchard_tree| { + let anchor_hash = orchard_tree + .root_at_checkpoint_id(&inputs.anchor_height())? + .ok_or(ProposalError::AnchorNotFound(inputs.anchor_height()))?; + let anchor: orchard::Anchor = anchor_hash.into(); + + tracing::debug!( + anchor_height = ?inputs.anchor_height(), + selected_note_count = inputs.notes().len(), + "building Orchard inputs from ShardTree witnesses", + ); - let orchard_inputs = inputs - .notes() - .iter() - .filter_map(|selected| match selected.note() { - #[cfg(feature = "orchard")] - Note::Orchard(note) => orchard_tree - .witness_at_checkpoint_id_caching( - selected.note_commitment_tree_position(), - &inputs.anchor_height(), - ) - .and_then(|witness| { - witness - .ok_or(ShardTreeError::Query(QueryError::CheckpointPruned)) + let orchard_inputs = inputs + .notes() + .iter() + .filter_map(|selected| match selected.note() { + #[cfg(feature = "orchard")] + Note::Orchard(note) => orchard_tree + .witness_at_checkpoint_id_caching( + selected.note_commitment_tree_position(), + &inputs.anchor_height(), + ) + .and_then(|witness| { + witness.ok_or(ShardTreeError::Query( + QueryError::CheckpointPruned, + )) + }) + .and_then(|merkle_path| { + // `witness_at_checkpoint_id_caching` is a cache-backed + // lookup. Under concurrent scanning it can occasionally + // hand back a path for the right note but the wrong + // checkpoint. Recompute the root from the note's cmx and + // reject anything that doesn't land on this proposal's + // anchor. + validate_shardtree_orchard_witness::< + ::Error, + >( + note, + merkle_path, + anchor, + ) + .map_err(|err| { + // Treat an anchor mismatch the same as a missing + // checkpoint so the caller takes the existing PIR + // witness fallback instead of building with an + // inconsistent Orchard witness. + tracing::warn!( + position = ?selected.note_commitment_tree_position(), + anchor_height = ?inputs.anchor_height(), + value = note.value().inner(), + "Orchard ShardTree witness root \ + does not match anchor, \ + falling back to PIR witnesses", + ); + err + }) + }) + .map(|merkle_path| Some((note, merkle_path))) + .map_err(Error::from) + .transpose(), + Note::Sapling(_) => None, }) - .map(|merkle_path| Some((note, merkle_path))) - .map_err(Error::from) - .transpose(), - Note::Sapling(_) => None, - }) - .collect::, Error<_, _, _, _, _, _>>>()?; + .collect::, Error<_, _, _, _, _, _>>>()?; - Ok((Some(anchor), orchard_inputs)) - }) - }, - )? - } else { - (None, vec![]) + Ok((Some(anchor), orchard_inputs)) + }, + ) + }, + ) + } else { + Ok((None, vec![])) + }; + + #[cfg(all(feature = "orchard", feature = "spendability-pir"))] + let (orchard_anchor, orchard_inputs) = match orchard_tree_result { + Ok(result) => result, + // These failures mean we could not produce a trustworthy Orchard witness + // from the local ShardTree for this proposal's anchor: + // - `Query(_)` covers missing checkpoints and the stale-cache guard above, + // which intentionally maps an anchor mismatch into a recoverable query + // failure. + // - `AnchorNotFound(_)` means the local tree cannot provide the requested + // checkpoint root at all. + // + // In either case, retry by loading PIR-stored witnesses for the selected + // Orchard notes instead of aborting transaction construction. + Err(Error::CommitmentTree(ShardTreeError::Query(_))) + | Err(Error::Proposal(ProposalError::AnchorNotFound(_))) => { + if let Some(inputs) = proposal_step.shielded_inputs() { + pir_orchard_witness_fallback(wallet_db, inputs)? + } else { + (None, vec![]) + } + } + Err(other) => return Err(other), }; + #[cfg(all(feature = "orchard", not(feature = "spendability-pir")))] + let (orchard_anchor, orchard_inputs) = orchard_tree_result?; + #[cfg(not(feature = "orchard"))] let orchard_anchor = None; @@ -1540,6 +1605,131 @@ where // `unused_transparent_outputs` maps `StepOutput`s for transparent outputs // that have not been consumed so far, to the corresponding pair of // `TransparentAddress` and `Outpoint`. +/// Falls back to PIR-stored witnesses when ShardTree witnesses are unavailable +/// (shard incomplete). Retrieves a `MerklePath` for each Orchard note from the +/// `pir_witness_data` table. All PIR witnesses must share the same anchor root; +/// that root becomes the transaction's Orchard anchor. +#[cfg(all(feature = "orchard", feature = "spendability-pir"))] +#[allow(clippy::type_complexity)] +fn pir_orchard_witness_fallback<'a, DbT, InputsErrT, FeeErrT, ChangeErrT, N>( + wallet_db: &mut DbT, + inputs: &'a crate::proposal::ShieldedInputs, +) -> Result< + ( + Option, + Vec<( + &'a orchard::Note, + incrementalmerkletree::MerklePath, + )>, + ), + Error< + ::Error, + ::Error, + InputsErrT, + FeeErrT, + ChangeErrT, + N, + >, +> +where + DbT: WalletWrite + WalletCommitmentTrees, +{ + use crate::wallet::Note; + + let mut pir_anchor: Option = None; + let mut pir_anchor_height: Option = None; + let mut orchard_inputs = vec![]; + for selected in inputs.notes().iter() { + if let Note::Orchard(note) = selected.note() { + let position = selected.note_commitment_tree_position(); + + let (merkle_path, anchor_height, anchor_root) = wallet_db + .get_pir_orchard_merkle_path(position) + .map_err(|e| Error::CommitmentTree(ShardTreeError::Storage(e)))? + .ok_or(Error::CommitmentTree(ShardTreeError::Query( + QueryError::CheckpointPruned, + )))?; + + let root_hash: orchard::tree::MerkleHashOrchard = + Option::from(orchard::tree::MerkleHashOrchard::from_bytes(&anchor_root)) + .ok_or_else(|| { + Error::CommitmentTree(ShardTreeError::Query(QueryError::CheckpointPruned)) + })?; + let anchor: orchard::Anchor = root_hash.into(); + let ecmx: orchard::note::ExtractedNoteCommitment = note.commitment().into(); + let cmx = orchard::tree::MerkleHashOrchard::from_cmx(&ecmx); + let witness_root: orchard::Anchor = merkle_path.root(cmx).into(); + + if witness_root != anchor { + tracing::warn!( + position = ?position, + value = note.value().inner(), + anchor_height, + anchor_root = %hex::encode(anchor_root), + "PIR witness root does not match stored anchor before add_orchard_spend", + ); + return Err(Error::Proposal(ProposalError::PIRWitnessAnchorMismatch)); + } + + match &pir_anchor_height { + None => pir_anchor_height = Some(anchor_height), + Some(existing) if *existing == anchor_height => {} + Some(existing) => { + tracing::warn!( + position = ?position, + value = note.value().inner(), + first_anchor_height = existing, + anchor_height, + "selected Orchard notes span multiple PIR witness anchor heights", + ); + return Err(Error::Proposal(ProposalError::PIRWitnessAnchorMismatch)); + } + } + + match &pir_anchor { + None => pir_anchor = Some(anchor), + Some(existing) if *existing == anchor => {} + Some(_) => { + tracing::warn!( + position = ?position, + value = note.value().inner(), + anchor_height, + anchor_root = %hex::encode(anchor_root), + "selected Orchard notes span multiple PIR witness anchors", + ); + return Err(Error::Proposal(ProposalError::PIRWitnessAnchorMismatch)); + } + } + orchard_inputs.push((note, merkle_path)); + } + } + + Ok(( + pir_anchor.or(Some(orchard::Anchor::empty_tree())), + orchard_inputs, + )) +} + +#[cfg(feature = "orchard")] +fn validate_shardtree_orchard_witness( + note: &orchard::note::Note, + merkle_path: incrementalmerkletree::MerklePath, + expected_anchor: orchard::Anchor, +) -> Result< + incrementalmerkletree::MerklePath, + ShardTreeError, +> { + let ecmx: orchard::note::ExtractedNoteCommitment = note.commitment().into(); + let cmx = orchard::tree::MerkleHashOrchard::from_cmx(&ecmx); + let witness_root: orchard::Anchor = merkle_path.root(cmx).into(); + + if witness_root == expected_anchor { + Ok(merkle_path) + } else { + Err(ShardTreeError::Query(QueryError::CheckpointPruned)) + } +} + #[allow(clippy::too_many_arguments)] #[allow(clippy::type_complexity)] fn create_proposed_transaction( @@ -2515,3 +2705,57 @@ where &proposal, ) } + +#[cfg(all(test, feature = "orchard"))] +mod tests { + use incrementalmerkletree::{Hashable, Level, MerklePath, Position}; + use orchard::{ + keys::{FullViewingKey, SpendingKey}, + note::{RandomSeed, Rho}, + tree::MerkleHashOrchard, + value::NoteValue, + }; + use shardtree::error::{QueryError, ShardTreeError}; + + use super::validate_shardtree_orchard_witness; + + #[test] + fn shardtree_orchard_witness_validation_rejects_anchor_mismatch() { + let sk = SpendingKey::from_zip32_seed(&[7; 32], 0, zip32::AccountId::ZERO).unwrap(); + let fvk = FullViewingKey::from(&sk); + let recipient = fvk.address_at(0u32, zip32::Scope::External); + let rho = Rho::from_bytes(&[3; 32]).into_option().unwrap(); + let rseed = RandomSeed::from_bytes([9; 32], &rho) + .into_option() + .unwrap(); + let note = orchard::Note::from_parts(recipient, NoteValue::from_raw(50_000), rho, rseed) + .into_option() + .unwrap(); + + // Build a deterministic synthetic witness. The helper only cares that the + // path and the note commitment agree on the resulting root. + let path = (0..32) + .map(|level| MerkleHashOrchard::empty_root(Level::from(level))) + .collect::>(); + let merkle_path = MerklePath::from_parts(path, Position::from(0)).unwrap(); + + let ecmx: orchard::note::ExtractedNoteCommitment = note.commitment().into(); + let cmx = MerkleHashOrchard::from_cmx(&ecmx); + let expected_anchor: orchard::Anchor = merkle_path.root(cmx).into(); + + // A witness that resolves back to the proposal anchor should be accepted. + validate_shardtree_orchard_witness::<()>(¬e, merkle_path.clone(), expected_anchor) + .expect("matching Orchard witness should validate"); + + // A different anchor must be reported as `CheckpointPruned` so the caller + // takes the existing PIR fallback path instead of using a stale witness. + let bad_anchor = orchard::Anchor::empty_tree(); + assert_ne!(expected_anchor, bad_anchor); + let err = validate_shardtree_orchard_witness::<()>(¬e, merkle_path, bad_anchor) + .expect_err("mismatched Orchard anchor should be rejected"); + assert!(matches!( + err, + ShardTreeError::Query(QueryError::CheckpointPruned) + )); + } +} diff --git a/zcash_client_backend/src/proposal.rs b/zcash_client_backend/src/proposal.rs index 8288fb5136..07c22f346b 100644 --- a/zcash_client_backend/src/proposal.rs +++ b/zcash_client_backend/src/proposal.rs @@ -38,6 +38,8 @@ pub enum ProposalError { ShieldingInvalid, /// No anchor information could be obtained for the specified block height. AnchorNotFound(BlockHeight), + /// Selected Orchard notes were backed by incompatible PIR witness anchors. + PIRWitnessAnchorMismatch, /// A reference to the output of a prior step is invalid. ReferenceError(StepOutput), /// An attempted double-spend of a prior step output was detected. @@ -94,6 +96,10 @@ impl Display for ProposalError { ProposalError::AnchorNotFound(h) => { write!(f, "Unable to compute anchor for block height {h:?}") } + ProposalError::PIRWitnessAnchorMismatch => write!( + f, + "Selected Orchard inputs were backed by incompatible PIR witness anchors." + ), ProposalError::ReferenceError(r) => { write!(f, "No prior step output found for reference {r:?}") } diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 349859d8f1..f9d076faa2 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -10,6 +10,27 @@ workspace. ## [Unreleased] +### Added +- `spendability-pir` feature flag, enabling PIR (Private Information Retrieval) + for immediate Orchard note spendability. This includes: + - `zcash_client_sqlite::wallet::pir` module for spent-note tracking via + nullifier PIR queries against an external server. + - `zcash_client_sqlite::wallet::pir_witness` module for storing PIR-obtained + Merkle authentication paths, enabling notes to be spent before the wallet + finishes scanning. + - `WalletCommitmentTrees::get_pir_orchard_merkle_path` implementation for + `WalletDb`. +- `pir_spent_notes` and `pir_witness_data` database migrations (unconditional, + not feature-gated) to keep the migration DAG identical across all builds. + +### Changed +- When `spendability-pir` is enabled, `get_wallet_summary` and note selection + skip the unscanned-range spendability gate for Orchard notes, and notes with + PIR witnesses are treated as spendable even when their shard is not fully + scanned. +- `truncate_to_height` now unconditionally clears the `pir_spent_notes` and + `pir_witness_data` tables to avoid stale data after reorgs. + ## [0.19.5] - 2026-03-10 ### Fixed diff --git a/zcash_client_sqlite/Cargo.toml b/zcash_client_sqlite/Cargo.toml index 52508e31f2..5ded7353a9 100644 --- a/zcash_client_sqlite/Cargo.toml +++ b/zcash_client_sqlite/Cargo.toml @@ -92,17 +92,24 @@ rand.workspace = true [dev-dependencies] ambassador.workspace = true assert_matches.workspace = true +axum = "0.7" bls12_381.workspace = true +commitment-tree-db = { git = "https://github.com/valargroup/spendability-pir.git", rev = "3b33092a2988cfdd28edd65837b9ad03b0003fd1" } incrementalmerkletree = { workspace = true, features = ["test-dependencies"] } incrementalmerkletree-testing.workspace = true pasta_curves.workspace = true +pir-types = { git = "https://github.com/valargroup/spendability-pir.git", rev = "3b33092a2988cfdd28edd65837b9ad03b0003fd1" } shardtree = { workspace = true, features = ["legacy-api", "test-dependencies"] } orchard = { workspace = true, features = ["test-dependencies"] } proptest.workspace = true rand_chacha.workspace = true rand_core.workspace = true +reqwest = { version = "0.12", features = ["json"] } secp256k1 = { workspace = true, features = ["rand"] } +serde_json.workspace = true tempfile = "3.5.0" +tokio = { workspace = true, features = ["full"] } +witness-server = { git = "https://github.com/valargroup/spendability-pir.git", rev = "3b33092a2988cfdd28edd65837b9ad03b0003fd1" } zcash_keys = { workspace = true, features = ["test-dependencies"] } zcash_note_encryption.workspace = true zcash_proofs = { workspace = true, features = ["bundled-prover"] } @@ -169,6 +176,11 @@ expensive-tests = [] ## protocol-specific flags. Test-only. pczt-tests = ["serde", "zcash_client_backend/pczt"] +## Enables PIR (Private Information Retrieval) for immediate Orchard note spendability: +## spent-note tracking via nullifier PIR queries and Merkle witness fetching for +## spending notes before the wallet finishes scanning. +spendability-pir = ["orchard", "zcash_client_backend/spendability-pir"] + [lib] bench = false diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 7798bd1df2..7ab48577a6 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -437,6 +437,258 @@ impl WalletDb { } } +#[cfg(feature = "spendability-pir")] +impl, P, CL, R> WalletDb { + /// Returns all Orchard notes that are candidates for PIR nullifier checking: + /// unspent notes with a known nullifier whose shard may not yet be fully scanned. + pub fn get_unspent_orchard_notes_for_pir( + &self, + ) -> Result, SqliteClientError> { + wallet::pir::get_unspent_orchard_notes_for_pir(self.conn.borrow()) + } + + /// Records a note as spent according to a PIR nullifier query. The note will be + /// excluded from balance calculations and coin selection. + pub fn insert_pir_spent_note(&self, note_id: i64) -> Result<(), SqliteClientError> { + wallet::pir::insert_pir_spent_note(self.conn.borrow(), note_id) + } + + /// Returns the `pir_notes.id` for a canonical note, if a row exists. + pub fn get_pir_note_id_for_canonical( + &self, + canonical_note_id: i64, + ) -> Result, SqliteClientError> { + wallet::pir::get_pir_note_id_for_canonical(self.conn.borrow(), canonical_note_id) + } + + /// Sets spending transaction metadata on a pir_notes row after change discovery. + pub fn set_pir_spending_tx_metadata( + &self, + pir_note_id: i64, + tx_hash: &[u8; 32], + block_time: u32, + fee: Option, + spend_height: Option, + ) -> Result<(), SqliteClientError> { + wallet::pir::set_pir_spending_tx_metadata( + self.conn.borrow(), + pir_note_id, + tx_hash, + block_time, + fee, + spend_height, + ) + } + + /// Returns PIR-derived transaction entries for the activity view. + pub fn get_pir_activity_entries( + &self, + ) -> Result, SqliteClientError> { + wallet::pir::get_pir_activity_entries(self.conn.borrow()) + } + + /// Returns Orchard notes that need a PIR witness: they have a commitment tree + /// position, are unspent, and their shard is not fully scanned. + pub fn get_notes_needing_pir_witness( + &self, + ) -> Result, SqliteClientError> { + wallet::pir::get_notes_needing_pir_witness(self.conn.borrow()) + } + + #[cfg(feature = "orchard")] + /// Returns Orchard notes referenced by a proposal that can be refreshed via + /// witness PIR. + pub fn get_pir_witness_notes_for_proposal( + &self, + proposal: &zcash_client_backend::proposal::Proposal< + zcash_client_backend::fees::StandardFeeRule, + ReceivedNoteId, + >, + ) -> Vec { + let mut out = Vec::new(); + for step in proposal.steps() { + if let Some(inputs) = step.shielded_inputs() { + for selected in inputs.notes() { + if let Note::Orchard(note) = selected.note() { + let ReceivedNoteId(protocol, note_id) = *selected.internal_note_id(); + if protocol != ShieldedProtocol::Orchard { + continue; + } + + out.push(wallet::pir::NoteNeedingWitness { + id: note_id, + position: u64::from(selected.note_commitment_tree_position()), + value: note.value().inner(), + }); + } + } + } + } + + out + } + + /// Stores a PIR-obtained Merkle authentication path for a note. The siblings + /// are ordered leaf-to-root. Existing rows are refreshed when a newer anchor + /// is fetched for the same `note_id`. + pub fn insert_pir_witness( + &self, + note_id: i64, + siblings: &[[u8; 32]; 32], + anchor_height: u64, + anchor_root: &[u8; 32], + ) -> Result<(), SqliteClientError> { + wallet::pir::insert_pir_witness( + self.conn.borrow(), + note_id, + siblings, + anchor_height, + anchor_root, + ) + } + + #[cfg(feature = "orchard")] + /// Validates an incoming PIR Orchard witness against the wallet's stored note + /// before persisting it. + pub fn validate_pir_orchard_witness( + &self, + note_id: i64, + siblings: &[[u8; 32]; 32], + anchor_height: u64, + anchor_root: &[u8; 32], + ) -> Result + where + P: consensus::Parameters, + { + wallet::pir::validate_orchard_witness( + self.conn.borrow(), + &self.params, + note_id, + siblings, + anchor_height, + anchor_root, + ) + } + + /// Retrieves a stored PIR witness for the given note, or `None` if no witness + /// has been stored. + pub fn get_pir_witness( + &self, + note_id: i64, + ) -> Result, SqliteClientError> { + wallet::pir::get_pir_witness(self.conn.borrow(), note_id) + } + + /// Returns all notes that have PIR witnesses and are still unspent. Useful for + /// displaying PIR-spendable balance in the wallet UI. + pub fn get_pir_witnessed_notes( + &self, + ) -> Result, SqliteClientError> { + wallet::pir::get_pir_witnessed_notes(self.conn.borrow()) + } + + /// Returns the internal account row ID and account UUID for an Orchard note. + /// Used by the FFI layer to look up FVK context for change discovery. + pub fn get_account_for_orchard_note( + &self, + note_id: i64, + ) -> Result<(i64, AccountUuid), SqliteClientError> { + let conn = self.conn.borrow(); + conn.query_row( + "SELECT rn.account_id, a.uuid + FROM orchard_received_notes rn + INNER JOIN accounts a ON a.id = rn.account_id + WHERE rn.id = ?1", + [note_id], + |row| { + let account_id: i64 = row.get(0)?; + let uuid: uuid::Uuid = row.get(1)?; + Ok((account_id, AccountUuid(uuid))) + }, + ) + .map_err(|e| e.into()) + } + + /// Inserts a provisional change note discovered via PIR trial decryption. + /// Returns the row ID of the inserted (or existing) row. + #[allow(clippy::too_many_arguments)] + pub fn insert_pir_provisional_note( + &self, + account_id: i64, + value: u64, + position: u64, + diversifier: &[u8; 11], + rseed: &[u8; 32], + rho: &[u8; 32], + nullifier: &[u8; 32], + cmx: &[u8; 32], + spend_height: u32, + depth: u32, + parent_provisional_id: Option, + ) -> Result { + wallet::pir::insert_pir_provisional_note( + self.conn.borrow(), + account_id, + value, + position, + diversifier, + rseed, + rho, + nullifier, + cmx, + spend_height, + depth, + parent_provisional_id, + ) + } + + /// Sets witness data on a provisional note after a PIR witness is obtained, + /// making it eligible for balance and coin selection. + pub fn mark_provisional_note_witnessed( + &self, + note_id: i64, + siblings: &[[u8; 32]; 32], + anchor_height: u64, + anchor_root: &[u8; 32], + ) -> Result { + wallet::pir::mark_provisional_note_witnessed( + self.conn.borrow(), + note_id, + siblings, + anchor_height, + anchor_root, + ) + } + + /// Returns provisional notes that have a tree position but lack a PIR witness. + pub fn get_provisional_notes_needing_witness( + &self, + ) -> Result, SqliteClientError> + { + wallet::pir::get_provisional_notes_needing_witness(self.conn.borrow()) + } + + /// Returns provisional notes whose nullifiers haven't been PIR-checked. + pub fn get_provisional_notes_for_pir_check( + &self, + ) -> Result, SqliteClientError> { + wallet::pir::get_provisional_notes_for_pir_check(self.conn.borrow()) + } + + /// Updates a provisional note after PIR nullifier check. + pub fn mark_provisional_pir_result( + &self, + note_id: i64, + is_spent: bool, + ) -> Result<(), SqliteClientError> { + wallet::pir::mark_provisional_pir_result( + self.conn.borrow(), + note_id, + is_spent, + ) + } +} + #[cfg(feature = "transparent-inputs")] impl WalletDb { /// Sets the gap limits to be used by the wallet in transparent address generation. @@ -2246,6 +2498,21 @@ impl, P: consensus::Parameters, CL, R> Wallet .map_err(|e| ShardTreeError::Storage(commitment_tree::Error::Query(e)))?; Ok(()) } + + #[cfg(all(feature = "orchard", feature = "spendability-pir"))] + fn get_pir_orchard_merkle_path( + &self, + position: incrementalmerkletree::Position, + ) -> Result, Self::Error> { + wallet::pir::get_pir_merkle_path_by_position(self.conn.borrow(), position).map_err( + |e| match e { + SqliteClientError::DbError(e) => commitment_tree::Error::Query(e), + other => commitment_tree::Error::Query(rusqlite::Error::ToSqlConversionFailure( + Box::new(other), + )), + }, + ) + } } impl WalletCommitmentTrees @@ -2308,6 +2575,21 @@ impl WalletCommitmentTrees roots, ) } + + #[cfg(all(feature = "orchard", feature = "spendability-pir"))] + fn get_pir_orchard_merkle_path( + &self, + position: incrementalmerkletree::Position, + ) -> Result, Self::Error> { + wallet::pir::get_pir_merkle_path_by_position(self.conn.0, position).map_err(|e| { + match e { + SqliteClientError::DbError(e) => commitment_tree::Error::Query(e), + other => commitment_tree::Error::Query(rusqlite::Error::ToSqlConversionFailure( + Box::new(other), + )), + } + }) + } } /// A handle for the SQLite block source. diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index e23867f21d..5c2b62ef33 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -161,6 +161,8 @@ pub(crate) mod encoding; pub mod init; #[cfg(feature = "orchard")] pub(crate) mod orchard; +#[cfg(feature = "spendability-pir")] +pub mod pir; pub(crate) mod sapling; pub(crate) mod scanning; #[cfg(feature = "transparent-inputs")] @@ -2154,20 +2156,45 @@ pub(crate) fn get_wallet_summary( let untrusted_height = target_height.saturating_sub(u32::from(confirmations_policy.untrusted())); + // With witness PIR enabled, Orchard spendability can be determined on a note-specific + // basis even when the global shard-tree scan is incomplete. + // Bypass the global gate here and let the per-note `has_pir_witness` / scan-state checks + // below decide whether each Orchard note is spendable. + #[cfg(feature = "spendability-pir")] + let any_spendable = if table_prefix == "orchard" { + true + } else { + anchor_height.map_or(Ok(false), |h| is_any_spendable(tx, h, table_prefix))? + }; + #[cfg(not(feature = "spendability-pir"))] let any_spendable = anchor_height.map_or(Ok(false), |h| is_any_spendable(tx, h, table_prefix))?; + #[cfg(feature = "spendability-pir")] + let pir_witness_available = table_prefix == "orchard"; + #[cfg(not(feature = "spendability-pir"))] + let pir_witness_available = false; + + let pir_witness_join = ""; + let pir_witness_col = if pir_witness_available { + ", EXISTS(SELECT 1 FROM pir_notes pw WHERE pw.canonical_note_id = rn.id AND pw.witness_siblings IS NOT NULL) AS has_pir_witness" + } else { + "" + }; + let mut stmt_select_notes = tx.prepare_cached(&format!( "SELECT accounts.uuid, rn.id, rn.value, rn.is_change, rn.recipient_key_scope, scan_state.max_priority, t.mined_height AS mined_height, MAX(tt.mined_height) AS max_shielding_input_height + {pir_witness_col} FROM {table_prefix}_received_notes rn INNER JOIN accounts ON accounts.id = rn.account_id INNER JOIN transactions t ON t.id_tx = rn.transaction_id LEFT OUTER JOIN v_{table_prefix}_shards_scan_state scan_state ON rn.commitment_tree_position >= scan_state.start_position AND rn.commitment_tree_position < scan_state.end_position_exclusive + {pir_witness_join} LEFT OUTER JOIN transparent_received_output_spends ros ON ros.transaction_id = t.id_tx LEFT OUTER JOIN transparent_received_outputs tro @@ -2216,6 +2243,12 @@ pub(crate) fn get_wallet_summary( }, )?; + let has_pir_witness = if pir_witness_available { + row.get::<_, bool>("has_pir_witness")? + } else { + false + }; + let received_height = row .get::<_, Option>("mined_height")? .map(BlockHeight::from); @@ -2228,7 +2261,7 @@ pub(crate) fn get_wallet_summary( // the shard that its witness resides in is sufficiently scanned that we can construct // the witness for the note, and the note has enough confirmations to be spent. let is_spendable = any_spendable - && max_priority <= ScanPriority::Scanned + && (max_priority <= ScanPriority::Scanned || has_pir_witness) && match recipient_key_scope { Some(KeyScope::INTERNAL) => { // The note was has at least `trusted` confirmations. @@ -2299,6 +2332,48 @@ pub(crate) fn get_wallet_summary( }, )?; drop(orchard_trace); + + // Supplement Orchard balance with PIR provisional notes (change notes + // discovered via trial decryption that aren't yet in the canonical scan). + // Only active leaf nodes count: exclude mid-chain spent notes and notes + // already reconciled by the scanner. + #[cfg(feature = "spendability-pir")] + { + let mut stmt = tx.prepare_cached( + "SELECT accounts.uuid, pn.value, + CASE WHEN pn.witness_siblings IS NOT NULL THEN 1 ELSE 0 END AS has_pir_witness + FROM pir_notes pn + INNER JOIN accounts ON accounts.id = pn.account_id + WHERE pn.canonical_note_id IS NULL + AND pn.is_spent = 0 + AND pn.discovered_by_scanner = 0", + )?; + let mut rows = stmt.query([])?; + while let Some(row) = rows.next()? { + let account = AccountUuid(row.get::<_, Uuid>("uuid")?); + let value_raw = row.get::<_, i64>("value")?; + let value = Zatoshis::from_nonnegative_i64(value_raw).map_err(|_| { + SqliteClientError::CorruptedData(format!( + "Negative provisional note value: {value_raw}" + )) + })?; + let has_witness = row.get::<_, bool>("has_pir_witness")?; + + if let Some(balances) = account_balances.get_mut(&account) { + let zero = Zatoshis::ZERO; + let (spendable, pending) = if has_witness { + (value, zero) + } else { + (zero, value) + }; + balances.with_orchard_balance_mut::<_, SqliteClientError>(|bal| { + bal.add_spendable_value(spendable)?; + bal.add_pending_spendable_value(pending)?; + Ok(()) + })?; + } + } + } } let sapling_trace = tracing::info_span!("sapling_balances").entered(); @@ -3320,6 +3395,11 @@ pub(crate) fn truncate_to_height( named_params![":height": u32::from(truncation_height)], )?; + // Clear all PIR state unconditionally. After a reorg the on-chain nullifier set + // and authentication paths may have changed. The scanner will re-discover + // legitimate notes during rescan, and PIR will re-detect spends. + conn.execute("DELETE FROM pir_notes", [])?; + // If we're removing scanned blocks, we need to truncate the note commitment tree and remove // affected block records from the database. if truncation_height < last_scanned_height { diff --git a/zcash_client_sqlite/src/wallet/common.rs b/zcash_client_sqlite/src/wallet/common.rs index 757413a362..cfd9c5de33 100644 --- a/zcash_client_sqlite/src/wallet/common.rs +++ b/zcash_client_sqlite/src/wallet/common.rs @@ -110,7 +110,7 @@ pub(crate) fn tx_unexpired_condition(tx: &str) -> String { /// - The parent must provide `:target_height` as a named argument. /// - The parent is responsible for enclosing this condition in parentheses as appropriate. pub(crate) fn spent_notes_clause(table_prefix: &str) -> String { - format!( + let base = format!( r#" SELECT rns.{table_prefix}_received_note_id FROM {table_prefix}_received_note_spends rns @@ -118,7 +118,35 @@ pub(crate) fn spent_notes_clause(table_prefix: &str) -> String { WHERE {} "#, tx_unexpired_condition("stx") - ) + ); + // For Orchard, also exclude notes that PIR has identified as spent but + // the scanner hasn't confirmed yet. These rows are unconditional (no + // tx_unexpired_condition) because they reflect on-chain nullifier state, + // not pending transactions. Stale rows are cleared on reorg/rescan. + #[cfg(feature = "spendability-pir")] + if table_prefix == "orchard" { + return format!( + "{base} UNION SELECT canonical_note_id FROM pir_notes \ + WHERE canonical_note_id IS NOT NULL AND is_spent = 1" + ); + } + base +} + +/// Returns the SQL condition for the shard-scanned gate in coin selection. +/// +/// Without `spendability-pir`, requires `scan_state.max_priority <= :scanned_priority`. +/// With the feature enabled for Orchard, also accepts notes that have a PIR witness. +fn shard_scanned_condition(protocol: ShieldedProtocol) -> &'static str { + #[cfg(feature = "spendability-pir")] + if matches!(protocol, ShieldedProtocol::Orchard) { + return "scan_state.max_priority <= :scanned_priority \ + OR EXISTS (SELECT 1 FROM pir_notes pn \ + WHERE pn.canonical_note_id = rn.id \ + AND pn.witness_siblings IS NOT NULL)"; + } + let _ = protocol; + "scan_state.max_priority <= :scanned_priority" } fn unscanned_tip_exists( @@ -376,6 +404,20 @@ where .. } = table_constants::(protocol)?; + // With witness PIR, Orchard notes that have a PIR-obtained authentication path + // can be considered shard-scanned for spending purposes. + #[cfg(feature = "spendability-pir")] + let pir_witness_available = matches!(protocol, ShieldedProtocol::Orchard); + #[cfg(not(feature = "spendability-pir"))] + let pir_witness_available = false; + + let pir_witness_join = ""; + let pir_witness_col = if pir_witness_available { + ", EXISTS(SELECT 1 FROM pir_notes pw WHERE pw.canonical_note_id = rn.id AND pw.witness_siblings IS NOT NULL) AS has_pir_witness" + } else { + "" + }; + // Select all unspent notes belonging to the given account, ignoring dust notes. let mut stmt_select_notes = conn.prepare_cached(&format!( "SELECT @@ -387,12 +429,14 @@ where IFNULL(t.trust_status, 0) AS trust_status, MAX(tt.mined_height) AS max_shielding_input_height, MIN(IFNULL(tt.trust_status, 0)) AS min_shielding_input_trust + {pir_witness_col} FROM {table_prefix}_received_notes rn INNER JOIN accounts ON accounts.id = rn.account_id INNER JOIN transactions t ON t.id_tx = rn.transaction_id LEFT OUTER JOIN v_{table_prefix}_shards_scan_state scan_state ON rn.commitment_tree_position >= scan_state.start_position AND rn.commitment_tree_position < scan_state.end_position_exclusive + {pir_witness_join} LEFT OUTER JOIN transparent_received_output_spends ros ON ros.transaction_id = t.id_tx LEFT OUTER JOIN transparent_received_outputs tro @@ -437,6 +481,11 @@ where let max_priority_raw = row.get::<_, Option>("max_priority")?; let tx_trust_status = row.get::<_, bool>("trust_status")?; let tx_shielding_inputs_trusted = row.get::<_, bool>("min_shielding_input_trust")?; + let has_pir_witness = if pir_witness_available { + row.get::<_, bool>("has_pir_witness")? + } else { + false + }; let shard_scan_priority = max_priority_raw .map(|code| { parse_priority_code(code).ok_or_else(|| { @@ -450,6 +499,7 @@ where Ok(( result_note, shard_scan_priority, + has_pir_witness, tx_trust_status, tx_shielding_inputs_trusted, )) @@ -462,10 +512,17 @@ where row_results .map(|t| match t? { - (Some(note), max_shard_priority, trusted, tx_shielding_inputs_trusted) => { + ( + Some(note), + max_shard_priority, + has_pir_witness, + trusted, + tx_shielding_inputs_trusted, + ) => { let shard_scanned = max_shard_priority .iter() - .any(|p| *p <= ScanPriority::Scanned); + .any(|p| *p <= ScanPriority::Scanned) + || has_pir_witness; let mined_at_anchor = note .mined_height() @@ -540,10 +597,22 @@ where note_reconstruction_cols, .. } = table_constants::(protocol)?; - if unscanned_tip_exists(conn, anchor_height, table_prefix)? { + // With witness PIR, Orchard spendability can be proven note-by-note even when the + // global shard-tree scan is incomplete. The per-note shard condition below remains + // authoritative for deciding which notes are actually eligible. + #[cfg(feature = "spendability-pir")] + let skip_unscanned_check = matches!(protocol, ShieldedProtocol::Orchard); + #[cfg(not(feature = "spendability-pir"))] + let skip_unscanned_check = false; + + if !skip_unscanned_check && unscanned_tip_exists(conn, anchor_height, table_prefix)? { return Ok(vec![]); } + // With witness PIR, Orchard notes that have a PIR-obtained authentication path + // can be spent even when their shard is not fully scanned. + let shard_scanned_condition = shard_scanned_condition(protocol); + // The goal of this SQL statement is to select the oldest notes until the required // value has been reached. // 1) Use a window function to create a view of all notes, ordered from oldest to @@ -588,10 +657,7 @@ where AND accounts.ufvk IS NOT NULL AND recipient_key_scope IS NOT NULL AND nf IS NOT NULL - -- the shard containing the note is fully scanned; this condition will exclude - -- notes for which `scan_state.max_priority IS NULL` (which will also arise if - -- `rn.commitment_tree_position IS NULL`; hence we don't need that explicit filter) - AND scan_state.max_priority <= :scanned_priority + AND ({shard_scanned_condition}) AND t.block <= :anchor_height AND rn.id NOT IN rarray(:exclude) AND rn.id NOT IN ({}) diff --git a/zcash_client_sqlite/src/wallet/db.rs b/zcash_client_sqlite/src/wallet/db.rs index 4942bc0f4a..4b9942f309 100644 --- a/zcash_client_sqlite/src/wallet/db.rs +++ b/zcash_client_sqlite/src/wallet/db.rs @@ -1346,6 +1346,66 @@ UNION JOIN addresses a ON a.id = tro.address_id JOIN transactions t ON t.id_tx = tro.transaction_id"; +/// Unified PIR note lifecycle table. Tracks both canonical notes (linked to +/// [`TABLE_ORCHARD_RECEIVED_NOTES`] via `canonical_note_id`) and provisional notes +/// (discovered via trial decryption ahead of the scanner, `canonical_note_id` is NULL). +/// +/// ### Columns +/// - `canonical_note_id`: foreign key to `orchard_received_notes(id)`; set when the scanner +/// has caught up and created the canonical row. NULL for provisional-only notes. +/// - `account_id`: the account that owns this note. +/// - `position`: the note's leaf index in the Orchard commitment tree (unique across all notes). +/// - `value`: the note value in zatoshis. +/// - `diversifier`: the 11-byte diversifier used to construct the note (provisional only). +/// - `rseed`: the note randomness seed (provisional only). +/// - `rho`: the nullifier derivation input (provisional only). +/// - `cmx`: the extracted note commitment (provisional only). +/// - `nullifier`: the note's nullifier, used for PIR spend-checking. +/// - `is_spent`: set to 1 when PIR detects the note's nullifier on-chain. Monotonic. +/// - `spend_height`: the block height at which the spend was detected. +/// - `witness_siblings`: 1024-byte Merkle authentication path (32 siblings × 32 bytes), +/// obtained via witness PIR. NULL until a witness is fetched. +/// - `witness_anchor_height`: the block height of the anchor the witness was computed against. +/// - `witness_anchor_root`: the 32-byte tree root hash at `witness_anchor_height`. +/// - `depth`: hop count in the recursive change-discovery chain (0 = canonical origin, +/// 1 = direct change note, 2+ = deeper recursion). +/// - `parent_id`: self-referential FK linking a change note to the note it was derived from. +/// - `pir_checked`: set to 1 after this note's nullifier has been checked via PIR. +/// - `discovered_by_scanner`: set to 1 when the canonical scanner reaches this note's +/// position and reconciles it (along with setting `canonical_note_id`). +/// - `spending_tx_hash`: 32-byte txid of the transaction that spent this note, captured +/// from the CompactTx during change discovery. +/// - `spending_block_time`: Unix timestamp of the block containing the spending tx. +/// - `spending_fee`: fee paid by the spending tx in zatoshis (nullable; not always available). +pub(super) const TABLE_PIR_NOTES: &str = "CREATE TABLE pir_notes ( + id INTEGER PRIMARY KEY, + canonical_note_id INTEGER UNIQUE + REFERENCES orchard_received_notes ( id ) ON DELETE CASCADE, + account_id INTEGER NOT NULL REFERENCES accounts ( id ), + position INTEGER NOT NULL UNIQUE, + value INTEGER NOT NULL, + diversifier BLOB, + rseed BLOB, + rho BLOB, + cmx BLOB, + nullifier BLOB UNIQUE, + is_spent INTEGER NOT NULL DEFAULT 0, + spend_height INTEGER, + witness_siblings BLOB + CHECK ( witness_siblings IS NULL OR length ( witness_siblings ) = 1024 ), + witness_anchor_height INTEGER, + witness_anchor_root BLOB + CHECK ( witness_anchor_root IS NULL OR length ( witness_anchor_root ) = 32 ), + depth INTEGER NOT NULL DEFAULT 0, + parent_id INTEGER REFERENCES pir_notes ( id ), + pir_checked INTEGER NOT NULL DEFAULT 0, + discovered_by_scanner INTEGER NOT NULL DEFAULT 0, + spending_tx_hash BLOB + CHECK ( spending_tx_hash IS NULL OR length ( spending_tx_hash ) = 32 ), + spending_block_time INTEGER, + spending_fee INTEGER +)"; + pub(super) const VIEW_ADDRESS_FIRST_USE: &str = " CREATE VIEW v_address_first_use AS SELECT diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 2737bc0147..4dc2554f0a 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -792,6 +792,7 @@ mod tests { db::TABLE_ORCHARD_TREE_CHECKPOINT_MARKS_REMOVED, db::TABLE_ORCHARD_TREE_CHECKPOINTS, db::TABLE_ORCHARD_TREE_SHARDS, + db::TABLE_PIR_NOTES, db::TABLE_SAPLING_RECEIVED_NOTE_SPENDS, db::TABLE_SAPLING_RECEIVED_NOTES, db::TABLE_SAPLING_TREE_CAP, diff --git a/zcash_client_sqlite/src/wallet/init/migrations.rs b/zcash_client_sqlite/src/wallet/init/migrations.rs index c63ee2075d..ad55c0d766 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations.rs @@ -26,6 +26,7 @@ mod initial_setup; mod nullifier_map; mod orchard_received_notes; mod orchard_shardtree; +mod pir_notes; mod received_notes_nullable_nf; mod receiving_key_scopes; mod sapling_memo_consistency; @@ -130,6 +131,8 @@ pub(super) fn all_migrations< // \ \ v_received_output_spends_account / / // \ \ / / / // `------------------- account_delete_cascade ---------------------------------' + // | + // pir_notes // let rng = Rc::new(Mutex::new(rng)); vec![ @@ -215,6 +218,7 @@ pub(super) fn all_migrations< Box::new(v_received_output_spends_account::Migration), Box::new(add_transaction_trust_marker::Migration), Box::new(account_delete_cascade::Migration), + Box::new(pir_notes::Migration), ] } @@ -227,7 +231,7 @@ pub(super) fn all_migrations< const PUBLIC_MIGRATION_STATES: &[&[Uuid]] = &[ V_0_4_0, V_0_6_0, V_0_8_0, V_0_9_0, V_0_10_0, V_0_10_3, V_0_11_0, V_0_11_1, V_0_11_2, V_0_12_0, V_0_13_0, V_0_14_0, V_0_15_0, V_0_16_0, V_0_16_2, V_0_16_4, V_0_17_2, V_0_17_3, V_0_18_0, - V_0_18_5, V_0_19_0, + V_0_18_5, V_0_19_0, V_0_19_6, ]; /// Leaf migrations in the 0.4.0 release. @@ -354,8 +358,11 @@ pub const V_0_18_5: &[Uuid] = &[ /// Leaf migrations in the 0.19.0 release. pub const V_0_19_0: &[Uuid] = &[account_delete_cascade::MIGRATION_ID]; +/// Leaf migrations in the 0.19.6 release. +pub const V_0_19_6: &[Uuid] = &[account_delete_cascade::MIGRATION_ID]; + /// Leaf migrations as of the current repository state. -pub const CURRENT_LEAF_MIGRATIONS: &[Uuid] = &[account_delete_cascade::MIGRATION_ID]; +pub const CURRENT_LEAF_MIGRATIONS: &[Uuid] = &[pir_notes::MIGRATION_ID]; pub(super) fn verify_network_compatibility( conn: &rusqlite::Connection, diff --git a/zcash_client_sqlite/src/wallet/init/migrations/pir_notes.rs b/zcash_client_sqlite/src/wallet/init/migrations/pir_notes.rs new file mode 100644 index 0000000000..d1a43c088a --- /dev/null +++ b/zcash_client_sqlite/src/wallet/init/migrations/pir_notes.rs @@ -0,0 +1,93 @@ +//! PIR note tracking table. +//! +//! A single `pir_notes` table that tracks the full PIR lifecycle for any +//! note — canonical or provisional: spent-state, witness data, recursive +//! change-discovery chain, and scanner reconciliation. +//! +//! The table is created unconditionally (not gated by `#[cfg(feature = "spendability-pir")]`) +//! to keep the migration DAG identical across all builds. + +use std::collections::HashSet; + +use schemerz_rusqlite::RusqliteMigration; +use tracing::debug; +use uuid::Uuid; + +use crate::wallet::init::WalletMigrationError; + +use super::account_delete_cascade; + +pub(super) const MIGRATION_ID: Uuid = Uuid::from_u128(0xd5f9c3b6_4a0b_4e28_c3f5_1b6a4d0e9f73); + +const DEPENDENCIES: &[Uuid] = &[account_delete_cascade::MIGRATION_ID]; + +pub(super) struct Migration; + +impl schemerz::Migration for Migration { + fn id(&self) -> Uuid { + MIGRATION_ID + } + + fn dependencies(&self) -> HashSet { + DEPENDENCIES.iter().copied().collect() + } + + fn description(&self) -> &'static str { + "Adds unified pir_notes table for PIR note lifecycle tracking." + } +} + +impl RusqliteMigration for Migration { + type Error = WalletMigrationError; + + fn up(&self, transaction: &rusqlite::Transaction) -> Result<(), Self::Error> { + debug!("Creating pir_notes table"); + transaction.execute_batch( + "CREATE TABLE pir_notes ( + id INTEGER PRIMARY KEY, + canonical_note_id INTEGER UNIQUE + REFERENCES orchard_received_notes(id) ON DELETE CASCADE, + account_id INTEGER NOT NULL REFERENCES accounts(id), + position INTEGER NOT NULL UNIQUE, + value INTEGER NOT NULL, + diversifier BLOB, + rseed BLOB, + rho BLOB, + cmx BLOB, + nullifier BLOB UNIQUE, + is_spent INTEGER NOT NULL DEFAULT 0, + spend_height INTEGER, + witness_siblings BLOB + CHECK(witness_siblings IS NULL OR length(witness_siblings) = 1024), + witness_anchor_height INTEGER, + witness_anchor_root BLOB + CHECK(witness_anchor_root IS NULL OR length(witness_anchor_root) = 32), + depth INTEGER NOT NULL DEFAULT 0, + parent_id INTEGER REFERENCES pir_notes(id), + pir_checked INTEGER NOT NULL DEFAULT 0, + discovered_by_scanner INTEGER NOT NULL DEFAULT 0, + spending_tx_hash BLOB + CHECK(spending_tx_hash IS NULL OR length(spending_tx_hash) = 32), + spending_block_time INTEGER, + spending_fee INTEGER + )", + )?; + + Ok(()) + } + + fn down(&self, transaction: &rusqlite::Transaction) -> Result<(), Self::Error> { + transaction.execute_batch("DROP TABLE pir_notes;")?; + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use crate::wallet::init::migrations::tests::test_migrate; + + #[test] + fn migrate() { + test_migrate(&[super::MIGRATION_ID]); + } +} diff --git a/zcash_client_sqlite/src/wallet/orchard.rs b/zcash_client_sqlite/src/wallet/orchard.rs index 664e9294ec..90f7400050 100644 --- a/zcash_client_sqlite/src/wallet/orchard.rs +++ b/zcash_client_sqlite/src/wallet/orchard.rs @@ -370,6 +370,19 @@ pub(crate) fn put_received_note< .query_row(sql_args, |row| row.get::<_, i64>(0)) .map_err(SqliteClientError::from)?; + // Reconcile: if a provisional PIR note exists at the same tree position, + // set canonical_note_id and mark it as scanner-discovered so its descendants + // remain valid. The is_spent flag on the same row is picked up by + // spent_notes_clause via canonical_note_id. + #[cfg(feature = "spendability-pir")] + if let Some(position) = output.note_commitment_tree_position() { + super::pir::reconcile_provisional_for_position( + conn, + u64::from(position), + received_note_id, + )?; + } + if let Some(spent_in) = spent_in { conn.execute( "INSERT INTO orchard_received_note_spends (orchard_received_note_id, transaction_id) @@ -672,4 +685,1116 @@ pub(crate) mod tests { fn receive_two_notes_with_same_value() { testing::pool::receive_two_notes_with_same_value::(); } + + /// Verifies the full PIR witness fallback path in `create_proposed_transactions`: + /// when ShardTree checkpoints are unavailable, the transaction builder falls back + /// to PIR-stored witnesses and anchors to build a valid Orchard spend. + #[cfg(feature = "spendability-pir")] + #[test] + fn pir_witness_fallback_creates_transaction() { + use std::convert::Infallible; + use zcash_client_backend::{ + data_api::{ + Account as _, WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::{ConfirmationsPolicy, input_selection::GreedyInputSelector}, + }, + fees::{DustOutputPolicy, StandardFeeRule, standard::SingleOutputChangeStrategy}, + wallet::OvkPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + use zip321::Payment; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + let value = Zatoshis::const_from_u64(60000); + let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h, 1); + + assert_eq!(st.get_total_balance(account.id()), value); + assert_eq!( + st.get_spendable_balance(account.id(), ConfirmationsPolicy::MIN), + value, + ); + + // Find note DB id and commitment tree position. + let (note_id, note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + let position = incrementalmerkletree::Position::from(note_position as u64); + + // Extract the real witness from the ShardTree while it is still complete. + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h)? + .expect("root exists at scanned height"); + + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(position, &h)? + .expect("witness exists for scanned note"); + + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + // Store as PIR witness (simulates what the PIR client would do). + pir::insert_pir_witness( + st.wallet().conn(), + note_id, + &siblings_bytes, + u32::from(h) as u64, + &anchor_root_bytes, + ) + .unwrap(); + + assert!(pir::has_pir_witness(st.wallet().conn(), note_id).unwrap()); + + // Remove ShardTree checkpoints so the tree path in build_proposed_transaction + // returns Err, triggering the PIR fallback in pir_orchard_witness_fallback. + st.wallet() + .conn() + .execute_batch( + "DELETE FROM orchard_tree_checkpoint_marks_removed; + DELETE FROM orchard_tree_checkpoints;", + ) + .unwrap(); + + // Propose a transfer — coin selection still works because the shard is + // marked as scanned and the note has a PIR witness. + let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]); + let to = OrchardPoolTester::sk_default_address(&to_extsk); + let request = zip321::TransactionRequest::new(vec![Payment::without_memo( + to.to_zcash_address(st.network()), + Zatoshis::const_from_u64(10000), + )]) + .unwrap(); + + let change_strategy = SingleOutputChangeStrategy::new( + StandardFeeRule::Zip317, + None, + OrchardPoolTester::SHIELDED_PROTOCOL, + DustOutputPolicy::default(), + ); + let input_selector = GreedyInputSelector::new(); + + let proposal = st + .propose_transfer( + account.id(), + &input_selector, + &change_strategy, + request, + ConfirmationsPolicy::MIN, + ) + .unwrap(); + + // Create the transaction — ShardTree has no checkpoints so the normal + // witness path fails, and the PIR fallback produces the spend. + let result = st.create_proposed_transactions::( + account.usk(), + OvkPolicy::Sender, + &proposal, + ); + + assert!( + result.is_ok(), + "PIR witness fallback should create transaction: {:?}", + result.err() + ); + assert_eq!(result.unwrap().len(), 1); + } + + /// Verifies that a server-produced witness for the wallet's actual note + /// commitment is rejected if tampered before insert, but succeeds end-to-end + /// when inserted honestly and later consumed by the PIR fallback path. + #[cfg(feature = "spendability-pir")] + #[test] + fn pir_witness_server_round_trip_inserts_and_spends_real_note() { + use std::convert::Infallible; + + use commitment_tree_db::CommitmentTreeDb; + use incrementalmerkletree::{Hashable, Level}; + use orchard::{note::ExtractedNoteCommitment, tree::MerkleHashOrchard}; + use zcash_client_backend::{ + data_api::{ + Account as _, WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::{ConfirmationsPolicy, input_selection::GreedyInputSelector}, + }, + fees::{DustOutputPolicy, StandardFeeRule, standard::SingleOutputChangeStrategy}, + wallet::OvkPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + use zip321::Payment; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + const TREE_DEPTH: usize = 32; + const SUBSHARD_HEIGHT: u8 = 8; + const SHARD_HEIGHT: u8 = 16; + + fn hash_combine(level: u8, left: &[u8; 32], right: &[u8; 32]) -> [u8; 32] { + let left = MerkleHashOrchard::from_bytes(left).unwrap(); + let right = MerkleHashOrchard::from_bytes(right).unwrap(); + ::combine(Level::from(level), &left, &right).to_bytes() + } + + fn empty_root(level: u8) -> [u8; 32] { + ::empty_root(Level::from(level)).to_bytes() + } + + fn extract_siblings( + nodes: &[[u8; 32]], + index: usize, + base_level: u8, + siblings: &mut [[u8; 32]; TREE_DEPTH], + ) { + let num_levels = nodes.len().trailing_zeros() as usize; + let mut current_nodes = nodes.to_vec(); + let mut idx = index; + + for level_offset in 0..num_levels { + let tree_level = base_level as usize + level_offset; + let sibling_idx = idx ^ 1; + siblings[tree_level] = if sibling_idx < current_nodes.len() { + current_nodes[sibling_idx] + } else { + empty_root(tree_level as u8) + }; + + let mut next = Vec::with_capacity(current_nodes.len() / 2); + for pair in current_nodes.chunks(2) { + let left = pair[0]; + let right = if pair.len() > 1 { + pair[1] + } else { + empty_root(tree_level as u8) + }; + next.push(hash_combine(tree_level as u8, &left, &right)); + } + current_nodes = next; + idx /= 2; + } + } + + fn compute_root_from_path( + position: u64, + leaf: &[u8; 32], + siblings: &[[u8; 32]; TREE_DEPTH], + ) -> [u8; 32] { + let mut current = *leaf; + let mut pos = position; + + for (level, sibling) in siblings.iter().enumerate() { + let (left, right) = if pos & 1 == 0 { + (¤t, sibling) + } else { + (sibling, ¤t) + }; + current = hash_combine(level as u8, left, right); + pos >>= 1; + } + + current + } + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + let value = Zatoshis::const_from_u64(60_000); + let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h, 1); + + let (note_id, note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + + let position = incrementalmerkletree::Position::from(note_position as u64); + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h)? + .expect("root exists at scanned height"); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(position, &h)? + .expect("witness exists for scanned note"); + + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + let anchor_height = u32::from(h) as u64; + let initial_validation = st + .wallet() + .db() + .validate_pir_orchard_witness( + note_id, + &siblings_bytes, + anchor_height, + &anchor_root_bytes, + ) + .unwrap(); + assert!( + initial_validation.witness_root_matches_anchor(), + "wallet's own checkpoint witness should validate before the server round-trip" + ); + + let received_note = st + .wallet() + .conn() + .query_row_and_then( + "SELECT + rn.id, + t.txid, + rn.action_index, + rn.diversifier, + rn.value, + rn.rho, + rn.rseed, + rn.commitment_tree_position, + accounts.ufvk, + rn.recipient_key_scope, + t.mined_height, + NULL AS max_shielding_input_height + FROM orchard_received_notes rn + INNER JOIN accounts ON accounts.id = rn.account_id + INNER JOIN transactions t ON t.id_tx = rn.transaction_id + WHERE rn.id = ?1", + [note_id], + |row| super::to_received_note(st.network(), row), + ) + .unwrap() + .expect("stored note should be reconstructible"); + let note_commitment: ExtractedNoteCommitment = received_note.note().commitment().into(); + + let mut server_leaves = + vec![MerkleHashOrchard::empty_leaf().to_bytes(); note_position as usize]; + server_leaves.push(MerkleHashOrchard::from_cmx(¬e_commitment).to_bytes()); + + let mut server_tree = CommitmentTreeDb::new(); + server_tree.append_commitments(anchor_height, [0xAA; 32], &server_leaves); + let expected_server_root = server_tree.tree_root(); + let (_, broadcast) = server_tree.build_pir_db_and_broadcast(anchor_height); + + let server_position = note_position as u64; + let shard_idx = (server_position >> SHARD_HEIGHT) as u32; + let subshard_idx = ((server_position >> SUBSHARD_HEIGHT) & 0xFF) as u8; + let leaf_idx = (server_position & 0xFF) as usize; + + let leaves = server_tree.subshard_leaves(shard_idx, subshard_idx); + let mut server_siblings = [[0u8; 32]; TREE_DEPTH]; + extract_siblings(&leaves, leaf_idx, 0, &mut server_siblings); + + let shard_offset = (shard_idx - broadcast.window_start_shard) as usize; + let ss_roots = &broadcast.subshard_roots[shard_offset].roots; + extract_siblings( + ss_roots, + subshard_idx as usize, + SUBSHARD_HEIGHT, + &mut server_siblings, + ); + + let total_cap_slots = 1usize << SHARD_HEIGHT; + let mut padded_cap = broadcast.cap.shard_roots.clone(); + padded_cap.resize(total_cap_slots, empty_root(SHARD_HEIGHT)); + extract_siblings( + &padded_cap, + shard_idx as usize, + SHARD_HEIGHT, + &mut server_siblings, + ); + + let server_anchor_root = + compute_root_from_path(server_position, &leaves[leaf_idx], &server_siblings); + let server_anchor_height = broadcast.anchor_height; + + assert_eq!(server_anchor_height, anchor_height); + assert_eq!(server_anchor_root, expected_server_root); + + let mut tampered_siblings = server_siblings; + tampered_siblings.swap(0, 1); + let tampered_validation = st + .wallet() + .db() + .validate_pir_orchard_witness( + note_id, + &tampered_siblings, + server_anchor_height, + &server_anchor_root, + ) + .unwrap(); + assert!( + !tampered_validation.witness_root_matches_anchor(), + "tampered server witness should fail pre-insert validation" + ); + assert!( + !pir::has_pir_witness(st.wallet().conn(), note_id).unwrap(), + "failed validation must not persist a PIR witness row" + ); + + st.wallet() + .db() + .insert_pir_witness( + note_id, + &server_siblings, + server_anchor_height, + &server_anchor_root, + ) + .unwrap(); + + st.wallet() + .conn() + .execute_batch( + "DELETE FROM orchard_tree_checkpoint_marks_removed; + DELETE FROM orchard_tree_checkpoints;", + ) + .unwrap(); + + let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]); + let to = OrchardPoolTester::sk_default_address(&to_extsk); + let request = zip321::TransactionRequest::new(vec![Payment::without_memo( + to.to_zcash_address(st.network()), + Zatoshis::const_from_u64(10_000), + )]) + .unwrap(); + + let change_strategy = SingleOutputChangeStrategy::new( + StandardFeeRule::Zip317, + None, + OrchardPoolTester::SHIELDED_PROTOCOL, + DustOutputPolicy::default(), + ); + let input_selector = GreedyInputSelector::new(); + let proposal = st + .propose_transfer( + account.id(), + &input_selector, + &change_strategy, + request, + ConfirmationsPolicy::MIN, + ) + .unwrap(); + + let result = st.create_proposed_transactions::( + account.usk(), + OvkPolicy::Sender, + &proposal, + ); + + assert!( + result.is_ok(), + "honest server witness should support PIR fallback spending: {:?}", + result.err() + ); + assert_eq!(result.unwrap().len(), 1); + } + + /// When no PIR witness is stored for a note, transaction creation should fail + /// rather than silently produce an invalid spend. + #[cfg(feature = "spendability-pir")] + #[test] + fn pir_witness_missing_fails_transaction() { + use std::convert::Infallible; + use zcash_client_backend::{ + data_api::{ + Account as _, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::{ConfirmationsPolicy, input_selection::GreedyInputSelector}, + }, + fees::{DustOutputPolicy, StandardFeeRule, standard::SingleOutputChangeStrategy}, + wallet::OvkPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + use zip321::Payment; + + use crate::testing::{BlockCache, db::TestDbFactory}; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + let value = Zatoshis::const_from_u64(60000); + let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h, 1); + + // Remove ShardTree checkpoints but do NOT insert a PIR witness. + st.wallet() + .conn() + .execute_batch( + "DELETE FROM orchard_tree_checkpoint_marks_removed; + DELETE FROM orchard_tree_checkpoints;", + ) + .unwrap(); + + let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]); + let to = OrchardPoolTester::sk_default_address(&to_extsk); + let request = zip321::TransactionRequest::new(vec![Payment::without_memo( + to.to_zcash_address(st.network()), + Zatoshis::const_from_u64(10000), + )]) + .unwrap(); + + let change_strategy = SingleOutputChangeStrategy::new( + StandardFeeRule::Zip317, + None, + OrchardPoolTester::SHIELDED_PROTOCOL, + DustOutputPolicy::default(), + ); + let input_selector = GreedyInputSelector::new(); + + let proposal = st + .propose_transfer( + account.id(), + &input_selector, + &change_strategy, + request, + ConfirmationsPolicy::MIN, + ) + .unwrap(); + + let result = st.create_proposed_transactions::( + account.usk(), + OvkPolicy::Sender, + &proposal, + ); + + assert!( + result.is_err(), + "Should fail when no PIR witness is available" + ); + } + + /// When two notes have PIR witnesses with different anchor roots, transaction + /// creation should fail because the Orchard bundle requires a single anchor. + #[cfg(feature = "spendability-pir")] + #[test] + fn pir_witness_anchor_mismatch_fails_transaction() { + use std::convert::Infallible; + use zcash_client_backend::{ + data_api::{ + Account as _, WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::{ConfirmationsPolicy, input_selection::GreedyInputSelector}, + }, + fees::{DustOutputPolicy, StandardFeeRule, standard::SingleOutputChangeStrategy}, + wallet::OvkPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + use zip321::Payment; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + // Generate two blocks with one note each so the proposal selects both. + let value = Zatoshis::const_from_u64(50000); + let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h1, 1); + let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h2, 1); + + // Extract real witnesses from ShardTree, but forge a different anchor root + // for the second note to simulate an anchor mismatch. + let notes: Vec<(i64, i64)> = { + let mut stmt = st + .wallet() + .conn() + .prepare( + "SELECT id, commitment_tree_position FROM orchard_received_notes \ + ORDER BY id", + ) + .unwrap(); + stmt.query_map([], |row| Ok((row.get(0)?, row.get(1)?))) + .unwrap() + .map(|r| r.unwrap()) + .collect() + }; + assert_eq!(notes.len(), 2); + + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h2)? + .expect("root exists"); + let pos = incrementalmerkletree::Position::from(notes[0].1 as u64); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(pos, &h2)? + .expect("witness exists"); + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + // Note 1: real anchor root + pir::insert_pir_witness( + st.wallet().conn(), + notes[0].0, + &siblings_bytes, + u32::from(h2) as u64, + &anchor_root_bytes, + ) + .unwrap(); + + // Note 2: deliberately different anchor root + let mut bad_root = anchor_root_bytes; + bad_root[0] ^= 0xFF; + pir::insert_pir_witness( + st.wallet().conn(), + notes[1].0, + &siblings_bytes, + u32::from(h2) as u64, + &bad_root, + ) + .unwrap(); + + // Remove ShardTree checkpoints to force PIR path. + st.wallet() + .conn() + .execute_batch( + "DELETE FROM orchard_tree_checkpoint_marks_removed; + DELETE FROM orchard_tree_checkpoints;", + ) + .unwrap(); + + let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]); + let to = OrchardPoolTester::sk_default_address(&to_extsk); + // Request enough to force both notes into the proposal. + let request = zip321::TransactionRequest::new(vec![Payment::without_memo( + to.to_zcash_address(st.network()), + Zatoshis::const_from_u64(60000), + )]) + .unwrap(); + + let change_strategy = SingleOutputChangeStrategy::new( + StandardFeeRule::Zip317, + None, + OrchardPoolTester::SHIELDED_PROTOCOL, + DustOutputPolicy::default(), + ); + let input_selector = GreedyInputSelector::new(); + + let proposal = st + .propose_transfer( + account.id(), + &input_selector, + &change_strategy, + request, + ConfirmationsPolicy::MIN, + ) + .unwrap(); + + let result = st.create_proposed_transactions::( + account.usk(), + OvkPolicy::Sender, + &proposal, + ); + + let err = result.expect_err("Should fail when PIR witnesses have incompatible anchors"); + assert!( + format!("{err}").contains("incompatible PIR witness anchors"), + "unexpected error: {err}" + ); + } + + /// Verifies that coin selection includes a note whose shard is NOT fully scanned + /// when a PIR witness is available. This exercises the `OR EXISTS` branch of + /// `shard_scanned_condition` — without it, the note would be excluded. + #[cfg(feature = "spendability-pir")] + #[test] + fn pir_witness_enables_selection_for_unscanned_shard() { + use std::convert::Infallible; + use zcash_client_backend::{ + data_api::{ + Account as _, WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::{ConfirmationsPolicy, input_selection::GreedyInputSelector}, + }, + fees::{DustOutputPolicy, StandardFeeRule, standard::SingleOutputChangeStrategy}, + wallet::OvkPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + use zip321::Payment; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + let value = Zatoshis::const_from_u64(60000); + let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h, 1); + + // Extract the real witness while ShardTree is complete. + let (note_id, note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + let position = incrementalmerkletree::Position::from(note_position as u64); + + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h)? + .expect("root exists"); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(position, &h)? + .expect("witness exists"); + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + pir::insert_pir_witness( + st.wallet().conn(), + note_id, + &siblings_bytes, + u32::from(h) as u64, + &anchor_root_bytes, + ) + .unwrap(); + + // Mark the shard as unscanned by raising scan_queue priority to ChainTip (50), + // which is above Scanned (10). This simulates a note in a shard that hasn't + // been fully scanned yet. + st.wallet() + .conn() + .execute("UPDATE scan_queue SET priority = 50", []) + .unwrap(); + + // Also remove ShardTree checkpoints so the normal witness path fails. + st.wallet() + .conn() + .execute_batch( + "DELETE FROM orchard_tree_checkpoint_marks_removed; + DELETE FROM orchard_tree_checkpoints;", + ) + .unwrap(); + + // Verify the shard really looks unscanned from the view's perspective. + let max_priority: i64 = st + .wallet() + .conn() + .query_row( + "SELECT max_priority FROM v_orchard_shards_scan_state LIMIT 1", + [], + |row| row.get(0), + ) + .unwrap(); + assert!( + max_priority > 10, + "shard should appear unscanned (priority {max_priority} > Scanned=10)" + ); + + // Coin selection should still include the note thanks to the PIR witness. + let to_extsk = OrchardPoolTester::sk(&[0xf5; 32]); + let to = OrchardPoolTester::sk_default_address(&to_extsk); + let request = zip321::TransactionRequest::new(vec![Payment::without_memo( + to.to_zcash_address(st.network()), + Zatoshis::const_from_u64(10000), + )]) + .unwrap(); + + let change_strategy = SingleOutputChangeStrategy::new( + StandardFeeRule::Zip317, + None, + OrchardPoolTester::SHIELDED_PROTOCOL, + DustOutputPolicy::default(), + ); + let input_selector = GreedyInputSelector::new(); + + let proposal = st + .propose_transfer( + account.id(), + &input_selector, + &change_strategy, + request, + ConfirmationsPolicy::MIN, + ) + .unwrap(); + + let result = st.create_proposed_transactions::( + account.usk(), + OvkPolicy::Sender, + &proposal, + ); + + assert!( + result.is_ok(), + "Note in unscanned shard with PIR witness should be spendable: {:?}", + result.err() + ); + } + + /// Verifies that `get_wallet_summary` reports a PIR-witnessed note as spendable + /// even when its shard is not fully scanned. This exercises the `|| has_pir_witness` + /// branch in the wallet summary query, separate from coin selection. + #[cfg(feature = "spendability-pir")] + #[test] + fn wallet_summary_includes_pir_witnessed_note_as_spendable() { + use zcash_client_backend::data_api::{ + Account as _, WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::ConfirmationsPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + let value = Zatoshis::const_from_u64(60000); + let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h, 1); + + // Confirm spendable before we manipulate scan state. + assert_eq!( + st.get_spendable_balance(account.id(), ConfirmationsPolicy::MIN), + value, + ); + + // Extract witness and store as PIR. + let (note_id, note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + let position = incrementalmerkletree::Position::from(note_position as u64); + + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h)? + .expect("root exists"); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(position, &h)? + .expect("witness exists"); + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + pir::insert_pir_witness( + st.wallet().conn(), + note_id, + &siblings_bytes, + u32::from(h) as u64, + &anchor_root_bytes, + ) + .unwrap(); + + // Mark shard as unscanned (ChainTip=50 > Scanned=10). + st.wallet() + .conn() + .execute("UPDATE scan_queue SET priority = 50", []) + .unwrap(); + + // Without PIR witness, the note would NOT be spendable (shard unscanned). + // With PIR witness, get_wallet_summary should still report it as spendable. + let spendable = st.get_spendable_balance(account.id(), ConfirmationsPolicy::MIN); + assert_eq!( + spendable, value, + "PIR-witnessed note in unscanned shard should appear spendable in wallet summary" + ); + } + + /// Verifies that wallet summary aggregation remains note-specific when only a + /// subset of Orchard notes have PIR witnesses available. + #[cfg(feature = "spendability-pir")] + #[test] + fn wallet_summary_only_upgrades_pir_witnessed_notes() { + use zcash_client_backend::data_api::{ + Account as _, WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + wallet::ConfirmationsPolicy, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let account = st.test_account().cloned().unwrap(); + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + let first_value = Zatoshis::const_from_u64(60_000); + let second_value = Zatoshis::const_from_u64(80_000); + + let (_h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, first_value); + st.scan_cached_blocks(_h1, 1); + let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, second_value); + st.scan_cached_blocks(h2, 1); + + let (first_note_id, first_note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes ORDER BY id LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + let first_position = incrementalmerkletree::Position::from(first_note_position as u64); + + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h2)? + .expect("root exists"); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(first_position, &h2)? + .expect("witness exists"); + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + pir::insert_pir_witness( + st.wallet().conn(), + first_note_id, + &siblings_bytes, + u32::from(h2) as u64, + &anchor_root_bytes, + ) + .unwrap(); + + st.wallet() + .conn() + .execute("UPDATE scan_queue SET priority = 50", []) + .unwrap(); + + let summary = st + .get_wallet_summary(ConfirmationsPolicy::MIN) + .expect("wallet summary should be present"); + let orchard_balance = summary + .account_balances() + .get(&account.id()) + .expect("account balance should exist") + .orchard_balance(); + + assert_eq!( + orchard_balance.spendable_value(), + first_value, + "only the PIR-witnessed Orchard note should remain spendable" + ); + assert_eq!( + orchard_balance.value_pending_spendability(), + second_value, + "unresolved Orchard notes should remain pending spendability" + ); + assert_eq!( + orchard_balance.total(), + (first_value + second_value).expect("sum should fit in Zatoshi range"), + "wallet summary should preserve the full Orchard total while splitting readiness note-by-note" + ); + } + + /// Verifies that `truncate_to_height` clears PIR witness data from `pir_notes` + /// to avoid stale authentication paths after a reorg. + #[cfg(feature = "spendability-pir")] + #[test] + fn truncate_to_height_clears_pir_notes() { + use zcash_client_backend::data_api::{ + WalletCommitmentTrees, + testing::{AddressType, TestBuilder, pool::ShieldedPoolTester}, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::{commitment_tree, pir}, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let dfvk = OrchardPoolTester::test_account_fvk(&st); + + let value = Zatoshis::const_from_u64(60000); + let (h1, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h1, 1); + let (h2, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h2, 1); + + let (note_id, note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + let position = incrementalmerkletree::Position::from(note_position as u64); + + let (siblings_bytes, anchor_root_bytes) = st + .wallet_mut() + .with_orchard_tree_mut::<_, _, shardtree::error::ShardTreeError>( + |orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h2)? + .expect("root exists"); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(position, &h2)? + .expect("witness exists"); + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + Ok((siblings, root.to_bytes())) + }, + ) + .unwrap(); + + pir::insert_pir_witness( + st.wallet().conn(), + note_id, + &siblings_bytes, + u32::from(h2) as u64, + &anchor_root_bytes, + ) + .unwrap(); + + assert!(pir::has_pir_witness(st.wallet().conn(), note_id).unwrap()); + + // Truncate to the first block, rewinding past the second. + st.truncate_to_height(h1); + + assert!( + !pir::has_pir_witness(st.wallet().conn(), note_id).unwrap(), + "PIR witness data should be cleared after truncate_to_height" + ); + } } diff --git a/zcash_client_sqlite/src/wallet/pir.rs b/zcash_client_sqlite/src/wallet/pir.rs new file mode 100644 index 0000000000..6bd399e35f --- /dev/null +++ b/zcash_client_sqlite/src/wallet/pir.rs @@ -0,0 +1,2388 @@ +//! PIR (Private Information Retrieval) note lifecycle storage. +//! +//! All PIR state lives in the single `pir_notes` table, created unconditionally by +//! migration so the schema is identical across builds. When the `spendability-pir` +//! feature is off, the table is empty and unused. +//! +//! A row tracks one Orchard note through three overlapping concerns: +//! +//! - **Spend detection** — nullifier PIR discovers that a canonical note has been +//! spent on-chain before the scanner confirms it (`is_spent = 1`). +//! +//! - **Witness storage** — witness PIR fetches the Merkle authentication path from +//! a server, enabling a note to be spent before its shard is fully scanned +//! (`witness_siblings IS NOT NULL`). +//! +//! - **Provisional notes** — when nullifier PIR detects a spend, trial decryption +//! discovers the resulting change notes. These "provisional" rows have +//! `canonical_note_id = NULL` until the scanner catches up and reconciles them. + +use rusqlite::{Connection, OptionalExtension, named_params, params}; + +use crate::error::SqliteClientError; + +#[cfg(feature = "orchard")] +use { + incrementalmerkletree::{MerklePath, Position}, + orchard::{note::ExtractedNoteCommitment, tree::MerkleHashOrchard}, + zcash_client_backend::wallet::ReceivedNote, + zcash_protocol::consensus, +}; + +// ========================================================================= +// Test infrastructure +// ========================================================================= + +#[cfg(any(test, feature = "test-dependencies"))] +pub mod testing { + use rusqlite::Connection; + + use secrecy::SecretVec; + use zcash_protocol::consensus::Network; + + use crate::{WalletDb, wallet::init::WalletMigrator}; + + /// Runs the full wallet migration on `path`, then reopens a plain + /// [`Connection`] with FK enforcement and prerequisite rows for PIR tests. + fn migrate_and_setup(path: impl AsRef) -> Connection { + let mut db = WalletDb::for_path( + path.as_ref(), + Network::TestNetwork, + crate::util::SystemClock, + rand_core::OsRng, + ) + .unwrap(); + WalletMigrator::new() + .with_seed(SecretVec::new(vec![0xab; 32])) + .init_or_migrate(&mut db) + .unwrap(); + drop(db); + + let conn = Connection::open(path.as_ref()).unwrap(); + conn.execute_batch("PRAGMA foreign_keys = ON;").unwrap(); + conn.execute_batch( + "INSERT INTO accounts ( + uuid, account_kind, uivk, birthday_height, has_spend_key + ) VALUES ( + X'00000000000000000000000000000001', 1, + 'test-uivk-for-pir', 1, 1 + ); + INSERT INTO transactions (id_tx, txid, min_observed_height) + VALUES ( + 100, + X'0000000000000000000000000000000000000000000000000000000000000001', + 1 + );", + ) + .unwrap(); + + conn + } + + /// A migrated wallet database for PIR tests. Holds the temp file so the + /// on-disk database is not cleaned up while tests are running. + #[cfg(test)] + pub struct PirTestDb { + conn: Connection, + _data_file: tempfile::NamedTempFile, + } + + #[cfg(test)] + impl Default for PirTestDb { + fn default() -> Self { + Self::new() + } + } + + #[cfg(test)] + impl PirTestDb { + pub fn new() -> Self { + let data_file = tempfile::NamedTempFile::new().unwrap(); + let conn = migrate_and_setup(data_file.path()); + Self { + conn, + _data_file: data_file, + } + } + + pub fn conn(&self) -> &Connection { + &self.conn + } + } + + /// Creates an on-disk SQLite database with the full migrated wallet schema, + /// ready for PIR tests. Caller is responsible for cleanup. + pub fn create_pir_test_db_on_disk(suffix: &str) -> (Connection, std::path::PathBuf) { + let db_path = std::env::temp_dir().join(format!( + "pir_test_{}_{}_{}.db", + std::process::id(), + suffix, + std::thread::current().name().unwrap_or("t") + )); + let conn = migrate_and_setup(&db_path); + (conn, db_path) + } + + /// Inserts a synthetic note row into `orchard_received_notes` for testing. + /// + /// Sets `commitment_tree_position` and `recipient_key_scope` so the note is + /// eligible for witness queries. Use `position = None` for notes that should + /// lack a tree position. + pub fn insert_test_note_with_position( + conn: &Connection, + id: i64, + value: i64, + nf: Option<&[u8]>, + position: Option, + ) { + conn.execute( + "INSERT INTO orchard_received_notes \ + (id, transaction_id, action_index, account_id, diversifier, value, \ + rho, rseed, nf, is_change, commitment_tree_position, recipient_key_scope) \ + VALUES (?1, 100, ?1, 1, X'00', ?2, X'00', X'00', ?3, 0, ?4, 0)", + rusqlite::params![id, value, nf, position], + ) + .unwrap(); + } + + /// Minimal note insert without `commitment_tree_position` or `recipient_key_scope`. + pub fn insert_test_note(conn: &Connection, id: i64, value: i64, nf: Option<&[u8]>) { + conn.execute( + "INSERT INTO orchard_received_notes \ + (id, transaction_id, action_index, account_id, diversifier, value, \ + rho, rseed, nf, is_change) \ + VALUES (?1, 100, ?1, 1, X'00', ?2, X'00', X'00', ?3, 0)", + rusqlite::params![id, value, nf], + ) + .unwrap(); + } +} + +// ========================================================================= +// Types +// ========================================================================= + +/// An unspent Orchard note with its nullifier, for PIR spend-checking. +pub struct UnspentOrchardNote { + pub id: i64, + pub nf: [u8; 32], + pub value: u64, +} + +/// An Orchard note whose shard is not fully scanned and that lacks a PIR witness. +pub struct NoteNeedingWitness { + pub id: i64, + pub position: u64, + pub value: u64, +} + +/// A stored PIR witness for an Orchard note. +pub struct PirWitnessRow { + pub note_id: i64, + pub siblings: [[u8; 32]; 32], + pub anchor_height: u64, + pub anchor_root: [u8; 32], +} + +/// A note that has a PIR witness but whose shard hasn't caught up yet. +pub struct PirWitnessedNote { + pub note_id: i64, + pub value: u64, + pub anchor_height: u64, +} + +#[cfg(feature = "orchard")] +type PirWitnessResult = + Result, u64, [u8; 32])>, SqliteClientError>; + +#[cfg(feature = "orchard")] +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct PirWitnessValidation { + pub provided_anchor_root: [u8; 32], + pub computed_root: [u8; 32], +} + +#[cfg(feature = "orchard")] +impl PirWitnessValidation { + pub fn witness_root_matches_anchor(&self) -> bool { + self.computed_root == self.provided_anchor_root + } +} + +/// A provisional note that needs a PIR witness before it can be spent. +pub struct ProvisionalNoteNeedingWitness { + pub id: i64, + pub position: u64, + pub value: u64, +} + +/// A provisional note ready for nullifier PIR checking. +pub struct ProvisionalNoteForPIR { + pub id: i64, + pub nullifier: [u8; 32], + pub value: u64, + /// The canonical `orchard_received_notes` ID that started this chain. + /// Needed for FVK lookup when discovering deeper change notes. + pub spent_note_id: i64, + /// This note's depth in the chain (1 = direct change from canonical). + /// Used to compute `depth + 1` for children accurately. + pub depth: u32, +} + +/// A PIR-derived transaction entry for the activity view. +/// +/// Aggregates co-spent canonical notes by `spending_tx_hash` and computes +/// the net spend as `gross_value - change_value`. +pub struct PirActivityEntry { + pub tx_hash: [u8; 32], + pub block_time: u32, + pub fee: Option, + pub height: u32, + pub gross_value: u64, + pub change_value: u64, +} + +impl PirActivityEntry { + pub fn net_value(&self) -> u64 { + self.gross_value.saturating_sub(self.change_value) + } +} + +// ========================================================================= +// Spend tracking +// ========================================================================= + +const UNSPENT_ORCHARD_NOTES_SQL: &str = "\ + SELECT rn.id, rn.nf, rn.value FROM orchard_received_notes rn \ + WHERE rn.nf IS NOT NULL \ + AND NOT EXISTS ( \ + SELECT 1 FROM orchard_received_note_spends sp \ + WHERE sp.orchard_received_note_id = rn.id \ + ) \ + AND NOT EXISTS ( \ + SELECT 1 FROM pir_notes pn \ + WHERE pn.canonical_note_id = rn.id AND pn.is_spent = 1 \ + )"; + +/// Returns unspent Orchard notes that have nullifiers, excluding both +/// scan-confirmed spends and PIR-detected spends. Used by the PIR FFI +/// to determine which nullifiers to check against the PIR server. +pub fn get_unspent_orchard_notes_for_pir( + conn: &Connection, +) -> Result, SqliteClientError> { + let mut stmt = conn.prepare(UNSPENT_ORCHARD_NOTES_SQL)?; + + let notes = stmt + .query_map([], |row| { + let id: i64 = row.get(0)?; + let nf_blob: Vec = row.get(1)?; + let value: i64 = row.get(2)?; + Ok((id, nf_blob, value as u64)) + })? + .collect::, _>>()?; + + notes + .into_iter() + .map(|(id, nf_blob, value)| { + let nf: [u8; 32] = nf_blob.try_into().map_err(|_| { + SqliteClientError::CorruptedData( + "orchard nullifier is not 32 bytes".to_string(), + ) + })?; + Ok(UnspentOrchardNote { id, nf, value }) + }) + .collect() +} + +/// Records a canonical note as PIR-spent by upserting into `pir_notes`. +/// +/// If a row already exists for this canonical note (e.g. from a witness insert), +/// sets `is_spent = 1`. Otherwise inserts a new row pulling position/value/account +/// from `orchard_received_notes`. +/// +/// Skips notes that are already scan-confirmed spent. +pub fn insert_pir_spent_note(conn: &Connection, note_id: i64) -> Result<(), SqliteClientError> { + conn.execute( + "INSERT INTO pir_notes (canonical_note_id, account_id, position, value, is_spent) + SELECT rn.id, rn.account_id, rn.commitment_tree_position, rn.value, 1 + FROM orchard_received_notes rn + WHERE rn.id = ?1 + AND rn.commitment_tree_position IS NOT NULL + AND NOT EXISTS ( + SELECT 1 FROM orchard_received_note_spends + WHERE orchard_received_note_id = ?1 + ) + ON CONFLICT(canonical_note_id) DO UPDATE SET + is_spent = 1", + [note_id], + )?; + Ok(()) +} + +// ========================================================================= +// Activity entries (PIR-derived transaction data for the UI) +// ========================================================================= + +/// Returns the `pir_notes.id` for a given canonical note ID, if one exists. +pub fn get_pir_note_id_for_canonical( + conn: &Connection, + canonical_note_id: i64, +) -> Result, SqliteClientError> { + let id = conn + .query_row( + "SELECT id FROM pir_notes WHERE canonical_note_id = ?1", + [canonical_note_id], + |row| row.get(0), + ) + .optional()?; + Ok(id) +} + +/// Sets spending transaction metadata on a `pir_notes` row after change discovery. +pub fn set_pir_spending_tx_metadata( + conn: &Connection, + pir_note_id: i64, + tx_hash: &[u8; 32], + block_time: u32, + fee: Option, + spend_height: Option, +) -> Result<(), SqliteClientError> { + conn.execute( + "UPDATE pir_notes + SET spending_tx_hash = :tx_hash, + spending_block_time = :block_time, + spending_fee = :fee, + spend_height = COALESCE(:spend_height, spend_height) + WHERE id = :id", + named_params! { + ":id": pir_note_id, + ":tx_hash": &tx_hash[..], + ":block_time": block_time, + ":fee": fee.map(|f| f as i64), + ":spend_height": spend_height, + }, + )?; + Ok(()) +} + +const PIR_ACTIVITY_ENTRIES_SQL: &str = "\ + WITH RECURSIVE pending_roots AS ( \ + SELECT pn.id, pn.spending_tx_hash, pn.spending_block_time, pn.spending_fee, \ + pn.spend_height, rn.value AS gross_value \ + FROM pir_notes pn \ + JOIN orchard_received_notes rn ON pn.canonical_note_id = rn.id \ + WHERE pn.is_spent = 1 \ + AND pn.spending_tx_hash IS NOT NULL \ + AND NOT EXISTS ( \ + SELECT 1 FROM orchard_received_note_spends sp \ + WHERE sp.orchard_received_note_id = pn.canonical_note_id \ + ) \ + ), \ + tree(node_id, tx_hash) AS ( \ + SELECT id, spending_tx_hash FROM pending_roots \ + UNION ALL \ + SELECT child.id, tree.tx_hash \ + FROM pir_notes child \ + JOIN tree ON child.parent_id = tree.node_id \ + ) \ + SELECT \ + pr.spending_tx_hash AS tx_hash, \ + MAX(pr.spending_block_time) AS block_time, \ + MAX(pr.spending_fee) AS fee, \ + MAX(pr.spend_height) AS height, \ + SUM(pr.gross_value) AS gross_value, \ + COALESCE(( \ + SELECT SUM(leaf.value) \ + FROM tree t \ + JOIN pir_notes leaf ON leaf.id = t.node_id \ + WHERE t.tx_hash = pr.spending_tx_hash \ + AND leaf.is_spent = 0 \ + AND leaf.canonical_note_id IS NULL \ + AND leaf.id NOT IN (SELECT id FROM pending_roots) \ + ), 0) AS change_value \ + FROM pending_roots pr \ + GROUP BY pr.spending_tx_hash"; + +/// Returns PIR-derived transaction entries for the activity view. +/// +/// Each entry represents a spending transaction detected via PIR that the +/// scanner has not yet confirmed. Co-spent canonical notes are grouped by +/// `spending_tx_hash`. The `change_value` is the sum of unspent descendant +/// provisional leaves, giving `net_value = gross_value - change_value`. +pub fn get_pir_activity_entries( + conn: &Connection, +) -> Result, SqliteClientError> { + let mut stmt = conn.prepare(PIR_ACTIVITY_ENTRIES_SQL)?; + + let entries = stmt + .query_map([], |row| { + let tx_hash_blob: Vec = row.get("tx_hash")?; + let block_time: i64 = row.get("block_time")?; + let fee: Option = row.get("fee")?; + let height: i64 = row.get("height")?; + let gross_value: i64 = row.get("gross_value")?; + let change_value: i64 = row.get("change_value")?; + Ok((tx_hash_blob, block_time, fee, height, gross_value, change_value)) + })? + .collect::, _>>()?; + + entries + .into_iter() + .map(|(tx_hash_blob, block_time, fee, height, gross_value, change_value)| { + let tx_hash: [u8; 32] = tx_hash_blob.try_into().map_err(|_| { + SqliteClientError::CorruptedData( + "pir_notes spending_tx_hash is not 32 bytes".to_string(), + ) + })?; + Ok(PirActivityEntry { + tx_hash, + block_time: block_time as u32, + fee: fee.map(|f| f as u64), + height: height as u32, + gross_value: gross_value as u64, + change_value: change_value as u64, + }) + }) + .collect() +} + +// ========================================================================= +// Witness storage +// ========================================================================= + +/// The `ScanPriority::Scanned` value as stored in the DB (must match +/// `scanning::priority_code(&ScanPriority::Scanned)`). +const SCANNED_PRIORITY_CODE: i64 = 10; + +const NOTES_NEEDING_WITNESS_SQL: &str = "\ + SELECT rn.id, rn.commitment_tree_position, rn.value \ + FROM orchard_received_notes rn \ + LEFT OUTER JOIN v_orchard_shards_scan_state scan_state \ + ON rn.commitment_tree_position >= scan_state.start_position \ + AND rn.commitment_tree_position < scan_state.end_position_exclusive \ + WHERE rn.commitment_tree_position IS NOT NULL \ + AND rn.nf IS NOT NULL \ + AND rn.recipient_key_scope IS NOT NULL \ + AND NOT EXISTS ( \ + SELECT 1 FROM orchard_received_note_spends sp \ + WHERE sp.orchard_received_note_id = rn.id \ + ) \ + AND NOT EXISTS ( \ + SELECT 1 FROM pir_notes pn \ + WHERE pn.canonical_note_id = rn.id AND pn.is_spent = 1 \ + ) \ + AND NOT EXISTS ( \ + SELECT 1 FROM pir_notes pn \ + WHERE pn.canonical_note_id = rn.id AND pn.witness_siblings IS NOT NULL \ + ) \ + AND (scan_state.max_priority IS NULL \ + OR scan_state.max_priority > ?1)"; + +const WITNESSED_NOTES_SQL: &str = "\ + SELECT pn.canonical_note_id AS note_id, rn.value, pn.witness_anchor_height AS anchor_height \ + FROM pir_notes pn \ + JOIN orchard_received_notes rn ON pn.canonical_note_id = rn.id \ + WHERE pn.witness_siblings IS NOT NULL \ + AND NOT EXISTS ( \ + SELECT 1 FROM orchard_received_note_spends sp \ + WHERE sp.orchard_received_note_id = pn.canonical_note_id \ + )"; + +/// Returns Orchard notes that need a PIR witness: they have a tree position, +/// are unspent, and their shard is not fully scanned. +pub fn get_notes_needing_pir_witness( + conn: &Connection, +) -> Result, SqliteClientError> { + let mut stmt = conn.prepare(NOTES_NEEDING_WITNESS_SQL)?; + + let notes = stmt + .query_map([SCANNED_PRIORITY_CODE], |row| { + let id: i64 = row.get(0)?; + let position: i64 = row.get(1)?; + let value: i64 = row.get(2)?; + Ok(NoteNeedingWitness { + id, + position: position as u64, + value: value as u64, + }) + })? + .collect::, _>>()?; + + Ok(notes) +} + +/// Stores a PIR-obtained witness for a canonical note by upserting into `pir_notes`. +/// +/// If a row already exists for this canonical note (e.g. from a spent-note insert), +/// the witness columns are updated. Otherwise a new row is inserted pulling +/// position/value/account from `orchard_received_notes`. +/// +/// Existing witnesses are refreshed only when the incoming snapshot is at least +/// as new as the stored anchor height. +pub fn insert_pir_witness( + conn: &Connection, + note_id: i64, + siblings: &[[u8; 32]; 32], + anchor_height: u64, + anchor_root: &[u8; 32], +) -> Result<(), SqliteClientError> { + let siblings_blob: Vec = siblings.iter().flat_map(|s| s.iter()).copied().collect(); + conn.execute( + "INSERT INTO pir_notes (canonical_note_id, account_id, position, value, + witness_siblings, witness_anchor_height, witness_anchor_root) + SELECT rn.id, rn.account_id, rn.commitment_tree_position, rn.value, + ?2, ?3, ?4 + FROM orchard_received_notes rn + WHERE rn.id = ?1 + AND rn.commitment_tree_position IS NOT NULL + ON CONFLICT(canonical_note_id) DO UPDATE SET + witness_siblings = excluded.witness_siblings, + witness_anchor_height = excluded.witness_anchor_height, + witness_anchor_root = excluded.witness_anchor_root + WHERE excluded.witness_anchor_height >= IFNULL(pir_notes.witness_anchor_height, 0)", + params![ + note_id, + siblings_blob, + anchor_height as i64, + anchor_root.as_slice() + ], + )?; + Ok(()) +} + +/// Sets witness data on a provisional note after a PIR witness is obtained, +/// making it eligible for balance and coin selection. +pub fn mark_provisional_note_witnessed( + conn: &Connection, + note_id: i64, + siblings: &[[u8; 32]; 32], + anchor_height: u64, + anchor_root: &[u8; 32], +) -> Result { + let siblings_blob: Vec = siblings.iter().flat_map(|s| s.iter()).copied().collect(); + let rows = conn.execute( + "UPDATE pir_notes + SET witness_siblings = :siblings, + witness_anchor_height = :anchor_height, + witness_anchor_root = :anchor_root + WHERE id = :id AND canonical_note_id IS NULL", + named_params! { + ":id": note_id, + ":siblings": siblings_blob, + ":anchor_height": anchor_height as i64, + ":anchor_root": &anchor_root[..], + }, + )?; + Ok(rows > 0) +} + +/// Retrieves a stored PIR witness for a specific canonical note. +pub fn get_pir_witness( + conn: &Connection, + note_id: i64, +) -> Result, SqliteClientError> { + let result = conn + .query_row( + "SELECT canonical_note_id, witness_siblings, witness_anchor_height, witness_anchor_root \ + FROM pir_notes WHERE canonical_note_id = ?1 AND witness_siblings IS NOT NULL", + [note_id], + |row| { + let note_id: i64 = row.get(0)?; + let siblings_blob: Vec = row.get(1)?; + let anchor_height: i64 = row.get(2)?; + let anchor_root_blob: Vec = row.get(3)?; + Ok(( + note_id, + siblings_blob, + anchor_height as u64, + anchor_root_blob, + )) + }, + ) + .optional()?; + + match result { + None => Ok(None), + Some((note_id, siblings_blob, anchor_height, anchor_root_blob)) => { + let siblings = parse_siblings(&siblings_blob)?; + let anchor_root: [u8; 32] = anchor_root_blob.try_into().map_err(|_| { + SqliteClientError::CorruptedData( + "pir_notes witness_anchor_root is not 32 bytes".to_string(), + ) + })?; + Ok(Some(PirWitnessRow { + note_id, + siblings, + anchor_height, + anchor_root, + })) + } + } +} + +/// Returns notes that have PIR witnesses and are still unspent. +pub fn get_pir_witnessed_notes( + conn: &Connection, +) -> Result, SqliteClientError> { + let mut stmt = conn.prepare(WITNESSED_NOTES_SQL)?; + + let notes = stmt + .query_map([], |row| { + let note_id: i64 = row.get(0)?; + let value: i64 = row.get(1)?; + let anchor_height: i64 = row.get(2)?; + Ok(PirWitnessedNote { + note_id, + value: value as u64, + anchor_height: anchor_height as u64, + }) + })? + .collect::, _>>()?; + + Ok(notes) +} + +/// Checks whether a PIR witness exists for the given canonical note. +pub fn has_pir_witness(conn: &Connection, note_id: i64) -> Result { + let count: i64 = conn.query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = ?1 AND witness_siblings IS NOT NULL", + [note_id], + |row| row.get(0), + )?; + Ok(count > 0) +} + +/// Returns provisional notes that have a tree position but lack a witness. +/// +/// These are active change notes discovered via PIR trial decryption that +/// haven't been reconciled by the scanner and aren't yet spendable because +/// `witness_siblings` is NULL. The caller fetches witnesses from the PIR +/// server and stores them via [`mark_provisional_note_witnessed`]. +pub fn get_provisional_notes_needing_witness( + conn: &Connection, +) -> Result, SqliteClientError> { + let mut stmt = conn.prepare( + "SELECT pn.id, pn.position, pn.value + FROM pir_notes pn + WHERE pn.canonical_note_id IS NULL + AND pn.is_spent = 0 + AND pn.discovered_by_scanner = 0 + AND pn.position IS NOT NULL + AND pn.witness_siblings IS NULL", + )?; + let rows = stmt.query_map([], |row| { + Ok(ProvisionalNoteNeedingWitness { + id: row.get("id")?, + position: row.get::<_, i64>("position").map(|v| v as u64)?, + value: row.get::<_, i64>("value").map(|v| v as u64)?, + }) + })?; + + rows.collect::, _>>() + .map_err(SqliteClientError::from) +} + +// ========================================================================= +// Merkle path construction +// ========================================================================= + +/// Retrieves a PIR witness for the given note and converts it into a `MerklePath` +/// suitable for the Orchard transaction builder. +/// +/// Returns `Ok(None)` if no PIR witness exists for the note. +/// +/// The `MerklePath` contains the same data as `ShardTree::witness_at_checkpoint_id_caching` +/// would return: 32 authentication path siblings ordered leaf-to-root, with the position +/// encoding the left/right direction at each level. +/// +/// The caller is responsible for using the returned anchor height and root to set the +/// transaction's Orchard anchor — the PIR anchor may differ from the proposal's computed +/// anchor. +#[cfg(feature = "orchard")] +pub fn get_pir_merkle_path( + conn: &Connection, + note_id: i64, + position: Position, +) -> PirWitnessResult { + let witness = get_pir_witness(conn, note_id)?; + match witness { + None => Ok(None), + Some(row) => { + let path: Vec = row + .siblings + .iter() + .map(|bytes| { + Option::from(MerkleHashOrchard::from_bytes(bytes)).ok_or_else(|| { + SqliteClientError::CorruptedData( + "invalid MerkleHashOrchard in pir_notes".to_string(), + ) + }) + }) + .collect::>()?; + + let merkle_path = MerklePath::from_parts(path, position).map_err(|_| { + SqliteClientError::CorruptedData( + "failed to construct MerklePath from PIR witness".to_string(), + ) + })?; + + Ok(Some((merkle_path, row.anchor_height, row.anchor_root))) + } + } +} + +/// Retrieves a PIR Merkle path by the note's commitment tree position. +/// +/// Joins through `orchard_received_notes` to find the matching `note_id`, then +/// delegates to [`get_pir_merkle_path`]. +#[cfg(feature = "orchard")] +pub fn get_pir_merkle_path_by_position(conn: &Connection, position: Position) -> PirWitnessResult { + let note_id: Option = conn + .query_row( + "SELECT rn.id FROM orchard_received_notes rn \ + INNER JOIN pir_notes pn ON pn.canonical_note_id = rn.id \ + WHERE rn.commitment_tree_position = ?1 \ + AND pn.witness_siblings IS NOT NULL", + [u64::from(position) as i64], + |row| row.get(0), + ) + .optional()?; + + match note_id { + Some(id) => get_pir_merkle_path(conn, id, position), + None => Ok(None), + } +} + +/// Validates a PIR-obtained Merkle witness against the note's commitment. +/// +/// Reconstructs the Orchard `MerklePath` from the supplied siblings and computes +/// the root from the note's extracted commitment (`cmx`). Returns a +/// [`PirWitnessValidation`] containing both the provided and computed roots so +/// the caller can check whether they match. +/// +/// This is used for server-trust verification: the PIR server supplies (siblings, +/// anchor_root), and we independently compute the root from the note + siblings to +/// confirm the path is authentic. +#[cfg(feature = "orchard")] +pub fn validate_orchard_witness( + conn: &Connection, + params: &P, + note_id: i64, + siblings: &[[u8; 32]; 32], + anchor_height: u64, + anchor_root: &[u8; 32], +) -> Result { + let received_note = get_orchard_received_note(conn, params, note_id)?; + let txid = hex::encode(received_note.txid().as_ref()); + let action_index = received_note.output_index(); + let position = received_note.note_commitment_tree_position(); + let value = received_note.note().value().inner(); + let mined_height = received_note.mined_height().map(u32::from); + + let path: Vec = siblings + .iter() + .map(|bytes| { + Option::from(MerkleHashOrchard::from_bytes(bytes)).ok_or_else(|| { + SqliteClientError::CorruptedData( + "invalid MerkleHashOrchard in PIR witness validation input".to_string(), + ) + }) + }) + .collect::>()?; + + let merkle_path: MerklePath = MerklePath::from_parts(path, position) + .map_err(|_| { + SqliteClientError::CorruptedData( + "failed to construct MerklePath from PIR witness validation input".to_string(), + ) + })?; + let note = received_note.note(); + let ecmx: ExtractedNoteCommitment = note.commitment().into(); + let cmx = MerkleHashOrchard::from_cmx(&ecmx); + let computed_root = merkle_path.root(cmx).to_bytes(); + let witness_root_matches_anchor = computed_root == *anchor_root; + + if !witness_root_matches_anchor { + tracing::warn!( + note_id, + txid = %txid, + action_index, + position = u64::from(position), + value, + mined_height, + anchor_height, + "wallet PIR witness validation root mismatch", + ); + } + + Ok(PirWitnessValidation { + provided_anchor_root: *anchor_root, + computed_root, + }) +} + +// ========================================================================= +// Provisional note lifecycle +// ========================================================================= + +/// Inserts a provisional note discovered via PIR trial decryption. +/// +/// Uses `INSERT OR IGNORE` so that duplicate positions are silently skipped +/// (idempotent across retries). +/// +/// Returns the row ID of the inserted (or existing) row. +#[allow(clippy::too_many_arguments)] +pub fn insert_pir_provisional_note( + conn: &Connection, + account_id: i64, + value: u64, + position: u64, + diversifier: &[u8; 11], + rseed: &[u8; 32], + rho: &[u8; 32], + nullifier: &[u8; 32], + cmx: &[u8; 32], + spend_height: u32, + depth: u32, + parent_provisional_id: Option, +) -> Result { + conn.execute( + "INSERT OR IGNORE INTO pir_notes + (account_id, value, position, diversifier, + rseed, rho, nullifier, cmx, spend_height, depth, parent_id) + VALUES + (:account_id, :value, :position, :diversifier, + :rseed, :rho, :nullifier, :cmx, :spend_height, :depth, :parent_id)", + named_params! { + ":account_id": account_id, + ":value": i64::try_from(value).expect("note value fits i64"), + ":position": i64::try_from(position).expect("position fits i64"), + ":diversifier": &diversifier[..], + ":rseed": &rseed[..], + ":rho": &rho[..], + ":nullifier": &nullifier[..], + ":cmx": &cmx[..], + ":spend_height": spend_height, + ":depth": depth, + ":parent_id": parent_provisional_id, + }, + )?; + + let row_id: i64 = conn.query_row( + "SELECT id FROM pir_notes WHERE position = :position", + named_params! { ":position": i64::try_from(position).expect("position fits i64") }, + |row| row.get(0), + )?; + + Ok(row_id) +} + +/// Returns provisional notes whose nullifiers have not yet been checked via PIR. +/// +/// Excludes notes already reconciled by the scanner (`discovered_by_scanner = 1`). +/// +/// `spent_note_id` is the canonical `orchard_received_notes` ID at the root of each +/// note's parent chain. It is resolved via a recursive CTE that walks `parent_id` +/// links up to the node whose `canonical_note_id` is set. +pub fn get_provisional_notes_for_pir_check( + conn: &Connection, +) -> Result, SqliteClientError> { + let mut stmt = conn.prepare( + "WITH RECURSIVE root_chain(node_id, root_canonical_id) AS ( + SELECT id, canonical_note_id FROM pir_notes + WHERE canonical_note_id IS NOT NULL + UNION ALL + SELECT child.id, rc.root_canonical_id + FROM pir_notes child + JOIN root_chain rc ON child.parent_id = rc.node_id + ) + SELECT pn.id, pn.nullifier, pn.value, pn.depth, + COALESCE(rc.root_canonical_id, 0) AS spent_note_id + FROM pir_notes pn + LEFT JOIN root_chain rc ON rc.node_id = pn.id + WHERE pn.canonical_note_id IS NULL + AND pn.pir_checked = 0 + AND pn.discovered_by_scanner = 0", + )?; + let rows = stmt.query_map( + [], + |row| { + let nf_blob: Vec = row.get("nullifier")?; + Ok(ProvisionalNoteForPIR { + id: row.get("id")?, + nullifier: nf_blob + .try_into() + .map_err(|_| rusqlite::Error::InvalidColumnType(1, "nullifier".into(), rusqlite::types::Type::Blob))?, + value: row.get::<_, i64>("value").map(|v| v as u64)?, + spent_note_id: row.get("spent_note_id")?, + depth: row.get::<_, i64>("depth").map(|v| v as u32)?, + }) + }, + )?; + + rows.collect::, _>>().map_err(SqliteClientError::from) +} + +/// Marks a provisional note as PIR-checked after a nullifier lookup. +/// +/// Always sets `pir_checked = 1`. If `is_spent` is true, also sets +/// `is_spent = 1` (the note's value is carried by its children). +/// The `is_spent` flag is monotonic: once set to 1 it cannot revert to 0, +/// even if this function is called again with `is_spent = false`. +pub fn mark_provisional_pir_result( + conn: &Connection, + note_id: i64, + is_spent: bool, +) -> Result<(), SqliteClientError> { + conn.execute( + "UPDATE pir_notes + SET pir_checked = 1, is_spent = MAX(is_spent, :is_spent) + WHERE id = :id", + named_params! { + ":id": note_id, + ":is_spent": is_spent, + }, + )?; + Ok(()) +} + +/// Reconciles a provisional note with the canonical scanner. +/// +/// When the scanner inserts a canonical note at the same tree position, this +/// function sets `canonical_note_id` and `discovered_by_scanner = 1` on the +/// existing row. The `is_spent` flag is already on the same row, so no +/// cross-table transfer is needed — the `spent_notes_clause` will pick it up +/// via `canonical_note_id`. +/// +/// The provisional note's descendants remain valid in the DB. +pub fn reconcile_provisional_for_position( + conn: &Connection, + position: u64, + canonical_note_id: i64, +) -> Result { + let pos_i64 = i64::try_from(position).expect("position fits i64"); + + let rows = conn.execute( + "UPDATE pir_notes + SET canonical_note_id = :canonical_note_id, + discovered_by_scanner = 1 + WHERE position = :position + AND canonical_note_id IS NULL", + named_params! { + ":canonical_note_id": canonical_note_id, + ":position": pos_i64, + }, + )?; + + Ok(rows > 0) +} + +// ========================================================================= +// Internal helpers +// ========================================================================= + +/// Loads an Orchard `ReceivedNote` from the database for witness validation. +/// +/// The note must have a UFVK, recipient key scope, and commitment tree position. +/// Returns a `CorruptedData` error if the note cannot be found or reconstructed. +#[cfg(feature = "orchard")] +fn get_orchard_received_note( + conn: &Connection, + params: &P, + note_id: i64, +) -> Result, SqliteClientError> { + let result = conn.query_row_and_then( + "SELECT + rn.id, + t.txid, + rn.action_index, + rn.diversifier, + rn.value, + rn.rho, + rn.rseed, + rn.commitment_tree_position, + accounts.ufvk, + rn.recipient_key_scope, + t.mined_height, + NULL AS max_shielding_input_height + FROM orchard_received_notes rn + INNER JOIN accounts ON accounts.id = rn.account_id + INNER JOIN transactions t ON t.id_tx = rn.transaction_id + WHERE rn.id = ?1 + AND accounts.ufvk IS NOT NULL + AND rn.recipient_key_scope IS NOT NULL + AND rn.commitment_tree_position IS NOT NULL", + [note_id], + |row| super::orchard::to_received_note(params, row), + ); + + match result { + Ok(Some(note)) => Ok(note), + Ok(None) => Err(SqliteClientError::CorruptedData(format!( + "failed to reconstruct Orchard note {note_id} for PIR witness validation" + ))), + Err(SqliteClientError::DbError(rusqlite::Error::QueryReturnedNoRows)) => { + Err(SqliteClientError::CorruptedData(format!( + "Orchard note {note_id} not found for PIR witness validation" + ))) + } + Err(e) => Err(e), + } +} + +/// Parses a 1024-byte blob into 32 Merkle siblings (32 bytes each). +fn parse_siblings(blob: &[u8]) -> Result<[[u8; 32]; 32], SqliteClientError> { + if blob.len() != 1024 { + return Err(SqliteClientError::CorruptedData(format!( + "pir_notes witness_siblings blob is {} bytes, expected 1024", + blob.len() + ))); + } + let mut siblings = [[0u8; 32]; 32]; + for (i, chunk) in blob.chunks_exact(32).enumerate() { + siblings[i].copy_from_slice(chunk); + } + Ok(siblings) +} + +// ========================================================================= +// Tests +// ========================================================================= + +#[cfg(test)] +mod tests { + use super::*; + use testing::{PirTestDb, insert_test_note, insert_test_note_with_position}; + + fn make_nf(byte: u8) -> Vec { + vec![byte; 32] + } + + fn make_siblings(seed: u8) -> [[u8; 32]; 32] { + let mut siblings = [[0u8; 32]; 32]; + for (i, sibling) in siblings.iter_mut().enumerate() { + sibling.fill(seed.wrapping_add(i as u8)); + } + siblings + } + + fn make_root(byte: u8) -> [u8; 32] { + [byte; 32] + } + + fn mark_spent(conn: &Connection, note_id: i64) { + conn.execute( + "INSERT INTO orchard_received_note_spends (orchard_received_note_id, transaction_id) \ + VALUES (?1, 100)", + [note_id], + ) + .unwrap(); + } + + fn mark_pir_spent(conn: &Connection, note_id: i64) { + insert_pir_spent_note(conn, note_id).unwrap(); + } + + fn insert_canonical_note(conn: &Connection, id: i64, position: i64, value: i64) { + insert_test_note_with_position( + conn, + id, + value, + Some(&[id as u8; 32]), + Some(position), + ); + } + + fn insert_test_provisional(conn: &Connection, position: u64, value: u64) -> i64 { + insert_pir_provisional_note( + conn, + 1, + value, + position, + &[0u8; 11], + &[0u8; 32], + &[0u8; 32], + &[position as u8; 32], + &[0u8; 32], + 3_200_000, + 1, + None, + ) + .unwrap() + } + + fn insert_test_provisional_with_depth( + conn: &Connection, + position: u64, + value: u64, + depth: u32, + parent_id: Option, + ) -> i64 { + insert_pir_provisional_note( + conn, + 1, + value, + position, + &[0u8; 11], + &[0u8; 32], + &[0u8; 32], + &[position as u8; 32], + &[0u8; 32], + 3_200_000, + depth, + parent_id, + ) + .unwrap() + } + + #[cfg(feature = "orchard")] + macro_rules! real_orchard_witness_fixture { + () => {{ + use zcash_client_backend::data_api::WalletCommitmentTrees; + use zcash_client_backend::data_api::testing::{ + AddressType, TestBuilder, orchard::OrchardPoolTester, pool::ShieldedPoolTester, + }; + use zcash_primitives::block::BlockHash; + use zcash_protocol::value::Zatoshis; + + use crate::{ + testing::{BlockCache, db::TestDbFactory}, + wallet::commitment_tree, + }; + + let mut st = TestBuilder::new() + .with_data_store_factory(TestDbFactory::default()) + .with_block_cache(BlockCache::new()) + .with_account_from_sapling_activation(BlockHash([0; 32])) + .build(); + + let dfvk = OrchardPoolTester::test_account_fvk(&st); + let value = Zatoshis::const_from_u64(60_000); + let (h, _, _) = st.generate_next_block(&dfvk, AddressType::DefaultExternal, value); + st.scan_cached_blocks(h, 1); + + let (note_id, note_position): (i64, i64) = st + .wallet() + .conn() + .query_row( + "SELECT id, commitment_tree_position FROM orchard_received_notes LIMIT 1", + [], + |row| Ok((row.get(0)?, row.get(1)?)), + ) + .unwrap(); + + let position = incrementalmerkletree::Position::from(note_position as u64); + let (siblings, anchor_root) = st + .wallet_mut() + .with_orchard_tree_mut::< + _, + _, + shardtree::error::ShardTreeError, + >(|orchard_tree| { + let root = orchard_tree + .root_at_checkpoint_id(&h)? + .expect("root exists at scanned height"); + let merkle_path = orchard_tree + .witness_at_checkpoint_id_caching(position, &h)? + .expect("witness exists for scanned note"); + + let mut siblings = [[0u8; 32]; 32]; + for (i, elem) in merkle_path.path_elems().iter().enumerate() { + siblings[i] = elem.to_bytes(); + } + + Ok((siblings, root.to_bytes())) + }) + .unwrap(); + + (st, note_id, note_position, siblings, anchor_root, u32::from(h) as u64) + }}; + } + + // ===================================================================== + // Spend tracking — unspent notes query + // ===================================================================== + + #[test] + fn spend_empty_table_returns_no_notes() { + let db = PirTestDb::new(); + let notes = get_unspent_orchard_notes_for_pir(db.conn()).unwrap(); + assert!(notes.is_empty()); + } + + #[test] + fn returns_unspent_notes_with_nullifiers() { + let db = PirTestDb::new(); + let nf1 = make_nf(0xAA); + let nf2 = make_nf(0xBB); + insert_test_note(db.conn(), 1, 50_000, Some(&nf1)); + insert_test_note(db.conn(), 2, 75_000, Some(&nf2)); + + let notes = get_unspent_orchard_notes_for_pir(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + assert_eq!(notes[0].id, 1); + assert_eq!(notes[0].value, 50_000); + assert_eq!(notes[0].nf, [0xAA; 32]); + assert_eq!(notes[1].id, 2); + assert_eq!(notes[1].value, 75_000); + } + + #[test] + fn spend_excludes_notes_without_nullifier() { + let db = PirTestDb::new(); + let nf1 = make_nf(0xAA); + insert_test_note(db.conn(), 1, 50_000, Some(&nf1)); + insert_test_note(db.conn(), 2, 75_000, None); + + let notes = get_unspent_orchard_notes_for_pir(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + #[test] + fn spend_excludes_spent_notes() { + let db = PirTestDb::new(); + let nf1 = make_nf(0xAA); + let nf2 = make_nf(0xBB); + let nf3 = make_nf(0xCC); + insert_test_note(db.conn(), 1, 10_000, Some(&nf1)); + insert_test_note(db.conn(), 2, 20_000, Some(&nf2)); + insert_test_note(db.conn(), 3, 30_000, Some(&nf3)); + + mark_spent(db.conn(), 2); + + let notes = get_unspent_orchard_notes_for_pir(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + let ids: Vec = notes.iter().map(|n| n.id).collect(); + assert!(ids.contains(&1)); + assert!(ids.contains(&3)); + assert!(!ids.contains(&2)); + } + + #[test] + fn spend_excludes_pir_spent_notes() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 10_000, Some(&make_nf(0x01)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 20_000, Some(&make_nf(0x02)), Some(2000)); + insert_test_note_with_position(db.conn(), 3, 30_000, Some(&make_nf(0x03)), Some(3000)); + + mark_pir_spent(db.conn(), 2); + + let notes = get_unspent_orchard_notes_for_pir(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + let ids: Vec = notes.iter().map(|n| n.id).collect(); + assert!(ids.contains(&1)); + assert!(ids.contains(&3)); + assert!(!ids.contains(&2)); + } + + #[test] + fn excludes_both_pir_and_real_spent() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 10_000, Some(&make_nf(0x01)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 20_000, Some(&make_nf(0x02)), Some(2000)); + insert_test_note_with_position(db.conn(), 3, 30_000, Some(&make_nf(0x03)), Some(3000)); + + mark_spent(db.conn(), 2); + mark_pir_spent(db.conn(), 3); + + let notes = get_unspent_orchard_notes_for_pir(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + #[test] + fn insert_pir_basic() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 10_000, Some(&make_nf(0x01)), Some(1000)); + + insert_pir_spent_note(db.conn(), 1).unwrap(); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1 AND is_spent = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + #[test] + fn insert_pir_skips_real_spent() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 10_000, Some(&make_nf(0x01)), Some(1000)); + + mark_spent(db.conn(), 1); + insert_pir_spent_note(db.conn(), 1).unwrap(); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 0); + } + + #[test] + fn insert_pir_idempotent() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 10_000, Some(&make_nf(0x01)), Some(1000)); + + insert_pir_spent_note(db.conn(), 1).unwrap(); + insert_pir_spent_note(db.conn(), 1).unwrap(); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + #[test] + fn insert_pir_fk_cascade() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 10_000, Some(&make_nf(0x01)), Some(1000)); + + mark_pir_spent(db.conn(), 1); + + db.conn() + .execute("DELETE FROM orchard_received_notes WHERE id = 1", []) + .unwrap(); + + let count: i64 = db + .conn() + .query_row("SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1", [], |r| r.get(0)) + .unwrap(); + assert_eq!(count, 0); + } + + // ===================================================================== + // Witness — notes needing witness + // ===================================================================== + + #[test] + fn witness_empty_table_returns_no_notes() { + let db = PirTestDb::new(); + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert!(notes.is_empty()); + } + + #[test] + fn returns_notes_with_position_and_unscanned_shard() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), Some(2000)); + + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + assert_eq!(notes[0].id, 1); + assert_eq!(notes[0].position, 1000); + assert_eq!(notes[0].value, 50_000); + } + + #[test] + fn witness_excludes_notes_without_position() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), None); + + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + #[test] + fn witness_excludes_notes_without_nullifier() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, None, Some(2000)); + + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + #[test] + fn witness_excludes_spent_notes() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), Some(2000)); + mark_spent(db.conn(), 2); + + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + #[test] + fn witness_excludes_pir_spent_notes() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), Some(2000)); + mark_pir_spent(db.conn(), 2); + + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + #[test] + fn excludes_notes_already_witnessed() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), Some(2000)); + insert_pir_witness(db.conn(), 2, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + + let notes = get_notes_needing_pir_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].id, 1); + } + + // ===================================================================== + // Witness — insert / get / has + // ===================================================================== + + #[test] + fn insert_witness_basic() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1 AND witness_siblings IS NOT NULL", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + #[test] + fn insert_replaces_existing_witness() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + insert_pir_witness(db.conn(), 1, &make_siblings(0x20), 200, &make_root(0xEE)).unwrap(); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + + let row = get_pir_witness(db.conn(), 1).unwrap().unwrap(); + assert_eq!(row.anchor_height, 200); + assert_eq!(row.anchor_root, make_root(0xEE)); + } + + #[test] + fn insert_does_not_replace_newer_witness_with_older_snapshot() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + + let newer_siblings = make_siblings(0x20); + let newer_root = make_root(0xEE); + insert_pir_witness(db.conn(), 1, &newer_siblings, 200, &newer_root).unwrap(); + + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + + let row = get_pir_witness(db.conn(), 1).unwrap().unwrap(); + assert_eq!(row.siblings, newer_siblings); + assert_eq!(row.anchor_height, 200); + assert_eq!(row.anchor_root, newer_root); + } + + #[test] + fn get_witness_returns_none_when_absent() { + let db = PirTestDb::new(); + let result = get_pir_witness(db.conn(), 999).unwrap(); + assert!(result.is_none()); + } + + #[test] + fn get_witness_returns_stored_data() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + + let siblings = make_siblings(0x10); + let root = make_root(0xFF); + insert_pir_witness(db.conn(), 1, &siblings, 100, &root).unwrap(); + + let row = get_pir_witness(db.conn(), 1).unwrap().unwrap(); + assert_eq!(row.note_id, 1); + assert_eq!(row.siblings, siblings); + assert_eq!(row.anchor_height, 100); + assert_eq!(row.anchor_root, root); + } + + // ===================================================================== + // Witness — witnessed notes query + // ===================================================================== + + #[test] + fn witnessed_notes_empty_when_no_witnesses() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + + let notes = get_pir_witnessed_notes(db.conn()).unwrap(); + assert!(notes.is_empty()); + } + + #[test] + fn witnessed_notes_returns_unspent_with_witness() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), Some(2000)); + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + insert_pir_witness(db.conn(), 2, &make_siblings(0x20), 100, &make_root(0xFF)).unwrap(); + + let notes = get_pir_witnessed_notes(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + assert_eq!(notes[0].value + notes[1].value, 125_000); + } + + #[test] + fn witnessed_notes_excludes_spent() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 75_000, Some(&make_nf(0xBB)), Some(2000)); + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + insert_pir_witness(db.conn(), 2, &make_siblings(0x20), 100, &make_root(0xFF)).unwrap(); + mark_spent(db.conn(), 2); + + let notes = get_pir_witnessed_notes(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].note_id, 1); + } + + // ===================================================================== + // Witness — has_pir_witness + // ===================================================================== + + #[test] + fn has_witness_false_when_absent() { + let db = PirTestDb::new(); + assert!(!has_pir_witness(db.conn(), 999).unwrap()); + } + + #[test] + fn has_witness_true_when_present() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + assert!(has_pir_witness(db.conn(), 1).unwrap()); + } + + // ===================================================================== + // Witness — FK cascade + // ===================================================================== + + #[test] + fn witness_fk_cascade_on_note_delete() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 100, &make_root(0xFF)).unwrap(); + + db.conn() + .execute("DELETE FROM orchard_received_notes WHERE id = 1", []) + .unwrap(); + + let count: i64 = db + .conn() + .query_row("SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 1", [], |r| r.get(0)) + .unwrap(); + assert_eq!(count, 0); + } + + // ===================================================================== + // Merkle path construction + // ===================================================================== + + #[cfg(feature = "orchard")] + #[test] + fn merkle_path_by_position_returns_none_without_witness() { + use incrementalmerkletree::Position; + + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + let result = get_pir_merkle_path_by_position(db.conn(), Position::from(1000u64)).unwrap(); + assert!(result.is_none()); + } + + #[cfg(feature = "orchard")] + #[test] + fn merkle_path_by_position_returns_path_with_witness() { + use incrementalmerkletree::Position; + + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + + let siblings = make_siblings(0x10); + let root = make_root(0xFF); + insert_pir_witness(db.conn(), 1, &siblings, 200, &root).unwrap(); + + let result = get_pir_merkle_path_by_position(db.conn(), Position::from(1000u64)).unwrap(); + assert!(result.is_some()); + + let (merkle_path, anchor_height, anchor_root) = result.unwrap(); + assert_eq!(anchor_height, 200); + assert_eq!(anchor_root, root); + assert_eq!(u64::from(merkle_path.position()), 1000); + } + + #[cfg(feature = "orchard")] + #[test] + fn merkle_path_by_position_no_match_for_wrong_position() { + use incrementalmerkletree::Position; + + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0xAA)), Some(1000)); + insert_pir_witness(db.conn(), 1, &make_siblings(0x10), 200, &make_root(0xFF)).unwrap(); + + let result = get_pir_merkle_path_by_position(db.conn(), Position::from(9999u64)).unwrap(); + assert!(result.is_none()); + } + + #[cfg(feature = "orchard")] + #[test] + fn validate_orchard_witness_accepts_real_merkle_path() { + let (st, note_id, _note_position, siblings, anchor_root, anchor_height) = + real_orchard_witness_fixture!(); + + let validation = validate_orchard_witness( + st.wallet().conn(), + st.network(), + note_id, + &siblings, + anchor_height, + &anchor_root, + ) + .expect("real Orchard witness should validate"); + + assert_eq!(validation.provided_anchor_root, anchor_root); + assert_eq!(validation.computed_root, anchor_root); + assert!( + validation.witness_root_matches_anchor(), + "real Orchard witness should hash back to the provided anchor" + ); + } + + #[cfg(feature = "orchard")] + #[test] + fn validate_orchard_witness_rejects_tampered_real_merkle_path() { + let (st, note_id, _note_position, mut siblings, anchor_root, anchor_height) = + real_orchard_witness_fixture!(); + + siblings.swap(0, 1); + let validation = validate_orchard_witness( + st.wallet().conn(), + st.network(), + note_id, + &siblings, + anchor_height, + &anchor_root, + ) + .expect("tampered Orchard witness should still produce a validation result"); + + assert!( + !validation.witness_root_matches_anchor(), + "tampered siblings should fail the note commitment -> anchor recomputation" + ); + } + + // ===================================================================== + // Provisional — insert / retrieve + // ===================================================================== + + #[test] + fn provisional_insert_and_retrieve() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + assert!(id > 0); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id IS NULL", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + #[test] + fn provisional_insert_idempotent_by_position() { + let db = PirTestDb::new(); + let id1 = insert_test_provisional(db.conn(), 1000, 50_000); + let id2 = insert_pir_provisional_note( + db.conn(), + 1, 50_000, 1000, + &[0u8; 11], &[0u8; 32], &[0u8; 32], + &[1u8; 32], + &[0u8; 32], 3_200_000, + 1, None, + ) + .unwrap(); + assert_eq!(id1, id2); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id IS NULL", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + // ===================================================================== + // Provisional — witness + // ===================================================================== + + #[test] + fn provisional_mark_witnessed() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + + let has_witness: bool = db + .conn() + .query_row( + "SELECT witness_siblings IS NOT NULL FROM pir_notes WHERE id = ?1", + [id], + |r| r.get(0), + ) + .unwrap(); + assert!(!has_witness); + + let siblings = [[0x10u8; 32]; 32]; + let updated = mark_provisional_note_witnessed(db.conn(), id, &siblings, 100, &[0xFF; 32]).unwrap(); + assert!(updated); + + let has_witness: bool = db + .conn() + .query_row( + "SELECT witness_siblings IS NOT NULL FROM pir_notes WHERE id = ?1", + [id], + |r| r.get(0), + ) + .unwrap(); + assert!(has_witness); + } + + #[test] + fn provisional_mark_witnessed_nonexistent() { + let db = PirTestDb::new(); + let siblings = [[0x10u8; 32]; 32]; + let updated = mark_provisional_note_witnessed(db.conn(), 9999, &siblings, 100, &[0xFF; 32]).unwrap(); + assert!(!updated); + } + + #[test] + fn provisional_needing_witness_returns_unwitnessed() { + let db = PirTestDb::new(); + insert_test_provisional(db.conn(), 1000, 50_000); + insert_test_provisional(db.conn(), 2000, 75_000); + + let notes = get_provisional_notes_needing_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + assert_eq!(notes[0].position, 1000); + assert_eq!(notes[0].value, 50_000); + assert_eq!(notes[1].position, 2000); + assert_eq!(notes[1].value, 75_000); + } + + #[test] + fn provisional_needing_witness_excludes_witnessed() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + insert_test_provisional(db.conn(), 2000, 75_000); + + let siblings = [[0x10u8; 32]; 32]; + mark_provisional_note_witnessed(db.conn(), id, &siblings, 100, &[0xFF; 32]).unwrap(); + + let notes = get_provisional_notes_needing_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].position, 2000); + } + + #[test] + fn provisional_needing_witness_excludes_spent() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + insert_test_provisional(db.conn(), 2000, 75_000); + + mark_provisional_pir_result(db.conn(), id, true).unwrap(); + + let notes = get_provisional_notes_needing_witness(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].position, 2000); + } + + #[test] + fn provisional_needing_witness_excludes_scanner_reconciled() { + let db = PirTestDb::new(); + insert_test_provisional(db.conn(), 1000, 50_000); + insert_canonical_note(db.conn(), 42, 1000, 50_000); + reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + + let notes = get_provisional_notes_needing_witness(db.conn()).unwrap(); + assert!(notes.is_empty()); + } + + #[test] + fn provisional_needing_witness_empty_table() { + let db = PirTestDb::new(); + let notes = get_provisional_notes_needing_witness(db.conn()).unwrap(); + assert!(notes.is_empty()); + } + + // ===================================================================== + // Provisional — PIR check + // ===================================================================== + + #[test] + fn get_notes_for_pir_check() { + let db = PirTestDb::new(); + insert_test_provisional(db.conn(), 1000, 50_000); + insert_test_provisional(db.conn(), 2000, 75_000); + + let notes = get_provisional_notes_for_pir_check(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + } + + #[test] + fn get_notes_for_pir_check_excludes_checked() { + let db = PirTestDb::new(); + let id1 = insert_test_provisional(db.conn(), 1000, 50_000); + insert_test_provisional(db.conn(), 2000, 75_000); + + mark_provisional_pir_result(db.conn(), id1, false).unwrap(); + + let notes = get_provisional_notes_for_pir_check(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].value, 75_000); + } + + #[test] + fn get_notes_for_pir_check_excludes_scanner_reconciled() { + let db = PirTestDb::new(); + insert_test_provisional(db.conn(), 1000, 50_000); + + db.conn() + .execute( + "UPDATE pir_notes SET discovered_by_scanner = 1 WHERE position = 1000", + [], + ) + .unwrap(); + + let notes = get_provisional_notes_for_pir_check(db.conn()).unwrap(); + assert!(notes.is_empty()); + } + + #[test] + fn mark_pir_result_not_spent() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + + mark_provisional_pir_result(db.conn(), id, false).unwrap(); + + let (checked, spent): (bool, bool) = db + .conn() + .query_row( + "SELECT pir_checked, is_spent FROM pir_notes WHERE id = ?1", + [id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert!(checked); + assert!(!spent); + } + + #[test] + fn mark_pir_result_spent() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + + mark_provisional_pir_result(db.conn(), id, true).unwrap(); + + let (checked, spent): (bool, bool) = db + .conn() + .query_row( + "SELECT pir_checked, is_spent FROM pir_notes WHERE id = ?1", + [id], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert!(checked); + assert!(spent); + } + + #[test] + fn mark_pir_result_spent_is_monotonic() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + + mark_provisional_pir_result(db.conn(), id, true).unwrap(); + + db.conn() + .execute( + "UPDATE pir_notes SET pir_checked = 0 WHERE id = ?1", + [id], + ) + .unwrap(); + mark_provisional_pir_result(db.conn(), id, false).unwrap(); + + let spent: bool = db + .conn() + .query_row( + "SELECT is_spent FROM pir_notes WHERE id = ?1", + [id], + |r| r.get(0), + ) + .unwrap(); + assert!(spent); + } + + #[test] + fn mark_pir_result_nonexistent_is_noop() { + let db = PirTestDb::new(); + mark_provisional_pir_result(db.conn(), 9999, true).unwrap(); + } + + // ===================================================================== + // Provisional — reconciliation + // ===================================================================== + + #[test] + fn reconcile_marks_discovered_by_scanner() { + let db = PirTestDb::new(); + insert_test_provisional(db.conn(), 1000, 50_000); + insert_canonical_note(db.conn(), 42, 1000, 50_000); + + let reconciled = reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + assert!(reconciled); + + let (dbs, canonical): (bool, Option) = db + .conn() + .query_row( + "SELECT discovered_by_scanner, canonical_note_id FROM pir_notes WHERE position = 1000", + [], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert!(dbs); + assert_eq!(canonical, Some(42)); + } + + #[test] + fn reconcile_spent_note_visible_in_spent_clause() { + let db = PirTestDb::new(); + let id = insert_test_provisional(db.conn(), 1000, 50_000); + mark_provisional_pir_result(db.conn(), id, true).unwrap(); + + insert_canonical_note(db.conn(), 42, 1000, 50_000); + reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 42 AND is_spent = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + #[test] + fn reconcile_nonexistent_position() { + let db = PirTestDb::new(); + let reconciled = reconcile_provisional_for_position(db.conn(), 9999, 42).unwrap(); + assert!(!reconciled); + } + + #[test] + fn reconcile_idempotent() { + let db = PirTestDb::new(); + insert_test_provisional(db.conn(), 1000, 50_000); + insert_canonical_note(db.conn(), 42, 1000, 50_000); + + let r1 = reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + assert!(r1); + + let r2 = reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + assert!(!r2); + + let count: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE discovered_by_scanner = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(count, 1); + } + + // ===================================================================== + // Provisional — recursive chains + // ===================================================================== + + #[test] + fn recursive_chain_depth() { + let db = PirTestDb::new(); + let b = insert_test_provisional_with_depth(db.conn(), 1000, 70_000, 1, None); + let c = insert_test_provisional_with_depth(db.conn(), 2000, 40_000, 2, Some(b)); + + let (depth, parent): (i64, Option) = db + .conn() + .query_row( + "SELECT depth, parent_id FROM pir_notes WHERE id = ?1", + [c], + |r| Ok((r.get(0)?, r.get(1)?)), + ) + .unwrap(); + assert_eq!(depth, 2); + assert_eq!(parent, Some(b)); + } + + #[test] + fn spent_note_id_resolves_via_parent_chain() { + let db = PirTestDb::new(); + insert_canonical_note(db.conn(), 42, 5000, 100_000); + insert_pir_spent_note(db.conn(), 42).unwrap(); + + let parent_pir_id: i64 = db + .conn() + .query_row( + "SELECT id FROM pir_notes WHERE canonical_note_id = 42", + [], + |r| r.get(0), + ) + .unwrap(); + + let b = insert_test_provisional_with_depth(db.conn(), 6000, 70_000, 1, Some(parent_pir_id)); + let _c = insert_test_provisional_with_depth(db.conn(), 7000, 40_000, 2, Some(b)); + + let notes = get_provisional_notes_for_pir_check(db.conn()).unwrap(); + assert_eq!(notes.len(), 2); + for note in ¬es { + assert_eq!(note.spent_note_id, 42, "depth-{} should resolve to canonical note 42", note.depth); + } + } + + #[test] + fn reconcile_mid_chain_preserves_descendants() { + let db = PirTestDb::new(); + let b = insert_test_provisional_with_depth(db.conn(), 1000, 70_000, 1, None); + mark_provisional_pir_result(db.conn(), b, true).unwrap(); + let _c = insert_test_provisional_with_depth(db.conn(), 2000, 40_000, 2, Some(b)); + + insert_canonical_note(db.conn(), 42, 1000, 70_000); + reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + + let total: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE discovered_by_scanner = 0", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(total, 1); + + let notes = get_provisional_notes_for_pir_check(db.conn()).unwrap(); + assert_eq!(notes.len(), 1); + assert_eq!(notes[0].value, 40_000); + } + + #[test] + fn balance_excludes_spent_and_scanner_reconciled() { + let db = PirTestDb::new(); + let b = insert_test_provisional_with_depth(db.conn(), 1000, 70_000, 1, None); + mark_provisional_pir_result(db.conn(), b, true).unwrap(); + let _c = insert_test_provisional_with_depth(db.conn(), 2000, 40_000, 2, Some(b)); + + let balance: i64 = db + .conn() + .query_row( + "SELECT COALESCE(SUM(value), 0) FROM pir_notes + WHERE is_spent = 0 AND discovered_by_scanner = 0", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(balance, 40_000); + + insert_canonical_note(db.conn(), 42, 1000, 70_000); + reconcile_provisional_for_position(db.conn(), 1000, 42).unwrap(); + + let balance_after: i64 = db + .conn() + .query_row( + "SELECT COALESCE(SUM(value), 0) FROM pir_notes + WHERE is_spent = 0 AND discovered_by_scanner = 0", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(balance_after, 40_000); + + let canonical_b_spent: i64 = db + .conn() + .query_row( + "SELECT COUNT(*) FROM pir_notes WHERE canonical_note_id = 42 AND is_spent = 1", + [], + |r| r.get(0), + ) + .unwrap(); + assert_eq!(canonical_b_spent, 1); + } + + // ===================================================================== + // Activity entries + // ===================================================================== + + fn make_tx_hash(byte: u8) -> [u8; 32] { + [byte; 32] + } + + fn setup_spent_with_change( + conn: &Connection, + note_id: i64, + note_value: i64, + position: i64, + change_position: u64, + change_value: u64, + tx_hash: &[u8; 32], + ) -> i64 { + insert_test_note_with_position(conn, note_id, note_value, Some(&[note_id as u8; 32]), Some(position)); + insert_pir_spent_note(conn, note_id).unwrap(); + + let pir_id: i64 = conn + .query_row( + "SELECT id FROM pir_notes WHERE canonical_note_id = ?1", + [note_id], + |r| r.get(0), + ) + .unwrap(); + + set_pir_spending_tx_metadata(conn, pir_id, tx_hash, 1700000000, Some(10_000), Some(3_200_000)).unwrap(); + + insert_pir_provisional_note( + conn, 1, change_value, change_position, + &[0u8; 11], &[0u8; 32], &[0u8; 32], + &[change_position as u8; 32], &[0u8; 32], + 3_200_000, 1, Some(pir_id), + ).unwrap(); + + pir_id + } + + #[test] + fn activity_empty_when_no_spends() { + let db = PirTestDb::new(); + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert!(entries.is_empty()); + } + + #[test] + fn activity_single_spend_with_change() { + let db = PirTestDb::new(); + let tx_hash = make_tx_hash(0xAA); + setup_spent_with_change(db.conn(), 1, 100_000, 1000, 2000, 70_000, &tx_hash); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].tx_hash, tx_hash); + assert_eq!(entries[0].gross_value, 100_000); + assert_eq!(entries[0].change_value, 70_000); + assert_eq!(entries[0].net_value(), 30_000); + assert_eq!(entries[0].fee, Some(10_000)); + assert_eq!(entries[0].block_time, 1700000000); + } + + #[test] + fn activity_no_change_shows_full_amount() { + let db = PirTestDb::new(); + let tx_hash = make_tx_hash(0xBB); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0x01)), Some(1000)); + insert_pir_spent_note(db.conn(), 1).unwrap(); + let pir_id: i64 = db.conn() + .query_row("SELECT id FROM pir_notes WHERE canonical_note_id = 1", [], |r| r.get(0)) + .unwrap(); + set_pir_spending_tx_metadata(db.conn(), pir_id, &tx_hash, 1700000000, None, Some(3_200_000)).unwrap(); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].gross_value, 50_000); + assert_eq!(entries[0].change_value, 0); + assert_eq!(entries[0].net_value(), 50_000); + assert!(entries[0].fee.is_none()); + } + + #[test] + fn activity_multi_hop_chain() { + let db = PirTestDb::new(); + let tx_hash_a = make_tx_hash(0xAA); + let pir_a = setup_spent_with_change(db.conn(), 1, 100_000, 1000, 2000, 70_000, &tx_hash_a); + + let change_b_id: i64 = db.conn() + .query_row("SELECT id FROM pir_notes WHERE parent_id = ?1", [pir_a], |r| r.get(0)) + .unwrap(); + mark_provisional_pir_result(db.conn(), change_b_id, true).unwrap(); + + let tx_hash_b = make_tx_hash(0xBB); + set_pir_spending_tx_metadata(db.conn(), change_b_id, &tx_hash_b, 1700001000, Some(10_000), Some(3_200_001)).unwrap(); + insert_pir_provisional_note( + db.conn(), 1, 50_000, 3000, + &[0u8; 11], &[0u8; 32], &[0u8; 32], + &[3u8; 32], &[0u8; 32], + 3_200_001, 2, Some(change_b_id), + ).unwrap(); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].tx_hash, tx_hash_a); + assert_eq!(entries[0].gross_value, 100_000); + assert_eq!(entries[0].change_value, 50_000); + assert_eq!(entries[0].net_value(), 50_000); + } + + #[test] + fn activity_co_spent_notes_same_tx() { + let db = PirTestDb::new(); + let tx_hash = make_tx_hash(0xCC); + + insert_test_note_with_position(db.conn(), 1, 60_000, Some(&make_nf(0x01)), Some(1000)); + insert_test_note_with_position(db.conn(), 2, 40_000, Some(&make_nf(0x02)), Some(1001)); + insert_pir_spent_note(db.conn(), 1).unwrap(); + insert_pir_spent_note(db.conn(), 2).unwrap(); + + let pir1: i64 = db.conn() + .query_row("SELECT id FROM pir_notes WHERE canonical_note_id = 1", [], |r| r.get(0)) + .unwrap(); + let pir2: i64 = db.conn() + .query_row("SELECT id FROM pir_notes WHERE canonical_note_id = 2", [], |r| r.get(0)) + .unwrap(); + + set_pir_spending_tx_metadata(db.conn(), pir1, &tx_hash, 1700000000, Some(10_000), Some(3_200_000)).unwrap(); + set_pir_spending_tx_metadata(db.conn(), pir2, &tx_hash, 1700000000, Some(10_000), Some(3_200_000)).unwrap(); + + insert_pir_provisional_note( + db.conn(), 1, 80_000, 2000, + &[0u8; 11], &[0u8; 32], &[0u8; 32], + &[2u8; 32], &[0u8; 32], + 3_200_000, 1, Some(pir1), + ).unwrap(); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].gross_value, 100_000); + assert_eq!(entries[0].change_value, 80_000); + assert_eq!(entries[0].net_value(), 20_000); + } + + #[test] + fn activity_scanner_confirmation_removes_entry() { + let db = PirTestDb::new(); + let tx_hash = make_tx_hash(0xDD); + setup_spent_with_change(db.conn(), 1, 100_000, 1000, 2000, 70_000, &tx_hash); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert_eq!(entries.len(), 1); + + mark_spent(db.conn(), 1); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert!(entries.is_empty()); + } + + #[test] + fn activity_excludes_entries_without_tx_metadata() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0x01)), Some(1000)); + insert_pir_spent_note(db.conn(), 1).unwrap(); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + assert!(entries.is_empty()); + } + + #[test] + fn set_tx_metadata_basic() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0x01)), Some(1000)); + insert_pir_spent_note(db.conn(), 1).unwrap(); + + let pir_id = get_pir_note_id_for_canonical(db.conn(), 1).unwrap().unwrap(); + let tx_hash = make_tx_hash(0xEE); + set_pir_spending_tx_metadata(db.conn(), pir_id, &tx_hash, 1700000000, Some(10_000), Some(3_200_000)).unwrap(); + + let (stored_hash, stored_time, stored_fee): (Vec, i64, Option) = db.conn() + .query_row( + "SELECT spending_tx_hash, spending_block_time, spending_fee FROM pir_notes WHERE id = ?1", + [pir_id], + |r| Ok((r.get(0)?, r.get(1)?, r.get(2)?)), + ) + .unwrap(); + assert_eq!(stored_hash, tx_hash.to_vec()); + assert_eq!(stored_time, 1700000000); + assert_eq!(stored_fee, Some(10_000)); + } + + #[test] + fn get_pir_note_id_for_canonical_basic() { + let db = PirTestDb::new(); + insert_test_note_with_position(db.conn(), 1, 50_000, Some(&make_nf(0x01)), Some(1000)); + insert_pir_spent_note(db.conn(), 1).unwrap(); + + let id = get_pir_note_id_for_canonical(db.conn(), 1).unwrap(); + assert!(id.is_some()); + + let id_none = get_pir_note_id_for_canonical(db.conn(), 999).unwrap(); + assert!(id_none.is_none()); + } + + #[test] + fn activity_multi_hop_intermediate_without_tx_metadata() { + let db = PirTestDb::new(); + let tx_hash_a = make_tx_hash(0xAA); + let pir_a = setup_spent_with_change(db.conn(), 1, 100_000, 1000, 2000, 70_000, &tx_hash_a); + + let change_b_id: i64 = db.conn() + .query_row("SELECT id FROM pir_notes WHERE parent_id = ?1", [pir_a], |r| r.get(0)) + .unwrap(); + // Mark B as spent via PIR but don't set spending_tx_hash yet + // (simulates nullifier detected but change discovery not yet run) + mark_provisional_pir_result(db.conn(), change_b_id, true).unwrap(); + + let entries = get_pir_activity_entries(db.conn()).unwrap(); + // A should still appear; its change_value should be 0 because B is spent + // (no unspent leaves in the subtree — B is spent and has no children) + assert_eq!(entries.len(), 1); + assert_eq!(entries[0].tx_hash, tx_hash_a); + assert_eq!(entries[0].gross_value, 100_000); + assert_eq!(entries[0].change_value, 0); + assert_eq!(entries[0].net_value(), 100_000); + } +}