diff --git a/.deny.toml b/.deny.toml index 57b5f759..e65cdd34 100644 --- a/.deny.toml +++ b/.deny.toml @@ -14,8 +14,6 @@ ignore = [ { id = "RUSTSEC-2025-0141", reason = "`bincode` is unmaintained but continuing to use it." }, { id = "RUSTSEC-2023-0089", reason = "atomic-polyfill is pulled transitively via risc0-zkvm; waiting on upstream fix (see https://github.com/risc0/risc0/issues/3453)" }, { id = "RUSTSEC-2026-0097", reason = "`rand` v0.8.5 is present transitively from logos crates, modification may break integration" }, - { id = "RUSTSEC-2026-0118", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration"}, - { id = "RUSTSEC-2026-0119", reason = "`hickory-proto` v0.25.0-alpha.5 is present transitively from logos crates, modification may break integration"}, ] yanked = "deny" unused-ignored-advisory = "deny" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f10532a8..02381dfc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -225,7 +225,7 @@ jobs: - uses: ./.github/actions/install-risc0 - name: Install just - run: cargo install --locked just + run: cargo install just - name: Build artifacts run: just build-artifacts diff --git a/Cargo.lock b/Cargo.lock index 60286441..73fbb12a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -627,51 +627,6 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" -[[package]] -name = "asn1-rs" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56624a96882bb8c26d61312ae18cb45868e5a9992ea73c58e45c3101e56a1e60" -dependencies = [ - "asn1-rs-derive", - "asn1-rs-impl", - "displaydoc", - "nom 7.1.3", - "num-traits", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - -[[package]] -name = "asn1-rs-derive" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3109e49b1e4909e9db6515a30c633684d68cdeaa252f215214cb4fa1a5bfee2c" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", - "synstructure", -] - -[[package]] -name = "asn1-rs-impl" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b18050c2cd6fe86c3a76584ef5e0baf286d038cda203eb6223df2cc413565f7" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "asn1_der" -version = "0.7.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" - [[package]] name = "astral-tokio-tar" version = "0.6.1" @@ -722,36 +677,6 @@ dependencies = [ "serde", ] -[[package]] -name = "async-channel" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2" -dependencies = [ - "concurrent-queue", - "event-listener-strategy", - "futures-core", - "pin-project-lite", -] - -[[package]] -name = "async-io" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc" -dependencies = [ - "autocfg", - "cfg-if", - "concurrent-queue", - "futures-io", - "futures-lite", - "parking", - "polling", - "rustix", - "slab", - "windows-sys 0.61.2", -] - [[package]] name = "async-lock" version = "3.4.2" @@ -769,17 +694,6 @@ version = "0.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4288f83726785267c6f2ef073a3d83dc3f9b81464e9f99898240cced85fce35a" -[[package]] -name = "async-recursion" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "async-stream" version = "0.3.6" @@ -813,19 +727,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "asynchronous-codec" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a860072022177f903e59730004fb5dc13db9275b79bb2aef7ba8ce831956c233" -dependencies = [ - "bytes", - "futures-sink", - "futures-util", - "memchr", - "pin-project-lite", -] - [[package]] name = "ata_core" version = "0.1.0" @@ -859,29 +760,6 @@ version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" -[[package]] -name = "attohttpc" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d9a9bf8b79a749ee0b911b91b671cc2b6c670bdbc7e3dfd537576ddc94bb2a2" -dependencies = [ - "http 0.2.12", - "log", - "url", -] - -[[package]] -name = "attohttpc" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "16e2cdb6d5ed835199484bb92bb8b3edd526effe995c61732580439c1a67e2e9" -dependencies = [ - "base64 0.22.1", - "http 1.4.0", - "log", - "url", -] - [[package]] name = "attribute-derive" version = "0.10.5" @@ -928,7 +806,7 @@ dependencies = [ "axum-core 0.4.5", "bytes", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -962,7 +840,7 @@ dependencies = [ "bytes", "form_urlencoded", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -997,7 +875,7 @@ dependencies = [ "async-trait", "bytes", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "mime", @@ -1016,7 +894,7 @@ checksum = "08c78f31d7b1291f7ee735c1c6780ccde7785daae9a9206026862dab7d8792d1" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http", "http-body", "http-body-util", "mime", @@ -1027,17 +905,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "backon" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" -dependencies = [ - "fastrand", - "gloo-timers 0.3.0", - "tokio", -] - [[package]] name = "base-x" version = "0.2.11" @@ -1090,6 +957,24 @@ version = "1.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2af50177e190e07a26ab74f8b1efbfe2ef87da2116221318cb1c2e82baf7de06" +[[package]] +name = "bedrock_client" +version = "0.1.0" +dependencies = [ + "anyhow", + "common", + "futures", + "humantime-serde", + "log", + "logos-blockchain-chain-broadcast-service", + "logos-blockchain-chain-service", + "logos-blockchain-common-http-client", + "logos-blockchain-core", + "reqwest", + "serde", + "tokio-retry", +] + [[package]] name = "bincode" version = "1.3.3" @@ -1216,7 +1101,7 @@ dependencies = [ "futures-util", "hex", "home", - "http 1.4.0", + "http", "http-body-util", "hyper", "hyper-named-pipe", @@ -1469,6 +1354,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "cfg_eval" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45565fc9416b9896014f5732ac776f810ee53a66730c17e4020c3ec064a8f88f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "chacha20" version = "0.10.0" @@ -1478,7 +1374,6 @@ dependencies = [ "cfg-if", "cipher 0.5.1", "cpufeatures 0.3.0", - "rand_core 0.10.1", ] [[package]] @@ -2078,20 +1973,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "der-parser" -version = "10.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07da5016415d5a3c4dd39b11ed26f915f52fc4e0dc197d87908bc916e51bc1a6" -dependencies = [ - "asn1-rs", - "displaydoc", - "nom 7.1.3", - "num-bigint 0.4.6", - "num-traits", - "rusticata-macros", -] - [[package]] name = "deranged" version = "0.5.8" @@ -2241,22 +2122,11 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "dlopen2" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b4f5f101177ff01b8ec4ecc81eead416a8aa42819a2869311b3420fa114ffa" -dependencies = [ - "libc", - "once_cell", - "winapi", -] - [[package]] name = "docker-compose-types" -version = "0.23.0" +version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ea51e75cfa9371c4d760270c3da13516d7206121d668c1fbdd6fd83d1782b0f" +checksum = "7edb75a85449fd9c34d9fb3376c6208ec4115d2ca43b965175a52d71349ecab8" dependencies = [ "derive_builder", "indexmap 2.13.0", @@ -2307,12 +2177,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "669a445ee724c5c69b1b06fe0b63e70a1c84bc9bb7d9696cd4f4e3ec45050408" -[[package]] -name = "dtoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" - [[package]] name = "duplicate" version = "2.0.1" @@ -2351,7 +2215,6 @@ version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" dependencies = [ - "pkcs8", "serde", "signature", ] @@ -2453,18 +2316,6 @@ dependencies = [ "cfg-if", ] -[[package]] -name = "enum-as-inner" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1e6a265c649f3f5979b601d26f1d05ada116434c87741c9493cb56218f76cbc" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "enum-map" version = "2.7.3" @@ -2650,12 +2501,12 @@ checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "ferroid" -version = "2.0.0" +version = "0.8.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee93edf3c501f0035bbeffeccfed0b79e14c311f12195ec0e661e114a0f60da4" +checksum = "bb330bbd4cb7a5b9f559427f06f98a4f853a137c8298f3bd3f8ca57663e21986" dependencies = [ "portable-atomic", - "rand 0.10.1", + "rand 0.9.3", "web-time", ] @@ -2732,6 +2583,15 @@ 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 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -2739,7 +2599,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -2753,6 +2613,12 @@ dependencies = [ "syn 2.0.117", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -2789,16 +2655,6 @@ dependencies = [ "futures-util", ] -[[package]] -name = "futures-bounded" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91f328e7fb845fc832912fb6a34f40cf6d1888c92f974d1893a54e97b5ff542e" -dependencies = [ - "futures-timer", - "futures-util", -] - [[package]] name = "futures-channel" version = "0.3.32" @@ -2832,16 +2688,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cecba35d7ad927e23624b22ad55235f2239cfa44fd10428eecbeba6d6a717718" -[[package]] -name = "futures-lite" -version = "2.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f78e10609fe0e0b3f4157ffab1876319b5b0db102a2c60dc4626306dc46b44ad" -dependencies = [ - "futures-core", - "pin-project-lite", -] - [[package]] name = "futures-macro" version = "0.3.32" @@ -2853,17 +2699,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "futures-rustls" -version = "0.26.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f2f12607f92c69b12ed746fabf9ca4f5c482cba46679c1a75b874ed7c26adb" -dependencies = [ - "futures-io", - "rustls", - "rustls-pki-types", -] - [[package]] name = "futures-sink" version = "0.3.32" @@ -2882,7 +2717,7 @@ version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f288b0a4f20f9a56b5d1da57e2227c661b7b16168e2f72365f57b63326e29b24" dependencies = [ - "gloo-timers 0.2.6", + "gloo-timers", "send_wrapper 0.4.0", ] @@ -2986,7 +2821,6 @@ dependencies = [ "js-sys", "libc", "r-efi 6.0.0", - "rand_core 0.10.1", "wasip2", "wasip3", "wasm-bindgen", @@ -3029,7 +2863,7 @@ dependencies = [ "futures-core", "futures-sink", "gloo-utils", - "http 1.4.0", + "http", "js-sys", "pin-project", "serde", @@ -3052,18 +2886,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "gloo-timers" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbb143cf96099802033e0d4f4963b19fd2e0b728bcf076cd9cf7f6634f092994" -dependencies = [ - "futures-channel", - "futures-core", - "js-sys", - "wasm-bindgen", -] - [[package]] name = "gloo-utils" version = "0.2.0" @@ -3105,7 +2927,7 @@ dependencies = [ "fnv", "futures-core", "futures-sink", - "http 1.4.0", + "http", "indexmap 2.13.0", "slab", "tokio", @@ -3153,7 +2975,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", - "equivalent", "foldhash", ] @@ -3163,15 +2984,6 @@ version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" -[[package]] -name = "hashlink" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ba4ff7128dee98c7dc9794b6a411377e1404dba1c97deb8d1a55297bd25d8af" -dependencies = [ - "hashbrown 0.14.5", -] - [[package]] name = "hashlink" version = "0.10.0" @@ -3201,12 +3013,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hermit-abi" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" - [[package]] name = "hex" version = "0.4.3" @@ -3234,59 +3040,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e712f64ec3850b98572bffac52e2c6f282b29fe6c5fa6d42334b30be438d95c1" -[[package]] -name = "hex_fmt" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b07f60793ff0a4d9cef0f18e63b5357e06209987153a64648c972c1e5aff336f" - -[[package]] -name = "hickory-proto" -version = "0.25.0-alpha.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d00147af6310f4392a31680db52a3ed45a2e0f68eb18e8c3fe5537ecc96d9e2" -dependencies = [ - "async-recursion", - "async-trait", - "cfg-if", - "data-encoding", - "enum-as-inner", - "futures-channel", - "futures-io", - "futures-util", - "idna", - "ipnet", - "once_cell", - "rand 0.9.3", - "socket2 0.5.10", - "thiserror 2.0.18", - "tinyvec", - "tokio", - "tracing", - "url", -] - -[[package]] -name = "hickory-resolver" -version = "0.25.0-alpha.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5762f69ebdbd4ddb2e975cd24690bf21fe6b2604039189c26acddbc427f12887" -dependencies = [ - "cfg-if", - "futures-util", - "hickory-proto", - "ipconfig", - "moka", - "once_cell", - "parking_lot", - "rand 0.9.3", - "resolv-conf", - "smallvec", - "thiserror 2.0.18", - "tokio", - "tracing", -] - [[package]] name = "hkdf" version = "0.12.4" @@ -3340,17 +3093,6 @@ dependencies = [ "utf8-width", ] -[[package]] -name = "http" -version = "0.2.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "601cbb57e577e2f5ef5be8e7b83f0f63994f25aa94d673e54a92d5c516d101f1" -dependencies = [ - "bytes", - "fnv", - "itoa", -] - [[package]] name = "http" version = "1.4.0" @@ -3368,7 +3110,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http 1.4.0", + "http", ] [[package]] @@ -3379,7 +3121,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http 1.4.0", + "http", "http-body", "pin-project-lite", ] @@ -3454,7 +3196,7 @@ dependencies = [ "futures-channel", "futures-core", "h2", - "http 1.4.0", + "http", "http-body", "httparse", "httpdate", @@ -3487,7 +3229,7 @@ version = "0.27.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" dependencies = [ - "http 1.4.0", + "http", "hyper", "hyper-util", "log", @@ -3512,6 +3254,22 @@ 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.20" @@ -3522,17 +3280,19 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http 1.4.0", + "http", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2 0.6.3", + "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -3688,81 +3448,6 @@ dependencies = [ "icu_properties", ] -[[package]] -name = "if-addrs" -version = "0.15.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0a05c691e1fae256cf7013d99dad472dc52d5543322761f83ec8d47eab40d2b" -dependencies = [ - "libc", - "windows-sys 0.61.2", -] - -[[package]] -name = "if-watch" -version = "3.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71c02a5161c313f0cbdbadc511611893584a10a7b6153cb554bdf83ddce99ec2" -dependencies = [ - "async-io", - "core-foundation 0.9.4", - "fnv", - "futures", - "if-addrs", - "ipnet", - "log", - "netlink-packet-core 0.8.1", - "netlink-packet-route 0.28.0", - "netlink-proto", - "netlink-sys", - "rtnetlink", - "system-configuration 0.7.0", - "tokio", - "windows", -] - -[[package]] -name = "igd-next" -version = "0.15.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "76b0d7d4541def58a37bf8efc559683f21edce7c82f0d866c93ac21f7e098f93" -dependencies = [ - "async-trait", - "attohttpc 0.24.1", - "bytes", - "futures", - "http 1.4.0", - "http-body-util", - "hyper", - "hyper-util", - "log", - "rand 0.8.5", - "tokio", - "url", - "xmltree", -] - -[[package]] -name = "igd-next" -version = "0.16.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516893339c97f6011282d5825ac94fc1c7aad5cad26bdc2d0cee068c0bf97f97" -dependencies = [ - "async-trait", - "attohttpc 0.30.1", - "bytes", - "futures", - "http 1.4.0", - "http-body-util", - "hyper", - "hyper-util", - "log", - "rand 0.9.3", - "tokio", - "url", - "xmltree", -] - [[package]] name = "include_bytes_aligned" version = "0.1.4" @@ -3775,13 +3460,13 @@ version = "0.1.0" dependencies = [ "anyhow", "async-stream", + "bedrock_client", "borsh", "common", "futures", "humantime-serde", "log", "logos-blockchain-core", - "logos-blockchain-zone-sdk", "nssa", "nssa_core", "serde", @@ -3797,16 +3482,10 @@ dependencies = [ name = "indexer_ffi" version = "0.1.0" dependencies = [ - "anyhow", "cbindgen", "indexer_service", - "indexer_service_protocol", - "indexer_service_rpc", - "jsonrpsee", "log", - "nssa", "tokio", - "url", ] [[package]] @@ -3931,9 +3610,7 @@ dependencies = [ "hex", "indexer_ffi", "indexer_service", - "indexer_service_protocol", "indexer_service_rpc", - "jsonrpsee", "key_protocol", "log", "nssa", @@ -3967,19 +3644,6 @@ dependencies = [ "rustversion", ] -[[package]] -name = "ipconfig" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" -dependencies = [ - "socket2 0.6.3", - "widestring", - "windows-registry", - "windows-result", - "windows-sys 0.61.2", -] - [[package]] name = "ipnet" version = "2.12.0" @@ -4163,7 +3827,7 @@ dependencies = [ "futures-channel", "futures-util", "gloo-net", - "http 1.4.0", + "http", "jsonrpsee-core", "pin-project", "rustls", @@ -4188,7 +3852,7 @@ dependencies = [ "bytes", "futures-timer", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "jsonrpsee-types", @@ -4249,7 +3913,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c51b7c290bb68ce3af2d029648148403863b982f138484a73f02a9dd52dbd7f" dependencies = [ "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -4275,7 +3939,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc88ff4688e43cc3fa9883a8a95c6fa27aa2e76c96e610b737b6554d650d7fd5" dependencies = [ - "http 1.4.0", + "http", "serde", "serde_json", "thiserror 2.0.18", @@ -4299,7 +3963,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6fceceeb05301cc4c065ab3bd2fa990d41ff4eb44e4ca1b30fa99c057c3e79" dependencies = [ - "http 1.4.0", + "http", "jsonrpsee-client-transport", "jsonrpsee-core", "jsonrpsee-types", @@ -4338,7 +4002,6 @@ dependencies = [ "aes-gcm", "anyhow", "base58", - "bincode", "bip39", "common", "hex", @@ -4664,401 +4327,18 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" -[[package]] -name = "libp2p" -version = "0.55.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b72dc443ddd0254cb49a794ed6b6728400ee446a0f7ab4a07d0209ee98de20e9" -dependencies = [ - "bytes", - "either", - "futures", - "futures-timer", - "getrandom 0.2.17", - "libp2p-allow-block-list", - "libp2p-autonat", - "libp2p-connection-limits", - "libp2p-core", - "libp2p-dns", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-identity", - "libp2p-kad", - "libp2p-mdns", - "libp2p-metrics", - "libp2p-quic", - "libp2p-swarm", - "libp2p-tcp", - "libp2p-upnp", - "multiaddr", - "pin-project", - "rw-stream-sink", - "thiserror 2.0.18", -] - -[[package]] -name = "libp2p-allow-block-list" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38944b7cb981cc93f2f0fb411ff82d0e983bd226fbcc8d559639a3a73236568b" -dependencies = [ - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", -] - -[[package]] -name = "libp2p-autonat" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e297bfc6cabb70c6180707f8fa05661b77ecb9cb67e8e8e1c469301358fa21d0" -dependencies = [ - "async-trait", - "asynchronous-codec", - "either", - "futures", - "futures-bounded", - "futures-timer", - "libp2p-core", - "libp2p-identity", - "libp2p-request-response", - "libp2p-swarm", - "quick-protobuf", - "quick-protobuf-codec", - "rand 0.8.5", - "rand_core 0.6.4", - "thiserror 2.0.18", - "tracing", - "web-time", -] - -[[package]] -name = "libp2p-connection-limits" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "efe9323175a17caa8a2ed4feaf8a548eeef5e0b72d03840a0eab4bcb0210ce1c" -dependencies = [ - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", -] - -[[package]] -name = "libp2p-core" -version = "0.43.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "249128cd37a2199aff30a7675dffa51caf073b51aa612d2f544b19932b9aebca" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "libp2p-identity", - "multiaddr", - "multihash", - "multistream-select", - "parking_lot", - "pin-project", - "quick-protobuf", - "rand 0.8.5", - "rw-stream-sink", - "thiserror 2.0.18", - "tracing", - "unsigned-varint 0.8.0", - "web-time", -] - -[[package]] -name = "libp2p-dns" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b780a1150214155b0ed1cdf09fbd2e1b0442604f9146a431d1b21d23eef7bd7" -dependencies = [ - "async-trait", - "futures", - "hickory-resolver", - "libp2p-core", - "libp2p-identity", - "parking_lot", - "smallvec", - "tracing", -] - -[[package]] -name = "libp2p-gossipsub" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d558548fa3b5a8e9b66392f785921e363c57c05dcadfda4db0d41ae82d313e4a" -dependencies = [ - "async-channel", - "asynchronous-codec", - "base64 0.22.1", - "byteorder", - "bytes", - "either", - "fnv", - "futures", - "futures-timer", - "getrandom 0.2.17", - "hashlink 0.9.1", - "hex_fmt", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "prometheus-client", - "quick-protobuf", - "quick-protobuf-codec", - "rand 0.8.5", - "regex", - "serde", - "sha2", - "tracing", - "web-time", -] - -[[package]] -name = "libp2p-identify" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c06862544f02d05d62780ff590cc25a75f5c2b9df38ec7a370dcae8bb873cf" -dependencies = [ - "asynchronous-codec", - "either", - "futures", - "futures-bounded", - "futures-timer", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "quick-protobuf", - "quick-protobuf-codec", - "smallvec", - "thiserror 2.0.18", - "tracing", -] - [[package]] name = "libp2p-identity" version = "0.2.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0c7892c221730ba55f7196e98b0b8ba5e04b4155651736036628e9f73ed6fc3" dependencies = [ - "asn1_der", "bs58", - "ed25519-dalek", "hkdf", - "k256", "multihash", - "quick-protobuf", - "rand 0.8.5", - "serde", "sha2", "thiserror 2.0.18", "tracing", - "zeroize", -] - -[[package]] -name = "libp2p-kad" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2bab0466a27ebe955bcbc27328fae5429c5b48c915fd6174931414149802ec23" -dependencies = [ - "asynchronous-codec", - "bytes", - "either", - "fnv", - "futures", - "futures-bounded", - "futures-timer", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "quick-protobuf", - "quick-protobuf-codec", - "rand 0.8.5", - "serde", - "sha2", - "smallvec", - "thiserror 2.0.18", - "tracing", - "uint", - "web-time", -] - -[[package]] -name = "libp2p-mdns" -version = "0.47.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d0ba095e1175d797540e16b62e7576846b883cb5046d4159086837b36846cc" -dependencies = [ - "futures", - "hickory-proto", - "if-watch", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "rand 0.8.5", - "smallvec", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "libp2p-metrics" -version = "0.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ce58c64292e87af624fcb86465e7dd8342e46a388d71e8fec0ab37ee789630a" -dependencies = [ - "futures", - "libp2p-core", - "libp2p-gossipsub", - "libp2p-identify", - "libp2p-identity", - "libp2p-kad", - "libp2p-swarm", - "pin-project", - "prometheus-client", - "web-time", -] - -[[package]] -name = "libp2p-quic" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41432a159b00424a0abaa2c80d786cddff81055ac24aa127e0cf375f7858d880" -dependencies = [ - "futures", - "futures-timer", - "if-watch", - "libp2p-core", - "libp2p-identity", - "libp2p-tls", - "quinn", - "rand 0.8.5", - "ring", - "rustls", - "socket2 0.5.10", - "thiserror 2.0.18", - "tokio", - "tracing", -] - -[[package]] -name = "libp2p-request-response" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "548fe44a80ff275d400f1b26b090d441d83ef73efabbeb6415f4ce37e5aed865" -dependencies = [ - "async-trait", - "futures", - "futures-bounded", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "rand 0.8.5", - "smallvec", - "tracing", -] - -[[package]] -name = "libp2p-stream" -version = "0.3.0-alpha" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "826716f1ee125895f1fb44911413cba023485b552ff96c7a2159bd037ac619bb" -dependencies = [ - "futures", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm", - "rand 0.8.5", - "tracing", -] - -[[package]] -name = "libp2p-swarm" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "803399b4b6f68adb85e63ab573ac568154b193e9a640f03e0f2890eabbcb37f8" -dependencies = [ - "either", - "fnv", - "futures", - "futures-timer", - "libp2p-core", - "libp2p-identity", - "libp2p-swarm-derive", - "lru", - "multistream-select", - "once_cell", - "rand 0.8.5", - "smallvec", - "tokio", - "tracing", - "web-time", -] - -[[package]] -name = "libp2p-swarm-derive" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "206e0aa0ebe004d778d79fb0966aa0de996c19894e2c0605ba2f8524dd4443d8" -dependencies = [ - "heck", - "proc-macro2", - "quote", - "syn 2.0.117", -] - -[[package]] -name = "libp2p-tcp" -version = "0.43.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65346fb4d36035b23fec4e7be4c320436ba53537ce9b6be1d1db1f70c905cad0" -dependencies = [ - "futures", - "futures-timer", - "if-watch", - "libc", - "libp2p-core", - "socket2 0.5.10", - "tokio", - "tracing", -] - -[[package]] -name = "libp2p-tls" -version = "0.6.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96ff65a82e35375cbc31ebb99cacbbf28cb6c4fefe26bf13756ddcf708d40080" -dependencies = [ - "futures", - "futures-rustls", - "libp2p-core", - "libp2p-identity", - "rcgen", - "ring", - "rustls", - "rustls-webpki", - "thiserror 2.0.18", - "x509-parser", - "yasna", -] - -[[package]] -name = "libp2p-upnp" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d457b9ecceb66e7199f049926fad447f1f17f040e8d29d690c086b4cab8ed14a" -dependencies = [ - "futures", - "futures-timer", - "igd-next 0.15.1", - "libp2p-core", - "libp2p-swarm", - "tokio", - "tracing", ] [[package]] @@ -5138,8 +4418,8 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "logos-blockchain-blend-crypto" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "blake2", "logos-blockchain-groth16", @@ -5152,8 +4432,8 @@ dependencies = [ [[package]] name = "logos-blockchain-blend-message" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "blake2", "derivative", @@ -5175,8 +4455,8 @@ dependencies = [ [[package]] name = "logos-blockchain-blend-proofs" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "ed25519-dalek", "generic-array 1.3.5", @@ -5194,8 +4474,8 @@ dependencies = [ [[package]] name = "logos-blockchain-chain-broadcast-service" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "derivative", @@ -5210,12 +4490,11 @@ dependencies = [ [[package]] name = "logos-blockchain-chain-service" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "bytes", - "derivative", "futures", "logos-blockchain-chain-broadcast-service", "logos-blockchain-core", @@ -5229,6 +4508,7 @@ dependencies = [ "logos-blockchain-time-service", "logos-blockchain-tracing", "logos-blockchain-utils", + "num-bigint 0.4.6", "overwatch", "serde", "serde_with", @@ -5241,8 +4521,8 @@ dependencies = [ [[package]] name = "logos-blockchain-circuits-prover" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "logos-blockchain-circuits-utils", "tempfile", @@ -5250,16 +4530,16 @@ dependencies = [ [[package]] name = "logos-blockchain-circuits-utils" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "dirs", ] [[package]] name = "logos-blockchain-common-http-client" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "futures", "hex", @@ -5278,8 +4558,8 @@ dependencies = [ [[package]] name = "logos-blockchain-core" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "ark-ff 0.4.2", "bincode", @@ -5292,7 +4572,6 @@ dependencies = [ "logos-blockchain-cryptarchia-engine", "logos-blockchain-groth16", "logos-blockchain-key-management-system-keys", - "logos-blockchain-mmr", "logos-blockchain-poc", "logos-blockchain-pol", "logos-blockchain-poseidon2", @@ -5301,7 +4580,6 @@ dependencies = [ "multiaddr", "nom 8.0.0", "num-bigint 0.4.6", - "rpds", "serde", "strum", "thiserror 1.0.69", @@ -5310,9 +4588,10 @@ dependencies = [ [[package]] name = "logos-blockchain-cryptarchia-engine" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ + "cfg_eval", "logos-blockchain-pol", "logos-blockchain-utils", "serde", @@ -5325,13 +4604,11 @@ dependencies = [ [[package]] name = "logos-blockchain-cryptarchia-sync" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "bytes", "futures", - "libp2p", - "libp2p-stream", "logos-blockchain-core", "logos-blockchain-cryptarchia-engine", "rand 0.8.5", @@ -5344,8 +4621,8 @@ dependencies = [ [[package]] name = "logos-blockchain-groth16" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "ark-bn254 0.4.0", "ark-ec 0.4.2", @@ -5362,8 +4639,8 @@ dependencies = [ [[package]] name = "logos-blockchain-http-api-common" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "axum 0.7.9", "logos-blockchain-core", @@ -5377,8 +4654,8 @@ dependencies = [ [[package]] name = "logos-blockchain-key-management-system-keys" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "bytes", @@ -5403,8 +4680,8 @@ dependencies = [ [[package]] name = "logos-blockchain-key-management-system-macros" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "proc-macro2", "quote", @@ -5413,8 +4690,8 @@ dependencies = [ [[package]] name = "logos-blockchain-key-management-system-operators" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "logos-blockchain-blend-proofs", @@ -5429,8 +4706,8 @@ dependencies = [ [[package]] name = "logos-blockchain-key-management-system-service" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "log", @@ -5446,8 +4723,8 @@ dependencies = [ [[package]] name = "logos-blockchain-ledger" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "derivative", "logos-blockchain-blend-crypto", @@ -5457,7 +4734,6 @@ dependencies = [ "logos-blockchain-cryptarchia-engine", "logos-blockchain-groth16", "logos-blockchain-key-management-system-keys", - "logos-blockchain-mmr", "logos-blockchain-pol", "logos-blockchain-utils", "logos-blockchain-utxotree", @@ -5470,61 +4746,17 @@ dependencies = [ "tracing", ] -[[package]] -name = "logos-blockchain-libp2p" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" -dependencies = [ - "async-trait", - "backon", - "blake2", - "either", - "futures", - "hex", - "igd-next 0.16.2", - "libp2p", - "logos-blockchain-cryptarchia-sync", - "logos-blockchain-utils", - "multiaddr", - "natpmp", - "netdev", - "num_enum", - "rand 0.8.5", - "serde", - "serde_with", - "thiserror 1.0.69", - "tokio", - "tracing", - "zerocopy", -] - -[[package]] -name = "logos-blockchain-mmr" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" -dependencies = [ - "ark-ff 0.4.2", - "logos-blockchain-groth16", - "logos-blockchain-poseidon2", - "rpds", - "serde", - "thiserror 2.0.18", -] - [[package]] name = "logos-blockchain-network-service" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "futures", "logos-blockchain-core", "logos-blockchain-cryptarchia-sync", - "logos-blockchain-libp2p", "logos-blockchain-tracing", "overwatch", - "rand 0.8.5", - "rand_chacha 0.3.1", "serde", "tokio", "tokio-stream", @@ -5533,8 +4765,8 @@ dependencies = [ [[package]] name = "logos-blockchain-poc" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "logos-blockchain-circuits-prover", "logos-blockchain-circuits-utils", @@ -5549,8 +4781,8 @@ dependencies = [ [[package]] name = "logos-blockchain-pol" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "astro-float", "logos-blockchain-circuits-prover", @@ -5568,8 +4800,8 @@ dependencies = [ [[package]] name = "logos-blockchain-poq" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "logos-blockchain-circuits-prover", "logos-blockchain-circuits-utils", @@ -5585,8 +4817,8 @@ dependencies = [ [[package]] name = "logos-blockchain-poseidon2" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "ark-bn254 0.4.0", "ark-ff 0.4.2", @@ -5596,8 +4828,8 @@ dependencies = [ [[package]] name = "logos-blockchain-services-utils" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "futures", @@ -5611,8 +4843,8 @@ dependencies = [ [[package]] name = "logos-blockchain-storage-service" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "bytes", @@ -5629,18 +4861,15 @@ dependencies = [ [[package]] name = "logos-blockchain-time-service" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "futures", "log", "logos-blockchain-cryptarchia-engine", "logos-blockchain-tracing", - "logos-blockchain-utils", "overwatch", - "serde", - "serde_with", "sntpc", "thiserror 2.0.18", "time", @@ -5651,8 +4880,8 @@ dependencies = [ [[package]] name = "logos-blockchain-tracing" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "opentelemetry", "opentelemetry-appender-tracing", @@ -5663,7 +4892,6 @@ dependencies = [ "rand 0.8.5", "serde", "tokio", - "tonic", "tracing", "tracing-appender", "tracing-gelf", @@ -5675,8 +4903,8 @@ dependencies = [ [[package]] name = "logos-blockchain-utils" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "async-trait", "blake2", @@ -5692,8 +4920,8 @@ dependencies = [ [[package]] name = "logos-blockchain-utxotree" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "ark-ff 0.4.2", "logos-blockchain-groth16", @@ -5706,16 +4934,16 @@ dependencies = [ [[package]] name = "logos-blockchain-witness-generator" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "tempfile", ] [[package]] name = "logos-blockchain-zksign" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" +version = "0.2.1" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=1da154c74b911318fb853d37261f8a05ffe513b4#1da154c74b911318fb853d37261f8a05ffe513b4" dependencies = [ "logos-blockchain-circuits-prover", "logos-blockchain-circuits-utils", @@ -5729,26 +4957,6 @@ dependencies = [ "tracing", ] -[[package]] -name = "logos-blockchain-zone-sdk" -version = "0.1.2" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=ee281a447d95a951752461ee0a6e88eb4a0f17cf#ee281a447d95a951752461ee0a6e88eb4a0f17cf" -dependencies = [ - "async-trait", - "futures", - "logos-blockchain-common-http-client", - "logos-blockchain-core", - "logos-blockchain-groth16", - "logos-blockchain-key-management-system-service", - "rand 0.8.5", - "reqwest", - "rpds", - "serde", - "thiserror 2.0.18", - "tokio", - "tracing", -] - [[package]] name = "loki-api" version = "0.1.3" @@ -5759,15 +4967,6 @@ dependencies = [ "prost-types 0.13.5", ] -[[package]] -name = "lru" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" -dependencies = [ - "hashbrown 0.15.5", -] - [[package]] name = "lru-slab" version = "0.1.2" @@ -5981,7 +5180,7 @@ dependencies = [ "bitflags 2.11.0", "block", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "log", "objc", "paste", @@ -6030,23 +5229,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "moka" -version = "0.12.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957228ad12042ee839f93c8f257b62b4c0ab5eaae1d4fa60de53b27c9d7c5046" -dependencies = [ - "crossbeam-channel", - "crossbeam-epoch", - "crossbeam-utils", - "equivalent", - "parking_lot", - "portable-atomic", - "smallvec", - "tagptr", - "uuid", -] - [[package]] name = "multer" version = "3.1.0" @@ -6056,7 +5238,7 @@ dependencies = [ "bytes", "encoding_rs", "futures-util", - "http 1.4.0", + "http", "httparse", "memchr", "mime", @@ -6079,8 +5261,7 @@ dependencies = [ "percent-encoding", "serde", "static_assertions", - "unsigned-varint 0.8.0", - "url", + "unsigned-varint", ] [[package]] @@ -6102,34 +5283,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89ace881e3f514092ce9efbcb8f413d0ad9763860b828981c2de51ddc666936c" dependencies = [ "no_std_io2", - "serde", - "unsigned-varint 0.8.0", + "unsigned-varint", ] [[package]] -name = "multistream-select" -version = "0.13.0" +name = "native-tls" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +checksum = "465500e14ea162429d264d44189adc38b199b62b1c21eea9f69e4b73cb03bbf2" dependencies = [ - "bytes", - "futures", + "libc", "log", - "pin-project", - "smallvec", - "unsigned-varint 0.7.2", -] - -[[package]] -name = "natpmp" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77366fa8ce34e2e1322dd97da65f11a62f451bd3daae8be6993c00800f61dd07" -dependencies = [ - "async-trait", - "cc", - "netdev", - "tokio", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", ] [[package]] @@ -6148,108 +5319,6 @@ dependencies = [ "rayon", ] -[[package]] -name = "netdev" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f901362e84cd407be6f8cd9d3a46bccf09136b095792785401ea7d283c79b91d" -dependencies = [ - "dlopen2", - "ipnet", - "libc", - "netlink-packet-core 0.7.0", - "netlink-packet-route 0.17.1", - "netlink-sys", - "once_cell", - "system-configuration 0.6.1", - "windows-sys 0.52.0", -] - -[[package]] -name = "netlink-packet-core" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72724faf704479d67b388da142b186f916188505e7e0b26719019c525882eda4" -dependencies = [ - "anyhow", - "byteorder", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-core" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3463cbb78394cb0141e2c926b93fc2197e473394b761986eca3b9da2c63ae0f4" -dependencies = [ - "paste", -] - -[[package]] -name = "netlink-packet-route" -version = "0.17.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "053998cea5a306971f88580d0829e90f270f940befd7cf928da179d4187a5a66" -dependencies = [ - "anyhow", - "bitflags 1.3.2", - "byteorder", - "libc", - "netlink-packet-core 0.7.0", - "netlink-packet-utils", -] - -[[package]] -name = "netlink-packet-route" -version = "0.28.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ce3636fa715e988114552619582b530481fd5ef176a1e5c1bf024077c2c9445" -dependencies = [ - "bitflags 2.11.0", - "libc", - "log", - "netlink-packet-core 0.8.1", -] - -[[package]] -name = "netlink-packet-utils" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ede8a08c71ad5a95cdd0e4e52facd37190977039a4704eb82a283f713747d34" -dependencies = [ - "anyhow", - "byteorder", - "paste", - "thiserror 1.0.69", -] - -[[package]] -name = "netlink-proto" -version = "0.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b65d130ee111430e47eed7896ea43ca693c387f097dd97376bffafbf25812128" -dependencies = [ - "bytes", - "futures", - "log", - "netlink-packet-core 0.8.1", - "netlink-sys", - "thiserror 2.0.18", -] - -[[package]] -name = "netlink-sys" -version = "0.8.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd6c30ed10fa69cc491d491b85cc971f6bdeb8e7367b7cde2ee6cc878d583fae" -dependencies = [ - "bytes", - "futures-util", - "libc", - "log", - "tokio", -] - [[package]] name = "next_tuple" version = "0.1.0" @@ -6274,18 +5343,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags 2.11.0", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "no_std_io2" version = "0.8.1" @@ -6550,15 +5607,6 @@ dependencies = [ "thiserror 2.0.18", ] -[[package]] -name = "oid-registry" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12f40cff3dde1b6087cc5d5f5d4d65712f34016a03ed60e9c08dcc392736b5b7" -dependencies = [ - "asn1-rs", -] - [[package]] name = "once_cell" version = "1.21.3" @@ -6577,12 +5625,50 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.11.0", + "cfg-if", + "foreign-types 0.3.2", + "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.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "opentelemetry" version = "0.31.0" @@ -6616,7 +5702,7 @@ checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" dependencies = [ "async-trait", "bytes", - "http 1.4.0", + "http", "opentelemetry", "reqwest", ] @@ -6627,7 +5713,7 @@ version = "0.31.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" dependencies = [ - "http 1.4.0", + "http", "opentelemetry", "opentelemetry-http", "opentelemetry-proto", @@ -6791,16 +5877,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "pem" -version = "3.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" -dependencies = [ - "base64 0.22.1", - "serde_core", -] - [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -6881,20 +5957,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" -[[package]] -name = "polling" -version = "3.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" -dependencies = [ - "cfg-if", - "concurrent-queue", - "hermit-abi", - "pin-project-lite", - "rustix", - "windows-sys 0.61.2", -] - [[package]] name = "polyval" version = "0.6.2" @@ -7069,29 +6131,6 @@ dependencies = [ "token_program", ] -[[package]] -name = "prometheus-client" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "504ee9ff529add891127c4827eb481bd69dc0ebc72e9a682e187db4caa60c3ca" -dependencies = [ - "dtoa", - "itoa", - "parking_lot", - "prometheus-client-derive-encode", -] - -[[package]] -name = "prometheus-client-derive-encode" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "440f724eba9f6996b75d63681b0a92b06947f1457076d503a4d2e2c8f56442b8" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.117", -] - [[package]] name = "proptest" version = "1.10.0" @@ -7245,25 +6284,6 @@ dependencies = [ "pyo3-build-config", "quote", "syn 2.0.117", -name = "quick-protobuf" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" -dependencies = [ - "byteorder", -] - -[[package]] -name = "quick-protobuf-codec" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15a0580ab32b169745d7a39db2ba969226ca16738931be152a3209b409de2474" -dependencies = [ - "asynchronous-codec", - "bytes", - "quick-protobuf", - "thiserror 1.0.69", - "unsigned-varint 0.8.0", ] [[package]] @@ -7274,13 +6294,12 @@ checksum = "b9e20a958963c291dc322d98411f541009df2ced7b5a4f2bd52337638cfccf20" dependencies = [ "bytes", "cfg_aliases", - "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2 0.6.3", + "socket2", "thiserror 2.0.18", "tokio", "tracing", @@ -7317,7 +6336,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2 0.6.3", + "socket2", "tracing", "windows-sys 0.59.0", ] @@ -7392,17 +6411,6 @@ dependencies = [ "rand_core 0.9.5", ] -[[package]] -name = "rand" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2e8e8bcc7961af1fdac401278c6a831614941f6164ee3bf4ce61b7edb162207" -dependencies = [ - "chacha20", - "getrandom 0.4.2", - "rand_core 0.10.1", -] - [[package]] name = "rand_chacha" version = "0.3.1" @@ -7441,12 +6449,6 @@ dependencies = [ "getrandom 0.3.4", ] -[[package]] -name = "rand_core" -version = "0.10.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" - [[package]] name = "rand_xorshift" version = "0.4.0" @@ -7482,19 +6484,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "rcgen" -version = "0.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75e669e5202259b5314d1ea5397316ad400819437857b90861765f24c4cf80a2" -dependencies = [ - "pem", - "ring", - "rustls-pki-types", - "time", - "yasna", -] - [[package]] name = "reactive_graph" version = "0.2.13" @@ -7635,18 +6624,22 @@ checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", "quinn", @@ -7657,6 +6650,7 @@ dependencies = [ "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-rustls", "tokio-util", "tower", @@ -7670,12 +6664,6 @@ dependencies = [ "webpki-roots", ] -[[package]] -name = "resolv-conf" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e061d1b48cb8d38042de4ae0a7a6401009d6143dc80d2e2d6f31f0bdd6470c7" - [[package]] name = "rfc6979" version = "0.4.0" @@ -8153,21 +7141,6 @@ checksum = "50a0e551c1e27e1731aba276dbeaeac73f53c7cd34d1bda485d02bd1e0f36844" dependencies = [ "libc", "windows-sys 0.59.0", -name = "rtnetlink" -version = "0.20.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b960d5d873a75b5be9761b1e73b146f52dddcd27bac75263f40fba686d4d7b5" -dependencies = [ - "futures-channel", - "futures-util", - "log", - "netlink-packet-core 0.8.1", - "netlink-packet-route 0.28.0", - "netlink-proto", - "netlink-sys", - "nix", - "thiserror 1.0.69", - "tokio", ] [[package]] @@ -8213,15 +7186,6 @@ dependencies = [ "semver", ] -[[package]] -name = "rusticata-macros" -version = "4.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "faf0c4a6ece9950b9abdb62b1cfcf2a68b3b67a10ba445b3bb85be2a293d0632" -dependencies = [ - "nom 7.1.3", -] - [[package]] name = "rustix" version = "1.1.4" @@ -8325,17 +7289,6 @@ dependencies = [ "twox-hash", ] -[[package]] -name = "rw-stream-sink" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8c9026ff5d2f23da5e45bbc283f156383001bfb09c4e44256d02c1a685fe9a1" -dependencies = [ - "futures", - "pin-project", - "static_assertions", -] - [[package]] name = "ryu" version = "1.0.23" @@ -8499,16 +7452,17 @@ name = "sequencer_core" version = "0.1.0" dependencies = [ "anyhow", + "bedrock_client", "borsh", "bytesize", "chrono", "common", "futures", "humantime-serde", + "jsonrpsee", "log", "logos-blockchain-core", "logos-blockchain-key-management-system-service", - "logos-blockchain-zone-sdk", "mempool", "nssa", "nssa_core", @@ -8534,6 +7488,7 @@ dependencies = [ "common", "env_logger", "futures", + "indexer_service_rpc", "jsonrpsee", "log", "mempool", @@ -8764,7 +7719,7 @@ dependencies = [ "const_format", "futures", "gloo-net", - "http 1.4.0", + "http", "http-body-util", "hyper", "inventory", @@ -8916,16 +7871,6 @@ dependencies = [ "tokio", ] -[[package]] -name = "socket2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e22376abed350d73dd1cd119b57ffccad95b4e585a7cda43e286245ce23c0678" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - [[package]] name = "socket2" version = "0.6.3" @@ -8945,7 +7890,7 @@ dependencies = [ "base64 0.22.1", "bytes", "futures", - "http 1.4.0", + "http", "httparse", "log", "rand 0.8.5", @@ -9115,17 +8060,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "system-configuration" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" -dependencies = [ - "bitflags 2.11.0", - "core-foundation 0.9.4", - "system-configuration-sys", -] - [[package]] name = "system-configuration" version = "0.7.0" @@ -9179,12 +8113,6 @@ dependencies = [ "web-sys", ] -[[package]] -name = "tagptr" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b2093cf4c8eb1e67749a6762251bc9cd836b6fc171623bd0a9d324d37af2417" - [[package]] name = "tap" version = "1.0.1" @@ -9262,9 +8190,9 @@ dependencies = [ [[package]] name = "testcontainers" -version = "0.27.3" +version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd5785b5483672915ed5fe3cddf9f546802779fc1eceff0a6fb7321fac81c1e" +checksum = "0bd36b06a2a6c0c3c81a83be1ab05fe86460d054d4d51bf513bc56b3e15bdc22" dependencies = [ "astral-tokio-tar", "async-trait", @@ -9276,7 +8204,7 @@ dependencies = [ "etcetera", "ferroid", "futures", - "http 1.4.0", + "http", "itertools 0.14.0", "log", "memchr", @@ -9456,7 +8384,7 @@ dependencies = [ "parking_lot", "pin-project-lite", "signal-hook-registry", - "socket2 0.6.3", + "socket2", "tokio-macros", "windows-sys 0.61.2", ] @@ -9472,6 +8400,27 @@ dependencies = [ "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-retry" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f57eb36ecbe0fc510036adff84824dd3c24bb781e21bfa67b69d556aa85214f" +dependencies = [ + "pin-project", + "rand 0.8.5", + "tokio", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -9632,7 +8581,7 @@ dependencies = [ "base64 0.22.1", "bytes", "h2", - "http 1.4.0", + "http", "http-body", "http-body-util", "hyper", @@ -9640,7 +8589,7 @@ dependencies = [ "hyper-util", "percent-encoding", "pin-project", - "socket2 0.6.3", + "socket2", "sync_wrapper", "tokio", "tokio-stream", @@ -9690,7 +8639,7 @@ dependencies = [ "bytes", "futures-core", "futures-util", - "http 1.4.0", + "http", "http-body", "http-body-util", "http-range-header", @@ -9898,7 +8847,7 @@ checksum = "8628dcc84e5a09eb3d8423d6cb682965dea9133204e8fb3efee74c2a0c259442" dependencies = [ "bytes", "data-encoding", - "http 1.4.0", + "http", "httparse", "log", "rand 0.9.3", @@ -9979,18 +8928,6 @@ dependencies = [ "syn 2.0.117", ] -[[package]] -name = "uint" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" -dependencies = [ - "byteorder", - "crunchy", - "hex", - "static_assertions", -] - [[package]] name = "unarray" version = "0.1.4" @@ -10064,12 +9001,6 @@ version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" -[[package]] -name = "unsigned-varint" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6889a77d49f1f013504cec6bf97a2c730394adedaeb1deb5ea08949a50541105" - [[package]] name = "unsigned-varint" version = "0.8.0" @@ -10104,7 +9035,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d81f9efa9df032be5934a46a068815a10a042b494b6a58cb0a1a97bb5467ed6f" dependencies = [ "base64 0.22.1", - "http 1.4.0", + "http", "httparse", "log", ] @@ -10475,12 +9406,6 @@ dependencies = [ "safe_arch", ] -[[package]] -name = "widestring" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72069c3113ab32ab29e5584db3c6ec55d416895e60715417b5b883a357c3e471" - [[package]] name = "winapi" version = "0.3.9" @@ -10512,27 +9437,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" -[[package]] -name = "windows" -version = "0.62.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "527fadee13e0c05939a6a05d5bd6eec6cd2e3dbd648b9f8e447c6518133d8580" -dependencies = [ - "windows-collections", - "windows-core", - "windows-future", - "windows-numerics", -] - -[[package]] -name = "windows-collections" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b2d95af1a8a14a3c7367e1ed4fc9c20e0a26e79551b1454d72583c97cc6610" -dependencies = [ - "windows-core", -] - [[package]] name = "windows-core" version = "0.62.2" @@ -10546,17 +9450,6 @@ dependencies = [ "windows-strings", ] -[[package]] -name = "windows-future" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d6f90251fe18a279739e78025bd6ddc52a7e22f921070ccdc67dde84c605cb" -dependencies = [ - "windows-core", - "windows-link", - "windows-threading", -] - [[package]] name = "windows-implement" version = "0.60.2" @@ -10585,16 +9478,6 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" -[[package]] -name = "windows-numerics" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e40844ac143cdb44aead537bbf727de9b044e107a0f1220392177d15b0f26" -dependencies = [ - "windows-core", - "windows-link", -] - [[package]] name = "windows-registry" version = "0.6.1" @@ -10691,15 +9574,6 @@ dependencies = [ "windows_x86_64_msvc 0.52.6", ] -[[package]] -name = "windows-threading" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3949bd5b99cafdf1c7ca86b43ca564028dfe27d66958f2470940f73d86d75b37" -dependencies = [ - "windows-link", -] - [[package]] name = "windows_aarch64_gnullvm" version = "0.42.2" @@ -10914,23 +9788,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "x509-parser" -version = "0.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4569f339c0c402346d4a75a9e39cf8dad310e287eef1ff56d4c68e5067f53460" -dependencies = [ - "asn1-rs", - "data-encoding", - "der-parser", - "lazy_static", - "nom 7.1.3", - "oid-registry", - "rusticata-macros", - "thiserror 2.0.18", - "time", -] - [[package]] name = "xattr" version = "1.6.1" @@ -10941,21 +9798,6 @@ dependencies = [ "rustix", ] -[[package]] -name = "xml-rs" -version = "0.8.28" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" - -[[package]] -name = "xmltree" -version = "0.10.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d7d8a75eaf6557bb84a65ace8609883db44a29951042ada9b393151532e41fcb" -dependencies = [ - "xml-rs", -] - [[package]] name = "xxhash-rust" version = "0.8.15" @@ -10970,7 +9812,7 @@ checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" dependencies = [ "arraydeque", "encoding_rs", - "hashlink 0.10.0", + "hashlink", ] [[package]] @@ -10979,15 +9821,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cfe53a6657fd280eaa890a3bc59152892ffa3e30101319d168b781ed6529b049" -[[package]] -name = "yasna" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" -dependencies = [ - "time", -] - [[package]] name = "yoke" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 2cb3aad4..551c1f98 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ "examples/program_deployment", "examples/program_deployment/methods", "examples/program_deployment/methods/guest", + "bedrock_client", "testnet_initial_state", "keycard_wallet", "indexer_ffi", @@ -59,7 +60,6 @@ indexer_service_rpc = { path = "indexer/service/rpc" } indexer_ffi = { path = "indexer_ffi" } wallet = { path = "wallet" } wallet-ffi = { path = "wallet-ffi", default-features = false } -indexer_ffi = { path = "indexer/ffi" } clock_core = { path = "programs/clock/core" } token_core = { path = "programs/token/core" } token_program = { path = "programs/token" } @@ -68,6 +68,7 @@ amm_program = { path = "programs/amm" } ata_core = { path = "programs/associated_token_account/core" } ata_program = { path = "programs/associated_token_account" } test_program_methods = { path = "test_program_methods" } +bedrock_client = { path = "bedrock_client" } testnet_initial_state = { path = "testnet_initial_state" } keycard_wallet = { path = "keycard_wallet" } @@ -124,12 +125,11 @@ schemars = "1.2" async-stream = "0.3.6" pyo3 = { version = "0.24", features = ["auto-initialize"] } -logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } -logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } -logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } -logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } -logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } -logos-blockchain-zone-sdk = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "ee281a447d95a951752461ee0a6e88eb4a0f17cf" } +logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" } +logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" } +logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" } +logos-blockchain-chain-broadcast-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" } +logos-blockchain-chain-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git", rev = "1da154c74b911318fb853d37261f8a05ffe513b4" } rocksdb = { version = "0.24.0", default-features = false, features = [ "snappy", diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 542fc93f..36caad85 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/associated_token_account.bin b/artifacts/program_methods/associated_token_account.bin index d29bcf02..5439d1af 100644 Binary files a/artifacts/program_methods/associated_token_account.bin and b/artifacts/program_methods/associated_token_account.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 928963dc..bdbcef61 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/clock.bin b/artifacts/program_methods/clock.bin index c2ce2594..d3ca0dab 100644 Binary files a/artifacts/program_methods/clock.bin and b/artifacts/program_methods/clock.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index a097ad3d..5e6a011b 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index abc964b6..57a201c4 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 5bb93687..dd613143 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index 615c12eb..6366eba6 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/auth_asserting_noop.bin b/artifacts/test_program_methods/auth_asserting_noop.bin index 24fb426c..f9e4d1d4 100644 Binary files a/artifacts/test_program_methods/auth_asserting_noop.bin and b/artifacts/test_program_methods/auth_asserting_noop.bin differ diff --git a/artifacts/test_program_methods/auth_transfer_proxy.bin b/artifacts/test_program_methods/auth_transfer_proxy.bin deleted file mode 100644 index fa1306d0..00000000 Binary files a/artifacts/test_program_methods/auth_transfer_proxy.bin and /dev/null differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index 59e8e43b..94a90236 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index d1f22c57..58331d6c 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin index afe2b1c2..2760b7a3 100644 Binary files a/artifacts/test_program_methods/changer_claimer.bin and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index df4466aa..ff504da1 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/clock_chain_caller.bin b/artifacts/test_program_methods/clock_chain_caller.bin index 13d81c8f..37c9a004 100644 Binary files a/artifacts/test_program_methods/clock_chain_caller.bin and b/artifacts/test_program_methods/clock_chain_caller.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 12d1ff64..3d69b8cb 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index 9ba00633..873ce66a 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/flash_swap_callback.bin b/artifacts/test_program_methods/flash_swap_callback.bin index 7a5962f9..0846f255 100644 Binary files a/artifacts/test_program_methods/flash_swap_callback.bin and b/artifacts/test_program_methods/flash_swap_callback.bin differ diff --git a/artifacts/test_program_methods/flash_swap_initiator.bin b/artifacts/test_program_methods/flash_swap_initiator.bin index 544a1718..1e285245 100644 Binary files a/artifacts/test_program_methods/flash_swap_initiator.bin and b/artifacts/test_program_methods/flash_swap_initiator.bin differ diff --git a/artifacts/test_program_methods/group_pda_spender.bin b/artifacts/test_program_methods/group_pda_spender.bin deleted file mode 100644 index 16efb8a4..00000000 Binary files a/artifacts/test_program_methods/group_pda_spender.bin and /dev/null differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin index 2034cbe7..cc757683 100644 Binary files a/artifacts/test_program_methods/malicious_authorization_changer.bin and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/malicious_caller_program_id.bin b/artifacts/test_program_methods/malicious_caller_program_id.bin index 514d3302..f152051d 100644 Binary files a/artifacts/test_program_methods/malicious_caller_program_id.bin and b/artifacts/test_program_methods/malicious_caller_program_id.bin differ diff --git a/artifacts/test_program_methods/malicious_self_program_id.bin b/artifacts/test_program_methods/malicious_self_program_id.bin index 45fa2e0b..6d83b95b 100644 Binary files a/artifacts/test_program_methods/malicious_self_program_id.bin and b/artifacts/test_program_methods/malicious_self_program_id.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index 623b25eb..29bcd715 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index 72feec32..c7cc1571 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index 92b61443..8f2b1e39 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index fed326c3..993c1451 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index a4fcda58..579db977 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/pda_claimer.bin b/artifacts/test_program_methods/pda_claimer.bin index 53b91181..1a541384 100644 Binary files a/artifacts/test_program_methods/pda_claimer.bin and b/artifacts/test_program_methods/pda_claimer.bin differ diff --git a/artifacts/test_program_methods/pda_fund_spend_proxy.bin b/artifacts/test_program_methods/pda_fund_spend_proxy.bin deleted file mode 100644 index e377e6bf..00000000 Binary files a/artifacts/test_program_methods/pda_fund_spend_proxy.bin and /dev/null differ diff --git a/artifacts/test_program_methods/pinata_cooldown.bin b/artifacts/test_program_methods/pinata_cooldown.bin index 69f6b617..2b0d979a 100644 Binary files a/artifacts/test_program_methods/pinata_cooldown.bin and b/artifacts/test_program_methods/pinata_cooldown.bin differ diff --git a/artifacts/test_program_methods/private_pda_claimer.bin b/artifacts/test_program_methods/private_pda_claimer.bin deleted file mode 100644 index 5a64c66d..00000000 Binary files a/artifacts/test_program_methods/private_pda_claimer.bin and /dev/null differ diff --git a/artifacts/test_program_methods/private_pda_delegator.bin b/artifacts/test_program_methods/private_pda_delegator.bin index 32dffd53..4b55e871 100644 Binary files a/artifacts/test_program_methods/private_pda_delegator.bin and b/artifacts/test_program_methods/private_pda_delegator.bin differ diff --git a/artifacts/test_program_methods/private_pda_spender.bin b/artifacts/test_program_methods/private_pda_spender.bin deleted file mode 100644 index 2db36680..00000000 Binary files a/artifacts/test_program_methods/private_pda_spender.bin and /dev/null differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index a9dbb869..3bdabade 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 5a4e7036..0aaf1a23 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/artifacts/test_program_methods/time_locked_transfer.bin b/artifacts/test_program_methods/time_locked_transfer.bin index db2dc82d..5700322e 100644 Binary files a/artifacts/test_program_methods/time_locked_transfer.bin and b/artifacts/test_program_methods/time_locked_transfer.bin differ diff --git a/artifacts/test_program_methods/two_pda_claimer.bin b/artifacts/test_program_methods/two_pda_claimer.bin index 56a8bd70..600b819d 100644 Binary files a/artifacts/test_program_methods/two_pda_claimer.bin and b/artifacts/test_program_methods/two_pda_claimer.bin differ diff --git a/artifacts/test_program_methods/validity_window.bin b/artifacts/test_program_methods/validity_window.bin index de524642..02ccc149 100644 Binary files a/artifacts/test_program_methods/validity_window.bin and b/artifacts/test_program_methods/validity_window.bin differ diff --git a/artifacts/test_program_methods/validity_window_chain_caller.bin b/artifacts/test_program_methods/validity_window_chain_caller.bin index 020f0c9b..d239c750 100644 Binary files a/artifacts/test_program_methods/validity_window_chain_caller.bin and b/artifacts/test_program_methods/validity_window_chain_caller.bin differ diff --git a/bedrock/deployment-settings.yaml b/bedrock/deployment-settings.yaml index 7ef63f03..d0c05e24 100644 --- a/bedrock/deployment-settings.yaml +++ b/bedrock/deployment-settings.yaml @@ -39,42 +39,42 @@ cryptarchia: threshold: 1 timestamp: 0 gossipsub_protocol: /integration/logos-blockchain/cryptarchia/proto/1.0.0 - genesis_block: - header: - version: Bedrock - parent_block: '0000000000000000000000000000000000000000000000000000000000000000' - slot: 0 - block_root: b5f8787ac23674822414c70eea15d842da38f2e806ede1a73cf7b5cf0277da07 - proof_of_leadership: - proof: '0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - entropy_contribution: '0000000000000000000000000000000000000000000000000000000000000000' - leader_key: '0000000000000000000000000000000000000000000000000000000000000000' - voucher_cm: '0000000000000000000000000000000000000000000000000000000000000000' - signature: '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - transactions: - - mantle_tx: - ops: + genesis_state: + mantle_tx: + ops: - opcode: 0 payload: - inputs: [] + inputs: [ ] outputs: - - value: 1 - pk: d204000000000000000000000000000000000000000000000000000000000000 - - value: 100 - pk: '2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26' - - value: 1 - pk: ed266e6e887b9b97059dc1aa1b7b2e19b934291753c6336a163fe4ebaa28e717 + - value: 1 + pk: d204000000000000000000000000000000000000000000000000000000000000 + - value: 100 + pk: 2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26 - opcode: 17 payload: - channel_id: '0000000000000000000000000000000000000000000000000000000000000000' - inscription: '67656e65736973' - parent: '0000000000000000000000000000000000000000000000000000000000000000' - signer: '0000000000000000000000000000000000000000000000000000000000000000' - execution_gas_price: 0 - storage_gas_price: 0 - ops_proofs: - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' - - !Ed25519Sig '00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' + channel_id: "0000000000000000000000000000000000000000000000000000000000000000" + inscription: [ 103, 101, 110, 101, 115, 105, 115 ] # "genesis" in bytes + parent: "0000000000000000000000000000000000000000000000000000000000000000" + signer: "0000000000000000000000000000000000000000000000000000000000000000" + execution_gas_price: 0 + storage_gas_price: 0 + ops_proofs: + - !ZkSig + pi_a: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + pi_b: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + pi_c: [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ] + - NoProof time: slot_duration: '1.0' chain_start_time: PLACEHOLDER_CHAIN_START_TIME diff --git a/bedrock/docker-compose.yml b/bedrock/docker-compose.yml index e16e505b..73795666 100644 --- a/bedrock/docker-compose.yml +++ b/bedrock/docker-compose.yml @@ -1,7 +1,7 @@ services: logos-blockchain-node-0: - image: ghcr.io/logos-blockchain/logos-blockchain@sha256:9f1829dea335c56f6ff68ae37ea872ed5313b96b69e8ffe143c02b7217de85fc + image: ghcr.io/logos-blockchain/logos-blockchain@sha256:c5243681b353278cabb562a176f0a5cfbefc2056f18cebc47fe0e3720c29fb12 ports: - "${PORT:-8080}:18080/tcp" volumes: diff --git a/bedrock_client/Cargo.toml b/bedrock_client/Cargo.toml new file mode 100644 index 00000000..2137cb74 --- /dev/null +++ b/bedrock_client/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bedrock_client" +version = "0.1.0" +edition = "2024" +license = { workspace = true } + +[lints] +workspace = true + +[dependencies] +common.workspace = true + +reqwest.workspace = true +anyhow.workspace = true +tokio-retry.workspace = true +futures.workspace = true +log.workspace = true +serde.workspace = true +humantime-serde.workspace = true +logos-blockchain-common-http-client.workspace = true +logos-blockchain-core.workspace = true +logos-blockchain-chain-broadcast-service.workspace = true +logos-blockchain-chain-service.workspace = true diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs new file mode 100644 index 00000000..4e9bfffd --- /dev/null +++ b/bedrock_client/src/lib.rs @@ -0,0 +1,121 @@ +use std::time::Duration; + +use anyhow::{Context as _, Result}; +use common::config::BasicAuth; +use futures::{Stream, TryFutureExt as _}; +#[expect(clippy::single_component_path_imports, reason = "Satisfy machete")] +use humantime_serde; +use log::{info, warn}; +pub use logos_blockchain_chain_broadcast_service::BlockInfo; +use logos_blockchain_chain_service::CryptarchiaInfo; +pub use logos_blockchain_common_http_client::{CommonHttpClient, Error}; +pub use logos_blockchain_core::{block::Block, header::HeaderId, mantle::SignedMantleTx}; +use reqwest::{Client, Url}; +use serde::{Deserialize, Serialize}; +use tokio_retry::Retry; + +/// Fibonacci backoff retry strategy configuration. +#[derive(Debug, Copy, Clone, Serialize, Deserialize)] +pub struct BackoffConfig { + #[serde(with = "humantime_serde")] + pub start_delay: Duration, + pub max_retries: usize, +} + +impl Default for BackoffConfig { + fn default() -> Self { + Self { + start_delay: Duration::from_millis(100), + max_retries: 5, + } + } +} + +/// Simple wrapper +/// maybe extend in the future for our purposes +/// `Clone` is cheap because `CommonHttpClient` is internally reference counted (`Arc`). +#[derive(Clone)] +pub struct BedrockClient { + http_client: CommonHttpClient, + node_url: Url, + backoff: BackoffConfig, +} + +impl BedrockClient { + pub fn new(backoff: BackoffConfig, node_url: Url, auth: Option) -> Result { + info!("Creating Bedrock client with node URL {node_url}"); + let client = Client::builder() + //Add more fields if needed + .timeout(std::time::Duration::from_mins(1)) + .build() + .context("Failed to build HTTP client")?; + + let auth = auth.map(|a| { + logos_blockchain_common_http_client::BasicAuthCredentials::new(a.username, a.password) + }); + + let http_client = CommonHttpClient::new_with_client(client, auth); + Ok(Self { + http_client, + node_url, + backoff, + }) + } + + pub async fn post_transaction(&self, tx: SignedMantleTx) -> Result, Error> { + Retry::spawn(self.backoff_strategy(), || async { + match self + .http_client + .post_transaction(self.node_url.clone(), tx.clone()) + .await + { + Ok(()) => Ok(Ok(())), + Err(err) => match err { + // Retry arm. + // Retrying only reqwest errors: mainly connected to http. + Error::Request(_) => Err(err), + // Returning non-retryable error + Error::Server(_) | Error::Client(_) | Error::Url(_) => Ok(Err(err)), + }, + } + }) + .await + } + + pub async fn get_lib_stream(&self) -> Result, Error> { + self.http_client.get_lib_stream(self.node_url.clone()).await + } + + pub async fn get_block_by_id( + &self, + header_id: HeaderId, + ) -> Result>, Error> { + Retry::spawn(self.backoff_strategy(), || { + self.http_client + .get_block_by_id(self.node_url.clone(), header_id) + .inspect_err(|err| warn!("Block fetching failed with error: {err:#}")) + }) + .await + } + + pub async fn get_consensus_info(&self) -> Result { + Retry::spawn(self.backoff_strategy(), || { + self.http_client + .consensus_info(self.node_url.clone()) + .inspect_err(|err| warn!("Block fetching failed with error: {err:#}")) + }) + .await + } + + fn backoff_strategy(&self) -> impl Iterator { + let start_delay_millis = self + .backoff + .start_delay + .as_millis() + .try_into() + .expect("Start delay must be less than u64::MAX milliseconds"); + + tokio_retry::strategy::FibonacciBackoff::from_millis(start_delay_millis) + .take(self.backoff.max_retries) + } +} diff --git a/common/src/block.rs b/common/src/block.rs index fbc4c9a6..3f354c2d 100644 --- a/common/src/block.rs +++ b/common/src/block.rs @@ -114,6 +114,11 @@ impl HashableBlockData { bedrock_parent_id, } } + + #[must_use] + pub fn block_hash(&self) -> BlockHash { + OwnHasher::hash(&borsh::to_vec(&self).unwrap()) + } } impl From for HashableBlockData { diff --git a/configs/docker-all-in-one/indexer_config.json b/configs/docker-all-in-one/indexer_config.json index ca99a90c..c2b07e3e 100644 --- a/configs/docker-all-in-one/indexer_config.json +++ b/configs/docker-all-in-one/indexer_config.json @@ -1,8 +1,12 @@ { "home": "./indexer/service", "consensus_info_polling_interval": "1s", - "bedrock_config": { - "addr": "http://logos-blockchain-node-0:18080" + "bedrock_client_config": { + "addr": "http://logos-blockchain-node-0:18080", + "backoff": { + "start_delay": "100ms", + "max_retries": 5 + } }, "channel_id": "0101010101010101010101010101010101010101010101010101010101010101", "initial_accounts": [ diff --git a/indexer/core/Cargo.toml b/indexer/core/Cargo.toml index d609f5cb..33fe2d9d 100644 --- a/indexer/core/Cargo.toml +++ b/indexer/core/Cargo.toml @@ -9,7 +9,7 @@ workspace = true [dependencies] common.workspace = true -logos-blockchain-zone-sdk.workspace = true +bedrock_client.workspace = true nssa.workspace = true nssa_core.workspace = true storage.workspace = true @@ -19,13 +19,13 @@ anyhow.workspace = true log.workspace = true serde.workspace = true humantime-serde.workspace = true +tokio.workspace = true borsh.workspace = true futures.workspace = true url.workspace = true logos-blockchain-core.workspace = true serde_json.workspace = true async-stream.workspace = true -tokio.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/indexer/core/src/block_store.rs b/indexer/core/src/block_store.rs index bfba5ed2..cff07b0f 100644 --- a/indexer/core/src/block_store.rs +++ b/indexer/core/src/block_store.rs @@ -1,12 +1,11 @@ use std::{path::Path, sync::Arc}; -use anyhow::{Context as _, Result}; +use anyhow::Result; +use bedrock_client::HeaderId; use common::{ block::{BedrockStatus, Block}, transaction::{NSSATransaction, clock_invocation}, }; -use logos_blockchain_core::{header::HeaderId, mantle::ops::channel::MsgId}; -use logos_blockchain_zone_sdk::Slot; use nssa::{Account, AccountId, V03State}; use nssa_core::BlockId; use storage::indexer::RocksDBIO; @@ -104,22 +103,6 @@ impl IndexerStore { Ok(self.dbio.calculate_state_for_id(block_id)?) } - pub fn get_zone_cursor(&self) -> Result> { - let Some(bytes) = self.dbio.get_zone_sdk_indexer_cursor_bytes()? else { - return Ok(None); - }; - let cursor: (MsgId, Slot) = serde_json::from_slice(&bytes) - .context("Failed to deserialize stored zone-sdk indexer cursor")?; - Ok(Some(cursor)) - } - - pub fn set_zone_cursor(&self, cursor: &(MsgId, Slot)) -> Result<()> { - let bytes = - serde_json::to_vec(cursor).context("Failed to serialize zone-sdk indexer cursor")?; - self.dbio.put_zone_sdk_indexer_cursor_bytes(&bytes)?; - Ok(()) - } - /// Recalculation of final state directly from DB. /// /// Used for indexer healthcheck. @@ -135,12 +118,6 @@ impl IndexerStore { .get_account_by_id(*account_id)) } - pub fn account_state_at_block(&self, account_id: &AccountId, block_id: u64) -> Result { - Ok(self - .get_state_at_block(block_id)? - .get_account_by_id(*account_id)) - } - pub async fn put_block(&self, mut block: Block, l1_header: HeaderId) -> Result<()> { { let mut state_guard = self.current_state.write().await; @@ -283,64 +260,4 @@ mod tests { assert_eq!(acc1_val.balance, 9920); assert_eq!(acc2_val.balance, 20080); } - - #[tokio::test] - async fn account_state_at_block() { - let home = tempdir().unwrap(); - - let storage = IndexerStore::open_db_with_genesis( - home.as_ref(), - &genesis_block(), - &nssa::V03State::new_with_genesis_accounts( - &[(acc1(), 10000), (acc2(), 20000)], - vec![], - 0, - ), - ) - .unwrap(); - - let mut prev_hash = genesis_block().header.hash; - - let from = acc1(); - let to = acc2(); - let sign_key = acc1_sign_key(); - - for i in 2..10 { - let tx = common::test_utils::create_transaction_native_token_transfer( - from, - i - 2, - to, - 10, - &sign_key, - ); - let block_id = u64::try_from(i).unwrap(); - - let next_block = - common::test_utils::produce_dummy_block(block_id, Some(prev_hash), vec![tx]); - prev_hash = next_block.header.hash; - - storage - .put_block(next_block, HeaderId::from([u8::try_from(i).unwrap(); 32])) - .await - .unwrap(); - } - - // Genesis block: no transfers applied yet. - let acc1_at_1 = storage.account_state_at_block(&acc1(), 1).unwrap(); - let acc2_at_1 = storage.account_state_at_block(&acc2(), 1).unwrap(); - assert_eq!(acc1_at_1.balance, 10000); - assert_eq!(acc2_at_1.balance, 20000); - - // After block 5: 4 transfers of 10 applied (one each in blocks 2..=5). - let acc1_at_5 = storage.account_state_at_block(&acc1(), 5).unwrap(); - let acc2_at_5 = storage.account_state_at_block(&acc2(), 5).unwrap(); - assert_eq!(acc1_at_5.balance, 9960); - assert_eq!(acc2_at_5.balance, 20040); - - // After final block 9: 8 transfers applied; should match current state. - let acc1_at_9 = storage.account_state_at_block(&acc1(), 9).unwrap(); - let acc2_at_9 = storage.account_state_at_block(&acc2(), 9).unwrap(); - assert_eq!(acc1_at_9.balance, 9920); - assert_eq!(acc2_at_9.balance, 20080); - } } diff --git a/indexer/core/src/config.rs b/indexer/core/src/config.rs index 40ac0870..291e54f5 100644 --- a/indexer/core/src/config.rs +++ b/indexer/core/src/config.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::{Context as _, Result}; +pub use bedrock_client::BackoffConfig; use common::config::BasicAuth; use humantime_serde; pub use logos_blockchain_core::mantle::ops::channel::ChannelId; @@ -15,6 +16,8 @@ use url::Url; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ClientConfig { + /// For individual RPC requests we use Fibonacci backoff retry strategy. + pub backoff: BackoffConfig, pub addr: Url, #[serde(default, skip_serializing_if = "Option::is_none")] pub auth: Option, @@ -28,7 +31,7 @@ pub struct IndexerConfig { pub signing_key: [u8; 32], #[serde(with = "humantime_serde")] pub consensus_info_polling_interval: Duration, - pub bedrock_config: ClientConfig, + pub bedrock_client_config: ClientConfig, pub channel_id: ChannelId, #[serde(skip_serializing_if = "Option::is_none")] pub initial_public_accounts: Option>, diff --git a/indexer/core/src/lib.rs b/indexer/core/src/lib.rs index eeb31ebb..44f0dc19 100644 --- a/indexer/core/src/lib.rs +++ b/indexer/core/src/lib.rs @@ -1,14 +1,15 @@ -use std::sync::Arc; +use std::collections::VecDeque; use anyhow::Result; -use common::block::{Block, HashableBlockData}; -// ToDo: Remove after testnet -use common::{HashType, PINATA_BASE58}; -use futures::StreamExt as _; -use log::{error, info, warn}; -use logos_blockchain_core::header::HeaderId; -use logos_blockchain_zone_sdk::{ - CommonHttpClient, ZoneMessage, adapter::NodeHttpClient, indexer::ZoneIndexer, +use bedrock_client::{BedrockClient, HeaderId}; +use common::{ + HashType, PINATA_BASE58, + block::{Block, HashableBlockData}, +}; +use log::{debug, error, info}; +use logos_blockchain_core::mantle::{ + Op, SignedMantleTx, + ops::channel::{ChannelId, inscribe::InscriptionOp}, }; use nssa::V03State; use testnet_initial_state::initial_state_testnet; @@ -20,11 +21,25 @@ pub mod config; #[derive(Clone)] pub struct IndexerCore { - pub zone_indexer: Arc>, + pub bedrock_client: BedrockClient, pub config: IndexerConfig, pub store: IndexerStore, } +#[derive(Clone)] +/// This struct represents one L1 block data fetched from backfilling. +pub struct BackfillBlockData { + l2_blocks: Vec, + l1_header: HeaderId, +} + +#[derive(Clone)] +/// This struct represents data fetched fom backfilling in one iteration. +pub struct BackfillData { + block_data: VecDeque, + curr_fin_l1_lib_header: HeaderId, +} + impl IndexerCore { pub fn new(config: IndexerConfig) -> Result { let hashable_data = HashableBlockData { @@ -48,7 +63,7 @@ impl IndexerCore { .iter() .map(|init_comm_data| { let npk = &init_comm_data.npk; - let account_id = nssa::AccountId::for_regular_private_account(npk, 0); + let account_id = nssa::AccountId::from((npk, 0)); let mut acc = init_comm_data.account.clone(); @@ -92,88 +107,279 @@ impl IndexerCore { let home = config.home.join("rocksdb"); - let basic_auth = config.bedrock_config.auth.clone().map(Into::into); - let node = NodeHttpClient::new( - CommonHttpClient::new(basic_auth), - config.bedrock_config.addr.clone(), - ); - let zone_indexer = ZoneIndexer::new(config.channel_id, node); - Ok(Self { - zone_indexer: Arc::new(zone_indexer), + bedrock_client: BedrockClient::new( + config.bedrock_client_config.backoff, + config.bedrock_client_config.addr.clone(), + config.bedrock_client_config.auth.clone(), + )?, config, store: IndexerStore::open_db_with_genesis(&home, &genesis_block, &state)?, }) } - pub fn subscribe_parse_block_stream(&self) -> impl futures::Stream> + '_ { - let poll_interval = self.config.consensus_info_polling_interval; - let initial_cursor = self - .store - .get_zone_cursor() - .expect("Failed to load zone-sdk indexer cursor"); - + pub fn subscribe_parse_block_stream(&self) -> impl futures::Stream> { async_stream::stream! { - let mut cursor = initial_cursor; + info!("Searching for initial header"); - if cursor.is_some() { - info!("Resuming indexer from cursor {cursor:?}"); + let last_stored_l1_lib_header = self.store.last_observed_l1_lib_header()?; + + let mut prev_last_l1_lib_header = if let Some(last_l1_lib_header) = last_stored_l1_lib_header { + info!("Last l1 lib header found: {last_l1_lib_header}"); + last_l1_lib_header } else { - info!("Starting indexer from beginning of channel"); - } + info!("Last l1 lib header not found in DB"); + info!("Searching for the start of a channel"); - loop { - let stream = match self.zone_indexer.next_messages(cursor).await { - Ok(s) => s, - Err(err) => { - error!("Failed to start zone-sdk next_messages stream: {err}"); - tokio::time::sleep(poll_interval).await; - continue; - } - }; - let mut stream = std::pin::pin!(stream); + let BackfillData { + block_data: start_buff, + curr_fin_l1_lib_header: last_l1_lib_header, + } = self.search_for_channel_start().await?; - while let Some((msg, slot)) = stream.next().await { - let zone_block = match msg { - ZoneMessage::Block(b) => b, - // Non-block messages don't carry a cursor position; the - // next ZoneBlock advances past them implicitly. - ZoneMessage::Deposit(_) | ZoneMessage::Withdraw(_) => continue, - }; + for BackfillBlockData { + l2_blocks: l2_block_vec, + l1_header, + } in start_buff { + let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect(); + l2_blocks_parsed_ids.sort_unstable(); + info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids); - let block: Block = match borsh::from_slice(&zone_block.data) { - Ok(b) => b, - Err(e) => { - error!("Failed to deserialize L2 block from zone-sdk: {e}"); - // Advance past the broken inscription so we don't - // re-process it on restart. - cursor = Some((zone_block.id, slot)); - if let Err(err) = self.store.set_zone_cursor(&(zone_block.id, slot)) { - warn!("Failed to persist indexer cursor: {err:#}"); - } - continue; + for l2_block in l2_block_vec { + // TODO: proper fix is to make the sequencer's genesis include a + // trailing `clock_invocation(0)` (and have the indexer's + // `open_db_with_genesis` not pre-apply state transitions) so the + // inscribed genesis can flow through `put_block` like any other + // block. For now we skip re-applying it. + // + // The channel-start (block_id == 1) is the sequencer's genesis + // inscription that we re-discover during initial search. The + // indexer already has its own locally-constructed genesis in + // the store from `open_db_with_genesis`, so re-applying the + // inscribed copy is both redundant and would fail the strict + // block validation in `put_block` (the inscribed genesis lacks + // the trailing clock invocation). + if l2_block.header.block_id != 1 { + self + .store + .put_block(l2_block.clone(), l1_header) + .await + .inspect_err(|err| error!("Failed to put block with err {err:?}"))?; } - }; - info!("Indexed L2 block {}", block.header.block_id); - - // TODO: Remove l1_header placeholder once storage layer - // no longer requires it. Zone-sdk handles L1 tracking internally. - let placeholder_l1_header = HeaderId::from([0_u8; 32]); - if let Err(err) = self.store.put_block(block.clone(), placeholder_l1_header).await { - error!("Failed to store block {}: {err:#}", block.header.block_id); + yield Ok(l2_block); } - - cursor = Some((zone_block.id, slot)); - if let Err(err) = self.store.set_zone_cursor(&(zone_block.id, slot)) { - warn!("Failed to persist indexer cursor: {err:#}"); - } - yield Ok(block); } - // Stream ended (caught up to LIB). Sleep then poll again. - tokio::time::sleep(poll_interval).await; + last_l1_lib_header + }; + + info!("Searching for initial header finished"); + + info!("Starting backfilling from {prev_last_l1_lib_header}"); + + loop { + let BackfillData { + block_data: buff, + curr_fin_l1_lib_header, + } = self + .backfill_to_last_l1_lib_header_id(prev_last_l1_lib_header, &self.config.channel_id) + .await + .inspect_err(|err| error!("Failed to backfill to last l1 lib header id with err {err:#?}"))?; + + prev_last_l1_lib_header = curr_fin_l1_lib_header; + + for BackfillBlockData { + l2_blocks: l2_block_vec, + l1_header: header, + } in buff { + let mut l2_blocks_parsed_ids: Vec<_> = l2_block_vec.iter().map(|block| block.header.block_id).collect(); + l2_blocks_parsed_ids.sort_unstable(); + info!("Parsed {} L2 blocks with ids {:?}", l2_block_vec.len(), l2_blocks_parsed_ids); + + for l2_block in l2_block_vec { + self.store.put_block(l2_block.clone(), header).await?; + + yield Ok(l2_block); + } + } } } } + + async fn get_lib(&self) -> Result { + Ok(self.bedrock_client.get_consensus_info().await?.lib) + } + + async fn get_next_lib(&self, prev_lib: HeaderId) -> Result { + loop { + let next_lib = self.get_lib().await?; + if next_lib == prev_lib { + info!( + "Wait {:?} to not spam the node", + self.config.consensus_info_polling_interval + ); + tokio::time::sleep(self.config.consensus_info_polling_interval).await; + } else { + break Ok(next_lib); + } + } + } + + /// WARNING: depending on channel state, + /// may take indefinite amount of time. + pub async fn search_for_channel_start(&self) -> Result { + let mut curr_last_l1_lib_header = self.get_lib().await?; + let mut backfill_start = curr_last_l1_lib_header; + // ToDo: How to get root? + let mut backfill_limit = HeaderId::from([0; 32]); + // ToDo: Not scalable, initial buffer should be stored in DB to not run out of memory + // Don't want to complicate DB even more right now. + let mut block_buffer = VecDeque::new(); + + 'outer: loop { + let mut cycle_header = curr_last_l1_lib_header; + + loop { + let Some(cycle_block) = self.bedrock_client.get_block_by_id(cycle_header).await? + else { + // First run can reach root easily + // so here we are optimistic about L1 + // failing to get parent. + break; + }; + + // It would be better to have id, but block does not have it, so slot will do. + info!( + "INITIAL SEARCH: Observed L1 block at slot {}", + cycle_block.header().slot().into_inner() + ); + debug!( + "INITIAL SEARCH: This block header is {}", + cycle_block.header().id() + ); + debug!( + "INITIAL SEARCH: This block parent is {}", + cycle_block.header().parent() + ); + + let (l2_block_vec, l1_header) = + parse_block_owned(&cycle_block, &self.config.channel_id); + + info!("Parsed {} L2 blocks", l2_block_vec.len()); + + if !l2_block_vec.is_empty() { + block_buffer.push_front(BackfillBlockData { + l2_blocks: l2_block_vec.clone(), + l1_header, + }); + } + + if let Some(first_l2_block) = l2_block_vec.first() + && first_l2_block.header.block_id == 1 + { + info!("INITIAL_SEARCH: Found channel start"); + break 'outer; + } + + // Step back to parent + let parent = cycle_block.header().parent(); + + if parent == backfill_limit { + break; + } + + cycle_header = parent; + } + + info!("INITIAL_SEARCH: Reached backfill limit, refetching last l1 lib header"); + + block_buffer.clear(); + backfill_limit = backfill_start; + curr_last_l1_lib_header = self.get_next_lib(curr_last_l1_lib_header).await?; + backfill_start = curr_last_l1_lib_header; + } + + Ok(BackfillData { + block_data: block_buffer, + curr_fin_l1_lib_header: curr_last_l1_lib_header, + }) + } + + pub async fn backfill_to_last_l1_lib_header_id( + &self, + last_fin_l1_lib_header: HeaderId, + channel_id: &ChannelId, + ) -> Result { + let curr_fin_l1_lib_header = self.get_next_lib(last_fin_l1_lib_header).await?; + // ToDo: Not scalable, buffer should be stored in DB to not run out of memory + // Don't want to complicate DB even more right now. + let mut block_buffer = VecDeque::new(); + + let mut cycle_header = curr_fin_l1_lib_header; + loop { + let Some(cycle_block) = self.bedrock_client.get_block_by_id(cycle_header).await? else { + return Err(anyhow::anyhow!("Parent not found")); + }; + + if cycle_block.header().id() == last_fin_l1_lib_header { + break; + } + // Step back to parent + cycle_header = cycle_block.header().parent(); + + // It would be better to have id, but block does not have it, so slot will do. + info!( + "Observed L1 block at slot {}", + cycle_block.header().slot().into_inner() + ); + + let (l2_block_vec, l1_header) = parse_block_owned(&cycle_block, channel_id); + + info!("Parsed {} L2 blocks", l2_block_vec.len()); + + if !l2_block_vec.is_empty() { + block_buffer.push_front(BackfillBlockData { + l2_blocks: l2_block_vec, + l1_header, + }); + } + } + + Ok(BackfillData { + block_data: block_buffer, + curr_fin_l1_lib_header, + }) + } +} + +fn parse_block_owned( + l1_block: &bedrock_client::Block, + decoded_channel_id: &ChannelId, +) -> (Vec, HeaderId) { + ( + #[expect( + clippy::wildcard_enum_match_arm, + reason = "We are only interested in channel inscription ops, so it's fine to ignore the rest" + )] + l1_block + .transactions() + .flat_map(|tx| { + tx.mantle_tx.ops.iter().filter_map(|op| match op { + Op::ChannelInscribe(InscriptionOp { + channel_id, + inscription, + .. + }) if channel_id == decoded_channel_id => { + borsh::from_slice::(inscription) + .inspect_err(|err| { + error!("Failed to deserialize our inscription with err: {err:#?}"); + }) + .ok() + } + _ => None, + }) + }) + .collect(), + l1_block.header().id(), + ) } diff --git a/indexer/ffi/indexer_ffi.h b/indexer/ffi/indexer_ffi.h deleted file mode 100644 index 7626b3b3..00000000 --- a/indexer/ffi/indexer_ffi.h +++ /dev/null @@ -1,685 +0,0 @@ -#include -#include -#include -#include - -typedef enum OperationStatus { - Ok = 0, - NullPointer = 1, - InitializationError = 2, - ClientError = 3, -} OperationStatus; - -typedef enum FfiTransactionKind { - Public = 0, - Private, - ProgramDeploy, -} FfiTransactionKind; - -typedef enum FfiBedrockStatus { - Pending = 0, - Safe, - Finalized, -} FfiBedrockStatus; - -typedef struct IndexerServiceFFI { - void *indexer_handle; - void *runtime; - void *indexer_client; -} IndexerServiceFFI; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_IndexerServiceFFI__OperationStatus { - struct IndexerServiceFFI *value; - enum OperationStatus error; -} PointerResult_IndexerServiceFFI__OperationStatus; - -typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_u64__OperationStatus { - uint64_t *value; - enum OperationStatus error; -} PointerResult_u64__OperationStatus; - -typedef uint64_t FfiBlockId; - -/** - * 32-byte array type for `AccountId`, keys, hashes, etc. - */ -typedef struct FfiBytes32 { - uint8_t data[32]; -} FfiBytes32; - -typedef struct FfiBytes32 FfiHashType; - -typedef uint64_t FfiTimestamp; - -/** - * 64-byte array type for signatures, etc. - */ -typedef struct FfiBytes64 { - uint8_t data[64]; -} FfiBytes64; - -typedef struct FfiBytes64 FfiSignature; - -typedef struct FfiBlockHeader { - FfiBlockId block_id; - FfiHashType prev_block_hash; - FfiHashType hash; - FfiTimestamp timestamp; - FfiSignature signature; -} FfiBlockHeader; - -/** - * Program ID - 8 u32 values (32 bytes total). - */ -typedef struct FfiProgramId { - uint32_t data[8]; -} FfiProgramId; - -typedef struct FfiBytes32 FfiAccountId; - -typedef struct FfiVec_FfiAccountId { - FfiAccountId *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiAccountId; - -typedef struct FfiVec_FfiAccountId FfiAccountIdList; - -/** - * U128 - 16 bytes little endian. - */ -typedef struct FfiU128 { - uint8_t data[16]; -} FfiU128; - -typedef struct FfiU128 FfiNonce; - -typedef struct FfiVec_FfiNonce { - FfiNonce *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiNonce; - -typedef struct FfiVec_FfiNonce FfiNonceList; - -typedef struct FfiVec_u32 { - uint32_t *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_u32; - -typedef struct FfiVec_u32 FfiInstructionDataList; - -typedef struct FfiPublicMessage { - struct FfiProgramId program_id; - FfiAccountIdList account_ids; - FfiNonceList nonces; - FfiInstructionDataList instruction_data; -} FfiPublicMessage; - -typedef struct FfiBytes32 FfiPublicKey; - -typedef struct FfiSignaturePubKeyEntry { - FfiSignature signature; - FfiPublicKey public_key; -} FfiSignaturePubKeyEntry; - -typedef struct FfiVec_FfiSignaturePubKeyEntry { - struct FfiSignaturePubKeyEntry *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiSignaturePubKeyEntry; - -typedef struct FfiVec_FfiSignaturePubKeyEntry FfiSignaturePubKeyList; - -typedef struct FfiPublicTransactionBody { - FfiHashType hash; - struct FfiPublicMessage message; - FfiSignaturePubKeyList witness_set; -} FfiPublicTransactionBody; - -/** - * Account data structure - C-compatible version of nssa Account. - * - * Note: `balance` and `nonce` are u128 values represented as little-endian - * byte arrays since C doesn't have native u128 support. - */ -typedef struct FfiAccount { - struct FfiProgramId program_owner; - /** - * Balance as little-endian [u8; 16]. - */ - struct FfiU128 balance; - /** - * Pointer to account data bytes. - */ - uint8_t *data; - /** - * Length of account data. - */ - uintptr_t data_len; - /** - * Capacity of account data. - */ - uintptr_t data_cap; - /** - * Nonce as little-endian [u8; 16]. - */ - struct FfiU128 nonce; -} FfiAccount; - -typedef struct FfiVec_FfiAccount { - struct FfiAccount *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiAccount; - -typedef struct FfiVec_FfiAccount FfiAccountList; - -typedef struct FfiVec_u8 { - uint8_t *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_u8; - -typedef struct FfiVec_u8 FfiVecU8; - -typedef struct FfiEncryptedAccountData { - FfiVecU8 ciphertext; - FfiVecU8 epk; - uint8_t view_tag; -} FfiEncryptedAccountData; - -typedef struct FfiVec_FfiEncryptedAccountData { - struct FfiEncryptedAccountData *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiEncryptedAccountData; - -typedef struct FfiVec_FfiEncryptedAccountData FfiEncryptedAccountDataList; - -typedef struct FfiVec_FfiBytes32 { - struct FfiBytes32 *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiBytes32; - -typedef struct FfiVec_FfiBytes32 FfiVecBytes32; - -typedef struct FfiNullifierCommitmentSet { - struct FfiBytes32 nullifier; - struct FfiBytes32 commitment_set_digest; -} FfiNullifierCommitmentSet; - -typedef struct FfiVec_FfiNullifierCommitmentSet { - struct FfiNullifierCommitmentSet *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiNullifierCommitmentSet; - -typedef struct FfiVec_FfiNullifierCommitmentSet FfiNullifierCommitmentSetList; - -typedef struct FfiPrivacyPreservingMessage { - FfiAccountIdList public_account_ids; - FfiNonceList nonces; - FfiAccountList public_post_states; - FfiEncryptedAccountDataList encrypted_private_post_states; - FfiVecBytes32 new_commitments; - FfiNullifierCommitmentSetList new_nullifiers; - uint64_t block_validity_window[2]; - uint64_t timestamp_validity_window[2]; -} FfiPrivacyPreservingMessage; - -typedef FfiVecU8 FfiProof; - -typedef struct FfiPrivateTransactionBody { - FfiHashType hash; - struct FfiPrivacyPreservingMessage message; - FfiSignaturePubKeyList witness_set; - FfiProof proof; -} FfiPrivateTransactionBody; - -typedef FfiVecU8 FfiProgramDeploymentMessage; - -typedef struct FfiProgramDeploymentTransactionBody { - FfiHashType hash; - FfiProgramDeploymentMessage message; -} FfiProgramDeploymentTransactionBody; - -typedef struct FfiTransactionBody { - struct FfiPublicTransactionBody *public_body; - struct FfiPrivateTransactionBody *private_body; - struct FfiProgramDeploymentTransactionBody *program_deployment_body; -} FfiTransactionBody; - -typedef struct FfiTransaction { - struct FfiTransactionBody body; - enum FfiTransactionKind kind; -} FfiTransaction; - -typedef struct FfiVec_FfiTransaction { - struct FfiTransaction *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiTransaction; - -typedef struct FfiVec_FfiTransaction FfiBlockBody; - -typedef struct FfiBytes32 FfiMsgId; - -typedef struct FfiBlock { - struct FfiBlockHeader header; - FfiBlockBody body; - enum FfiBedrockStatus bedrock_status; - FfiMsgId bedrock_parent_id; -} FfiBlock; - -typedef struct FfiOption_FfiBlock { - struct FfiBlock *value; - bool is_some; -} FfiOption_FfiBlock; - -typedef struct FfiOption_FfiBlock FfiBlockOpt; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_FfiBlockOpt__OperationStatus { - FfiBlockOpt *value; - enum OperationStatus error; -} PointerResult_FfiBlockOpt__OperationStatus; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_FfiAccount__OperationStatus { - struct FfiAccount *value; - enum OperationStatus error; -} PointerResult_FfiAccount__OperationStatus; - -typedef struct FfiOption_FfiTransaction { - struct FfiTransaction *value; - bool is_some; -} FfiOption_FfiTransaction; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_FfiOption_FfiTransaction_____OperationStatus { - struct FfiOption_FfiTransaction *value; - enum OperationStatus error; -} PointerResult_FfiOption_FfiTransaction_____OperationStatus; - -typedef struct FfiVec_FfiBlock { - struct FfiBlock *entries; - uintptr_t len; - uintptr_t capacity; -} FfiVec_FfiBlock; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_FfiVec_FfiBlock_____OperationStatus { - struct FfiVec_FfiBlock *value; - enum OperationStatus error; -} PointerResult_FfiVec_FfiBlock_____OperationStatus; - -typedef struct FfiOption_u64 { - uint64_t *value; - bool is_some; -} FfiOption_u64; - -/** - * Simple wrapper around a pointer to a value or an error. - * - * Pointer is not guaranteed. You should check the error field before - * dereferencing the pointer. - */ -typedef struct PointerResult_FfiVec_FfiTransaction_____OperationStatus { - struct FfiVec_FfiTransaction *value; - enum OperationStatus error; -} PointerResult_FfiVec_FfiTransaction_____OperationStatus; - -/** - * Creates and starts an indexer based on the provided - * configuration file path. - * - * # Arguments - * - * - `config_path`: A pointer to a string representing the path to the configuration file. - * - `port`: Number representing a port, on which indexers RPC will start. - * - * # Returns - * - * An `InitializedIndexerServiceFFIResult` containing either a pointer to the - * initialized `IndexerServiceFFI` or an error code. - */ -InitializedIndexerServiceFFIResult start_indexer(const char *config_path, uint16_t port); - -/** - * Stops and frees the resources associated with the given indexer service. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. - * - * # Returns - * - * An `OperationStatus` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - * - The `IndexerServiceFFI` instance was created by this library - * - The pointer will not be used after this function returns - */ -enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer); - -/** - * # Safety - * It's up to the caller to pass a proper pointer, if somehow from c/c++ side - * this is called with a type which doesn't come from a returned `CString` it - * will cause a segfault. - */ -void free_cstring(char *block); - -/** - * Query the last block id from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - * # Returns - * - * A `PointerResult` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_u64__OperationStatus query_last_block(const struct IndexerServiceFFI *indexer); - -/** - * Query the block by id from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - `block_id`: `u64` number of block id - * - * # Returns - * - * A `PointerResult` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_FfiBlockOpt__OperationStatus query_block(const struct IndexerServiceFFI *indexer, - FfiBlockId block_id); - -/** - * Query the block by id from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - `hash`: `FfiHashType` - hash of block - * - * # Returns - * - * A `PointerResult` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_FfiBlockOpt__OperationStatus query_block_by_hash(const struct IndexerServiceFFI *indexer, - FfiHashType hash); - -/** - * Query the account by id from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - `account_id`: `FfiAccountId` - id of queried account - * - * # Returns - * - * A `PointerResult` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_FfiAccount__OperationStatus query_account(const struct IndexerServiceFFI *indexer, - FfiAccountId account_id); - -/** - * Query the trasnaction by hash from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - `hash`: `FfiHashType` - hash of transaction - * - * # Returns - * - * A `PointerResult, OperationStatus>` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_FfiOption_FfiTransaction_____OperationStatus query_transaction(const struct IndexerServiceFFI *indexer, - FfiHashType hash); - -/** - * Query the blocks by block range from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - `before`: `FfiOption` - end block of query - * - `limit`: `u64` - number of blocks to query before `before` - * - * # Returns - * - * A `PointerResult, OperationStatus>` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_FfiVec_FfiBlock_____OperationStatus query_block_vec(const struct IndexerServiceFFI *indexer, - struct FfiOption_u64 before, - uint64_t limit); - -/** - * Query the transactions range by account id from indexer. - * - * # Arguments - * - * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. - * - `account_id`: `FfiAccountId` - id of queried account - * - `offset`: `u64` - first tx id of query - * - `limit`: `u64` - number of tx ids to query after `offset` - * - * # Returns - * - * A `PointerResult, OperationStatus>` indicating success or failure. - * - * # Safety - * - * The caller must ensure that: - * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance - */ -struct PointerResult_FfiVec_FfiTransaction_____OperationStatus query_transactions_by_account(const struct IndexerServiceFFI *indexer, - FfiAccountId account_id, - uint64_t offset, - uint64_t limit); - -/** - * Frees the resources associated with the given ffi account. - * - * # Arguments - * - * - `val`: An instance of `FfiAccount`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiAccount`. - */ -void free_ffi_account(struct FfiAccount val); - -/** - * Frees the resources associated with the given ffi block. - * - * # Arguments - * - * - `val`: An instance of `FfiBlock`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiBlock`. - */ -void free_ffi_block(struct FfiBlock val); - -/** - * Frees the resources associated with the given ffi block option. - * - * # Arguments - * - * - `val`: An instance of `FfiBlockOpt`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiBlockOpt`. - */ -void free_ffi_block_opt(FfiBlockOpt val); - -/** - * Frees the resources associated with the given ffi block vector. - * - * # Arguments - * - * - `val`: An instance of `FfiVec`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiVec`. - */ -void free_ffi_block_vec(struct FfiVec_FfiBlock val); - -/** - * Frees the resources associated with the given ffi transaction. - * - * # Arguments - * - * - `val`: An instance of `FfiTransaction`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiTransaction`. - */ -void free_ffi_transaction(struct FfiTransaction val); - -/** - * Frees the resources associated with the given ffi transaction option. - * - * # Arguments - * - * - `val`: An instance of `FfiOption`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiOption`. - */ -void free_ffi_transaction_opt(struct FfiOption_FfiTransaction val); - -/** - * Frees the resources associated with the given vector of ffi transactions. - * - * # Arguments - * - * - `val`: An instance of `FfiVec`. - * - * # Returns - * - * void. - * - * # Safety - * - * The caller must ensure that: - * - `val` is a valid instance of `FfiVec`. - */ -void free_ffi_transaction_vec(struct FfiVec_FfiTransaction val); - -bool is_ok(const enum OperationStatus *self); - -bool is_error(const enum OperationStatus *self); diff --git a/indexer/ffi/src/api/client.rs b/indexer/ffi/src/api/client.rs deleted file mode 100644 index 825a57de..00000000 --- a/indexer/ffi/src/api/client.rs +++ /dev/null @@ -1,36 +0,0 @@ -use std::net::SocketAddr; - -use url::Url; - -use crate::OperationStatus; - -#[derive(Debug, Clone, Copy)] -pub enum UrlProtocol { - Http, - Ws, -} - -impl std::fmt::Display for UrlProtocol { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::Http => write!(f, "http"), - Self::Ws => write!(f, "ws"), - } - } -} - -pub(crate) fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result { - // Convert 0.0.0.0 to 127.0.0.1 for client connections - // When binding to port 0, the server binds to 0.0.0.0: - // but clients need to connect to 127.0.0.1: to work reliably - let url_string = if addr.ip().is_unspecified() { - format!("{protocol}://127.0.0.1:{}", addr.port()) - } else { - format!("{protocol}://{addr}") - }; - - url_string.parse().map_err(|e| { - log::error!("Could not parse indexer url: {e}"); - OperationStatus::InitializationError - }) -} diff --git a/indexer/ffi/src/api/query.rs b/indexer/ffi/src/api/query.rs deleted file mode 100644 index 1e39d961..00000000 --- a/indexer/ffi/src/api/query.rs +++ /dev/null @@ -1,334 +0,0 @@ -use indexer_service_protocol::{AccountId, HashType}; -use indexer_service_rpc::RpcClient as _; - -use crate::{ - IndexerServiceFFI, - api::{ - PointerResult, - types::{ - FfiAccountId, FfiBlockId, FfiHashType, FfiOption, FfiVec, - account::FfiAccount, - block::{FfiBlock, FfiBlockOpt}, - transaction::FfiTransaction, - }, - }, - errors::OperationStatus, -}; - -/// Query the last block id from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// -/// # Returns -/// -/// A `PointerResult` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_last_block( - indexer: *const IndexerServiceFFI, -) -> PointerResult { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - runtime - .block_on(client.get_last_finalized_block_id()) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - PointerResult::from_value, - ) -} - -/// Query the block by id from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// - `block_id`: `u64` number of block id -/// -/// # Returns -/// -/// A `PointerResult` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_block( - indexer: *const IndexerServiceFFI, - block_id: FfiBlockId, -) -> PointerResult { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - runtime - .block_on(client.get_block_by_id(block_id)) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |block_opt| { - let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { - FfiBlockOpt::from_value(block.into()) - }); - - PointerResult::from_value(block_ffi) - }, - ) -} - -/// Query the block by id from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// - `hash`: `FfiHashType` - hash of block -/// -/// # Returns -/// -/// A `PointerResult` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_block_by_hash( - indexer: *const IndexerServiceFFI, - hash: FfiHashType, -) -> PointerResult { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - runtime - .block_on(client.get_block_by_hash(HashType(hash.data))) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |block_opt| { - let block_ffi = block_opt.map_or_else(FfiBlockOpt::from_none, |block| { - FfiBlockOpt::from_value(block.into()) - }); - - PointerResult::from_value(block_ffi) - }, - ) -} - -/// Query the account by id from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// - `account_id`: `FfiAccountId` - id of queried account -/// -/// # Returns -/// -/// A `PointerResult` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_account( - indexer: *const IndexerServiceFFI, - account_id: FfiAccountId, -) -> PointerResult { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - runtime - .block_on(client.get_account(AccountId { - value: account_id.data, - })) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |acc| { - let acc_nssa: nssa::Account = - acc.try_into().expect("Source is in blocks, must fit"); - PointerResult::from_value(acc_nssa.into()) - }, - ) -} - -/// Query the trasnaction by hash from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// - `hash`: `FfiHashType` - hash of transaction -/// -/// # Returns -/// -/// A `PointerResult, OperationStatus>` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_transaction( - indexer: *const IndexerServiceFFI, - hash: FfiHashType, -) -> PointerResult, OperationStatus> { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - runtime - .block_on(client.get_transaction(HashType(hash.data))) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |tx_opt| { - let tx_ffi = tx_opt.map_or_else(FfiOption::::from_none, |tx| { - FfiOption::::from_value(tx.into()) - }); - - PointerResult::from_value(tx_ffi) - }, - ) -} - -/// Query the blocks by block range from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// - `before`: `FfiOption` - end block of query -/// - `limit`: `u64` - number of blocks to query before `before` -/// -/// # Returns -/// -/// A `PointerResult, OperationStatus>` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_block_vec( - indexer: *const IndexerServiceFFI, - before: FfiOption, - limit: u64, -) -> PointerResult, OperationStatus> { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - let before_std = before.is_some.then(|| unsafe { *before.value }); - - runtime - .block_on(client.get_blocks(before_std, limit)) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |block_vec| { - PointerResult::from_value( - block_vec - .into_iter() - .map(Into::into) - .collect::>() - .into(), - ) - }, - ) -} - -/// Query the transactions range by account id from indexer. -/// -/// # Arguments -/// -/// - `indexer`: A pointer to the `IndexerServiceFFI` instance to be queried. -/// - `account_id`: `FfiAccountId` - id of queried account -/// - `offset`: `u64` - first tx id of query -/// - `limit`: `u64` - number of tx ids to query after `offset` -/// -/// # Returns -/// -/// A `PointerResult, OperationStatus>` indicating success or failure. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `indexer` is a valid pointer to a `IndexerServiceFFI` instance -#[unsafe(no_mangle)] -pub unsafe extern "C" fn query_transactions_by_account( - indexer: *const IndexerServiceFFI, - account_id: FfiAccountId, - offset: u64, - limit: u64, -) -> PointerResult, OperationStatus> { - if indexer.is_null() { - log::error!("Attempted to query a null indexer pointer. This is a bug. Aborting."); - return PointerResult::from_error(OperationStatus::NullPointer); - } - - let indexer = unsafe { &*indexer }; - - let client = unsafe { indexer.client() }; - let runtime = unsafe { indexer.runtime() }; - - runtime - .block_on(client.get_transactions_by_account( - AccountId { - value: account_id.data, - }, - offset, - limit, - )) - .map_or_else( - |_| PointerResult::from_error(OperationStatus::ClientError), - |tx_vec| { - PointerResult::from_value( - tx_vec - .into_iter() - .map(Into::into) - .collect::>() - .into(), - ) - }, - ) -} diff --git a/indexer/ffi/src/api/types/account.rs b/indexer/ffi/src/api/types/account.rs deleted file mode 100644 index 6c35347f..00000000 --- a/indexer/ffi/src/api/types/account.rs +++ /dev/null @@ -1,119 +0,0 @@ -use indexer_service_protocol::ProgramId; - -use crate::api::types::{FfiBytes32, FfiProgramId, FfiU128}; - -/// Account data structure - C-compatible version of nssa Account. -/// -/// Note: `balance` and `nonce` are u128 values represented as little-endian -/// byte arrays since C doesn't have native u128 support. -#[repr(C)] -pub struct FfiAccount { - pub program_owner: FfiProgramId, - /// Balance as little-endian [u8; 16]. - pub balance: FfiU128, - /// Pointer to account data bytes. - pub data: *mut u8, - /// Length of account data. - pub data_len: usize, - /// Capacity of account data. - pub data_cap: usize, - /// Nonce as little-endian [u8; 16]. - pub nonce: FfiU128, -} - -// Helper functions to convert between Rust and FFI types - -impl From<&nssa::AccountId> for FfiBytes32 { - fn from(id: &nssa::AccountId) -> Self { - Self::from_account_id(id) - } -} - -impl From for FfiAccount { - fn from(value: nssa::Account) -> Self { - let nssa::Account { - program_owner, - balance, - data, - nonce, - } = value; - - let (data, data_len, data_cap) = data.into_inner().into_raw_parts(); - - let program_owner = FfiProgramId { - data: program_owner, - }; - Self { - program_owner, - balance: balance.into(), - data, - data_len, - data_cap, - nonce: nonce.0.into(), - } - } -} - -impl From for indexer_service_protocol::Account { - fn from(value: FfiAccount) -> Self { - let FfiAccount { - program_owner, - balance, - data, - data_cap, - data_len, - nonce, - } = value; - - Self { - program_owner: ProgramId(program_owner.data), - balance: balance.into(), - data: indexer_service_protocol::Data(unsafe { - Vec::from_raw_parts(data, data_len, data_cap) - }), - nonce: nonce.into(), - } - } -} - -impl From<&FfiAccount> for indexer_service_protocol::Account { - fn from(value: &FfiAccount) -> Self { - let &FfiAccount { - program_owner, - balance, - data, - data_cap, - data_len, - nonce, - } = value; - - Self { - program_owner: ProgramId(program_owner.data), - balance: balance.into(), - data: indexer_service_protocol::Data(unsafe { - Vec::from_raw_parts(data, data_len, data_cap) - }), - nonce: nonce.into(), - } - } -} - -/// Frees the resources associated with the given ffi account. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiAccount`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiAccount`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_account(val: FfiAccount) { - let orig_val: indexer_service_protocol::Account = val.into(); - drop(orig_val); -} diff --git a/indexer/ffi/src/api/types/block.rs b/indexer/ffi/src/api/types/block.rs deleted file mode 100644 index bca2fdb5..00000000 --- a/indexer/ffi/src/api/types/block.rs +++ /dev/null @@ -1,199 +0,0 @@ -use indexer_service_protocol::{ - BedrockStatus, Block, BlockHeader, HashType, MantleMsgId, Signature, -}; - -use crate::api::types::{ - FfiBlockId, FfiHashType, FfiMsgId, FfiOption, FfiSignature, FfiTimestamp, FfiVec, - transaction::free_ffi_transaction_vec, vectors::FfiBlockBody, -}; - -#[repr(C)] -pub struct FfiBlock { - pub header: FfiBlockHeader, - pub body: FfiBlockBody, - pub bedrock_status: FfiBedrockStatus, - pub bedrock_parent_id: FfiMsgId, -} - -impl From for FfiBlock { - fn from(value: Block) -> Self { - let Block { - header, - body, - bedrock_status, - bedrock_parent_id, - } = value; - - Self { - header: header.into(), - body: body - .transactions - .into_iter() - .map(Into::into) - .collect::>() - .into(), - bedrock_status: bedrock_status.into(), - bedrock_parent_id: bedrock_parent_id.into(), - } - } -} - -pub type FfiBlockOpt = FfiOption; - -#[repr(C)] -pub struct FfiBlockHeader { - pub block_id: FfiBlockId, - pub prev_block_hash: FfiHashType, - pub hash: FfiHashType, - pub timestamp: FfiTimestamp, - pub signature: FfiSignature, -} - -impl From for FfiBlockHeader { - fn from(value: BlockHeader) -> Self { - let BlockHeader { - block_id, - prev_block_hash, - hash, - timestamp, - signature, - } = value; - - Self { - block_id, - prev_block_hash: prev_block_hash.into(), - hash: hash.into(), - timestamp, - signature: signature.into(), - } - } -} - -#[repr(C)] -pub enum FfiBedrockStatus { - Pending = 0x0, - Safe, - Finalized, -} - -impl From for FfiBedrockStatus { - fn from(value: BedrockStatus) -> Self { - match value { - BedrockStatus::Finalized => Self::Finalized, - BedrockStatus::Pending => Self::Pending, - BedrockStatus::Safe => Self::Safe, - } - } -} - -impl From for BedrockStatus { - fn from(value: FfiBedrockStatus) -> Self { - match value { - FfiBedrockStatus::Finalized => Self::Finalized, - FfiBedrockStatus::Pending => Self::Pending, - FfiBedrockStatus::Safe => Self::Safe, - } - } -} - -/// Frees the resources associated with the given ffi block. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiBlock`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiBlock`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_block(val: FfiBlock) { - // We don't really need all the casts, but just in case - // All except `ffi_tx_ffi_vec` is Copy types, so no need for Drop - let _ = BlockHeader { - block_id: val.header.block_id, - prev_block_hash: HashType(val.header.prev_block_hash.data), - hash: HashType(val.header.hash.data), - timestamp: val.header.timestamp, - signature: Signature(val.header.signature.data), - }; - let ffi_tx_ffi_vec = val.body; - - #[expect(clippy::let_underscore_must_use, reason = "No use for this Copy type")] - let _: BedrockStatus = val.bedrock_status.into(); - - let _ = MantleMsgId(val.bedrock_parent_id.data); - - unsafe { - free_ffi_transaction_vec(ffi_tx_ffi_vec); - }; -} - -/// Frees the resources associated with the given ffi block option. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiBlockOpt`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiBlockOpt`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_block_opt(val: FfiBlockOpt) { - if val.is_some { - let value = unsafe { Box::from_raw(val.value) }; - - // We don't really need all the casts, but just in case - // All except `ffi_tx_ffi_vec` is Copy types, so no need for Drop - let _ = BlockHeader { - block_id: value.header.block_id, - prev_block_hash: HashType(value.header.prev_block_hash.data), - hash: HashType(value.header.hash.data), - timestamp: value.header.timestamp, - signature: Signature(value.header.signature.data), - }; - let ffi_tx_ffi_vec = value.body; - - #[expect(clippy::let_underscore_must_use, reason = "No use for this Copy type")] - let _: BedrockStatus = value.bedrock_status.into(); - - let _ = MantleMsgId(value.bedrock_parent_id.data); - - unsafe { - free_ffi_transaction_vec(ffi_tx_ffi_vec); - }; - } -} - -/// Frees the resources associated with the given ffi block vector. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiVec`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiVec`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_block_vec(val: FfiVec) { - let ffi_block_std_vec: Vec<_> = val.into(); - for block in ffi_block_std_vec { - unsafe { - free_ffi_block(block); - } - } -} diff --git a/indexer/ffi/src/api/types/mod.rs b/indexer/ffi/src/api/types/mod.rs deleted file mode 100644 index 2e7a77ad..00000000 --- a/indexer/ffi/src/api/types/mod.rs +++ /dev/null @@ -1,165 +0,0 @@ -use indexer_service_protocol::{AccountId, HashType, MantleMsgId, ProgramId, PublicKey, Signature}; - -pub mod account; -pub mod block; -pub mod transaction; -pub mod vectors; - -/// 32-byte array type for `AccountId`, keys, hashes, etc. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiBytes32 { - pub data: [u8; 32], -} - -/// 64-byte array type for signatures, etc. -#[repr(C)] -#[derive(Clone, Copy)] -pub struct FfiBytes64 { - pub data: [u8; 64], -} - -/// Program ID - 8 u32 values (32 bytes total). -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiProgramId { - pub data: [u32; 8], -} - -impl From for FfiProgramId { - fn from(value: ProgramId) -> Self { - Self { data: value.0 } - } -} - -/// U128 - 16 bytes little endian. -#[repr(C)] -#[derive(Clone, Copy, Default)] -pub struct FfiU128 { - pub data: [u8; 16], -} - -impl FfiBytes32 { - /// Create from a 32-byte array. - #[must_use] - pub const fn from_bytes(bytes: [u8; 32]) -> Self { - Self { data: bytes } - } - - /// Create from an `AccountId`. - #[must_use] - pub const fn from_account_id(id: &nssa::AccountId) -> Self { - Self { data: *id.value() } - } -} - -impl From for FfiU128 { - fn from(value: u128) -> Self { - Self { - data: value.to_le_bytes(), - } - } -} - -impl From for u128 { - fn from(value: FfiU128) -> Self { - Self::from_le_bytes(value.data) - } -} - -pub type FfiHashType = FfiBytes32; -pub type FfiMsgId = FfiBytes32; -pub type FfiBlockId = u64; -pub type FfiTimestamp = u64; -pub type FfiSignature = FfiBytes64; -pub type FfiAccountId = FfiBytes32; -pub type FfiNonce = FfiU128; -pub type FfiPublicKey = FfiBytes32; - -impl From for FfiHashType { - fn from(value: HashType) -> Self { - Self { data: value.0 } - } -} - -impl From for FfiMsgId { - fn from(value: MantleMsgId) -> Self { - Self { data: value.0 } - } -} - -impl From for FfiSignature { - fn from(value: Signature) -> Self { - Self { data: value.0 } - } -} - -impl From for FfiAccountId { - fn from(value: AccountId) -> Self { - Self { data: value.value } - } -} - -impl From for FfiPublicKey { - fn from(value: PublicKey) -> Self { - Self { data: value.0 } - } -} - -#[repr(C)] -pub struct FfiVec { - pub entries: *mut T, - pub len: usize, - pub capacity: usize, -} - -impl From> for FfiVec { - fn from(value: Vec) -> Self { - let (entries, len, capacity) = value.into_raw_parts(); - Self { - entries, - len, - capacity, - } - } -} - -impl From> for Vec { - fn from(value: FfiVec) -> Self { - unsafe { Self::from_raw_parts(value.entries, value.len, value.capacity) } - } -} - -impl FfiVec { - /// # Safety - /// - /// `index` must be lesser than `self.len`. - #[must_use] - pub unsafe fn get(&self, index: usize) -> &T { - let ptr = unsafe { self.entries.add(index) }; - unsafe { &*ptr } - } -} - -#[repr(C)] -pub struct FfiOption { - pub value: *mut T, - pub is_some: bool, -} - -impl FfiOption { - pub fn from_value(val: T) -> Self { - Self { - value: Box::into_raw(Box::new(val)), - is_some: true, - } - } - - #[must_use] - pub const fn from_none() -> Self { - Self { - value: std::ptr::null_mut(), - is_some: false, - } - } -} diff --git a/indexer/ffi/src/api/types/transaction.rs b/indexer/ffi/src/api/types/transaction.rs deleted file mode 100644 index ee3bd01b..00000000 --- a/indexer/ffi/src/api/types/transaction.rs +++ /dev/null @@ -1,548 +0,0 @@ -use indexer_service_protocol::{ - AccountId, Ciphertext, Commitment, CommitmentSetDigest, EncryptedAccountData, - EphemeralPublicKey, HashType, Nullifier, PrivacyPreservingMessage, - PrivacyPreservingTransaction, ProgramDeploymentMessage, ProgramDeploymentTransaction, - ProgramId, Proof, PublicKey, PublicMessage, PublicTransaction, Signature, Transaction, - ValidityWindow, WitnessSet, -}; - -use crate::api::types::{ - FfiBytes32, FfiHashType, FfiOption, FfiProgramId, FfiPublicKey, FfiSignature, FfiVec, - vectors::{ - FfiAccountIdList, FfiAccountList, FfiEncryptedAccountDataList, FfiInstructionDataList, - FfiNonceList, FfiNullifierCommitmentSetList, FfiProgramDeploymentMessage, FfiProof, - FfiSignaturePubKeyList, FfiVecBytes32, FfiVecU8, - }, -}; - -#[repr(C)] -pub struct FfiPublicTransactionBody { - pub hash: FfiHashType, - pub message: FfiPublicMessage, - pub witness_set: FfiSignaturePubKeyList, -} - -impl From for FfiPublicTransactionBody { - fn from(value: PublicTransaction) -> Self { - let PublicTransaction { - hash, - message, - witness_set, - } = value; - - Self { - hash: hash.into(), - message: message.into(), - witness_set: witness_set - .signatures_and_public_keys - .into_iter() - .map(Into::into) - .collect::>() - .into(), - } - } -} - -impl From> for PublicTransaction { - fn from(value: Box) -> Self { - Self { - hash: HashType(value.hash.data), - message: PublicMessage { - program_id: ProgramId(value.message.program_id.data), - account_ids: { - let std_vec: Vec<_> = value.message.account_ids.into(); - std_vec - .into_iter() - .map(|ffi_val| AccountId { - value: ffi_val.data, - }) - .collect() - }, - nonces: { - let std_vec: Vec<_> = value.message.nonces.into(); - std_vec.into_iter().map(Into::into).collect() - }, - instruction_data: value.message.instruction_data.into(), - }, - witness_set: WitnessSet { - signatures_and_public_keys: { - let std_vec: Vec<_> = value.witness_set.into(); - std_vec - .into_iter() - .map(|ffi_val| { - ( - Signature(ffi_val.signature.data), - PublicKey(ffi_val.public_key.data), - ) - }) - .collect() - }, - proof: None, - }, - } - } -} - -#[repr(C)] -pub struct FfiPublicMessage { - pub program_id: FfiProgramId, - pub account_ids: FfiAccountIdList, - pub nonces: FfiNonceList, - pub instruction_data: FfiInstructionDataList, -} - -impl From for FfiPublicMessage { - fn from(value: PublicMessage) -> Self { - let PublicMessage { - program_id, - account_ids, - nonces, - instruction_data, - } = value; - - Self { - program_id: program_id.into(), - account_ids: account_ids - .into_iter() - .map(Into::into) - .collect::>() - .into(), - nonces: nonces - .into_iter() - .map(Into::into) - .collect::>() - .into(), - instruction_data: instruction_data.into(), - } - } -} - -#[repr(C)] -pub struct FfiPrivateTransactionBody { - pub hash: FfiHashType, - pub message: FfiPrivacyPreservingMessage, - pub witness_set: FfiSignaturePubKeyList, - pub proof: FfiProof, -} - -impl From for FfiPrivateTransactionBody { - fn from(value: PrivacyPreservingTransaction) -> Self { - let PrivacyPreservingTransaction { - hash, - message, - witness_set, - } = value; - - Self { - hash: hash.into(), - message: message.into(), - witness_set: witness_set - .signatures_and_public_keys - .into_iter() - .map(Into::into) - .collect::>() - .into(), - proof: witness_set - .proof - .expect("Private execution: proof must be present") - .0 - .into(), - } - } -} - -impl From> for PrivacyPreservingTransaction { - fn from(value: Box) -> Self { - Self { - hash: HashType(value.hash.data), - message: PrivacyPreservingMessage { - public_account_ids: { - let std_vec: Vec<_> = value.message.public_account_ids.into(); - std_vec - .into_iter() - .map(|ffi_val| AccountId { - value: ffi_val.data, - }) - .collect() - }, - nonces: { - let std_vec: Vec<_> = value.message.nonces.into(); - std_vec.into_iter().map(Into::into).collect() - }, - public_post_states: { - let std_vec: Vec<_> = value.message.public_post_states.into(); - std_vec.into_iter().map(Into::into).collect() - }, - encrypted_private_post_states: { - let std_vec: Vec<_> = value.message.encrypted_private_post_states.into(); - std_vec - .into_iter() - .map(|ffi_val| EncryptedAccountData { - ciphertext: Ciphertext(ffi_val.ciphertext.into()), - epk: EphemeralPublicKey(ffi_val.epk.into()), - view_tag: ffi_val.view_tag, - }) - .collect() - }, - new_commitments: { - let std_vec: Vec<_> = value.message.new_commitments.into(); - std_vec - .into_iter() - .map(|ffi_val| Commitment(ffi_val.data)) - .collect() - }, - new_nullifiers: { - let std_vec: Vec<_> = value.message.new_nullifiers.into(); - std_vec - .into_iter() - .map(|ffi_val| { - ( - Nullifier(ffi_val.nullifier.data), - CommitmentSetDigest(ffi_val.commitment_set_digest.data), - ) - }) - .collect() - }, - block_validity_window: cast_ffi_validity_window( - value.message.block_validity_window, - ), - timestamp_validity_window: cast_ffi_validity_window( - value.message.timestamp_validity_window, - ), - }, - witness_set: WitnessSet { - signatures_and_public_keys: { - let std_vec: Vec<_> = value.witness_set.into(); - std_vec - .into_iter() - .map(|ffi_val| { - ( - Signature(ffi_val.signature.data), - PublicKey(ffi_val.public_key.data), - ) - }) - .collect() - }, - proof: Some(Proof(value.proof.into())), - }, - } - } -} - -#[repr(C)] -pub struct FfiPrivacyPreservingMessage { - pub public_account_ids: FfiAccountIdList, - pub nonces: FfiNonceList, - pub public_post_states: FfiAccountList, - pub encrypted_private_post_states: FfiEncryptedAccountDataList, - pub new_commitments: FfiVecBytes32, - pub new_nullifiers: FfiNullifierCommitmentSetList, - pub block_validity_window: [u64; 2], - pub timestamp_validity_window: [u64; 2], -} - -impl From for FfiPrivacyPreservingMessage { - fn from(value: PrivacyPreservingMessage) -> Self { - let PrivacyPreservingMessage { - public_account_ids, - nonces, - public_post_states, - encrypted_private_post_states, - new_commitments, - new_nullifiers, - block_validity_window, - timestamp_validity_window, - } = value; - - Self { - public_account_ids: public_account_ids - .into_iter() - .map(Into::into) - .collect::>() - .into(), - nonces: nonces - .into_iter() - .map(Into::into) - .collect::>() - .into(), - public_post_states: public_post_states - .into_iter() - .map(|acc_ind| -> nssa::Account { - acc_ind.try_into().expect("Source is in blocks, must fit") - }) - .map(Into::into) - .collect::>() - .into(), - encrypted_private_post_states: encrypted_private_post_states - .into_iter() - .map(Into::into) - .collect::>() - .into(), - new_commitments: new_commitments - .into_iter() - .map(|comm| FfiBytes32 { data: comm.0 }) - .collect::>() - .into(), - new_nullifiers: new_nullifiers - .into_iter() - .map(Into::into) - .collect::>() - .into(), - block_validity_window: cast_validity_window(block_validity_window), - timestamp_validity_window: cast_validity_window(timestamp_validity_window), - } - } -} - -#[repr(C)] -pub struct FfiNullifierCommitmentSet { - pub nullifier: FfiBytes32, - pub commitment_set_digest: FfiBytes32, -} - -impl From<(Nullifier, CommitmentSetDigest)> for FfiNullifierCommitmentSet { - fn from(value: (Nullifier, CommitmentSetDigest)) -> Self { - Self { - nullifier: FfiBytes32 { data: value.0.0 }, - commitment_set_digest: FfiBytes32 { data: value.1.0 }, - } - } -} - -#[repr(C)] -pub struct FfiEncryptedAccountData { - pub ciphertext: FfiVecU8, - pub epk: FfiVecU8, - pub view_tag: u8, -} - -impl From for FfiEncryptedAccountData { - fn from(value: EncryptedAccountData) -> Self { - let EncryptedAccountData { - ciphertext, - epk, - view_tag, - } = value; - - Self { - ciphertext: ciphertext.0.into(), - epk: epk.0.into(), - view_tag, - } - } -} - -#[repr(C)] -pub struct FfiSignaturePubKeyEntry { - pub signature: FfiSignature, - pub public_key: FfiPublicKey, -} - -impl From<(Signature, PublicKey)> for FfiSignaturePubKeyEntry { - fn from(value: (Signature, PublicKey)) -> Self { - Self { - signature: value.0.into(), - public_key: value.1.into(), - } - } -} - -#[repr(C)] -pub struct FfiProgramDeploymentTransactionBody { - pub hash: FfiHashType, - pub message: FfiProgramDeploymentMessage, -} - -impl From> for ProgramDeploymentTransaction { - fn from(value: Box) -> Self { - Self { - hash: HashType(value.hash.data), - message: ProgramDeploymentMessage { - bytecode: value.message.into(), - }, - } - } -} - -impl From for FfiProgramDeploymentTransactionBody { - fn from(value: ProgramDeploymentTransaction) -> Self { - let ProgramDeploymentTransaction { hash, message } = value; - - Self { - hash: hash.into(), - message: message.bytecode.into(), - } - } -} - -#[repr(C)] -pub struct FfiTransactionBody { - pub public_body: *mut FfiPublicTransactionBody, - pub private_body: *mut FfiPrivateTransactionBody, - pub program_deployment_body: *mut FfiProgramDeploymentTransactionBody, -} - -#[repr(C)] -pub struct FfiTransaction { - pub body: FfiTransactionBody, - pub kind: FfiTransactionKind, -} - -impl From for FfiTransaction { - fn from(value: Transaction) -> Self { - match value { - Transaction::Public(pub_tx) => Self { - body: FfiTransactionBody { - public_body: Box::into_raw(Box::new(pub_tx.into())), - private_body: std::ptr::null_mut(), - program_deployment_body: std::ptr::null_mut(), - }, - kind: FfiTransactionKind::Public, - }, - Transaction::PrivacyPreserving(priv_tx) => Self { - body: FfiTransactionBody { - public_body: std::ptr::null_mut(), - private_body: Box::into_raw(Box::new(priv_tx.into())), - program_deployment_body: std::ptr::null_mut(), - }, - kind: FfiTransactionKind::Private, - }, - Transaction::ProgramDeployment(pr_dep_tx) => Self { - body: FfiTransactionBody { - public_body: std::ptr::null_mut(), - private_body: std::ptr::null_mut(), - program_deployment_body: Box::into_raw(Box::new(pr_dep_tx.into())), - }, - kind: FfiTransactionKind::ProgramDeploy, - }, - } - } -} - -#[repr(C)] -pub enum FfiTransactionKind { - Public = 0x0, - Private, - ProgramDeploy, -} - -/// Frees the resources associated with the given ffi transaction. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiTransaction`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiTransaction`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_transaction(val: FfiTransaction) { - match val.kind { - FfiTransactionKind::Public => { - let body = unsafe { Box::from_raw(val.body.public_body) }; - let std_body: PublicTransaction = body.into(); - drop(std_body); - } - FfiTransactionKind::Private => { - let body = unsafe { Box::from_raw(val.body.private_body) }; - let std_body: PrivacyPreservingTransaction = body.into(); - drop(std_body); - } - FfiTransactionKind::ProgramDeploy => { - let body = unsafe { Box::from_raw(val.body.program_deployment_body) }; - let std_body: ProgramDeploymentTransaction = body.into(); - drop(std_body); - } - } -} - -/// Frees the resources associated with the given ffi transaction option. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiOption`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiOption`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_transaction_opt(val: FfiOption) { - if val.is_some { - let value = unsafe { Box::from_raw(val.value) }; - - match value.kind { - FfiTransactionKind::Public => { - let body = unsafe { Box::from_raw(value.body.public_body) }; - let std_body: PublicTransaction = body.into(); - drop(std_body); - } - FfiTransactionKind::Private => { - let body = unsafe { Box::from_raw(value.body.private_body) }; - let std_body: PrivacyPreservingTransaction = body.into(); - drop(std_body); - } - FfiTransactionKind::ProgramDeploy => { - let body = unsafe { Box::from_raw(value.body.program_deployment_body) }; - let std_body: ProgramDeploymentTransaction = body.into(); - drop(std_body); - } - } - } -} - -/// Frees the resources associated with the given vector of ffi transactions. -/// -/// # Arguments -/// -/// - `val`: An instance of `FfiVec`. -/// -/// # Returns -/// -/// void. -/// -/// # Safety -/// -/// The caller must ensure that: -/// - `val` is a valid instance of `FfiVec`. -#[unsafe(no_mangle)] -pub unsafe extern "C" fn free_ffi_transaction_vec(val: FfiVec) { - let ffi_tx_std_vec: Vec<_> = val.into(); - for tx in ffi_tx_std_vec { - unsafe { - free_ffi_transaction(tx); - } - } -} - -fn cast_validity_window(window: ValidityWindow) -> [u64; 2] { - [ - window.0.0.unwrap_or_default(), - window.0.1.unwrap_or(u64::MAX), - ] -} - -const fn cast_ffi_validity_window(ffi_window: [u64; 2]) -> ValidityWindow { - let left = if ffi_window[0] == 0 { - None - } else { - Some(ffi_window[0]) - }; - - let right = if ffi_window[1] == u64::MAX { - None - } else { - Some(ffi_window[1]) - }; - - ValidityWindow((left, right)) -} diff --git a/indexer/ffi/src/api/types/vectors.rs b/indexer/ffi/src/api/types/vectors.rs deleted file mode 100644 index 46f08737..00000000 --- a/indexer/ffi/src/api/types/vectors.rs +++ /dev/null @@ -1,31 +0,0 @@ -use crate::api::types::{ - FfiAccountId, FfiBytes32, FfiNonce, FfiVec, - account::FfiAccount, - transaction::{ - FfiEncryptedAccountData, FfiNullifierCommitmentSet, FfiSignaturePubKeyEntry, FfiTransaction, - }, -}; - -pub type FfiVecU8 = FfiVec; - -pub type FfiAccountList = FfiVec; - -pub type FfiAccountIdList = FfiVec; - -pub type FfiVecBytes32 = FfiVec; - -pub type FfiBlockBody = FfiVec; - -pub type FfiNonceList = FfiVec; - -pub type FfiInstructionDataList = FfiVec; - -pub type FfiSignaturePubKeyList = FfiVec; - -pub type FfiProof = FfiVecU8; - -pub type FfiProgramDeploymentMessage = FfiVecU8; - -pub type FfiEncryptedAccountDataList = FfiVec; - -pub type FfiNullifierCommitmentSetList = FfiVec; diff --git a/indexer/service/configs/indexer_config.json b/indexer/service/configs/indexer_config.json index 558a3bfe..e4dd8f93 100644 --- a/indexer/service/configs/indexer_config.json +++ b/indexer/service/configs/indexer_config.json @@ -1,8 +1,12 @@ { "home": ".", "consensus_info_polling_interval": "1s", - "bedrock_config": { - "addr": "http://localhost:8080" + "bedrock_client_config": { + "addr": "http://localhost:8080", + "backoff": { + "start_delay": "100ms", + "max_retries": 5 + } }, "channel_id": "0101010101010101010101010101010101010101010101010101010101010101", "initial_accounts": [ diff --git a/indexer/service/rpc/src/lib.rs b/indexer/service/rpc/src/lib.rs index a6476ebd..217c60d4 100644 --- a/indexer/service/rpc/src/lib.rs +++ b/indexer/service/rpc/src/lib.rs @@ -41,13 +41,6 @@ pub trait Rpc { #[method(name = "getAccount")] async fn get_account(&self, account_id: AccountId) -> Result; - #[method(name = "getAccountAtBlock")] - async fn get_account_at_block( - &self, - account_id: AccountId, - block_id: BlockId, - ) -> Result; - #[method(name = "getTransaction")] async fn get_transaction( &self, diff --git a/indexer/service/src/mock_service.rs b/indexer/service/src/mock_service.rs index a413973d..c4a099b8 100644 --- a/indexer/service/src/mock_service.rs +++ b/indexer/service/src/mock_service.rs @@ -239,22 +239,6 @@ impl indexer_service_rpc::RpcServer for MockIndexerService { .ok_or_else(|| ErrorObjectOwned::owned(-32001, "Account not found", None::<()>)) } - async fn get_account_at_block( - &self, - account_id: AccountId, - _block_id: BlockId, - ) -> Result { - // Mock service does not track historical state; returns current state regardless of - // block_id. - self.state - .read() - .await - .accounts - .get(&account_id) - .cloned() - .ok_or_else(|| ErrorObjectOwned::owned(-32001, "Account not found", None::<()>)) - } - async fn get_transaction( &self, tx_hash: HashType, diff --git a/indexer/service/src/service.rs b/indexer/service/src/service.rs index 8d079265..e2f8a321 100644 --- a/indexer/service/src/service.rs +++ b/indexer/service/src/service.rs @@ -83,19 +83,6 @@ impl indexer_service_rpc::RpcServer for IndexerService { .into()) } - async fn get_account_at_block( - &self, - account_id: AccountId, - block_id: BlockId, - ) -> Result { - Ok(self - .indexer - .store - .account_state_at_block(&account_id.into(), block_id) - .map_err(db_error)? - .into()) - } - async fn get_transaction( &self, tx_hash: HashType, diff --git a/indexer/ffi/Cargo.toml b/indexer_ffi/Cargo.toml similarity index 71% rename from indexer/ffi/Cargo.toml rename to indexer_ffi/Cargo.toml index 1e6b1468..b55230c6 100644 --- a/indexer/ffi/Cargo.toml +++ b/indexer_ffi/Cargo.toml @@ -5,16 +5,9 @@ name = "indexer_ffi" version = "0.1.0" [dependencies] -nssa.workspace = true indexer_service.workspace = true -indexer_service_rpc = { workspace = true, features = ["client"] } -indexer_service_protocol.workspace = true - -url.workspace = true log = { workspace = true } tokio = { features = ["rt-multi-thread"], workspace = true } -jsonrpsee.workspace = true -anyhow.workspace = true [build-dependencies] cbindgen = "0.29" diff --git a/indexer/ffi/build.rs b/indexer_ffi/build.rs similarity index 100% rename from indexer/ffi/build.rs rename to indexer_ffi/build.rs diff --git a/indexer/ffi/cbindgen.toml b/indexer_ffi/cbindgen.toml similarity index 100% rename from indexer/ffi/cbindgen.toml rename to indexer_ffi/cbindgen.toml diff --git a/indexer_ffi/indexer_ffi.h b/indexer_ffi/indexer_ffi.h new file mode 100644 index 00000000..7c7d9a4d --- /dev/null +++ b/indexer_ffi/indexer_ffi.h @@ -0,0 +1,76 @@ +#include +#include +#include +#include + +typedef enum OperationStatus { + Ok = 0, + NullPointer = 1, + InitializationError = 2, +} OperationStatus; + +typedef struct IndexerServiceFFI { + void *indexer_handle; + void *runtime; +} IndexerServiceFFI; + +/** + * Simple wrapper around a pointer to a value or an error. + * + * Pointer is not guaranteed. You should check the error field before + * dereferencing the pointer. + */ +typedef struct PointerResult_IndexerServiceFFI__OperationStatus { + struct IndexerServiceFFI *value; + enum OperationStatus error; +} PointerResult_IndexerServiceFFI__OperationStatus; + +typedef struct PointerResult_IndexerServiceFFI__OperationStatus InitializedIndexerServiceFFIResult; + +/** + * Creates and starts an indexer based on the provided + * configuration file path. + * + * # Arguments + * + * - `config_path`: A pointer to a string representing the path to the configuration file. + * - `port`: Number representing a port, on which indexers RPC will start. + * + * # Returns + * + * An `InitializedIndexerServiceFFIResult` containing either a pointer to the + * initialized `IndexerServiceFFI` or an error code. + */ +InitializedIndexerServiceFFIResult start_indexer(const char *config_path, uint16_t port); + +/** + * Stops and frees the resources associated with the given indexer service. + * + * # Arguments + * + * - `indexer`: A pointer to the `IndexerServiceFFI` instance to be stopped. + * + * # Returns + * + * An `OperationStatus` indicating success or failure. + * + * # Safety + * + * The caller must ensure that: + * - `indexer` is a valid pointer to a `IndexerServiceFFI` instance + * - The `IndexerServiceFFI` instance was created by this library + * - The pointer will not be used after this function returns + */ +enum OperationStatus stop_indexer(struct IndexerServiceFFI *indexer); + +/** + * # Safety + * It's up to the caller to pass a proper pointer, if somehow from c/c++ side + * this is called with a type which doesn't come from a returned `CString` it + * will cause a segfault. + */ +void free_cstring(char *block); + +bool is_ok(const enum OperationStatus *self); + +bool is_error(const enum OperationStatus *self); diff --git a/indexer/ffi/src/api/lifecycle.rs b/indexer_ffi/src/api/lifecycle.rs similarity index 84% rename from indexer/ffi/src/api/lifecycle.rs rename to indexer_ffi/src/api/lifecycle.rs index c9cd859d..735efd4d 100644 --- a/indexer/ffi/src/api/lifecycle.rs +++ b/indexer_ffi/src/api/lifecycle.rs @@ -2,15 +2,7 @@ use std::{ffi::c_char, path::PathBuf}; use tokio::runtime::Runtime; -use crate::{ - IndexerServiceFFI, - api::{ - PointerResult, - client::{UrlProtocol, addr_to_url}, - }, - client::{IndexerClient, IndexerClientTrait as _}, - errors::OperationStatus, -}; +use crate::{IndexerServiceFFI, api::PointerResult, errors::OperationStatus}; pub type InitializedIndexerServiceFFIResult = PointerResult; @@ -75,13 +67,7 @@ fn setup_indexer( OperationStatus::InitializationError })?; - let indexer_url = addr_to_url(UrlProtocol::Ws, indexer_handle.addr())?; - let indexer_client = rt.block_on(IndexerClient::new(&indexer_url)).map_err(|e| { - log::error!("Could not start indexer client: {e}"); - OperationStatus::InitializationError - })?; - - Ok(IndexerServiceFFI::new(indexer_handle, rt, indexer_client)) + Ok(IndexerServiceFFI::new(indexer_handle, rt)) } /// Stops and frees the resources associated with the given indexer service. diff --git a/indexer/ffi/src/api/memory.rs b/indexer_ffi/src/api/memory.rs similarity index 100% rename from indexer/ffi/src/api/memory.rs rename to indexer_ffi/src/api/memory.rs diff --git a/indexer/ffi/src/api/mod.rs b/indexer_ffi/src/api/mod.rs similarity index 64% rename from indexer/ffi/src/api/mod.rs rename to indexer_ffi/src/api/mod.rs index ea2b91d7..e84a3913 100644 --- a/indexer/ffi/src/api/mod.rs +++ b/indexer_ffi/src/api/mod.rs @@ -1,8 +1,5 @@ pub use result::PointerResult; -pub mod client; pub mod lifecycle; pub mod memory; -pub mod query; pub mod result; -pub mod types; diff --git a/indexer/ffi/src/api/result.rs b/indexer_ffi/src/api/result.rs similarity index 100% rename from indexer/ffi/src/api/result.rs rename to indexer_ffi/src/api/result.rs diff --git a/indexer/ffi/src/errors.rs b/indexer_ffi/src/errors.rs similarity index 94% rename from indexer/ffi/src/errors.rs rename to indexer_ffi/src/errors.rs index 4572474c..46aa0f9f 100644 --- a/indexer/ffi/src/errors.rs +++ b/indexer_ffi/src/errors.rs @@ -5,7 +5,6 @@ pub enum OperationStatus { Ok = 0x0, NullPointer = 0x1, InitializationError = 0x2, - ClientError = 0x3, } impl OperationStatus { diff --git a/indexer/ffi/src/indexer.rs b/indexer_ffi/src/indexer.rs similarity index 59% rename from indexer/ffi/src/indexer.rs rename to indexer_ffi/src/indexer.rs index 33800356..c110b183 100644 --- a/indexer/ffi/src/indexer.rs +++ b/indexer_ffi/src/indexer.rs @@ -3,26 +3,18 @@ use std::{ffi::c_void, net::SocketAddr}; use indexer_service::IndexerHandle; use tokio::runtime::Runtime; -use crate::client::IndexerClient; - #[repr(C)] pub struct IndexerServiceFFI { indexer_handle: *mut c_void, runtime: *mut c_void, - indexer_client: *mut c_void, } impl IndexerServiceFFI { - pub fn new( - indexer_handle: indexer_service::IndexerHandle, - runtime: Runtime, - indexer_client: IndexerClient, - ) -> Self { + pub fn new(indexer_handle: indexer_service::IndexerHandle, runtime: Runtime) -> Self { Self { // Box the complex types and convert to opaque pointers indexer_handle: Box::into_raw(Box::new(indexer_handle)).cast::(), runtime: Box::into_raw(Box::new(runtime)).cast::(), - indexer_client: Box::into_raw(Box::new(indexer_client)).cast::(), } } @@ -33,11 +25,10 @@ impl IndexerServiceFFI { /// The caller must ensure that: /// - `self` is a valid object(contains valid pointers in all fields) #[must_use] - pub unsafe fn into_parts(self) -> (Box, Box, Box) { + pub unsafe fn into_parts(self) -> (Box, Box) { let indexer_handle = unsafe { Box::from_raw(self.indexer_handle.cast::()) }; let runtime = unsafe { Box::from_raw(self.runtime.cast::()) }; - let indexer_client = unsafe { Box::from_raw(self.indexer_client.cast::()) }; - (indexer_handle, runtime, indexer_client) + (indexer_handle, runtime) } /// Helper to get indexer handle addr. @@ -58,7 +49,7 @@ impl IndexerServiceFFI { indexer_handle.addr() } - /// Helper to get indexer handle ref. + /// Helper to get indexer handle addr. /// /// # Safety /// @@ -73,38 +64,6 @@ impl IndexerServiceFFI { .expect("Indexer Handle must be non-null pointer") } } - - /// Helper to get indexer client ref. - /// - /// # Safety - /// - /// The caller must ensure that: - /// - `self` is a valid object(contains valid pointers in all fields) - #[must_use] - pub const unsafe fn client(&self) -> &IndexerClient { - unsafe { - self.indexer_client - .cast::() - .as_ref() - .expect("Indexer Client must be non-null pointer") - } - } - - /// Helper to get indexer runtime ref. - /// - /// # Safety - /// - /// The caller must ensure that: - /// - `self` is a valid object(contains valid pointers in all fields) - #[must_use] - pub const unsafe fn runtime(&self) -> &Runtime { - unsafe { - self.runtime - .cast::() - .as_ref() - .expect("Indexer Runtime must be non-null pointer") - } - } } // Implement Drop to prevent memory leaks @@ -113,7 +72,6 @@ impl Drop for IndexerServiceFFI { let Self { indexer_handle, runtime, - indexer_client, } = self; if indexer_handle.is_null() { @@ -122,11 +80,7 @@ impl Drop for IndexerServiceFFI { if runtime.is_null() { log::error!("Attempted to drop a null tokio runtime pointer. This is a bug"); } - if indexer_client.is_null() { - log::error!("Attempted to drop a null client pointer. This is a bug"); - } drop(unsafe { Box::from_raw(indexer_handle.cast::()) }); drop(unsafe { Box::from_raw(runtime.cast::()) }); - drop(unsafe { Box::from_raw(indexer_client.cast::()) }); } } diff --git a/indexer/ffi/src/lib.rs b/indexer_ffi/src/lib.rs similarity index 93% rename from indexer/ffi/src/lib.rs rename to indexer_ffi/src/lib.rs index 5806a074..fe594ec0 100644 --- a/indexer/ffi/src/lib.rs +++ b/indexer_ffi/src/lib.rs @@ -4,6 +4,5 @@ pub use errors::OperationStatus; pub use indexer::IndexerServiceFFI; pub mod api; -mod client; mod errors; mod indexer; diff --git a/integration_tests/Cargo.toml b/integration_tests/Cargo.toml index 5f1f1037..53f0ee98 100644 --- a/integration_tests/Cargo.toml +++ b/integration_tests/Cargo.toml @@ -19,13 +19,11 @@ indexer_service.workspace = true serde_json.workspace = true token_core.workspace = true ata_core.workspace = true -indexer_service_rpc = { workspace = true, features = ["client"] } +indexer_service_rpc.workspace = true sequencer_service_rpc = { workspace = true, features = ["client"] } -jsonrpsee = { workspace = true, features = ["ws-client"] } wallet-ffi.workspace = true indexer_ffi.workspace = true testnet_initial_state.workspace = true -indexer_service_protocol.workspace = true url.workspace = true @@ -37,4 +35,4 @@ hex.workspace = true tempfile.workspace = true bytesize.workspace = true futures.workspace = true -testcontainers = { version = "0.27.3", features = ["docker-compose"] } +testcontainers = { version = "0.27.0", features = ["docker-compose"] } diff --git a/integration_tests/src/config.rs b/integration_tests/src/config.rs index 5c381e0e..faff1e79 100644 --- a/integration_tests/src/config.rs +++ b/integration_tests/src/config.rs @@ -2,7 +2,7 @@ use std::{net::SocketAddr, path::PathBuf, time::Duration}; use anyhow::{Context as _, Result}; use bytesize::ByteSize; -use indexer_service::{ChannelId, ClientConfig, IndexerConfig}; +use indexer_service::{BackoffConfig, ChannelId, ClientConfig, IndexerConfig}; use key_protocol::key_management::KeyChain; use nssa::{Account, AccountId, PrivateKey, PublicKey}; use nssa_core::{account::Data, program::DEFAULT_PROGRAM_ID}; @@ -59,16 +59,12 @@ impl InitialData { } let mut private_charlie_key_chain = KeyChain::new_os_random(); - let mut private_charlie_account_id = AccountId::for_regular_private_account( - &private_charlie_key_chain.nullifier_public_key, - 0, - ); + let mut private_charlie_account_id = + AccountId::from((&private_charlie_key_chain.nullifier_public_key, 0)); let mut private_david_key_chain = KeyChain::new_os_random(); - let mut private_david_account_id = AccountId::for_regular_private_account( - &private_david_key_chain.nullifier_public_key, - 0, - ); + let mut private_david_account_id = + AccountId::from((&private_david_key_chain.nullifier_public_key, 0)); // Ensure consistent ordering if private_charlie_account_id > private_david_account_id { @@ -168,10 +164,35 @@ impl std::fmt::Display for UrlProtocol { } } +pub fn indexer_config( + bedrock_addr: SocketAddr, + home: PathBuf, + initial_data: &InitialData, +) -> Result { + Ok(IndexerConfig { + home, + consensus_info_polling_interval: Duration::from_secs(1), + bedrock_client_config: ClientConfig { + addr: addr_to_url(UrlProtocol::Http, bedrock_addr) + .context("Failed to convert bedrock addr to URL")?, + auth: None, + backoff: BackoffConfig { + start_delay: Duration::from_millis(100), + max_retries: 10, + }, + }, + initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()), + initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()), + signing_key: [37; 32], + channel_id: bedrock_channel_id(), + }) +} + pub fn sequencer_config( partial: SequencerPartialConfig, home: PathBuf, bedrock_addr: SocketAddr, + indexer_addr: SocketAddr, initial_data: &InitialData, ) -> Result { let SequencerPartialConfig { @@ -194,11 +215,17 @@ pub fn sequencer_config( initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()), signing_key: [37; 32], bedrock_config: BedrockConfig { + backoff: BackoffConfig { + start_delay: Duration::from_millis(100), + max_retries: 5, + }, channel_id: bedrock_channel_id(), node_url: addr_to_url(UrlProtocol::Http, bedrock_addr) .context("Failed to convert bedrock addr to URL")?, auth: None, }, + indexer_rpc_url: addr_to_url(UrlProtocol::Ws, indexer_addr) + .context("Failed to convert indexer addr to URL")?, }) } @@ -218,26 +245,6 @@ pub fn wallet_config( }) } -pub fn indexer_config( - bedrock_addr: SocketAddr, - home: PathBuf, - initial_data: &InitialData, -) -> Result { - Ok(IndexerConfig { - home, - consensus_info_polling_interval: Duration::from_secs(1), - bedrock_config: ClientConfig { - addr: addr_to_url(UrlProtocol::Http, bedrock_addr) - .context("Failed to convert bedrock addr to URL")?, - auth: None, - }, - initial_public_accounts: Some(initial_data.sequencer_initial_public_accounts()), - initial_private_accounts: Some(initial_data.sequencer_initial_private_accounts()), - signing_key: [37; 32], - channel_id: bedrock_channel_id(), - }) -} - pub fn addr_to_url(protocol: UrlProtocol, addr: SocketAddr) -> Result { // Convert 0.0.0.0 to 127.0.0.1 for client connections // When binding to port 0, the server binds to 0.0.0.0: diff --git a/integration_tests/src/indexer_client.rs b/integration_tests/src/indexer_client.rs deleted file mode 100644 index 5641d824..00000000 --- a/integration_tests/src/indexer_client.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Thin client wrapper for querying the indexer's JSON-RPC API in tests. -//! -//! The sequencer doesn't depend on the indexer at runtime — finalization comes -//! from zone-sdk events. This wrapper exists purely for test ergonomics so -//! integration tests can construct a single connection and call -//! `indexer_service_rpc::RpcClient` methods directly via `Deref`. - -use std::ops::Deref; - -use anyhow::{Context as _, Result}; -use jsonrpsee::ws_client::{WsClient, WsClientBuilder}; -use log::info; -use url::Url; - -pub struct IndexerClient(WsClient); - -impl IndexerClient { - pub async fn new(indexer_url: &Url) -> Result { - info!("Connecting to Indexer at {indexer_url}"); - let client = WsClientBuilder::default() - .build(indexer_url) - .await - .context("Failed to create websocket client")?; - Ok(Self(client)) - } -} - -impl Deref for IndexerClient { - type Target = WsClient; - - fn deref(&self) -> &Self::Target { - &self.0 - } -} diff --git a/integration_tests/src/lib.rs b/integration_tests/src/lib.rs index 7abd0897..fcae2c71 100644 --- a/integration_tests/src/lib.rs +++ b/integration_tests/src/lib.rs @@ -9,19 +9,16 @@ use indexer_service::IndexerHandle; use log::{debug, error}; use nssa::{AccountId, PrivacyPreservingTransaction}; use nssa_core::Commitment; +use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _}; use sequencer_service::SequencerHandle; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; use tempfile::TempDir; use testcontainers::compose::DockerCompose; use wallet::WalletCore; -use crate::{ - indexer_client::IndexerClient, - setup::{setup_bedrock_node, setup_indexer, setup_sequencer, setup_wallet}, -}; +use crate::setup::{setup_bedrock_node, setup_indexer, setup_sequencer, setup_wallet}; pub mod config; -pub mod indexer_client; pub mod setup; pub mod test_context_ffi; @@ -29,7 +26,6 @@ pub mod test_context_ffi; pub const TIME_TO_WAIT_FOR_BLOCK_SECONDS: u64 = 12; pub const NSSA_PROGRAM_FOR_TEST_DATA_CHANGER: &str = "data_changer.bin"; pub const NSSA_PROGRAM_FOR_TEST_NOOP: &str = "noop.bin"; -pub const NSSA_PROGRAM_FOR_TEST_PDA_FUND_SPEND_PROXY: &str = "pda_fund_spend_proxy.bin"; const BEDROCK_SERVICE_WITH_OPEN_PORT: &str = "logos-blockchain-node-0"; const BEDROCK_SERVICE_PORT: u16 = 18080; @@ -81,10 +77,14 @@ impl TestContext { .await .context("Failed to setup Indexer")?; - let (sequencer_handle, temp_sequencer_dir) = - setup_sequencer(sequencer_partial_config, bedrock_addr, &initial_data) - .await - .context("Failed to setup Sequencer")?; + let (sequencer_handle, temp_sequencer_dir) = setup_sequencer( + sequencer_partial_config, + bedrock_addr, + indexer_handle.addr(), + &initial_data, + ) + .await + .context("Failed to setup Sequencer")?; let (wallet, temp_wallet_dir, wallet_password) = setup_wallet(sequencer_handle.addr(), &initial_data) diff --git a/integration_tests/src/setup.rs b/integration_tests/src/setup.rs index 774c67e3..58b33c60 100644 --- a/integration_tests/src/setup.rs +++ b/integration_tests/src/setup.rs @@ -119,6 +119,7 @@ pub(crate) async fn setup_indexer( pub(crate) async fn setup_sequencer( partial: config::SequencerPartialConfig, bedrock_addr: SocketAddr, + indexer_addr: SocketAddr, initial_data: &config::InitialData, ) -> Result<(SequencerHandle, TempDir)> { let temp_sequencer_dir = @@ -133,6 +134,7 @@ pub(crate) async fn setup_sequencer( partial, temp_sequencer_dir.path().to_owned(), bedrock_addr, + indexer_addr, initial_data, ) .context("Failed to create Sequencer config")?; diff --git a/integration_tests/src/test_context_ffi.rs b/integration_tests/src/test_context_ffi.rs index d03a4e00..7d21aa28 100644 --- a/integration_tests/src/test_context_ffi.rs +++ b/integration_tests/src/test_context_ffi.rs @@ -6,6 +6,7 @@ use indexer_ffi::IndexerServiceFFI; use indexer_service_rpc::RpcClient as _; use log::{debug, error}; use nssa::AccountId; +use sequencer_core::indexer_client::{IndexerClient, IndexerClientTrait as _}; use sequencer_service::SequencerHandle; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; use tempfile::TempDir; @@ -14,7 +15,6 @@ use wallet::WalletCore; use crate::{ BEDROCK_SERVICE_WITH_OPEN_PORT, LOGGER, TestContextBuilder, config, - indexer_client::IndexerClient, setup::{setup_bedrock_node, setup_indexer_ffi, setup_sequencer, setup_wallet}, }; @@ -85,6 +85,8 @@ impl TestContextFFI { .block_on(setup_sequencer( sequencer_partial_config, bedrock_addr, + // SAFETY: addr is valid if indexer_ffi is valid. + unsafe { indexer_ffi.addr() }, initial_data, )) .context("Failed to setup Sequencer")?; @@ -266,11 +268,6 @@ impl BlockingTestContextFFI { pub fn runtime_clone(&self) -> Arc { Arc::::clone(&self.runtime) } - - #[must_use] - pub const fn indexer_ffi(&self) -> *const IndexerServiceFFI { - &raw const (self.indexer_ffi) - } } impl Drop for BlockingTestContextFFI { diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 9fbc174b..f08e3759 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -627,14 +627,14 @@ async fn shielded_transfers_to_two_identifiers_same_npk() -> Result<()> { .await?; // Both accounts must be discovered with the correct balances. - let account_id_1 = AccountId::for_regular_private_account(&npk, identifier_1); + let account_id_1 = AccountId::from((&npk, identifier_1)); let acc_1 = ctx .wallet() .get_account_private(account_id_1) .context("account for identifier 1 not found after sync")?; assert_eq!(acc_1.balance, 100); - let account_id_2 = AccountId::for_regular_private_account(&npk, identifier_2); + let account_id_2 = AccountId::from((&npk, identifier_2)); let acc_2 = ctx .wallet() .get_account_private(account_id_2) diff --git a/integration_tests/tests/indexer_ffi.rs b/integration_tests/tests/indexer_ffi.rs index ea0924d7..2730f9b5 100644 --- a/integration_tests/tests/indexer_ffi.rs +++ b/integration_tests/tests/indexer_ffi.rs @@ -1,18 +1,11 @@ #![expect( clippy::shadow_unrelated, clippy::tests_outside_test_module, - clippy::undocumented_unsafe_blocks, reason = "We don't care about these in tests" )] use anyhow::{Context as _, Result}; -use indexer_ffi::{ - IndexerServiceFFI, OperationStatus, - api::{ - PointerResult, - types::{FfiAccountId, FfiOption, FfiVec, account::FfiAccount, block::FfiBlock}, - }, -}; +use indexer_service_rpc::RpcClient as _; use integration_tests::{ TIME_TO_WAIT_FOR_BLOCK_SECONDS, format_private_account_id, format_public_account_id, test_context_ffi::BlockingTestContextFFI, verify_commitment_is_in_state, @@ -24,23 +17,6 @@ use wallet::cli::{Command, programs::native_token_transfer::AuthTransferSubcomma /// Maximum time to wait for the indexer to catch up to the sequencer. const L2_TO_L1_TIMEOUT_MILLIS: u64 = 180_000; -unsafe extern "C" { - unsafe fn query_last_block( - indexer: *const IndexerServiceFFI, - ) -> PointerResult; - - unsafe fn query_block_vec( - indexer: *const IndexerServiceFFI, - before: FfiOption, - limit: u64, - ) -> PointerResult, OperationStatus>; - - unsafe fn query_account( - indexer: *const IndexerServiceFFI, - account_id: FfiAccountId, - ) -> PointerResult; -} - #[test] fn indexer_test_run_ffi() -> Result<()> { let blocking_ctx = BlockingTestContextFFI::new()?; @@ -52,19 +28,10 @@ fn indexer_test_run_ffi() -> Result<()> { }); let last_block_indexer = blocking_ctx.ctx().get_last_block_indexer(runtime_wrapped)?; - let last_block_indexer_ffi_res = unsafe { query_last_block(blocking_ctx.indexer_ffi()) }; - - assert!(last_block_indexer_ffi_res.error.is_ok()); - - let last_block_indexer_ffi = unsafe { *last_block_indexer_ffi_res.value }; info!("Last block on ind now is {last_block_indexer}"); - info!("Last block on ind ffi now is {last_block_indexer_ffi}"); assert!(last_block_indexer > 1); - assert!(last_block_indexer_ffi > 1); - - assert_eq!(last_block_indexer, last_block_indexer_ffi); Ok(()) } @@ -73,6 +40,7 @@ fn indexer_test_run_ffi() -> Result<()> { fn indexer_ffi_block_batching() -> Result<()> { let blocking_ctx = BlockingTestContextFFI::new()?; let runtime_wrapped = blocking_ctx.runtime(); + let ctx = blocking_ctx.ctx(); // WAIT info!("Waiting for indexer to parse blocks"); @@ -80,36 +48,31 @@ fn indexer_ffi_block_batching() -> Result<()> { tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; }); - let last_block_indexer_ffi_res = unsafe { query_last_block(blocking_ctx.indexer_ffi()) }; - - assert!(last_block_indexer_ffi_res.error.is_ok()); - - let last_block_indexer = unsafe { *last_block_indexer_ffi_res.value }; + let last_block_indexer = runtime_wrapped + .block_on(ctx.indexer_client().get_last_finalized_block_id()) + .unwrap(); info!("Last block on ind now is {last_block_indexer}"); assert!(last_block_indexer > 1); - let before_ffi = FfiOption::::from_none(); - let limit = 100; + // Getting wide batch to fit all blocks (from latest backwards) + let mut block_batch = runtime_wrapped + .block_on(ctx.indexer_client().get_blocks(None, 100)) + .unwrap(); - let block_batch_ffi_res = - unsafe { query_block_vec(blocking_ctx.indexer_ffi(), before_ffi, limit) }; + // Reverse to check chain consistency from oldest to newest + block_batch.reverse(); - assert!(block_batch_ffi_res.error.is_ok()); + // Checking chain consistency + let mut prev_block_hash = block_batch.first().unwrap().header.hash; - let block_batch = unsafe { &*block_batch_ffi_res.value }; - - let mut last_block_prev_hash = unsafe { block_batch.get(0) }.header.prev_block_hash.data; - - for i in 1..block_batch.len { - let block = unsafe { block_batch.get(i) }; - - assert_eq!(last_block_prev_hash, block.header.hash.data); + for block in &block_batch[1..] { + assert_eq!(block.header.prev_block_hash, prev_block_hash); info!("Block {} chain-consistent", block.header.block_id); - last_block_prev_hash = block.header.prev_block_hash.data; + prev_block_hash = block.header.hash; } Ok(()) @@ -119,7 +82,6 @@ fn indexer_ffi_block_batching() -> Result<()> { fn indexer_ffi_state_consistency() -> Result<()> { let mut blocking_ctx = BlockingTestContextFFI::new()?; let runtime_wrapped = blocking_ctx.runtime_clone(); - let indexer_ffi = blocking_ctx.indexer_ffi(); let ctx = blocking_ctx.ctx_mut(); let command = Command::AuthTransfer(AuthTransferSubcommand::Send { @@ -217,21 +179,14 @@ fn indexer_ffi_state_consistency() -> Result<()> { tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; }); - let acc1_ind_state_ffi = - unsafe { query_account(indexer_ffi, (&ctx.existing_public_accounts()[0]).into()) }; - - assert!(acc1_ind_state_ffi.error.is_ok()); - - let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; - let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); - - let acc2_ind_state_ffi = - unsafe { query_account(indexer_ffi, (&ctx.existing_public_accounts()[1]).into()) }; - - assert!(acc2_ind_state_ffi.error.is_ok()); - - let acc2_ind_state_pre = unsafe { &*acc2_ind_state_ffi.value }; - let acc2_ind_state: indexer_service_protocol::Account = acc2_ind_state_pre.into(); + let acc1_ind_state = runtime_wrapped.block_on( + ctx.indexer_client() + .get_account(ctx.existing_public_accounts()[0].into()), + )?; + let acc2_ind_state = runtime_wrapped.block_on( + ctx.indexer_client() + .get_account(ctx.existing_public_accounts()[1].into()), + )?; info!("Checking correct state transition"); let acc1_seq_state = @@ -257,7 +212,6 @@ fn indexer_ffi_state_consistency() -> Result<()> { fn indexer_ffi_state_consistency_with_labels() -> Result<()> { let mut blocking_ctx = BlockingTestContextFFI::new()?; let runtime_wrapped = blocking_ctx.runtime_clone(); - let indexer_ffi = blocking_ctx.indexer_ffi(); let ctx = blocking_ctx.ctx_mut(); // Assign labels to both accounts @@ -321,14 +275,10 @@ fn indexer_ffi_state_consistency_with_labels() -> Result<()> { tokio::time::sleep(std::time::Duration::from_millis(L2_TO_L1_TIMEOUT_MILLIS)).await; }); - let acc1_ind_state_ffi = - unsafe { query_account(indexer_ffi, (&ctx.existing_public_accounts()[0]).into()) }; - - assert!(acc1_ind_state_ffi.error.is_ok()); - - let acc1_ind_state_pre = unsafe { &*acc1_ind_state_ffi.value }; - let acc1_ind_state: indexer_service_protocol::Account = acc1_ind_state_pre.into(); - + let acc1_ind_state = runtime_wrapped.block_on( + ctx.indexer_client() + .get_account(ctx.existing_public_accounts()[0].into()), + )?; let acc1_seq_state = runtime_wrapped.block_on(sequencer_service_rpc::RpcClient::get_account( ctx.sequencer_client(), diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs deleted file mode 100644 index 518239e7..00000000 --- a/integration_tests/tests/private_pda.rs +++ /dev/null @@ -1,309 +0,0 @@ -#![expect( - clippy::tests_outside_test_module, - reason = "We don't care about these in tests" -)] - -use std::{path::PathBuf, time::Duration}; - -use anyhow::{Context as _, Result}; -use integration_tests::{ - NSSA_PROGRAM_FOR_TEST_PDA_FUND_SPEND_PROXY, TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, - verify_commitment_is_in_state, -}; -use log::info; -use nssa::{ - AccountId, ProgramId, privacy_preserving_transaction::circuit::ProgramWithDependencies, - program::Program, -}; -use nssa_core::{NullifierPublicKey, encryption::ViewingPublicKey, program::PdaSeed}; -use tokio::test; -use wallet::{ - PrivacyPreservingAccount, WalletCore, - cli::{Command, account::AccountSubcommand}, -}; - -/// Funds a private PDA via the proxy program with a chained call to `auth_transfer`. -/// -/// A direct call to `auth_transfer` cannot establish the PDA-to-npk binding because it uses -/// `Claim::Authorized` rather than `Claim::Pda`. Routing through the proxy provides the binding -/// via `pda_seeds` in the chained call to `auth_transfer`. -#[expect( - clippy::too_many_arguments, - reason = "test helper — grouping args would obscure intent" -)] -async fn fund_private_pda( - wallet: &WalletCore, - sender: AccountId, - pda_account_id: AccountId, - npk: NullifierPublicKey, - vpk: ViewingPublicKey, - identifier: u128, - seed: PdaSeed, - amount: u128, - proxy_program: &ProgramWithDependencies, - auth_transfer_id: ProgramId, -) -> Result<()> { - wallet - .send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::Public(sender), - PrivacyPreservingAccount::PrivatePdaForeign { - account_id: pda_account_id, - npk, - vpk, - identifier, - }, - ], - Program::serialize_instruction((seed, amount, auth_transfer_id, true)) - .context("failed to serialize pda_fund_spend_proxy fund instruction")?, - proxy_program, - ) - .await - .map_err(|e| anyhow::anyhow!("{e}"))?; - Ok(()) -} - -/// Spends from an owned private PDA to a fresh private-foreign recipient. -/// -/// Alice must own the PDA in the wallet (i.e. it must have been synced after a receive). -#[expect( - clippy::too_many_arguments, - reason = "test helper — grouping args would obscure intent" -)] -async fn spend_private_pda( - wallet: &WalletCore, - pda_account_id: AccountId, - recipient_npk: NullifierPublicKey, - recipient_vpk: ViewingPublicKey, - seed: PdaSeed, - amount: u128, - spend_program: &ProgramWithDependencies, - auth_transfer_id: nssa::ProgramId, -) -> Result<()> { - wallet - .send_privacy_preserving_tx( - vec![ - PrivacyPreservingAccount::PrivatePdaOwned(pda_account_id), - PrivacyPreservingAccount::PrivateForeign { - npk: recipient_npk, - vpk: recipient_vpk, - identifier: 0, - }, - ], - Program::serialize_instruction((seed, amount, auth_transfer_id, false)) - .context("failed to serialize pda_fund_spend_proxy instruction")?, - spend_program, - ) - .await - .map_err(|e| anyhow::anyhow!("{e}"))?; - Ok(()) -} - -/// Two private transfers go to distinct members of the same PDA family (same seed and npk, -/// but identifier=0 and identifier=1). Alice then spends from both PDAs. -/// -/// This exercises the full identifier-diversified private PDA lifecycle: -/// receive(id=0), receive(id=1) → sync → spend(id=0), spend(id=1) → sync → assert. -#[test] -async fn private_pda_family_members_receive_and_spend() -> Result<()> { - let mut ctx = TestContext::new().await?; - - // ── Build alice's key chain ────────────────────────────────────────────────────────────────── - let alice_chain_index = ctx.wallet_mut().create_private_accounts_key(None); - let (alice_npk, alice_vpk) = { - let node = ctx - .wallet() - .storage() - .user_data - .private_key_tree - .key_map - .get(&alice_chain_index) - .context("key node was just inserted")?; - let kc = &node.value.0; - (kc.nullifier_public_key, kc.viewing_public_key.clone()) - }; - - let proxy = { - let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) - .join("../artifacts/test_program_methods") - .join(NSSA_PROGRAM_FOR_TEST_PDA_FUND_SPEND_PROXY); - Program::new(std::fs::read(&path).with_context(|| format!("reading {path:?}"))?) - .context("invalid pda_fund_spend_proxy binary")? - }; - let auth_transfer = Program::authenticated_transfer_program(); - let proxy_id = proxy.id(); - let auth_transfer_id = auth_transfer.id(); - let seed = PdaSeed::new([42; 32]); - let amount: u128 = 100; - - let spend_program = - ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer)].into()); - - let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0); - let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1); - - // Use two different public senders to avoid nonce conflicts between the back-to-back txs. - let senders = ctx.existing_public_accounts(); - let sender_0 = senders[0]; - let sender_1 = senders[1]; - - // ── Receive ────────────────────────────────────────────────────────────────────────────────── - - info!("Sending to alice_pda_0 (identifier=0)"); - fund_private_pda( - ctx.wallet(), - sender_0, - alice_pda_0_id, - alice_npk, - alice_vpk.clone(), - 0, - seed, - amount, - &spend_program, - auth_transfer_id, - ) - .await?; - - info!("Sending to alice_pda_1 (identifier=1)"); - fund_private_pda( - ctx.wallet(), - sender_1, - alice_pda_1_id, - alice_npk, - alice_vpk.clone(), - 1, - seed, - amount, - &spend_program, - auth_transfer_id, - ) - .await?; - - info!("Waiting for block"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync so alice's wallet discovers and stores both PDAs. - wallet::cli::execute_subcommand( - ctx.wallet_mut(), - Command::Account(AccountSubcommand::SyncPrivate {}), - ) - .await?; - - // Both PDAs must be discoverable and have the correct balance. - let pda_0_account = ctx - .wallet() - .get_account_private(alice_pda_0_id) - .context("alice_pda_0 not found after sync")?; - assert_eq!(pda_0_account.balance, amount); - - let pda_1_account = ctx - .wallet() - .get_account_private(alice_pda_1_id) - .context("alice_pda_1 not found after sync")?; - assert_eq!(pda_1_account.balance, amount); - - // Commitments for both PDAs must be in the sequencer's state. - let commitment_0 = ctx - .wallet() - .get_private_account_commitment(alice_pda_0_id) - .context("commitment for alice_pda_0 missing")?; - assert!( - verify_commitment_is_in_state(commitment_0.clone(), ctx.sequencer_client()).await, - "alice_pda_0 commitment not in state after receive" - ); - - let commitment_1 = ctx - .wallet() - .get_private_account_commitment(alice_pda_1_id) - .context("commitment for alice_pda_1 missing")?; - assert!( - verify_commitment_is_in_state(commitment_1.clone(), ctx.sequencer_client()).await, - "alice_pda_1 commitment not in state after receive" - ); - assert_ne!( - commitment_0, commitment_1, - "distinct identifiers must yield distinct commitments" - ); - - // ── Spend ───────────────────────────────────────────────────────────────────────────────────── - - // Fresh recipients — hardcoded npks not in any wallet. - let recipient_npk_0 = NullifierPublicKey([0xAA; 32]); - let recipient_vpk_0 = ViewingPublicKey::from_scalar(recipient_npk_0.0); - - let recipient_npk_1 = NullifierPublicKey([0xBB; 32]); - let recipient_vpk_1 = ViewingPublicKey::from_scalar(recipient_npk_1.0); - - let amount_spend_0: u128 = 13; - let amount_spend_1: u128 = 37; - - info!("Alice spending from alice_pda_0"); - spend_private_pda( - ctx.wallet(), - alice_pda_0_id, - recipient_npk_0, - recipient_vpk_0, - seed, - amount_spend_0, - &spend_program, - auth_transfer_id, - ) - .await?; - - info!("Alice spending from alice_pda_1"); - spend_private_pda( - ctx.wallet(), - alice_pda_1_id, - recipient_npk_1, - recipient_vpk_1, - seed, - amount_spend_1, - &spend_program, - auth_transfer_id, - ) - .await?; - - info!("Waiting for block"); - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - wallet::cli::execute_subcommand( - ctx.wallet_mut(), - Command::Account(AccountSubcommand::SyncPrivate {}), - ) - .await?; - - // After spending, PDAs should have the remaining balance. - let pda_0_spent = ctx - .wallet() - .get_account_private(alice_pda_0_id) - .context("alice_pda_0 not found after spend sync")?; - assert_eq!(pda_0_spent.balance, amount - amount_spend_0); - - let pda_1_spent = ctx - .wallet() - .get_account_private(alice_pda_1_id) - .context("alice_pda_1 not found after spend sync")?; - assert_eq!(pda_1_spent.balance, amount - amount_spend_1); - - // Post-spend commitments must be in state. - let post_spend_commitment_0 = ctx - .wallet() - .get_private_account_commitment(alice_pda_0_id) - .context("post-spend commitment for alice_pda_0 missing")?; - assert!( - verify_commitment_is_in_state(post_spend_commitment_0, ctx.sequencer_client()).await, - "alice_pda_0 post-spend commitment not in state" - ); - - let post_spend_commitment_1 = ctx - .wallet() - .get_private_account_commitment(alice_pda_1_id) - .context("post-spend commitment for alice_pda_1 missing")?; - assert!( - verify_commitment_is_in_state(post_spend_commitment_1, ctx.sequencer_client()).await, - "alice_pda_1 post-spend commitment not in state" - ); - - info!("Private PDA family member receive-and-spend test passed"); - Ok(()) -} diff --git a/integration_tests/tests/shared_accounts.rs b/integration_tests/tests/shared_accounts.rs deleted file mode 100644 index 80ea1bd4..00000000 --- a/integration_tests/tests/shared_accounts.rs +++ /dev/null @@ -1,234 +0,0 @@ -#![expect( - clippy::tests_outside_test_module, - reason = "Integration test file, not inside a #[cfg(test)] module" -)] -#![expect( - clippy::shadow_unrelated, - reason = "Sequential wallet commands naturally reuse the `command` binding" -)] - -//! Shared account integration tests. -//! -//! Demonstrates: -//! 1. Group creation and GMS distribution via seal/unseal. -//! 2. Shared regular private account creation via `--for-gms`. -//! 3. Funding a shared account from a public account. -//! 4. Syncing discovers the funded shared account state. - -use std::time::Duration; - -use anyhow::{Context as _, Result}; -use integration_tests::{TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, format_public_account_id}; -use log::info; -use tokio::test; -use wallet::cli::{ - Command, SubcommandReturnValue, - account::{AccountSubcommand, NewSubcommand}, - group::GroupSubcommand, - programs::native_token_transfer::AuthTransferSubcommand, -}; - -/// Create a group, create a shared account from it, and verify registration. -#[test] -async fn group_create_and_shared_account_registration() -> Result<()> { - let mut ctx = TestContext::new().await?; - - // Create a group - let command = Command::Group(GroupSubcommand::New { - name: "test-group".into(), - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - // Verify group exists - assert!( - ctx.wallet() - .storage() - .user_data - .group_key_holder("test-group") - .is_some() - ); - - // Create a shared regular private account from the group - let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateGms { - group: "test-group".into(), - label: Some("shared-acc".into()), - pda: false, - seed: None, - program_id: None, - identifier: None, - })); - - let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - let SubcommandReturnValue::RegisterAccount { - account_id: shared_account_id, - } = result - else { - anyhow::bail!("Expected RegisterAccount return value"); - }; - - // Verify shared account is registered in storage - let entry = ctx - .wallet() - .storage() - .user_data - .shared_private_account(&shared_account_id) - .context("Shared account not found in storage")?; - assert_eq!(entry.group_label, "test-group"); - assert!(entry.pda_seed.is_none()); - - info!("Shared account registered: {shared_account_id}"); - Ok(()) -} - -/// GMS seal/unseal round-trip via invite/join, verify key agreement. -#[test] -async fn group_invite_join_key_agreement() -> Result<()> { - let mut ctx = TestContext::new().await?; - - // Generate a sealing key - let command = Command::Group(GroupSubcommand::NewSealingKey); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - // Create a group - let command = Command::Group(GroupSubcommand::New { - name: "alice-group".into(), - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - // Seal GMS for ourselves (simulating invite to another wallet) - let sealing_sk = ctx - .wallet() - .storage() - .user_data - .sealing_secret_key - .context("Sealing key not found")?; - let sealing_pk = - key_protocol::key_management::group_key_holder::SealingPublicKey::from_scalar(sealing_sk); - - let holder = ctx - .wallet() - .storage() - .user_data - .group_key_holder("alice-group") - .context("Group not found")?; - let sealed = holder.seal_for(&sealing_pk); - let sealed_hex = hex::encode(&sealed); - - // Join under a different name (simulating Bob receiving the sealed GMS) - let command = Command::Group(GroupSubcommand::Join { - name: "bob-copy".into(), - sealed: sealed_hex, - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - // Both derive the same keys for the same derivation seed - let alice_holder = ctx - .wallet() - .storage() - .user_data - .group_key_holder("alice-group") - .unwrap(); - let bob_holder = ctx - .wallet() - .storage() - .user_data - .group_key_holder("bob-copy") - .unwrap(); - - let seed = [42_u8; 32]; - let alice_npk = alice_holder - .derive_keys_for_shared_account(&seed) - .generate_nullifier_public_key(); - let bob_npk = bob_holder - .derive_keys_for_shared_account(&seed) - .generate_nullifier_public_key(); - - assert_eq!( - alice_npk, bob_npk, - "Key agreement: same GMS produces same keys" - ); - - info!("Key agreement verified via invite/join"); - Ok(()) -} - -/// Fund a shared account from a public account via auth-transfer, then sync. -/// TODO: Requires auth-transfer init to work with shared accounts (authorization flow). -#[test] -async fn fund_shared_account_from_public() -> Result<()> { - let mut ctx = TestContext::new().await?; - - // Create group and shared account - let command = Command::Group(GroupSubcommand::New { - name: "fund-group".into(), - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - let command = Command::Account(AccountSubcommand::New(NewSubcommand::PrivateGms { - group: "fund-group".into(), - label: None, - pda: false, - seed: None, - program_id: None, - identifier: None, - })); - let result = wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - let SubcommandReturnValue::RegisterAccount { - account_id: shared_id, - } = result - else { - anyhow::bail!("Expected RegisterAccount return value"); - }; - - // Initialize the shared account under auth-transfer - let command = Command::AuthTransfer(AuthTransferSubcommand::Init { - account_id: Some(format!("Private/{shared_id}")), - account_label: None, - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync private accounts - let command = Command::Account(AccountSubcommand::SyncPrivate); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - // Fund from a public account - let from_public = ctx.existing_public_accounts()[0]; - let command = Command::AuthTransfer(AuthTransferSubcommand::Send { - from: Some(format_public_account_id(from_public)), - from_label: None, - to: Some(format!("Private/{shared_id}")), - to_label: None, - to_npk: None, - to_vpk: None, - to_identifier: None, - amount: 100, - }); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; - - // Sync private accounts - let command = Command::Account(AccountSubcommand::SyncPrivate); - wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; - - // Verify the shared account was updated - let entry = ctx - .wallet() - .storage() - .user_data - .shared_private_account(&shared_id) - .context("Shared account not found after sync")?; - - info!( - "Shared account balance after funding: {}", - entry.account.balance - ); - assert_eq!( - entry.account.balance, 100, - "Shared account should have received 100" - ); - - Ok(()) -} diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 1f132932..41de30ed 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -27,7 +27,7 @@ use nssa::{ public_transaction as putx, }; use nssa_core::{ - InputAccountIdentity, MembershipProof, NullifierPublicKey, + MembershipProof, NullifierPublicKey, account::{AccountWithMetadata, Nonce, data::Data}, encryption::ViewingPublicKey, }; @@ -220,7 +220,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { data: Data::default(), }, true, - AccountId::for_regular_private_account(&sender_npk, 0), + AccountId::from((&sender_npk, 0)), ); let recipient_nsk = [2; 32]; let recipient_vsk = [99; 32]; @@ -229,7 +229,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { let recipient_pre = AccountWithMetadata::new( Account::default(), false, - AccountId::for_regular_private_account(&recipient_npk, 0), + AccountId::from((&recipient_npk, 0)), ); let eph_holder_from = EphemeralKeyHolder::new(&sender_npk); @@ -251,19 +251,10 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(balance_to_move).unwrap(), - vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: sender_ss, - nsk: sender_nsk, - membership_proof: proof, - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_npk, - ssk: recipient_ss, - identifier: 0, - }, - ], + vec![1, 2], + vec![(sender_npk, 0, sender_ss), (recipient_npk, 0, recipient_ss)], + vec![sender_nsk], + vec![Some(proof)], &program.into(), ) .unwrap(); diff --git a/integration_tests/tests/wallet_ffi.rs b/integration_tests/tests/wallet_ffi.rs index a0904a9a..db84b066 100644 --- a/integration_tests/tests/wallet_ffi.rs +++ b/integration_tests/tests/wallet_ffi.rs @@ -801,7 +801,7 @@ fn test_wallet_ffi_transfer_shielded() -> Result<()> { let (to, to_keys) = unsafe { let mut out_keys = FfiPrivateAccountKeys::default(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys); - let account_id = nssa::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); + let account_id = nssa::AccountId::from((&out_keys.npk(), 0_u128)); let to: FfiBytes32 = (&account_id).into(); (to, out_keys) }; @@ -935,7 +935,7 @@ fn test_wallet_ffi_transfer_private() -> Result<()> { let (to, to_keys) = unsafe { let mut out_keys = FfiPrivateAccountKeys::default(); wallet_ffi_create_private_accounts_key(wallet_ffi_handle, &raw mut out_keys); - let account_id = nssa::AccountId::for_regular_private_account(&out_keys.npk(), 0_u128); + let account_id = nssa::AccountId::from((&out_keys.npk(), 0_u128)); let to: FfiBytes32 = (&account_id).into(); (to, out_keys) }; diff --git a/key_protocol/Cargo.toml b/key_protocol/Cargo.toml index 72829ca8..022f3ccd 100644 --- a/key_protocol/Cargo.toml +++ b/key_protocol/Cargo.toml @@ -26,4 +26,3 @@ itertools.workspace = true [dev-dependencies] base58.workspace = true -bincode.workspace = true diff --git a/key_protocol/src/key_management/group_key_holder.rs b/key_protocol/src/key_management/group_key_holder.rs deleted file mode 100644 index 8bc9ed13..00000000 --- a/key_protocol/src/key_management/group_key_holder.rs +++ /dev/null @@ -1,601 +0,0 @@ -use aes_gcm::{Aes256Gcm, KeyInit as _, aead::Aead as _}; -use nssa_core::{ - SharedSecretKey, - encryption::{Scalar, shared_key_derivation::Secp256k1Point}, - program::{PdaSeed, ProgramId}, -}; -use rand::{RngCore as _, rngs::OsRng}; -use serde::{Deserialize, Serialize}; -use sha2::{Digest as _, digest::FixedOutput as _}; - -use super::secret_holders::{PrivateKeyHolder, SecretSpendingKey}; - -/// Public key used to seal a `GroupKeyHolder` for distribution to a recipient. -/// -/// Wraps a secp256k1 point but is a distinct type from `ViewingPublicKey` to enforce -/// key separation: viewing keys encrypt account state, sealing keys encrypt the GMS -/// for off-chain distribution. -pub struct SealingPublicKey(Secp256k1Point); - -impl SealingPublicKey { - /// Derive the sealing public key from a secret scalar. - #[must_use] - pub fn from_scalar(scalar: Scalar) -> Self { - Self(Secp256k1Point::from_scalar(scalar)) - } - - /// Construct from raw serialized bytes (e.g. received from another wallet). - #[must_use] - pub const fn from_bytes(bytes: Vec) -> Self { - Self(Secp256k1Point(bytes)) - } - - /// Returns the raw bytes for display or transmission. - #[must_use] - pub fn to_bytes(&self) -> &[u8] { - &self.0.0 - } -} - -/// Secret key used to unseal a `GroupKeyHolder` received from another member. -pub type SealingSecretKey = Scalar; - -/// Manages shared viewing keys for a group of controllers owning private PDAs. -/// -/// The Group Master Secret (GMS) is a 32-byte random value shared among controllers. -/// Each private PDA owned by the group gets a unique [`SecretSpendingKey`] derived from -/// the GMS by mixing the PDA seed into the SHA-256 input (see `secret_spending_key_for_pda`). -/// -/// # Distribution -/// -/// The GMS is a long-term secret and must never cross a trust boundary in raw form. -/// Controllers share it off-chain by sealing it under each recipient's [`SealingPublicKey`] -/// (see `seal_for` / `unseal`). Wallets persisting a `GroupKeyHolder` must encrypt it at -/// rest; the raw bytes are exposed only via [`GroupKeyHolder::dangerous_raw_gms`], which -/// is intended for the sealing path exclusively. -/// -/// # Logging safety -/// -/// `Debug` is implemented manually to redact the GMS; formatting this value with `{:?}` -/// will not leak the secret. Code that formats through `{:#?}` on containing types is -/// safe for the same reason. -#[derive(Serialize, Deserialize, Clone)] -pub struct GroupKeyHolder { - gms: [u8; 32], -} - -impl std::fmt::Debug for GroupKeyHolder { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("GroupKeyHolder") - .field("gms", &"") - .finish() - } -} - -impl Default for GroupKeyHolder { - fn default() -> Self { - Self::new() - } -} - -impl GroupKeyHolder { - /// Create a new group with a fresh random GMS. - #[must_use] - pub fn new() -> Self { - let mut gms = [0_u8; 32]; - OsRng.fill_bytes(&mut gms); - Self { gms } - } - - /// Restore from an existing GMS (received via `unseal`). - #[must_use] - pub const fn from_gms(gms: [u8; 32]) -> Self { - Self { gms } - } - - /// Returns the raw 32-byte GMS. The name reflects intent: only the sealed-distribution - /// path (`seal_for`) and sealed-at-rest persistence should ever need the raw bytes. Do - /// not log the result, do not pass it across an untrusted channel. - #[must_use] - pub const fn dangerous_raw_gms(&self) -> &[u8; 32] { - &self.gms - } - - /// Derive a per-PDA [`SecretSpendingKey`] by mixing the seed into the SHA-256 input. - /// - /// Each distinct `(program_id, pda_seed)` pair produces a distinct SSK in the full 256-bit - /// space, so adversarial seed-grinding cannot collide two PDAs' derived keys under the same - /// group. Uses the codebase's 32-byte protocol-versioned domain-separation convention. - fn secret_spending_key_for_pda( - &self, - program_id: &ProgramId, - pda_seed: &PdaSeed, - ) -> SecretSpendingKey { - const PREFIX: &[u8; 32] = b"/LEE/v0.3/GroupKeyDerivation/SSK"; - let mut hasher = sha2::Sha256::new(); - hasher.update(PREFIX); - hasher.update(self.gms); - for word in program_id { - hasher.update(word.to_le_bytes()); - } - hasher.update(pda_seed.as_ref()); - SecretSpendingKey(hasher.finalize_fixed().into()) - } - - /// Derive keys for a specific PDA under a given program. - /// - /// All controllers holding the same GMS independently derive the same keys for the - /// same `(program_id, seed)` because the derivation is deterministic. - #[must_use] - pub fn derive_keys_for_pda( - &self, - program_id: &ProgramId, - pda_seed: &PdaSeed, - ) -> PrivateKeyHolder { - self.secret_spending_key_for_pda(program_id, pda_seed) - .produce_private_key_holder(None) - } - - /// Derive keys for a shared regular (non-PDA) private account. - /// - /// Uses a distinct domain separator from `derive_keys_for_pda` to prevent cross-domain - /// key collisions. The `derivation_seed` should be a stable, unique 32-byte value - /// (e.g. derived deterministically from the account's identifier). - #[must_use] - pub fn derive_keys_for_shared_account(&self, derivation_seed: &[u8; 32]) -> PrivateKeyHolder { - const PREFIX: &[u8; 32] = b"/LEE/v0.3/GroupKeyDerivation/SHA"; - let mut hasher = sha2::Sha256::new(); - hasher.update(PREFIX); - hasher.update(self.gms); - hasher.update(derivation_seed); - SecretSpendingKey(hasher.finalize_fixed().into()).produce_private_key_holder(None) - } - - /// Encrypts this holder's GMS under the recipient's [`SealingPublicKey`]. - /// - /// Uses an ephemeral ECDH key exchange to derive a shared secret, then AES-256-GCM - /// to encrypt the payload. The returned bytes are - /// `ephemeral_pubkey (33) || nonce (12) || ciphertext+tag (48)` = 93 bytes. - /// - /// Each call generates a fresh ephemeral key, so two seals of the same holder produce - /// different ciphertexts. - #[must_use] - pub fn seal_for(&self, recipient_key: &SealingPublicKey) -> Vec { - let mut ephemeral_scalar: Scalar = [0_u8; 32]; - OsRng.fill_bytes(&mut ephemeral_scalar); - let ephemeral_pubkey = Secp256k1Point::from_scalar(ephemeral_scalar); - let shared = SharedSecretKey::new(&ephemeral_scalar, &recipient_key.0); - let aes_key = Self::seal_kdf(&shared); - let cipher = Aes256Gcm::new(&aes_key.into()); - - let mut nonce_bytes = [0_u8; 12]; - OsRng.fill_bytes(&mut nonce_bytes); - let nonce = aes_gcm::Nonce::from(nonce_bytes); - - let ciphertext = cipher - .encrypt(&nonce, self.gms.as_ref()) - .expect("AES-GCM encryption should not fail with valid key/nonce"); - - let capacity = 33_usize - .checked_add(12) - .and_then(|n| n.checked_add(ciphertext.len())) - .expect("seal capacity overflow"); - let mut out = Vec::with_capacity(capacity); - out.extend_from_slice(&ephemeral_pubkey.0); - out.extend_from_slice(&nonce_bytes); - out.extend_from_slice(&ciphertext); - out - } - - /// Decrypts a sealed `GroupKeyHolder` using the recipient's [`SealingSecretKey`]. - /// - /// Returns `Err` if the ciphertext is too short, the ECDH point is invalid, or the - /// AES-GCM authentication tag doesn't verify (wrong key or tampered data). - pub fn unseal(sealed: &[u8], own_key: &SealingSecretKey) -> Result { - const HEADER_LEN: usize = 33 + 12; - const MIN_LEN: usize = HEADER_LEN + 16; - if sealed.len() < MIN_LEN { - return Err(SealError::TooShort); - } - // MIN_LEN (61) > HEADER_LEN (45), so all slicing below is in bounds. - let ephemeral_pubkey = Secp256k1Point(sealed[..33].to_vec()); - let nonce = aes_gcm::Nonce::from_slice(&sealed[33..HEADER_LEN]); - let ciphertext = &sealed[HEADER_LEN..]; - - let shared = SharedSecretKey::new(own_key, &ephemeral_pubkey); - let aes_key = Self::seal_kdf(&shared); - let cipher = Aes256Gcm::new(&aes_key.into()); - - let plaintext = cipher - .decrypt(nonce, ciphertext) - .map_err(|_err| SealError::DecryptionFailed)?; - - if plaintext.len() != 32 { - return Err(SealError::DecryptionFailed); - } - - let mut gms = [0_u8; 32]; - gms.copy_from_slice(&plaintext); - Ok(Self::from_gms(gms)) - } - - /// Derives an AES-256 key from the ECDH shared secret via SHA-256 with a domain prefix. - fn seal_kdf(shared: &SharedSecretKey) -> [u8; 32] { - const PREFIX: &[u8; 32] = b"/LEE/v0.3/GroupKeySeal/AES\x00\x00\x00\x00\x00\x00"; - let mut hasher = sha2::Sha256::new(); - hasher.update(PREFIX); - hasher.update(shared.0); - hasher.finalize_fixed().into() - } -} - -#[derive(Debug)] -pub enum SealError { - TooShort, - DecryptionFailed, -} - -#[cfg(test)] -mod tests { - use nssa_core::NullifierPublicKey; - - use super::*; - - const TEST_PROGRAM_ID: ProgramId = [9; 8]; - - /// Two holders from the same GMS derive identical keys for the same PDA seed. - #[test] - fn same_gms_same_seed_produces_same_keys() { - let gms = [42_u8; 32]; - let holder_a = GroupKeyHolder::from_gms(gms); - let holder_b = GroupKeyHolder::from_gms(gms); - let seed = PdaSeed::new([1; 32]); - - let keys_a = holder_a.derive_keys_for_pda(&TEST_PROGRAM_ID, &seed); - let keys_b = holder_b.derive_keys_for_pda(&TEST_PROGRAM_ID, &seed); - - assert_eq!( - keys_a.generate_nullifier_public_key().to_byte_array(), - keys_b.generate_nullifier_public_key().to_byte_array(), - ); - } - - /// Different PDA seeds produce different keys from the same GMS. - #[test] - fn same_gms_different_seed_produces_different_keys() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - let seed_a = PdaSeed::new([1; 32]); - let seed_b = PdaSeed::new([2; 32]); - - let npk_a = holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed_a) - .generate_nullifier_public_key(); - let npk_b = holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed_b) - .generate_nullifier_public_key(); - - assert_ne!(npk_a.to_byte_array(), npk_b.to_byte_array()); - } - - /// Different GMS produce different keys for the same PDA seed. - #[test] - fn different_gms_same_seed_produces_different_keys() { - let holder_a = GroupKeyHolder::from_gms([42_u8; 32]); - let holder_b = GroupKeyHolder::from_gms([99_u8; 32]); - let seed = PdaSeed::new([1; 32]); - - let npk_a = holder_a - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - let npk_b = holder_b - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - - assert_ne!(npk_a.to_byte_array(), npk_b.to_byte_array()); - } - - /// GMS round-trip: export and restore produces the same keys. - #[test] - fn gms_round_trip() { - let original = GroupKeyHolder::from_gms([7_u8; 32]); - let restored = GroupKeyHolder::from_gms(*original.dangerous_raw_gms()); - let seed = PdaSeed::new([1; 32]); - - let npk_original = original - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - let npk_restored = restored - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - - assert_eq!(npk_original.to_byte_array(), npk_restored.to_byte_array()); - } - - /// The derived `NullifierPublicKey` is non-zero (sanity check). - #[test] - fn derived_npk_is_non_zero() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - let seed = PdaSeed::new([1; 32]); - let npk = holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - - assert_ne!(npk, NullifierPublicKey([0; 32])); - } - - /// Pins the end-to-end derivation for a fixed (GMS, `ProgramId`, `PdaSeed`). Any change - /// to `secret_spending_key_for_pda`, the `PrivateKeyHolder` nsk/npk chain, or the - /// `AccountId::for_private_pda` formula breaks this test. Mirrors the pinned-value - /// pattern from `for_private_pda_matches_pinned_value` in `nssa_core`. - #[test] - fn pinned_end_to_end_derivation_for_private_pda() { - use nssa_core::{account::AccountId, program::ProgramId}; - - let gms = [42_u8; 32]; - let seed = PdaSeed::new([1; 32]); - let program_id: ProgramId = [9; 8]; - - let holder = GroupKeyHolder::from_gms(gms); - let npk = holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - let account_id = AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX); - - let expected_npk = NullifierPublicKey([ - 136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79, - 215, 76, 92, 60, 166, 104, 35, 51, 91, 16, 114, 188, 112, - ]); - // AccountId is derived from (program_id, seed, npk), so it changes when npk changes. - // We verify npk is pinned, and AccountId is deterministically derived from it. - let expected_account_id = - AccountId::for_private_pda(&program_id, &seed, &expected_npk, u128::MAX); - - assert_eq!(npk, expected_npk); - assert_eq!(account_id, expected_account_id); - } - - /// Wallets persist `GroupKeyHolder` to disk and reload it on startup. This test pins - /// the serde round-trip: serialize, deserialize, and assert the derived keys for a - /// sample seed match on both sides. A silent encoding drift would corrupt every - /// group-owned account. - #[test] - fn gms_serde_round_trip_preserves_derivation() { - let original = GroupKeyHolder::from_gms([7_u8; 32]); - let encoded = bincode::serialize(&original).expect("serialize"); - let restored: GroupKeyHolder = bincode::deserialize(&encoded).expect("deserialize"); - - let seed = PdaSeed::new([1; 32]); - let npk_original = original - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - let npk_restored = restored - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - - assert_eq!(npk_original, npk_restored); - assert_eq!(original.dangerous_raw_gms(), restored.dangerous_raw_gms()); - } - - /// A `GroupKeyHolder` constructed from the same 32 bytes as a personal - /// `SecretSpendingKey` must not derive the same `NullifierPublicKey` as the personal - /// path, so a private PDA cannot be spent by a personal nullifier even under - /// adversarial key-material reuse. The safety rests on the group path's distinct - /// domain-separation prefix plus the seed mix-in (see `secret_spending_key_for_pda`). - #[test] - fn group_derivation_does_not_collide_with_personal_path_at_shared_bytes() { - let shared_bytes = [13_u8; 32]; - let seed = PdaSeed::new([5; 32]); - - let group_npk = GroupKeyHolder::from_gms(shared_bytes) - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(); - - let personal_npk = SecretSpendingKey(shared_bytes) - .produce_private_key_holder(None) - .generate_nullifier_public_key(); - - assert_ne!(group_npk, personal_npk); - } - - /// Seal then unseal recovers the same GMS and derived keys. - #[test] - fn seal_unseal_round_trip() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - - let recipient_ssk = SecretSpendingKey([7_u8; 32]); - let recipient_keys = recipient_ssk.produce_private_key_holder(None); - let recipient_vpk = recipient_keys.generate_viewing_public_key(); - let recipient_vsk = recipient_keys.viewing_secret_key; - - let sealed = holder.seal_for(&SealingPublicKey::from_bytes(recipient_vpk.0)); - let restored = GroupKeyHolder::unseal(&sealed, &recipient_vsk).expect("unseal"); - - assert_eq!(restored.dangerous_raw_gms(), holder.dangerous_raw_gms()); - - let seed = PdaSeed::new([1; 32]); - assert_eq!( - holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(), - restored - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key(), - ); - } - - /// Unsealing with a different VSK fails with `DecryptionFailed`. - #[test] - fn unseal_wrong_vsk_fails() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - - let recipient_ssk = SecretSpendingKey([7_u8; 32]); - let recipient_vpk = recipient_ssk - .produce_private_key_holder(None) - .generate_viewing_public_key(); - - let wrong_ssk = SecretSpendingKey([99_u8; 32]); - let wrong_vsk = wrong_ssk - .produce_private_key_holder(None) - .viewing_secret_key; - - let sealed = holder.seal_for(&SealingPublicKey::from_bytes(recipient_vpk.0)); - let result = GroupKeyHolder::unseal(&sealed, &wrong_vsk); - assert!(matches!(result, Err(super::SealError::DecryptionFailed))); - } - - /// Tampered ciphertext fails authentication. - #[test] - fn unseal_tampered_ciphertext_fails() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - - let recipient_ssk = SecretSpendingKey([7_u8; 32]); - let recipient_keys = recipient_ssk.produce_private_key_holder(None); - let recipient_vpk = recipient_keys.generate_viewing_public_key(); - let recipient_vsk = recipient_keys.viewing_secret_key; - - let mut sealed = holder.seal_for(&SealingPublicKey::from_bytes(recipient_vpk.0)); - // Flip a byte in the ciphertext portion (after ephemeral_pubkey + nonce) - let last = sealed.len() - 1; - sealed[last] ^= 0xFF; - - let result = GroupKeyHolder::unseal(&sealed, &recipient_vsk); - assert!(matches!(result, Err(super::SealError::DecryptionFailed))); - } - - /// Two seals of the same holder produce different ciphertexts (ephemeral randomness). - #[test] - fn two_seals_produce_different_ciphertexts() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - - let recipient_ssk = SecretSpendingKey([7_u8; 32]); - let recipient_vpk = recipient_ssk - .produce_private_key_holder(None) - .generate_viewing_public_key(); - - let sealing_key = SealingPublicKey::from_bytes(recipient_vpk.0); - let sealed_a = holder.seal_for(&sealing_key); - let sealed_b = holder.seal_for(&sealing_key); - assert_ne!(sealed_a, sealed_b); - } - - /// Sealed payload is too short. - #[test] - fn unseal_too_short_fails() { - let vsk: SealingSecretKey = [7_u8; 32]; - let result = GroupKeyHolder::unseal(&[0_u8; 10], &vsk); - assert!(matches!(result, Err(super::SealError::TooShort))); - } - - /// Degenerate GMS values (all-zeros, all-ones, single-bit) must still produce valid, - /// non-zero, pairwise-distinct npks. Rules out accidental "if gms == default { return - /// default }" style shortcuts in the derivation. - #[test] - fn degenerate_gms_produces_distinct_non_zero_keys() { - let seed = PdaSeed::new([1; 32]); - let degenerate = [[0_u8; 32], [0xFF_u8; 32], { - let mut v = [0_u8; 32]; - v[0] = 1; - v - }]; - - let npks: Vec = degenerate - .iter() - .map(|gms| { - GroupKeyHolder::from_gms(*gms) - .derive_keys_for_pda(&TEST_PROGRAM_ID, &seed) - .generate_nullifier_public_key() - }) - .collect(); - - for npk in &npks { - assert_ne!(*npk, NullifierPublicKey([0; 32])); - } - for (i, a) in npks.iter().enumerate() { - for b in &npks[i + 1..] { - assert_ne!(a, b); - } - } - } - - /// Full lifecycle: create group, distribute GMS via seal/unseal, verify key agreement. - #[test] - fn group_pda_lifecycle() { - use nssa_core::account::AccountId; - - let alice_holder = GroupKeyHolder::new(); - let pda_seed = PdaSeed::new([42_u8; 32]); - let program_id: nssa_core::program::ProgramId = [1; 8]; - - // Derive Alice's keys - let alice_keys = alice_holder.derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed); - let alice_npk = alice_keys.generate_nullifier_public_key(); - - // Seal GMS for Bob using Bob's viewing key, Bob unseals - let bob_ssk = SecretSpendingKey([77_u8; 32]); - let bob_keys = bob_ssk.produce_private_key_holder(None); - let bob_vpk = bob_keys.generate_viewing_public_key(); - let bob_vsk = bob_keys.viewing_secret_key; - - let sealed = alice_holder.seal_for(&SealingPublicKey::from_bytes(bob_vpk.0)); - let bob_holder = - GroupKeyHolder::unseal(&sealed, &bob_vsk).expect("Bob should unseal the GMS"); - - // Key agreement: both derive identical NPK and AccountId - let bob_npk = bob_holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed) - .generate_nullifier_public_key(); - assert_eq!(alice_npk, bob_npk); - - let alice_account_id = AccountId::for_private_pda(&program_id, &pda_seed, &alice_npk, 0); - let bob_account_id = AccountId::for_private_pda(&program_id, &pda_seed, &bob_npk, 0); - assert_eq!(alice_account_id, bob_account_id); - } - - /// Same GMS + same derivation seed produces same keys for shared accounts. - #[test] - fn shared_account_same_gms_same_seed_produces_same_keys() { - let gms = [42_u8; 32]; - let derivation_seed = [1_u8; 32]; - let holder_a = GroupKeyHolder::from_gms(gms); - let holder_b = GroupKeyHolder::from_gms(gms); - - let npk_a = holder_a - .derive_keys_for_shared_account(&derivation_seed) - .generate_nullifier_public_key(); - let npk_b = holder_b - .derive_keys_for_shared_account(&derivation_seed) - .generate_nullifier_public_key(); - - assert_eq!(npk_a, npk_b); - } - - /// Different derivation seeds produce different keys for shared accounts. - #[test] - fn shared_account_different_seeds_produce_different_keys() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - let npk_a = holder - .derive_keys_for_shared_account(&[1_u8; 32]) - .generate_nullifier_public_key(); - let npk_b = holder - .derive_keys_for_shared_account(&[2_u8; 32]) - .generate_nullifier_public_key(); - - assert_ne!(npk_a, npk_b); - } - - /// PDA and shared account derivations from the same GMS + same bytes never collide. - #[test] - fn pda_and_shared_derivations_do_not_collide() { - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - let bytes = [1_u8; 32]; - - let pda_npk = holder - .derive_keys_for_pda(&TEST_PROGRAM_ID, &PdaSeed::new(bytes)) - .generate_nullifier_public_key(); - let shared_npk = holder - .derive_keys_for_shared_account(&bytes) - .generate_nullifier_public_key(); - - assert_ne!(pda_npk, shared_npk); - } -} diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 05a7c996..6ffc8119 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -1,5 +1,5 @@ use k256::{Scalar, elliptic_curve::PrimeField as _}; -use nssa_core::{NullifierPublicKey, PrivateAccountKind, encryption::ViewingPublicKey}; +use nssa_core::{Identifier, NullifierPublicKey, encryption::ViewingPublicKey}; use serde::{Deserialize, Serialize}; use crate::key_management::{ @@ -10,7 +10,7 @@ use crate::key_management::{ #[derive(Debug, Serialize, Deserialize, Clone)] pub struct ChildKeysPrivate { - pub value: (KeyChain, Vec<(PrivateAccountKind, nssa::Account)>), + pub value: (KeyChain, Vec<(Identifier, nssa::Account)>), pub ccc: [u8; 32], /// Can be [`None`] if root. pub cci: Option, @@ -115,11 +115,9 @@ impl KeyTreeNode for ChildKeysPrivate { } fn account_ids(&self) -> impl Iterator { - let npk = self.value.0.nullifier_public_key; - self.value - .1 - .iter() - .map(move |(kind, _)| nssa::AccountId::for_private_account(&npk, kind)) + self.value.1.iter().map(|(identifier, _)| { + nssa::AccountId::from((&self.value.0.nullifier_public_key, *identifier)) + }) } } diff --git a/key_protocol/src/key_management/key_tree/mod.rs b/key_protocol/src/key_management/key_tree/mod.rs index edf9dadd..0ae0a52f 100644 --- a/key_protocol/src/key_management/key_tree/mod.rs +++ b/key_protocol/src/key_management/key_tree/mod.rs @@ -274,10 +274,7 @@ impl KeyTree { identifier: Identifier, ) -> Option { let node = self.key_map.get(cci)?; - let account_id = nssa::AccountId::for_regular_private_account( - &node.value.0.nullifier_public_key, - identifier, - ); + let account_id = nssa::AccountId::from((&node.value.0.nullifier_public_key, identifier)); if self.account_id_map.contains_key(&account_id) { return None; } @@ -322,7 +319,6 @@ mod tests { use std::{collections::HashSet, str::FromStr as _}; use nssa::AccountId; - use nssa_core::PrivateAccountKind; use super::*; @@ -536,7 +532,7 @@ mod tests { .get_mut(&ChainIndex::from_str("/1").unwrap()) .unwrap(); acc.value.1.push(( - PrivateAccountKind::Regular(0), + 0, nssa::Account { balance: 2, ..nssa::Account::default() @@ -548,7 +544,7 @@ mod tests { .get_mut(&ChainIndex::from_str("/2").unwrap()) .unwrap(); acc.value.1.push(( - PrivateAccountKind::Regular(0), + 0, nssa::Account { balance: 3, ..nssa::Account::default() @@ -560,7 +556,7 @@ mod tests { .get_mut(&ChainIndex::from_str("/0/1").unwrap()) .unwrap(); acc.value.1.push(( - PrivateAccountKind::Regular(0), + 0, nssa::Account { balance: 5, ..nssa::Account::default() @@ -572,7 +568,7 @@ mod tests { .get_mut(&ChainIndex::from_str("/1/0").unwrap()) .unwrap(); acc.value.1.push(( - PrivateAccountKind::Regular(0), + 0, nssa::Account { balance: 6, ..nssa::Account::default() diff --git a/key_protocol/src/key_management/mod.rs b/key_protocol/src/key_management/mod.rs index aa5a1a75..065af364 100644 --- a/key_protocol/src/key_management/mod.rs +++ b/key_protocol/src/key_management/mod.rs @@ -6,7 +6,6 @@ use secret_holders::{PrivateKeyHolder, SecretSpendingKey, SeedHolder}; use serde::{Deserialize, Serialize}; pub mod ephemeral_key_holder; -pub mod group_key_holder; pub mod key_tree; pub mod secret_holders; diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index b228e3ef..4df6df82 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -3,12 +3,11 @@ use std::collections::BTreeMap; use anyhow::Result; use k256::AffinePoint; use nssa::{Account, AccountId}; -use nssa_core::{Identifier, PrivateAccountKind}; +use nssa_core::Identifier; use serde::{Deserialize, Serialize}; use crate::key_management::{ KeyChain, - group_key_holder::GroupKeyHolder, key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex}, secret_holders::SeedHolder, }; @@ -18,25 +17,10 @@ pub type PublicKey = AffinePoint; #[derive(Clone, Debug, Serialize, Deserialize)] pub struct UserPrivateAccountData { pub key_chain: KeyChain, - pub accounts: Vec<(PrivateAccountKind, Account)>, + pub accounts: Vec<(Identifier, Account)>, } -/// Metadata for a shared account (GMS-derived), stored alongside the cached plaintext state. -/// The group label and identifier (or PDA seed) are needed to re-derive keys during sync. #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SharedAccountEntry { - pub group_label: String, - pub identifier: Identifier, - /// For PDA accounts, the seed and program ID used to derive keys via `derive_keys_for_pda`. - /// `None` for regular shared accounts (keys derived from identifier via derivation seed). - #[serde(default)] - pub pda_seed: Option, - #[serde(default)] - pub pda_program_id: Option, - pub account: Account, -} - -#[derive(Clone, Debug)] pub struct NSSAUserData { /// Default public accounts. pub default_pub_account_signing_keys: BTreeMap, @@ -46,16 +30,6 @@ pub struct NSSAUserData { pub public_key_tree: KeyTreePublic, /// Tree of private keys. pub private_key_tree: KeyTreePrivate, - /// Group key holders for shared account management, keyed by a human-readable label. - pub group_key_holders: BTreeMap, - /// Cached plaintext state of shared private accounts (PDAs and regular shared accounts), - /// keyed by `AccountId`. Each entry stores the group label and identifier needed - /// to re-derive keys during sync. - pub shared_private_accounts: BTreeMap, - /// Dedicated sealing secret key for GMS distribution. Generated once via - /// `wallet group new-sealing-key`. The corresponding public key is shared with - /// group members so they can seal GMS for this wallet. - pub sealing_secret_key: Option, } impl NSSAUserData { @@ -79,11 +53,10 @@ impl NSSAUserData { ) -> bool { let mut check_res = true; for (account_id, entry) in accounts_keys_map { - let npk = &entry.key_chain.nullifier_public_key; - let any_match = entry - .accounts - .iter() - .any(|(kind, _)| nssa::AccountId::for_private_account(npk, kind) == *account_id); + let any_match = entry.accounts.iter().any(|(identifier, _)| { + nssa::AccountId::from((&entry.key_chain.nullifier_public_key, *identifier)) + == *account_id + }); if !any_match { println!("No matching entry found for account_id {account_id}"); check_res = false; @@ -115,9 +88,6 @@ impl NSSAUserData { default_user_private_accounts: default_accounts_key_chains, public_key_tree, private_key_tree, - group_key_holders: BTreeMap::new(), - shared_private_accounts: BTreeMap::new(), - sealing_secret_key: None, }) } @@ -178,7 +148,6 @@ impl NSSAUserData { } /// Returns the key chain and account data for the given private account ID. - /// Does not cover shared private accounts — use `shared_private_account` for those. #[must_use] pub fn get_private_account( &self, @@ -186,27 +155,24 @@ impl NSSAUserData { ) -> Option<(KeyChain, nssa_core::account::Account, Identifier)> { // Check default accounts if let Some(entry) = self.default_user_private_accounts.get(&account_id) { - let npk = &entry.key_chain.nullifier_public_key; - if let Some((kind, account)) = entry - .accounts - .iter() - .find(|(kind, _)| nssa::AccountId::for_private_account(npk, kind) == account_id) - { - return Some((entry.key_chain.clone(), account.clone(), kind.identifier())); + for (identifier, account) in &entry.accounts { + let expected_id = + nssa::AccountId::from((&entry.key_chain.nullifier_public_key, *identifier)); + if expected_id == account_id { + return Some((entry.key_chain.clone(), account.clone(), *identifier)); + } } return None; } // Check tree if let Some(node) = self.private_key_tree.get_node(account_id) { let key_chain = &node.value.0; - let npk = &key_chain.nullifier_public_key; - if let Some((kind, account)) = node - .value - .1 - .iter() - .find(|(kind, _)| nssa::AccountId::for_private_account(npk, kind) == account_id) - { - return Some((key_chain.clone(), account.clone(), kind.identifier())); + for (identifier, account) in &node.value.1 { + let expected_id = + nssa::AccountId::from((&key_chain.nullifier_public_key, *identifier)); + if expected_id == account_id { + return Some((key_chain.clone(), account.clone(), *identifier)); + } } } None @@ -229,56 +195,6 @@ impl NSSAUserData { .copied() .chain(self.private_key_tree.account_id_map.keys().copied()) } - - /// Returns the `GroupKeyHolder` for the given label, if it exists. - #[must_use] - pub fn group_key_holder(&self, label: &str) -> Option<&GroupKeyHolder> { - self.group_key_holders.get(label) - } - - /// Inserts or replaces a `GroupKeyHolder` under the given label. - /// - /// If a holder already exists under this label, it is silently replaced and the old - /// GMS is lost. Callers must ensure label uniqueness across groups. - pub fn insert_group_key_holder(&mut self, label: String, holder: GroupKeyHolder) { - self.group_key_holders.insert(label, holder); - } - - /// Returns the cached account for a shared private account, if it exists. - #[must_use] - pub fn shared_private_account( - &self, - account_id: &nssa::AccountId, - ) -> Option<&SharedAccountEntry> { - self.shared_private_accounts.get(account_id) - } - - /// Inserts or replaces a shared private account entry. - pub fn insert_shared_private_account( - &mut self, - account_id: nssa::AccountId, - entry: SharedAccountEntry, - ) { - self.shared_private_accounts.insert(account_id, entry); - } - - /// Updates the cached account state for a shared private account. - pub fn update_shared_private_account_state( - &mut self, - account_id: &nssa::AccountId, - account: nssa_core::account::Account, - ) { - if let Some(entry) = self.shared_private_accounts.get_mut(account_id) { - entry.account = account; - } - } - - /// Iterates over all shared private accounts. - pub fn shared_private_accounts_iter( - &self, - ) -> impl Iterator { - self.shared_private_accounts.iter() - } } impl Default for NSSAUserData { @@ -298,112 +214,6 @@ impl Default for NSSAUserData { mod tests { use super::*; - #[test] - fn group_key_holder_storage_round_trip() { - let mut user_data = NSSAUserData::default(); - assert!(user_data.group_key_holder("test-group").is_none()); - - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - user_data.insert_group_key_holder(String::from("test-group"), holder.clone()); - - let retrieved = user_data - .group_key_holder("test-group") - .expect("should exist"); - assert_eq!(retrieved.dangerous_raw_gms(), holder.dangerous_raw_gms()); - } - - #[test] - fn group_key_holders_default_empty() { - let user_data = NSSAUserData::default(); - assert!(user_data.group_key_holders.is_empty()); - assert!(user_data.shared_private_accounts.is_empty()); - } - - #[test] - fn shared_account_entry_serde_round_trip() { - use nssa_core::program::PdaSeed; - - let entry = SharedAccountEntry { - group_label: String::from("test-group"), - identifier: 42, - pda_seed: None, - pda_program_id: None, - account: nssa_core::account::Account::default(), - }; - let encoded = bincode::serialize(&entry).expect("serialize"); - let decoded: SharedAccountEntry = bincode::deserialize(&encoded).expect("deserialize"); - assert_eq!(decoded.group_label, "test-group"); - assert_eq!(decoded.identifier, 42); - assert!(decoded.pda_seed.is_none()); - - let pda_entry = SharedAccountEntry { - group_label: String::from("pda-group"), - identifier: u128::MAX, - pda_seed: Some(PdaSeed::new([7_u8; 32])), - pda_program_id: Some([9; 8]), - account: nssa_core::account::Account::default(), - }; - let pda_encoded = bincode::serialize(&pda_entry).expect("serialize pda"); - let pda_decoded: SharedAccountEntry = - bincode::deserialize(&pda_encoded).expect("deserialize pda"); - assert_eq!(pda_decoded.group_label, "pda-group"); - assert_eq!(pda_decoded.identifier, u128::MAX); - assert_eq!(pda_decoded.pda_seed.unwrap(), PdaSeed::new([7_u8; 32])); - } - - #[test] - fn shared_account_entry_none_pda_seed_round_trips() { - // Verify that an entry with pda_seed=None serializes and deserializes correctly, - // confirming the #[serde(default)] attribute works for backward compatibility. - let entry = SharedAccountEntry { - group_label: String::from("old"), - identifier: 1, - pda_seed: None, - pda_program_id: None, - account: nssa_core::account::Account::default(), - }; - let encoded = bincode::serialize(&entry).expect("serialize"); - let decoded: SharedAccountEntry = bincode::deserialize(&encoded).expect("deserialize"); - assert_eq!(decoded.group_label, "old"); - assert_eq!(decoded.identifier, 1); - assert!(decoded.pda_seed.is_none()); - } - - #[test] - fn shared_account_derives_consistent_keys_from_group() { - use nssa_core::program::PdaSeed; - - let mut user_data = NSSAUserData::default(); - let gms_holder = GroupKeyHolder::from_gms([42_u8; 32]); - user_data.insert_group_key_holder(String::from("my-group"), gms_holder); - - let holder = user_data.group_key_holder("my-group").unwrap(); - - // Regular shared account: derive via tag - let tag = [1_u8; 32]; - let keys_a = holder.derive_keys_for_shared_account(&tag); - let keys_b = holder.derive_keys_for_shared_account(&tag); - assert_eq!( - keys_a.generate_nullifier_public_key(), - keys_b.generate_nullifier_public_key(), - ); - - // PDA shared account: derive via seed - let seed = PdaSeed::new([2_u8; 32]); - let pda_keys_a = holder.derive_keys_for_pda(&[9; 8], &seed); - let pda_keys_b = holder.derive_keys_for_pda(&[9; 8], &seed); - assert_eq!( - pda_keys_a.generate_nullifier_public_key(), - pda_keys_b.generate_nullifier_public_key(), - ); - - // PDA and shared derivations don't collide - assert_ne!( - keys_a.generate_nullifier_public_key(), - pda_keys_a.generate_nullifier_public_key(), - ); - } - #[test] fn new_account() { let mut user_data = NSSAUserData::default(); diff --git a/nssa/core/src/circuit_io.rs b/nssa/core/src/circuit_io.rs index 63c188ef..c71003de 100644 --- a/nssa/core/src/circuit_io.rs +++ b/nssa/core/src/circuit_io.rs @@ -12,99 +12,23 @@ use crate::{ pub struct PrivacyPreservingCircuitInput { /// Outputs of the program execution. pub program_outputs: Vec, - /// One entry per `pre_state`, in the same order as the program's `pre_states`. - /// Length must equal the number of `pre_states` derived from `program_outputs`. - /// The guest's `private_pda_npk_by_position` and `private_pda_bound_positions` - /// rely on this position alignment. - pub account_identities: Vec, + /// Visibility mask for accounts. + /// + /// - `0` - public account + /// - `1` - private account with authentication + /// - `2` - private account without authentication + /// - `3` - private PDA account + pub visibility_mask: Vec, + /// Public keys and identifiers of private accounts. + pub private_account_keys: Vec<(NullifierPublicKey, Identifier, SharedSecretKey)>, + /// Nullifier secret keys for authorized private accounts. + pub private_account_nsks: Vec, + /// Membership proofs for private accounts. Can be [`None`] for uninitialized accounts. + pub private_account_membership_proofs: Vec>, /// Program ID. pub program_id: ProgramId, } -/// Per-account input to the privacy-preserving circuit. Each variant carries exactly the fields -/// the guest needs for that account's code path. -#[derive(Serialize, Deserialize, Clone)] -pub enum InputAccountIdentity { - /// Public account. The guest reads pre/post state from `program_outputs` and emits no - /// commitment, ciphertext, or nullifier. - Public, - /// Init of an authorized standalone private account: no membership proof. The `pre_state` - /// must be `Account::default()`. The `account_id` is derived as - /// `AccountId::for_regular_private_account(&NullifierPublicKey::from(nsk), identifier)` and - /// matched against `pre_state.account_id`. - PrivateAuthorizedInit { - ssk: SharedSecretKey, - nsk: NullifierSecretKey, - identifier: Identifier, - }, - /// Update of an authorized standalone private account: existing on-chain commitment, with - /// membership proof. - PrivateAuthorizedUpdate { - ssk: SharedSecretKey, - nsk: NullifierSecretKey, - membership_proof: MembershipProof, - identifier: Identifier, - }, - /// Init of a standalone private account the caller does not own (e.g. a recipient who - /// doesn't yet exist on chain). No `nsk`, no membership proof. - PrivateUnauthorized { - npk: NullifierPublicKey, - ssk: SharedSecretKey, - identifier: Identifier, - }, - /// Init of a private PDA, unauthorized. The npk-to-account_id binding is proven upstream - /// via `Claim::Pda(seed)` or a caller's `pda_seeds` match. The identifier diversifies the - /// PDA within the `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it - /// as the 4th input. - PrivatePdaInit { - npk: NullifierPublicKey, - ssk: SharedSecretKey, - identifier: Identifier, - }, - /// Update of an existing private PDA, authorized, with membership proof. `npk` is derived - /// from `nsk`. Authorization is established upstream by a caller `pda_seeds` match or a - /// previously-seen authorization in a chained call. - PrivatePdaUpdate { - ssk: SharedSecretKey, - nsk: NullifierSecretKey, - membership_proof: MembershipProof, - identifier: Identifier, - }, -} - -impl InputAccountIdentity { - #[must_use] - pub const fn is_public(&self) -> bool { - matches!(self, Self::Public) - } - - #[must_use] - pub const fn is_private_pda(&self) -> bool { - matches!( - self, - Self::PrivatePdaInit { .. } | Self::PrivatePdaUpdate { .. } - ) - } - - /// For private PDA variants, return the `(npk, identifier)` pair. `Init` carries both - /// directly; `Update` derives `npk` from `nsk`. For non-PDA variants returns `None`. - #[must_use] - pub fn npk_if_private_pda(&self) -> Option<(NullifierPublicKey, Identifier)> { - match self { - Self::PrivatePdaInit { - npk, identifier, .. - } => Some((*npk, *identifier)), - Self::PrivatePdaUpdate { - nsk, identifier, .. - } => Some((NullifierPublicKey::from(nsk), *identifier)), - Self::Public - | Self::PrivateAuthorizedInit { .. } - | Self::PrivateAuthorizedUpdate { .. } - | Self::PrivateUnauthorized { .. } => None, - } - } -} - #[derive(Serialize, Deserialize)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] pub struct PrivacyPreservingCircuitOutput { diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index 4b675d0e..80d62f30 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "host")] pub use shared_key_derivation::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey}; -use crate::{Commitment, account::Account, program::PrivateAccountKind}; +use crate::{Commitment, Identifier, account::Account}; #[cfg(feature = "host")] pub mod shared_key_derivation; @@ -40,14 +40,13 @@ impl EncryptionScheme { #[must_use] pub fn encrypt( account: &Account, - kind: &PrivateAccountKind, + identifier: Identifier, shared_secret: &SharedSecretKey, commitment: &Commitment, output_index: u32, ) -> Ciphertext { - // Plaintext: PrivateAccountKind::HEADER_LEN bytes header || account bytes. - // Both variants produce the same header length — see PrivateAccountKind::to_header_bytes. - let mut buffer = kind.to_header_bytes().to_vec(); + // Plaintext: identifier (16 bytes, little-endian) || account bytes + let mut buffer = identifier.to_le_bytes().to_vec(); buffer.extend_from_slice(&account.to_bytes()); Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index); Ciphertext(buffer) @@ -90,19 +89,17 @@ impl EncryptionScheme { shared_secret: &SharedSecretKey, commitment: &Commitment, output_index: u32, - ) -> Option<(PrivateAccountKind, Account)> { + ) -> Option<(Identifier, Account)> { use std::io::Cursor; let mut buffer = ciphertext.0.clone(); Self::symmetric_transform(&mut buffer, shared_secret, commitment, output_index); - if buffer.len() < PrivateAccountKind::HEADER_LEN { + if buffer.len() < 16 { return None; } - let header: &[u8; PrivateAccountKind::HEADER_LEN] = - buffer[..PrivateAccountKind::HEADER_LEN].try_into().unwrap(); - let kind = PrivateAccountKind::from_header_bytes(header)?; + let identifier = Identifier::from_le_bytes(buffer[..16].try_into().unwrap()); - let mut cursor = Cursor::new(&buffer[PrivateAccountKind::HEADER_LEN..]); + let mut cursor = Cursor::new(&buffer[16..]); Account::from_cursor(&mut cursor) .inspect_err(|err| { println!( @@ -115,43 +112,6 @@ impl EncryptionScheme { ); }) .ok() - .map(|account| (kind, account)) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use crate::{ - account::{Account, AccountId}, - program::PdaSeed, - }; - - #[test] - fn encrypt_same_length_for_account_and_pda() { - let account = Account::default(); - let secret = SharedSecretKey([0_u8; 32]); - let commitment = crate::Commitment::new(&AccountId::new([0_u8; 32]), &Account::default()); - - let account_ct = EncryptionScheme::encrypt( - &account, - &PrivateAccountKind::Regular(42), - &secret, - &commitment, - 0, - ); - let pda_ct = EncryptionScheme::encrypt( - &account, - &PrivateAccountKind::Pda { - program_id: [1_u32; 8], - seed: PdaSeed::new([2_u8; 32]), - identifier: 42, - }, - &secret, - &commitment, - 0, - ); - - assert_eq!(account_ct.0.len(), pda_ct.0.len()); + .map(|account| (identifier, account)) } } diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index 894b611f..478d475c 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -3,16 +3,13 @@ reason = "We prefer to group methods by functionality rather than by type for encoding" )] -pub use circuit_io::{ - InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, -}; +pub use circuit_io::{PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput}; pub use commitment::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof, compute_digest_for_path, }; pub use encryption::{EncryptionScheme, SharedSecretKey}; pub use nullifier::{Identifier, Nullifier, NullifierPublicKey, NullifierSecretKey}; -pub use program::PrivateAccountKind; pub mod account; mod circuit_io; diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index ab23ddc0..aafe3f7c 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -12,11 +12,10 @@ pub type Identifier = u128; #[cfg_attr(any(feature = "host", test), derive(Hash))] pub struct NullifierPublicKey(pub [u8; 32]); -impl AccountId { - /// Derives an [`AccountId`] for a regular (non-PDA) private account from the nullifier public - /// key and identifier. - #[must_use] - pub fn for_regular_private_account(npk: &NullifierPublicKey, identifier: Identifier) -> Self { +impl From<(&NullifierPublicKey, Identifier)> for AccountId { + fn from(value: (&NullifierPublicKey, Identifier)) -> Self { + let (npk, identifier) = value; + // 32 bytes prefix || 32 bytes npk || 16 bytes identifier let mut bytes = [0; 80]; bytes[0..32].copy_from_slice(PRIVATE_ACCOUNT_ID_PREFIX); @@ -32,12 +31,6 @@ impl AccountId { } } -impl From<(&NullifierPublicKey, Identifier)> for AccountId { - fn from((npk, identifier): (&NullifierPublicKey, Identifier)) -> Self { - Self::for_regular_private_account(npk, identifier) - } -} - impl AsRef<[u8]> for NullifierPublicKey { fn as_ref(&self) -> &[u8] { self.0.as_slice() @@ -162,7 +155,7 @@ mod tests { 253, 105, 164, 89, 84, 40, 191, 182, 119, 64, 255, 67, 142, ]); - let account_id = AccountId::for_regular_private_account(&npk, 0); + let account_id = AccountId::from((&npk, 0)); assert_eq!(account_id, expected_account_id); } @@ -179,7 +172,7 @@ mod tests { 56, 247, 99, 121, 165, 182, 234, 255, 19, 127, 191, 72, ]); - let account_id = AccountId::for_regular_private_account(&npk, 1); + let account_id = AccountId::from((&npk, 1)); assert_eq!(account_id, expected_account_id); } @@ -197,7 +190,7 @@ mod tests { 19, 245, 25, 214, 162, 209, 135, 252, 82, 27, 2, 174, 196, ]); - let account_id = AccountId::for_regular_private_account(&npk, identifier); + let account_id = AccountId::from((&npk, identifier)); assert_eq!(account_id, expected_account_id); } diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 275b40a6..1ef2ef6c 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -1,11 +1,12 @@ use std::collections::HashSet; +#[cfg(any(feature = "host", test))] use borsh::{BorshDeserialize, BorshSerialize}; use risc0_zkvm::{DeserializeOwned, guest::env, serde::Deserializer}; use serde::{Deserialize, Serialize}; use crate::{ - BlockId, Identifier, NullifierPublicKey, Timestamp, + BlockId, NullifierPublicKey, Timestamp, account::{Account, AccountId, AccountWithMetadata}, }; @@ -26,18 +27,7 @@ pub struct ProgramInput { /// Each program can derive up to `2^256` unique account IDs by choosing different /// seeds. PDAs allow programs to control namespaced account identifiers without /// collisions between programs. -#[derive( - Debug, - Clone, - Copy, - Eq, - PartialEq, - Hash, - Serialize, - Deserialize, - BorshSerialize, - BorshDeserialize, -)] +#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Serialize, Deserialize)] pub struct PdaSeed([u8; 32]); impl PdaSeed { @@ -45,66 +35,6 @@ impl PdaSeed { pub const fn new(value: [u8; 32]) -> Self { Self(value) } - - #[must_use] - pub const fn as_bytes(&self) -> &[u8; 32] { - &self.0 - } -} - -impl AsRef<[u8]> for PdaSeed { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -/// Discriminates the type of private account a ciphertext belongs to, carrying the data needed -/// to reconstruct the account's [`AccountId`] on the receiver side. -/// -/// [`AccountId`]: crate::account::AccountId -#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, BorshSerialize, BorshDeserialize)] -pub enum PrivateAccountKind { - Regular(Identifier), - Pda { - program_id: ProgramId, - seed: PdaSeed, - identifier: Identifier, - }, -} - -impl PrivateAccountKind { - /// Borsh layout (all integers little-endian, variant index is u8): - /// - /// ```text - /// Regular(ident): 0x00 || ident (16 LE) || [0u8; 64] - /// Pda { program_id, seed, ident }: 0x01 || program_id (32) || seed (32) || ident (16 LE) - /// ``` - /// - /// Both variants are zero-padded to the same length so all ciphertexts are the same size, - /// preventing observers from distinguishing `Regular` from `Pda` via ciphertext length. - /// `HEADER_LEN` equals the borsh size of the largest variant (`Pda`): 1 + 32 + 32 + 16 = 81. - pub const HEADER_LEN: usize = 81; - - #[must_use] - pub const fn identifier(&self) -> Identifier { - match self { - Self::Regular(identifier) | Self::Pda { identifier, .. } => *identifier, - } - } - - #[must_use] - pub fn to_header_bytes(&self) -> [u8; Self::HEADER_LEN] { - let mut bytes = [0_u8; Self::HEADER_LEN]; - let serialized = borsh::to_vec(self).expect("borsh serialization is infallible"); - bytes[..serialized.len()].copy_from_slice(&serialized); - bytes - } - - #[cfg(feature = "host")] - #[must_use] - pub fn from_header_bytes(bytes: &[u8; Self::HEADER_LEN]) -> Option { - BorshDeserialize::deserialize(&mut bytes.as_ref()).ok() - } } impl AccountId { @@ -129,31 +59,27 @@ impl AccountId { ) } - /// Derives an [`AccountId`] for a private PDA from the program ID, seed, nullifier public - /// key, and identifier. + /// Derives an [`AccountId`] for a private PDA from the program ID, seed, and nullifier + /// public key. /// /// Unlike public PDAs ([`AccountId::for_public_pda`]), this includes the `npk` in the /// derivation, making the address unique per group of controllers sharing viewing keys. - /// The `identifier` further diversifies the address, so a single `(program_id, seed, npk)` - /// tuple controls a family of 2^128 addresses. #[must_use] pub fn for_private_pda( program_id: &ProgramId, seed: &PdaSeed, npk: &NullifierPublicKey, - identifier: Identifier, ) -> Self { use risc0_zkvm::sha::{Impl, Sha256 as _}; const PRIVATE_PDA_PREFIX: &[u8; 32] = b"/LEE/v0.3/AccountId/PrivatePDA/\x00"; - let mut bytes = [0_u8; 144]; + let mut bytes = [0_u8; 128]; bytes[0..32].copy_from_slice(PRIVATE_PDA_PREFIX); let program_id_bytes: &[u8] = bytemuck::try_cast_slice(program_id).expect("ProgramId should be castable to &[u8]"); bytes[32..64].copy_from_slice(program_id_bytes); bytes[64..96].copy_from_slice(&seed.0); bytes[96..128].copy_from_slice(&npk.to_byte_array()); - bytes[128..144].copy_from_slice(&identifier.to_le_bytes()); Self::new( Impl::hash_bytes(&bytes) .as_bytes() @@ -161,21 +87,6 @@ impl AccountId { .expect("Hash output must be exactly 32 bytes long"), ) } - - /// Derives the [`AccountId`] for a private account from the nullifier public key and kind. - #[must_use] - pub fn for_private_account(npk: &NullifierPublicKey, kind: &PrivateAccountKind) -> Self { - match kind { - PrivateAccountKind::Regular(identifier) => { - Self::for_regular_private_account(npk, *identifier) - } - PrivateAccountKind::Pda { - program_id, - seed, - identifier, - } => Self::for_private_pda(program_id, seed, npk, *identifier), - } - } } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -934,20 +845,19 @@ mod tests { // ---- AccountId::for_private_pda tests ---- /// Pins `AccountId::for_private_pda` against a hardcoded expected output for a specific - /// `(program_id, seed, npk, identifier)` tuple. Any change to `PRIVATE_PDA_PREFIX`, byte - /// ordering, or the underlying hash breaks this test. + /// `(program_id, seed, npk)` triple. Any change to `PRIVATE_PDA_PREFIX`, byte ordering, + /// or the underlying hash breaks this test. #[test] fn for_private_pda_matches_pinned_value() { let program_id: ProgramId = [1; 8]; let seed = PdaSeed::new([2; 32]); let npk = NullifierPublicKey([3; 32]); - let identifier: Identifier = u128::MAX; let expected = AccountId::new([ - 59, 239, 182, 97, 14, 220, 96, 115, 238, 133, 143, 33, 234, 82, 237, 255, 148, 110, 54, - 124, 98, 159, 245, 101, 146, 182, 150, 54, 37, 62, 25, 17, + 132, 198, 103, 173, 244, 211, 188, 217, 249, 99, 126, 205, 152, 120, 192, 47, 13, 53, + 133, 3, 17, 69, 92, 243, 140, 94, 182, 211, 218, 75, 215, 45, ]); assert_eq!( - AccountId::for_private_pda(&program_id, &seed, &npk, identifier), + AccountId::for_private_pda(&program_id, &seed, &npk), expected ); } @@ -960,8 +870,8 @@ mod tests { let npk_a = NullifierPublicKey([3; 32]); let npk_b = NullifierPublicKey([4; 32]); assert_ne!( - AccountId::for_private_pda(&program_id, &seed, &npk_a, u128::MAX), - AccountId::for_private_pda(&program_id, &seed, &npk_b, u128::MAX), + AccountId::for_private_pda(&program_id, &seed, &npk_a), + AccountId::for_private_pda(&program_id, &seed, &npk_b), ); } @@ -973,8 +883,8 @@ mod tests { let seed_b = PdaSeed::new([5; 32]); let npk = NullifierPublicKey([3; 32]); assert_ne!( - AccountId::for_private_pda(&program_id, &seed_a, &npk, u128::MAX), - AccountId::for_private_pda(&program_id, &seed_b, &npk, u128::MAX), + AccountId::for_private_pda(&program_id, &seed_a, &npk), + AccountId::for_private_pda(&program_id, &seed_b, &npk), ); } @@ -986,25 +896,8 @@ mod tests { let seed = PdaSeed::new([2; 32]); let npk = NullifierPublicKey([3; 32]); assert_ne!( - AccountId::for_private_pda(&program_id_a, &seed, &npk, u128::MAX), - AccountId::for_private_pda(&program_id_b, &seed, &npk, u128::MAX), - ); - } - - /// Different identifiers produce different addresses for the same `(program_id, seed, npk)`, - /// confirming that each `(program_id, seed, npk)` tuple controls a family of 2^128 addresses. - #[test] - fn for_private_pda_differs_for_different_identifier() { - let program_id: ProgramId = [1; 8]; - let seed = PdaSeed::new([2; 32]); - let npk = NullifierPublicKey([3; 32]); - assert_ne!( - AccountId::for_private_pda(&program_id, &seed, &npk, 0), - AccountId::for_private_pda(&program_id, &seed, &npk, 1), - ); - assert_ne!( - AccountId::for_private_pda(&program_id, &seed, &npk, 0), - AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX), + AccountId::for_private_pda(&program_id_a, &seed, &npk), + AccountId::for_private_pda(&program_id_b, &seed, &npk), ); } @@ -1015,62 +908,14 @@ mod tests { let program_id: ProgramId = [1; 8]; let seed = PdaSeed::new([2; 32]); let npk = NullifierPublicKey([3; 32]); - let private_id = AccountId::for_private_pda(&program_id, &seed, &npk, u128::MAX); + let private_id = AccountId::for_private_pda(&program_id, &seed, &npk); let public_id = AccountId::for_public_pda(&program_id, &seed); assert_ne!(private_id, public_id); } - #[cfg(feature = "host")] - #[test] - fn private_account_kind_header_round_trips() { - let regular = PrivateAccountKind::Regular(42); - let pda = PrivateAccountKind::Pda { - program_id: [1_u32; 8], - seed: PdaSeed::new([2_u8; 32]), - identifier: u128::MAX, - }; - assert_eq!( - PrivateAccountKind::from_header_bytes(®ular.to_header_bytes()), - Some(regular) - ); - assert_eq!( - PrivateAccountKind::from_header_bytes(&pda.to_header_bytes()), - Some(pda) - ); - } - - #[cfg(feature = "host")] - #[test] - fn private_account_kind_unknown_discriminant_returns_none() { - let mut bytes = [0_u8; PrivateAccountKind::HEADER_LEN]; - bytes[0] = 0xFF; - assert_eq!(PrivateAccountKind::from_header_bytes(&bytes), None); - } - - #[test] - fn for_private_account_dispatches_correctly() { - let program_id: ProgramId = [1; 8]; - let seed = PdaSeed::new([2; 32]); - let npk = NullifierPublicKey([3; 32]); - let identifier: Identifier = 77; - - assert_eq!( - AccountId::for_private_account(&npk, &PrivateAccountKind::Regular(identifier)), - AccountId::for_regular_private_account(&npk, identifier), - ); - assert_eq!( - AccountId::for_private_account( - &npk, - &PrivateAccountKind::Pda { - program_id, - seed, - identifier - } - ), - AccountId::for_private_pda(&program_id, &seed, &npk, identifier), - ); - } + // ---- compute_public_authorized_pdas tests ---- + /// `compute_public_authorized_pdas` returns the public PDA addresses for the caller's seeds. #[test] fn compute_public_authorized_pdas_with_seeds() { let caller: ProgramId = [1; 8]; diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 09e37664..f5bd8cea 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -2,7 +2,8 @@ use std::collections::{HashMap, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ - InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + Identifier, MembershipProof, NullifierPublicKey, NullifierSecretKey, + PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, program::{ChainedCall, InstructionData, ProgramId, ProgramOutput}, }; @@ -62,10 +63,14 @@ impl From for ProgramWithDependencies { /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution /// circuit. +/// TODO: too many parameters. pub fn execute_and_prove( pre_states: Vec, instruction_data: InstructionData, - account_identities: Vec, + visibility_mask: Vec, + private_account_keys: Vec<(NullifierPublicKey, Identifier, SharedSecretKey)>, + private_account_nsks: Vec, + private_account_membership_proofs: Vec>, program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { let ProgramWithDependencies { @@ -123,7 +128,10 @@ pub fn execute_and_prove( let circuit_input = PrivacyPreservingCircuitInput { program_outputs, - account_identities, + visibility_mask, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, program_id: program_with_dependencies.program.id(), }; @@ -176,10 +184,8 @@ mod tests { #![expect(clippy::shadow_unrelated, reason = "We don't care about it in tests")] use nssa_core::{ - Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, - PrivacyPreservingCircuitOutput, SharedSecretKey, + Commitment, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, - program::{PdaSeed, PrivateAccountKind}, }; use super::*; @@ -193,21 +199,6 @@ mod tests { }, }; - fn decrypt_kind( - output: &PrivacyPreservingCircuitOutput, - ssk: &SharedSecretKey, - idx: usize, - ) -> PrivateAccountKind { - let (kind, _) = EncryptionScheme::decrypt( - &output.ciphertexts[idx], - ssk, - &output.new_commitments[idx], - u32::try_from(idx).expect("idx fits in u32"), - ) - .unwrap(); - kind - } - #[test] fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() { let recipient_keys = test_private_account_keys_1(); @@ -222,7 +213,7 @@ mod tests { AccountId::new([0; 32]), ); - let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); + let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0)); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let balance_to_move: u128 = 37; @@ -249,14 +240,10 @@ mod tests { let (output, proof) = execute_and_prove( vec![sender, recipient], Program::serialize_instruction(balance_to_move).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: shared_secret, - identifier: 0, - }, - ], + vec![0, 2], + vec![(recipient_keys.npk(), 0, shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -296,12 +283,12 @@ mod tests { data: Data::default(), }, true, - AccountId::for_regular_private_account(&sender_keys.npk(), 0), + AccountId::from((&sender_keys.npk(), 0)), ); - let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); + let sender_account_id = AccountId::from((&sender_keys.npk(), 0)); let commitment_sender = Commitment::new(&sender_account_id, &sender_pre.account); - let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); + let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0)); let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id); let balance_to_move: u128 = 37; @@ -346,21 +333,13 @@ mod tests { let (output, proof) = execute_and_prove( vec![sender_pre, recipient], Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: shared_secret_1, - nsk: sender_keys.nsk, - membership_proof: commitment_set - .get_proof_for(&commitment_sender) - .expect("sender's commitment must be in the set"), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: shared_secret_2, - identifier: 0, - }, + (sender_keys.npk(), 0, shared_secret_1), + (recipient_keys.npk(), 0, shared_secret_2), ], + vec![sender_keys.nsk], + vec![commitment_set.get_proof_for(&commitment_sender), None], &program.into(), ) .unwrap(); @@ -397,7 +376,7 @@ mod tests { let pre = AccountWithMetadata::new( Account::default(), false, - AccountId::for_regular_private_account(&account_keys.npk(), 0), + AccountId::from((&account_keys.npk(), 0)), ); let validity_window_chain_caller = Program::validity_window_chain_caller(); @@ -423,431 +402,10 @@ mod tests { let result = execute_and_prove( vec![pre], instruction, - vec![InputAccountIdentity::PrivateUnauthorized { - npk: account_keys.npk(), - ssk: shared_secret, - identifier: 0, - }], - &program_with_deps, - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - /// A private PDA claimed with a non-default identifier produces a ciphertext that decrypts - /// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`. - #[test] - fn private_pda_claim_with_custom_identifier_encrypts_correct_kind() { - let program = Program::pda_claimer(); - let keys = test_private_account_keys_1(); - let npk = keys.npk(); - let seed = PdaSeed::new([42; 32]); - let identifier: u128 = 99; - let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier); - let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); - - let (output, _proof) = execute_and_prove( - vec![pre_state], - Program::serialize_instruction(seed).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier, - }], - &program.clone().into(), - ) - .unwrap(); - - assert_eq!( - decrypt_kind(&output, &shared_secret, 0), - PrivateAccountKind::Pda { - program_id: program.id(), - seed, - identifier - }, - ); - } - - /// PDA init: initializes a new PDA under `authenticated_transfer`'s ownership. - /// The `auth_transfer_proxy` program chains to `authenticated_transfer` with `pda_seeds` - /// to establish authorization and the private PDA binding. - #[test] - fn private_pda_init() { - let program = Program::auth_transfer_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let npk = keys.npk(); - let seed = PdaSeed::new([42; 32]); - let shared_secret_pda = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - // PDA (new, mask 3) - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); - let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id); - - let auth_id = auth_transfer.id(); - let program_with_deps = - ProgramWithDependencies::new(program, [(auth_id, auth_transfer)].into()); - - // is_withdraw=false triggers init path (1 pre-state) - let instruction = Program::serialize_instruction((seed, auth_id, 0_u128, false)).unwrap(); - - let result = execute_and_prove( - vec![pda_pre], - instruction, - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret_pda, - identifier: 0, - }], - &program_with_deps, - ); - - let (output, _proof) = result.expect("PDA init should succeed"); - assert_eq!(output.new_commitments.len(), 1); - } - - /// PDA withdraw: chains to `authenticated_transfer` to move balance from PDA to recipient. - /// Uses a default PDA (amount=0) because testing with a pre-funded PDA requires a - /// two-tx sequence with membership proofs. - #[test] - fn private_pda_withdraw() { - let program = Program::auth_transfer_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let npk = keys.npk(); - let seed = PdaSeed::new([42; 32]); - let shared_secret_pda = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - // PDA (new, private PDA) - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); - let pda_pre = AccountWithMetadata::new(Account::default(), false, pda_id); - - // Recipient (public) - let recipient_id = AccountId::new([88; 32]); - let recipient_pre = AccountWithMetadata::new( - Account { - program_owner: auth_transfer.id(), - balance: 10000, - ..Account::default() - }, - true, - recipient_id, - ); - - let auth_id = auth_transfer.id(); - let program_with_deps = - ProgramWithDependencies::new(program, [(auth_id, auth_transfer)].into()); - - // is_withdraw=true, amount=0 (PDA has no balance yet) - let instruction = Program::serialize_instruction((seed, auth_id, 0_u128, true)).unwrap(); - - let result = execute_and_prove( - vec![pda_pre, recipient_pre], - instruction, - vec![ - InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret_pda, - identifier: 0, - }, - InputAccountIdentity::Public, - ], - &program_with_deps, - ); - - let (output, _proof) = result.expect("PDA withdraw should succeed"); - assert_eq!(output.new_commitments.len(), 1); - } - - /// Shared regular private account: receives funds via `authenticated_transfer` directly, - /// no custom program needed. This demonstrates the non-PDA shared account flow where - /// keys are derived from GMS via `derive_keys_for_shared_account`. The shared account - /// uses the standard unauthorized private account path and works with auth-transfer's - /// transfer path like any other private account. - #[test] - fn shared_account_receives_via_auth_transfer() { - let program = Program::authenticated_transfer_program(); - let shared_keys = test_private_account_keys_1(); - let shared_npk = shared_keys.npk(); - let shared_identifier: u128 = 42; - let shared_secret = SharedSecretKey::new(&[55; 32], &shared_keys.vpk()); - - // Sender: public account with balance, owned by auth-transfer - let sender_id = AccountId::new([99; 32]); - let sender = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 1000, - ..Account::default() - }, - true, - sender_id, - ); - - // Recipient: shared private account (new, unauthorized) - let shared_account_id = AccountId::from((&shared_npk, shared_identifier)); - let recipient = AccountWithMetadata::new(Account::default(), false, shared_account_id); - - let balance_to_move: u128 = 100; - let instruction = Program::serialize_instruction(balance_to_move).unwrap(); - - let result = execute_and_prove( - vec![sender, recipient], - instruction, - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateUnauthorized { - npk: shared_npk, - ssk: shared_secret, - identifier: shared_identifier, - }, - ], - &program.into(), - ); - - let (output, _proof) = result.expect("shared account receive should succeed"); - // Sender is public (no commitment), recipient is private (1 commitment) - assert_eq!(output.new_commitments.len(), 1); - } - - /// `PrivateAuthorizedInit` with a non-default identifier produces a ciphertext that decrypts - /// to `PrivateAccountKind::Regular` carrying the correct identifier. - #[test] - fn private_authorized_init_encrypts_regular_kind_with_identifier() { - let program = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let identifier: u128 = 99; - let ssk = SharedSecretKey::new(&[55; 32], &keys.vpk()); - let account_id = AccountId::for_regular_private_account(&keys.npk(), identifier); - let pre = AccountWithMetadata::new(Account::default(), true, account_id); - - let (output, _) = execute_and_prove( - vec![pre], - Program::serialize_instruction(0_u128).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedInit { - ssk, - nsk: keys.nsk, - identifier, - }], - &program.into(), - ) - .unwrap(); - - assert_eq!( - decrypt_kind(&output, &ssk, 0), - PrivateAccountKind::Regular(identifier) - ); - } - - /// `PrivateUnauthorized` with a non-default identifier produces a ciphertext that decrypts - /// to `PrivateAccountKind::Regular` carrying the correct identifier. - #[test] - fn private_unauthorized_init_encrypts_regular_kind_with_identifier() { - let program = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let identifier: u128 = 99; - let ssk = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - let sender = AccountWithMetadata::new( - Account { - program_owner: program.id(), - balance: 1, - ..Account::default() - }, - true, - AccountId::new([0; 32]), - ); - let recipient_id = AccountId::for_regular_private_account(&keys.npk(), identifier); - let recipient = AccountWithMetadata::new(Account::default(), false, recipient_id); - - let (output, _) = execute_and_prove( - vec![sender, recipient], - Program::serialize_instruction(1_u128).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateUnauthorized { - npk: keys.npk(), - ssk, - identifier, - }, - ], - &program.into(), - ) - .unwrap(); - - assert_eq!( - decrypt_kind(&output, &ssk, 0), - PrivateAccountKind::Regular(identifier) - ); - } - - /// `PrivateAuthorizedUpdate` with a non-default identifier produces a ciphertext that decrypts - /// to `PrivateAccountKind::Regular` carrying the correct identifier. - #[test] - fn private_authorized_update_encrypts_regular_kind_with_identifier() { - let program = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let identifier: u128 = 99; - let ssk = SharedSecretKey::new(&[55; 32], &keys.vpk()); - let account_id = AccountId::for_regular_private_account(&keys.npk(), identifier); - let account = Account { - program_owner: program.id(), - balance: 1, - ..Account::default() - }; - let commitment = Commitment::new(&account_id, &account); - let mut commitment_set = CommitmentSet::with_capacity(1); - commitment_set.extend(std::slice::from_ref(&commitment)); - - let sender = AccountWithMetadata::new(account, true, account_id); - let recipient = AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32])); - - let (output, _) = execute_and_prove( - vec![sender, recipient], - Program::serialize_instruction(1_u128).unwrap(), - vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk, - nsk: keys.nsk, - membership_proof: commitment_set.get_proof_for(&commitment).unwrap(), - identifier, - }, - InputAccountIdentity::Public, - ], - &program.into(), - ) - .unwrap(); - - assert_eq!( - decrypt_kind(&output, &ssk, 0), - PrivateAccountKind::Regular(identifier) - ); - } - - /// `PrivatePdaUpdate` with a non-default identifier produces a ciphertext that decrypts - /// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`. - #[test] - fn private_pda_update_encrypts_pda_kind_with_identifier() { - let program = Program::pda_fund_spend_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let npk = keys.npk(); - let seed = PdaSeed::new([42; 32]); - let identifier: u128 = 99; - let ssk = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - let auth_transfer_id = auth_transfer.id(); - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier); - let pda_account = Account { - program_owner: auth_transfer_id, - balance: 1, - ..Account::default() - }; - let pda_commitment = Commitment::new(&pda_id, &pda_account); - let mut commitment_set = CommitmentSet::with_capacity(1); - commitment_set.extend(std::slice::from_ref(&pda_commitment)); - - let pda_pre = AccountWithMetadata::new(pda_account, true, pda_id); - let recipient_pre = - AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32])); - - let program_with_deps = ProgramWithDependencies::new( - program.clone(), - [(auth_transfer_id, auth_transfer)].into(), - ); - - let (output, _) = execute_and_prove( - vec![pda_pre, recipient_pre], - Program::serialize_instruction((seed, 1_u128, auth_transfer_id, false)).unwrap(), - vec![ - InputAccountIdentity::PrivatePdaUpdate { - ssk, - nsk: keys.nsk, - membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(), - identifier, - }, - InputAccountIdentity::Public, - ], - &program_with_deps, - ) - .unwrap(); - - assert_eq!( - decrypt_kind(&output, &ssk, 0), - PrivateAccountKind::Pda { - program_id: program.id(), - seed, - identifier - }, - ); - } - - #[test] - fn private_pda_init_identifier_mismatch_fails() { - let program = Program::pda_claimer(); - let keys = test_private_account_keys_1(); - let npk = keys.npk(); - let seed = PdaSeed::new([42; 32]); - let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5); - let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); - - let result = execute_and_prove( - vec![pre_state], - Program::serialize_instruction(seed).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier: 99, - }], - &program.into(), - ); - - assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); - } - - #[test] - fn private_pda_update_identifier_mismatch_fails() { - let program = Program::pda_fund_spend_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); - let keys = test_private_account_keys_1(); - let npk = keys.npk(); - let seed = PdaSeed::new([42; 32]); - let ssk = SharedSecretKey::new(&[55; 32], &keys.vpk()); - - let auth_transfer_id = auth_transfer.id(); - let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5); - let pda_account = Account { - program_owner: auth_transfer_id, - balance: 1, - ..Account::default() - }; - let pda_commitment = Commitment::new(&pda_id, &pda_account); - let mut commitment_set = CommitmentSet::with_capacity(1); - commitment_set.extend(std::slice::from_ref(&pda_commitment)); - - let pda_pre = AccountWithMetadata::new(pda_account, true, pda_id); - let recipient_pre = - AccountWithMetadata::new(Account::default(), true, AccountId::new([0; 32])); - - let program_with_deps = - ProgramWithDependencies::new(program, [(auth_transfer_id, auth_transfer)].into()); - - let result = execute_and_prove( - vec![pda_pre, recipient_pre], - Program::serialize_instruction((seed, 1_u128, auth_transfer_id, false)).unwrap(), - vec![ - InputAccountIdentity::PrivatePdaUpdate { - ssk, - nsk: keys.nsk, - membership_proof: commitment_set.get_proof_for(&pda_commitment).unwrap(), - identifier: 99, - }, - InputAccountIdentity::Public, - ], + vec![2], + vec![(account_keys.npk(), 0, shared_secret)], + vec![], + vec![None], &program_with_deps, ); diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index d86273d8..01e6e04f 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -122,16 +122,15 @@ impl Message { } #[must_use] - pub fn hash(&self) -> [u8; 32] { - let msg = self.to_bytes(); + pub fn hash_message(&self) -> [u8; 32] { let mut bytes = Vec::with_capacity( PREFIX .len() - .checked_add(msg.len()) + .checked_add(self.to_bytes().len()) .expect("length overflow"), ); bytes.extend_from_slice(PREFIX); - bytes.extend_from_slice(&msg); + bytes.extend_from_slice(&self.to_bytes()); Sha256::digest(bytes).into() } @@ -140,8 +139,7 @@ impl Message { #[cfg(test)] pub mod tests { use nssa_core::{ - Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, PrivateAccountKind, - SharedSecretKey, + Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, SharedSecretKey, account::{Account, AccountId, Nonce}, encryption::{EphemeralPublicKey, ViewingPublicKey}, program::{BlockValidityWindow, TimestampValidityWindow}, @@ -169,10 +167,10 @@ pub mod tests { let encrypted_private_post_states = Vec::new(); - let account_id2 = nssa_core::account::AccountId::for_regular_private_account(&npk2, 0); + let account_id2 = nssa_core::account::AccountId::from((&npk2, 0)); let new_commitments = vec![Commitment::new(&account_id2, &account2)]; - let account_id1 = nssa_core::account::AccountId::for_regular_private_account(&npk1, 0); + let account_id1 = nssa_core::account::AccountId::from((&npk1, 0)); let old_commitment = Commitment::new(&account_id1, &account1); let new_nullifiers = vec![( Nullifier::for_account_update(&old_commitment, &nsk1), @@ -192,7 +190,7 @@ pub mod tests { } #[test] - fn hash_privacy_pinned() { + fn hash_message_privacy_pinned() { let msg = Message { public_account_ids: vec![AccountId::new([42_u8; 32])], nonces: vec![Nonce(5)], @@ -208,7 +206,7 @@ pub mod tests { let nonces_bytes: &[u8] = &[1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // all remaining vec fields are empty: u32 len=0 let empty_vec_bytes: &[u8] = &[0_u8; 4]; - // validity windows: unbounded = {from: None (0u8), to: None (0u8)} + // validity windows: unbounded = {from: None (0_u8), to: None (0_u8)} let unbounded_window_bytes: &[u8] = &[0_u8; 2]; let expected_borsh_vec: Vec = [ @@ -228,7 +226,7 @@ pub mod tests { assert_eq!( borsh::to_vec(&msg).unwrap(), expected_borsh, - "`privacy_preserving_transaction::hash()`: expected borsh order has changed" + "`privacy_preserving_transaction::hash_message()`: expected borsh order has changed" ); let mut preimage = Vec::with_capacity(PREFIX.len() + expected_borsh.len()); @@ -237,9 +235,9 @@ pub mod tests { let expected_hash: [u8; 32] = Sha256::digest(&preimage).into(); assert_eq!( - msg.hash(), + msg.hash_message(), expected_hash, - "`privacy_preserving_transaction::hash()`: serialization has changed" + "`privacy_preserving_transaction::hash_message()`: serialization has changed" ); } @@ -248,18 +246,12 @@ pub mod tests { let npk = NullifierPublicKey::from(&[1; 32]); let vpk = ViewingPublicKey::from_scalar([2; 32]); let account = Account::default(); - let account_id = nssa_core::account::AccountId::for_regular_private_account(&npk, 0); + let account_id = nssa_core::account::AccountId::from((&npk, 0)); let commitment = Commitment::new(&account_id, &account); let esk = [3; 32]; let shared_secret = SharedSecretKey::new(&esk, &vpk); let epk = EphemeralPublicKey::from_scalar(esk); - let ciphertext = EncryptionScheme::encrypt( - &account, - &PrivateAccountKind::Regular(0), - &shared_secret, - &commitment, - 2, - ); + let ciphertext = EncryptionScheme::encrypt(&account, 0, &shared_secret, &commitment, 2); let encrypted_account_data = EncryptedAccountData::new(ciphertext.clone(), &npk, &vpk, epk.clone()); diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 20f4c7ca..43a36671 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -15,7 +15,7 @@ impl WitnessSet { #[must_use] // TODO: swap for Keycard signing path. pub fn for_message(message: &Message, proof: Proof, private_keys: &[&PrivateKey]) -> Self { - let message_hash = message.hash(); + let message_hash = message.hash_message(); let signatures_and_public_keys = private_keys .iter() .map(|&key| { @@ -49,7 +49,7 @@ impl WitnessSet { #[must_use] pub fn signatures_are_valid_for(&self, message: &Message) -> bool { - let message_hash = message.hash(); + let message_hash = message.hash_message(); for (signature, public_key) in self.signatures_and_public_keys() { if !signature.is_valid_for(&message_hash, public_key) { return false; diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 059aa5ca..b8c3fe77 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -312,16 +312,6 @@ mod tests { } } - #[must_use] - pub fn auth_transfer_proxy() -> Self { - use test_program_methods::{AUTH_TRANSFER_PROXY_ELF, AUTH_TRANSFER_PROXY_ID}; - - Self { - id: AUTH_TRANSFER_PROXY_ID, - elf: AUTH_TRANSFER_PROXY_ELF.to_vec(), - } - } - #[must_use] pub fn two_pda_claimer() -> Self { use test_program_methods::{TWO_PDA_CLAIMER_ELF, TWO_PDA_CLAIMER_ID}; @@ -332,16 +322,6 @@ mod tests { } } - #[must_use] - pub fn pda_fund_spend_proxy() -> Self { - use test_program_methods::{PDA_FUND_SPEND_PROXY_ELF, PDA_FUND_SPEND_PROXY_ID}; - - Self { - id: PDA_FUND_SPEND_PROXY_ID, - elf: PDA_FUND_SPEND_PROXY_ELF.to_vec(), - } - } - #[must_use] pub fn changer_claimer() -> Self { use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID}; diff --git a/nssa/src/public_transaction/message.rs b/nssa/src/public_transaction/message.rs index 3ab7d74c..f71fb372 100644 --- a/nssa/src/public_transaction/message.rs +++ b/nssa/src/public_transaction/message.rs @@ -68,7 +68,7 @@ impl Message { } #[must_use] - pub fn hash(&self) -> [u8; 32] { + pub fn hash_message(&self) -> [u8; 32] { let mut bytes = Vec::with_capacity( PREFIX .len() @@ -90,7 +90,7 @@ mod tests { use super::{Message, PREFIX}; #[test] - fn hash_public_pinned() { + fn hash_message_public_pinned() { let msg = Message::new_preserialized( [1_u32; 8], vec![AccountId::new([42_u8; 32])], @@ -122,7 +122,7 @@ mod tests { assert_eq!( borsh::to_vec(&msg).unwrap(), expected_borsh, - "`public_transaction::hash()`: expected borsh order has changed" + "`public_transaction::hash_message()`: expected borsh order has changed" ); let mut preimage = Vec::with_capacity(PREFIX.len() + expected_borsh.len()); @@ -131,9 +131,9 @@ mod tests { let expected_hash: [u8; 32] = Sha256::digest(&preimage).into(); assert_eq!( - msg.hash(), + msg.hash_message(), expected_hash, - "`public_transaction::hash()`: serialization has changed" + "`public_transaction::hash_message()`: serialization has changed" ); } } diff --git a/nssa/src/public_transaction/witness_set.rs b/nssa/src/public_transaction/witness_set.rs index f5721091..7a32c0ea 100644 --- a/nssa/src/public_transaction/witness_set.rs +++ b/nssa/src/public_transaction/witness_set.rs @@ -41,7 +41,7 @@ impl WitnessSet { #[must_use] pub fn for_message(message: &Message, private_keys: &[&PrivateKey]) -> Self { - let message_hash = message.hash(); + let message_hash = message.hash_message(); let signatures_and_public_keys = private_keys .iter() .map(|&key| { @@ -58,7 +58,7 @@ impl WitnessSet { #[must_use] pub fn is_valid_for(&self, message: &Message) -> bool { - let message_hash = message.hash(); + let message_hash = message.hash_message(); for (signature, public_key) in self.signatures_and_public_keys() { if !signature.is_valid_for(&message_hash, public_key) { return false; @@ -173,7 +173,7 @@ mod tests { assert_eq!(witness_set.signatures_and_public_keys.len(), 2); - let message_bytes = message.hash(); + let message_bytes = message.hash_message(); for ((signature, public_key), expected_public_key) in witness_set .signatures_and_public_keys .into_iter() diff --git a/nssa/src/signature/mod.rs b/nssa/src/signature/mod.rs index a46b1ff5..19daca2e 100644 --- a/nssa/src/signature/mod.rs +++ b/nssa/src/signature/mod.rs @@ -36,9 +36,9 @@ impl FromStr for Signature { } impl Signature { + #[must_use] /// This function expects the incoming message to be prehashed to be pre-2022 BIP-340/Keycard /// compatible. - #[must_use] pub fn new(key: &PrivateKey, message: &[u8; 32]) -> Self { let mut aux_random = [0_u8; 32]; OsRng.fill_bytes(&mut aux_random); diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 8cf1d68e..ff16175c 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -362,8 +362,8 @@ pub mod tests { use std::collections::HashMap; use nssa_core::{ - BlockId, Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, - NullifierSecretKey, SharedSecretKey, Timestamp, + BlockId, Commitment, Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + Timestamp, account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data}, encryption::{EphemeralPublicKey, Scalar, ViewingPublicKey}, program::{ @@ -459,7 +459,7 @@ pub mod tests { #[must_use] pub fn with_private_account(mut self, keys: &TestPrivateKeys, account: &Account) -> Self { - let account_id = AccountId::for_regular_private_account(&keys.npk(), 0); + let account_id = AccountId::from((&keys.npk(), 0)); let commitment = Commitment::new(&account_id, account); self.private_state.0.extend(&[commitment]); self @@ -618,8 +618,8 @@ pub mod tests { ..Account::default() }; - let account_id1 = AccountId::for_regular_private_account(&keys1.npk(), 0); - let account_id2 = AccountId::for_regular_private_account(&keys2.npk(), 0); + let account_id1 = AccountId::from((&keys1.npk(), 0)); + let account_id2 = AccountId::from((&keys2.npk(), 0)); let init_commitment1 = Commitment::new(&account_id1, &account); let init_commitment2 = Commitment::new(&account_id2, &account); @@ -1256,12 +1256,6 @@ pub mod tests { } } - fn test_public_account_keys_2() -> TestPublicKeys { - TestPublicKeys { - signing_key: PrivateKey::try_new([38; 32]).unwrap(), - } - } - pub fn test_private_account_keys_1() -> TestPrivateKeys { TestPrivateKeys { nsk: [13; 32], @@ -1300,14 +1294,10 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender, recipient], Program::serialize_instruction(balance_to_move).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: shared_secret, - identifier: 0, - }, - ], + vec![0, 2], + vec![(recipient_keys.npk(), 0, shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -1332,7 +1322,7 @@ pub mod tests { state: &V03State, ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); - let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); + let sender_account_id = AccountId::from((&sender_keys.npk(), 0)); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_pre = AccountWithMetadata::new( sender_private_account.clone(), @@ -1353,21 +1343,13 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: shared_secret_1, - nsk: sender_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&sender_commitment) - .expect("sender's commitment must be in state"), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: shared_secret_2, - identifier: 0, - }, + (sender_keys.npk(), 0, shared_secret_1), + (recipient_keys.npk(), 0, shared_secret_2), ], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment), None], &program.into(), ) .unwrap(); @@ -1396,7 +1378,7 @@ pub mod tests { state: &V03State, ) -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); - let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); + let sender_account_id = AccountId::from((&sender_keys.npk(), 0)); let sender_commitment = Commitment::new(&sender_account_id, sender_private_account); let sender_pre = AccountWithMetadata::new( sender_private_account.clone(), @@ -1416,17 +1398,10 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(balance_to_move).unwrap(), - vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: shared_secret, - nsk: sender_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&sender_commitment) - .expect("sender's commitment must be in state"), - identifier: 0, - }, - InputAccountIdentity::Public, - ], + vec![1, 0], + vec![(sender_keys.npk(), 0, shared_secret)], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment)], &program.into(), ) .unwrap(); @@ -1511,8 +1486,8 @@ pub mod tests { &state, ); - let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); - let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); + let sender_account_id = AccountId::from((&sender_keys.npk(), 0)); + let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0)); let expected_new_commitment_1 = Commitment::new( &sender_account_id, &Account { @@ -1590,7 +1565,7 @@ pub mod tests { &state, ); - let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); + let sender_account_id = AccountId::from((&sender_keys.npk(), 0)); let expected_new_commitment = Commitment::new( &sender_account_id, &Account { @@ -1640,7 +1615,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(10_u128).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1663,7 +1641,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(10_u128).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1686,7 +1667,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(()).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1709,7 +1693,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(vec![0]).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1740,7 +1727,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(large_data).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1763,7 +1753,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(()).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1795,7 +1788,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account_1, public_account_2], Program::serialize_instruction(()).unwrap(), - vec![InputAccountIdentity::Public, InputAccountIdentity::Public], + vec![0, 0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1818,7 +1814,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(()).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1850,7 +1849,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account_1, public_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![InputAccountIdentity::Public, InputAccountIdentity::Public], + vec![0, 0], + vec![], + vec![], + vec![], &program.into(), ); @@ -1879,11 +1881,177 @@ pub mod tests { AccountId::new([1; 32]), ); - // Single account_identity entry for a circuit execution with two pre_state accounts. + // Setting only one visibility mask for a circuit execution with two pre_state accounts. + let visibility_mask = [0]; let result = execute_and_prove( vec![public_account_1, public_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![InputAccountIdentity::Public], + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_nonces_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![ + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + + // Setting only one key for an execution with two private accounts. + let private_account_keys = [( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + )]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_commitment_proofs_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); + + // Setting no second commitment proof. + let private_account_membership_proofs = [Some((0, vec![]))]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![ + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + private_account_membership_proofs.to_vec(), + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_fails_if_insufficient_auth_keys_are_provided() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); + + // Setting no auth key for an execution with one non default private accounts. + let private_account_nsks = []; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![ + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + private_account_nsks.to_vec(), + vec![], &program.into(), ); @@ -1907,26 +2075,33 @@ pub mod tests { let private_account_2 = AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); - // Setting the recipient nsk to authorize the sender. - // This should be set to the sender private account in a normal circumstance. - // `PrivateAuthorizedUpdate` derives npk from nsk and asserts equality with - // `pre_state.account_id`, so a mismatched nsk fails that check. + let private_account_keys = [ + // First private account is the sender + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + // Second private account is the recipient + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ]; + + // Setting the recipient key to authorize the sender. + // This should be set to the sender private account in + // a normal circumstance. The recipient can't authorize this. + let private_account_nsks = [recipient_keys.nsk]; + let private_account_membership_proofs = [Some((0, vec![]))]; let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), - vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - nsk: recipient_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - identifier: 0, - }, - ], + vec![1, 2], + private_account_keys.to_vec(), + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1960,19 +2135,21 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - identifier: 0, - }, + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2006,19 +2183,21 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - identifier: 0, - }, + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2052,19 +2231,21 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - identifier: 0, - }, + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2098,19 +2279,21 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - identifier: 0, - }, + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2142,19 +2325,21 @@ pub mod tests { let result = execute_and_prove( vec![private_account_1, private_account_2], Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - npk: recipient_keys.npk(), - ssk: SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), - identifier: 0, - }, + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2183,17 +2368,14 @@ pub mod tests { let private_pda_account = AccountWithMetadata::new(Account::default(), false, AccountId::new([1; 32])); + let visibility_mask = [0, 3]; let result = execute_and_prove( vec![public_account_1, private_pda_account], Program::serialize_instruction(10_u128).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier: u128::MAX, - }, - ], + visibility_mask.to_vec(), + vec![(npk, 0, shared_secret)], + vec![], + vec![None], &program.into(), ); @@ -2213,17 +2395,16 @@ pub mod tests { let seed = PdaSeed::new([42; 32]); let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk()); - let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, u128::MAX); + let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let result = execute_and_prove( vec![pre_state], Program::serialize_instruction(seed).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier: u128::MAX, - }], + vec![3], + vec![(npk, u128::MAX, shared_secret)], + vec![], + vec![None], &program.into(), ); @@ -2252,17 +2433,16 @@ pub mod tests { // `account_id` is derived from `npk_a`, but `npk_b` is supplied for this pre_state. // `AccountId::for_private_pda(program, seed, npk_b) != account_id`, so the claim check in // the circuit must reject. - let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk_a, u128::MAX); + let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk_a); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let result = execute_and_prove( vec![pre_state], Program::serialize_instruction(seed).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk: npk_b, - ssk: shared_secret, - identifier: u128::MAX, - }], + vec![3], + vec![(npk_b, 0, shared_secret)], + vec![], + vec![None], &program.into(), ); @@ -2283,7 +2463,7 @@ pub mod tests { let seed = PdaSeed::new([77; 32]); let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk()); - let account_id = AccountId::for_private_pda(&delegator.id(), &seed, &npk, u128::MAX); + let account_id = AccountId::for_private_pda(&delegator.id(), &seed, &npk); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let callee_id = callee.id(); @@ -2293,11 +2473,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_state], Program::serialize_instruction((seed, seed, callee_id)).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier: u128::MAX, - }], + vec![3], + vec![(npk, u128::MAX, shared_secret)], + vec![], + vec![None], &program_with_deps, ); @@ -2321,7 +2500,7 @@ pub mod tests { let wrong_delegated_seed = PdaSeed::new([88; 32]); let shared_secret = SharedSecretKey::new(&[55; 32], &keys.vpk()); - let account_id = AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk, u128::MAX); + let account_id = AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); let callee_id = callee.id(); @@ -2331,11 +2510,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_state], Program::serialize_instruction((claim_seed, wrong_delegated_seed, callee_id)).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier: u128::MAX, - }], + vec![3], + vec![(npk, 0, shared_secret)], + vec![], + vec![None], &program_with_deps, ); @@ -2359,8 +2537,8 @@ pub mod tests { let shared_a = SharedSecretKey::new(&[66; 32], &keys_a.vpk()); let shared_b = SharedSecretKey::new(&[77; 32], &keys_b.vpk()); - let account_a = AccountId::for_private_pda(&program.id(), &seed, &keys_a.npk(), u128::MAX); - let account_b = AccountId::for_private_pda(&program.id(), &seed, &keys_b.npk(), u128::MAX); + let account_a = AccountId::for_private_pda(&program.id(), &seed, &keys_a.npk()); + let account_b = AccountId::for_private_pda(&program.id(), &seed, &keys_b.npk()); let pre_a = AccountWithMetadata::new(Account::default(), false, account_a); let pre_b = AccountWithMetadata::new(Account::default(), false, account_b); @@ -2368,18 +2546,10 @@ pub mod tests { let result = execute_and_prove( vec![pre_a, pre_b], Program::serialize_instruction(seed).unwrap(), - vec![ - InputAccountIdentity::PrivatePdaInit { - npk: keys_a.npk(), - ssk: shared_a, - identifier: u128::MAX, - }, - InputAccountIdentity::PrivatePdaInit { - npk: keys_b.npk(), - ssk: shared_b, - identifier: u128::MAX, - }, - ], + vec![3, 3], + vec![(keys_a.npk(), 0, shared_a), (keys_b.npk(), 0, shared_b)], + vec![], + vec![None, None], &program.into(), ); @@ -2407,7 +2577,7 @@ pub mod tests { // Simulate a previously-claimed private PDA: program_owner != DEFAULT, is_authorized = // true, account_id derived via the private formula. - let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, u128::MAX); + let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk); let owned_pre_state = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2420,11 +2590,146 @@ pub mod tests { let result = execute_and_prove( vec![owned_pre_state], Program::serialize_instruction(()).unwrap(), - vec![InputAccountIdentity::PrivatePdaInit { - npk, - ssk: shared_secret, - identifier: u128::MAX, - }], + vec![3], + vec![(npk, 0, shared_secret)], + vec![], + vec![None], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_too_many_nonces() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); + + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + vec![ + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_too_many_private_account_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); + + // Setting three private account keys for a circuit execution with only two private + // accounts. + let private_account_keys = [ + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[57; 32], &sender_keys.vpk()), + ), + ]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + vec![1, 2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn circuit_should_fail_with_too_many_private_account_auth_keys() { + let program = Program::simple_balance_transfer(); + let sender_keys = test_private_account_keys_1(); + let recipient_keys = test_private_account_keys_2(); + let private_account_1 = AccountWithMetadata::new( + Account { + program_owner: program.id(), + balance: 100, + ..Account::default() + }, + true, + (&sender_keys.npk(), 0), + ); + let private_account_2 = + AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); + + // Setting two private account keys for a circuit execution with only one non default + // private account (visibility mask equal to 1 means that auth keys are expected). + let visibility_mask = [1, 2]; + let private_account_nsks = [sender_keys.nsk, recipient_keys.nsk]; + let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; + let result = execute_and_prove( + vec![private_account_1, private_account_2], + Program::serialize_instruction(10_u128).unwrap(), + visibility_mask.to_vec(), + vec![ + ( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[55; 32], &sender_keys.vpk()), + ), + ( + recipient_keys.npk(), + 0, + SharedSecretKey::new(&[56; 32], &recipient_keys.vpk()), + ), + ], + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -2501,24 +2806,20 @@ pub mod tests { (&sender_keys.npk(), 0), ); + let visibility_mask = [1, 1]; + let private_account_nsks = [sender_keys.nsk, sender_keys.nsk]; + let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.vpk()); let result = execute_and_prove( vec![private_account_1.clone(), private_account_1], Program::serialize_instruction(100_u128).unwrap(), + visibility_mask.to_vec(), vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: shared_secret, - nsk: sender_keys.nsk, - membership_proof: (1, vec![]), - identifier: 0, - }, - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: shared_secret, - nsk: sender_keys.nsk, - membership_proof: (1, vec![]), - identifier: 0, - }, + (sender_keys.npk(), 0, shared_secret), + (sender_keys.npk(), 0, shared_secret), ], + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -2809,7 +3110,10 @@ pub mod tests { let result = execute_and_prove( vec![public_account], Program::serialize_instruction(0_u128).unwrap(), - vec![InputAccountIdentity::Public], + vec![0], + vec![], + vec![], + vec![], &program.into(), ); @@ -2826,7 +3130,7 @@ pub mod tests { balance: 100, ..Account::default() }; - let sender_account_id = AccountId::for_regular_private_account(&sender_keys.npk(), 0); + let sender_account_id = AccountId::from((&sender_keys.npk(), 0)); let sender_commitment = Commitment::new(&sender_account_id, &sender_private_account); let sender_init_nullifier = Nullifier::for_account_initialization(&sender_account_id); let mut state = V03State::new_with_genesis_accounts( @@ -2848,17 +3152,10 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![sender_pre, recipient_pre], Program::serialize_instruction(37_u128).unwrap(), - vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: shared_secret, - nsk: sender_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&sender_commitment) - .expect("sender's commitment must be in state"), - identifier: 0, - }, - InputAccountIdentity::Public, - ], + vec![1, 0], + vec![(sender_keys.npk(), 0, shared_secret)], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment)], &program.into(), ) .unwrap(); @@ -2919,8 +3216,8 @@ pub mod tests { (&to_keys.npk(), 0), ); - let from_account_id = AccountId::for_regular_private_account(&from_keys.npk(), 0); - let to_account_id = AccountId::for_regular_private_account(&to_keys.npk(), 0); + let from_account_id = AccountId::from((&from_keys.npk(), 0)); + let to_account_id = AccountId::from((&to_keys.npk(), 0)); let from_commitment = Commitment::new(&from_account_id, &from_account.account); let to_commitment = Commitment::new(&to_account_id, &to_account.account); let from_init_nullifier = Nullifier::for_account_initialization(&from_account_id); @@ -2976,23 +3273,12 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![to_account, from_account], Program::serialize_instruction(instruction).unwrap(), + vec![1, 1], + vec![(from_keys.npk(), 0, to_ss), (to_keys.npk(), 0, from_ss)], + vec![from_keys.nsk, to_keys.nsk], vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: to_ss, - nsk: from_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&from_commitment) - .expect("from's commitment must be in state"), - identifier: 0, - }, - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: from_ss, - nsk: to_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&to_commitment) - .expect("to's commitment must be in state"), - identifier: 0, - }, + state.get_proof_for_commitment(&from_commitment), + state.get_proof_for_commitment(&to_commitment), ], &program_with_deps, ) @@ -3256,11 +3542,10 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![authorized_account], Program::serialize_instruction(balance).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedInit { - ssk: shared_secret, - nsk: private_keys.nsk, - identifier: 0, - }], + vec![1], + vec![(private_keys.npk(), 0, shared_secret)], + vec![private_keys.nsk], + vec![None], &program.into(), ) .unwrap(); @@ -3280,7 +3565,7 @@ pub mod tests { let result = state.transition_from_privacy_preserving_transaction(&tx, 1, 0); assert!(result.is_ok()); - let account_id = AccountId::for_regular_private_account(&private_keys.npk(), 0); + let account_id = AccountId::from((&private_keys.npk(), 0)); let nullifier = Nullifier::for_account_initialization(&account_id); assert!(state.private_state.1.contains(&nullifier)); } @@ -3305,11 +3590,10 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![unauthorized_account], Program::serialize_instruction(0_u128).unwrap(), - vec![InputAccountIdentity::PrivateUnauthorized { - npk: private_keys.npk(), - ssk: shared_secret, - identifier: 0, - }], + vec![2], + vec![(private_keys.npk(), 0, shared_secret)], + vec![], + vec![None], &program.into(), ) .unwrap(); @@ -3329,7 +3613,7 @@ pub mod tests { .transition_from_privacy_preserving_transaction(&tx, 1, 0) .unwrap(); - let account_id = AccountId::for_regular_private_account(&private_keys.npk(), 0); + let account_id = AccountId::from((&private_keys.npk(), 0)); let nullifier = Nullifier::for_account_initialization(&account_id); assert!(state.private_state.1.contains(&nullifier)); } @@ -3358,11 +3642,10 @@ pub mod tests { let (output, proof) = execute_and_prove( vec![authorized_account.clone()], Program::serialize_instruction(balance).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedInit { - ssk: shared_secret, - nsk: private_keys.nsk, - identifier: 0, - }], + vec![1], + vec![(private_keys.npk(), 0, shared_secret)], + vec![private_keys.nsk], + vec![None], &claimer_program.into(), ) .unwrap(); @@ -3386,7 +3669,7 @@ pub mod tests { ); // Verify the account is now initialized (nullifier exists) - let account_id = AccountId::for_regular_private_account(&private_keys.npk(), 0); + let account_id = AccountId::from((&private_keys.npk(), 0)); let nullifier = Nullifier::for_account_initialization(&account_id); assert!(state.private_state.1.contains(&nullifier)); @@ -3405,11 +3688,10 @@ pub mod tests { let res = execute_and_prove( vec![account_metadata], Program::serialize_instruction(()).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedInit { - ssk: shared_secret2, - nsk: private_keys.nsk, - identifier: 0, - }], + vec![1], + vec![(private_keys.npk(), 0, shared_secret2)], + vec![private_keys.nsk], + vec![None], &noop_program.into(), ); @@ -3482,12 +3764,14 @@ pub mod tests { let result = execute_and_prove( vec![private_account], Program::serialize_instruction(instruction).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }], + vec![1], + vec![( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -3508,12 +3792,14 @@ pub mod tests { let result = execute_and_prove( vec![private_account], Program::serialize_instruction(instruction).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), - nsk: sender_keys.nsk, - membership_proof: (0, vec![]), - identifier: 0, - }], + vec![1], + vec![( + sender_keys.npk(), + 0, + SharedSecretKey::new(&[3; 32], &sender_keys.vpk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -3541,7 +3827,7 @@ pub mod tests { let recipient_account = AccountWithMetadata::new(Account::default(), true, (&recipient_keys.npk(), 0)); - let recipient_account_id = AccountId::for_regular_private_account(&recipient_keys.npk(), 0); + let recipient_account_id = AccountId::from((&recipient_keys.npk(), 0)); let recipient_commitment = Commitment::new(&recipient_account_id, &recipient_account.account); let recipient_init_nullifier = Nullifier::for_account_initialization(&recipient_account_id); @@ -3566,17 +3852,10 @@ pub mod tests { let result = execute_and_prove( vec![sender_account, recipient_account], Program::serialize_instruction(instruction).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: recipient, - nsk: recipient_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&recipient_commitment) - .expect("recipient's commitment must be in state"), - identifier: 0, - }, - ], + vec![0, 1], + vec![(recipient_keys.npk(), 0, recipient)], + vec![recipient_keys.nsk], + vec![state.get_proof_for_commitment(&recipient_commitment)], &program_with_deps, ); @@ -3723,11 +4002,10 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![pre], Program::serialize_instruction(instruction).unwrap(), - vec![InputAccountIdentity::PrivateUnauthorized { - npk: account_keys.npk(), - ssk: shared_secret, - identifier: 0, - }], + vec![2], + vec![(account_keys.npk(), 0, shared_secret)], + vec![], + vec![None], &validity_window_program.into(), ) .unwrap(); @@ -3793,11 +4071,10 @@ pub mod tests { let (output, proof) = circuit::execute_and_prove( vec![pre], Program::serialize_instruction(instruction).unwrap(), - vec![InputAccountIdentity::PrivateUnauthorized { - npk: account_keys.npk(), - ssk: shared_secret, - identifier: 0, - }], + vec![2], + vec![(account_keys.npk(), 0, shared_secret)], + vec![], + vec![None], &validity_window_program.into(), ) .unwrap(); @@ -4300,225 +4577,4 @@ pub mod tests { "program with spoofed caller_program_id in output should be rejected" ); } - - #[test] - fn two_private_pda_family_members_receive_and_spend() { - let funder_keys = test_public_account_keys_1(); - let alice_keys = test_private_account_keys_1(); - let alice_npk = alice_keys.npk(); - - let proxy = Program::pda_fund_spend_proxy(); - let auth_transfer = Program::authenticated_transfer_program(); - let proxy_id = proxy.id(); - let auth_transfer_id = auth_transfer.id(); - let seed = PdaSeed::new([42; 32]); - let amount: u128 = 100; - - let program_with_deps = - ProgramWithDependencies::new(proxy, [(auth_transfer_id, auth_transfer)].into()); - - let funder_id = funder_keys.account_id(); - let alice_pda_0_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 0); - let alice_pda_1_id = AccountId::for_private_pda(&proxy_id, &seed, &alice_npk, 1); - let recipient_id = test_public_account_keys_2().account_id(); - let recipient_signing_key = test_public_account_keys_2().signing_key; - - let mut state = V03State::new_with_genesis_accounts(&[(funder_id, 500)], vec![], 0); - - let alice_pda_0_account = Account { - program_owner: auth_transfer_id, - balance: amount, - nonce: Nonce::private_account_nonce_init(&alice_pda_0_id), - ..Account::default() - }; - let alice_pda_1_account = Account { - program_owner: auth_transfer_id, - balance: amount, - nonce: Nonce::private_account_nonce_init(&alice_pda_1_id), - ..Account::default() - }; - - let alice_shared_0 = SharedSecretKey::new(&[10; 32], &alice_keys.vpk()); - let alice_shared_1 = SharedSecretKey::new(&[11; 32], &alice_keys.vpk()); - - // Fund alice_pda_0 - { - let funder_account = state.get_account_by_id(funder_id); - let funder_nonce = funder_account.nonce; - let (output, proof) = execute_and_prove( - vec![ - AccountWithMetadata::new(funder_account, true, funder_id), - AccountWithMetadata::new(Account::default(), false, alice_pda_0_id), - ], - Program::serialize_instruction((seed, amount, auth_transfer_id, true)).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivatePdaInit { - npk: alice_npk, - ssk: alice_shared_0, - identifier: 0, - }, - ], - &program_with_deps, - ) - .unwrap(); - let message = Message::try_from_circuit_output( - vec![funder_id], - vec![funder_nonce], - vec![( - alice_npk, - alice_keys.vpk(), - EphemeralPublicKey::from_scalar([10; 32]), - )], - output, - ) - .unwrap(); - let witness_set = WitnessSet::for_message(&message, proof, &[&funder_keys.signing_key]); - state - .transition_from_privacy_preserving_transaction( - &PrivacyPreservingTransaction::new(message, witness_set), - 1, - 0, - ) - .unwrap(); - } - - // Fund alice_pda_1 - { - let funder_account = state.get_account_by_id(funder_id); - let funder_nonce = funder_account.nonce; - let (output, proof) = execute_and_prove( - vec![ - AccountWithMetadata::new(funder_account, true, funder_id), - AccountWithMetadata::new(Account::default(), false, alice_pda_1_id), - ], - Program::serialize_instruction((seed, amount, auth_transfer_id, true)).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivatePdaInit { - npk: alice_npk, - ssk: alice_shared_1, - identifier: 1, - }, - ], - &program_with_deps, - ) - .unwrap(); - let message = Message::try_from_circuit_output( - vec![funder_id], - vec![funder_nonce], - vec![( - alice_npk, - alice_keys.vpk(), - EphemeralPublicKey::from_scalar([11; 32]), - )], - output, - ) - .unwrap(); - let witness_set = WitnessSet::for_message(&message, proof, &[&funder_keys.signing_key]); - state - .transition_from_privacy_preserving_transaction( - &PrivacyPreservingTransaction::new(message, witness_set), - 2, - 0, - ) - .unwrap(); - } - - let commitment_pda_0 = Commitment::new(&alice_pda_0_id, &alice_pda_0_account); - let commitment_pda_1 = Commitment::new(&alice_pda_1_id, &alice_pda_1_account); - - assert!(state.get_proof_for_commitment(&commitment_pda_0).is_some()); - assert!(state.get_proof_for_commitment(&commitment_pda_1).is_some()); - - // Alice spends alice_pda_0 into the public recipient. - { - let recipient_account = state.get_account_by_id(recipient_id); - let (output, proof) = execute_and_prove( - vec![ - AccountWithMetadata::new(alice_pda_0_account, true, alice_pda_0_id), - AccountWithMetadata::new(recipient_account, true, recipient_id), - ], - Program::serialize_instruction((seed, amount, auth_transfer_id, false)).unwrap(), - vec![ - InputAccountIdentity::PrivatePdaUpdate { - ssk: alice_shared_0, - nsk: alice_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&commitment_pda_0) - .expect("pda_0 must be in state"), - identifier: 0, - }, - InputAccountIdentity::Public, - ], - &program_with_deps, - ) - .unwrap(); - let message = Message::try_from_circuit_output( - vec![recipient_id], - vec![Nonce(0)], - vec![( - alice_npk, - alice_keys.vpk(), - EphemeralPublicKey::from_scalar([10; 32]), - )], - output, - ) - .unwrap(); - let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_signing_key]); - state - .transition_from_privacy_preserving_transaction( - &PrivacyPreservingTransaction::new(message, witness_set), - 3, - 0, - ) - .unwrap(); - } - - // Alice spends alice_pda_1 into the same public recipient. - { - let recipient_account = state.get_account_by_id(recipient_id); - let (output, proof) = execute_and_prove( - vec![ - AccountWithMetadata::new(alice_pda_1_account, true, alice_pda_1_id), - AccountWithMetadata::new(recipient_account, false, recipient_id), - ], - Program::serialize_instruction((seed, amount, auth_transfer_id, false)).unwrap(), - vec![ - InputAccountIdentity::PrivatePdaUpdate { - ssk: alice_shared_1, - nsk: alice_keys.nsk, - membership_proof: state - .get_proof_for_commitment(&commitment_pda_1) - .expect("pda_1 must be in state"), - identifier: 1, - }, - InputAccountIdentity::Public, - ], - &program_with_deps, - ) - .unwrap(); - let message = Message::try_from_circuit_output( - vec![recipient_id], - vec![], - vec![( - alice_npk, - alice_keys.vpk(), - EphemeralPublicKey::from_scalar([11; 32]), - )], - output, - ) - .unwrap(); - let witness_set = WitnessSet::for_message(&message, proof, &[]); - state - .transition_from_privacy_preserving_transaction( - &PrivacyPreservingTransaction::new(message, witness_set), - 4, - 0, - ) - .unwrap(); - } - - assert_eq!(state.get_account_by_id(recipient_id).balance, 2 * amount); - } } diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index 16ad56d2..70979b7e 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,13 +1,12 @@ use std::{ - collections::{HashMap, VecDeque, hash_map::Entry}, + collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, convert::Infallible, }; use nssa_core::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Identifier, - InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, - PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, PrivateAccountKind, - SharedSecretKey, + MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, + PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::{Account, AccountId, AccountWithMetadata, Nonce}, compute_digest_for_path, program::{ @@ -18,26 +17,25 @@ use nssa_core::{ }; use risc0_zkvm::{guest::env, serde::to_vec}; +const PRIVATE_PDA_FIXED_IDENTIFIER: u128 = u128::MAX; + /// State of the involved accounts before and after program execution. struct ExecutionState { pre_states: Vec, post_states: HashMap, block_validity_window: BlockValidityWindow, timestamp_validity_window: TimestampValidityWindow, - /// Positions (in `pre_states`) of private-PDA accounts whose supplied npk has been bound to - /// their `AccountId` via a proven `AccountId::for_private_pda(program_id, seed, npk, - /// identifier)` check. + /// Positions (in `pre_states`) of mask-3 accounts whose supplied npk has been bound to + /// their `AccountId` via a proven `AccountId::for_private_pda(program_id, seed, npk)` + /// check. /// Two proof paths populate this set: a `Claim::Pda(seed)` in a program's `post_state` on /// that `pre_state`, or a caller's `ChainedCall.pda_seeds` entry matching that `pre_state` /// under the private derivation. Binding is an idempotent property, not an event: the same /// position can legitimately be bound through both paths in the same tx (e.g. a program - /// claims a private PDA and then delegates it to a callee), and the map uses `contains_key`, - /// not `assert!(insert)`. After the main loop, every private-PDA position must appear in this - /// map; otherwise the npk is unbound and the circuit rejects. - /// The stored `(ProgramId, PdaSeed)` is the owner program and seed, used in - /// `compute_circuit_output` to construct `PrivateAccountKind::Pda { program_id, seed, - /// identifier }`. - private_pda_bound_positions: HashMap, + /// claims a private PDA and then delegates it to a callee), and the set uses `contains`, + /// not `assert!(insert)`. After the main loop, every mask-3 position must appear in this + /// set; otherwise the npk is unbound and the circuit rejects. + private_pda_bound_positions: HashSet, /// Across the whole transaction, each `(program_id, seed)` pair may resolve to at most one /// `AccountId`. A seed under a program can derive a family of accounts, one public PDA and /// one private PDA per distinct npk. Without this check, a single `pda_seeds: [S]` entry in @@ -47,30 +45,39 @@ struct ExecutionState { /// `AccountId` entry or as an equality check against the existing one, making the rule: one /// `(program, seed)` → one account per tx. pda_family_binding: HashMap<(ProgramId, PdaSeed), AccountId>, - /// Map from a private-PDA `pre_state`'s position in `account_identities` to the (npk, - /// identifier) supplied for that position. Built once in `derive_from_outputs` by walking - /// `account_identities` and consulting `npk_if_private_pda`. Used later by the claim and - /// caller-seeds authorization paths to verify - /// `AccountId::for_private_pda(program_id, seed, npk, identifier) == pre_state.account_id`. - private_pda_npk_by_position: HashMap, + /// Map from a mask-3 `pre_state`'s position in `visibility_mask` to the npk supplied for + /// that position in `private_account_keys`. Built once in `derive_from_outputs` by walking + /// `visibility_mask` in lock-step with `private_account_keys`, used later by the claim and + /// caller-seeds authorization paths. + private_pda_npk_by_position: HashMap, } impl ExecutionState { /// Validate program outputs and derive the overall execution state. pub fn derive_from_outputs( - account_identities: &[InputAccountIdentity], + visibility_mask: &[u8], + private_account_keys: &[(NullifierPublicKey, Identifier, SharedSecretKey)], program_id: ProgramId, program_outputs: Vec, ) -> Self { - // Build position → (npk, identifier) map for private-PDA pre_states, indexed by position - // in `account_identities`. The vec is documented as 1:1 with the program's pre_state - // order, so position here matches `pre_state_position` used downstream in - // `validate_and_sync_states`. - let mut private_pda_npk_by_position: HashMap = - HashMap::new(); - for (pos, account_identity) in account_identities.iter().enumerate() { - if let Some((npk, identifier)) = account_identity.npk_if_private_pda() { - private_pda_npk_by_position.insert(pos, (npk, identifier)); + // Build position → npk map for mask-3 pre_states. `private_account_keys` is consumed in + // pre_state order across all masks 1/2/3, so walk `visibility_mask` in lock-step. The + // downstream `compute_circuit_output` also consumes the same iterator and its trailing + // assertions catch an over-supply of keys; under-supply surfaces here. + let mut private_pda_npk_by_position: HashMap = HashMap::new(); + { + let mut keys_iter = private_account_keys.iter(); + for (pos, &mask) in visibility_mask.iter().enumerate() { + if matches!(mask, 1..=3) { + let (npk, _, _) = keys_iter.next().unwrap_or_else(|| { + panic!( + "private_account_keys shorter than visibility_mask demands: no key for masked position {pos} (mask {mask})" + ) + }); + if mask == 3 { + private_pda_npk_by_position.insert(pos, *npk); + } + } } } @@ -108,7 +115,7 @@ impl ExecutionState { post_states: HashMap::new(), block_validity_window, timestamp_validity_window, - private_pda_bound_positions: HashMap::new(), + private_pda_bound_positions: HashSet::new(), pda_family_binding: HashMap::new(), private_pda_npk_by_position, }; @@ -187,7 +194,7 @@ impl ExecutionState { } execution_state.validate_and_sync_states( - account_identities, + visibility_mask, chained_call.program_id, caller_program_id, &chained_call.pda_seeds, @@ -204,16 +211,14 @@ impl ExecutionState { "Inner call without a chained call found", ); - // Every private-PDA pre_state must have had its npk bound to its account_id, either via - // a `Claim::Pda(seed)` in some program's post_state or via a caller's `pda_seeds` - // matching the private derivation. An unbound private-PDA pre_state has no - // cryptographic link between the supplied npk and the account_id, and must be rejected. - for (pos, account_identity) in account_identities.iter().enumerate() { - if account_identity.is_private_pda() { + // Every mask-3 pre_state must have had its npk bound to its account_id, either via a + // `Claim::Pda(seed)` in some program's post_state or via a caller's `pda_seeds` matching + // the private derivation. An unbound mask-3 pre_state has no cryptographic link between + // the supplied npk and the account_id, and must be rejected. + for (pos, &mask) in visibility_mask.iter().enumerate() { + if mask == 3 { assert!( - execution_state - .private_pda_bound_positions - .contains_key(&pos), + execution_state.private_pda_bound_positions.contains(&pos), "private PDA pre_state at position {pos} has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds" ); } @@ -246,7 +251,7 @@ impl ExecutionState { /// Validate program pre and post states and populate the execution state. fn validate_and_sync_states( &mut self, - account_identities: &[InputAccountIdentity], + visibility_mask: &[u8], program_id: ProgramId, caller_program_id: Option, caller_pda_seeds: &[PdaSeed], @@ -324,9 +329,9 @@ impl ExecutionState { .position(|acc| acc.account_id == pre_account_id) .expect("Pre state must exist at this point"); - let account_identity = &account_identities[pre_state_position]; - if account_identity.is_public() { - match claim { + let mask = visibility_mask[pre_state_position]; + match mask { + 0 => match claim { Claim::Authorized => { // Note: no need to check authorized pdas because we have already // checked consistency of authorization above. @@ -348,46 +353,40 @@ impl ExecutionState { pre_account_id, ); } - } - } else if account_identity.is_private_pda() { - match claim { - Claim::Authorized => { - assert!( - pre_is_authorized, - "Cannot claim unauthorized private PDA {pre_account_id}" - ); - } - Claim::Pda(seed) => { - let (npk, identifier) = self + }, + 3 => { + match claim { + Claim::Authorized => { + assert!( + pre_is_authorized, + "Cannot claim unauthorized private PDA {pre_account_id}" + ); + } + Claim::Pda(seed) => { + let npk = self .private_pda_npk_by_position .get(&pre_state_position) - .expect( - "private PDA pre_state must have an npk in the position map", + .expect("private PDA pre_state must have an npk in the position map"); + let pda = AccountId::for_private_pda(&program_id, &seed, npk); + assert_eq!( + pre_account_id, pda, + "Invalid private PDA claim for account {pre_account_id}" ); - let pda = - AccountId::for_private_pda(&program_id, &seed, npk, *identifier); - assert_eq!( - pre_account_id, pda, - "Invalid private PDA claim for account {pre_account_id}" - ); - bind_private_pda_position( - &mut self.private_pda_bound_positions, - pre_state_position, - program_id, - seed, - ); - assert_family_binding( - &mut self.pda_family_binding, - program_id, - seed, - pre_account_id, - ); + self.private_pda_bound_positions.insert(pre_state_position); + assert_family_binding( + &mut self.pda_family_binding, + program_id, + seed, + pre_account_id, + ); + } } } - } else { - // Standalone private accounts: don't enforce the claim semantics. - // Unauthorized private claiming is intentionally allowed since operating - // these accounts requires the npk/nsk keypair anyway. + _ => { + // Mask 1/2: standard private accounts don't enforce the claim semantics. + // Unauthorized private claiming is intentionally allowed since operating + // these accounts requires the npk/nsk keypair anyway. + } } post.account_mut().program_owner = program_id; @@ -439,24 +438,6 @@ fn assert_family_binding( } } -fn bind_private_pda_position( - map: &mut HashMap, - position: usize, - program_id: ProgramId, - seed: PdaSeed, -) { - match map.entry(position) { - Entry::Occupied(e) => assert_eq!( - *e.get(), - (program_id, seed), - "Duplicate binding at position {position}: conflicting (program_id, seed)" - ), - Entry::Vacant(e) => { - e.insert((program_id, seed)); - } - } -} - /// Resolve the authorization state of a `pre_state` seen again in a chained call and record /// any resulting bindings. Returns `true` if the `pre_state` is authorized through either a /// previously-seen authorization or a matching caller seed (under the public or private @@ -472,8 +453,8 @@ fn bind_private_pda_position( )] fn resolve_authorization_and_record_bindings( pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>, - private_pda_bound_positions: &mut HashMap, - private_pda_npk_by_position: &HashMap, + private_pda_bound_positions: &mut HashSet, + private_pda_npk_by_position: &HashMap, pre_account_id: AccountId, pre_state_position: usize, caller_program_id: Option, @@ -486,9 +467,8 @@ fn resolve_authorization_and_record_bindings( if AccountId::for_public_pda(&caller, seed) == pre_account_id { return Some((*seed, false, caller)); } - if let Some((npk, identifier)) = - private_pda_npk_by_position.get(&pre_state_position) - && AccountId::for_private_pda(&caller, seed, npk, *identifier) == pre_account_id + if let Some(npk) = private_pda_npk_by_position.get(&pre_state_position) + && AccountId::for_private_pda(&caller, seed, npk) == pre_account_id { return Some((*seed, true, caller)); } @@ -499,12 +479,7 @@ fn resolve_authorization_and_record_bindings( if let Some((seed, is_private_form, caller)) = matched_caller_seed { assert_family_binding(pda_family_binding, caller, seed, pre_account_id); if is_private_form { - bind_private_pda_position( - private_pda_bound_positions, - pre_state_position, - caller, - seed, - ); + private_pda_bound_positions.insert(pre_state_position); } } @@ -512,8 +487,11 @@ fn resolve_authorization_and_record_bindings( } fn compute_circuit_output( - mut execution_state: ExecutionState, - account_identities: &[InputAccountIdentity], + execution_state: ExecutionState, + visibility_mask: &[u8], + private_account_keys: &[(NullifierPublicKey, Identifier, SharedSecretKey)], + private_account_nsks: &[NullifierSecretKey], + private_account_membership_proofs: &[Option], ) -> PrivacyPreservingCircuitOutput { let mut output = PrivacyPreservingCircuitOutput { public_pre_states: Vec::new(), @@ -525,280 +503,292 @@ fn compute_circuit_output( timestamp_validity_window: execution_state.timestamp_validity_window, }; - let pda_seed_by_position = std::mem::take(&mut execution_state.private_pda_bound_positions); let states_iter = execution_state.into_states_iter(); assert_eq!( - account_identities.len(), + visibility_mask.len(), states_iter.len(), - "Invalid account_identities length" + "Invalid visibility mask length" ); + let mut private_keys_iter = private_account_keys.iter(); + let mut private_nsks_iter = private_account_nsks.iter(); + let mut private_membership_proofs_iter = private_account_membership_proofs.iter(); + let mut output_index = 0; - for (pos, (account_identity, (pre_state, post_state))) in - account_identities.iter().zip(states_iter).enumerate() + for (account_visibility_mask, (pre_state, post_state)) in + visibility_mask.iter().copied().zip(states_iter) { - match account_identity { - InputAccountIdentity::Public => { + match account_visibility_mask { + 0 => { + // Public account output.public_pre_states.push(pre_state); output.public_post_states.push(post_state); } - InputAccountIdentity::PrivateAuthorizedInit { - ssk, - nsk, - identifier, - } => { - let npk = NullifierPublicKey::from(nsk); - let account_id = AccountId::for_regular_private_account(&npk, *identifier); + 1 | 2 => { + let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else { + panic!("Missing private account key"); + }; + assert_ne!( + *identifier, PRIVATE_PDA_FIXED_IDENTIFIER, + "Identifier must be different from {PRIVATE_PDA_FIXED_IDENTIFIER}. This is reserved for private PDA." + ); + + let account_id = AccountId::from((npk, *identifier)); assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); - assert!( - pre_state.is_authorized, - "Pre-state not authorized for authenticated private account" + + let (new_nullifier, new_nonce) = if account_visibility_mask == 1 { + // Private account with authentication + + let Some(nsk) = private_nsks_iter.next() else { + panic!("Missing private account nullifier secret key"); + }; + + // Verify the nullifier public key + assert_eq!( + npk, + &NullifierPublicKey::from(nsk), + "Nullifier public key mismatch" + ); + + // Check pre_state authorization + assert!( + pre_state.is_authorized, + "Pre-state not authorized for authenticated private account" + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; + + let new_nullifier = compute_nullifier_and_set_digest( + membership_proof_opt.as_ref(), + &pre_state.account, + &account_id, + nsk, + ); + + let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + + (new_nullifier, new_nonce) + } else { + // Private account without authentication + + assert_eq!( + pre_state.account, + Account::default(), + "Found new private account with non default values", + ); + + assert!( + !pre_state.is_authorized, + "Found new private account marked as authorized." + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; + + assert!( + membership_proof_opt.is_none(), + "Membership proof must be None for unauthorized accounts" + ); + + let nullifier = Nullifier::for_account_initialization(&account_id); + + let new_nonce = Nonce::private_account_nonce_init(&account_id); + + ((nullifier, DUMMY_COMMITMENT_HASH), new_nonce) + }; + output.new_nullifiers.push(new_nullifier); + + // Update post-state with new nonce + let mut post_with_updated_nonce = post_state; + post_with_updated_nonce.nonce = new_nonce; + + // Compute commitment + let commitment_post = Commitment::new(&account_id, &post_with_updated_nonce); + + // Encrypt and push post state + let encrypted_account = EncryptionScheme::encrypt( + &post_with_updated_nonce, + *identifier, + shared_secret, + &commitment_post, + output_index, ); + + output.new_commitments.push(commitment_post); + output.ciphertexts.push(encrypted_account); + output_index = output_index + .checked_add(1) + .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); + } + 3 => { + // Private PDA account. The supplied npk has already been bound to + // `pre_state.account_id` upstream in `validate_and_sync_states`, either via a + // `Claim::Pda(seed)` match or via a caller `pda_seeds` match, both of which + // assert `AccountId::for_private_pda(owner, seed, npk) == account_id`. The + // post-loop assertion in `derive_from_outputs` (see the + // `private_pda_bound_positions` check) guarantees that every mask-3 + // position has been through at least one such binding, so this + // branch can safely use the wallet npk without re-verifying. + let Some((npk, identifier, shared_secret)) = private_keys_iter.next() else { + panic!("Missing private account key"); + }; + assert_eq!( - pre_state.account, - Account::default(), - "Found new private account with non default values" + *identifier, PRIVATE_PDA_FIXED_IDENTIFIER, + "Identifier for private PDAs must be {PRIVATE_PDA_FIXED_IDENTIFIER}." ); - let new_nullifier = ( - Nullifier::for_account_initialization(&account_id), - DUMMY_COMMITMENT_HASH, - ); - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + let (new_nullifier, new_nonce) = if pre_state.is_authorized { + // Existing private PDA with authentication (like mask 1) + let Some(nsk) = private_nsks_iter.next() else { + panic!("Missing private account nullifier secret key"); + }; + assert_eq!( + npk, + &NullifierPublicKey::from(nsk), + "Nullifier public key mismatch" + ); - emit_private_output( - &mut output, - &mut output_index, - post_state, - &account_id, - &PrivateAccountKind::Regular(*identifier), - ssk, - new_nullifier, - new_nonce, - ); - } - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk, - nsk, - membership_proof, - identifier, - } => { - let npk = NullifierPublicKey::from(nsk); - let account_id = AccountId::for_regular_private_account(&npk, *identifier); - - assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); - assert!( - pre_state.is_authorized, - "Pre-state not authorized for authenticated private account" - ); - - let new_nullifier = compute_update_nullifier_and_set_digest( - membership_proof, - &pre_state.account, - &account_id, - nsk, - ); - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); - - emit_private_output( - &mut output, - &mut output_index, - post_state, - &account_id, - &PrivateAccountKind::Regular(*identifier), - ssk, - new_nullifier, - new_nonce, - ); - } - InputAccountIdentity::PrivateUnauthorized { - npk, - ssk, - identifier, - } => { - let account_id = AccountId::for_regular_private_account(npk, *identifier); - - assert_eq!(account_id, pre_state.account_id, "AccountId mismatch"); - assert_eq!( - pre_state.account, - Account::default(), - "Found new private account with non default values", - ); - assert!( - !pre_state.is_authorized, - "Found new private account marked as authorized." - ); - - let new_nullifier = ( - Nullifier::for_account_initialization(&account_id), - DUMMY_COMMITMENT_HASH, - ); - let new_nonce = Nonce::private_account_nonce_init(&account_id); - - emit_private_output( - &mut output, - &mut output_index, - post_state, - &account_id, - &PrivateAccountKind::Regular(*identifier), - ssk, - new_nullifier, - new_nonce, - ); - } - InputAccountIdentity::PrivatePdaInit { - npk: _, - ssk, - identifier, - } => { - // The npk-to-account_id binding is established upstream in - // `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds` - // match. Here we only enforce the init pre-conditions. The supplied npk on - // the variant has been recorded into `private_pda_npk_by_position` and used - // for the binding check; we use `pre_state.account_id` directly for nullifier - // and commitment derivation. - assert!( - !pre_state.is_authorized, - "PrivatePdaInit requires unauthorized pre_state" - ); - assert_eq!( - pre_state.account, - Account::default(), - "New private PDA must be default" - ); - - let new_nullifier = ( - Nullifier::for_account_initialization(&pre_state.account_id), - DUMMY_COMMITMENT_HASH, - ); - let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id); - - let account_id = pre_state.account_id; - let (pda_program_id, seed) = pda_seed_by_position - .get(&pos) - .expect("PrivatePdaInit position must be in pda_seed_by_position"); - emit_private_output( - &mut output, - &mut output_index, - post_state, - &account_id, - &PrivateAccountKind::Pda { - program_id: *pda_program_id, - seed: *seed, - identifier: *identifier, - }, - ssk, - new_nullifier, - new_nonce, - ); - } - InputAccountIdentity::PrivatePdaUpdate { - ssk, - nsk, - membership_proof, - identifier, - } => { - // The npk binding is established upstream. Authorization must already be set; - // an unauthorized PrivatePdaUpdate would mean the prover supplied an nsk for an - // unbound PDA, which the upstream binding check would have rejected anyway, - // but we assert here to fail fast and document the precondition. - assert!( - pre_state.is_authorized, - "PrivatePdaUpdate requires authorized pre_state" - ); - - let new_nullifier = compute_update_nullifier_and_set_digest( - membership_proof, - &pre_state.account, - &pre_state.account_id, - nsk, - ); - let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); - - let account_id = pre_state.account_id; - let (pda_program_id, seed) = pda_seed_by_position - .get(&pos) - .expect("PrivatePdaUpdate position must be in pda_seed_by_position"); - emit_private_output( - &mut output, - &mut output_index, - post_state, - &account_id, - &PrivateAccountKind::Pda { - program_id: *pda_program_id, - seed: *seed, - identifier: *identifier, - }, - ssk, - new_nullifier, - new_nonce, + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; + + let new_nullifier = compute_nullifier_and_set_digest( + membership_proof_opt.as_ref(), + &pre_state.account, + &pre_state.account_id, + nsk, + ); + let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk); + (new_nullifier, new_nonce) + } else { + // New private PDA (like mask 2). The default + unauthorized requirement + // here rules out use cases like a fully-private multisig, which would need + // a non-default, non-authorized private PDA input account. + // TODO(private-pdas-pr-2/3): relax this once the wallet can supply a + // `(seed, owner)` side input so the npk-to-account_id binding can be + // re-verified for an existing private PDA without a `Claim::Pda` or caller + // `pda_seeds` match. + assert_eq!( + pre_state.account, + Account::default(), + "New private PDA must be default" + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; + assert!( + membership_proof_opt.is_none(), + "Membership proof must be None for new accounts" + ); + + let nullifier = Nullifier::for_account_initialization(&pre_state.account_id); + let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id); + ((nullifier, DUMMY_COMMITMENT_HASH), new_nonce) + }; + output.new_nullifiers.push(new_nullifier); + + let mut post_with_updated_nonce = post_state; + post_with_updated_nonce.nonce = new_nonce; + + let commitment_post = + Commitment::new(&pre_state.account_id, &post_with_updated_nonce); + + let encrypted_account = EncryptionScheme::encrypt( + &post_with_updated_nonce, + PRIVATE_PDA_FIXED_IDENTIFIER, + shared_secret, + &commitment_post, + output_index, ); + + output.new_commitments.push(commitment_post); + output.ciphertexts.push(encrypted_account); + output_index = output_index + .checked_add(1) + .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); } + _ => panic!("Invalid visibility mask value"), } } + assert!( + private_keys_iter.next().is_none(), + "Too many private account keys" + ); + + assert!( + private_nsks_iter.next().is_none(), + "Too many private account nullifier secret keys" + ); + + assert!( + private_membership_proofs_iter.next().is_none(), + "Too many private account membership proofs" + ); + output } -#[expect( - clippy::too_many_arguments, - reason = "All seven inputs are distinct concerns from the variant arms; bundling would be artificial" -)] -fn emit_private_output( - output: &mut PrivacyPreservingCircuitOutput, - output_index: &mut u32, - post_state: Account, - account_id: &AccountId, - kind: &PrivateAccountKind, - shared_secret: &SharedSecretKey, - new_nullifier: (Nullifier, CommitmentSetDigest), - new_nonce: Nonce, -) { - output.new_nullifiers.push(new_nullifier); - - let mut post_with_updated_nonce = post_state; - post_with_updated_nonce.nonce = new_nonce; - - let commitment_post = Commitment::new(account_id, &post_with_updated_nonce); - let encrypted_account = EncryptionScheme::encrypt( - &post_with_updated_nonce, - kind, - shared_secret, - &commitment_post, - *output_index, - ); - - output.new_commitments.push(commitment_post); - output.ciphertexts.push(encrypted_account); - *output_index = output_index - .checked_add(1) - .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); -} - -fn compute_update_nullifier_and_set_digest( - membership_proof: &MembershipProof, +fn compute_nullifier_and_set_digest( + membership_proof_opt: Option<&MembershipProof>, pre_account: &Account, account_id: &AccountId, nsk: &NullifierSecretKey, ) -> (Nullifier, CommitmentSetDigest) { - let commitment_pre = Commitment::new(account_id, pre_account); - let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); - let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); - (nullifier, set_digest) + membership_proof_opt.as_ref().map_or_else( + || { + assert_eq!( + *pre_account, + Account::default(), + "Found new private account with non default values" + ); + + // Compute initialization nullifier + let nullifier = Nullifier::for_account_initialization(account_id); + (nullifier, DUMMY_COMMITMENT_HASH) + }, + |membership_proof| { + // Compute commitment set digest associated with provided auth path + let commitment_pre = Commitment::new(account_id, pre_account); + let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); + + // Compute update nullifier + let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); + (nullifier, set_digest) + }, + ) } fn main() { let PrivacyPreservingCircuitInput { program_outputs, - account_identities, + visibility_mask, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, program_id, } = env::read(); - let execution_state = - ExecutionState::derive_from_outputs(&account_identities, program_id, program_outputs); + let execution_state = ExecutionState::derive_from_outputs( + &visibility_mask, + &private_account_keys, + program_id, + program_outputs, + ); - let output = compute_circuit_output(execution_state, &account_identities); + let output = compute_circuit_output( + execution_state, + &visibility_mask, + &private_account_keys, + &private_account_nsks, + &private_account_membership_proofs, + ); env::commit(&output); } diff --git a/sequencer/core/Cargo.toml b/sequencer/core/Cargo.toml index 827c8b2e..efd0e359 100644 --- a/sequencer/core/Cargo.toml +++ b/sequencer/core/Cargo.toml @@ -13,7 +13,7 @@ nssa_core.workspace = true common.workspace = true storage.workspace = true mempool.workspace = true -logos-blockchain-zone-sdk.workspace = true +bedrock_client.workspace = true testnet_initial_state.workspace = true anyhow.workspace = true @@ -30,6 +30,7 @@ rand.workspace = true borsh.workspace = true bytesize.workspace = true url.workspace = true +jsonrpsee = { workspace = true, features = ["ws-client"] } [features] default = [] diff --git a/sequencer/core/src/block_publisher.rs b/sequencer/core/src/block_publisher.rs deleted file mode 100644 index 9f4c8235..00000000 --- a/sequencer/core/src/block_publisher.rs +++ /dev/null @@ -1,136 +0,0 @@ -use std::{sync::Arc, time::Duration}; - -use anyhow::{Context as _, Result, anyhow}; -use common::block::Block; -use log::warn; -pub use logos_blockchain_core::mantle::ops::channel::MsgId; -pub use logos_blockchain_key_management_system_service::keys::Ed25519Key; -pub use logos_blockchain_zone_sdk::sequencer::SequencerCheckpoint; -use logos_blockchain_zone_sdk::{ - CommonHttpClient, - adapter::NodeHttpClient, - sequencer::{Event, SequencerConfig as ZoneSdkSequencerConfig, SequencerHandle, ZoneSequencer}, - state::InscriptionInfo, -}; -use tokio::task::JoinHandle; - -use crate::config::BedrockConfig; - -/// Sink for `Event::Published` checkpoints emitted by the drive task. -/// Caller is responsible for persistence (e.g. writing to rocksdb). -pub type CheckpointSink = Box; - -/// Sink for finalized L2 block ids derived from `Event::TxsFinalized` and -/// `Event::FinalizedInscriptions`. Caller is responsible for cleanup -/// (e.g. marking pending blocks as finalized in storage). -pub type FinalizedBlockSink = Box; - -#[expect(async_fn_in_trait, reason = "We don't care about Send/Sync here")] -pub trait BlockPublisherTrait: Clone { - async fn new( - config: &BedrockConfig, - bedrock_signing_key: Ed25519Key, - resubmit_interval: Duration, - initial_checkpoint: Option, - on_checkpoint: CheckpointSink, - on_finalized_block: FinalizedBlockSink, - ) -> Result; - - /// Fire-and-forget publish. Zone-sdk drives the actual submission and - /// retries internally; this just hands the payload off. - async fn publish_block(&self, block: &Block) -> Result<()>; -} - -/// Real block publisher backed by zone-sdk's `ZoneSequencer`. -#[derive(Clone)] -pub struct ZoneSdkPublisher { - handle: SequencerHandle, - // Aborts the drive task when the last clone is dropped. - _drive_task: Arc, -} - -struct DriveTaskGuard(JoinHandle<()>); - -impl Drop for DriveTaskGuard { - fn drop(&mut self) { - self.0.abort(); - } -} - -impl BlockPublisherTrait for ZoneSdkPublisher { - async fn new( - config: &BedrockConfig, - bedrock_signing_key: Ed25519Key, - resubmit_interval: Duration, - initial_checkpoint: Option, - on_checkpoint: CheckpointSink, - on_finalized_block: FinalizedBlockSink, - ) -> Result { - let basic_auth = config.auth.clone().map(Into::into); - let node = NodeHttpClient::new(CommonHttpClient::new(basic_auth), config.node_url.clone()); - - let zone_sdk_config = ZoneSdkSequencerConfig { - resubmit_interval, - ..ZoneSdkSequencerConfig::default() - }; - - let (mut sequencer, mut handle) = ZoneSequencer::init_with_config( - config.channel_id, - bedrock_signing_key, - node, - zone_sdk_config, - initial_checkpoint, - ); - - let drive_task = tokio::spawn(async move { - loop { - let Some(event) = sequencer.next_event().await else { - continue; - }; - match event { - Event::Published { checkpoint, .. } => on_checkpoint(checkpoint), - Event::TxsFinalized { inscriptions, .. } - | Event::FinalizedInscriptions { inscriptions } => { - if let Some(max_block_id) = max_block_id_from_inscriptions(&inscriptions) { - on_finalized_block(max_block_id); - } - } - Event::ChannelUpdate { .. } | Event::Ready => {} - } - } - }); - - handle.wait_ready().await; - - Ok(Self { - handle, - _drive_task: Arc::new(DriveTaskGuard(drive_task)), - }) - } - - async fn publish_block(&self, block: &Block) -> Result<()> { - let data = borsh::to_vec(block).context("Failed to serialize block")?; - self.handle - .publish_message(data) - .await - .map_err(|e| anyhow!("zone-sdk publish failed: {e}"))?; - Ok(()) - } -} - -/// Deserialize each inscription payload as a `Block` and return the highest -/// `block_id`. Bad payloads are logged and skipped. -fn max_block_id_from_inscriptions(inscriptions: &[InscriptionInfo]) -> Option { - inscriptions - .iter() - .filter_map( - |inscription| match borsh::from_slice::(&inscription.payload) { - Ok(block) => Some(block.header.block_id), - Err(err) => { - warn!("Failed to deserialize finalized inscription as Block: {err:#}"); - None - } - }, - ) - .max() -} diff --git a/sequencer/core/src/block_settlement_client.rs b/sequencer/core/src/block_settlement_client.rs new file mode 100644 index 00000000..6b32f8de --- /dev/null +++ b/sequencer/core/src/block_settlement_client.rs @@ -0,0 +1,116 @@ +use anyhow::{Context as _, Result}; +use bedrock_client::BedrockClient; +pub use common::block::Block; +pub use logos_blockchain_core::mantle::{MantleTx, SignedMantleTx, ops::channel::MsgId}; +use logos_blockchain_core::mantle::{ + Op, OpProof, Transaction as _, + ops::channel::{ChannelId, inscribe::InscriptionOp}, +}; +pub use logos_blockchain_key_management_system_service::keys::Ed25519Key; +use logos_blockchain_key_management_system_service::keys::Ed25519PublicKey; + +use crate::config::BedrockConfig; + +#[expect(async_fn_in_trait, reason = "We don't care about Send/Sync here")] +pub trait BlockSettlementClientTrait: Clone { + //// Create a new client. + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result; + + /// Get the bedrock channel ID used by this client. + fn bedrock_channel_id(&self) -> ChannelId; + + /// Get the bedrock signing key used by this client. + fn bedrock_signing_key(&self) -> &Ed25519Key; + + /// Post a transaction to the node. + async fn submit_inscribe_tx_to_bedrock(&self, tx: SignedMantleTx) -> Result<()>; + + /// Create and sign a transaction for inscribing data. + fn create_inscribe_tx(&self, block: &Block) -> Result<(SignedMantleTx, MsgId)> { + let inscription_data = borsh::to_vec(block)?; + log::debug!( + "The size of the block {} is {} bytes", + block.header.block_id, + inscription_data.len() + ); + let verifying_key_bytes = self.bedrock_signing_key().public_key().to_bytes(); + let verifying_key = + Ed25519PublicKey::from_bytes(&verifying_key_bytes).expect("valid ed25519 public key"); + + let inscribe_op = InscriptionOp { + channel_id: self.bedrock_channel_id(), + inscription: inscription_data, + parent: block.bedrock_parent_id.into(), + signer: verifying_key, + }; + let inscribe_op_id = inscribe_op.id(); + + let inscribe_tx = MantleTx { + ops: vec![Op::ChannelInscribe(inscribe_op)], + // Altruistic test config + storage_gas_price: 0.into(), + execution_gas_price: 0.into(), + }; + + let tx_hash = inscribe_tx.hash(); + let signature_bytes = self + .bedrock_signing_key() + .sign_payload(tx_hash.as_signing_bytes().as_ref()) + .to_bytes(); + let signature = + logos_blockchain_key_management_system_service::keys::Ed25519Signature::from_bytes( + &signature_bytes, + ); + + let signed_mantle_tx = SignedMantleTx { + ops_proofs: vec![OpProof::Ed25519Sig(signature)], + mantle_tx: inscribe_tx, + }; + Ok((signed_mantle_tx, inscribe_op_id)) + } +} + +/// A component that posts block data to logos blockchain. +#[derive(Clone)] +pub struct BlockSettlementClient { + client: BedrockClient, + signing_key: Ed25519Key, + channel_id: ChannelId, +} + +impl BlockSettlementClientTrait for BlockSettlementClient { + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result { + let client = + BedrockClient::new(config.backoff, config.node_url.clone(), config.auth.clone()) + .context("Failed to initialize bedrock client")?; + Ok(Self { + client, + signing_key, + channel_id: config.channel_id, + }) + } + + async fn submit_inscribe_tx_to_bedrock(&self, tx: SignedMantleTx) -> Result<()> { + let (parent_id, msg_id) = match tx.mantle_tx.ops.first() { + Some(Op::ChannelInscribe(inscribe)) => (inscribe.parent, inscribe.id()), + _ => panic!("Expected ChannelInscribe op"), + }; + self.client + .post_transaction(tx) + .await + .context("Failed to post transaction to Bedrock after retries")? + .context("Failed to post transaction to Bedrock with non-retryable error")?; + + log::debug!("Posted block to Bedrock with parent id {parent_id:?} and msg id: {msg_id:?}"); + + Ok(()) + } + + fn bedrock_channel_id(&self) -> ChannelId { + self.channel_id + } + + fn bedrock_signing_key(&self) -> &Ed25519Key { + &self.signing_key + } +} diff --git a/sequencer/core/src/block_store.rs b/sequencer/core/src/block_store.rs index e85b5d33..7e47005d 100644 --- a/sequencer/core/src/block_store.rs +++ b/sequencer/core/src/block_store.rs @@ -1,17 +1,16 @@ -use std::{collections::HashMap, path::Path, sync::Arc}; +use std::{collections::HashMap, path::Path}; -use anyhow::{Context as _, Result}; +use anyhow::Result; use common::{ HashType, block::{Block, BlockMeta, MantleMsgId}, transaction::NSSATransaction, }; -use logos_blockchain_zone_sdk::sequencer::SequencerCheckpoint; use nssa::V03State; use storage::{error::DbError, sequencer::RocksDBIO}; pub struct SequencerStore { - dbio: Arc, + dbio: RocksDBIO, // TODO: Consider adding the hashmap to the database for faster recovery. tx_hash_to_block_map: HashMap, genesis_id: u64, @@ -31,11 +30,7 @@ impl SequencerStore { ) -> Result { let tx_hash_to_block_map = block_to_transactions_map(genesis_block); - let dbio = Arc::new(RocksDBIO::open_or_create( - location, - genesis_block, - genesis_msg_id, - )?); + let dbio = RocksDBIO::open_or_create(location, genesis_block, genesis_msg_id)?; let genesis_id = dbio.get_meta_first_block_in_db()?; @@ -47,14 +42,6 @@ impl SequencerStore { }) } - /// Shared handle to the underlying rocksdb. Used to persist the zone-sdk - /// checkpoint from the sequencer's drive task without needing &mut to the - /// store. - #[must_use] - pub fn dbio(&self) -> Arc { - Arc::clone(&self.dbio) - } - pub fn get_block_at_id(&self, id: u64) -> Result, DbError> { self.dbio.get_block(id) } @@ -68,7 +55,6 @@ impl SequencerStore { } /// Returns the transaction corresponding to the given hash, if it exists in the blockchain. - #[must_use] pub fn get_transaction_by_hash(&self, hash: HashType) -> Option { let block_id = *self.tx_hash_to_block_map.get(&hash)?; let block = self @@ -90,12 +76,10 @@ impl SequencerStore { Ok(self.dbio.latest_block_meta()?) } - #[must_use] pub const fn genesis_id(&self) -> u64 { self.genesis_id } - #[must_use] pub const fn signing_key(&self) -> &nssa::PrivateKey { &self.signing_key } @@ -116,26 +100,9 @@ impl SequencerStore { Ok(()) } - #[must_use] pub fn get_nssa_state(&self) -> Option { self.dbio.get_nssa_state().ok() } - - pub fn get_zone_checkpoint(&self) -> Result> { - let Some(bytes) = self.dbio.get_zone_sdk_checkpoint_bytes()? else { - return Ok(None); - }; - let checkpoint: SequencerCheckpoint = serde_json::from_slice(&bytes) - .context("Failed to deserialize stored zone-sdk checkpoint")?; - Ok(Some(checkpoint)) - } - - pub fn set_zone_checkpoint(&self, checkpoint: &SequencerCheckpoint) -> Result<()> { - let bytes = - serde_json::to_vec(checkpoint).context("Failed to serialize zone-sdk checkpoint")?; - self.dbio.put_zone_sdk_checkpoint_bytes(&bytes)?; - Ok(()) - } } pub(crate) fn block_to_transactions_map(block: &Block) -> HashMap { diff --git a/sequencer/core/src/config.rs b/sequencer/core/src/config.rs index b33dd694..fa4a2fa7 100644 --- a/sequencer/core/src/config.rs +++ b/sequencer/core/src/config.rs @@ -6,6 +6,7 @@ use std::{ }; use anyhow::Result; +use bedrock_client::BackoffConfig; use bytesize::ByteSize; use common::config::BasicAuth; use humantime_serde; @@ -41,6 +42,8 @@ pub struct SequencerConfig { pub signing_key: [u8; 32], /// Bedrock configuration options. pub bedrock_config: BedrockConfig, + /// Indexer RPC URL. + pub indexer_rpc_url: Url, #[serde(skip_serializing_if = "Option::is_none")] pub initial_public_accounts: Option>, #[serde(skip_serializing_if = "Option::is_none")] @@ -49,6 +52,9 @@ pub struct SequencerConfig { #[derive(Clone, Serialize, Deserialize)] pub struct BedrockConfig { + /// Fibonacci backoff retry strategy configuration. + #[serde(default)] + pub backoff: BackoffConfig, /// Bedrock channel ID. pub channel_id: ChannelId, /// Bedrock Url. diff --git a/indexer/ffi/src/client.rs b/sequencer/core/src/indexer_client.rs similarity index 91% rename from indexer/ffi/src/client.rs rename to sequencer/core/src/indexer_client.rs index f05b350e..960b77a4 100644 --- a/indexer/ffi/src/client.rs +++ b/sequencer/core/src/indexer_client.rs @@ -4,6 +4,7 @@ use anyhow::{Context as _, Result}; use log::info; pub use url::Url; +#[expect(async_fn_in_trait, reason = "We don't care about Send/Sync here")] pub trait IndexerClientTrait: Clone { async fn new(indexer_url: &Url) -> Result; } diff --git a/sequencer/core/src/lib.rs b/sequencer/core/src/lib.rs index df9aa87c..22c09d85 100644 --- a/sequencer/core/src/lib.rs +++ b/sequencer/core/src/lib.rs @@ -1,6 +1,7 @@ use std::{path::Path, time::Instant}; use anyhow::{Context as _, Result, anyhow}; +use bedrock_client::SignedMantleTx; #[cfg(feature = "testnet")] use common::PINATA_BASE58; use common::{ @@ -19,27 +20,33 @@ pub use storage::error::DbError; use testnet_initial_state::initial_state; use crate::{ - block_publisher::{BlockPublisherTrait, ZoneSdkPublisher}, + block_settlement_client::{BlockSettlementClient, BlockSettlementClientTrait, MsgId}, block_store::SequencerStore, + indexer_client::{IndexerClient, IndexerClientTrait}, }; -pub mod block_publisher; +pub mod block_settlement_client; pub mod block_store; pub mod config; +pub mod indexer_client; #[cfg(feature = "mock")] pub mod mock; -pub struct SequencerCore { +pub struct SequencerCore< + BC: BlockSettlementClientTrait = BlockSettlementClient, + IC: IndexerClientTrait = IndexerClient, +> { state: nssa::V03State, store: SequencerStore, mempool: MemPool, sequencer_config: SequencerConfig, chain_height: u64, - block_publisher: BP, + block_settlement_client: BC, + indexer_client: IC, } -impl SequencerCore { +impl SequencerCore { /// Starts the sequencer using the provided configuration. /// If an existing database is found, the sequencer state is loaded from it and /// assumed to represent the correct latest state consistent with Bedrock-finalized data. @@ -63,16 +70,23 @@ impl SequencerCore { load_or_create_signing_key(&config.home.join("bedrock_signing_key")) .expect("Failed to load or create bedrock signing key"); - // TODO: Remove msg_id from BlockMeta — it is no longer needed now that - // zone-sdk manages L1 settlement state via its own checkpoint. - let genesis_msg_id = [0_u8; 32]; + let block_settlement_client = BC::new(&config.bedrock_config, bedrock_signing_key) + .expect("Failed to initialize Block Settlement Client"); + + let indexer_client = IC::new(&config.indexer_rpc_url) + .await + .expect("Failed to create Indexer Client"); + + let (_tx, genesis_msg_id) = block_settlement_client + .create_inscribe_tx(&genesis_block) + .expect("Failed to create inscribe tx for genesis block"); // Sequencer should panic if unable to open db, // as fixing this issue may require actions non-native to program scope let store = SequencerStore::open_db_with_genesis( &config.home.join("rocksdb"), &genesis_block, - genesis_msg_id, + genesis_msg_id.into(), signing_key, ) .unwrap(); @@ -80,51 +94,6 @@ impl SequencerCore { .latest_block_meta() .expect("Failed to read latest block meta from store"); - let initial_checkpoint = store - .get_zone_checkpoint() - .expect("Failed to load zone-sdk checkpoint"); - let is_fresh_start = initial_checkpoint.is_none(); - - let dbio_for_checkpoint = store.dbio(); - let on_checkpoint: block_publisher::CheckpointSink = Box::new(move |cp| { - let bytes = match serde_json::to_vec(&cp) { - Ok(b) => b, - Err(err) => { - error!("Failed to serialize zone-sdk checkpoint: {err:#}"); - return; - } - }; - if let Err(err) = dbio_for_checkpoint.put_zone_sdk_checkpoint_bytes(&bytes) { - error!("Failed to persist zone-sdk checkpoint: {err:#}"); - } - }); - - let dbio_for_finalized = store.dbio(); - let on_finalized_block: block_publisher::FinalizedBlockSink = Box::new(move |block_id| { - if let Err(err) = dbio_for_finalized.clean_pending_blocks_up_to(block_id) { - error!("Failed to mark pending blocks finalized up to {block_id}: {err:#}"); - } - }); - - let block_publisher = BP::new( - &config.bedrock_config, - bedrock_signing_key, - config.retry_pending_blocks_timeout, - initial_checkpoint, - on_checkpoint, - on_finalized_block, - ) - .await - .expect("Failed to initialize Block Publisher"); - - // On a truly fresh start (no checkpoint persisted yet), publish the - // genesis block so the indexer can find the channel start. After the - // first publish, zone-sdk's checkpoint persistence covers further - // restarts. - if is_fresh_start && let Err(err) = block_publisher.publish_block(&genesis_block).await { - error!("Failed to publish genesis block: {err:#}"); - } - #[cfg_attr(not(feature = "testnet"), allow(unused_mut))] let mut state = if let Some(state) = store.get_nssa_state() { info!("Found local database. Loading state and pending blocks from it."); @@ -141,7 +110,7 @@ impl SequencerCore { .iter() .map(|init_comm_data| { let npk = &init_comm_data.npk; - let account_id = nssa::AccountId::for_regular_private_account(npk, 0); + let account_id = nssa::AccountId::from((npk, 0)); let mut acc = init_comm_data.account.clone(); @@ -190,33 +159,35 @@ impl SequencerCore { mempool, chain_height: latest_block_meta.id, sequencer_config: config, - block_publisher, + block_settlement_client, + indexer_client, }; (sequencer_core, mempool_handle) } - /// Produces a new block from mempool transactions and publishes it via zone-sdk. pub async fn produce_new_block(&mut self) -> Result { - let block = self - .build_block_from_mempool() - .context("Failed to build block from mempool transactions")?; - - // TODO: Remove msg_id from store.update — it is no longer needed now that - // zone-sdk manages L1 settlement state via its own checkpoint. - let placeholder_msg_id = [0_u8; 32]; - - if let Err(err) = self.block_publisher.publish_block(&block).await { - error!("Failed to publish block to Bedrock with error: {err:#}"); + let (tx, _msg_id) = self + .produce_new_block_with_mempool_transactions() + .context("Failed to produce new block with mempool transactions")?; + match self + .block_settlement_client + .submit_inscribe_tx_to_bedrock(tx) + .await + { + Ok(()) => {} + Err(err) => { + error!("Failed to post block data to Bedrock with error: {err:#}"); + } } - self.store.update(&block, placeholder_msg_id, &self.state)?; Ok(self.chain_height) } - /// Builds a new block from transactions in the mempool. - /// Does NOT publish or store the block — the caller is responsible for that. - pub fn build_block_from_mempool(&mut self) -> Result { + /// Produces new block from transactions in mempool and packs it into a `SignedMantleTx`. + pub fn produce_new_block_with_mempool_transactions( + &mut self, + ) -> Result<(SignedMantleTx, MsgId)> { let now = Instant::now(); let new_block_height = self.next_block_id(); @@ -306,12 +277,21 @@ impl SequencerCore { timestamp: new_block_timestamp, }; - // TODO: Remove bedrock_parent_id from Block — it is no longer needed now - // that zone-sdk manages the inscription parent chain internally. - let placeholder_parent_id = [0_u8; 32]; let block = hashable_data .clone() - .into_pending_block(self.store.signing_key(), placeholder_parent_id); + .into_pending_block(self.store.signing_key(), latest_block_meta.msg_id); + + let (tx, msg_id) = self + .block_settlement_client + .create_inscribe_tx(&block) + .with_context(|| { + format!( + "Failed to create inscribe transaction for block with id {}", + block.header.block_id + ) + })?; + + self.store.update(&block, msg_id.into(), &self.state)?; self.chain_height = new_block_height; @@ -320,7 +300,7 @@ impl SequencerCore { hashable_data.transactions.len(), now.elapsed().as_secs() ); - Ok(block) + Ok((tx, msg_id)) } pub const fn state(&self) -> &nssa::V03State { @@ -339,19 +319,22 @@ impl SequencerCore { &self.sequencer_config } - /// Marks all pending blocks with `block_id <= last_finalized_block_id` as - /// finalized. Idempotent. Production callers don't invoke this directly — - /// it's wired up in `start_from_config` to the publisher's - /// `on_finalized_block` sink, which fires on `Event::TxsFinalized` / - /// `Event::FinalizedInscriptions`. Kept on the type for tests. - // TODO: Delete blocks instead of marking them as finalized. Current - // approach is used because we still have `GetBlockDataRequest`. - pub fn clean_finalized_blocks_from_db(&self, last_finalized_block_id: u64) -> Result<()> { - info!("Clearing pending blocks up to id: {last_finalized_block_id}"); - self.store - .dbio() - .clean_pending_blocks_up_to(last_finalized_block_id)?; - Ok(()) + /// Deletes finalized blocks from the sequencer's pending block list. + /// This method must be called when new blocks are finalized on Bedrock. + /// All pending blocks with an ID less than or equal to `last_finalized_block_id` + /// are removed from the database. + pub fn clean_finalized_blocks_from_db(&mut self, last_finalized_block_id: u64) -> Result<()> { + self.get_pending_blocks()? + .iter() + .map(|block| block.header.block_id) + .min() + .map_or(Ok(()), |first_pending_block_id| { + info!("Clearing pending blocks up to id: {last_finalized_block_id}"); + // TODO: Delete blocks instead of marking them as finalized. + // Current approach is used because we still have `GetBlockDataRequest`. + (first_pending_block_id..=last_finalized_block_id) + .try_for_each(|id| self.store.mark_block_as_finalized(id)) + }) } /// Returns the list of stored pending blocks. @@ -365,8 +348,12 @@ impl SequencerCore { .collect()) } - pub fn block_publisher(&self) -> BP { - self.block_publisher.clone() + pub fn block_settlement_client(&self) -> BC { + self.block_settlement_client.clone() + } + + pub fn indexer_client(&self) -> IC { + self.indexer_client.clone() } fn next_block_id(&self) -> u64 { @@ -405,6 +392,7 @@ mod tests { use std::{pin::pin, time::Duration}; + use bedrock_client::BackoffConfig; use common::{ test_utils::sequencer_sign_key_for_testing, transaction::{NSSATransaction, clock_invocation}, @@ -432,11 +420,16 @@ mod tests { block_create_timeout: Duration::from_secs(1), signing_key: *sequencer_sign_key_for_testing().value(), bedrock_config: BedrockConfig { + backoff: BackoffConfig { + start_delay: Duration::from_millis(100), + max_retries: 5, + }, channel_id: ChannelId::from([0; 32]), node_url: "http://not-used-in-unit-tests".parse().unwrap(), auth: None, }, retry_pending_blocks_timeout: Duration::from_mins(4), + indexer_rpc_url: "ws://localhost:8779".parse().unwrap(), initial_public_accounts: None, initial_private_accounts: None, } @@ -464,7 +457,9 @@ mod tests { let tx = common::test_utils::produce_dummy_empty_transaction(); mempool_handle.push(tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); (sequencer, mempool_handle) } @@ -609,21 +604,23 @@ mod tests { assert!(poll.is_pending()); // Empty the mempool by producing a block - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); // Resolve the pending push assert!(push_fut.await.is_ok()); } #[tokio::test] - async fn build_block_from_mempool() { + async fn produce_new_block_with_mempool_transactions() { let (mut sequencer, mempool_handle) = common_setup().await; let genesis_height = sequencer.chain_height; let tx = common::test_utils::produce_dummy_empty_transaction(); mempool_handle.push(tx).await.unwrap(); - let result = sequencer.build_block_from_mempool(); + let result = sequencer.produce_new_block_with_mempool_transactions(); assert!(result.is_ok()); assert_eq!(sequencer.chain_height, genesis_height + 1); } @@ -648,7 +645,9 @@ mod tests { mempool_handle.push(tx_replay).await.unwrap(); // Create block - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height) @@ -680,7 +679,9 @@ mod tests { // The transaction should be included the first time mempool_handle.push(tx.clone()).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height) @@ -696,7 +697,9 @@ mod tests { // Add same transaction should fail mempool_handle.push(tx.clone()).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height) @@ -735,7 +738,9 @@ mod tests { ); mempool_handle.push(tx.clone()).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let block = sequencer .store .get_block_at_id(sequencer.chain_height) @@ -773,9 +778,15 @@ mod tests { let config = setup_sequencer_config(); let (mut sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); assert_eq!(sequencer.get_pending_blocks().unwrap().len(), 4); } @@ -784,9 +795,15 @@ mod tests { let config = setup_sequencer_config(); let (mut sequencer, _mempool_handle) = SequencerCoreWithMockClients::start_from_config(config).await; - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let last_finalized_block = 3; sequencer @@ -819,7 +836,9 @@ mod tests { ); mempool_handle.push(tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); // Get the metadata of the last block produced sequencer.store.latest_block_meta().unwrap() @@ -842,7 +861,9 @@ mod tests { mempool_handle.push(tx.clone()).await.unwrap(); // Step 4: Produce new block - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); // Step 5: Verify the new block has correct previous block metadata let new_block = sequencer @@ -855,6 +876,10 @@ mod tests { new_block.header.prev_block_hash, expected_prev_meta.hash, "New block's prev_block_hash should match the stored metadata hash" ); + assert_eq!( + new_block.bedrock_parent_id, expected_prev_meta.msg_id, + "New block's bedrock_parent_id should match the stored metadata msg_id" + ); assert_eq!( new_block.body.transactions, vec![ @@ -889,7 +914,9 @@ mod tests { .await .unwrap(); mempool_handle.push(crafted_clock_tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let block = sequencer .store @@ -922,11 +949,15 @@ mod tests { // Produce multiple blocks to advance chain height let tx = common::test_utils::produce_dummy_empty_transaction(); mempool_handle.push(tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let tx = common::test_utils::produce_dummy_empty_transaction(); mempool_handle.push(tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); // Return the current chain height (should be genesis_id + 2) sequencer.chain_height @@ -963,7 +994,9 @@ mod tests { ), )); mempool_handle.push(deploy_tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); // Build a user transaction that invokes clock_chain_caller, which in turn chain-calls the // clock program with the clock accounts. The sequencer should detect that the resulting @@ -988,7 +1021,9 @@ mod tests { )); mempool_handle.push(user_tx).await.unwrap(); - sequencer.produce_new_block().await.unwrap(); + sequencer + .produce_new_block_with_mempool_transactions() + .unwrap(); let block = sequencer .store @@ -1022,7 +1057,7 @@ mod tests { mempool_handle.push(tx).await.unwrap(); // Block production must fail because the appended clock tx cannot execute. - let result = sequencer.produce_new_block().await; + let result = sequencer.produce_new_block_with_mempool_transactions(); assert!( result.is_err(), "Block production should abort when clock account data is corrupted" @@ -1041,7 +1076,7 @@ mod tests { program::Program, }; use nssa_core::{ - InputAccountIdentity, SharedSecretKey, + SharedSecretKey, account::AccountWithMetadata, encryption::{EphemeralPublicKey, EphemeralSecretKey, ViewingPublicKey}, }; @@ -1079,11 +1114,10 @@ mod tests { (&npk, 0), )], Program::serialize_instruction(0_u128).unwrap(), - vec![InputAccountIdentity::PrivateAuthorizedInit { - ssk: shared_secret, - nsk, - identifier: 0, - }], + vec![1], + vec![(npk, 0, shared_secret)], + vec![nsk], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); diff --git a/sequencer/core/src/mock.rs b/sequencer/core/src/mock.rs index ebe6ea5d..45a682e2 100644 --- a/sequencer/core/src/mock.rs +++ b/sequencer/core/src/mock.rs @@ -1,34 +1,76 @@ -use std::time::Duration; - -use anyhow::Result; -use common::block::Block; +use anyhow::{Result, anyhow}; +use bedrock_client::SignedMantleTx; +use logos_blockchain_core::mantle::ops::channel::ChannelId; use logos_blockchain_key_management_system_service::keys::Ed25519Key; +use url::Url; use crate::{ - block_publisher::{ - BlockPublisherTrait, CheckpointSink, FinalizedBlockSink, SequencerCheckpoint, - }, - config::BedrockConfig, + block_settlement_client::BlockSettlementClientTrait, config::BedrockConfig, + indexer_client::IndexerClientTrait, }; -pub type SequencerCoreWithMockClients = crate::SequencerCore; +pub type SequencerCoreWithMockClients = + crate::SequencerCore; #[derive(Clone)] -pub struct MockBlockPublisher; +pub struct MockBlockSettlementClient { + bedrock_channel_id: ChannelId, + bedrock_signing_key: Ed25519Key, +} -impl BlockPublisherTrait for MockBlockPublisher { - async fn new( - _config: &BedrockConfig, - _bedrock_signing_key: Ed25519Key, - _resubmit_interval: Duration, - _initial_checkpoint: Option, - _on_checkpoint: CheckpointSink, - _on_finalized_block: FinalizedBlockSink, - ) -> Result { - Ok(Self) +impl BlockSettlementClientTrait for MockBlockSettlementClient { + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result { + Ok(Self { + bedrock_channel_id: config.channel_id, + bedrock_signing_key: signing_key, + }) } - async fn publish_block(&self, _block: &Block) -> Result<()> { + fn bedrock_channel_id(&self) -> ChannelId { + self.bedrock_channel_id + } + + fn bedrock_signing_key(&self) -> &Ed25519Key { + &self.bedrock_signing_key + } + + async fn submit_inscribe_tx_to_bedrock(&self, _tx: SignedMantleTx) -> Result<()> { Ok(()) } } + +#[derive(Clone)] +pub struct MockBlockSettlementClientWithError { + bedrock_channel_id: ChannelId, + bedrock_signing_key: Ed25519Key, +} + +impl BlockSettlementClientTrait for MockBlockSettlementClientWithError { + fn new(config: &BedrockConfig, signing_key: Ed25519Key) -> Result { + Ok(Self { + bedrock_channel_id: config.channel_id, + bedrock_signing_key: signing_key, + }) + } + + fn bedrock_channel_id(&self) -> ChannelId { + self.bedrock_channel_id + } + + fn bedrock_signing_key(&self) -> &Ed25519Key { + &self.bedrock_signing_key + } + + async fn submit_inscribe_tx_to_bedrock(&self, _tx: SignedMantleTx) -> Result<()> { + Err(anyhow!("Mock error")) + } +} + +#[derive(Copy, Clone)] +pub struct MockIndexerClient; + +impl IndexerClientTrait for MockIndexerClient { + async fn new(_indexer_url: &Url) -> Result { + Ok(Self) + } +} diff --git a/sequencer/service/Cargo.toml b/sequencer/service/Cargo.toml index beed6be2..6fee808c 100644 --- a/sequencer/service/Cargo.toml +++ b/sequencer/service/Cargo.toml @@ -14,6 +14,7 @@ mempool.workspace = true sequencer_core = { workspace = true, features = ["testnet"] } sequencer_service_protocol.workspace = true sequencer_service_rpc = { workspace = true, features = ["server"] } +indexer_service_rpc = { workspace = true, features = ["client"] } clap = { workspace = true, features = ["derive", "env"] } anyhow.workspace = true diff --git a/sequencer/service/src/lib.rs b/sequencer/service/src/lib.rs index 319b75ad..5373b31f 100644 --- a/sequencer/service/src/lib.rs +++ b/sequencer/service/src/lib.rs @@ -5,13 +5,15 @@ use bytesize::ByteSize; use common::transaction::NSSATransaction; use futures::never::Never; use jsonrpsee::server::ServerHandle; +#[cfg(not(feature = "standalone"))] +use log::warn; use log::{error, info}; use mempool::MemPoolHandle; -#[cfg(not(feature = "standalone"))] -use sequencer_core::SequencerCore; #[cfg(feature = "standalone")] use sequencer_core::SequencerCoreWithMockClients as SequencerCore; pub use sequencer_core::config::*; +#[cfg(not(feature = "standalone"))] +use sequencer_core::{SequencerCore, block_settlement_client::BlockSettlementClientTrait as _}; use sequencer_service_rpc::RpcServer as _; use tokio::{sync::Mutex, task::JoinHandle}; @@ -27,6 +29,8 @@ pub struct SequencerHandle { /// Option because of `Drop` which forbids to simply move out of `self` in `stopped()`. server_handle: Option, main_loop_handle: JoinHandle>, + retry_pending_blocks_loop_handle: JoinHandle>, + listen_for_bedrock_blocks_loop_handle: JoinHandle>, } impl SequencerHandle { @@ -34,11 +38,15 @@ impl SequencerHandle { addr: SocketAddr, server_handle: ServerHandle, main_loop_handle: JoinHandle>, + retry_pending_blocks_loop_handle: JoinHandle>, + listen_for_bedrock_blocks_loop_handle: JoinHandle>, ) -> Self { Self { addr, server_handle: Some(server_handle), main_loop_handle, + retry_pending_blocks_loop_handle, + listen_for_bedrock_blocks_loop_handle, } } @@ -52,6 +60,8 @@ impl SequencerHandle { addr: _, server_handle, main_loop_handle, + retry_pending_blocks_loop_handle, + listen_for_bedrock_blocks_loop_handle, } = &mut self; let server_handle = server_handle.take().expect("Server handle is set"); @@ -65,6 +75,16 @@ impl SequencerHandle { .context("Main loop task panicked")? .context("Main loop exited unexpectedly") } + res = retry_pending_blocks_loop_handle => { + res + .context("Retry pending blocks loop task panicked")? + .context("Retry pending blocks loop exited unexpectedly") + } + res = listen_for_bedrock_blocks_loop_handle => { + res + .context("Listen for bedrock blocks loop task panicked")? + .context("Listen for bedrock blocks loop exited unexpectedly") + } } } @@ -78,10 +98,14 @@ impl SequencerHandle { addr: _, server_handle, main_loop_handle, + retry_pending_blocks_loop_handle, + listen_for_bedrock_blocks_loop_handle, } = self; let stopped = server_handle.as_ref().is_none_or(ServerHandle::is_stopped) - || main_loop_handle.is_finished(); + || main_loop_handle.is_finished() + || retry_pending_blocks_loop_handle.is_finished() + || listen_for_bedrock_blocks_loop_handle.is_finished(); !stopped } @@ -97,9 +121,13 @@ impl Drop for SequencerHandle { addr: _, server_handle, main_loop_handle, + retry_pending_blocks_loop_handle, + listen_for_bedrock_blocks_loop_handle, } = self; main_loop_handle.abort(); + retry_pending_blocks_loop_handle.abort(); + listen_for_bedrock_blocks_loop_handle.abort(); let Some(handle) = server_handle else { return; @@ -113,6 +141,7 @@ impl Drop for SequencerHandle { pub async fn run(config: SequencerConfig, port: u16) -> Result { let block_timeout = config.block_create_timeout; + let retry_pending_blocks_timeout = config.retry_pending_blocks_timeout; let max_block_size = config.max_block_size; let (sequencer_core, mempool_handle) = SequencerCore::start_from_config(config).await; @@ -130,10 +159,34 @@ pub async fn run(config: SequencerConfig, port: u16) -> Result .await?; info!("RPC server started"); - info!("Starting main sequencer loop"); - let main_loop_handle = tokio::spawn(main_loop(seq_core_wrapped, block_timeout)); + #[cfg(not(feature = "standalone"))] + { + info!("Submitting stored pending blocks"); + retry_pending_blocks(&seq_core_wrapped) + .await + .expect("Failed to submit pending blocks on startup"); + } - Ok(SequencerHandle::new(addr, server_handle, main_loop_handle)) + info!("Starting main sequencer loop"); + let main_loop_handle = tokio::spawn(main_loop(Arc::clone(&seq_core_wrapped), block_timeout)); + + info!("Starting pending block retry loop"); + let retry_pending_blocks_loop_handle = tokio::spawn(retry_pending_blocks_loop( + Arc::clone(&seq_core_wrapped), + retry_pending_blocks_timeout, + )); + + info!("Starting bedrock block listening loop"); + let listen_for_bedrock_blocks_loop_handle = + tokio::spawn(listen_for_bedrock_blocks_loop(seq_core_wrapped)); + + Ok(SequencerHandle::new( + addr, + server_handle, + main_loop_handle, + retry_pending_blocks_loop_handle, + listen_for_bedrock_blocks_loop_handle, + )) } async fn run_server( @@ -182,3 +235,118 @@ async fn main_loop(seq_core: Arc>, block_timeout: Duration) info!("Waiting for new transactions"); } } + +#[cfg(not(feature = "standalone"))] +async fn retry_pending_blocks(seq_core: &Arc>) -> Result<()> { + use std::time::Instant; + + use log::debug; + + let (mut pending_blocks, block_settlement_client) = { + let sequencer_core = seq_core.lock().await; + let client = sequencer_core.block_settlement_client(); + let pending_blocks = sequencer_core + .get_pending_blocks() + .expect("Sequencer should be able to retrieve pending blocks"); + (pending_blocks, client) + }; + + pending_blocks.sort_by(|block1, block2| block1.header.block_id.cmp(&block2.header.block_id)); + + if !pending_blocks.is_empty() { + info!( + "Resubmitting blocks from {} to {}", + pending_blocks.first().unwrap().header.block_id, + pending_blocks.last().unwrap().header.block_id + ); + } + + for block in &pending_blocks { + debug!( + "Resubmitting pending block with id {}", + block.header.block_id + ); + // TODO: We could cache the inscribe tx for each pending block to avoid re-creating it + // on every retry. + let now = Instant::now(); + let (tx, _msg_id) = block_settlement_client + .create_inscribe_tx(block) + .context("Failed to create inscribe tx for pending block")?; + + debug!("Create inscribe: {:?}", now.elapsed()); + + let now = Instant::now(); + if let Err(e) = block_settlement_client + .submit_inscribe_tx_to_bedrock(tx) + .await + { + warn!( + "Failed to resubmit block with id {} with error {e:#}", + block.header.block_id + ); + } + debug!("Post: {:?}", now.elapsed()); + } + Ok(()) +} + +#[cfg(not(feature = "standalone"))] +async fn retry_pending_blocks_loop( + seq_core: Arc>, + retry_pending_blocks_timeout: Duration, +) -> Result { + loop { + tokio::time::sleep(retry_pending_blocks_timeout).await; + retry_pending_blocks(&seq_core).await?; + } +} + +#[cfg(not(feature = "standalone"))] +async fn listen_for_bedrock_blocks_loop(seq_core: Arc>) -> Result { + use indexer_service_rpc::RpcClient as _; + + let indexer_client = seq_core.lock().await.indexer_client(); + + let retry_delay = Duration::from_secs(5); + + loop { + // TODO: Subscribe from the first pending block ID? + let mut subscription = indexer_client + .subscribe_to_finalized_blocks() + .await + .context("Failed to subscribe to finalized blocks")?; + + while let Some(block_id) = subscription.next().await { + let block_id = block_id.context("Failed to get next block from subscription")?; + + info!("Received new L2 block with ID {block_id}"); + + seq_core + .lock() + .await + .clean_finalized_blocks_from_db(block_id) + .with_context(|| { + format!("Failed to clean finalized blocks from DB for block ID {block_id}") + })?; + } + + warn!( + "Block subscription closed unexpectedly, reason: {:?}, retrying after {retry_delay:?}", + subscription.close_reason() + ); + tokio::time::sleep(retry_delay).await; + } +} + +#[cfg(feature = "standalone")] +async fn listen_for_bedrock_blocks_loop(_seq_core: Arc>) -> Result { + std::future::pending::>().await +} + +#[cfg(feature = "standalone")] +async fn retry_pending_blocks_loop( + _seq_core: Arc>, + _retry_pending_blocks_timeout: Duration, +) -> Result { + std::future::pending::>().await +} diff --git a/sequencer/service/src/service.rs b/sequencer/service/src/service.rs index 0bb8e1dd..71645363 100644 --- a/sequencer/service/src/service.rs +++ b/sequencer/service/src/service.rs @@ -8,7 +8,10 @@ use jsonrpsee::{ use log::warn; use mempool::MemPoolHandle; use nssa::{self, program::Program}; -use sequencer_core::{DbError, SequencerCore, block_publisher::BlockPublisherTrait}; +use sequencer_core::{ + DbError, SequencerCore, block_settlement_client::BlockSettlementClientTrait, + indexer_client::IndexerClientTrait, +}; use sequencer_service_protocol::{ Account, AccountId, Block, BlockId, Commitment, HashType, MembershipProof, Nonce, ProgramId, }; @@ -16,15 +19,15 @@ use tokio::sync::Mutex; const NOT_FOUND_ERROR_CODE: i32 = -31999; -pub struct SequencerService { - sequencer: Arc>>, +pub struct SequencerService { + sequencer: Arc>>, mempool_handle: MemPoolHandle, max_block_size: u64, } -impl SequencerService { +impl SequencerService { pub const fn new( - sequencer: Arc>>, + sequencer: Arc>>, mempool_handle: MemPoolHandle, max_block_size: u64, ) -> Self { @@ -37,8 +40,8 @@ impl SequencerService { } #[async_trait] -impl sequencer_service_rpc::RpcServer - for SequencerService +impl + sequencer_service_rpc::RpcServer for SequencerService { async fn send_transaction(&self, tx: NSSATransaction) -> Result { // Reserve ~200 bytes for block header overhead diff --git a/storage/src/indexer/indexer_cells.rs b/storage/src/indexer/indexer_cells.rs index 615902bd..76a2c035 100644 --- a/storage/src/indexer/indexer_cells.rs +++ b/storage/src/indexer/indexer_cells.rs @@ -8,8 +8,7 @@ use crate::{ indexer::{ ACC_NUM_CELL_NAME, BLOCK_HASH_CELL_NAME, BREAKPOINT_CELL_NAME, CF_ACC_META, CF_BREAKPOINT_NAME, CF_HASH_TO_ID, CF_TX_TO_ID, DB_META_LAST_BREAKPOINT_ID, - DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY, DB_META_ZONE_SDK_INDEXER_CURSOR_KEY, - TX_HASH_CELL_NAME, + DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY, TX_HASH_CELL_NAME, }, }; @@ -212,41 +211,6 @@ impl SimpleWritableCell for AccNumTxCell { } } -/// Opaque bytes for the zone-sdk indexer cursor `Option<(MsgId, Slot)>`. -/// The caller serializes via `serde_json` (neither type derives borsh). -#[derive(BorshDeserialize)] -pub struct ZoneSdkIndexerCursorCellOwned(pub Vec); - -impl SimpleStorableCell for ZoneSdkIndexerCursorCellOwned { - type KeyParams = (); - - const CELL_NAME: &'static str = DB_META_ZONE_SDK_INDEXER_CURSOR_KEY; - const CF_NAME: &'static str = CF_META_NAME; -} - -impl SimpleReadableCell for ZoneSdkIndexerCursorCellOwned {} - -#[derive(BorshSerialize)] -pub struct ZoneSdkIndexerCursorCellRef<'bytes>(pub &'bytes [u8]); - -impl SimpleStorableCell for ZoneSdkIndexerCursorCellRef<'_> { - type KeyParams = (); - - const CELL_NAME: &'static str = DB_META_ZONE_SDK_INDEXER_CURSOR_KEY; - const CF_NAME: &'static str = CF_META_NAME; -} - -impl SimpleWritableCell for ZoneSdkIndexerCursorCellRef<'_> { - fn value_constructor(&self) -> DbResult> { - borsh::to_vec(&self).map_err(|err| { - DbError::borsh_cast_message( - err, - Some("Failed to serialize zone-sdk indexer cursor cell".to_owned()), - ) - }) - } -} - #[cfg(test)] mod uniform_tests { use crate::{ diff --git a/storage/src/indexer/mod.rs b/storage/src/indexer/mod.rs index 75538835..7ef21258 100644 --- a/storage/src/indexer/mod.rs +++ b/storage/src/indexer/mod.rs @@ -22,8 +22,6 @@ pub const DB_META_LAST_OBSERVED_L1_LIB_HEADER_ID_IN_DB_KEY: &str = "last_observed_l1_lib_header_in_db"; /// Key base for storing metainformation about the last breakpoint. pub const DB_META_LAST_BREAKPOINT_ID: &str = "last_breakpoint_id"; -/// Key base for storing the zone-sdk indexer cursor (opaque bytes). -pub const DB_META_ZONE_SDK_INDEXER_CURSOR_KEY: &str = "zone_sdk_indexer_cursor"; /// Cell name for a breakpoint. pub const BREAKPOINT_CELL_NAME: &str = "breakpoint"; diff --git a/storage/src/indexer/read_once.rs b/storage/src/indexer/read_once.rs index 8ab7fd23..b1ae0ada 100644 --- a/storage/src/indexer/read_once.rs +++ b/storage/src/indexer/read_once.rs @@ -4,7 +4,7 @@ use crate::{ cells::shared_cells::{BlockCell, FirstBlockCell, FirstBlockSetCell, LastBlockCell}, indexer::indexer_cells::{ AccNumTxCell, BlockHashToBlockIdMapCell, BreakpointCellOwned, LastBreakpointIdCell, - LastObservedL1LibHeaderCell, TxHashToBlockIdMapCell, ZoneSdkIndexerCursorCellOwned, + LastObservedL1LibHeaderCell, TxHashToBlockIdMapCell, }, }; @@ -64,10 +64,4 @@ impl RocksDBIO { self.get_opt::(acc_id) .map(|opt| opt.map(|cell| cell.0)) } - - pub fn get_zone_sdk_indexer_cursor_bytes(&self) -> DbResult>> { - Ok(self - .get_opt::(())? - .map(|cell| cell.0)) - } } diff --git a/storage/src/indexer/write_non_atomic.rs b/storage/src/indexer/write_non_atomic.rs index 505360fa..62b466a2 100644 --- a/storage/src/indexer/write_non_atomic.rs +++ b/storage/src/indexer/write_non_atomic.rs @@ -4,7 +4,6 @@ use crate::{ cells::shared_cells::{FirstBlockSetCell, LastBlockCell}, indexer::indexer_cells::{ BreakpointCellRef, LastBreakpointIdCell, LastObservedL1LibHeaderCell, - ZoneSdkIndexerCursorCellRef, }, }; @@ -31,10 +30,6 @@ impl RocksDBIO { self.put(&FirstBlockSetCell(true), ()) } - pub fn put_zone_sdk_indexer_cursor_bytes(&self, bytes: &[u8]) -> DbResult<()> { - self.put(&ZoneSdkIndexerCursorCellRef(bytes), ()) - } - // State pub fn put_breakpoint(&self, br_id: u64, breakpoint: &V03State) -> DbResult<()> { diff --git a/storage/src/sequencer/mod.rs b/storage/src/sequencer/mod.rs index 537d198d..508f6c29 100644 --- a/storage/src/sequencer/mod.rs +++ b/storage/src/sequencer/mod.rs @@ -12,7 +12,7 @@ use crate::{ error::DbError, sequencer::sequencer_cells::{ LastFinalizedBlockIdCell, LatestBlockMetaCellOwned, LatestBlockMetaCellRef, - NSSAStateCellOwned, NSSAStateCellRef, ZoneSdkCheckpointCellOwned, ZoneSdkCheckpointCellRef, + NSSAStateCellOwned, NSSAStateCellRef, }, }; @@ -22,8 +22,6 @@ pub mod sequencer_cells; pub const DB_META_LAST_FINALIZED_BLOCK_ID: &str = "last_finalized_block_id"; /// Key base for storing metainformation about the latest block meta. pub const DB_META_LATEST_BLOCK_META_KEY: &str = "latest_block_meta"; -/// Key base for storing the zone-sdk sequencer checkpoint (opaque bytes). -pub const DB_META_ZONE_SDK_CHECKPOINT_KEY: &str = "zone_sdk_checkpoint"; /// Key base for storing the NSSA state. pub const DB_NSSA_STATE_KEY: &str = "nssa_state"; @@ -207,16 +205,6 @@ impl RocksDBIO { self.get::(()).map(|val| val.0) } - pub fn get_zone_sdk_checkpoint_bytes(&self) -> DbResult>> { - Ok(self - .get_opt::(())? - .map(|cell| cell.0)) - } - - pub fn put_zone_sdk_checkpoint_bytes(&self, bytes: &[u8]) -> DbResult<()> { - self.put(&ZoneSdkCheckpointCellRef(bytes), ()) - } - pub fn put_block( &self, block: &Block, @@ -287,22 +275,6 @@ impl RocksDBIO { Ok(()) } - /// Mark every pending block with `block_id <= last_finalized` as finalized. - /// Idempotent — already-finalized blocks are skipped. - pub fn clean_pending_blocks_up_to(&self, last_finalized: u64) -> DbResult<()> { - let pending_ids: Vec = self - .get_all_blocks() - .filter_map(Result::ok) - .filter(|b| matches!(b.bedrock_status, BedrockStatus::Pending)) - .map(|b| b.header.block_id) - .filter(|id| *id <= last_finalized) - .collect(); - for id in pending_ids { - self.mark_block_as_finalized(id)?; - } - Ok(()) - } - pub fn mark_block_as_finalized(&self, block_id: u64) -> DbResult<()> { let mut block = self.get_block(block_id)?.ok_or_else(|| { DbError::db_interaction_error(format!("Block with id {block_id} not found")) diff --git a/storage/src/sequencer/sequencer_cells.rs b/storage/src/sequencer/sequencer_cells.rs index 2bf65367..0ad092d7 100644 --- a/storage/src/sequencer/sequencer_cells.rs +++ b/storage/src/sequencer/sequencer_cells.rs @@ -8,7 +8,7 @@ use crate::{ error::DbError, sequencer::{ CF_NSSA_STATE_NAME, DB_META_LAST_FINALIZED_BLOCK_ID, DB_META_LATEST_BLOCK_META_KEY, - DB_META_ZONE_SDK_CHECKPOINT_KEY, DB_NSSA_STATE_KEY, + DB_NSSA_STATE_KEY, }, }; @@ -95,42 +95,6 @@ impl SimpleWritableCell for LatestBlockMetaCellRef<'_> { } } -/// Opaque bytes for the zone-sdk sequencer checkpoint. The caller is -/// responsible for the actual encoding (we use `serde_json` since -/// `SequencerCheckpoint` only derives serde, not borsh). -#[derive(BorshDeserialize)] -pub struct ZoneSdkCheckpointCellOwned(pub Vec); - -impl SimpleStorableCell for ZoneSdkCheckpointCellOwned { - type KeyParams = (); - - const CELL_NAME: &'static str = DB_META_ZONE_SDK_CHECKPOINT_KEY; - const CF_NAME: &'static str = CF_META_NAME; -} - -impl SimpleReadableCell for ZoneSdkCheckpointCellOwned {} - -#[derive(BorshSerialize)] -pub struct ZoneSdkCheckpointCellRef<'bytes>(pub &'bytes [u8]); - -impl SimpleStorableCell for ZoneSdkCheckpointCellRef<'_> { - type KeyParams = (); - - const CELL_NAME: &'static str = DB_META_ZONE_SDK_CHECKPOINT_KEY; - const CF_NAME: &'static str = CF_META_NAME; -} - -impl SimpleWritableCell for ZoneSdkCheckpointCellRef<'_> { - fn value_constructor(&self) -> DbResult> { - borsh::to_vec(&self).map_err(|err| { - DbError::borsh_cast_message( - err, - Some("Failed to serialize zone-sdk checkpoint cell".to_owned()), - ) - }) - } -} - #[cfg(test)] mod uniform_tests { use crate::{ diff --git a/test_program_methods/guest/src/bin/auth_transfer_proxy.rs b/test_program_methods/guest/src/bin/auth_transfer_proxy.rs deleted file mode 100644 index 17316f16..00000000 --- a/test_program_methods/guest/src/bin/auth_transfer_proxy.rs +++ /dev/null @@ -1,97 +0,0 @@ -use nssa_core::program::{ - AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, - read_nssa_inputs, -}; - -/// PDA authorization program that delegates balance operations to `authenticated_transfer`. -/// -/// The PDA is owned by `authenticated_transfer`, not by this program. This program's role -/// is solely to provide PDA authorization via `pda_seeds` in chained calls. -/// -/// Instruction: `(pda_seed, auth_transfer_id, amount, is_withdraw)`. -/// -/// **Init** (`is_withdraw = false`, 1 pre-state `[pda]`): -/// Chains to `authenticated_transfer` with `instruction=0` (init path) and `pda_seeds=[seed]` -/// to initialize the PDA under `authenticated_transfer`'s ownership. -/// -/// **Withdraw** (`is_withdraw = true`, 2 pre-states `[pda, recipient]`): -/// Chains to `authenticated_transfer` with the amount and `pda_seeds=[seed]` to authorize -/// the PDA for a balance transfer. The actual balance modification happens in -/// `authenticated_transfer`, not here. -/// -/// **Deposit**: done directly via `authenticated_transfer` (no need for this program). -type Instruction = (PdaSeed, ProgramId, u128, bool); - -#[expect( - clippy::allow_attributes, - reason = "allow is needed because the clones are only redundant in test compilation" -)] -#[allow( - clippy::redundant_clone, - reason = "clones needed in non-test compilation" -)] -fn main() { - let ( - ProgramInput { - self_program_id, - caller_program_id, - pre_states, - instruction: (pda_seed, auth_transfer_id, amount, is_withdraw), - }, - instruction_words, - ) = read_nssa_inputs::(); - - if is_withdraw { - let Ok([pda_pre, recipient_pre]) = <[_; 2]>::try_from(pre_states.clone()) else { - panic!("expected exactly 2 pre_states for withdraw: [pda, recipient]"); - }; - - // Post-states stay unchanged in this program. The actual balance transfer - // happens in the chained call to authenticated_transfer. - let pda_post = AccountPostState::new(pda_pre.account.clone()); - let recipient_post = AccountPostState::new(recipient_pre.account.clone()); - - // Chain to authenticated_transfer with pda_seeds to authorize the PDA. - // The circuit's resolve_authorization_and_record_bindings establishes the - // private PDA (seed, npk) binding when pda_seeds match the private PDA derivation. - let mut auth_pda_pre = pda_pre; - auth_pda_pre.is_authorized = true; - let auth_call = - ChainedCall::new(auth_transfer_id, vec![auth_pda_pre, recipient_pre], &amount) - .with_pda_seeds(vec![pda_seed]); - - ProgramOutput::new( - self_program_id, - caller_program_id, - instruction_words, - pre_states, - vec![pda_post, recipient_post], - ) - .with_chained_calls(vec![auth_call]) - .write(); - } else { - // Init: initialize the PDA under authenticated_transfer's ownership. - let Ok([pda_pre]) = <[_; 1]>::try_from(pre_states.clone()) else { - panic!("expected exactly 1 pre_state for init: [pda]"); - }; - - let pda_post = AccountPostState::new(pda_pre.account.clone()); - - // Chain to authenticated_transfer with instruction=0 (init path) and pda_seeds - // to authorize the PDA. authenticated_transfer will claim it with Claim::Authorized. - let mut auth_pda_pre = pda_pre; - auth_pda_pre.is_authorized = true; - let auth_call = ChainedCall::new(auth_transfer_id, vec![auth_pda_pre], &0_u128) - .with_pda_seeds(vec![pda_seed]); - - ProgramOutput::new( - self_program_id, - caller_program_id, - instruction_words, - pre_states, - vec![pda_post], - ) - .with_chained_calls(vec![auth_call]) - .write(); - } -} diff --git a/test_program_methods/guest/src/bin/pda_fund_spend_proxy.rs b/test_program_methods/guest/src/bin/pda_fund_spend_proxy.rs deleted file mode 100644 index c02261f9..00000000 --- a/test_program_methods/guest/src/bin/pda_fund_spend_proxy.rs +++ /dev/null @@ -1,70 +0,0 @@ -use nssa_core::{ - account::AccountWithMetadata, - program::{ - AccountPostState, ChainedCall, PdaSeed, ProgramId, ProgramInput, ProgramOutput, - read_nssa_inputs, - }, -}; -use risc0_zkvm::serde::to_vec; - -/// Proxy for interacting with private PDAs via `auth_transfer`. -/// -/// The `is_fund` flag selects the operating mode: -/// -/// - `false` (Spend): `pre_states = [pda (authorized), recipient]`. Debits the PDA. The PDA-to-npk -/// binding is established via `pda_seeds` in the chained call to `auth_transfer`. -/// -/// - `true` (Fund): `pre_states = [sender (authorized), pda (foreign/uninitialized)]`. Credits the -/// PDA. A direct call to `auth_transfer` cannot bind the PDA because `auth_transfer` uses -/// `Claim::Authorized`, not `Claim::Pda`. Routing through this proxy establishes the binding via -/// `pda_seeds` in the chained call. -type Instruction = (PdaSeed, u128, ProgramId, bool); - -fn main() { - let ( - ProgramInput { - self_program_id, - caller_program_id, - pre_states, - instruction: (seed, amount, auth_transfer_id, is_fund), - }, - instruction_words, - ) = read_nssa_inputs::(); - - let Ok([first, second]) = <[_; 2]>::try_from(pre_states) else { - return; - }; - - assert!(first.is_authorized, "first pre_state must be authorized"); - - let chained_pre_states = if is_fund { - let pda_authorized = AccountWithMetadata { - account: second.account.clone(), - account_id: second.account_id, - is_authorized: true, - }; - vec![first.clone(), pda_authorized] - } else { - vec![first.clone(), second.clone()] - }; - - let first_post = AccountPostState::new(first.account.clone()); - let second_post = AccountPostState::new(second.account.clone()); - - let chained_call = ChainedCall { - program_id: auth_transfer_id, - instruction_data: to_vec(&amount).unwrap(), - pre_states: chained_pre_states, - pda_seeds: vec![seed], - }; - - ProgramOutput::new( - self_program_id, - caller_program_id, - instruction_words, - vec![first, second], - vec![first_post, second_post], - ) - .with_chained_calls(vec![chained_call]) - .write(); -} diff --git a/testnet_initial_state/src/lib.rs b/testnet_initial_state/src/lib.rs index b5c91d4d..f6f1e288 100644 --- a/testnet_initial_state/src/lib.rs +++ b/testnet_initial_state/src/lib.rs @@ -103,10 +103,7 @@ pub struct PrivateAccountPrivateInitialData { impl PrivateAccountPrivateInitialData { #[must_use] pub fn account_id(&self) -> nssa::AccountId { - nssa::AccountId::for_regular_private_account( - &self.key_chain.nullifier_public_key, - self.identifier, - ) + nssa::AccountId::from((&self.key_chain.nullifier_public_key, self.identifier)) } } @@ -211,7 +208,7 @@ pub fn initial_state() -> V03State { .iter() .map(|init_comm_data| { let npk = &init_comm_data.npk; - let account_id = nssa::AccountId::for_regular_private_account(npk, 0); + let account_id = nssa::AccountId::from((npk, 0)); let mut acc = init_comm_data.account.clone(); diff --git a/wallet-ffi/Cargo.toml b/wallet-ffi/Cargo.toml index 37d552ec..0af20a54 100644 --- a/wallet-ffi/Cargo.toml +++ b/wallet-ffi/Cargo.toml @@ -15,7 +15,6 @@ wallet.workspace = true nssa.workspace = true nssa_core.workspace = true sequencer_service_rpc = { workspace = true, features = ["client"] } - tokio.workspace = true [build-dependencies] diff --git a/wallet/src/chain_storage.rs b/wallet/src/chain_storage.rs index 1b883e2f..3bfdb383 100644 --- a/wallet/src/chain_storage.rs +++ b/wallet/src/chain_storage.rs @@ -11,7 +11,6 @@ use key_protocol::{ }; use log::debug; use nssa::program::Program; -use nssa_core::PrivateAccountKind; use crate::config::{InitialAccountData, Label, PersistentAccountData, WalletConfig}; @@ -79,8 +78,8 @@ impl WalletChainStore { PersistentAccountData::Private(data) => { let npk = data.data.value.0.nullifier_public_key; let chain_index = data.chain_index; - for kind in &data.kinds { - let account_id = nssa::AccountId::for_private_account(&npk, kind); + for identifier in &data.identifiers { + let account_id = nssa::AccountId::from((&npk, *identifier)); private_tree .account_id_map .insert(account_id, chain_index.clone()); @@ -96,10 +95,7 @@ impl WalletChainStore { data.account_id(), UserPrivateAccountData { key_chain: data.key_chain, - accounts: vec![( - PrivateAccountKind::Regular(data.identifier), - data.account, - )], + accounts: vec![(data.identifier, data.account)], }, ); } @@ -145,7 +141,7 @@ impl WalletChainStore { account_id, UserPrivateAccountData { key_chain: data.key_chain, - accounts: vec![(PrivateAccountKind::Regular(data.identifier), account)], + accounts: vec![(data.identifier, account)], }, ); } @@ -200,7 +196,7 @@ impl WalletChainStore { pub fn insert_private_account_data( &mut self, account_id: nssa::AccountId, - kind: &PrivateAccountKind, + identifier: nssa_core::Identifier, account: nssa_core::account::Account, ) { debug!("inserting at address {account_id}, this account {account:?}"); @@ -212,10 +208,10 @@ impl WalletChainStore { .entry(account_id) { let entry = entry.get_mut(); - if let Some((_, acc)) = entry.accounts.iter_mut().find(|(k, _)| k == kind) { + if let Some((_, acc)) = entry.accounts.iter_mut().find(|(id, _)| *id == identifier) { *acc = account; } else { - entry.accounts.push((kind.clone(), account)); + entry.accounts.push((identifier, account)); } return; } @@ -238,21 +234,24 @@ impl WalletChainStore { .key_map .get_mut(&chain_index) { - if let Some((_, acc)) = node.value.1.iter_mut().find(|(k, _)| k == kind) { + if let Some((_, acc)) = node.value.1.iter_mut().find(|(id, _)| *id == identifier) { *acc = account; } else { - node.value.1.push((kind.clone(), account)); + node.value.1.push((identifier, account)); } } } else { // Node not yet in account_id_map — find it by checking all nodes for (ci, node) in &mut self.user_data.private_key_tree.key_map { - let npk = &node.value.0.nullifier_public_key; - if nssa::AccountId::for_private_account(npk, kind) == account_id { - if let Some((_, acc)) = node.value.1.iter_mut().find(|(k, _)| k == kind) { + let expected_id = + nssa::AccountId::from((&node.value.0.nullifier_public_key, identifier)); + if expected_id == account_id { + if let Some((_, acc)) = + node.value.1.iter_mut().find(|(id, _)| *id == identifier) + { *acc = account; } else { - node.value.1.push((kind.clone(), account)); + node.value.1.push((identifier, account)); } // Register in account_id_map self.user_data @@ -298,7 +297,7 @@ mod tests { data: Some(public_data), }), PersistentAccountData::Private(Box::new(PersistentAccountDataPrivate { - kinds: vec![], + identifiers: vec![], chain_index: ChainIndex::root(), data: private_data, })), diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index e2ae12e7..5ed99d90 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -89,27 +89,6 @@ pub enum NewSubcommand { /// Label to assign to the new account. label: Option, }, - /// Create a shared private account from a group's GMS. - PrivateGms { - /// Group name to derive keys from. - group: String, - #[arg(short, long)] - /// Label to assign to the new account. - label: Option, - #[arg(long)] - /// Create a PDA account (requires --seed and --program-id). - pda: bool, - #[arg(long, requires = "pda")] - /// PDA seed as 64-character hex string. - seed: Option, - #[arg(long, requires = "pda")] - /// Program ID as hex string. - program_id: Option, - #[arg(long, requires = "pda")] - /// Identifier that diversifies this PDA within the (`program_id`, seed, npk) family. - /// Defaults to a random value if not specified. - identifier: Option, - }, /// Recommended for receiving from multiple senders: creates a key node (npk + vpk) without /// registering any account. PrivateAccountsKey { @@ -201,73 +180,9 @@ impl WalletSubcommand for NewSubcommand { ); wallet_core.store_persistent_data().await?; + Ok(SubcommandReturnValue::RegisterAccount { account_id }) } - Self::PrivateGms { - group, - label, - pda, - seed, - program_id, - identifier, - } => { - if let Some(label) = &label - && wallet_core - .storage - .labels - .values() - .any(|l| l.to_string() == *label) - { - anyhow::bail!("Label '{label}' is already in use by another account"); - } - - let info = if pda { - let seed_hex = seed.context("--seed is required for PDA accounts")?; - let pid_hex = - program_id.context("--program-id is required for PDA accounts")?; - - let seed_bytes: [u8; 32] = hex::decode(&seed_hex) - .context("Invalid seed hex")? - .try_into() - .map_err(|_err| anyhow::anyhow!("Seed must be exactly 32 bytes"))?; - let pda_seed = nssa_core::program::PdaSeed::new(seed_bytes); - - let pid_bytes = hex::decode(&pid_hex).context("Invalid program ID hex")?; - if pid_bytes.len() != 32 { - anyhow::bail!("Program ID must be exactly 32 bytes"); - } - let mut pid: nssa_core::program::ProgramId = [0; 8]; - for (i, chunk) in pid_bytes.chunks_exact(4).enumerate() { - pid[i] = u32::from_le_bytes(chunk.try_into().unwrap()); - } - - wallet_core.create_shared_pda_account( - &group, - pda_seed, - pid, - identifier.unwrap_or_else(rand::random), - )? - } else { - wallet_core.create_shared_regular_account(&group)? - }; - - if let Some(label) = label { - wallet_core - .storage - .labels - .insert(info.account_id.to_string(), Label::new(label)); - } - - println!("Shared account from group '{group}'"); - println!("AccountId: Private/{}", info.account_id); - println!("NPK: {}", hex::encode(info.npk.0)); - println!("VPK: {}", hex::encode(&info.vpk.0)); - - wallet_core.store_persistent_data().await?; - Ok(SubcommandReturnValue::RegisterAccount { - account_id: info.account_id, - }) - } Self::PrivateAccountsKey { cci } => { let chain_index = wallet_core.create_private_accounts_key(cci); diff --git a/wallet/src/cli/group.rs b/wallet/src/cli/group.rs deleted file mode 100644 index e1dd9159..00000000 --- a/wallet/src/cli/group.rs +++ /dev/null @@ -1,158 +0,0 @@ -use anyhow::{Context as _, Result}; -use clap::Subcommand; -use key_protocol::key_management::group_key_holder::{GroupKeyHolder, SealingPublicKey}; - -use crate::{ - WalletCore, - cli::{SubcommandReturnValue, WalletSubcommand}, -}; - -/// Group key management commands. -#[derive(Subcommand, Debug, Clone)] -pub enum GroupSubcommand { - /// Create a new group with a fresh random GMS. - New { - /// Human-readable name for the group. - name: String, - }, - /// List all groups. - #[command(visible_alias = "ls")] - List, - /// Remove a group from the wallet. - Remove { - /// Group name. - name: String, - }, - /// Seal the group's GMS for a recipient (invite). - Invite { - /// Group name. - name: String, - /// Recipient's sealing public key as hex string. - #[arg(long)] - key: String, - }, - /// Unseal a received GMS and store it (join a group). - /// Uses the wallet's dedicated sealing key (generated via `new-sealing-key`). - Join { - /// Human-readable name to store the group under. - name: String, - /// Sealed GMS as hex string (from the inviter). - #[arg(long)] - sealed: String, - }, - /// Generate a dedicated sealing key pair for GMS distribution. - /// Share the printed public key with group members so they can seal GMS for you. - NewSealingKey, -} - -impl WalletSubcommand for GroupSubcommand { - async fn handle_subcommand( - self, - wallet_core: &mut WalletCore, - ) -> Result { - match self { - Self::New { name } => { - if wallet_core - .storage() - .user_data - .group_key_holder(&name) - .is_some() - { - anyhow::bail!("Group '{name}' already exists"); - } - - let holder = GroupKeyHolder::new(); - wallet_core.insert_group_key_holder(name.clone(), holder); - wallet_core.store_persistent_data().await?; - - println!("Created group '{name}'"); - Ok(SubcommandReturnValue::Empty) - } - - Self::List => { - let holders = &wallet_core.storage().user_data.group_key_holders; - if holders.is_empty() { - println!("No groups found"); - } else { - for name in holders.keys() { - println!("{name}"); - } - } - Ok(SubcommandReturnValue::Empty) - } - - Self::Remove { name } => { - if wallet_core.remove_group_key_holder(&name).is_none() { - anyhow::bail!("Group '{name}' not found"); - } - - wallet_core.store_persistent_data().await?; - println!("Removed group '{name}'"); - Ok(SubcommandReturnValue::Empty) - } - - Self::Invite { name, key } => { - let holder = wallet_core - .storage() - .user_data - .group_key_holder(&name) - .context(format!("Group '{name}' not found"))?; - - let key_bytes = hex::decode(&key).context("Invalid key hex")?; - let recipient_key = - key_protocol::key_management::group_key_holder::SealingPublicKey::from_bytes( - key_bytes, - ); - - let sealed = holder.seal_for(&recipient_key); - println!("{}", hex::encode(&sealed)); - Ok(SubcommandReturnValue::Empty) - } - - Self::Join { name, sealed } => { - if wallet_core - .storage() - .user_data - .group_key_holder(&name) - .is_some() - { - anyhow::bail!("Group '{name}' already exists"); - } - - let sealing_key = - wallet_core.storage().user_data.sealing_secret_key.context( - "No sealing key found. Run 'wallet group new-sealing-key' first.", - )?; - - let sealed_bytes = hex::decode(&sealed).context("Invalid sealed hex")?; - - let holder = GroupKeyHolder::unseal(&sealed_bytes, &sealing_key) - .map_err(|e| anyhow::anyhow!("Failed to unseal: {e:?}"))?; - - wallet_core.insert_group_key_holder(name.clone(), holder); - wallet_core.store_persistent_data().await?; - - println!("Joined group '{name}'"); - Ok(SubcommandReturnValue::Empty) - } - - Self::NewSealingKey => { - if wallet_core.storage().user_data.sealing_secret_key.is_some() { - anyhow::bail!("Sealing key already exists. Each wallet has one sealing key."); - } - - let mut secret: nssa_core::encryption::Scalar = [0_u8; 32]; - rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut secret); - let public_key = SealingPublicKey::from_scalar(secret); - - wallet_core.set_sealing_secret_key(secret); - wallet_core.store_persistent_data().await?; - - println!("Sealing key generated."); - println!("Public key: {}", hex::encode(public_key.to_bytes())); - println!("Share this public key with group members so they can seal GMS for you."); - Ok(SubcommandReturnValue::Empty) - } - } - } -} diff --git a/wallet/src/cli/mod.rs b/wallet/src/cli/mod.rs index df0ac840..17a90f79 100644 --- a/wallet/src/cli/mod.rs +++ b/wallet/src/cli/mod.rs @@ -16,7 +16,6 @@ use crate::{ chain::ChainSubcommand, config::ConfigSubcommand, keycard::KeycardSubcommand, - group::GroupSubcommand, programs::{ amm::AmmProgramAgnosticSubcommand, ata::AtaSubcommand, native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, @@ -29,7 +28,6 @@ pub mod account; pub mod chain; pub mod config; pub mod keycard; -pub mod group; pub mod programs; pub(crate) trait WalletSubcommand { @@ -62,9 +60,6 @@ pub enum Command { /// Associated Token Account program interaction subcommand. #[command(subcommand)] Ata(AtaSubcommand), - /// Group key management (create, invite, join, derive keys). - #[command(subcommand)] - Group(GroupSubcommand), /// Check the wallet can connect to the node and builtin local programs /// match the remote versions. CheckHealth, @@ -177,7 +172,6 @@ pub async fn execute_subcommand( Command::Token(token_subcommand) => token_subcommand.handle_subcommand(wallet_core).await?, Command::AMM(amm_subcommand) => amm_subcommand.handle_subcommand(wallet_core).await?, Command::Ata(ata_subcommand) => ata_subcommand.handle_subcommand(wallet_core).await?, - Command::Group(group_subcommand) => group_subcommand.handle_subcommand(wallet_core).await?, Command::Config(config_subcommand) => { config_subcommand.handle_subcommand(wallet_core).await? } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index 1503c48e..185648e4 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -28,7 +28,7 @@ pub struct PersistentAccountDataPublic { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PersistentAccountDataPrivate { - pub kinds: Vec, + pub identifiers: Vec, pub chain_index: ChainIndex, pub data: ChildKeysPrivate, } @@ -98,21 +98,6 @@ pub struct PersistentStorage { /// "2rnKprXqWGWJTkDZKsQbFXa4ctKRbapsdoTKQFnaVGG8"). #[serde(default)] pub labels: HashMap, - /// Group key holders for shared account management. - #[serde(default)] - pub group_key_holders: std::collections::BTreeMap< - String, - key_protocol::key_management::group_key_holder::GroupKeyHolder, - >, - /// Cached state of shared private accounts (PDA and regular). - #[serde(default)] - pub shared_private_accounts: std::collections::BTreeMap< - nssa::AccountId, - key_protocol::key_protocol_core::SharedAccountEntry, - >, - /// Dedicated sealing secret key for GMS distribution. - #[serde(default)] - pub sealing_secret_key: Option, } impl PersistentStorage { diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 25116abd..2222ffe6 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -200,10 +200,10 @@ pub fn produce_data_for_storage( } for (chain_index, node) in &user_data.private_key_tree.key_map { - let kinds = node.value.1.iter().map(|(kind, _)| kind.clone()).collect(); + let identifiers = node.value.1.iter().map(|(id, _)| *id).collect(); vec_for_storage.push( PersistentAccountDataPrivate { - kinds, + identifiers, chain_index: chain_index.clone(), data: node.clone(), } @@ -222,12 +222,12 @@ pub fn produce_data_for_storage( } for entry in user_data.default_user_private_accounts.values() { - for (kind, account) in &entry.accounts { + for (identifier, account) in &entry.accounts { vec_for_storage.push( InitialAccountData::Private(Box::new(PrivateAccountPrivateInitialData { account: account.clone(), key_chain: entry.key_chain.clone(), - identifier: kind.identifier(), + identifier: *identifier, })) .into(), ); @@ -238,9 +238,6 @@ pub fn produce_data_for_storage( accounts: vec_for_storage, last_synced_block, labels, - group_key_holders: user_data.group_key_holders.clone(), - shared_private_accounts: user_data.shared_private_accounts.clone(), - sealing_secret_key: user_data.sealing_secret_key, } } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index beeaf611..0de34efe 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -25,8 +25,7 @@ use nssa::{ }, }; use nssa_core::{ - Commitment, MembershipProof, PrivateAccountKind, SharedSecretKey, account::Nonce, - program::InstructionData, + Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData, }; pub use privacy_preserving_tx::PrivacyPreservingAccount; use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder}; @@ -53,13 +52,6 @@ pub enum AccDecodeData { Decode(nssa_core::SharedSecretKey, AccountId), } -/// Info returned when creating a shared account. -pub struct SharedAccountInfo { - pub account_id: AccountId, - pub npk: nssa_core::NullifierPublicKey, - pub vpk: nssa_core::encryption::ViewingPublicKey, -} - #[derive(Debug, thiserror::Error)] pub enum ExecutionFailureKind { #[error("Failed to get data from sequencer")] @@ -109,9 +101,6 @@ impl WalletCore { accounts: persistent_accounts, last_synced_block, labels, - group_key_holders, - shared_private_accounts, - sealing_secret_key, } = PersistentStorage::from_path(&storage_path).with_context(|| { format!( "Failed to read persistent storage at {}", @@ -123,13 +112,7 @@ impl WalletCore { config_path, storage_path, config_overrides, - |config| { - let mut store = WalletChainStore::new(config, persistent_accounts, labels)?; - store.user_data.group_key_holders = group_key_holders; - store.user_data.shared_private_accounts = shared_private_accounts; - store.user_data.sealing_secret_key = sealing_secret_key; - Ok(store) - }, + |config| WalletChainStore::new(config, persistent_accounts, labels), last_synced_block, ) } @@ -301,176 +284,10 @@ impl WalletCore { .value .0 .nullifier_public_key; - let account_id = AccountId::for_regular_private_account(&npk, identifier); - self.storage.insert_private_account_data( - account_id, - &PrivateAccountKind::Regular(identifier), - Account::default(), - ); - (account_id, cci) - } - - /// Insert a group key holder into storage. - pub fn insert_group_key_holder( - &mut self, - name: String, - holder: key_protocol::key_management::group_key_holder::GroupKeyHolder, - ) { - self.storage.user_data.insert_group_key_holder(name, holder); - } - - /// Set the wallet's dedicated sealing secret key. - pub const fn set_sealing_secret_key(&mut self, key: nssa_core::encryption::Scalar) { - self.storage.user_data.sealing_secret_key = Some(key); - } - - /// Resolve an `AccountId` to the appropriate `PrivacyPreservingAccount` variant. - /// Checks the key tree first, then shared private accounts. - #[must_use] - pub fn resolve_private_account( - &self, - account_id: nssa::AccountId, - ) -> Option { - // Check key tree first - if self - .storage - .user_data - .get_private_account(account_id) - .is_some() - { - return Some(PrivacyPreservingAccount::PrivateOwned(account_id)); - } - - // Check shared private accounts - let entry = self.storage.user_data.shared_private_account(&account_id)?; - let holder = self - .storage - .user_data - .group_key_holder(&entry.group_label)?; - - if let (Some(pda_seed), Some(program_id)) = (entry.pda_seed, entry.pda_program_id) { - let keys = holder.derive_keys_for_pda(&program_id, &pda_seed); - Some(PrivacyPreservingAccount::PrivatePdaShared { - account_id, - nsk: keys.nullifier_secret_key, - npk: keys.generate_nullifier_public_key(), - vpk: keys.generate_viewing_public_key(), - identifier: entry.identifier, - }) - } else { - let derivation_seed = { - use sha2::Digest as _; - let mut hasher = sha2::Sha256::new(); - hasher.update(b"/LEE/v0.3/SharedAccountTag/\x00\x00\x00\x00\x00"); - hasher.update(entry.identifier.to_le_bytes()); - let result: [u8; 32] = hasher.finalize().into(); - result - }; - let keys = holder.derive_keys_for_shared_account(&derivation_seed); - Some(PrivacyPreservingAccount::PrivateShared { - nsk: keys.nullifier_secret_key, - npk: keys.generate_nullifier_public_key(), - vpk: keys.generate_viewing_public_key(), - identifier: entry.identifier, - }) - } - } - - /// Remove a group key holder from storage. Returns the removed holder if it existed. - pub fn remove_group_key_holder( - &mut self, - name: &str, - ) -> Option { - self.storage.user_data.group_key_holders.remove(name) - } - - /// Register a shared account in storage for sync tracking. - fn register_shared_account( - &mut self, - account_id: AccountId, - group_label: String, - identifier: nssa_core::Identifier, - pda_seed: Option, - pda_program_id: Option, - ) { - use key_protocol::key_protocol_core::SharedAccountEntry; - self.storage.user_data.insert_shared_private_account( - account_id, - SharedAccountEntry { - group_label, - identifier, - pda_seed, - pda_program_id, - account: Account::default(), - }, - ); - } - - /// Create a shared PDA account from a group's GMS. Returns the `AccountId` and derived keys. - pub fn create_shared_pda_account( - &mut self, - group_name: &str, - pda_seed: nssa_core::program::PdaSeed, - program_id: nssa_core::program::ProgramId, - identifier: nssa_core::Identifier, - ) -> Result { - let holder = self - .storage - .user_data - .group_key_holder(group_name) - .context(format!("Group '{group_name}' not found"))?; - - let keys = holder.derive_keys_for_pda(&program_id, &pda_seed); - let npk = keys.generate_nullifier_public_key(); - let vpk = keys.generate_viewing_public_key(); - let account_id = AccountId::for_private_pda(&program_id, &pda_seed, &npk, identifier); - - self.register_shared_account( - account_id, - String::from(group_name), - identifier, - Some(pda_seed), - Some(program_id), - ); - - Ok(SharedAccountInfo { - account_id, - npk, - vpk, - }) - } - - /// Create a shared regular private account from a group's GMS. Returns the `AccountId` and - /// derived keys. The derivation seed is computed deterministically from a random identifier. - pub fn create_shared_regular_account(&mut self, group_name: &str) -> Result { - let identifier: nssa_core::Identifier = rand::random(); - let derivation_seed = { - use sha2::Digest as _; - let mut hasher = sha2::Sha256::new(); - hasher.update(b"/LEE/v0.3/SharedAccountTag/\x00\x00\x00\x00\x00"); - hasher.update(identifier.to_le_bytes()); - let result: [u8; 32] = hasher.finalize().into(); - result - }; - - let holder = self - .storage - .user_data - .group_key_holder(group_name) - .context(format!("Group '{group_name}' not found"))?; - - let keys = holder.derive_keys_for_shared_account(&derivation_seed); - let npk = keys.generate_nullifier_public_key(); - let vpk = keys.generate_viewing_public_key(); let account_id = AccountId::from((&npk, identifier)); - - self.register_shared_account(account_id, String::from(group_name), identifier, None, None); - - Ok(SharedAccountInfo { - account_id, - npk, - vpk, - }) + self.storage + .insert_private_account_data(account_id, identifier, Account::default()); + (account_id, cci) } /// Get account balance. @@ -508,17 +325,8 @@ impl WalletCore { #[must_use] pub fn get_private_account_commitment(&self, account_id: AccountId) -> Option { - let account = self - .storage - .user_data - .get_private_account(account_id) - .map(|(_keys, account, _identifier)| account) - .or_else(|| { - self.storage - .user_data - .shared_private_account(&account_id) - .map(|entry| entry.account.clone()) - })?; + let (_keys, account, _identifier) = + self.storage.user_data.get_private_account(account_id)?; Some(Commitment::new(&account_id, &account)) } @@ -552,7 +360,7 @@ impl WalletCore { let acc_ead = tx.message.encrypted_private_post_states[output_index].clone(); let acc_comm = tx.message.new_commitments[output_index].clone(); - let (kind, res_acc) = nssa_core::EncryptionScheme::decrypt( + let (identifier, res_acc) = nssa_core::EncryptionScheme::decrypt( &acc_ead.ciphertext, secret, &acc_comm, @@ -565,7 +373,7 @@ impl WalletCore { println!("Received new acc {res_acc:#?}"); self.storage - .insert_private_account_data(*acc_account_id, &kind, res_acc); + .insert_private_account_data(*acc_account_id, identifier, res_acc); } AccDecodeData::Skip => {} } @@ -608,7 +416,13 @@ impl WalletCore { let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( pre_states, instruction_data, - acc_manager.account_identities(), + acc_manager.visibility_mask().to_vec(), + private_account_keys + .iter() + .map(|keys| (keys.npk, keys.identifier, keys.ssk)) + .collect::>(), + acc_manager.private_account_auth(), + acc_manager.private_account_membership_proofs(), &program.to_owned(), ) .unwrap(); @@ -728,93 +542,24 @@ impl WalletCore { .try_into() .expect("Ciphertext ID is expected to fit in u32"), ) - .map(|(kind, res_acc)| { - let npk = &key_chain.nullifier_public_key; - let account_id = nssa::AccountId::for_private_account(npk, &kind); - (account_id, kind, res_acc) + .map(|(identifier, res_acc)| { + let account_id = nssa::AccountId::from(( + &key_chain.nullifier_public_key, + identifier, + )); + (account_id, identifier, res_acc) }) }) .collect::>() }) .collect::>(); - for (affected_account_id, kind, new_acc) in affected_accounts { + for (affected_account_id, identifier, new_acc) in affected_accounts { info!( "Received new account for account_id {affected_account_id:#?} with account object {new_acc:#?}" ); self.storage - .insert_private_account_data(affected_account_id, &kind, new_acc); - } - - // Scan for updates to shared accounts (GMS-derived). - self.sync_shared_private_accounts_with_tx(&tx); - } - - fn sync_shared_private_accounts_with_tx(&mut self, tx: &PrivacyPreservingTransaction) { - let shared_keys: Vec<_> = self - .storage - .user_data - .shared_private_accounts_iter() - .filter_map(|(&account_id, entry)| { - let holder = self - .storage - .user_data - .group_key_holder(&entry.group_label)?; - - let keys = match (&entry.pda_seed, &entry.pda_program_id) { - (Some(pda_seed), Some(program_id)) => { - holder.derive_keys_for_pda(program_id, pda_seed) - } - (Some(_), None) => return None, // PDA without program_id, skip - _ => { - let derivation_seed = { - use sha2::Digest as _; - let mut hasher = sha2::Sha256::new(); - hasher.update(b"/LEE/v0.3/SharedAccountTag/\x00\x00\x00\x00\x00"); - hasher.update(entry.identifier.to_le_bytes()); - let result: [u8; 32] = hasher.finalize().into(); - result - }; - holder.derive_keys_for_shared_account(&derivation_seed) - } - }; - let npk = keys.generate_nullifier_public_key(); - let vpk = keys.generate_viewing_public_key(); - let vsk = keys.viewing_secret_key; - Some((account_id, npk, vpk, vsk)) - }) - .collect(); - - for (account_id, npk, vpk, vsk) in shared_keys { - let view_tag = EncryptedAccountData::compute_view_tag(&npk, &vpk); - - for (ciph_id, encrypted_data) in tx - .message() - .encrypted_private_post_states - .iter() - .enumerate() - { - if encrypted_data.view_tag != view_tag { - continue; - } - - let shared_secret = SharedSecretKey::new(&vsk, &encrypted_data.epk); - let commitment = &tx.message.new_commitments[ciph_id]; - - if let Some((_kind, new_acc)) = nssa_core::EncryptionScheme::decrypt( - &encrypted_data.ciphertext, - &shared_secret, - commitment, - ciph_id - .try_into() - .expect("Ciphertext ID is expected to fit in u32"), - ) { - info!("Synced shared account {account_id:#?} with new state {new_acc:#?}"); - self.storage - .user_data - .update_shared_private_account_state(&account_id, new_acc); - } - } + .insert_private_account_data(affected_account_id, identifier, new_acc); } } diff --git a/wallet/src/pinata_interactions.rs b/wallet/src/pinata_interactions.rs new file mode 100644 index 00000000..77549772 --- /dev/null +++ b/wallet/src/pinata_interactions.rs @@ -0,0 +1,165 @@ +use common::{HashType, transaction::NSSATransaction}; +use sequencer_service_rpc::RpcClient as _; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{AccountId, privacy_preserving_transaction::circuit}; +use nssa_core::{MembershipProof, SharedSecretKey, account::AccountWithMetadata}; + +use crate::{ + ExecutionFailureKind, WalletCore, helperfunctions::produce_random_nonces, + transaction_utils::AccountPreparedData, +}; + +impl WalletCore { + pub async fn claim_pinata( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result { + let account_ids = vec![pinata_account_id, winner_account_id]; + let program_id = nssa::program::Program::pinata().id(); + let message = + nssa::public_transaction::Message::try_new(program_id, account_ids, vec![], solution) + .unwrap(); + + let witness_set = nssa::public_transaction::WitnessSet::for_message(&message, &[]); + let tx = nssa::PublicTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_transaction(NSSATransaction::Public(tx).into()).await?) + } + + pub async fn claim_pinata_private_owned_account_already_initialized( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + winner_proof: MembershipProof, + ) -> Result<(HashType, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: winner_nsk, + npk: winner_npk, + vpk: winner_vpk, + auth_acc: winner_pre, + proof: _, + } = self + .private_acc_preparation(winner_account_id, true, false) + .await?; + + let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); + + let program = nssa::program::Program::pinata(); + + let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); + + let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); + let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[pinata_pre, winner_pre], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + &[0, 1], + &produce_random_nonces(1), + &[(winner_npk, shared_secret_winner.clone())], + &[(winner_nsk.unwrap())], + &[winner_proof], + &program.into(), + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![pinata_account_id], + vec![], + vec![( + winner_npk, + winner_vpk.clone(), + eph_holder_winner.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret_winner], + )) + } + + pub async fn claim_pinata_private_owned_account_not_initialized( + &self, + pinata_account_id: AccountId, + winner_account_id: AccountId, + solution: u128, + ) -> Result<(HashType, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: _, + npk: winner_npk, + vpk: winner_vpk, + auth_acc: winner_pre, + proof: _, + } = self + .private_acc_preparation(winner_account_id, false, false) + .await?; + + let pinata_acc = self.get_account_public(pinata_account_id).await.unwrap(); + + let program = nssa::program::Program::pinata(); + + let pinata_pre = AccountWithMetadata::new(pinata_acc.clone(), false, pinata_account_id); + + let eph_holder_winner = EphemeralKeyHolder::new(&winner_npk); + let shared_secret_winner = eph_holder_winner.calculate_shared_secret_sender(&winner_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[pinata_pre, winner_pre], + &nssa::program::Program::serialize_instruction(solution).unwrap(), + &[0, 2], + &produce_random_nonces(1), + &[(winner_npk, shared_secret_winner.clone())], + &[], + &[], + &program.into(), + ) + .unwrap(); + + let message = + nssa::privacy_preserving_transaction::message::Message::try_from_circuit_output( + vec![pinata_account_id], + vec![], + vec![( + winner_npk, + winner_vpk.clone(), + eph_holder_winner.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = + nssa::privacy_preserving_transaction::witness_set::WitnessSet::for_message( + &message, + proof, + &[], + ); + let tx = nssa::privacy_preserving_transaction::PrivacyPreservingTransaction::new( + message, + witness_set, + ); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret_winner], + )) + } +} diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index 6d118c0c..3df2ecc1 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -2,8 +2,7 @@ use anyhow::Result; use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; use nssa::{AccountId, PrivateKey}; use nssa_core::{ - Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey, - SharedSecretKey, + Identifier, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, account::{AccountWithMetadata, Nonce}, encryption::{EphemeralPublicKey, ViewingPublicKey}, }; @@ -19,35 +18,6 @@ pub enum PrivacyPreservingAccount { vpk: ViewingPublicKey, identifier: Identifier, }, - /// An owned private PDA: wallet holds the nsk/npk; `account_id` was derived via - /// [`AccountId::for_private_pda`]. - PrivatePdaOwned(AccountId), - /// A foreign private PDA: wallet knows the recipient's npk/vpk but not their nsk. - /// Uses a default (uninitialised) account. - PrivatePdaForeign { - account_id: AccountId, - npk: NullifierPublicKey, - vpk: ViewingPublicKey, - identifier: Identifier, - }, - /// A shared regular private account with externally-provided keys (e.g. from GMS). - /// Uses standard `AccountId = from((&npk, identifier))` with authorized/unauthorized private - /// paths. Works with `authenticated_transfer` and all existing programs out of the box. - PrivateShared { - nsk: NullifierSecretKey, - npk: NullifierPublicKey, - vpk: ViewingPublicKey, - identifier: Identifier, - }, - /// A shared private PDA with externally-provided keys (e.g. from GMS). - /// `account_id` was derived via [`AccountId::for_private_pda`]. - PrivatePdaShared { - account_id: AccountId, - nsk: NullifierSecretKey, - npk: NullifierPublicKey, - vpk: ViewingPublicKey, - identifier: Identifier, - }, } impl PrivacyPreservingAccount { @@ -61,17 +31,18 @@ impl PrivacyPreservingAccount { matches!( &self, Self::PrivateOwned(_) - | Self::PrivateForeign { .. } - | Self::PrivatePdaOwned(_) - | Self::PrivatePdaForeign { .. } - | Self::PrivateShared { .. } - | Self::PrivatePdaShared { .. } + | Self::PrivateForeign { + npk: _, + vpk: _, + identifier: _ + } ) } } pub struct PrivateAccountKeys { pub npk: NullifierPublicKey, + pub identifier: Identifier, pub ssk: SharedSecretKey, pub vpk: ViewingPublicKey, pub epk: EphemeralPublicKey, @@ -87,6 +58,7 @@ enum State { pub struct AccountManager { states: Vec, + visibility_mask: Vec, } impl AccountManager { @@ -94,10 +66,11 @@ impl AccountManager { wallet: &WalletCore, accounts: Vec, ) -> Result { - let mut states = Vec::with_capacity(accounts.len()); + let mut pre_states = Vec::with_capacity(accounts.len()); + let mut visibility_mask = Vec::with_capacity(accounts.len()); for account in accounts { - let state = match account { + let (state, mask) = match account { PrivacyPreservingAccount::Public(account_id) => { let acc = wallet .get_account_public(account_id) @@ -107,12 +80,13 @@ impl AccountManager { let sk = wallet.get_account_public_signing_key(account_id).cloned(); let account = AccountWithMetadata::new(acc.clone(), sk.is_some(), account_id); - State::Public { account, sk } + (State::Public { account, sk }, 0) } PrivacyPreservingAccount::PrivateOwned(account_id) => { - let pre = private_key_tree_acc_preparation(wallet, account_id, false).await?; + let pre = private_acc_preparation(wallet, account_id).await?; + let mask = if pre.pre_state.is_authorized { 1 } else { 2 }; - State::Private(pre) + (State::Private(pre), mask) } PrivacyPreservingAccount::PrivateForeign { npk, @@ -121,9 +95,6 @@ impl AccountManager { } => { let acc = nssa_core::account::Account::default(); let auth_acc = AccountWithMetadata::new(acc, false, (&npk, identifier)); - let eph_holder = EphemeralKeyHolder::new(&npk); - let ssk = eph_holder.calculate_shared_secret_sender(&vpk); - let epk = eph_holder.generate_ephemeral_public_key(); let pre = AccountPreparedData { nsk: None, npk, @@ -131,75 +102,20 @@ impl AccountManager { vpk, pre_state: auth_acc, proof: None, - ssk, - epk, - is_pda: false, }; - State::Private(pre) - } - PrivacyPreservingAccount::PrivatePdaOwned(account_id) => { - let pre = private_key_tree_acc_preparation(wallet, account_id, true).await?; - State::Private(pre) - } - PrivacyPreservingAccount::PrivatePdaForeign { - account_id, - npk, - vpk, - identifier, - } => { - let acc = nssa_core::account::Account::default(); - let auth_acc = AccountWithMetadata::new(acc, false, account_id); - let eph_holder = EphemeralKeyHolder::new(&npk); - let ssk = eph_holder.calculate_shared_secret_sender(&vpk); - let epk = eph_holder.generate_ephemeral_public_key(); - let pre = AccountPreparedData { - nsk: None, - npk, - identifier, - vpk, - pre_state: auth_acc, - proof: None, - ssk, - epk, - is_pda: true, - }; - State::Private(pre) - } - PrivacyPreservingAccount::PrivateShared { - nsk, - npk, - vpk, - identifier, - } => { - let account_id = nssa::AccountId::from((&npk, identifier)); - let pre = private_shared_acc_preparation( - wallet, account_id, nsk, npk, vpk, identifier, false, - ) - .await?; - - State::Private(pre) - } - PrivacyPreservingAccount::PrivatePdaShared { - account_id, - nsk, - npk, - vpk, - identifier, - } => { - let pre = private_shared_acc_preparation( - wallet, account_id, nsk, npk, vpk, identifier, true, - ) - .await?; - - State::Private(pre) + (State::Private(pre), 2) } }; - states.push(state); + pre_states.push(state); + visibility_mask.push(mask); } - Ok(Self { states }) + Ok(Self { + states: pre_states, + visibility_mask, + }) } pub fn pre_states(&self) -> Vec { @@ -212,6 +128,10 @@ impl AccountManager { .collect() } + pub fn visibility_mask(&self) -> &[u8] { + &self.visibility_mask + } + pub fn public_account_nonces(&self) -> Vec { self.states .iter() @@ -226,59 +146,38 @@ impl AccountManager { self.states .iter() .filter_map(|state| match state { - State::Private(pre) => Some(PrivateAccountKeys { - npk: pre.npk, - ssk: pre.ssk, - vpk: pre.vpk.clone(), - epk: pre.epk.clone(), - }), + State::Private(pre) => { + let eph_holder = EphemeralKeyHolder::new(&pre.npk); + + Some(PrivateAccountKeys { + npk: pre.npk, + identifier: pre.identifier, + ssk: eph_holder.calculate_shared_secret_sender(&pre.vpk), + vpk: pre.vpk.clone(), + epk: eph_holder.generate_ephemeral_public_key(), + }) + } State::Public { .. } => None, }) .collect() } - /// Build the per-account input vec for the privacy-preserving circuit. Each variant carries - /// exactly the fields the circuit's code path for that account needs, with the ephemeral - /// keys (`ssk`) drawn from the cached values that `private_account_keys` and the message - /// construction also use, so all three views agree on the same ephemeral key. - pub fn account_identities(&self) -> Vec { + pub fn private_account_auth(&self) -> Vec { self.states .iter() - .map(|state| match state { - State::Public { .. } => InputAccountIdentity::Public, - State::Private(pre) if pre.is_pda => match (pre.nsk, pre.proof.clone()) { - (Some(nsk), Some(membership_proof)) => InputAccountIdentity::PrivatePdaUpdate { - ssk: pre.ssk, - nsk, - membership_proof, - identifier: pre.identifier, - }, - _ => InputAccountIdentity::PrivatePdaInit { - npk: pre.npk, - ssk: pre.ssk, - identifier: pre.identifier, - }, - }, - State::Private(pre) => match (pre.nsk, pre.proof.clone()) { - (Some(nsk), Some(membership_proof)) => { - InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: pre.ssk, - nsk, - membership_proof, - identifier: pre.identifier, - } - } - (Some(nsk), None) => InputAccountIdentity::PrivateAuthorizedInit { - ssk: pre.ssk, - nsk, - identifier: pre.identifier, - }, - (None, _) => InputAccountIdentity::PrivateUnauthorized { - npk: pre.npk, - ssk: pre.ssk, - identifier: pre.identifier, - }, - }, + .filter_map(|state| match state { + State::Private(pre) => pre.nsk, + State::Public { .. } => None, + }) + .collect() + } + + pub fn private_account_membership_proofs(&self) -> Vec> { + self.states + .iter() + .filter_map(|state| match state { + State::Private(pre) => Some(pre.proof.clone()), + State::Public { .. } => None, }) .collect() } @@ -311,30 +210,20 @@ struct AccountPreparedData { vpk: ViewingPublicKey, pre_state: AccountWithMetadata, proof: Option, - /// Cached shared-secret key derived once at `AccountManager::new`. Reused for both the - /// circuit input variant (`account_identities()`) and the message ephemeral-key tuples - /// (`private_account_keys()`), so all consumers see the same key. The corresponding - /// `EphemeralKeyHolder` uses `OsRng` and would produce a different value on a second call. - ssk: SharedSecretKey, - /// Cached ephemeral public key, paired with `ssk`. - epk: EphemeralPublicKey, - /// True when this account is a private PDA (owned or foreign). Used by `account_identities()` - /// to select `PrivatePdaInit`/`PrivatePdaUpdate` rather than the standalone private variants. - is_pda: bool, } -async fn private_key_tree_acc_preparation( +async fn private_acc_preparation( wallet: &WalletCore, account_id: AccountId, - is_pda: bool, ) -> Result { - let (from_keys, from_acc, from_identifier) = wallet - .storage - .user_data - .get_private_account(account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?; + let Some((from_keys, from_acc, from_identifier)) = + wallet.storage.user_data.get_private_account(account_id) + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; let nsk = from_keys.private_key_holder.nullifier_secret_key; + let from_npk = from_keys.nullifier_public_key; let from_vpk = from_keys.viewing_public_key; @@ -346,11 +235,7 @@ async fn private_key_tree_acc_preparation( // TODO: Technically we could allow unauthorized owned accounts, but currently we don't have // support from that in the wallet. - let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, account_id); - - let eph_holder = EphemeralKeyHolder::new(&from_npk); - let ssk = eph_holder.calculate_shared_secret_sender(&from_vpk); - let epk = eph_holder.generate_ephemeral_public_key(); + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, (&from_npk, from_identifier)); Ok(AccountPreparedData { nsk: Some(nsk), @@ -359,64 +244,5 @@ async fn private_key_tree_acc_preparation( vpk: from_vpk, pre_state: sender_pre, proof, - ssk, - epk, - is_pda, }) } - -async fn private_shared_acc_preparation( - wallet: &WalletCore, - account_id: AccountId, - nsk: NullifierSecretKey, - npk: NullifierPublicKey, - vpk: ViewingPublicKey, - identifier: Identifier, - is_pda: bool, -) -> Result { - let acc = wallet - .storage - .user_data - .shared_private_account(&account_id) - .map(|e| e.account.clone()) - .unwrap_or_default(); - - let pre_state = AccountWithMetadata::new(acc, true, account_id); - - let proof = wallet - .check_private_account_initialized(account_id) - .await - .unwrap_or(None); - - let eph_holder = EphemeralKeyHolder::new(&npk); - let ssk = eph_holder.calculate_shared_secret_sender(&vpk); - let epk = eph_holder.generate_ephemeral_public_key(); - Ok(AccountPreparedData { - nsk: Some(nsk), - npk, - identifier, - vpk, - pre_state, - proof, - ssk, - epk, - is_pda, - }) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn private_shared_is_private() { - let acc = PrivacyPreservingAccount::PrivateShared { - nsk: [0; 32], - npk: NullifierPublicKey([1; 32]), - vpk: ViewingPublicKey::from_scalar([2; 32]), - identifier: 42, - }; - assert!(acc.is_private()); - assert!(!acc.is_public()); - } -} diff --git a/wallet/src/program_facades/ata.rs b/wallet/src/program_facades/ata.rs index fa868750..ac60fb63 100644 --- a/wallet/src/program_facades/ata.rs +++ b/wallet/src/program_facades/ata.rs @@ -188,9 +188,7 @@ impl Ata<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); let accounts = vec![ - self.0 - .resolve_private_account(owner_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(owner_id), PrivacyPreservingAccount::Public(definition_id), PrivacyPreservingAccount::Public(ata_id), ]; @@ -225,9 +223,7 @@ impl Ata<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); let accounts = vec![ - self.0 - .resolve_private_account(owner_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(owner_id), PrivacyPreservingAccount::Public(sender_ata_id), PrivacyPreservingAccount::Public(recipient_id), ]; @@ -261,9 +257,7 @@ impl Ata<'_> { Program::serialize_instruction(instruction).expect("Instruction should serialize"); let accounts = vec![ - self.0 - .resolve_private_account(owner_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(owner_id), PrivacyPreservingAccount::Public(holder_ata_id), PrivacyPreservingAccount::Public(definition_id), ]; diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index d4bde39f..d51f15ce 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -16,9 +16,7 @@ impl NativeTokenTransfer<'_> { self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - self.0 - .resolve_private_account(from) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::Public(to), ], instruction_data, diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index 501ead50..d317b31c 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -14,14 +14,9 @@ impl NativeTokenTransfer<'_> { ) -> Result<(HashType, SharedSecretKey), ExecutionFailureKind> { let instruction: u128 = 0; - let account = self - .0 - .resolve_private_account(from) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - self.0 .send_privacy_preserving_tx( - vec![account], + vec![PrivacyPreservingAccount::PrivateOwned(from)], Program::serialize_instruction(instruction).unwrap(), &Program::authenticated_transfer_program().into(), ) @@ -46,9 +41,7 @@ impl NativeTokenTransfer<'_> { self.0 .send_privacy_preserving_tx_with_pre_check( vec![ - self.0 - .resolve_private_account(from) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::PrivateForeign { npk: to_npk, vpk: to_vpk, @@ -76,18 +69,12 @@ impl NativeTokenTransfer<'_> { ) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> { let (instruction_data, program, tx_pre_check) = auth_transfer_preparation(balance_to_move); - let from_account = self - .0 - .resolve_private_account(from) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - let to_account = self - .0 - .resolve_private_account(to) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?; - self.0 .send_privacy_preserving_tx_with_pre_check( - vec![from_account, to_account], + vec![ + PrivacyPreservingAccount::PrivateOwned(from), + PrivacyPreservingAccount::PrivateOwned(to), + ], instruction_data, &program.into(), tx_pre_check, diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index 98dd0081..8f7ba2b5 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -18,9 +18,7 @@ impl NativeTokenTransfer<'_> { .send_privacy_preserving_tx_with_pre_check( vec![ PrivacyPreservingAccount::Public(from), - self.0 - .resolve_private_account(to) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(to), ], instruction_data, &program.into(), diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index 0575455e..97118ecd 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -56,9 +56,7 @@ impl Pinata<'_> { .send_privacy_preserving_tx( vec![ PrivacyPreservingAccount::Public(pinata_account_id), - self.0 - .resolve_private_account(winner_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(winner_account_id), ], nssa::program::Program::serialize_instruction(solution).unwrap(), &nssa::program::Program::pinata().into(), diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index da069bc2..d105a4de 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -74,9 +74,7 @@ impl Token<'_> { .send_privacy_preserving_tx( vec![ PrivacyPreservingAccount::Public(definition_account_id), - self.0 - .resolve_private_account(supply_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], instruction_data, &Program::token().into(), @@ -105,9 +103,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(supply_account_id), ], instruction_data, @@ -137,12 +133,8 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - self.0 - .resolve_private_account(supply_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], instruction_data, &Program::token().into(), @@ -235,12 +227,8 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(sender_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - self.0 - .resolve_private_account(recipient_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(sender_account_id), + PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], instruction_data, &Program::token().into(), @@ -271,9 +259,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(sender_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::PrivateForeign { npk: recipient_npk, vpk: recipient_vpk, @@ -307,9 +293,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(sender_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::Public(recipient_account_id), ], instruction_data, @@ -341,9 +325,7 @@ impl Token<'_> { .send_privacy_preserving_tx( vec![ PrivacyPreservingAccount::Public(sender_account_id), - self.0 - .resolve_private_account(recipient_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], instruction_data, &Program::token().into(), @@ -452,12 +434,8 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - self.0 - .resolve_private_account(holder_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], instruction_data, &Program::token().into(), @@ -486,9 +464,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(holder_account_id), ], instruction_data, @@ -520,9 +496,7 @@ impl Token<'_> { .send_privacy_preserving_tx( vec![ PrivacyPreservingAccount::Public(definition_account_id), - self.0 - .resolve_private_account(holder_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], instruction_data, &Program::token().into(), @@ -616,12 +590,8 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, - self.0 - .resolve_private_account(holder_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), + PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], instruction_data, &Program::token().into(), @@ -652,9 +622,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateForeign { npk: holder_npk, vpk: holder_vpk, @@ -688,9 +656,7 @@ impl Token<'_> { self.0 .send_privacy_preserving_tx( vec![ - self.0 - .resolve_private_account(definition_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(holder_account_id), ], instruction_data, @@ -722,9 +688,7 @@ impl Token<'_> { .send_privacy_preserving_tx( vec![ PrivacyPreservingAccount::Public(definition_account_id), - self.0 - .resolve_private_account(holder_account_id) - .ok_or(ExecutionFailureKind::KeyNotFoundError)?, + PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], instruction_data, &Program::token().into(), diff --git a/wallet/src/transaction_utils.rs b/wallet/src/transaction_utils.rs new file mode 100644 index 00000000..f2def802 --- /dev/null +++ b/wallet/src/transaction_utils.rs @@ -0,0 +1,594 @@ +use common::{HashType, transaction::NSSATransaction}; +use sequencer_service_rpc::RpcClient as _; +use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder; +use nssa::{ + Account, AccountId, PrivacyPreservingTransaction, + privacy_preserving_transaction::{circuit, message::Message, witness_set::WitnessSet}, + program::Program, +}; +use nssa_core::{ + Commitment, MembershipProof, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, + account::AccountWithMetadata, encryption::ViewingPublicKey, program::InstructionData, +}; + +use crate::{ExecutionFailureKind, WalletCore, helperfunctions::produce_random_nonces}; + +pub(crate) struct AccountPreparedData { + pub nsk: Option, + pub npk: NullifierPublicKey, + pub vpk: ViewingPublicKey, + pub auth_acc: AccountWithMetadata, + pub proof: Option, +} + +impl WalletCore { + pub(crate) async fn private_acc_preparation( + &self, + account_id: AccountId, + is_authorized: bool, + needs_proof: bool, + ) -> Result { + let Some((from_keys, from_acc)) = self + .storage + .user_data + .get_private_account(&account_id) + .cloned() + else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let mut nsk = None; + let mut proof = None; + + let from_npk = from_keys.nullifier_public_key; + let from_vpk = from_keys.viewing_public_key; + + let sender_commitment = Commitment::new(&from_npk, &from_acc); + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), is_authorized, &from_npk); + + if is_authorized { + nsk = Some(from_keys.private_key_holder.nullifier_secret_key); + } + + if needs_proof { + proof = Some( + self.sequencer_client + .get_proof_for_commitment(sender_commitment) + .await + .unwrap(), + ); + } + + Ok(AccountPreparedData { + nsk, + npk: from_npk, + vpk: from_vpk, + auth_acc: sender_pre, + proof, + }) + } + + pub(crate) async fn private_tx_two_accs_all_init( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + to_proof: MembershipProof, + ) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + vpk: from_vpk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let AccountPreparedData { + nsk: to_nsk, + npk: to_npk, + vpk: to_vpk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, true, false).await?; + + tx_pre_check(&sender_pre.account, &recipient_pre.account)?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_vpk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 1], + &produce_random_nonces(2), + &[ + (from_npk, shared_secret_from.clone()), + (to_npk, shared_secret_to.clone()), + ], + &[ + (from_nsk.unwrap(), from_proof.unwrap()), + (to_nsk.unwrap(), to_proof), + ], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk, + from_vpk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk, + to_vpk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn private_tx_two_accs_receiver_uninit( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + vpk: from_vpk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let AccountPreparedData { + nsk: _, + npk: to_npk, + vpk: to_vpk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, false, false).await?; + + tx_pre_check(&sender_pre.account, &recipient_pre.account)?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_vpk); + + let eph_holder_to = EphemeralKeyHolder::new(&to_npk); + let shared_secret_to = eph_holder_to.calculate_shared_secret_sender(&to_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk, shared_secret_from.clone()), + (to_npk, shared_secret_to.clone()), + ], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk, + from_vpk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + ), + ( + to_npk, + to_vpk.clone(), + eph_holder_to.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn private_tx_two_accs_receiver_outer( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_vpk: ViewingPublicKey, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(HashType, [SharedSecretKey; 2]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + vpk: from_vpk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let to_acc = nssa_core::account::Account::default(); + + tx_pre_check(&sender_pre.account, &to_acc)?; + + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + + let shared_secret_from = eph_holder.calculate_shared_secret_sender(&from_vpk); + let shared_secret_to = eph_holder.calculate_shared_secret_sender(&to_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 2], + &produce_random_nonces(2), + &[ + (from_npk, shared_secret_from.clone()), + (to_npk, shared_secret_to.clone()), + ], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![ + ( + from_npk, + from_vpk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ( + to_npk, + to_vpk.clone(), + eph_holder.generate_ephemeral_public_key(), + ), + ], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret_from, shared_secret_to], + )) + } + + pub(crate) async fn deshielded_tx_two_accs( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(HashType, [nssa_core::SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: from_nsk, + npk: from_npk, + vpk: from_vpk, + auth_acc: sender_pre, + proof: from_proof, + } = self.private_acc_preparation(from, true, true).await?; + + let Ok(to_acc) = self.get_account_public(to).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + tx_pre_check(&sender_pre.account, &to_acc)?; + + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, to); + + let eph_holder = EphemeralKeyHolder::new(&from_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&from_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[1, 0], + &produce_random_nonces(1), + &[(from_npk, shared_secret.clone())], + &[(from_nsk.unwrap(), from_proof.unwrap())], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![to], + vec![], + vec![( + from_npk, + from_vpk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_all_init( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + to_proof: MembershipProof, + ) -> Result<(HashType, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let AccountPreparedData { + nsk: to_nsk, + npk: to_npk, + vpk: to_vpk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, true, false).await?; + + tx_pre_check(&from_acc, &recipient_pre.account)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 1], + &produce_random_nonces(1), + &[(to_npk, shared_secret.clone())], + &[(to_nsk.unwrap(), to_proof)], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk, + to_vpk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_receiver_uninit( + &self, + from: AccountId, + to: AccountId, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result<(HashType, [SharedSecretKey; 1]), ExecutionFailureKind> { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let AccountPreparedData { + nsk: _, + npk: to_npk, + vpk: to_vpk, + auth_acc: recipient_pre, + proof: _, + } = self.private_acc_preparation(to, false, false).await?; + + tx_pre_check(&from_acc, &recipient_pre.account)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 2], + &produce_random_nonces(1), + &[(to_npk, shared_secret.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk, + to_vpk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret], + )) + } + + pub(crate) async fn shielded_two_accs_receiver_outer( + &self, + from: AccountId, + to_npk: NullifierPublicKey, + to_vpk: ViewingPublicKey, + instruction_data: InstructionData, + tx_pre_check: impl FnOnce(&Account, &Account) -> Result<(), ExecutionFailureKind>, + program: Program, + ) -> Result { + let Ok(from_acc) = self.get_account_public(from).await else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let to_acc = Account::default(); + + tx_pre_check(&from_acc, &to_acc)?; + + let sender_pre = AccountWithMetadata::new(from_acc.clone(), true, from); + let recipient_pre = AccountWithMetadata::new(to_acc.clone(), false, &to_npk); + + let eph_holder = EphemeralKeyHolder::new(&to_npk); + let shared_secret = eph_holder.calculate_shared_secret_sender(&to_vpk); + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre, recipient_pre], + &instruction_data, + &[0, 2], + &produce_random_nonces(1), + &[(to_npk, shared_secret.clone())], + &[], + &program.into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![from], + vec![from_acc.nonce], + vec![( + to_npk, + to_vpk.clone(), + eph_holder.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let signing_key = self.storage.user_data.get_pub_account_signing_key(&from); + + let Some(signing_key) = signing_key else { + return Err(ExecutionFailureKind::KeyNotFoundError); + }; + + let witness_set = WitnessSet::for_message(&message, proof, &[signing_key]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?) + } + + pub async fn register_account_under_authenticated_transfers_programs_private( + &self, + from: AccountId, + ) -> Result<(HashType, [SharedSecretKey; 1]), ExecutionFailureKind> { + let AccountPreparedData { + nsk: _, + npk: from_npk, + vpk: from_vpk, + auth_acc: sender_pre, + proof: _, + } = self.private_acc_preparation(from, false, false).await?; + + let eph_holder_from = EphemeralKeyHolder::new(&from_npk); + let shared_secret_from = eph_holder_from.calculate_shared_secret_sender(&from_vpk); + + let instruction: u128 = 0; + + let (output, proof) = circuit::execute_and_prove( + &[sender_pre], + &Program::serialize_instruction(instruction).unwrap(), + &[2], + &produce_random_nonces(1), + &[(from_npk, shared_secret_from.clone())], + &[], + &Program::authenticated_transfer_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![], + vec![], + vec![( + from_npk, + from_vpk.clone(), + eph_holder_from.generate_ephemeral_public_key(), + )], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + + Ok(( + self.sequencer_client.send_transaction(NSSATransaction::PrivacyPreserving(tx).into()).await?, + [shared_secret_from], + )) + } +}