diff --git a/Cargo.lock b/Cargo.lock index 7c3a1403..84c99778 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,47 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common 0.1.6", + "generic-array 0.14.9", +] + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher 0.4.4", + "cpufeatures 0.2.17", +] + +[[package]] +name = "aes-gcm" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "831010a0f742e1209b3bcea8fab6a8e149051ba6099432c8cb2cc117dec3ead1" +dependencies = [ + "aead", + "aes", + "cipher 0.4.4", + "ctr", + "ghash", + "subtle", +] + [[package]] name = "ahash" version = "0.8.12" @@ -54,7 +95,7 @@ dependencies = [ "rustc-hash", "secp256k1", "serde", - "sha3", + "sha3 0.11.0", ] [[package]] @@ -116,6 +157,15 @@ version = "1.0.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" +[[package]] +name = "archery" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e0a5f99dfebb87bb342d0f53bb92c81842e100bbb915223e38349580e5441d" +dependencies = [ + "triomphe", +] + [[package]] name = "ark-bn254" version = "0.5.0" @@ -364,7 +414,7 @@ dependencies = [ "ark-ff 0.5.0", "ark-std 0.5.0", "tracing", - "tracing-subscriber", + "tracing-subscriber 0.2.25", ] [[package]] @@ -460,12 +510,160 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7d902e3d592a523def97af8f317b08ce16b7ab854c1985a0c671e6f15cebc236" +[[package]] +name = "arrayref" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76a2e8124351fda1ef8aaaa3bbd7ebbcb486bbcd4225aca0aa0d84bb2db8fecb" + [[package]] name = "arrayvec" version = "0.7.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f02882884d3e1bc524fb12c79f107f6ad0e1cfd498c536ffb494301740995dfe" +[[package]] +name = "asn1-rs" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7f43a50ac4fdca5df8e885c21b835997f0a1cdee65494a6847694a98652d9d8" +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.118", + "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.118", +] + +[[package]] +name = "asn1_der" +version = "0.7.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4858a9d740c5007a9069007c3b4e91152d0506f13c1b31dd49051fd537656156" + +[[package]] +name = "astro-float" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96034cc871c05bb65ad7fb77e6a8bebf45d8b055ed0311769e2f83a1d373c1ec" +dependencies = [ + "astro-float-macro", + "astro-float-num", +] + +[[package]] +name = "astro-float-macro" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05cfe0f6df5a74fb25b9e713470ad77e091f35f798730673c8772f26ed438963" +dependencies = [ + "astro-float-num", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "astro-float-num" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86887daca11d02e0b04f37a9cb81888aae881397fb48ff66494e356aea97554a" +dependencies = [ + "itertools 0.10.5", + "lazy_static", +] + +[[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-recursion" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b43422f69d8ff38f95f1b2bb76517c91589a924d1559a0e935d7c8ce0274c11" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "async-trait" +version = "0.1.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9035ad2d096bed7955a320ee7e2230574d28fd3c3a0f186cbea1ff3c7eed5dbb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[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-methods" version = "0.1.0" @@ -500,6 +698,37 @@ 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", + "http 1.4.2", + "log", + "url", +] + +[[package]] +name = "authenticated_transfer_core" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" +dependencies = [ + "serde", +] + [[package]] name = "auto_impl" version = "1.3.0" @@ -517,12 +746,97 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" +[[package]] +name = "axum" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edca88bc138befd0323b20752846e6587272d3b03b0343c8ea28a6f819e6e71f" +dependencies = [ + "async-trait", + "axum-core", + "bytes", + "futures-util", + "http 1.4.2", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "itoa", + "matchit", + "memchr", + "mime", + "percent-encoding", + "pin-project-lite", + "rustversion", + "serde", + "serde_json", + "serde_path_to_error", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tower", + "tower-layer", + "tower-service", +] + +[[package]] +name = "axum-core" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09f2bd6146b97ae3359fa0cc6d6b376d9539582c7b4220f041a33ec24c226199" +dependencies = [ + "async-trait", + "bytes", + "futures-util", + "http 1.4.2", + "http-body", + "http-body-util", + "mime", + "pin-project-lite", + "rustversion", + "sync_wrapper", + "tower-layer", + "tower-service", +] + +[[package]] +name = "backon" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cffb0e931875b666fc4fcb20fee52e9bbd1ef836fd9e9e04ec21555f9f85f7ef" +dependencies = [ + "fastrand", + "tokio", +] + +[[package]] +name = "base-x" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cbbc9d0964165b47557570cce6c952866c2678457aca742aafc9fb771d30270" + [[package]] name = "base16ct" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base256emoji" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e9430d9a245a77c92176e649af6e275f20839a48389859d1661e9a128d077c" +dependencies = [ + "const-str", + "match-lookup", +] + +[[package]] +name = "base45" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240e56f4d3c453c36faacb695c535a4d5f8c7d23dac175014f32eb0a71012a03" + [[package]] name = "base58" version = "0.2.0" @@ -550,6 +864,17 @@ dependencies = [ "serde", ] +[[package]] +name = "bip39" +version = "2.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90dbd31c98227229239363921e60fcf5e558e43ec69094d46fc4996f08d1d5bc" +dependencies = [ + "bitcoin_hashes", + "serde", + "unicode-normalization", +] + [[package]] name = "bit-set" version = "0.8.0" @@ -647,7 +972,7 @@ version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ - "generic-array", + "generic-array 0.14.9", ] [[package]] @@ -696,6 +1021,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "bridge_core" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" +dependencies = [ + "lee_core", + "serde", +] + [[package]] name = "bs58" version = "0.5.1" @@ -831,10 +1165,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d524456ba66e72eb8b115ff89e01e497f8e6d11d78b70b1aa13c0fbd97540a81" dependencies = [ "cfg-if", - "cipher", + "cipher 0.5.2", "cpufeatures 0.3.0", ] +[[package]] +name = "chkstk_stub" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "047f6ab2f3b9bcaf23b593d1580898e4244d27eadf1a1fae99212ee5735d3d1c" +dependencies = [ + "cc", +] + [[package]] name = "chrono" version = "0.4.45" @@ -847,6 +1190,16 @@ dependencies = [ "windows-link", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common 0.1.6", + "inout 0.1.4", +] + [[package]] name = "cipher" version = "0.5.2" @@ -855,7 +1208,7 @@ checksum = "e8cf2a2c93cd704877c0858356ed03480ff301ee950b43f1cbe4573b088bfa6c" dependencies = [ "block-buffer 0.12.1", "crypto-common 0.2.2", - "inout", + "inout 0.2.2", ] [[package]] @@ -883,17 +1236,49 @@ dependencies = [ ] [[package]] -name = "const-hex" -version = "1.19.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +name = "common" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" dependencies = [ - "cfg-if", - "cpufeatures 0.2.17", - "proptest", - "serde_core", -] - + "anyhow", + "authenticated_transfer_core", + "base64", + "borsh", + "clock_core", + "hex", + "lee", + "lee_core", + "log", + "logos-blockchain-common-http-client", + "programs", + "serde", + "serde_with", + "sha2", + "system_accounts", + "thiserror 2.0.18", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "const-hex" +version = "1.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33e2a781ebdf4467d1428dc4593067825fb646f6871475098d8577421af73558" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "proptest", + "serde_core", +] + [[package]] name = "const-oid" version = "0.9.6" @@ -906,6 +1291,12 @@ version = "0.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a6ef517f0926dd24a1582492c791b6a4818a4d94e789a334894aa15b0d12f55c" +[[package]] +name = "const-str" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f421161cb492475f1661ddc9815a745a1c894592070661180fdec3d4872e9c3" + [[package]] name = "const_format" version = "0.2.36" @@ -927,6 +1318,15 @@ dependencies = [ "unicode-xid", ] +[[package]] +name = "convert_case" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baaaa0ecca5b51987b9423ccdc971514dd8b0bb7b4060b983d3664dad3f1f89f" +dependencies = [ + "unicode-segmentation", +] + [[package]] name = "convert_case" version = "0.10.0" @@ -981,6 +1381,39 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d85363c37faeca707aef026efa9f3b34d077bce547e48f770770625c6013679e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d6914041f254d6e9176c01941b21115dcfb7089e55135a35411081bd106ef3f" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + [[package]] name = "crunchy" version = "0.2.4" @@ -993,7 +1426,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" dependencies = [ - "generic-array", + "generic-array 0.14.9", "rand_core 0.6.4", "subtle", "zeroize", @@ -1005,7 +1438,8 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ - "generic-array", + "generic-array 0.14.9", + "rand_core 0.6.4", "typenum", ] @@ -1020,6 +1454,15 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher 0.4.4", +] + [[package]] name = "ctutils" version = "0.4.2" @@ -1029,6 +1472,34 @@ dependencies = [ "cmov", ] +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "curve25519-dalek-derive", + "digest 0.10.7", + "fiat-crypto", + "rustc_version 0.4.1", + "serde", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "darling" version = "0.20.11" @@ -1098,6 +1569,32 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "data-encoding" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4ae5f15dda3c708c0ade84bfee31ccab44a3da4f88015ed22f63732abe300c8" + +[[package]] +name = "data-encoding-macro" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3259c913752a86488b501ed8680446a5ed2d5aeac6e596cb23ba3800768ea32c" +dependencies = [ + "data-encoding", + "data-encoding-macro-internal", +] + +[[package]] +name = "data-encoding-macro-internal" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccc2776f0c61eca1ca32528f85548abd1a4be8fb53d1b21c013e4f18da1e7090" +dependencies = [ + "data-encoding", + "syn 2.0.118", +] + [[package]] name = "der" version = "0.7.10" @@ -1119,6 +1616,20 @@ 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", + "num-traits", + "rusticata-macros", +] + [[package]] name = "deranged" version = "0.5.8" @@ -1185,7 +1696,7 @@ version = "2.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" dependencies = [ - "convert_case", + "convert_case 0.10.0", "proc-macro2", "quote", "rustc_version 0.4.1", @@ -1199,7 +1710,7 @@ version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" dependencies = [ - "generic-array", + "generic-array 0.14.9", ] [[package]] @@ -1256,6 +1767,17 @@ dependencies = [ "syn 2.0.118", ] +[[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-generate" version = "0.1.3" @@ -1268,6 +1790,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" +[[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" @@ -1300,6 +1828,32 @@ dependencies = [ "spki 0.7.3", ] +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8 0.10.2", + "serde", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70e796c081cee67dc755e1a36a0a172b897fab85fc3f6bc48307991f64e4eca9" +dependencies = [ + "curve25519-dalek", + "ed25519", + "rand_core 0.6.4", + "serde", + "sha2", + "subtle", + "zeroize", +] + [[package]] name = "educe" version = "0.6.0" @@ -1334,7 +1888,7 @@ dependencies = [ "crypto-bigint", "digest 0.10.7", "ff", - "generic-array", + "generic-array 0.14.9", "group", "pem-rfc7468", "pkcs8 0.10.2", @@ -1366,6 +1920,18 @@ 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.118", +] + [[package]] name = "enum-ordinalize" version = "4.4.1" @@ -1402,6 +1968,27 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "event-listener" +version = "5.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13b66accf52311f30a0db42147dadea9850cb48cd070028831ae5f5d4b856ab" +dependencies = [ + "concurrent-queue", + "parking", + "pin-project-lite", +] + +[[package]] +name = "event-listener-strategy" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8be9f3dfaaffdae2972880079a491a1a8bb7cbed0b8dd7a347f668b4150a3b93" +dependencies = [ + "event-listener", + "pin-project-lite", +] + [[package]] name = "eyre" version = "0.6.12" @@ -1440,6 +2027,26 @@ dependencies = [ "bytes", ] +[[package]] +name = "faucet_core" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" +dependencies = [ + "lee_core", + "serde", +] + +[[package]] +name = "fd-lock" +version = "4.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ce92ff622d6dadf7349484f42c93271a0d49b7cc4d466a936405bacbe10aa78" +dependencies = [ + "cfg-if", + "rustix", + "windows-sys 0.52.0", +] + [[package]] name = "ff" version = "0.13.1" @@ -1450,6 +2057,22 @@ dependencies = [ "subtle", ] +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" @@ -1468,6 +2091,16 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "fnv" version = "1.0.7" @@ -1528,6 +2161,31 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" +[[package]] +name = "futures" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b147ee9d1f6d097cef9ce628cd2ee62288d963e16fb287bd9286455b241382d" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "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" @@ -1544,12 +2202,33 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" +[[package]] +name = "futures-executor" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf29c38818342a3b26b5b923639e7b1f4a61fc5e76102d4b1981c6dc7a7579d" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + [[package]] name = "futures-io" 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" @@ -1561,6 +2240,17 @@ dependencies = [ "syn 2.0.118", ] +[[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" @@ -1573,12 +2263,19 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" +[[package]] +name = "futures-timer" +version = "3.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af43fadb8a98512d547e37b4e92e0ced13e205c061b87b4623eff01d918d6968" + [[package]] name = "futures-util" version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" dependencies = [ + "futures-channel", "futures-core", "futures-io", "futures-macro", @@ -1601,15 +2298,26 @@ dependencies = [ ] [[package]] -name = "getrandom" -version = "0.2.17" +name = "generic-array" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +checksum = "c2e55f16dcf0e9c00efbe2e655ffe45fc98e7066b52bc92f8a79e64060a79351" dependencies = [ - "cfg-if", - "js-sys", - "libc", - "wasi", + "rustversion", + "serde_core", + "typenum", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", "wasm-bindgen", ] @@ -1639,6 +2347,16 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "ghash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0d8a4362ccb29cb0b265253fb0a2728f592895ee6854fd9bc13f2ffda266ff1" +dependencies = [ + "opaque-debug", + "polyval", +] + [[package]] name = "group" version = "0.13.0" @@ -1650,12 +2368,40 @@ dependencies = [ "subtle", ] +[[package]] +name = "h2" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6cb093c84e8bd9b188d4c4a8cb6579fc016968d14c99882163cd3ff402a4f155" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http 1.4.2", + "indexmap 2.14.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -1663,6 +2409,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", + "equivalent", "foldhash 0.1.5", ] @@ -1675,6 +2422,15 @@ dependencies = [ "foldhash 0.2.0", ] +[[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" @@ -1690,6 +2446,12 @@ 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" @@ -1720,6 +2482,68 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[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.4", + "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.4", + "resolv-conf", + "smallvec", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1729,6 +2553,34 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "hmac-sha512" +version = "1.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "019ece39bbefc17f13f677a690328cb978dbf6790e141a3c24e66372cb38588b" + +[[package]] +name = "hostname" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867" +dependencies = [ + "libc", + "match_cfg", + "winapi", +] + +[[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.2" @@ -1746,7 +2598,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", - "http", + "http 1.4.2", ] [[package]] @@ -1757,7 +2609,7 @@ checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" dependencies = [ "bytes", "futures-core", - "http", + "http 1.4.2", "http-body", "pin-project-lite", ] @@ -1768,6 +2620,18 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + [[package]] name = "hybrid-array" version = "0.4.13" @@ -1788,9 +2652,11 @@ dependencies = [ "bytes", "futures-channel", "futures-core", - "http", + "h2", + "http 1.4.2", "http-body", "httparse", + "httpdate", "itoa", "pin-project-lite", "smallvec", @@ -1804,7 +2670,7 @@ version = "0.27.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" dependencies = [ - "http", + "http 1.4.2", "hyper", "hyper-util", "rustls", @@ -1814,6 +2680,19 @@ dependencies = [ "webpki-roots", ] +[[package]] +name = "hyper-timeout" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b90d566bffbce6a75bd8b09a05aa8c2cb1fabb6cb348f8840c9e4c90a0d83b0" +dependencies = [ + "hyper", + "hyper-util", + "pin-project-lite", + "tokio", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.20" @@ -1824,14 +2703,14 @@ dependencies = [ "bytes", "futures-channel", "futures-util", - "http", + "http 1.4.2", "http-body", "hyper", "ipnet", "libc", "percent-encoding", "pin-project-lite", - "socket2", + "socket2 0.6.4", "tokio", "tower-service", "tracing", @@ -1979,6 +2858,81 @@ 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", + "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.2", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.8.6", + "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.2", + "http-body-util", + "hyper", + "hyper-util", + "log", + "rand 0.9.4", + "tokio", + "url", + "xmltree", +] + [[package]] name = "impl-codec" version = "0.6.0" @@ -2034,6 +2988,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "inout" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01" +dependencies = [ + "generic-array 0.14.9", +] + [[package]] name = "inout" version = "0.2.2" @@ -2052,6 +3015,7 @@ dependencies = [ "ata-methods", "ata_core", "clock_core", + "key_protocol", "lee", "lee_core", "stablecoin-methods", @@ -2062,6 +3026,19 @@ dependencies = [ "twap_oracle_core", ] +[[package]] +name = "ipconfig" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d40460c0ce33d6ce4b0630ad68ff63d6661961c48b6dba35e5a4d81cfb48222" +dependencies = [ + "socket2 0.6.4", + "widestring", + "windows-registry", + "windows-result", + "windows-sys 0.61.2", +] + [[package]] name = "ipnet" version = "2.12.0" @@ -2101,6 +3078,31 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" +[[package]] +name = "jf-crhf" +version = "0.2.0" +source = "git+https://github.com/EspressoSystems/jellyfish?tag=jf-crhf-v0.2.0#f1538793f7f0e391495cb17bbb0c8703ec5f689d" +dependencies = [ + "ark-serialize 0.5.0", + "ark-std 0.5.0", +] + +[[package]] +name = "jf-poseidon2" +version = "0.2.0" +source = "git+https://github.com/EspressoSystems/jellyfish.git?rev=8d80230358e900f8d63765a937f63f4978ca1daa#8d80230358e900f8d63765a937f63f4978ca1daa" +dependencies = [ + "ark-bn254", + "ark-ff 0.5.0", + "ark-std 0.5.0", + "displaydoc", + "hex", + "jf-crhf", + "lazy_static", + "spongefish", + "zeroize", +] + [[package]] name = "js-sys" version = "0.3.103" @@ -2166,6 +3168,28 @@ dependencies = [ "rand_core 0.10.1", ] +[[package]] +name = "key_protocol" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" +dependencies = [ + "aes-gcm", + "anyhow", + "bip39", + "common", + "hex", + "hmac-sha512", + "itertools 0.14.0", + "k256", + "lee", + "lee_core", + "ml-kem", + "rand 0.8.6", + "serde", + "sha2", + "thiserror 2.0.18", +] + [[package]] name = "konst" version = "0.2.20" @@ -2264,32 +3288,1140 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6d2cec3eae94f9f509c767b45932f1ada8350c4bdb85af2fcab4a3c14807981" [[package]] -name = "libredox" -version = "0.1.18" +name = "libp2p" +version = "0.55.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +checksum = "b72dc443ddd0254cb49a794ed6b6728400ee446a0f7ab4a07d0209ee98de20e9" dependencies = [ - "libc", + "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 = "linux-raw-sys" -version = "0.12.1" +name = "libp2p-allow-block-list" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" +checksum = "38944b7cb981cc93f2f0fb411ff82d0e983bd226fbcc8d559639a3a73236568b" +dependencies = [ + "libp2p-core", + "libp2p-identity", + "libp2p-swarm", +] [[package]] -name = "litemap" -version = "0.8.2" +name = "libp2p-autonat" +version = "0.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" +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.6", + "rand_core 0.6.4", + "thiserror 2.0.18", + "tracing", + "web-time", +] [[package]] -name = "log" -version = "0.4.33" +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.6", + "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", + "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.6", + "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.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9525f3831544f7ae497bde79adf114ef127b0fbbb97edbbf692a80408636421c" +dependencies = [ + "asn1_der", + "bs58", + "ed25519-dalek", + "hkdf", + "k256", + "multihash", + "prost 0.14.4", + "rand 0.8.6", + "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.6", + "serde", + "sha2", + "smallvec", + "thiserror 2.0.18", + "tracing", + "uint 0.10.0", + "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.6", + "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.6", + "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.6", + "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.6", + "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.6", + "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.118", +] + +[[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]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "libc", +] + +[[package]] +name = "light-poseidon" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47a1ccadd0bb5a32c196da536fd72c59183de24a055f6bf0513bf845fefab862" +dependencies = [ + "ark-bn254", + "ark-ff 0.5.0", + "num-bigint", + "thiserror 1.0.69", +] + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "litemap" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92daf443525c4cce67b150400bc2316076100ce0b3686209eb8cf3c31612e6f0" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.33" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" +[[package]] +name = "logos-blockchain-blend-crypto" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "blake2", + "logos-blockchain-groth16", + "logos-blockchain-poq", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "rs-merkle-tree", + "thiserror 2.0.18", +] + +[[package]] +name = "logos-blockchain-blend-message" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "blake2", + "derivative", + "hex", + "itertools 0.14.0", + "logos-blockchain-blend-crypto", + "logos-blockchain-blend-proofs", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-log-targets", + "logos-blockchain-utils", + "serde", + "serde_with", + "thiserror 2.0.18", + "tracing", + "zeroize", +] + +[[package]] +name = "logos-blockchain-blend-proofs" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "ed25519-dalek", + "generic-array 1.4.3", + "hex", + "logos-blockchain-blend-crypto", + "logos-blockchain-groth16", + "logos-blockchain-pol", + "logos-blockchain-poq", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "num-bigint", + "serde", + "thiserror 2.0.18", + "zeroize", +] + +[[package]] +name = "logos-blockchain-chain-broadcast-service" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "derivative", + "logos-blockchain-core", + "overwatch", + "serde", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-chain-service" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "bytes", + "derivative", + "futures", + "logos-blockchain-chain-broadcast-service", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-cryptarchia-sync", + "logos-blockchain-groth16", + "logos-blockchain-ledger", + "logos-blockchain-network-service", + "logos-blockchain-services-utils", + "logos-blockchain-storage-service", + "logos-blockchain-time-service", + "logos-blockchain-tracing", + "logos-blockchain-utils", + "overwatch", + "serde", + "serde_with", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "logos-blockchain-circuits-build" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "dirs", + "fd-lock", + "flate2", + "tar", + "ureq", +] + +[[package]] +name = "logos-blockchain-circuits-common" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-poc-sys" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-pol-sys" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-poq-sys" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-prover" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "rust-rapidsnark", +] + +[[package]] +name = "logos-blockchain-circuits-signature-sys" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "logos-blockchain-circuits-build", + "logos-blockchain-circuits-common", + "logos-blockchain-circuits-types", +] + +[[package]] +name = "logos-blockchain-circuits-types" +version = "0.5.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-circuits.git?tag=v0.5.3#127626881faa975aa8e9868422cf6bbb08fcb512" +dependencies = [ + "bytes", + "libc", +] + +[[package]] +name = "logos-blockchain-common-http-client" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "futures", + "hex", + "log", + "logos-blockchain-chain-broadcast-service", + "logos-blockchain-chain-service", + "logos-blockchain-core", + "logos-blockchain-groth16", + "logos-blockchain-http-api-common", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-log-targets", + "reqwest", + "serde", + "serde_json", + "thiserror 2.0.18", + "tokio-util", + "url", +] + +[[package]] +name = "logos-blockchain-core" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "ark-ff 0.5.0", + "bincode", + "blake2", + "bytes", + "const-hex", + "futures", + "hex", + "logos-blockchain-blend-proofs", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-log-targets", + "logos-blockchain-mmr", + "logos-blockchain-poc", + "logos-blockchain-pol", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "logos-blockchain-utxotree", + "multiaddr", + "nom 8.0.0", + "num-bigint", + "rpds", + "serde", + "strum", + "thiserror 2.0.18", + "time", + "tracing", +] + +[[package]] +name = "logos-blockchain-cryptarchia-engine" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "logos-blockchain-pol", + "logos-blockchain-utils", + "serde", + "serde_with", + "thiserror 2.0.18", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-cryptarchia-sync" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "bytes", + "futures", + "libp2p", + "libp2p-stream", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "rand 0.8.6", + "serde", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-groth16" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "ark-bn254", + "ark-ec", + "ark-ff 0.5.0", + "ark-groth16", + "ark-serialize 0.5.0", + "generic-array 1.4.3", + "hex", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "logos-blockchain-http-api-common" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "axum", + "logos-blockchain-core", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-log-targets", + "logos-blockchain-tracing", + "serde", + "serde_json", + "serde_urlencoded", + "serde_with", + "time", + "tracing", + "url", + "utoipa", + "validator", +] + +[[package]] +name = "logos-blockchain-key-management-system-keys" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "bytes", + "ed25519-dalek", + "generic-array 1.4.3", + "hex", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-macros", + "logos-blockchain-log-targets", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "logos-blockchain-zksign", + "num-bigint", + "rand_core 0.6.4", + "serde", + "subtle", + "thiserror 2.0.18", + "tokio", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "logos-blockchain-key-management-system-macros" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "logos-blockchain-ledger" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "derivative", + "logos-blockchain-blend-crypto", + "logos-blockchain-blend-message", + "logos-blockchain-blend-proofs", + "logos-blockchain-core", + "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", + "num-bigint", + "rand 0.8.6", + "rpds", + "serde", + "serde_arrays", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "logos-blockchain-libp2p" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "backon", + "blake2", + "either", + "futures", + "hex", + "igd-next 0.16.2", + "libp2p", + "logos-blockchain-cryptarchia-sync", + "logos-blockchain-log-targets", + "logos-blockchain-utils", + "multiaddr", + "natpmp", + "netdev", + "num_enum", + "rand 0.8.6", + "serde", + "serde_with", + "thiserror 2.0.18", + "tokio", + "tracing", + "zerocopy", +] + +[[package]] +name = "logos-blockchain-log-targets" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "logos-blockchain-log-targets-macros", +] + +[[package]] +name = "logos-blockchain-log-targets-macros" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "logos-blockchain-mmr" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "ark-ff 0.5.0", + "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=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-sync", + "logos-blockchain-libp2p", + "logos-blockchain-log-targets", + "logos-blockchain-tracing", + "overwatch", + "rand 0.8.6", + "rand_chacha 0.3.1", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-poc" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "logos-blockchain-circuits-poc-sys", + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "logos-blockchain-log-targets", + "logos-blockchain-proofs-error", + "num-bigint", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "logos-blockchain-pol" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "astro-float", + "logos-blockchain-circuits-pol-sys", + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "logos-blockchain-log-targets", + "logos-blockchain-proofs-error", + "logos-blockchain-utils", + "num-bigint", + "num-traits", + "serde", + "serde_json", + "tracing", +] + +[[package]] +name = "logos-blockchain-poq" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "logos-blockchain-circuits-poq-sys", + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "logos-blockchain-log-targets", + "logos-blockchain-pol", + "logos-blockchain-proofs-error", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "logos-blockchain-poseidon2" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "ark-bn254", + "ark-ff 0.5.0", + "jf-poseidon2", + "num-bigint", +] + +[[package]] +name = "logos-blockchain-proofs-error" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "serde_json", + "thiserror 2.0.18", +] + +[[package]] +name = "logos-blockchain-services-utils" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "futures", + "log", + "logos-blockchain-log-targets", + "overwatch", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "logos-blockchain-storage-service" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "bytes", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-log-targets", + "logos-blockchain-tracing", + "overwatch", + "serde", + "thiserror 2.0.18", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-time-service" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "futures", + "log", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-log-targets", + "logos-blockchain-tracing", + "logos-blockchain-utils", + "overwatch", + "serde", + "serde_with", + "sntpc", + "thiserror 2.0.18", + "time", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-tracing" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "flate2", + "logos-blockchain-log-targets", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-http", + "opentelemetry-otlp", + "opentelemetry-semantic-conventions", + "opentelemetry_sdk", + "rand 0.8.6", + "serde", + "tokio", + "tonic", + "tracing", + "tracing-appender", + "tracing-gelf", + "tracing-loki", + "tracing-opentelemetry", + "tracing-subscriber 0.3.23", + "url", +] + +[[package]] +name = "logos-blockchain-utils" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "async-trait", + "blake2", + "cipher 0.4.4", + "const-hex", + "humantime", + "logos-blockchain-log-targets", + "overwatch", + "rand 0.8.6", + "serde", + "serde_ignored", + "serde_with", + "serde_yaml", + "thiserror 2.0.18", + "time", + "tracing", +] + +[[package]] +name = "logos-blockchain-utxotree" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "ark-ff 0.5.0", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "num-bigint", + "rpds", + "serde", + "thiserror 2.0.18", +] + +[[package]] +name = "logos-blockchain-zksign" +version = "0.1.2" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git?rev=d8711bbc3d43d3ef9755ef9b73af32fd0f703160#d8711bbc3d43d3ef9755ef9b73af32fd0f703160" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-signature-sys", + "logos-blockchain-circuits-types", + "logos-blockchain-groth16", + "logos-blockchain-log-targets", + "logos-blockchain-poseidon2", + "logos-blockchain-proofs-error", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.18", + "tracing", +] + +[[package]] +name = "loki-api" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bdc38a304f59a03e6efa3876766a48c70a766a93f88341c3fff4212834b8e327" +dependencies = [ + "prost 0.13.5", + "prost-types", +] + +[[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" @@ -2305,6 +4437,38 @@ dependencies = [ "libc", ] +[[package]] +name = "match-lookup" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "757aee279b8bdbb9f9e676796fd459e4207a1f986e87886700abf589f5abf771" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "match_cfg" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4" + +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + +[[package]] +name = "matchit" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e7465ac9959cc2b1404e8e2367b43684a6d13790fe23056cc8c6c5a6b7bcb94" + [[package]] name = "maybe-async" version = "0.2.11" @@ -2349,6 +4513,28 @@ dependencies = [ "paste", ] +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "mio" version = "1.2.1" @@ -2371,7 +4557,7 @@ dependencies = [ "module-lattice", "pkcs8 0.11.0", "rand_core 0.10.1", - "sha3", + "sha3 0.11.0", ] [[package]] @@ -2385,12 +4571,239 @@ dependencies = [ "num-traits", ] +[[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 = "multiaddr" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe6351f60b488e04c1d21bc69e56b89cb3f5e8f5d22557d6e8031bdfd79b6961" +dependencies = [ + "arrayref", + "byteorder", + "data-encoding", + "libp2p-identity", + "multibase", + "multihash", + "percent-encoding", + "serde", + "static_assertions", + "unsigned-varint 0.8.0", + "url", +] + +[[package]] +name = "multibase" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e0e4a371cbf1dfd666b658ba137763edb23c45beb43cfe369b5593cd6b437b6" +dependencies = [ + "base-x", + "base256emoji", + "base45", + "data-encoding", + "data-encoding-macro", +] + +[[package]] +name = "multihash" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "577c63b00ad74d57e8c9aa870b5fccebf2fd64a308a5aee9f1bb88e4aea19447" +dependencies = [ + "serde", + "unsigned-varint 0.8.0", +] + +[[package]] +name = "multistream-select" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea0df8e5eec2298a62b326ee4f0d7fe1a6b90a09dfcf9df37b38f947a8c42f19" +dependencies = [ + "bytes", + "futures", + "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", +] + +[[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.13.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 = "nix" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" +dependencies = [ + "bitflags 2.13.0", + "cfg-if", + "cfg_aliases", + "libc", +] + [[package]] name = "no_std_strings" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a5b0c77c1b780822bc749a33e39aeb2c07584ab93332303babeabb645298a76e" +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "nom" +version = "8.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df9761775871bdef83bee530e60050f7e54b1105350d6884eb0fb4f46c2f9405" +dependencies = [ + "memchr", +] + +[[package]] +name = "nu-ansi-term" +version = "0.50.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "num-bigint" version = "0.4.6" @@ -2483,6 +4896,15 @@ dependencies = [ "malloc_buf", ] +[[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.4" @@ -2490,10 +4912,135 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + +[[package]] +name = "opentelemetry" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b84bcd6ae87133e903af7ef497404dda70c60d0ea14895fc8a5e6722754fc2a0" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror 2.0.18", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef6a1ac5ca3accf562b8c306fa8483c85f4390f768185ab775f242f7fe8fdcc2" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "opentelemetry-http" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7a6d09a73194e6b66df7c8f1b680f156d916a1a942abf2de06823dd02b7855d" +dependencies = [ + "async-trait", + "bytes", + "http 1.4.2", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f69cd6acbb9af919df949cd1ec9e5e7fdc2ef15d234b6b795aaa525cc02f71f" +dependencies = [ + "http 1.4.2", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost 0.14.4", + "reqwest", + "thiserror 2.0.18", + "tokio", + "tonic", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7175df06de5eaee9909d4805a3d07e28bb752c34cab57fa9cff549da596b30f" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost 0.14.4", + "tonic", + "tonic-prost", +] + +[[package]] +name = "opentelemetry-semantic-conventions" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e62e29dfe041afb8ed2a6c9737ab57db4907285d999ef8ad3a59092a36bdc846" + +[[package]] +name = "opentelemetry_sdk" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e14ae4f5991976fd48df6d843de219ca6d31b01daaab2dad5af2badeded372bd" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "rand 0.9.4", + "thiserror 2.0.18", + "tokio", + "tokio-stream", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "overwatch" +version = "0.1.0" +source = "git+https://github.com/logos-co/Overwatch?rev=448c192#448c192895b8311c742b1726a1bb12ee314ad95c" +dependencies = [ + "async-trait", + "futures", + "overwatch-derive", + "thiserror 2.0.18", + "tokio", + "tokio-stream", + "tokio-util", + "tracing", +] + +[[package]] +name = "overwatch-derive" +version = "0.1.0" +source = "git+https://github.com/logos-co/Overwatch?rev=448c192#448c192895b8311c742b1726a1bb12ee314ad95c" +dependencies = [ + "convert_case 0.8.0", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] [[package]] name = "parity-scale-codec" @@ -2523,12 +5070,51 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "parking" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba" + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link", +] + [[package]] name = "paste" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" +[[package]] +name = "pem" +version = "3.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d30c53c26bc5b31a98cd02d20f25a7c8567146caf63ed593a9d87b2775291be" +dependencies = [ + "base64", + "serde_core", +] + [[package]] name = "pem-rfc7468" version = "0.7.0" @@ -2554,6 +5140,26 @@ dependencies = [ "ucd-trie", ] +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "pin-project-lite" version = "0.2.17" @@ -2591,6 +5197,38 @@ dependencies = [ "spki 0.8.0", ] +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1fe60d06143b2430aa532c94cfe9e29783047f06c0d7fd359a9a51b729fa25" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "opaque-debug", + "universal-hash", +] + +[[package]] +name = "portable-atomic" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c33a9471896f1c69cecef8d20cbe2f7accd12527ce60845ff44c153bb2a21b49" + [[package]] name = "postcard" version = "1.1.3" @@ -2635,7 +5273,7 @@ checksum = "0b34d9fd68ae0b74a41b21c03c2f62847aa0ffea044eee893b4c140b37e244e2" dependencies = [ "fixed-hash", "impl-codec", - "uint", + "uint 0.9.5", ] [[package]] @@ -2647,6 +5285,52 @@ dependencies = [ "toml_edit 0.25.12+spec-1.1.0", ] +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -2668,6 +5352,38 @@ dependencies = [ "version_check", ] +[[package]] +name = "programs" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" +dependencies = [ + "build_utils", + "lee", +] + +[[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.118", +] + [[package]] name = "proptest" version = "1.11.0" @@ -2694,7 +5410,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2796faa41db3ec313a31f7624d9286acf277b52de526150b7e69f3debf891ee5" dependencies = [ "bytes", - "prost-derive", + "prost-derive 0.13.5", +] + +[[package]] +name = "prost" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" +dependencies = [ + "bytes", + "prost-derive 0.14.4", ] [[package]] @@ -2710,12 +5436,56 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "prost-derive" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" +dependencies = [ + "anyhow", + "itertools 0.14.0", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "prost-types" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52c2c1bf36ddb1a1c396b3601a3cec27c2462e45f07c386894ec3ccf5332bd16" +dependencies = [ + "prost 0.13.5", +] + [[package]] name = "quick-error" version = "1.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" +[[package]] +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]] name = "quinn" version = "0.11.11" @@ -2724,12 +5494,13 @@ checksum = "0c1a41e437b6bbd489372cd4971de128e85c855f56c57f283d20ff016cf7c0a8" dependencies = [ "bytes", "cfg_aliases", + "futures-io", "pin-project-lite", "quinn-proto", "quinn-udp", "rustc-hash", "rustls", - "socket2", + "socket2 0.6.4", "thiserror 2.0.18", "tokio", "tracing", @@ -2766,7 +5537,7 @@ dependencies = [ "cfg_aliases", "libc", "once_cell", - "socket2", + "socket2 0.6.4", "tracing", "windows-sys 0.60.2", ] @@ -2881,6 +5652,28 @@ dependencies = [ "rustversion", ] +[[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 = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.0", +] + [[package]] name = "redox_users" version = "0.5.2" @@ -2952,7 +5745,8 @@ dependencies = [ "futures-channel", "futures-core", "futures-util", - "http", + "h2", + "http 1.4.2", "http-body", "http-body-util", "hyper", @@ -2983,6 +5777,12 @@ 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" @@ -3193,7 +5993,7 @@ dependencies = [ "derive_more", "hex", "lazy-regex", - "prost", + "prost 0.13.5", "risc0-binfmt", "risc0-build", "risc0-circuit-keccak", @@ -3240,6 +6040,17 @@ dependencies = [ "rustc-hex", ] +[[package]] +name = "rpds" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e025feb26210bc196b908e72deb063b1b4000754304341cbc168a1e72c857ebc" +dependencies = [ + "archery", + "serde", + "smallvec", +] + [[package]] name = "rrs-lib" version = "0.1.0" @@ -3250,6 +6061,25 @@ dependencies = [ "paste", ] +[[package]] +name = "rs-merkle-tree" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7a3ef170810c387d31b64c0b59734abb0839dac2a8d137909e271bfdec9b1e0" +dependencies = [ + "ark-bn254", + "ark-ff 0.5.0", + "byteorder", + "futures", + "light-poseidon", + "quote", + "rand 0.9.4", + "syn 1.0.109", + "thiserror 2.0.18", + "tiny-keccak", + "tokio", +] + [[package]] name = "rsa" version = "0.9.10" @@ -3270,6 +6100,24 @@ dependencies = [ "zeroize", ] +[[package]] +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]] name = "ruint" version = "1.17.0" @@ -3305,6 +6153,18 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48fd7bd8a6377e15ad9d42a8ec25371b94ddc67abe7c8b9127bec79bebaaae18" +[[package]] +name = "rust-rapidsnark" +version = "0.1.3" +source = "git+https://github.com/logos-blockchain/logos-blockchain-rust-rapidsnark.git?rev=e91187f8ccb5bbfc7bb00dac88169112428da78f#e91187f8ccb5bbfc7bb00dac88169112428da78f" +dependencies = [ + "anyhow", + "cc", + "chkstk_stub", + "num-bigint", + "num-traits", +] + [[package]] name = "rustc-hash" version = "2.1.2" @@ -3335,6 +6195,15 @@ dependencies = [ "semver 1.0.28", ] +[[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" @@ -3354,6 +6223,7 @@ version = "0.23.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b92b125634d9b795e7beca796cc790df15a7fb38323bf3196fda83292d06b1f" dependencies = [ + "log", "once_cell", "ring", "rustls-pki-types", @@ -3401,6 +6271,17 @@ dependencies = [ "wait-timeout", ] +[[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" @@ -3450,6 +6331,12 @@ dependencies = [ "serde_json", ] +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + [[package]] name = "sec1" version = "0.7.3" @@ -3458,7 +6345,7 @@ checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" dependencies = [ "base16ct", "der 0.7.10", - "generic-array", + "generic-array 0.14.9", "pkcs8 0.10.2", "serdect", "subtle", @@ -3523,6 +6410,15 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde_arrays" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94a16b99c5ea4fe3daccd14853ad260ec00ea043b2708d1fd1da3106dcd8d9df" +dependencies = [ + "serde", +] + [[package]] name = "serde_core" version = "1.0.228" @@ -3543,6 +6439,16 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "serde_ignored" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115dffd5f3853e06e746965a20dcbae6ee747ae30b543d91b0e089668bb07798" +dependencies = [ + "serde", + "serde_core", +] + [[package]] name = "serde_json" version = "1.0.150" @@ -3557,6 +6463,17 @@ dependencies = [ "zmij", ] +[[package]] +name = "serde_path_to_error" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a9ff822e371bb5403e391ecd83e182e0e77ba7f6fe0160b795797109d1b457" +dependencies = [ + "itoa", + "serde", + "serde_core", +] + [[package]] name = "serde_spanned" version = "0.6.9" @@ -3610,6 +6527,19 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "serde_yaml" +version = "0.9.34+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8b1a1a2ebf674015cc02edccce75287f1a0130d394307b36743c2f5d504b47" +dependencies = [ + "indexmap 2.14.0", + "itoa", + "ryu", + "serde", + "unsafe-libyaml", +] + [[package]] name = "serdect" version = "0.2.0" @@ -3631,6 +6561,16 @@ dependencies = [ "digest 0.10.7", ] +[[package]] +name = "sha3" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77fd7028345d415a4034cf8777cd4f8ab1851274233b45f84e3d955502d93874" +dependencies = [ + "digest 0.10.7", + "keccak 0.1.6", +] + [[package]] name = "sha3" version = "0.11.0" @@ -3651,12 +6591,31 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "signal-hook-registry" +version = "1.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b" +dependencies = [ + "errno", + "libc", +] + [[package]] name = "signature" version = "2.2.0" @@ -3667,6 +6626,12 @@ dependencies = [ "rand_core 0.6.4", ] +[[package]] +name = "simd-adler32" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214" + [[package]] name = "slab" version = "0.4.12" @@ -3679,6 +6644,32 @@ version = "1.15.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" +[[package]] +name = "snap" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b6b67fb9a61334225b5b790716f609cd58395f895b3fe8b328786812a40bc3b" + +[[package]] +name = "sntpc" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78f778a0f82b3cf5d75f858eceee38e84d5292f1d03415e88cc4ec45ca6ba8a2" +dependencies = [ + "cfg-if", + "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.4" @@ -3745,6 +6736,24 @@ dependencies = [ "der 0.8.0", ] +[[package]] +name = "spongefish" +version = "0.2.0" +source = "git+https://github.com/arkworks-rs/spongefish.git?rev=3ded547f7f56d7f8a1fc4c9a5c0ce965310bba5f#3ded547f7f56d7f8a1fc4c9a5c0ce965310bba5f" +dependencies = [ + "ark-ec", + "ark-ff 0.5.0", + "ark-serialize 0.5.0", + "digest 0.10.7", + "hex", + "keccak 0.1.6", + "rand 0.8.6", + "sha3 0.10.9", + "thiserror 2.0.18", + "zerocopy", + "zeroize", +] + [[package]] name = "stability" version = "0.2.1" @@ -3832,6 +6841,12 @@ version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +[[package]] +name = "symlink" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7973cce6668464ea31f176d85b13c7ab3bba2cb3b77a2ed26abd7801688010a" + [[package]] name = "syn" version = "1.0.109" @@ -3874,12 +6889,73 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a13f3d0daba03132c0aa9767f98351b3488edc2c100cda2d2ec2b04f3d8d3c8b" +dependencies = [ + "bitflags 2.13.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "system_accounts" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-execution-zone.git?tag=v0.2.0#a58fbce2ff48c58b7bb5001b1a27e64b9596ee3a" +dependencies = [ + "bridge_core", + "clock_core", + "faucet_core", + "lee_core", + "programs", +] + +[[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" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -3933,6 +7009,15 @@ dependencies = [ "syn 2.0.118", ] +[[package]] +name = "thread_local" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f60246a4944f24f6e018aa17cdeffb7818b76356965d03b07d6a9886e8962185" +dependencies = [ + "cfg-if", +] + [[package]] name = "time" version = "0.3.53" @@ -3963,6 +7048,15 @@ dependencies = [ "time-core", ] +[[package]] +name = "tiny-keccak" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" +dependencies = [ + "crunchy", +] + [[package]] name = "tinystr" version = "0.8.3" @@ -4024,11 +7118,25 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", - "socket2", + "signal-hook-registry", + "socket2 0.6.4", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "385a6cb71ab9ab790c5fe8d67f1645e6c450a7ce006a33de03daa956cf70a496" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "tokio-rustls" version = "0.26.4" @@ -4039,6 +7147,18 @@ dependencies = [ "tokio", ] +[[package]] +name = "tokio-stream" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32da49809aab5c3bc678af03902d4ccddea2a87d028d86392a4b1560c6906c70" +dependencies = [ + "futures-core", + "pin-project-lite", + "tokio", + "tokio-util", +] + [[package]] name = "tokio-util" version = "0.7.18" @@ -4123,6 +7243,43 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d99f8c9a7727884afe522e9bd5edbfc91a3312b36a77b5fb8926e4c31a41801" +[[package]] +name = "tonic" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2a5518c70fa84342385732db33fb3f44bc4cc748936eb5833d2df34d6445ef" +dependencies = [ + "async-trait", + "base64", + "bytes", + "http 1.4.2", + "http-body", + "http-body-util", + "hyper", + "hyper-timeout", + "hyper-util", + "percent-encoding", + "pin-project", + "sync_wrapper", + "tokio", + "tokio-stream", + "tower", + "tower-layer", + "tower-service", + "tracing", +] + +[[package]] +name = "tonic-prost" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50849f68853be452acf590cde0b146665b8d507b3b8af17261df47e02c209ea0" +dependencies = [ + "bytes", + "prost 0.14.4", + "tonic", +] + [[package]] name = "tower" version = "0.5.3" @@ -4131,11 +7288,15 @@ checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" dependencies = [ "futures-core", "futures-util", + "indexmap 2.14.0", "pin-project-lite", + "slab", "sync_wrapper", "tokio", + "tokio-util", "tower-layer", "tower-service", + "tracing", ] [[package]] @@ -4147,7 +7308,7 @@ dependencies = [ "bitflags 2.13.0", "bytes", "futures-util", - "http", + "http 1.4.2", "http-body", "pin-project-lite", "tower", @@ -4169,36 +7330,134 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] -name = "tracing" -version = "0.1.44" +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-appender" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "050686193eb999b4bb3bc2acfa891a13da00f79734704c4b8b4ef1a10b368a3c" +dependencies = [ + "crossbeam-channel", + "symlink", + "thiserror 2.0.18", + "time", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-futures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97d095ae15e245a057c8e8451bab9b3ee1e1f68e9ba2b4fbc18d0ac5237835f2" +dependencies = [ + "pin-project", + "tracing", +] + +[[package]] +name = "tracing-gelf" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c0170f1bf67b749d4377c2da1d99d6e722600051ee53870cfb6f618611e29e" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "hostname", + "serde_json", + "thiserror 1.0.69", + "tokio", + "tokio-util", + "tracing-core", + "tracing-futures", + "tracing-subscriber 0.3.23", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-loki" +version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +checksum = "a8d1ad78bf74c1790b0825ddc35ad1bb9498736c51c8437796b81cadf4916cd8" dependencies = [ - "log", - "pin-project-lite", - "tracing-attributes", + "loki-api", + "reqwest", + "serde", + "serde_json", + "snap", + "tokio", + "tracing", "tracing-core", + "tracing-log", + "tracing-serde", + "tracing-subscriber 0.3.23", + "url", ] [[package]] -name = "tracing-attributes" -version = "0.1.31" +name = "tracing-opentelemetry" +version = "0.32.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +checksum = "1ac28f2d093c6c477eaa76b23525478f38de514fa9aeb1285738d4b97a9552fc" dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.118", + "js-sys", + "opentelemetry", + "smallvec", + "tracing", + "tracing-core", + "tracing-subscriber 0.3.23", + "web-time", ] [[package]] -name = "tracing-core" -version = "0.1.36" +name = "tracing-serde" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +checksum = "704b1aeb7be0d0a84fc9828cae51dab5970fee5088f83d1dd7ee6f6246fc6ff1" dependencies = [ - "once_cell", - "valuable", + "serde", + "tracing-core", ] [[package]] @@ -4210,6 +7469,30 @@ dependencies = [ "tracing-core", ] +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "nu-ansi-term", + "once_cell", + "regex-automata", + "sharded-slab", + "smallvec", + "thread_local", + "tracing", + "tracing-core", + "tracing-log", +] + +[[package]] +name = "triomphe" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b40688ea6389c8171614b25491f71d4a27946e0c7ce2da1c6de27e25abf1a0ae" + [[package]] name = "try-lock" version = "0.2.5" @@ -4272,6 +7555,18 @@ dependencies = [ "static_assertions", ] +[[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" @@ -4284,6 +7579,15 @@ version = "1.0.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" +[[package]] +name = "unicode-normalization" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" +dependencies = [ + "tinyvec", +] + [[package]] name = "unicode-segmentation" version = "1.13.3" @@ -4307,12 +7611,69 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common 0.1.6", + "subtle", +] + +[[package]] +name = "unsafe-libyaml" +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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb066959b24b5196ae73cb057f45598450d2c5f71460e98c49b738086eff9c06" + [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" +[[package]] +name = "ureq" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dea7109cdcd5864d4eeb1b58a1648dc9bf520360d7af16ec26d0a9354bafcfc0" +dependencies = [ + "base64", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "ureq-proto", + "utf8-zero", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e994ba84b0bd1b1b0cf92878b7ef898a5c1760108fe7b6010327e274917a808c" +dependencies = [ + "base64", + "http 1.4.2", + "httparse", + "log", +] + [[package]] name = "url" version = "2.5.8" @@ -4323,14 +7684,86 @@ dependencies = [ "idna", "percent-encoding", "serde", + "serde_derive", ] +[[package]] +name = "utf8-zero" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8c0a043c9540bae7c578c88f91dda8bd82e59ae27c21baca69c8b191aaf5a6e" + [[package]] name = "utf8_iter" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" +[[package]] +name = "utoipa" +version = "4.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5afb1a60e207dca502682537fefcfd9921e71d0b83e9576060f09abc6efab23" +dependencies = [ + "indexmap 2.14.0", + "serde", + "serde_json", + "utoipa-gen", +] + +[[package]] +name = "utoipa-gen" +version = "4.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20c24e8ab68ff9ee746aad22d39b5535601e6416d1b0feeabf78be986a5c4392" +dependencies = [ + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.118", +] + +[[package]] +name = "uuid" +version = "1.23.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf80a72845275afea99e7f2b434723d3bc7e38470fcd1c7ed39a599c73319a53" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "validator" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43fb22e1a008ece370ce08a3e9e4447a910e92621bb49b85d6e48a45397e7cfa" +dependencies = [ + "idna", + "once_cell", + "regex", + "serde", + "serde_derive", + "serde_json", + "url", + "validator_derive", +] + +[[package]] +name = "validator_derive" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7df16e474ef958526d1205f6dda359fdfab79d9aa6d54bafcb92dcd07673dca" +dependencies = [ + "darling 0.20.11", + "once_cell", + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.118", +] + [[package]] name = "valuable" version = "0.1.1" @@ -4473,6 +7906,55 @@ dependencies = [ "rustls-pki-types", ] +[[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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows" +version = "0.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" @@ -4486,6 +7968,17 @@ 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" @@ -4514,6 +8007,27 @@ 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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link", + "windows-result", + "windows-strings", +] + [[package]] name = "windows-result" version = "0.4.1" @@ -4592,6 +8106,15 @@ dependencies = [ "windows_x86_64_msvc 0.53.1", ] +[[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.52.6" @@ -4727,6 +8250,60 @@ dependencies = [ "tap", ] +[[package]] +name = "x25519-dalek" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7e468321c81fb07fa7f4c636c3972b9100f0346e5b6a9f2bd0603a52f7ed277" +dependencies = [ + "curve25519-dalek", + "rand_core 0.6.4", + "serde", + "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" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "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 = "yaml-rust2" version = "0.10.4" @@ -4735,7 +8312,16 @@ checksum = "2462ea039c445496d8793d052e13787f2b90e750b833afee748e601c17621ed9" dependencies = [ "arraydeque", "encoding_rs", - "hashlink", + "hashlink 0.10.0", +] + +[[package]] +name = "yasna" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17bb3549cc1321ae1296b9cdc2698e2b6cb1992adfa19a8c72e5b7a738f44cd" +dependencies = [ + "time", ] [[package]] diff --git a/docs/findings.md b/docs/findings.md new file mode 100644 index 00000000..3f4ddf38 --- /dev/null +++ b/docs/findings.md @@ -0,0 +1,185 @@ + +# Privacy coverage in LEZ programs + +LEZ programs, ideally, are privacy agnostic. E.g., a program should work the same for public and private accounts. Currently, LEZ program integration tests only cover public accounts. This task, we expand the tests for LEZ programs to determine how compatible LEZ programs are with selective privacy. + + +# Private account variants in LEE + +LEE's private state supports (regular) accounts, PDAs and group owned accounts. + +## Overview of (regular) private accounts + +### Private account initialization +Regular private accounts can be initialized with or without knowledge of the account's nullifier secret key `nsk`. This results in two initialization "types": `PrivateUnauthorized` and `PrivateAuthorizedInit`. + +- `PrivateUnauthorized` + + A special case for private accounts initialization that uses only public keys `npk` and `vpk`. Example: Alice can use Bob's keys (`npk`, `vpk`) and an `identifier` to send Bob a private transaction. Since Alice does not know the corresponding `nsk`, she is unable to spend the resulting private account. E.g., Alice cannot authorize the transaction. + +- `PrivateAuthorizedInit` + Private account initialized using the account's `nsk` (and some `identifier`). This operation cannot be done by the a third-party (an entity that does not possess spending authority of the account). + +### Private account update (`PrivateAuthorizedUpdate`) +Private account updates require knowledge of the account's `nsk`. E.g., Alice cannot update the private account that she initialized for Bob. + +### Summary + +|type | authorized | who can use | +|----|----|----| +| `PrivateUnauthorized`| ❌ | anyone | +| `PrivateAuthorizedInit` | ✅ | owner | +| `PrivateAuthorizedUpdate` | ✅ | owner | + +Only the account owner can (1) update their initialized account, and (2) use functions that require authorization with their account. + +### Remark +- `is_authorized = false` is crucial for public accounts, since a public account id can be freely referenced and used by anyone in a transaction — `is_authorized` is what stops that. Private accounts don't have this exposure: only the account owner (via `nsk`) can ever update a private account, regardless of `is_authorized`. So for `PrivateUnauthorized` specifically — a third party initializing a *new* account on the owner's behalf, which nobody but the owner can subsequently update — `is_authorized = false` doesn't protect anything; it only blocks legitimate program functions that require a signer. This issue has been resolved by [PR 621](https://github.com/logos-blockchain/logos-execution-zone/pull/621); for more information see the Action items section. + +## Private PDA + +Private PDAs spending is restrict by a specific program. E.g., an AMM pool has PDAs for liquidity definition and vaults (for Token A and Token B). A program sets `is_authorized = true` for an account (purported PDA) by checking the correctness of its `AccountId`. + +- `AccountId` formulas: + - Public: `hash(prefix || program_id || seed)` + - Private: `hash(prefix || program_id || seed || npk || identifier)` + +The difference in these PDA `AccountId` formulas prevents programs from being privacy agnostic for PDAs. + +## Group-shared (multi-party) private accounts + +A single private account can be jointly controlled by two or more parties without either one +handing over their actual secret key. The mechanism is a **Group Master Secret (GMS)**, +distributed via a real seal/unseal handshake (ML-KEM-768), not key reuse: + +1. Alice creates a `GroupKeyHolder` and derives the shared account's keys (`nsk`, `vsk`) + from it. +2. Alice **seals** the GMS against Bob's sealing public key and hands over only the sealed bytes. +3. Bob **unseals** it with his own sealing secret key, then + independently re-derives the account's keys from the same seed. + +This ensures that any member of the group can execute programs on shared accounts using either `PrivateAuthorizedInit` or `PrivateAuthorizedUpdate`. From a program's perspective, shared accounts should behave the same as regular private accounts. + +# Privacy coverage for LEZ programs objectives + +In this task, we extend testing for LEZ programs to cover privacy features: + +| | description | +|---------|----| +| PDA | test checks for private PDA functionality. | +| REGULAR | private accounts usage using `nsk` | +| EXIST | private account initialized without `nsk`; `PrivateUnauthorized` | +| GROUP | Shared group account | +| CHAIN | private account used in a chain call | + +A test function whose name ends in `_is_not_expressible` is a negative test: it demonstrates that +a desirable privacy pattern currently fails. + +# LEZ programs + +## AMM program + +| Function tested | Test name | Category | Description of objective | Result | +|---|---|---|---|---| +| SwapExactInput | `amm_swap_a_to_b_private_user_holding` | REGULAR, CHAIN | Private `user_holding_a` deposit leg, through the Token + TWAP-oracle chained calls | ✅ | +| SwapExactOutput | `amm_swap_exact_output_private_user_holding` | REGULAR, CHAIN | Same coverage for `SwapExactOutput` | ✅ | +| AddLiquidity | `amm_add_liquidity_private_lp_holding` | REGULAR, CHAIN | Private LP-output holding (`user_holding_lp`) receives newly-minted LP on top of an existing private balance | ✅ | +| AddLiquidity | `amm_add_liquidity_private_user_holdings` | REGULAR, CHAIN | Private deposit legs (`user_holding_a` + `user_holding_b`), public LP recipient | ✅ | +| RemoveLiquidity | `amm_remove_liquidity_private_lp_holding` | REGULAR, CHAIN | Private LP holding (the account that signs/burns to remove liquidity) | ✅ | +| RemoveLiquidity | `amm_remove_liquidity_private_new_user_holdings_is_not_expressible` | EXIST, CHAIN | Brand-new `PrivateUnauthorized` token A/B destinations — rejected by a separate, unrelated program-level precondition (destination must already exist) | ❌ Not-expressible — AMM's own precondition requires the destination to already be owned by the Token Program. **[Open — Programs]** | +| SwapExactInput | `amm_swap_a_to_b_private_unauthorized_destination_is_not_expressible` | EXIST, CHAIN | Swap paying out to a brand-new `PrivateUnauthorized` destination (`npk` only, no `nsk`) | ❌ Not-expressible — the guest's signer check requires `is_authorized == true` on both swap legs, and `PrivateUnauthorized` always initializes with `is_authorized == false`. **[Resolved — PR #621]** | +| SwapExactInput | `amm_swap_a_to_b_private_authorized_init_destination_is_not_expressible` | REGULAR, CHAIN | Swap paying out to a brand-new `PrivateAuthorizedInit` destination (owner self-initializes with its own `nsk`) | ❌ Not-expressible — same "destination must already exist" precondition as `RemoveLiquidity`. **[Open — Programs]** | +| NewDefinition | `amm_new_definition_private_initial_lp_holder` | REGULAR | Pool creation with a private `PrivateAuthorizedInit` initial LP holder | ✅ | +| NewDefinition | `amm_new_definition_private_unauthorized_lp_holder_is_not_expressible` | EXIST, REGULAR | Pool creation with a `PrivateUnauthorized` initial LP holder (`npk` only, no `nsk`) | ❌ Not-expressible — same `is_authorized == true` signer check as the `Swap` row above, on `user_holding_lp`. **[Resolved — PR #621]** | + +### Remarks +- `Swap` and `Remove` rejects any uninitialized destination account; this is a AMM design choice, and not Token program requirement. +- AMM's chained-call privacy tests were initially blocked by a "bug" in `logos-execution-zone`. An account was silently dropped from the programs output (pre and post states) before the transaction was validated. This behavior was acceptable in public transactions, but not for privacy transactions. This issue has been resolved by [PR 625](https://github.com/logos-blockchain/logos-execution-zone/pull/625); for more information see the Action items section. + - The `clock` account was the offending account in the tests. For `integration_tests` the default account id was used which resulted in the account being dropped by LEZ. This behavior does not occur in practice as the `clock` account id is used for real. The AMM tests have been updated to avoid this issue for public and privacy tests. + +## ATA program + +ATA program offers limited usage with private accounts. Private accounts can be used as the `owner` (or as a recipient to transactions). But, ATA program can only generate public PDAs. The `owner` account can be public/private/shared and have any `program_owner`. + +| Function tested | Test name | Category | Description of objective | Result | +|---|---|---|---|---| +| Create | `ata_create_from_private_owner` | REGULAR, EXIST | Any third party can bootstrap another owner's ATA using only that owner's public key material (`PrivateUnauthorized` — `npk`/`vpk` only, no `nsk`) — `Create` never asserts `owner.is_authorized` | ✅ | +| Create | `ata_create_private_ata_holding_is_not_expressible` | PDA | Attempts to make the ATA holding itself a private account via `PrivatePdaInit`/`PrivatePdaUpdate` — confirms the public-form PDA match ATA authorizes with and the private-form binding those variants require are mutually exclusive for the same account id | ❌ Not-expressible — `ata_core` derives the ATA holding's `AccountId` via the public-only formula, and the public/private formulas are mutually exclusive for the same account id. **[Open — Zones]**, a unified `AccountId` formula needs to be devised before programs can support private PDAs | +| Create | `ata_create_from_group_owned_owner` | GROUP | Group-derived owner identity used to create an ATA — **weaker than the other `GROUP` rows**: `Create` never requires `owner` to prove control. | ✅ (defensive/symmetry coverage only) | +| Transfer | `ata_transfer_to_existing_private_recipient` | REGULAR | Sends more into an already-shielded private recipient through ATA's *nested* chained call into Token — the first test in the whole exercise proving a private identity survives a chained call at all | ✅ | +| Transfer | `ata_transfer_with_private_owner_signing` | REGULAR | Key discovery: unlike `Create` (merely `mut`), `Transfer` requires `owner` to be a *signer* (`#[account(signer)]`) — a private owner self-initializes and signs in the same transaction via `PrivateAuthorizedInit` | ✅ | +| Transfer | `ata_transfer_with_group_owned_owner_signing` | GROUP | Group-owned owner (real GMS seal/unseal handshake) signs `ATA::Transfer` as the required authorizing party | ✅ | +| Burn | `ata_burn_with_private_owner_signing` | REGULAR | Same signer-authorization discovery as `ata_transfer_with_private_owner_signing`, for `Burn` | ✅ | +| Burn | `ata_group_owned_owner_signing` | GROUP | Group-owned owner signs `ATA::Burn` as the required authorizing party | ✅ | + +### Remarks +- Transfer explicitly blocks `PrivateUnauthorized`and `PrivateAuthorizedInit`. ATA's transfer checks that the recipient's account is non-default. E.g., ATA can not transfer funds to a third-party's private account. +- ATA does not permit the creation of private token accounts. E.g., ATA only emits public PDA accounts. This is based on the PDA `AccountId` formulas used. + +## Stablecoin program + +| Function tested | Test name | Category | Description of objective | Result | +|---|---|---|---|---| +| WithdrawCollateral | `stablecoin_withdraw_collateral_private_destination` | REGULAR | Withdraws collateral through the single `Token::Transfer` chained call into an already-existing private destination holding | ✅ | +| WithdrawCollateral | `stablecoin_withdraw_collateral_group_owned_destination` | EXIST, GROUP | Same, but the destination holding is group-owned (real GMS seal/unseal handshake) | ✅ | +| WithdrawCollateral | `stablecoin_group_owned_position_owner` | GROUP | The position's `owner` identity itself (not the destination) is group-derived — proves shared authority over a CDP by withdrawing collateral through it | ✅ | +| RepayDebt | `stablecoin_repay_debt_private_stablecoin_holding` | REGULAR | Burns from a private stablecoin holding through the single `Token::Burn` chained call | ✅ | +| RepayDebt | `stablecoin_repay_debt_group_owned_stablecoin_holding` | GROUP | Same, group-owned holding | ✅ | + +### Remarks +- `OpenPosition` is blocked for use in privacy transactions due to the chained calls usage. `OpenPosition` calls `Token::InitializeAccount` and `Token::Transfer` for the same vault account which is disallowed behavior in privacy preserving circuit. Demonstrated with test `stablecoin_open_position_via_privacy_transaction_is_not_expressible`. **[Open — Zones]**, see Conclusions. +- `WithdrawCollateral` does not support withdrawals to `PrivateUnauthorized` and `PrivateAuthorizedInit`; explicitly checks that the destination account is not default. Demonstrated with the test `stablecoin_withdraw_collateral_to_new_private_destination_is_not_expressible`. **[Open — Programs]** +- Vault is explicitly public PDA by formula requirement. **[Open — Zones]**, same `AccountId` formula issue as ATA's — see the Action items section. + +## Token program + +| Function tested | Test name | Category | Description of objective | Result | +|---|---|---|---|---| +| Transfer | `token_shielded_transfer` | EXIST | A public sender shields tokens into a fresh private recipient (`PrivateUnauthorized` — only `npk`/`vpk` known, no `nsk`) | ✅ | +| Transfer | `token_private_transfer` | REGULAR -> EXIST | Two private accounts (sender via `PrivateAuthorizedUpdate` + fresh recipient via `PrivateUnauthorized`) compose in a single transaction with no public account at all — fulfills the "multiple private accounts in one tx" | ✅ | +| Transfer | `token_deshielded_transfer` | REGULAR | A private sender (`PrivateAuthorizedUpdate`) transfers out to a public recipient | ✅ | +| Transfer | `token_shielded_transfer_authorized_private_init` | REGULAR | Fresh recipient self-initializes via `PrivateAuthorizedInit` (own `nsk` supplied) instead of being passively credited via `PrivateUnauthorized` | ✅ | +| Transfer | `token_transfer_into_existing_private_holding` | REGULAR | Similar to `token_shielded_transfer_authorized_private_init`, but this shielded transaction does not initialize the private account. Second transfer into an already-shielded recipient — confirms crediting an existing private account requires the recipient's own cooperation (`nsk`), not just their public key | ✅ | +| Transfer | `token_private_transfer_into_existing_private_holding` | REGULAR -> REGULAR | Both legs private (sender + recipient) in one transaction, and the recipient is already existing rather than fresh | ✅ | +| Transfer | `token_group_owned_holding_shared_control_transfer` | GROUP -> EXIST | Group-owned sender (real GMS seal/unseal handshake) spends outward via `Transfer` to a fresh private recipient (`PrivateUnauthorized`) | ✅ | +| Mint | `token_mint_private_unauthorized` | EXIST | Mint directly to a fresh private recipient (self-authority signer + `PrivateUnauthorized` recipient) | ✅ | +| Mint | `token_mint_authorized_private_init` | REGULAR (authorized variant) | Mint to a fresh recipient that self-initializes via `PrivateAuthorizedInit` (own `nsk` supplied) instead of being passively credited | ✅ | +| Mint | `token_mint_into_existing_private_holding` | REGULAR | Mint once to establish a private holding, mint again into it via `PrivateAuthorizedUpdate` — crediting an existing private account | ✅ | +| Burn | `token_private_burn` | REGULAR | Burn from an existing private holding via a single `PrivateAuthorizedUpdate` | ✅ | +| Burn | `token_group_owned_holding_shared_control_burn` | GROUP | Shield tokens into a GMS-derived shared holding, then burn from it using an independently re-derived key | ✅ | +| InitializeAccount | `token_initialize_private_account_succeeds_for_canonical_definition` | REGULAR | Self-init of a private holding via `PrivateAuthorizedInit` | ✅ | +| InitializeAccount | `token_initialize_private_account_without_nsk_is_not_expressible` | EXIST | `InitializeAccount`'s target is `#[account(init, signer)]` — the guest's signer check requires `is_authorized == true`, and a third party initializing via `PrivateUnauthorized` (no `nsk`) always gets `is_authorized == false`, rejected before the program's own logic runs | ❌ Not-expressible. **[Resolved — PR #621]** | +| InitializeAccount | `token_group_owned_holding_shared_control_initialize` | GROUP | A group member — not the party who created the group — self-initializes the shared holding directly via `PrivateAuthorizedInit` | ✅ | +| MintWithAuthority | `token_mint_with_authority_to_private_holding` | EXIST | External-authority mint (distinct signer from the definition) directly to a fresh private recipient | ✅ | +| NewFungibleDefinition | `token_new_fungible_definition_private_initial_holder` | REGULAR | Public token definition, private initial holder that self-initializes via `PrivateAuthorizedInit` (own `nsk` supplied) — same self-service shape as `InitializeAccount`'s target | ✅ | +| NewFungibleDefinition | `token_new_fungible_definition_private_holder_without_nsk_is_not_expressible` | EXIST | The initial holder cannot be created via `PrivateUnauthorized` — same `is_authorized == true` signer check as `InitializeAccount` above, rejected before the program's own logic runs | ❌ Not-expressible. **[Resolved — PR #621]** | + + +### Remarks +- `Initialization` is not possible for `PrivateUnauthorized` accounts due to `is_authorized = false`. **[Resolved — PR #621]** +- New token definition is not permitted for `PrivateUnauthorized` as Token holding due to `is_authorized = false`. E.g., both Token Definition and Token Holding for a new Token must be from an authorized account. **[Resolved — PR #621]** + +# Conclusions + +Privacy coverage for LEZ program tests is greatly improved from the added tests. Though, there are a few noticable gaps: +- `PrivateUnauthorized` accounts can be blocked by programs with a check requiring `is_authorized = true`, since a fresh `PrivateUnauthorized` account is always initialized with `is_authorized = false`. This is a `logos-execution-zone` protocol-level issue, not something programs can work around — see the `PrivateUnauthorized` remark above for the full reasoning. This has been resolved by [PR 621](https://github.com/logos-blockchain/logos-execution-zone/pull/621); see the Action items section. +- Privacy transactions have issues with chain calls in which multiple calls affect the same private account. The privacy preserving circuit's `authorized_accounts` bookkeeping is monotonic (once an account is authorized, every later occurrence within the same transaction must also declare it authorized), which rejects some call patterns that are valid on the public-transaction path (E.g. `Stablecoin::OpenPosition`). This is a `logos-execution-zone` protocol level issue. A proposed revision to account updates would mitigate this issue: accounts updated iteratively based on their state diff rather than "full replacement". + +Additionally, testing undercovered a "bug" in LEZ: +AMM's chained-call privacy tests were initially blocked because the `clock` account — seeded in the test fixture with a default (unclaimed) account id, `DEFAULT_PROGRAM_ID`, rather than owned by a dedicated clock program as in production — was silently dropped from a program's output, undetected by `logos-execution-zone`'s public-transaction validation: `ValidatedStateDiff::from_public_transaction` never checked that the accounts touched in a program's output matched the caller-declared `message.account_ids` — no count check, no membership check, nothing like the privacy circuit's own `account_identities.len() == states_iter.len()` assertion. That gap is why no pre-existing public AMM test ever caught the clock account being dropped: the public path had no validation capable of catching a silently-dropped account at all; only the privacy circuit's stricter bookkeeping turned it into a hard failure. Worked around at the test-fixture level in the meantime (giving the fixture's clock account a non-default owner; see the AMM section). This also had a soundness implication beyond blocking AMM tests: because `clock` never reached `public_pre_states` on the privacy-preserving path, the host validator never checked the clock data a proof was generated against real chain state — a malicious prover could in principle have supplied an arbitrary timestamp as a private witness with nothing to catch it. This has been resolved by [PR 625](https://github.com/logos-blockchain/logos-execution-zone/pull/625), which closes both the test-blocking symptom and the soundness gap; see the Action items section. + +## Action items + +Every open or resolved issue raised in this report, grouped by which repo the fix lives in — +**Zones** owns `logos-execution-zone`, **Programs** owns `lez-programs`: + +| Owner | Item | Status | +|---|---|---| +| Zones | `is_authorized = false` blocks `PrivateUnauthorized` on signer-gated instructions | ✅ Resolved — [PR #621](https://github.com/logos-blockchain/logos-execution-zone/pull/621) | +| Zones | Public transactions don't detect a silently-dropped declared account | ✅ Resolved — [PR #625](https://github.com/logos-blockchain/logos-execution-zone/pull/625) | +| Zones | `OpenPosition`-shaped chain calls (re-authorizing the same account across chained calls) fail under the privacy circuit | 🔲 Open — proposed circuit revision, no PR yet | +| Zones | ATA/AMM/Stablecoin PDAs derive via `for_public_pda` only, blocking private PDA use — the public and private `AccountId` formulas are mutually exclusive for the same account, so a unified formula needs to be devised before programs can adopt it | 🔲 Open | +| Programs | AMM/Stablecoin reject any destination account that isn't already initialized, blocking fresh private destinations (3 tests) | 🔲 Open | +| Programs | ATA's `Transfer` rejects a non-default (fresh) recipient, blocking shield-style transfers into a brand-new private destination | 🔲 Open | + +Resolved items from Zones land in `dev` branch of `logos-execution-zone`. \ No newline at end of file diff --git a/programs/integration_tests/Cargo.toml b/programs/integration_tests/Cargo.toml index e968903b..d46022fa 100644 --- a/programs/integration_tests/Cargo.toml +++ b/programs/integration_tests/Cargo.toml @@ -10,6 +10,7 @@ workspace = true nssa = { workspace = true } nssa_core = { workspace = true, features = ["host", "test_utils"] } clock_core = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0" } +key_protocol = { git = "https://github.com/logos-blockchain/logos-execution-zone.git", tag = "v0.2.0" } amm_core = { workspace = true } token_core = { workspace = true } ata_core = { workspace = true } diff --git a/programs/integration_tests/src/lib.rs b/programs/integration_tests/src/lib.rs index 8b137891..a96269ae 100644 --- a/programs/integration_tests/src/lib.rs +++ b/programs/integration_tests/src/lib.rs @@ -1 +1,121 @@ +//! Shared account/key setup helpers for privacy-preserving integration tests. +use key_protocol::key_management::{ + group_key_holder::{GroupKeyHolder, SealingPublicKey}, + secret_holders::SecretSpendingKey, +}; +use nssa::SharedSecretKey; +use nssa_core::{ + account::AccountId, + encryption::{EphemeralPublicKey, ViewingPublicKey}, + EncryptedAccountData, InputAccountIdentity, MembershipProof, NullifierPublicKey, + NullifierSecretKey, +}; + +/// Builds a `PrivateUnauthorized` identity: a third party credits a fresh private account it +/// does not control (no `nsk`, `is_authorized` must be `false` on the paired pre-state). +pub fn private_unauthorized_identity( + npk: NullifierPublicKey, + vpk: &ViewingPublicKey, + output_index: u32, +) -> InputAccountIdentity { + InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, vpk), + npk, + ssk: SharedSecretKey::encapsulate_deterministic(vpk, &[0u8; 32], output_index).0, + identifier: 0, + } +} + +/// Builds a `PrivateAuthorizedInit` identity: the owner self-initializes a fresh private +/// account by supplying its own `nsk` directly (`is_authorized` must be `true`). +pub fn private_authorized_init_identity( + nsk: NullifierSecretKey, + vpk: &ViewingPublicKey, + output_index: u32, +) -> InputAccountIdentity { + let npk = NullifierPublicKey::from(&nsk); + InputAccountIdentity::PrivateAuthorizedInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, vpk), + ssk: SharedSecretKey::encapsulate_deterministic(vpk, &[0u8; 32], output_index).0, + nsk, + identifier: 0, + } +} + +/// Builds a `PrivateAuthorizedUpdate` identity: spends/credits an *existing* private account, +/// requiring its own `nsk` and a membership proof of its current committed state. +pub fn private_authorized_update_identity( + nsk: NullifierSecretKey, + vpk: &ViewingPublicKey, + membership_proof: MembershipProof, + output_index: u32, +) -> InputAccountIdentity { + let npk = NullifierPublicKey::from(&nsk); + InputAccountIdentity::PrivateAuthorizedUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&npk, vpk), + ssk: SharedSecretKey::encapsulate_deterministic(vpk, &[0u8; 32], output_index).0, + nsk, + membership_proof, + identifier: 0, + } +} + +/// "Alice": creates a shared private account's `GroupKeyHolder` (Group Master Secret) and +/// derives its public identity. The GMS itself never leaves this struct — other parties only +/// ever receive it through [`GroupOwner::admit_member`]'s real seal/unseal ML-KEM-768 handshake, +/// never by handing over key material directly. +pub struct GroupOwner { + holder: GroupKeyHolder, + derivation_seed: [u8; 32], + pub npk: NullifierPublicKey, + pub vpk: ViewingPublicKey, + pub id: AccountId, +} + +impl GroupOwner { + /// Creates the group and derives the shared account's public identity from + /// `derivation_seed`. + #[must_use] + pub fn new(derivation_seed: [u8; 32]) -> Self { + let holder = GroupKeyHolder::new(); + 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 id = AccountId::for_regular_private_account(&npk, 0); + Self { + holder, + derivation_seed, + npk, + vpk, + id, + } + } + + /// "Bob": distributes the GMS to a new member via the real seal/unseal handshake and + /// returns that member's independently re-derived secret key — the member never touches + /// this `GroupOwner`'s `GroupKeyHolder`, only the sealed bytes. + #[must_use] + pub fn admit_member(&self) -> NullifierSecretKey { + let member_sealing_keys = SecretSpendingKey([9_u8; 32]).produce_private_key_holder(None); + let member_sealing_vpk = member_sealing_keys.generate_viewing_public_key(); + let member_sealing_vsk = member_sealing_keys.viewing_secret_key; + let sealed_gms = self.holder.seal_for(&SealingPublicKey::from_bytes( + member_sealing_vpk.to_bytes().to_vec(), + )); + let member_holder = GroupKeyHolder::unseal(&sealed_gms, &member_sealing_vsk) + .expect("member must unseal the GMS"); + + let member_keys = member_holder.derive_keys_for_shared_account(&self.derivation_seed); + let member_nsk = member_keys.nullifier_secret_key; + assert_eq!( + member_keys.generate_nullifier_public_key(), + self.npk, + "member must derive the identical npk as the group owner from the shared GMS" + ); + member_nsk + } +} diff --git a/programs/integration_tests/tests/amm.rs b/programs/integration_tests/tests/amm.rs index af711aa7..b827a37a 100644 --- a/programs/integration_tests/tests/amm.rs +++ b/programs/integration_tests/tests/amm.rs @@ -3,23 +3,89 @@ reason = "integration fixtures use fixed balances to assert AMM state transitions" )] +use std::collections::HashMap; + use amm_core::{ PoolDefinition, FEE_TIER_BPS_1, FEE_TIER_BPS_100, FEE_TIER_BPS_30, FEE_TIER_BPS_5, MINIMUM_LIQUIDITY, }; use clock_core::{ClockAccountData, CLOCK_01_PROGRAM_ACCOUNT_ID}; +use integration_tests::{ + private_authorized_init_identity, private_authorized_update_identity, + private_unauthorized_identity, +}; use nssa::{ error::LeeError, + execute_and_prove, + privacy_preserving_transaction::{ + circuit::ProgramWithDependencies, Message, PrivacyPreservingTransaction, WitnessSet, + }, + program::Program, program_deployment_transaction::{self, ProgramDeploymentTransaction}, public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, }; -use nssa_core::account::{Account, AccountId, Data, Nonce}; +use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, + encryption::ViewingPublicKey, + Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, NullifierSecretKey, +}; use token_core::{TokenDefinition, TokenHolding}; struct Keys; struct Ids; struct Balances; struct Accounts; +struct PrivateKeys; + +impl PrivateKeys { + fn user_a_nsk() -> NullifierSecretKey { + [161; 32] + } + + fn user_a_npk() -> NullifierPublicKey { + NullifierPublicKey::from(&Self::user_a_nsk()) + } + + fn user_a_vpk() -> ViewingPublicKey { + ViewingPublicKey::from_seed(&[171; 32], &[172; 32]) + } + + fn user_a_id() -> AccountId { + AccountId::for_regular_private_account(&Self::user_a_npk(), 0) + } + + fn user_lp_nsk() -> NullifierSecretKey { + [162; 32] + } + + fn user_lp_npk() -> NullifierPublicKey { + NullifierPublicKey::from(&Self::user_lp_nsk()) + } + + fn user_lp_vpk() -> ViewingPublicKey { + ViewingPublicKey::from_seed(&[173; 32], &[174; 32]) + } + + fn user_lp_id() -> AccountId { + AccountId::for_regular_private_account(&Self::user_lp_npk(), 0) + } + + fn user_b_nsk() -> NullifierSecretKey { + [163; 32] + } + + fn user_b_npk() -> NullifierPublicKey { + NullifierPublicKey::from(&Self::user_b_nsk()) + } + + fn user_b_vpk() -> ViewingPublicKey { + ViewingPublicKey::from_seed(&[175; 32], &[176; 32]) + } + + fn user_b_id() -> AccountId { + AccountId::for_regular_private_account(&Self::user_b_npk(), 0) + } +} impl Keys { fn user_a() -> PrivateKey { @@ -1677,19 +1743,16 @@ fn amm_create_price_observations_without_current_tick_account_fails() { /// Advances the canonical 1-block clock to `timestamp` by writing the clock account directly into /// state. `RecordTick` reads this account (`CLOCK_01_PROGRAM_ACCOUNT_ID`), so the TWAP tests use it /// to simulate the passage of time between observations. -/// -/// rc6 moved the clock program out of `nssa` into the separate system-programs crate (gated behind -/// the guest-building `artifacts` feature), so the clock can no longer be ticked by submitting a -/// real clock transaction here. Instead we set the account state directly via -/// `force_insert_account`, matching how the upstream rc6 state-machine tests seed accounts. #[cfg(test)] fn advance_clock(state: &mut V03State, timestamp: u64) { + let clock_id: nssa_core::program::ProgramId = [42_u32; 8]; let data = ClockAccountData { block_id: 0, timestamp, } .to_bytes(); let clock_account = Account { + program_owner: clock_id, data: Data::try_from(data).expect("clock account data fits"), ..Account::default() }; @@ -3038,3 +3101,1434 @@ fn amm_add_liquidity_after_fee_accrual() { 6_437 ); } + +fn amm_program_instance() -> Program { + Program::new(amm_methods::AMM_ELF.to_vec().into()).expect("valid amm ELF") +} + +fn token_program_instance() -> Program { + Program::new(token_methods::TOKEN_ELF.to_vec().into()).expect("valid token ELF") +} + +fn twap_oracle_program_instance() -> Program { + Program::new(twap_oracle_methods::TWAP_ORACLE_ELF.to_vec().into()) + .expect("valid twap oracle ELF") +} + +fn amm_with_deps() -> ProgramWithDependencies { + ProgramWithDependencies::new( + amm_program_instance(), + HashMap::from([ + (Ids::token_program(), token_program_instance()), + (Ids::twap_oracle_program(), twap_oracle_program_instance()), + ]), + ) +} + +#[test] +fn amm_swap_a_to_b_private_user_holding() { + let mut state = state_for_amm_tests(); + + let user_a_nsk = PrivateKeys::user_a_nsk(); + let user_a_vpk = PrivateKeys::user_a_vpk(); + let user_a_id = PrivateKeys::user_a_id(); + let user_a_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_init(), + }), + nonce: Nonce::private_account_nonce_init(&user_a_id), + }; + state = state.with_private_accounts([( + Commitment::new(&user_a_id, &user_a_account), + Nullifier::for_account_initialization(&user_a_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&user_a_id, &user_a_account)) + .expect("user_a's commitment must be in the set"); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let user_a_pre = AccountWithMetadata::new(user_a_account, true, user_a_id); + let user_b_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_b()), true, Ids::user_b()); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::SwapExactInput { + swap_amount_in: Balances::swap_amount_in(), + min_amount_out: Balances::swap_min_out(), + token_definition_id_in: Ids::token_a_definition(), + deadline: u64::MAX, + }; + + let (output, proof) = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + user_a_pre, + user_b_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(user_a_nsk, &user_a_vpk, membership_proof, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ) + .expect("SwapExactInput with a private user holding must succeed now that the clock account is properly owned"); + + let message = Message::try_from_circuit_output( + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![current_nonce(&state, Ids::user_b())], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::user_b()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_swap_2() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_swap_2() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_swap_2() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding_swap_2() + ); + + let user_a_nonce_after = + Nonce::private_account_nonce_init(&user_a_id).private_account_nonce_increment(&user_a_nsk); + let new_user_a_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_swap_2(), + }), + nonce: user_a_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_a_id, &new_user_a_account)) + .is_some()); +} + +/// Swap that pays out to `PrivateUnauthorized` is prohibited: both swap legs must be signers, +/// which `PrivateUnauthorized` (no `nsk`) can never be. +#[test] +fn amm_swap_a_to_b_private_unauthorized_destination_is_not_expressible() { + let state = state_for_amm_tests(); + + let user_b_npk = PrivateKeys::user_b_npk(); + let user_b_vpk = PrivateKeys::user_b_vpk(); + let user_b_id = PrivateKeys::user_b_id(); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let user_a_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_a()), true, Ids::user_a()); + let user_b_pre = AccountWithMetadata::new(Account::default(), false, user_b_id); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::SwapExactInput { + swap_amount_in: Balances::swap_amount_in(), + min_amount_out: Balances::swap_min_out(), + token_definition_id_in: Ids::token_a_definition(), + deadline: u64::MAX, + }; + + let result = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + user_a_pre, + user_b_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_unauthorized_identity(user_b_npk, &user_b_vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ); + + let err = result.expect_err( + "SwapExactInput must be rejected: user_holding_b must be a signer per the guest ABI, \ + which a PrivateUnauthorized identity (no nsk, unauthorized by construction) can never \ + satisfy", + ); + let message = format!("{err:?}"); + assert!( + message.contains("must be a signer"), + "expected the guest ABI's signer requirement on user_holding_b, got a different \ + error: {message}" + ); +} + +/// Swap that pays out to `PrivateAuthorizedInit` is prohibited. Payment +/// is only permitted to already initialized accounts. +#[test] +fn amm_swap_a_to_b_private_authorized_init_destination_is_not_expressible() { + let state = state_for_amm_tests(); + + let user_b_nsk = PrivateKeys::user_b_nsk(); + let user_b_vpk = PrivateKeys::user_b_vpk(); + let user_b_id = PrivateKeys::user_b_id(); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let user_a_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_a()), true, Ids::user_a()); + let user_b_pre = AccountWithMetadata::new(Account::default(), true, user_b_id); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::SwapExactInput { + swap_amount_in: Balances::swap_amount_in(), + min_amount_out: Balances::swap_min_out(), + token_definition_id_in: Ids::token_a_definition(), + deadline: u64::MAX, + }; + + let result = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + user_a_pre, + user_b_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_init_identity(user_b_nsk, &user_b_vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ); + + let err = result.expect_err( + "SwapExactInput must be rejected by the AMM program itself: user_holding_b must already \ + be initialized and owned by the configured Token Program before any chained call or the \ + privacy-preserving circuit is ever reached", + ); + let message = format!("{err:?}"); + assert!( + message.contains("User Token B holding must be owned by the configured Token Program"), + "expected the AMM program's own initialized-destination precondition, got a different \ + error: {message}" + ); +} + +#[test] +fn amm_swap_exact_output_private_user_holding() { + let mut state = state_for_amm_tests(); + + let user_a_nsk = PrivateKeys::user_a_nsk(); + let user_a_vpk = PrivateKeys::user_a_vpk(); + let user_a_id = PrivateKeys::user_a_id(); + let user_a_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_init(), + }), + nonce: Nonce::private_account_nonce_init(&user_a_id), + }; + state = state.with_private_accounts([( + Commitment::new(&user_a_id, &user_a_account), + Nullifier::for_account_initialization(&user_a_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&user_a_id, &user_a_account)) + .expect("user_a's commitment must be in the set"); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let user_a_pre = AccountWithMetadata::new(user_a_account, true, user_a_id); + let user_b_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_b()), true, Ids::user_b()); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::SwapExactOutput { + exact_amount_out: Balances::swap_min_out(), + max_amount_in: Balances::swap_amount_in(), + token_definition_id_in: Ids::token_a_definition(), + deadline: u64::MAX, + }; + + let (output, proof) = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + user_a_pre, + user_b_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(user_a_nsk, &user_a_vpk, membership_proof, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ) + .expect("SwapExactOutput with a private user holding must succeed now that the clock account is properly owned"); + + let message = Message::try_from_circuit_output( + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![current_nonce(&state, Ids::user_b())], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::user_b()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + // exact_amount_out = 200 (token B), max_amount_in = 1_000 (token A cap), 30 bps fee, against + // the fresh pool's initial reserves (5_000 A / 2_500 B): + // effective_in_min = ceil(5_000 * 200 / (2_500 - 200)) = ceil(1_000_000 / 2_300) = 435 + // deposit_amount = ceil(435 * 10_000 / (10_000 - 30)) = ceil(4_350_000 / 9_970) = 437 + let deposit_amount = 437_u128; + let withdraw_amount = Balances::swap_min_out(); + + let pool = pool_definition(&state.get_account_by_id(Ids::pool_definition())); + assert_eq!(pool.reserve_a, Balances::vault_a_init() + deposit_amount); + assert_eq!(pool.reserve_b, Balances::vault_b_init() - withdraw_amount); + match TokenHolding::try_from(&state.get_account_by_id(Ids::vault_a()).data) + .expect("valid holding") + { + TokenHolding::Fungible { balance, .. } => { + assert_eq!(balance, Balances::vault_a_init() + deposit_amount); + } + TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. } => { + panic!("expected Fungible vault holding") + } + } + match TokenHolding::try_from(&state.get_account_by_id(Ids::vault_b()).data) + .expect("valid holding") + { + TokenHolding::Fungible { balance, .. } => { + assert_eq!(balance, Balances::vault_b_init() - withdraw_amount); + } + TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. } => { + panic!("expected Fungible vault holding") + } + } + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::user_b_init() + withdraw_amount, + }), + nonce: Nonce(1), + } + ); + + let user_a_nonce_after = + Nonce::private_account_nonce_init(&user_a_id).private_account_nonce_increment(&user_a_nsk); + let new_user_a_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_init() - deposit_amount, + }), + nonce: user_a_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_a_id, &new_user_a_account)) + .is_some()); +} + +#[test] +fn amm_add_liquidity_private_lp_holding() { + let mut state = state_for_amm_tests(); + + let user_lp_nsk = PrivateKeys::user_lp_nsk(); + let user_lp_vpk = PrivateKeys::user_lp_vpk(); + let user_lp_id = PrivateKeys::user_lp_id(); + let user_lp_initial_balance = 500_u128; + let user_lp_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_lp_definition(), + balance: user_lp_initial_balance, + }), + nonce: Nonce::private_account_nonce_init(&user_lp_id), + }; + state = state.with_private_accounts([( + Commitment::new(&user_lp_id, &user_lp_account), + Nullifier::for_account_initialization(&user_lp_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&user_lp_id, &user_lp_account)) + .expect("user_lp's commitment must be in the set"); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let token_lp_definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_lp_definition()), + false, + Ids::token_lp_definition(), + ); + let user_a_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_a()), true, Ids::user_a()); + let user_b_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_b()), true, Ids::user_b()); + let user_lp_pre = AccountWithMetadata::new(user_lp_account, true, user_lp_id); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::AddLiquidity { + min_amount_liquidity: Balances::add_min_lp(), + max_amount_to_add_token_a: Balances::add_max_a(), + max_amount_to_add_token_b: Balances::add_max_b(), + deadline: u64::MAX, + }; + + let (output, proof) = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + token_lp_definition_pre, + user_a_pre, + user_b_pre, + user_lp_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(user_lp_nsk, &user_lp_vpk, membership_proof, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ) + .expect("AddLiquidity with a private LP holding must succeed now that the clock account is properly owned"); + + let message = Message::try_from_circuit_output( + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![ + current_nonce(&state, Ids::user_a()), + current_nonce(&state, Ids::user_b()), + ], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::user_a(), &Keys::user_b()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_add() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_add() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_add() + ); + assert_eq!( + state.get_account_by_id(Ids::token_lp_definition()), + Accounts::token_lp_definition_add() + ); + assert_eq!( + state.get_account_by_id(Ids::user_a()), + Accounts::user_a_holding_add() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding_add() + ); + + // Minted LP = post-add total supply - pre-add total supply, independent of who holds it. + let minted_lp = Balances::token_lp_supply_add() - Balances::pool_lp_supply_init(); + let user_lp_nonce_after = Nonce::private_account_nonce_init(&user_lp_id) + .private_account_nonce_increment(&user_lp_nsk); + let new_user_lp_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_lp_definition(), + balance: user_lp_initial_balance + minted_lp, + }), + nonce: user_lp_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_lp_id, &new_user_lp_account)) + .is_some()); +} + +#[test] +fn amm_remove_liquidity_private_lp_holding() { + let mut state = state_for_amm_tests(); + + let user_lp_nsk = PrivateKeys::user_lp_nsk(); + let user_lp_vpk = PrivateKeys::user_lp_vpk(); + let user_lp_id = PrivateKeys::user_lp_id(); + let user_lp_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_lp_definition(), + balance: Balances::remove_lp(), + }), + nonce: Nonce::private_account_nonce_init(&user_lp_id), + }; + state = state.with_private_accounts([( + Commitment::new(&user_lp_id, &user_lp_account), + Nullifier::for_account_initialization(&user_lp_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&user_lp_id, &user_lp_account)) + .expect("user_lp's commitment must be in the set"); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let token_lp_definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_lp_definition()), + false, + Ids::token_lp_definition(), + ); + let user_a_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_a()), false, Ids::user_a()); + let user_b_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_b()), false, Ids::user_b()); + let user_lp_pre = AccountWithMetadata::new(user_lp_account, true, user_lp_id); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::RemoveLiquidity { + remove_liquidity_amount: Balances::remove_lp(), + min_amount_to_remove_token_a: Balances::remove_min_a(), + min_amount_to_remove_token_b: Balances::remove_min_b(), + deadline: u64::MAX, + }; + + let (output, proof) = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + token_lp_definition_pre, + user_a_pre, + user_b_pre, + user_lp_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(user_lp_nsk, &user_lp_vpk, membership_proof, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ) + .expect("RemoveLiquidity with a private LP holding must succeed now that the clock account is properly owned"); + + let message = Message::try_from_circuit_output( + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_remove() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_remove() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_remove() + ); + assert_eq!( + state.get_account_by_id(Ids::token_lp_definition()), + Accounts::token_lp_definition_remove() + ); + assert_eq!( + state.get_account_by_id(Ids::user_a()), + Accounts::user_a_holding_remove() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding_remove() + ); + + // user_lp burned its entire private balance (remove_liquidity_amount == its full holding). + let user_lp_nonce_after = Nonce::private_account_nonce_init(&user_lp_id) + .private_account_nonce_increment(&user_lp_nsk); + let new_user_lp_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_lp_definition(), + balance: 0, + }), + nonce: user_lp_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_lp_id, &new_user_lp_account)) + .is_some()); +} + +#[test] +fn amm_remove_liquidity_private_new_user_holdings_is_not_expressible() { + let state = state_for_amm_tests(); + + let user_a_npk = PrivateKeys::user_a_npk(); + let user_a_vpk = PrivateKeys::user_a_vpk(); + let user_a_id = PrivateKeys::user_a_id(); + let user_b_npk = PrivateKeys::user_b_npk(); + let user_b_vpk = PrivateKeys::user_b_vpk(); + let user_b_id = PrivateKeys::user_b_id(); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let token_lp_definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_lp_definition()), + false, + Ids::token_lp_definition(), + ); + let user_a_pre = AccountWithMetadata::new(Account::default(), false, user_a_id); + let user_b_pre = AccountWithMetadata::new(Account::default(), false, user_b_id); + let user_lp_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::user_lp()), + true, + Ids::user_lp(), + ); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::RemoveLiquidity { + remove_liquidity_amount: Balances::remove_lp(), + min_amount_to_remove_token_a: Balances::remove_min_a(), + min_amount_to_remove_token_b: Balances::remove_min_b(), + deadline: u64::MAX, + }; + + let result = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + token_lp_definition_pre, + user_a_pre, + user_b_pre, + user_lp_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_unauthorized_identity(user_a_npk, &user_a_vpk, 0), + private_unauthorized_identity(user_b_npk, &user_b_vpk, 1), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ); + + let err = result.expect_err( + "RemoveLiquidity must be rejected by the AMM program itself: user_holding_a/b must \ + already be initialized and owned by the configured Token Program before any chained \ + call or the privacy-preserving circuit is ever reached", + ); + let message = format!("{err:?}"); + assert!( + message.contains("User Token A holding must be owned by the configured Token Program"), + "expected the AMM program's own initialized-destination precondition, got a different \ + error: {message}" + ); +} + +#[test] +fn amm_add_liquidity_private_user_holdings() { + let mut state = state_for_amm_tests(); + + let user_a_nsk = PrivateKeys::user_a_nsk(); + let user_a_vpk = PrivateKeys::user_a_vpk(); + let user_a_id = PrivateKeys::user_a_id(); + let user_a_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_init(), + }), + nonce: Nonce::private_account_nonce_init(&user_a_id), + }; + + let user_b_nsk = PrivateKeys::user_b_nsk(); + let user_b_vpk = PrivateKeys::user_b_vpk(); + let user_b_id = PrivateKeys::user_b_id(); + let user_b_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::user_b_init(), + }), + nonce: Nonce::private_account_nonce_init(&user_b_id), + }; + + state = state.with_private_accounts([ + ( + Commitment::new(&user_a_id, &user_a_account), + Nullifier::for_account_initialization(&user_a_id), + ), + ( + Commitment::new(&user_b_id, &user_b_account), + Nullifier::for_account_initialization(&user_b_id), + ), + ]); + let user_a_membership_proof = state + .get_proof_for_commitment(&Commitment::new(&user_a_id, &user_a_account)) + .expect("user_a's commitment must be in the set"); + let user_b_membership_proof = state + .get_proof_for_commitment(&Commitment::new(&user_b_id, &user_b_account)) + .expect("user_b's commitment must be in the set"); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let token_lp_definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_lp_definition()), + false, + Ids::token_lp_definition(), + ); + let user_a_pre = AccountWithMetadata::new(user_a_account, true, user_a_id); + let user_b_pre = AccountWithMetadata::new(user_b_account, true, user_b_id); + let user_lp_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::user_lp()), + false, + Ids::user_lp(), + ); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::AddLiquidity { + min_amount_liquidity: Balances::add_min_lp(), + max_amount_to_add_token_a: Balances::add_max_a(), + max_amount_to_add_token_b: Balances::add_max_b(), + deadline: u64::MAX, + }; + + let (output, proof) = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + token_lp_definition_pre, + user_a_pre, + user_b_pre, + user_lp_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(user_a_nsk, &user_a_vpk, user_a_membership_proof, 0), + private_authorized_update_identity(user_b_nsk, &user_b_vpk, user_b_membership_proof, 1), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ) + .expect("AddLiquidity with private deposit holdings must succeed now that the clock account is properly owned"); + + let message = Message::try_from_circuit_output( + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::user_lp(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_add() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_add() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_add() + ); + assert_eq!( + state.get_account_by_id(Ids::token_lp_definition()), + Accounts::token_lp_definition_add() + ); + // user_lp is public and already seeded by state_for_amm_tests() (Accounts::user_lp_holding(), + // balance Balances::user_lp_init()); it just receives the same minted LP as the all-public + // amm_add_liquidity test, landing on the same post-state. + assert_eq!( + state.get_account_by_id(Ids::user_lp()), + Accounts::user_lp_holding_add() + ); + + let user_a_nonce_after = + Nonce::private_account_nonce_init(&user_a_id).private_account_nonce_increment(&user_a_nsk); + let new_user_a_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_a_definition(), + balance: Balances::user_a_init() - Balances::add_max_a(), + }), + nonce: user_a_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_a_id, &new_user_a_account)) + .is_some()); + + let user_b_nonce_after = + Nonce::private_account_nonce_init(&user_b_id).private_account_nonce_increment(&user_b_nsk); + let new_user_b_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_b_definition(), + balance: Balances::user_b_init() - Balances::add_max_b(), + }), + nonce: user_b_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_b_id, &new_user_b_account)) + .is_some()); +} + +/// Initialized pool mints LP tokens to `PrivateAuthorizedInit`. +#[test] +fn amm_new_definition_private_initial_lp_holder() { + let mut state = state_for_amm_tests_with_new_def(); + state.force_insert_account(Ids::vault_a(), Accounts::vault_a_reinitializable()); + state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable()); + + let user_lp_nsk = PrivateKeys::user_lp_nsk(); + let user_lp_vpk = PrivateKeys::user_lp_vpk(); + let user_lp_id = PrivateKeys::user_lp_id(); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let token_lp_definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_lp_definition()), + false, + Ids::token_lp_definition(), + ); + let lp_lock_holding_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::lp_lock_holding()), + false, + Ids::lp_lock_holding(), + ); + let user_a_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_a()), true, Ids::user_a()); + let user_b_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_b()), true, Ids::user_b()); + let user_lp_pre = AccountWithMetadata::new(Account::default(), true, user_lp_id); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::NewDefinition { + token_a_amount: Balances::vault_a_init(), + token_b_amount: Balances::vault_b_init(), + fees: Balances::fee_tier(), + deadline: u64::MAX, + }; + + let (output, proof) = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + token_lp_definition_pre, + lp_lock_holding_pre, + user_a_pre, + user_b_pre, + user_lp_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_init_identity(user_lp_nsk, &user_lp_vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ) + .expect("NewDefinition with a private initial LP holder must succeed: the program's own precondition allows a fresh, authorized user_holding_lp"); + + let message = Message::try_from_circuit_output( + vec![ + Ids::config(), + Ids::pool_definition(), + Ids::vault_a(), + Ids::vault_b(), + Ids::token_lp_definition(), + Ids::lp_lock_holding(), + Ids::user_a(), + Ids::user_b(), + Ids::current_tick_account(), + CLOCK_01_PROGRAM_ACCOUNT_ID, + ], + vec![ + current_nonce(&state, Ids::user_a()), + current_nonce(&state, Ids::user_b()), + ], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::user_a(), &Keys::user_b()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::pool_definition()), + Accounts::pool_definition_new_init() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_a()), + Accounts::vault_a_init() + ); + assert_eq!( + state.get_account_by_id(Ids::vault_b()), + Accounts::vault_b_init() + ); + assert_eq!( + state.get_account_by_id(Ids::token_lp_definition()), + Accounts::token_lp_definition_new_init() + ); + assert_eq!( + state.get_account_by_id(Ids::lp_lock_holding()), + Accounts::lp_lock_holding_new_init() + ); + assert_eq!( + state.get_account_by_id(Ids::user_a()), + Accounts::user_a_holding_new_init() + ); + assert_eq!( + state.get_account_by_id(Ids::user_b()), + Accounts::user_b_holding_new_init() + ); + + let current_tick = state.get_account_by_id(Ids::current_tick_account()); + assert_eq!(current_tick.program_owner, Ids::twap_oracle_program()); + let tick_account = twap_oracle_core::CurrentTickAccount::try_from(¤t_tick.data) + .expect("current tick account must hold a valid CurrentTickAccount"); + let expected_tick = twap_oracle_core::price_to_tick(amm_core::spot_price_q64_64( + Balances::vault_a_init(), + Balances::vault_b_init(), + )); + assert_eq!(tick_account.tick, expected_tick); + + // A fresh PrivateAuthorizedInit account's nonce starts at private_account_nonce_init, not + // incremented — this is its first-ever commitment, not an update to an existing one. + let new_user_lp_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_lp_definition(), + balance: Balances::lp_user_init(), + }), + nonce: Nonce::private_account_nonce_init(&user_lp_id), + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&user_lp_id, &new_user_lp_account)) + .is_some()); +} + +/// Initialized pool cannot mint LP tokens to `PrivateUnauthorized`. +#[test] +fn amm_new_definition_private_unauthorized_lp_holder_is_not_expressible() { + let mut state = state_for_amm_tests_with_new_def(); + state.force_insert_account(Ids::vault_a(), Accounts::vault_a_reinitializable()); + state.force_insert_account(Ids::vault_b(), Accounts::vault_b_reinitializable()); + + let user_lp_npk = PrivateKeys::user_lp_npk(); + let user_lp_vpk = PrivateKeys::user_lp_vpk(); + let user_lp_id = PrivateKeys::user_lp_id(); + + let config_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::config()), false, Ids::config()); + let pool_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::pool_definition()), + false, + Ids::pool_definition(), + ); + let vault_a_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_a()), + false, + Ids::vault_a(), + ); + let vault_b_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::vault_b()), + false, + Ids::vault_b(), + ); + let token_lp_definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_lp_definition()), + false, + Ids::token_lp_definition(), + ); + let lp_lock_holding_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::lp_lock_holding()), + false, + Ids::lp_lock_holding(), + ); + let user_a_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_a()), true, Ids::user_a()); + let user_b_pre = + AccountWithMetadata::new(state.get_account_by_id(Ids::user_b()), true, Ids::user_b()); + let user_lp_pre = AccountWithMetadata::new(Account::default(), false, user_lp_id); + let current_tick_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::current_tick_account()), + false, + Ids::current_tick_account(), + ); + let clock_pre = AccountWithMetadata::new( + state.get_account_by_id(CLOCK_01_PROGRAM_ACCOUNT_ID), + false, + CLOCK_01_PROGRAM_ACCOUNT_ID, + ); + + let instruction = amm_core::Instruction::NewDefinition { + token_a_amount: Balances::vault_a_init(), + token_b_amount: Balances::vault_b_init(), + fees: Balances::fee_tier(), + deadline: u64::MAX, + }; + + let result = execute_and_prove( + vec![ + config_pre, + pool_pre, + vault_a_pre, + vault_b_pre, + token_lp_definition_pre, + lp_lock_holding_pre, + user_a_pre, + user_b_pre, + user_lp_pre, + current_tick_pre, + clock_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_unauthorized_identity(user_lp_npk, &user_lp_vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &amm_with_deps(), + ); + + let err = result.expect_err( + "NewDefinition must be rejected: user_holding_lp must be a signer per the guest ABI, \ + which a PrivateUnauthorized identity (no nsk, unauthorized by construction) can never \ + satisfy", + ); + let message = format!("{err:?}"); + assert!( + message.contains("must be a signer"), + "expected the guest ABI's signer requirement on user_holding_lp, got a different \ + error: {message}" + ); +} diff --git a/programs/integration_tests/tests/ata.rs b/programs/integration_tests/tests/ata.rs index 6e5d016a..32c45e9a 100644 --- a/programs/integration_tests/tests/ata.rs +++ b/programs/integration_tests/tests/ata.rs @@ -1,6 +1,9 @@ use std::collections::HashMap; use ata_core::{compute_ata_seed, get_associated_token_account_id}; +use integration_tests::{ + private_authorized_init_identity, private_unauthorized_identity, GroupOwner, +}; use nssa::{ execute_and_prove, privacy_preserving_transaction::{ @@ -13,7 +16,7 @@ use nssa::{ use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, encryption::{EphemeralPublicKey, ViewingPublicKey}, - EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, NullifierSecretKey, + Commitment, EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, NullifierSecretKey, }; use token_core::{TokenDefinition, TokenHolding}; @@ -592,3 +595,775 @@ fn ata_create_from_private_owner() { } ); } + +/// ATA cannot be created as a private account. +#[test] +fn ata_create_private_ata_holding_is_not_expressible() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + + let owner_id = Ids::owner(); + let owner_account = state.get_account_by_id(owner_id); + + // Fresh personal npk/vpk for the ATA holding's privacy identity — distinct from the + // owner's plain public keypair, which only supplies the seed input. + let ata_nsk: NullifierSecretKey = [21u8; 32]; + let ata_npk = NullifierPublicKey::from(&ata_nsk); + let ata_vpk = ViewingPublicKey::from_seed(&[41u8; 32], &[42u8; 32]); + + // Address stays the *standard* public-PDA formula ATA always uses — only its state + // privacy is under test, not its address derivation. + let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); + let ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + + let owner_pre = AccountWithMetadata::new(owner_account, true, owner_id); + let def_pre = AccountWithMetadata::new( + Accounts::token_definition_init(), + false, + Ids::token_definition(), + ); + let ata_pre = AccountWithMetadata::new(Account::default(), false, ata_id); + + let instruction = ata_core::Instruction::Create { + token_program_id: Ids::token_program(), + }; + let instruction_data = Program::serialize_instruction(instruction).unwrap(); + + let shared_secret = SharedSecretKey::encapsulate_deterministic(&ata_vpk, &[0u8; 32], 0).0; + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let result = execute_and_prove( + vec![owner_pre, def_pre, ata_pre], + instruction_data, + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::PrivatePdaInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&ata_npk, &ata_vpk), + npk: ata_npk, + ssk: shared_secret, + identifier: 0, + seed: None, + }, + ], + &program_with_deps, + ); + + let err = result.expect_err( + "a private-PDA ATA holding must be rejected: its account id can never satisfy both \ + ATA's own for_public_pda address check and PrivatePdaInit's for_private_pda binding \ + requirement simultaneously", + ); + let message = format!("{err:?}"); + assert!( + message.contains("has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds"), + "expected the private-PDA binding rejection, got a different error: {message}" + ); +} + +/// Verifies ATA account can be used to transfer to a private account. +#[test] +fn ata_transfer_to_existing_private_recipient() { + let mut state = state_for_ata_tests(); + + // A throwaway public holder to shield from directly via Token — bypassing ATA entirely, + // since ATA::Transfer cannot originate a fresh private recipient (see doc comment above). + let shield_source_key = PrivateKey::try_new([77u8; 32]).expect("valid private key"); + let shield_source_id = AccountId::from(&PublicKey::new_from_private_key(&shield_source_key)); + state.force_insert_account( + shield_source_id, + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128, + }), + nonce: Nonce(0), + }, + ); + + let recipient_nsk: NullifierSecretKey = [51u8; 32]; + let recipient_npk = NullifierPublicKey::from(&recipient_nsk); + let recipient_vpk = ViewingPublicKey::from_seed(&[61u8; 32], &[62u8; 32]); + let recipient_id = AccountId::for_regular_private_account(&recipient_npk, 0); + + let shield_amount = 500_000_u128; + let source_pre = AccountWithMetadata::new( + state.get_account_by_id(shield_source_id), + true, + shield_source_id, + ); + let fresh_recipient_pre = AccountWithMetadata::new(Account::default(), false, recipient_id); + let shield_secret = SharedSecretKey::encapsulate_deterministic(&recipient_vpk, &[0u8; 32], 0).0; + + let token_program_for_shield = Program::new(token_methods::TOKEN_ELF.to_vec().into()) + .expect("valid token ELF") + .into(); + let (shield_output, shield_proof) = execute_and_prove( + vec![source_pre, fresh_recipient_pre], + Program::serialize_instruction(token_core::Instruction::Transfer { + amount_to_transfer: shield_amount, + }) + .unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::PrivateUnauthorized { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&recipient_npk, &recipient_vpk), + npk: recipient_npk, + ssk: shield_secret, + identifier: 0, + }, + ], + &token_program_for_shield, + ) + .unwrap(); + let shield_message = + Message::try_from_circuit_output(vec![shield_source_id], vec![Nonce(0)], shield_output) + .unwrap(); + let shield_witness = + WitnessSet::for_message(&shield_message, shield_proof, &[&shield_source_key]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(shield_message, shield_witness), + 0, + 0, + ) + .unwrap(); + + let recipient_after_shield = Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: shield_amount, + }), + nonce: Nonce::private_account_nonce_init(&recipient_id), + }; + assert!( + state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &recipient_after_shield)) + .is_some(), + "shield setup must land before the ATA transfer under test" + ); + + // Now the actual test: owner's ATA sends more into that now-existing private recipient. + let owner_id = Ids::owner(); + let owner_account = state.get_account_by_id(owner_id); + let sender_ata_id = Ids::owner_ata(); + let sender_ata_account = state.get_account_by_id(sender_ata_id); + + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &recipient_after_shield)) + .expect("recipient's commitment must be in the set"); + let transfer_secret = + SharedSecretKey::encapsulate_deterministic(&recipient_vpk, &[0u8; 32], 0).0; + + let owner_pre = AccountWithMetadata::new(owner_account, true, owner_id); + let sender_ata_pre = AccountWithMetadata::new(sender_ata_account, false, sender_ata_id); + let recipient_pre = + AccountWithMetadata::new(recipient_after_shield.clone(), true, recipient_id); + + let ata_transfer_amount = 200_000_u128; + let instruction = ata_core::Instruction::Transfer { + token_program_id: Ids::token_program(), + amount: ata_transfer_amount, + }; + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, sender_ata_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::PrivateAuthorizedUpdate { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&recipient_npk, &recipient_vpk), + ssk: transfer_secret, + nsk: recipient_nsk, + membership_proof, + identifier: 0, + }, + ], + &program_with_deps, + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![owner_id, sender_ata_id], vec![Nonce(0)], output) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::owner_key()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(sender_ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128 - ata_transfer_amount, + }), + nonce: Nonce(0), + } + ); + + let recipient_nonce_after = Nonce::private_account_nonce_init(&recipient_id) + .private_account_nonce_increment(&recipient_nsk); + let recipient_after_ata_transfer = Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: shield_amount + ata_transfer_amount, + }), + nonce: recipient_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new( + &recipient_id, + &recipient_after_ata_transfer + )) + .is_some()); +} + +/// Private account owner can sign transactions. +#[test] +fn ata_burn_with_private_owner_signing() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + + let owner_nsk: NullifierSecretKey = [91u8; 32]; + let owner_npk = NullifierPublicKey::from(&owner_nsk); + let owner_vpk = ViewingPublicKey::from_seed(&[93u8; 32], &[94u8; 32]); + let owner_id = AccountId::for_regular_private_account(&owner_npk, 0); + + // The ATA holding must stay public (per the confirmed PDA finding), so it's seeded + // directly rather than via a real `Create` transaction. + let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); + let ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + let ata_account = Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128, + }), + nonce: Nonce(0), + }; + state.force_insert_account(ata_id, ata_account.clone()); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let ata_pre = AccountWithMetadata::new(ata_account, false, ata_id); + let def_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + + let burn_amount = 300_000_u128; + let instruction = ata_core::Instruction::Burn { + token_program_id: Ids::token_program(), + amount: burn_amount, + }; + + let shared_secret = SharedSecretKey::encapsulate_deterministic(&owner_vpk, &[0u8; 32], 0).0; + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, ata_pre, def_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::PrivateAuthorizedInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&owner_npk, &owner_vpk), + ssk: shared_secret, + nsk: owner_nsk, + identifier: 0, + }, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &program_with_deps, + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![ata_id, Ids::token_definition()], vec![], output) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128 - burn_amount, + }), + nonce: Nonce(0), + } + ); + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 - burn_amount, + metadata_id: None, + authority: None, + }), + nonce: Nonce(0), + } + ); + + let owner_expected = Account { + nonce: Nonce::private_account_nonce_init(&owner_id), + ..Account::default() + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&owner_id, &owner_expected)) + .is_some()); +} + +/// Group-owned variant of `ata_burn_with_private_owner_signing`: the GMS is distributed through +/// the real seal/unseal handshake, and it's Bob — not Alice, who created the group — who +/// self-initializes and signs the owner identity in the same transaction via +/// `PrivateAuthorizedInit`, then burns from the ATA holding through it. +#[test] +fn ata_group_owned_owner_signing() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + + let alice = GroupOwner::new([97_u8; 32]); + let owner_id = alice.id; + let bob_nsk = alice.admit_member(); + + // The ATA holding must stay public (per the confirmed PDA finding), so it's seeded + // directly rather than via a real `Create` transaction. + let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); + let ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + let ata_account = Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128, + }), + nonce: Nonce(0), + }; + state.force_insert_account(ata_id, ata_account.clone()); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let ata_pre = AccountWithMetadata::new(ata_account, false, ata_id); + let def_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + + let burn_amount = 300_000_u128; + let instruction = ata_core::Instruction::Burn { + token_program_id: Ids::token_program(), + amount: burn_amount, + }; + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, ata_pre, def_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + private_authorized_init_identity(bob_nsk, &alice.vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &program_with_deps, + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![ata_id, Ids::token_definition()], vec![], output) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128 - burn_amount, + }), + nonce: Nonce(0), + } + ); + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 - burn_amount, + metadata_id: None, + authority: None, + }), + nonce: Nonce(0), + } + ); + + let owner_expected = Account { + nonce: Nonce::private_account_nonce_init(&owner_id), + ..Account::default() + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&owner_id, &owner_expected)) + .is_some()); +} + +/// Private owner +#[test] +fn ata_transfer_with_private_owner_signing() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + state.force_insert_account(Ids::recipient_ata(), Accounts::recipient_ata_init()); + + let owner_nsk: NullifierSecretKey = [95u8; 32]; + let owner_npk = NullifierPublicKey::from(&owner_nsk); + let owner_vpk = ViewingPublicKey::from_seed(&[96u8; 32], &[97u8; 32]); + let owner_id = AccountId::for_regular_private_account(&owner_npk, 0); + + // The ATA holding must stay public (per the confirmed PDA finding), so it's seeded + // directly rather than via a real `Create` transaction. + let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); + let sender_ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + let sender_ata_account = Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128, + }), + nonce: Nonce(0), + }; + state.force_insert_account(sender_ata_id, sender_ata_account.clone()); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let sender_ata_pre = AccountWithMetadata::new(sender_ata_account, false, sender_ata_id); + let recipient_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::recipient_ata()), + false, + Ids::recipient_ata(), + ); + + let transfer_amount = 400_000_u128; + let instruction = ata_core::Instruction::Transfer { + token_program_id: Ids::token_program(), + amount: transfer_amount, + }; + + let shared_secret = SharedSecretKey::encapsulate_deterministic(&owner_vpk, &[0u8; 32], 0).0; + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, sender_ata_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::PrivateAuthorizedInit { + epk: EphemeralPublicKey(Vec::new()), + view_tag: EncryptedAccountData::compute_view_tag(&owner_npk, &owner_vpk), + ssk: shared_secret, + nsk: owner_nsk, + identifier: 0, + }, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &program_with_deps, + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![sender_ata_id, Ids::recipient_ata()], vec![], output) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(sender_ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128 - transfer_amount, + }), + nonce: Nonce(0), + } + ); + assert_eq!( + state.get_account_by_id(Ids::recipient_ata()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: transfer_amount, + }), + nonce: Nonce(0), + } + ); + + let owner_expected = Account { + nonce: Nonce::private_account_nonce_init(&owner_id), + ..Account::default() + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&owner_id, &owner_expected)) + .is_some()); +} + +/// Group transfer is possible with group members added after the ATA is initialized. +#[test] +fn ata_transfer_with_group_owned_owner_signing() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + state.force_insert_account(Ids::recipient_ata(), Accounts::recipient_ata_init()); + + let alice = GroupOwner::new([19_u8; 32]); + let owner_id = alice.id; + + // The ATA holding must stay public (per the confirmed PDA finding), so it's seeded + // directly rather than via a real `Create` transaction. + let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); + let sender_ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + let sender_ata_account = Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128, + }), + nonce: Nonce(0), + }; + state.force_insert_account(sender_ata_id, sender_ata_account.clone()); + + let bob_nsk = alice.admit_member(); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let sender_ata_pre = AccountWithMetadata::new(sender_ata_account, false, sender_ata_id); + let recipient_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::recipient_ata()), + false, + Ids::recipient_ata(), + ); + + let transfer_amount = 400_000_u128; + let instruction = ata_core::Instruction::Transfer { + token_program_id: Ids::token_program(), + amount: transfer_amount, + }; + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, sender_ata_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + private_authorized_init_identity(bob_nsk, &alice.vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &program_with_deps, + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![sender_ata_id, Ids::recipient_ata()], vec![], output) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(sender_ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 1_000_000_u128 - transfer_amount, + }), + nonce: Nonce(0), + } + ); + assert_eq!( + state.get_account_by_id(Ids::recipient_ata()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: transfer_amount, + }), + nonce: Nonce(0), + } + ); + + let owner_expected = Account { + nonce: Nonce::private_account_nonce_init(&owner_id), + ..Account::default() + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&owner_id, &owner_expected)) + .is_some()); +} + +#[test] +fn ata_create_from_group_owned_owner() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account(Ids::token_definition(), Accounts::token_definition_init()); + + let alice = GroupOwner::new([23_u8; 32]); + let owner_id = alice.id; + + let seed = compute_ata_seed(Ids::token_program(), owner_id, Ids::token_definition()); + let owner_ata_id = get_associated_token_account_id(&Ids::ata_program(), &seed); + + let owner_pre = AccountWithMetadata::new(Account::default(), false, owner_id); + let def_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let ata_pre = AccountWithMetadata::new(Account::default(), false, owner_ata_id); + + let instruction = ata_core::Instruction::Create { + token_program_id: Ids::token_program(), + }; + + let ata_program = Program::new(ata_methods::ATA_ELF.to_vec().into()).unwrap(); + let token_program = Program::new(token_methods::TOKEN_ELF.to_vec().into()).unwrap(); + let program_with_deps = ProgramWithDependencies::new( + ata_program, + HashMap::from([(Ids::token_program(), token_program)]), + ); + + let (output, proof) = execute_and_prove( + vec![owner_pre, def_pre, ata_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + private_unauthorized_identity(alice.npk, &alice.vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &program_with_deps, + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![Ids::token_definition(), owner_ata_id], + vec![], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(owner_ata_id), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance: 0_u128, + }), + nonce: Nonce(0), + } + ); +} diff --git a/programs/integration_tests/tests/stablecoin.rs b/programs/integration_tests/tests/stablecoin.rs index d72ad671..d67de42b 100644 --- a/programs/integration_tests/tests/stablecoin.rs +++ b/programs/integration_tests/tests/stablecoin.rs @@ -1,8 +1,23 @@ +use std::collections::HashMap; + +use integration_tests::{ + private_authorized_init_identity, private_authorized_update_identity, + private_unauthorized_identity, GroupOwner, +}; use nssa::{ + execute_and_prove, + privacy_preserving_transaction::{ + circuit::ProgramWithDependencies, Message, PrivacyPreservingTransaction, WitnessSet, + }, + program::Program, program_deployment_transaction::{self, ProgramDeploymentTransaction}, public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, }; -use nssa_core::account::{Account, AccountId, Data, Nonce}; +use nssa_core::{ + account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, + encryption::ViewingPublicKey, + Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, NullifierSecretKey, +}; use stablecoin_core::{compute_position_pda, compute_position_vault_pda, Position}; use token_core::{TokenDefinition, TokenHolding}; @@ -10,6 +25,41 @@ struct Keys; struct Ids; struct Balances; struct Accounts; +struct PrivateKeys; + +impl PrivateKeys { + fn destination_nsk() -> NullifierSecretKey { + [111; 32] + } + + fn destination_npk() -> NullifierPublicKey { + NullifierPublicKey::from(&Self::destination_nsk()) + } + + fn destination_vpk() -> ViewingPublicKey { + ViewingPublicKey::from_seed(&[141; 32], &[142; 32]) + } + + fn destination_id() -> AccountId { + AccountId::for_regular_private_account(&Self::destination_npk(), 0) + } + + fn stablecoin_holding_nsk() -> NullifierSecretKey { + [121; 32] + } + + fn stablecoin_holding_npk() -> NullifierPublicKey { + NullifierPublicKey::from(&Self::stablecoin_holding_nsk()) + } + + fn stablecoin_holding_vpk() -> ViewingPublicKey { + ViewingPublicKey::from_seed(&[151; 32], &[152; 32]) + } + + fn stablecoin_holding_id() -> AccountId { + AccountId::for_regular_private_account(&Self::stablecoin_holding_npk(), 0) + } +} impl Keys { fn owner() -> PrivateKey { @@ -398,3 +448,838 @@ fn stablecoin_repay_debt_burns_stablecoins_and_decreases_debt() { } } } + +fn stablecoin_program() -> Program { + Program::new(stablecoin_methods::STABLECOIN_ELF.to_vec().into()).expect("valid stablecoin ELF") +} + +fn token_program_instance() -> Program { + Program::new(token_methods::TOKEN_ELF.to_vec().into()).expect("valid token ELF") +} + +fn stablecoin_with_token_deps() -> ProgramWithDependencies { + ProgramWithDependencies::new( + stablecoin_program(), + HashMap::from([(Ids::token_program(), token_program_instance())]), + ) +} + +/// `OpenPosition` is blocked by the `privacy_preserving_circuit` due to the handling of +/// sibling chain calls of (uninitialized) private accounts. +#[test] +fn stablecoin_open_position_via_privacy_transaction_is_not_expressible() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + state.force_insert_account(Ids::user_holding(), Accounts::user_holding_init()); + + let owner_id = Ids::owner(); + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = AccountWithMetadata::new(Account::default(), false, position_id); + let vault_pre = AccountWithMetadata::new(Account::default(), false, vault_id); + let user_holding_pre = + AccountWithMetadata::new(Accounts::user_holding_init(), true, Ids::user_holding()); + let definition_pre = AccountWithMetadata::new( + Accounts::collateral_definition_init(), + false, + Ids::collateral_definition(), + ); + + let collateral_amount = Balances::collateral_deposit(); + let instruction = stablecoin_core::Instruction::OpenPosition { collateral_amount }; + + let result = execute_and_prove( + vec![ + owner_pre, + position_pre, + vault_pre, + user_holding_pre, + definition_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &stablecoin_with_token_deps(), + ); + + let err = result.expect_err( + "OpenPosition must be rejected by the privacy-preserving circuit: vault's second \ + chained-call occurrence declares is_authorized: false after already being marked \ + authorized by the first chained call's pda_seeds match", + ); + let message = format!("{err:?}"); + assert!( + message.contains("Inconsistent authorization for account"), + "expected the authorization-consistency rejection, got a different error: {message}" + ); +} + +/// `WithdrawCollateral` to private account (`PrivateAuthorized`; `nsk` is known). +#[test] +fn stablecoin_withdraw_collateral_private_destination() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + + let owner_id = Ids::owner(); + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let position_collateral = 500_000_u128; + let withdraw_amount = 200_000_u128; + + let position_account = Account { + program_owner: Ids::stablecoin_program(), + balance: 0, + data: Data::from(&Position { + collateral_vault_id: vault_id, + collateral_definition_id: Ids::collateral_definition(), + collateral_amount: position_collateral, + debt_amount: 0, + }), + nonce: Nonce(0), + }; + let vault_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: position_collateral, + }), + nonce: Nonce(0), + }; + state.force_insert_account(position_id, position_account); + state.force_insert_account(vault_id, vault_account); + + let destination_nsk = PrivateKeys::destination_nsk(); + let destination_vpk = PrivateKeys::destination_vpk(); + let destination_id = PrivateKeys::destination_id(); + let destination_initial_balance = 100_000_u128; + let destination_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: destination_initial_balance, + }), + nonce: Nonce::private_account_nonce_init(&destination_id), + }; + state = state.with_private_accounts([( + Commitment::new(&destination_id, &destination_account), + Nullifier::for_account_initialization(&destination_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&destination_id, &destination_account)) + .expect("destination's commitment must be in the set"); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = + AccountWithMetadata::new(state.get_account_by_id(position_id), false, position_id); + let vault_pre = AccountWithMetadata::new(state.get_account_by_id(vault_id), false, vault_id); + let destination_pre = + AccountWithMetadata::new(destination_account.clone(), true, destination_id); + + let instruction = stablecoin_core::Instruction::WithdrawCollateral { + amount: withdraw_amount, + }; + + let (output, proof) = execute_and_prove( + vec![owner_pre, position_pre, vault_pre, destination_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity( + destination_nsk, + &destination_vpk, + membership_proof, + 0, + ), + ], + &stablecoin_with_token_deps(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![owner_id, position_id, vault_id], + vec![Nonce(0)], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::owner()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let position = + Position::try_from(&state.get_account_by_id(position_id).data).expect("valid Position"); + assert_eq!( + position.collateral_amount, + position_collateral - withdraw_amount + ); + assert_eq!(position.debt_amount, 0); + + match TokenHolding::try_from(&state.get_account_by_id(vault_id).data).expect("valid holding") { + TokenHolding::Fungible { balance, .. } => { + assert_eq!(balance, position_collateral - withdraw_amount); + } + TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. } => { + panic!("expected Fungible vault holding") + } + } + + let destination_nonce_after = Nonce::private_account_nonce_init(&destination_id) + .private_account_nonce_increment(&destination_nsk); + let new_destination_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: destination_initial_balance + withdraw_amount, + }), + nonce: destination_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&destination_id, &new_destination_account)) + .is_some()); +} + +/// `WithdrawCollateral` blocks withdraws to private accounts (via private donations); +/// `PrivateUnauthorized` account initialization (e.g., `nsk` is not known) is not permitted +/// due to the assertion in `withdraw_collateral.rs` asserts `destination.account != +/// Account::default()` +#[test] +fn stablecoin_withdraw_collateral_to_new_private_destination_is_not_expressible() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + + let owner_id = Ids::owner(); + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let position_collateral = 500_000_u128; + let withdraw_amount = 200_000_u128; + + let position_account = Account { + program_owner: Ids::stablecoin_program(), + balance: 0, + data: Data::from(&Position { + collateral_vault_id: vault_id, + collateral_definition_id: Ids::collateral_definition(), + collateral_amount: position_collateral, + debt_amount: 0, + }), + nonce: Nonce(0), + }; + let vault_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: position_collateral, + }), + nonce: Nonce(0), + }; + state.force_insert_account(position_id, position_account); + state.force_insert_account(vault_id, vault_account); + + let destination_npk = PrivateKeys::destination_npk(); + let destination_vpk = PrivateKeys::destination_vpk(); + let destination_id = PrivateKeys::destination_id(); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = + AccountWithMetadata::new(state.get_account_by_id(position_id), false, position_id); + let vault_pre = AccountWithMetadata::new(state.get_account_by_id(vault_id), false, vault_id); + let destination_pre = AccountWithMetadata::new(Account::default(), false, destination_id); + + let instruction = stablecoin_core::Instruction::WithdrawCollateral { + amount: withdraw_amount, + }; + + let result = execute_and_prove( + vec![owner_pre, position_pre, vault_pre, destination_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_unauthorized_identity(destination_npk, &destination_vpk, 0), + ], + &stablecoin_with_token_deps(), + ); + + let err = result.expect_err( + "WithdrawCollateral must be rejected: destination is a brand-new (default) private \ + account, but withdraw_collateral.rs requires the destination to already be initialized", + ); + let message = format!("{err:?}"); + assert!( + message.contains("Destination must be initialized"), + "expected the destination-must-be-initialized rejection, got a different error: {message}" + ); +} + +#[test] +fn stablecoin_withdraw_collateral_group_owned_destination() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + + let owner_id = Ids::owner(); + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let position_collateral = 500_000_u128; + let withdraw_amount = 200_000_u128; + + let position_account = Account { + program_owner: Ids::stablecoin_program(), + balance: 0, + data: Data::from(&Position { + collateral_vault_id: vault_id, + collateral_definition_id: Ids::collateral_definition(), + collateral_amount: position_collateral, + debt_amount: 0, + }), + nonce: Nonce(0), + }; + let vault_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: position_collateral, + }), + nonce: Nonce(0), + }; + state.force_insert_account(position_id, position_account); + state.force_insert_account(vault_id, vault_account); + + // Alice creates the group and derives the shared destination's keys; Bob is admitted via + // the real seal/unseal handshake and independently re-derives the same keys. + let alice = GroupOwner::new([7_u8; 32]); + let bob_nsk = alice.admit_member(); + let destination_vpk = alice.vpk; + let destination_id = alice.id; + + let destination_initial_balance = 100_000_u128; + let destination_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: destination_initial_balance, + }), + nonce: Nonce::private_account_nonce_init(&destination_id), + }; + state = state.with_private_accounts([( + Commitment::new(&destination_id, &destination_account), + Nullifier::for_account_initialization(&destination_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&destination_id, &destination_account)) + .expect("destination's commitment must be in the set"); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = + AccountWithMetadata::new(state.get_account_by_id(position_id), false, position_id); + let vault_pre = AccountWithMetadata::new(state.get_account_by_id(vault_id), false, vault_id); + let destination_pre = + AccountWithMetadata::new(destination_account.clone(), true, destination_id); + + let instruction = stablecoin_core::Instruction::WithdrawCollateral { + amount: withdraw_amount, + }; + + let (output, proof) = execute_and_prove( + vec![owner_pre, position_pre, vault_pre, destination_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(bob_nsk, &destination_vpk, membership_proof, 0), + ], + &stablecoin_with_token_deps(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![owner_id, position_id, vault_id], + vec![Nonce(0)], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::owner()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let position = + Position::try_from(&state.get_account_by_id(position_id).data).expect("valid Position"); + assert_eq!( + position.collateral_amount, + position_collateral - withdraw_amount + ); + + let destination_nonce_after = Nonce::private_account_nonce_init(&destination_id) + .private_account_nonce_increment(&bob_nsk); + let new_destination_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: destination_initial_balance + withdraw_amount, + }), + nonce: destination_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&destination_id, &new_destination_account)) + .is_some()); +} + +#[test] +fn stablecoin_repay_debt_private_stablecoin_holding() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + state.force_insert_account( + Ids::stablecoin_definition(), + Accounts::stablecoin_definition_init(), + ); + + let owner_id = Ids::owner(); + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let position_collateral = Balances::collateral_deposit(); + let initial_debt = Balances::initial_debt(); + let repay_amount = Balances::debt_repay_amount(); + + let position_account = Account { + program_owner: Ids::stablecoin_program(), + balance: 0, + data: Data::from(&Position { + collateral_vault_id: vault_id, + collateral_definition_id: Ids::collateral_definition(), + collateral_amount: position_collateral, + debt_amount: initial_debt, + }), + nonce: Nonce(0), + }; + state.force_insert_account(position_id, position_account); + + let stablecoin_holding_nsk = PrivateKeys::stablecoin_holding_nsk(); + let stablecoin_holding_vpk = PrivateKeys::stablecoin_holding_vpk(); + let stablecoin_holding_id = PrivateKeys::stablecoin_holding_id(); + let initial_stablecoin_balance = Balances::user_stablecoin_holding_init(); + let stablecoin_holding_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::stablecoin_definition(), + balance: initial_stablecoin_balance, + }), + nonce: Nonce::private_account_nonce_init(&stablecoin_holding_id), + }; + state = state.with_private_accounts([( + Commitment::new(&stablecoin_holding_id, &stablecoin_holding_account), + Nullifier::for_account_initialization(&stablecoin_holding_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new( + &stablecoin_holding_id, + &stablecoin_holding_account, + )) + .expect("stablecoin holding's commitment must be in the set"); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = + AccountWithMetadata::new(state.get_account_by_id(position_id), false, position_id); + let definition_pre = AccountWithMetadata::new( + Accounts::stablecoin_definition_init(), + false, + Ids::stablecoin_definition(), + ); + let stablecoin_holding_pre = AccountWithMetadata::new( + stablecoin_holding_account.clone(), + true, + stablecoin_holding_id, + ); + + let instruction = stablecoin_core::Instruction::RepayDebt { + amount: repay_amount, + }; + + let (output, proof) = execute_and_prove( + vec![ + owner_pre, + position_pre, + definition_pre, + stablecoin_holding_pre, + ], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity( + stablecoin_holding_nsk, + &stablecoin_holding_vpk, + membership_proof, + 0, + ), + ], + &stablecoin_with_token_deps(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![owner_id, position_id, Ids::stablecoin_definition()], + vec![Nonce(0)], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::owner()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let position = + Position::try_from(&state.get_account_by_id(position_id).data).expect("valid Position"); + assert_eq!(position.debt_amount, initial_debt - repay_amount); + assert_eq!(position.collateral_amount, position_collateral); + + match TokenDefinition::try_from(&state.get_account_by_id(Ids::stablecoin_definition()).data) + .expect("valid TokenDefinition") + { + TokenDefinition::Fungible { total_supply, .. } => { + assert_eq!( + total_supply, + Balances::stablecoin_supply_init() - repay_amount + ); + } + _ => panic!("expected Fungible definition"), + } + + let stablecoin_holding_nonce_after = Nonce::private_account_nonce_init(&stablecoin_holding_id) + .private_account_nonce_increment(&stablecoin_holding_nsk); + let new_stablecoin_holding_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::stablecoin_definition(), + balance: initial_stablecoin_balance - repay_amount, + }), + nonce: stablecoin_holding_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new( + &stablecoin_holding_id, + &new_stablecoin_holding_account + )) + .is_some()); +} + +#[test] +fn stablecoin_repay_debt_group_owned_stablecoin_holding() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + state.force_insert_account( + Ids::stablecoin_definition(), + Accounts::stablecoin_definition_init(), + ); + + let owner_id = Ids::owner(); + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let position_collateral = Balances::collateral_deposit(); + let initial_debt = Balances::initial_debt(); + let repay_amount = Balances::debt_repay_amount(); + + let position_account = Account { + program_owner: Ids::stablecoin_program(), + balance: 0, + data: Data::from(&Position { + collateral_vault_id: vault_id, + collateral_definition_id: Ids::collateral_definition(), + collateral_amount: position_collateral, + debt_amount: initial_debt, + }), + nonce: Nonce(0), + }; + state.force_insert_account(position_id, position_account); + + // Alice creates the group and derives the shared stablecoin holding's keys; Bob is + // admitted via the real seal/unseal handshake and independently re-derives the same keys. + let alice = GroupOwner::new([7_u8; 32]); + let bob_nsk = alice.admit_member(); + let holding_vpk = alice.vpk; + let holding_id = alice.id; + + let initial_stablecoin_balance = Balances::user_stablecoin_holding_init(); + let holding_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::stablecoin_definition(), + balance: initial_stablecoin_balance, + }), + nonce: Nonce::private_account_nonce_init(&holding_id), + }; + state = state.with_private_accounts([( + Commitment::new(&holding_id, &holding_account), + Nullifier::for_account_initialization(&holding_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&Commitment::new(&holding_id, &holding_account)) + .expect("stablecoin holding's commitment must be in the set"); + + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = + AccountWithMetadata::new(state.get_account_by_id(position_id), false, position_id); + let definition_pre = AccountWithMetadata::new( + Accounts::stablecoin_definition_init(), + false, + Ids::stablecoin_definition(), + ); + let holding_pre = AccountWithMetadata::new(holding_account.clone(), true, holding_id); + + let instruction = stablecoin_core::Instruction::RepayDebt { + amount: repay_amount, + }; + + let (output, proof) = execute_and_prove( + vec![owner_pre, position_pre, definition_pre, holding_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + private_authorized_update_identity(bob_nsk, &holding_vpk, membership_proof, 0), + ], + &stablecoin_with_token_deps(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![owner_id, position_id, Ids::stablecoin_definition()], + vec![Nonce(0)], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::owner()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let position = + Position::try_from(&state.get_account_by_id(position_id).data).expect("valid Position"); + assert_eq!(position.debt_amount, initial_debt - repay_amount); + + let holding_nonce_after = + Nonce::private_account_nonce_init(&holding_id).private_account_nonce_increment(&bob_nsk); + let new_holding_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::stablecoin_definition(), + balance: initial_stablecoin_balance - repay_amount, + }), + nonce: holding_nonce_after, + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&holding_id, &new_holding_account)) + .is_some()); +} + +#[test] +fn stablecoin_group_owned_position_owner() { + let mut state = V03State::new(); + deploy_programs(&mut state); + state.force_insert_account( + Ids::collateral_definition(), + Accounts::collateral_definition_init(), + ); + state.force_insert_account(Ids::user_holding(), Accounts::user_holding_init()); + + // Alice creates the group and derives the shared owner identity's keys; Bob is admitted + // via the real seal/unseal handshake and independently re-derives the same keys. + let alice = GroupOwner::new([7_u8; 32]); + let bob_nsk = alice.admit_member(); + let owner_id = alice.id; + + // Position/vault addresses are derived from the group-owned owner_id — still ordinary + // public PDAs (the seed formula doesn't care whether owner_id is public or private), seeded + // directly since OpenPosition can't be routed through the privacy circuit at all. + let position_id = compute_position_pda( + Ids::stablecoin_program(), + owner_id, + Ids::collateral_definition(), + ); + let vault_id = compute_position_vault_pda(Ids::stablecoin_program(), position_id); + + let position_collateral = 500_000_u128; + let withdraw_amount = 200_000_u128; + let position_account = Account { + program_owner: Ids::stablecoin_program(), + balance: 0, + data: Data::from(&Position { + collateral_vault_id: vault_id, + collateral_definition_id: Ids::collateral_definition(), + collateral_amount: position_collateral, + debt_amount: 0, + }), + nonce: Nonce(0), + }; + let vault_account = Account { + program_owner: Ids::token_program(), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::collateral_definition(), + balance: position_collateral, + }), + nonce: Nonce(0), + }; + state.force_insert_account(position_id, position_account); + state.force_insert_account(vault_id, vault_account); + + // Bob self-initializes and signs the owner identity in the same transaction, then + // withdraws collateral through it. Destination stays public to isolate what's under test: + // only the owner identity's privacy/sharing, nothing else. + let owner_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + let position_pre = + AccountWithMetadata::new(state.get_account_by_id(position_id), false, position_id); + let vault_pre = AccountWithMetadata::new(state.get_account_by_id(vault_id), false, vault_id); + let destination_pre = + AccountWithMetadata::new(Accounts::user_holding_init(), false, Ids::user_holding()); + + let instruction = stablecoin_core::Instruction::WithdrawCollateral { + amount: withdraw_amount, + }; + + let (output, proof) = execute_and_prove( + vec![owner_pre, position_pre, vault_pre, destination_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + private_authorized_init_identity(bob_nsk, &alice.vpk, 0), + InputAccountIdentity::Public, + InputAccountIdentity::Public, + InputAccountIdentity::Public, + ], + &stablecoin_with_token_deps(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![position_id, vault_id, Ids::user_holding()], + vec![], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let position = + Position::try_from(&state.get_account_by_id(position_id).data).expect("valid Position"); + assert_eq!( + position.collateral_amount, + position_collateral - withdraw_amount + ); + + match TokenHolding::try_from(&state.get_account_by_id(Ids::user_holding()).data) + .expect("valid holding") + { + TokenHolding::Fungible { balance, .. } => { + assert_eq!(balance, Balances::user_holding_init() + withdraw_amount); + } + TokenHolding::NftMaster { .. } | TokenHolding::NftPrintedCopy { .. } => { + panic!("expected Fungible destination holding") + } + } + + let owner_expected = Account { + nonce: Nonce::private_account_nonce_init(&owner_id), + ..Account::default() + }; + assert!(state + .get_proof_for_commitment(&Commitment::new(&owner_id, &owner_expected)) + .is_some()); +} diff --git a/programs/integration_tests/tests/token.rs b/programs/integration_tests/tests/token.rs index bb276d21..5ecd9d78 100644 --- a/programs/integration_tests/tests/token.rs +++ b/programs/integration_tests/tests/token.rs @@ -1,14 +1,18 @@ +use integration_tests::{ + private_authorized_init_identity, private_authorized_update_identity, + private_unauthorized_identity, GroupOwner, +}; use nssa::{ execute_and_prove, privacy_preserving_transaction::{Message, PrivacyPreservingTransaction, WitnessSet}, program::Program, program_deployment_transaction::{self, ProgramDeploymentTransaction}, - public_transaction, PrivateKey, PublicKey, PublicTransaction, SharedSecretKey, V03State, + public_transaction, PrivateKey, PublicKey, PublicTransaction, V03State, }; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata, Data, Nonce}, - encryption::{EphemeralPublicKey, ViewingPublicKey}, - Commitment, EncryptedAccountData, InputAccountIdentity, NullifierPublicKey, NullifierSecretKey, + encryption::ViewingPublicKey, + Commitment, InputAccountIdentity, Nullifier, NullifierPublicKey, NullifierSecretKey, }; use token_core::{TokenDefinition, TokenHolding}; @@ -90,35 +94,34 @@ impl Accounts { } fn holder_init() -> Account { - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 1_000_000_u128, - }), - nonce: Nonce(0), - } + Self::token_holding(1_000_000_u128, Nonce(0)) } fn recipient_init() -> Account { + Self::token_holding(0_u128, Nonce(0)) + } + + fn authority_init() -> Account { Account { program_owner: Ids::token_program(), balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 0_u128, - }), + data: Data::default(), nonce: Nonce(0), } } - fn authority_init() -> Account { + /// A token holding account for the canonical `Ids::token_definition()`, at the given + /// balance and nonce. Covers every private and public token-holding shape in this file — + /// the `program_owner`/`definition_id` are fixed for this test module. + fn token_holding(balance: u128, nonce: Nonce) -> Account { Account { program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::default(), - nonce: Nonce(0), + balance: 0, + data: Data::from(&TokenHolding::Fungible { + definition_id: Ids::token_definition(), + balance, + }), + nonce, } } } @@ -194,15 +197,7 @@ fn token_new_fungible_definition() { assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 1_000_000_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(1_000_000_u128, Nonce(1)) ); } @@ -232,15 +227,7 @@ fn token_initialize_account_succeeds_for_canonical_definition() { ); assert_eq!( state.get_account_by_id(Ids::recipient()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 0_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(0_u128, Nonce(1)) ); } @@ -301,28 +288,12 @@ fn token_transfer() { assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 500_000_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(500_000_u128, Nonce(1)) ); assert_eq!( state.get_account_by_id(Ids::recipient()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 500_000_u128, - }), - nonce: Nonce(0), - } + Accounts::token_holding(500_000_u128, Nonce(0)) ); } @@ -383,28 +354,12 @@ fn token_transfer_fresh_authorized_public_recipient() { assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 500_000_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(500_000_u128, Nonce(1)) ); assert_eq!( state.get_account_by_id(Ids::recipient()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 500_000_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(500_000_u128, Nonce(1)) ); } @@ -446,15 +401,7 @@ fn token_burn() { assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 800_000_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(800_000_u128, Nonce(1)) ); } @@ -496,15 +443,7 @@ fn token_mint() { assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 1_500_000_u128, - }), - nonce: Nonce(0), - } + Accounts::token_holding(1_500_000_u128, Nonce(0)) ); } @@ -618,15 +557,7 @@ fn token_mint_fresh_authorized_public_recipient() { assert_eq!( state.get_account_by_id(Ids::recipient()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 500_000_u128, - }), - nonce: Nonce(1), - } + Accounts::token_holding(500_000_u128, Nonce(1)) ); } @@ -676,21 +607,21 @@ fn token_program() -> Program { /// `Ids::holder()` to a new private account keyed by `PrivateKeys::recipient_*`. /// Returns the resulting private recipient account. #[cfg(test)] -fn shielded_token_transfer(amount: u128, state: &mut V03State) -> Account { +fn shielded_token_transfer( + amount: u128, + state: &mut V03State, + recipient_is_authorized: bool, + recipient_identity: InputAccountIdentity, +) -> Account { let sender_id = Ids::holder(); let sender_account = state.get_account_by_id(sender_id); let sender_nonce = sender_account.nonce; - let recipient_npk = PrivateKeys::recipient_npk(); - let recipient_vpk = PrivateKeys::recipient_vpk(); let recipient_id = PrivateKeys::recipient_id(); let sender = AccountWithMetadata::new(sender_account, true, sender_id); - let recipient = AccountWithMetadata::new(Account::default(), false, recipient_id); - - // Sender encapsulates a shared secret against the recipient's viewing key. The - // circuit fills the real EPK, so we pass an empty placeholder in the identity. - let shared_secret = SharedSecretKey::encapsulate_deterministic(&recipient_vpk, &[0u8; 32], 0).0; + let recipient = + AccountWithMetadata::new(Account::default(), recipient_is_authorized, recipient_id); let instruction = token_core::Instruction::Transfer { amount_to_transfer: amount, @@ -698,16 +629,7 @@ fn shielded_token_transfer(amount: u128, state: &mut V03State) -> Account { let (output, proof) = execute_and_prove( vec![sender, recipient], Program::serialize_instruction(instruction).unwrap(), - vec![ - InputAccountIdentity::Public, - InputAccountIdentity::PrivateUnauthorized { - epk: EphemeralPublicKey(Vec::new()), - view_tag: EncryptedAccountData::compute_view_tag(&recipient_npk, &recipient_vpk), - npk: recipient_npk, - ssk: shared_secret, - identifier: 0, - }, - ], + vec![InputAccountIdentity::Public, recipient_identity], &token_program().into(), ) .unwrap(); @@ -721,15 +643,7 @@ fn shielded_token_transfer(amount: u128, state: &mut V03State) -> Account { .transition_from_privacy_preserving_transaction(&tx, 0, 0) .unwrap(); - Account { - program_owner: Ids::token_program(), - balance: 0, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: amount, - }), - nonce: Nonce::private_account_nonce_init(&recipient_id), - } + Accounts::token_holding(amount, Nonce::private_account_nonce_init(&recipient_id)) } #[test] @@ -737,19 +651,47 @@ fn token_shielded_transfer() { let mut state = state_for_token_tests(); let amount = 500_000_u128; - let recipient_account = shielded_token_transfer(amount, &mut state); + let recipient_npk = PrivateKeys::recipient_npk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_account = shielded_token_transfer( + amount, + &mut state, + false, + private_unauthorized_identity(recipient_npk, &recipient_vpk, 0), + ); assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 1_000_000 - amount, - }), - nonce: Nonce(1), - } + Accounts::token_holding(1_000_000 - amount, Nonce(1)) + ); + + let recipient_commitment = Commitment::new(&PrivateKeys::recipient_id(), &recipient_account); + assert!(state + .get_proof_for_commitment(&recipient_commitment) + .is_some()); +} + +/// Shielded transaction to a private account using the account's `nsk`. +/// `token_shielded_transfer` only uses the account's `npk`; thus, `PrivateUnauthorized` private. +#[test] +fn token_shielded_transfer_authorized_private_init() { + let mut state = state_for_token_tests(); + let amount = 500_000_u128; + + let recipient_account = shielded_token_transfer( + amount, + &mut state, + true, + private_authorized_init_identity( + PrivateKeys::recipient_nsk(), + &PrivateKeys::recipient_vpk(), + 0, + ), + ); + + assert_eq!( + state.get_account_by_id(Ids::holder()), + Accounts::token_holding(1_000_000 - amount, Nonce(1)) ); let recipient_commitment = Commitment::new(&PrivateKeys::recipient_id(), &recipient_account); @@ -765,8 +707,16 @@ fn token_private_transfer() { let transfer_amount = 200_000_u128; // Shield tokens into a private account (becomes the sender for the private transfer). - let sender_account = shielded_token_transfer(shielded_amount, &mut state); - let sender_npk = PrivateKeys::recipient_npk(); + let sender_account = shielded_token_transfer( + shielded_amount, + &mut state, + false, + private_unauthorized_identity( + PrivateKeys::recipient_npk(), + &PrivateKeys::recipient_vpk(), + 0, + ), + ); let sender_nsk = PrivateKeys::recipient_nsk(); let sender_vpk = PrivateKeys::recipient_vpk(); let sender_id = PrivateKeys::recipient_id(); @@ -780,11 +730,6 @@ fn token_private_transfer() { .get_proof_for_commitment(&sender_commitment) .expect("sender's commitment must be in the set"); - // Distinct `output_index` per private output keeps the encapsulated secrets reproducible. - let shared_secret_1 = SharedSecretKey::encapsulate_deterministic(&sender_vpk, &[0u8; 32], 0).0; - let shared_secret_2 = - SharedSecretKey::encapsulate_deterministic(&new_recipient_vpk, &[0u8; 32], 1).0; - let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, sender_id); let new_recipient_pre = AccountWithMetadata::new(Account::default(), false, new_recipient_id); @@ -795,24 +740,10 @@ fn token_private_transfer() { vec![sender_pre, new_recipient_pre], Program::serialize_instruction(instruction).unwrap(), vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - epk: EphemeralPublicKey(Vec::new()), - view_tag: EncryptedAccountData::compute_view_tag(&sender_npk, &sender_vpk), - ssk: shared_secret_1, - nsk: sender_nsk, - membership_proof, - identifier: 0, - }, - InputAccountIdentity::PrivateUnauthorized { - epk: EphemeralPublicKey(Vec::new()), - view_tag: EncryptedAccountData::compute_view_tag( - &new_recipient_npk, - &new_recipient_vpk, - ), - npk: new_recipient_npk, - ssk: shared_secret_2, - identifier: 0, - }, + // Distinct `output_index` per private output keeps the encapsulated secrets + // reproducible. + private_authorized_update_identity(sender_nsk, &sender_vpk, membership_proof, 0), + private_unauthorized_identity(new_recipient_npk, &new_recipient_vpk, 1), ], &token_program().into(), ) @@ -828,28 +759,16 @@ fn token_private_transfer() { let sender_nonce_after = Nonce::private_account_nonce_init(&sender_id).private_account_nonce_increment(&sender_nsk); - let new_sender_account = Account { - program_owner: Ids::token_program(), - balance: 0, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: shielded_amount - transfer_amount, - }), - nonce: sender_nonce_after, - }; + let new_sender_account = + Accounts::token_holding(shielded_amount - transfer_amount, sender_nonce_after); assert!(state .get_proof_for_commitment(&Commitment::new(&sender_id, &new_sender_account)) .is_some()); - let new_recipient_account = Account { - program_owner: Ids::token_program(), - balance: 0, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: transfer_amount, - }), - nonce: Nonce::private_account_nonce_init(&new_recipient_id), - }; + let new_recipient_account = Accounts::token_holding( + transfer_amount, + Nonce::private_account_nonce_init(&new_recipient_id), + ); assert!(state .get_proof_for_commitment(&Commitment::new(&new_recipient_id, &new_recipient_account)) .is_some()); @@ -862,8 +781,16 @@ fn token_deshielded_transfer() { let deshield_amount = 300_000_u128; // Shield tokens into a private account, then deshield some back to a public account. - let sender_account = shielded_token_transfer(shielded_amount, &mut state); - let sender_npk = PrivateKeys::recipient_npk(); + let sender_account = shielded_token_transfer( + shielded_amount, + &mut state, + false, + private_unauthorized_identity( + PrivateKeys::recipient_npk(), + &PrivateKeys::recipient_vpk(), + 0, + ), + ); let sender_nsk = PrivateKeys::recipient_nsk(); let sender_vpk = PrivateKeys::recipient_vpk(); let sender_id = PrivateKeys::recipient_id(); @@ -874,8 +801,6 @@ fn token_deshielded_transfer() { .get_proof_for_commitment(&sender_commitment) .expect("sender's commitment must be in the set"); - let shared_secret = SharedSecretKey::encapsulate_deterministic(&sender_vpk, &[0u8; 32], 0).0; - let public_recipient_pre = AccountWithMetadata::new( state.get_account_by_id(public_recipient_id), false, @@ -890,14 +815,7 @@ fn token_deshielded_transfer() { vec![sender_pre, public_recipient_pre], Program::serialize_instruction(instruction).unwrap(), vec![ - InputAccountIdentity::PrivateAuthorizedUpdate { - epk: EphemeralPublicKey(Vec::new()), - view_tag: EncryptedAccountData::compute_view_tag(&sender_npk, &sender_vpk), - ssk: shared_secret, - nsk: sender_nsk, - membership_proof, - identifier: 0, - }, + private_authorized_update_identity(sender_nsk, &sender_vpk, membership_proof, 0), InputAccountIdentity::Public, ], &token_program().into(), @@ -915,122 +833,998 @@ fn token_deshielded_transfer() { assert_eq!( state.get_account_by_id(public_recipient_id), - Account { - program_owner: Ids::token_program(), - balance: 0, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: deshield_amount, - }), - nonce: Nonce(0), - } + Accounts::token_holding(deshield_amount, Nonce(0)) ); let sender_nonce_after = Nonce::private_account_nonce_init(&sender_id).private_account_nonce_increment(&sender_nsk); - let new_sender_account = Account { - program_owner: Ids::token_program(), - balance: 0, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: shielded_amount - deshield_amount, - }), - nonce: sender_nonce_after, - }; + let new_sender_account = + Accounts::token_holding(shielded_amount - deshield_amount, sender_nonce_after); assert!(state .get_proof_for_commitment(&Commitment::new(&sender_id, &new_sender_account)) .is_some()); } +/// Mints directly to a new recipient private holding (`PrivateUnauthorized`). +/// The recipient's cooperation is unnecessary; only known of the recipient's `npk`, `vpk`. #[test] -fn token_new_fungible_definition_with_authority() { - let mut state = V03State::new(); - deploy_token(&mut state); - let authority_key: [u8; 32] = Ids::token_definition() - .as_ref() - .try_into() - .expect("AccountId is always 32 bytes"); - let instruction = token_core::Instruction::NewFungibleDefinition { - name: String::from("AuthCoin"), - total_supply: 1_000_000_u128, - mint_authority: Some(AccountId::new(authority_key)), - }; - let message = public_transaction::Message::try_new( - Ids::token_program(), - vec![Ids::token_definition(), Ids::holder()], - vec![Nonce(0), Nonce(0)], - instruction, +fn token_mint_private_unauthorized() { + let mut state = state_for_token_tests_without_recipient(); + let amount_to_mint = 500_000_u128; + + let recipient_npk = PrivateKeys::recipient_npk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_id = PrivateKeys::recipient_id(); + + let definition_account = state.get_account_by_id(Ids::token_definition()); + let definition_nonce = definition_account.nonce; + let definition_pre = + AccountWithMetadata::new(definition_account, true, Ids::token_definition()); + let recipient_pre = AccountWithMetadata::new(Account::default(), false, recipient_id); + + let instruction = token_core::Instruction::Mint { amount_to_mint }; + let (output, proof) = execute_and_prove( + vec![definition_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_unauthorized_identity(recipient_npk, &recipient_vpk, 0), + ], + &token_program().into(), ) .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message( - &message, - &[&Keys::def_key(), &Keys::holder_key()], - ); - let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + + let message = Message::try_from_circuit_output( + vec![Ids::token_definition()], + vec![definition_nonce], + output, + ) + .unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::def_key()]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + assert_eq!( state.get_account_by_id(Ids::token_definition()), Account { program_owner: Ids::token_program(), balance: 0_u128, data: Data::from(&TokenDefinition::Fungible { - name: String::from("AuthCoin"), - total_supply: 1_000_000_u128, + name: String::from("Gold"), + total_supply: 1_000_000_u128 + amount_to_mint, metadata_id: None, - authority: Some(AccountId::new(authority_key)), + authority: Some(Ids::token_definition()), }), nonce: Nonce(1), } ); + + let recipient_account = Accounts::token_holding( + amount_to_mint, + Nonce::private_account_nonce_init(&recipient_id), + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &recipient_account)) + .is_some()); } +/// Mints directly to a new recipient private holding (`PrivateAuthorizedInit`). +/// This requires the recipient's secret key `nsk`. #[test] -fn token_set_authority_revoke() { - let mut state = V03State::new(); - deploy_token(&mut state); - let authority_key: [u8; 32] = Ids::token_definition() - .as_ref() - .try_into() - .expect("AccountId is always 32 bytes"); - // Create token with authority - let instruction = token_core::Instruction::NewFungibleDefinition { - name: String::from("AuthCoin"), - total_supply: 1_000_000_u128, - mint_authority: Some(AccountId::new(authority_key)), - }; - let message = public_transaction::Message::try_new( - Ids::token_program(), - vec![Ids::token_definition(), Ids::holder()], - vec![Nonce(0), Nonce(0)], - instruction, +fn token_mint_authorized_private_init() { + let mut state = state_for_token_tests_without_recipient(); + let amount_to_mint = 500_000_u128; + + let recipient_nsk = PrivateKeys::recipient_nsk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_id = PrivateKeys::recipient_id(); + + let definition_account = state.get_account_by_id(Ids::token_definition()); + let definition_nonce = definition_account.nonce; + let definition_pre = + AccountWithMetadata::new(definition_account, true, Ids::token_definition()); + let recipient_pre = AccountWithMetadata::new(Account::default(), true, recipient_id); + + let instruction = token_core::Instruction::Mint { amount_to_mint }; + let (output, proof) = execute_and_prove( + vec![definition_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_init_identity(recipient_nsk, &recipient_vpk, 0), + ], + &token_program().into(), ) .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message( - &message, - &[&Keys::def_key(), &Keys::holder_key()], - ); - let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - // Seed the authority account so it can sign the revoke - state.force_insert_account(Ids::authority(), Accounts::authority_init()); - - // Revoke authority - let instruction = token_core::Instruction::SetAuthority { - new_authority: None, - }; - let message = public_transaction::Message::try_new( - Ids::token_program(), + let message = Message::try_from_circuit_output( vec![Ids::token_definition()], - vec![Nonce(1)], - instruction, + vec![definition_nonce], + output, ) .unwrap(); - let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]); - let tx = PublicTransaction::new(message, witness_set); - state.transition_from_public_transaction(&tx, 0, 0).unwrap(); - assert_eq!( - state.get_account_by_id(Ids::token_definition()), + + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::def_key()]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 + amount_to_mint, + metadata_id: None, + authority: Some(Ids::token_definition()), + }), + nonce: Nonce(1), + } + ); + + let recipient_account = Accounts::token_holding( + amount_to_mint, + Nonce::private_account_nonce_init(&recipient_id), + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &recipient_account)) + .is_some()); +} + +/// Mints directly to a pre-existing recipient private holding +/// This requires the recipient's secret key `nsk`. +#[test] +fn token_mint_into_existing_private_holding() { + let mut state = state_for_token_tests_without_recipient(); + let pre_balance = 500_000_u128; + let amount_to_mint = 250_000_u128; + + let recipient_nsk = PrivateKeys::recipient_nsk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_id = PrivateKeys::recipient_id(); + + let recipient_pre = Accounts::token_holding( + pre_balance, + Nonce::private_account_nonce_init(&recipient_id), + ); + let recipient_commitment = Commitment::new(&recipient_id, &recipient_pre); + state = state.with_private_accounts([( + recipient_commitment.clone(), + Nullifier::for_account_initialization(&recipient_id), + )]); + + let membership_proof = state + .get_proof_for_commitment(&recipient_commitment) + .expect("seeded recipient's commitment must be in the set"); + + let definition_account = state.get_account_by_id(Ids::token_definition()); + let definition_nonce = definition_account.nonce; + let definition_pre = + AccountWithMetadata::new(definition_account, true, Ids::token_definition()); + let existing_recipient_pre = + AccountWithMetadata::new(recipient_pre.clone(), true, recipient_id); + + let (output, second_proof) = execute_and_prove( + vec![definition_pre, existing_recipient_pre], + Program::serialize_instruction(token_core::Instruction::Mint { amount_to_mint }).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_update_identity(recipient_nsk, &recipient_vpk, membership_proof, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![Ids::token_definition()], + vec![definition_nonce], + output, + ) + .unwrap(); + let witness = WitnessSet::for_message(&message, second_proof, &[&Keys::def_key()]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness), + 0, + 0, + ) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 + amount_to_mint, + metadata_id: None, + authority: Some(Ids::token_definition()), + }), + nonce: Nonce(1), + } + ); + + let recipient_nonce_after = Nonce::private_account_nonce_init(&recipient_id) + .private_account_nonce_increment(&recipient_nsk); + let recipient_after_second_mint = + Accounts::token_holding(pre_balance + amount_to_mint, recipient_nonce_after); + assert!(state + .get_proof_for_commitment(&Commitment::new( + &recipient_id, + &recipient_after_second_mint + )) + .is_some()); +} + +/// Burns from an existing private holding (`PrivateAuthorizedUpdate`). +#[test] +fn token_private_burn() { + let mut state = state_for_token_tests(); + let holding_balance = 500_000_u128; + let burn_amount = 200_000_u128; + + let holder_nsk = PrivateKeys::recipient_nsk(); + let holder_vpk = PrivateKeys::recipient_vpk(); + let holder_id = PrivateKeys::recipient_id(); + + // Predefined holding account to burn from. + let holder_account = Accounts::token_holding( + holding_balance, + Nonce::private_account_nonce_init(&holder_id), + ); + let holder_commitment = Commitment::new(&holder_id, &holder_account); + state = state.with_private_accounts([( + holder_commitment.clone(), + Nullifier::for_account_initialization(&holder_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&holder_commitment) + .expect("holder's commitment must be in the set"); + + let definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let holder_pre = AccountWithMetadata::new(holder_account.clone(), true, holder_id); + + let instruction = token_core::Instruction::Burn { + amount_to_burn: burn_amount, + }; + let (output, proof) = execute_and_prove( + vec![definition_pre, holder_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_update_identity(holder_nsk, &holder_vpk, membership_proof, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![Ids::token_definition()], vec![], output).unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 - burn_amount, + metadata_id: None, + authority: Some(Ids::token_definition()), + }), + nonce: Nonce(0), + } + ); + + let new_holder_account = Accounts::token_holding( + holding_balance - burn_amount, + Nonce::private_account_nonce_init(&holder_id).private_account_nonce_increment(&holder_nsk), + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&holder_id, &new_holder_account)) + .is_some()); +} + +/// Token transfer into a pre-existing Token holding account. This requires +/// the account's `nsk`; `PrivateAuthorizedUpdate`. +#[test] +fn token_transfer_into_existing_private_holding() { + let mut state = state_for_token_tests(); + let init_balance = 500_000_u128; + let second_amount = 100_000_u128; + + let recipient_nsk = PrivateKeys::recipient_nsk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_id = PrivateKeys::recipient_id(); + + let recipient_account = Accounts::token_holding( + init_balance, + Nonce::private_account_nonce_init(&recipient_id), + ); + let recipient_commitment = Commitment::new(&recipient_id, &recipient_account); + state = state.with_private_accounts([( + recipient_commitment.clone(), + Nullifier::for_account_initialization(&recipient_id), + )]); + let membership_proof = state + .get_proof_for_commitment(&recipient_commitment) + .expect("recipient's commitment must be in the set after seeding"); + + let sender_id = Ids::holder(); + let sender_account = state.get_account_by_id(sender_id); + let sender_nonce = sender_account.nonce; + + let sender_pre = AccountWithMetadata::new(sender_account, true, sender_id); + let recipient_pre = AccountWithMetadata::new(recipient_account.clone(), true, recipient_id); + + let instruction = token_core::Instruction::Transfer { + amount_to_transfer: second_amount, + }; + let (output, proof) = execute_and_prove( + vec![sender_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_update_identity(recipient_nsk, &recipient_vpk, membership_proof, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![sender_id], vec![sender_nonce], output).unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::holder_key()]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(sender_id), + // `first_amount` was seeded directly into the recipient, never debited from the + // sender — only the real transfer (`second_amount`) actually happened. + Accounts::token_holding(1_000_000 - second_amount, Nonce(1)) + ); + + let recipient_nonce_after = Nonce::private_account_nonce_init(&recipient_id) + .private_account_nonce_increment(&recipient_nsk); + let new_recipient_account = + Accounts::token_holding(init_balance + second_amount, recipient_nonce_after); + assert!(state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &new_recipient_account)) + .is_some()); +} + +/// Private Token transfer into a pre-existing Token holding account. This requires +/// the account's `nsk`; `PrivateAuthorizedUpdate`. +#[test] +fn token_private_transfer_into_existing_private_holding() { + let mut state = state_for_token_tests(); + let sender_initial_balance = 500_000_u128; + let recipient_initial_balance = 300_000_u128; + let transfer_amount = 200_000_u128; + + let sender_nsk = PrivateKeys::recipient_nsk(); + let sender_vpk = PrivateKeys::recipient_vpk(); + let sender_id = PrivateKeys::recipient_id(); + + let recipient_nsk = PrivateKeys::holder_nsk(); + let recipient_vpk = PrivateKeys::holder_vpk(); + let recipient_id = PrivateKeys::holder_id(); + + // Seed both sides directly — neither needs a real prior transaction to exist. + let sender_account = Accounts::token_holding( + sender_initial_balance, + Nonce::private_account_nonce_init(&sender_id), + ); + let recipient_account = Accounts::token_holding( + recipient_initial_balance, + Nonce::private_account_nonce_init(&recipient_id), + ); + let sender_commitment = Commitment::new(&sender_id, &sender_account); + let recipient_commitment = Commitment::new(&recipient_id, &recipient_account); + state = state.with_private_accounts([ + ( + sender_commitment.clone(), + Nullifier::for_account_initialization(&sender_id), + ), + ( + recipient_commitment.clone(), + Nullifier::for_account_initialization(&recipient_id), + ), + ]); + + let sender_membership_proof = state + .get_proof_for_commitment(&sender_commitment) + .expect("sender's commitment must be in the set"); + let recipient_membership_proof = state + .get_proof_for_commitment(&recipient_commitment) + .expect("recipient's commitment must be in the set"); + + let sender_pre = AccountWithMetadata::new(sender_account.clone(), true, sender_id); + let recipient_pre = AccountWithMetadata::new(recipient_account.clone(), true, recipient_id); + + let instruction = token_core::Instruction::Transfer { + amount_to_transfer: transfer_amount, + }; + let (output, proof) = execute_and_prove( + vec![sender_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + private_authorized_update_identity(sender_nsk, &sender_vpk, sender_membership_proof, 0), + private_authorized_update_identity( + recipient_nsk, + &recipient_vpk, + recipient_membership_proof, + 1, + ), + ], + &token_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let sender_nonce_after = + Nonce::private_account_nonce_init(&sender_id).private_account_nonce_increment(&sender_nsk); + let new_sender_account = + Accounts::token_holding(sender_initial_balance - transfer_amount, sender_nonce_after); + assert!(state + .get_proof_for_commitment(&Commitment::new(&sender_id, &new_sender_account)) + .is_some()); + + let recipient_nonce_after = Nonce::private_account_nonce_init(&recipient_id) + .private_account_nonce_increment(&recipient_nsk); + let new_recipient_account = Accounts::token_holding( + recipient_initial_balance + transfer_amount, + recipient_nonce_after, + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &new_recipient_account)) + .is_some()); +} + +/// Initializes a private holding account directly (private account secret keys are known). +/// `InitializeAccount` requires `account_to_initialize` to be authorized. E.g., for private +/// accounts must be `PrivateAuthorizedInit` and not `PrivateUnauthorized`; the account owner +/// must supply their own `nsk`. +#[test] +fn token_initialize_private_account_succeeds_for_canonical_definition() { + let mut state = state_for_token_tests_without_recipient(); + + let owner_nsk = PrivateKeys::recipient_nsk(); + let owner_vpk = PrivateKeys::recipient_vpk(); + let owner_id = PrivateKeys::recipient_id(); + + let definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let account_to_init_pre = AccountWithMetadata::new(Account::default(), true, owner_id); + + let instruction = token_core::Instruction::InitializeAccount; + let (output, proof) = execute_and_prove( + vec![definition_pre, account_to_init_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_init_identity(owner_nsk, &owner_vpk, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![Ids::token_definition()], vec![], output).unwrap(); + + let witness_set = WitnessSet::for_message(&message, proof, &[]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + + let expected_account = Accounts::token_holding(0, Nonce::private_account_nonce_init(&owner_id)); + assert!(state + .get_proof_for_commitment(&Commitment::new(&owner_id, &expected_account)) + .is_some()); +} + +/// Confirms that `InitializeAccount` cannot be performed without private account's `nsk`. +/// E.g., account must be `PrivateAuthorizedInit` and not `PrivateUnauthorized`. +#[test] +fn token_initialize_private_account_without_nsk_is_not_expressible() { + let state = state_for_token_tests_without_recipient(); + + let recipient_npk = PrivateKeys::recipient_npk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_id = PrivateKeys::recipient_id(); + + let definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let account_to_init_pre = AccountWithMetadata::new(Account::default(), false, recipient_id); + + let result = execute_and_prove( + vec![definition_pre, account_to_init_pre], + Program::serialize_instruction(token_core::Instruction::InitializeAccount).unwrap(), + vec![ + InputAccountIdentity::Public, + private_unauthorized_identity(recipient_npk, &recipient_vpk, 0), + ], + &token_program().into(), + ); + + let err = result.expect_err( + "initializing a private holding without its nsk must be rejected: InitializeAccount \ + requires is_authorized == true, but PrivateUnauthorized forces is_authorized == false", + ); + let message = format!("{err:?}"); + assert!( + message.contains("must be a signer"), + "expected the self-service-only rejection, got a different error: {message}" + ); +} + +#[test] +fn token_new_fungible_definition_private_initial_holder() { + let mut state = V03State::new(); + deploy_token(&mut state); + + let holder_nsk = PrivateKeys::holder_nsk(); + let holder_vpk = PrivateKeys::holder_vpk(); + let holder_id = PrivateKeys::holder_id(); + + let definition_nonce = state.get_account_by_id(Ids::token_definition()).nonce; + let definition_pre = + AccountWithMetadata::new(Account::default(), true, Ids::token_definition()); + let holder_pre = AccountWithMetadata::new(Account::default(), true, holder_id); + + let instruction = token_core::Instruction::NewFungibleDefinition { + name: String::from("Gold"), + total_supply: 1_000_000_u128, + mint_authority: None, + }; + let (output, proof) = execute_and_prove( + vec![definition_pre, holder_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_init_identity(holder_nsk, &holder_vpk, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output( + vec![Ids::token_definition()], + vec![definition_nonce], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::def_key()]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128, + metadata_id: None, + authority: None, + }), + nonce: Nonce(1), + } + ); + + let holder_account = Accounts::token_holding( + 1_000_000_u128, + Nonce::private_account_nonce_init(&holder_id), + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&holder_id, &holder_account)) + .is_some()); +} + +#[test] +fn token_new_fungible_definition_private_holder_without_nsk_is_not_expressible() { + let mut state = V03State::new(); + deploy_token(&mut state); + + let holder_npk = PrivateKeys::holder_npk(); + let holder_vpk = PrivateKeys::holder_vpk(); + let holder_id = PrivateKeys::holder_id(); + + let definition_pre = + AccountWithMetadata::new(Account::default(), true, Ids::token_definition()); + let holder_pre = AccountWithMetadata::new(Account::default(), false, holder_id); + + let instruction = token_core::Instruction::NewFungibleDefinition { + name: String::from("Gold"), + total_supply: 1_000_000_u128, + mint_authority: None, + }; + let result = execute_and_prove( + vec![definition_pre, holder_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_unauthorized_identity(holder_npk, &holder_vpk, 0), + ], + &token_program().into(), + ); + + let err = result.expect_err( + "creating the initial holder via PrivateUnauthorized must be rejected: \ + NewFungibleDefinition requires is_authorized == true for holding_target_account, but \ + PrivateUnauthorized forces is_authorized == false", + ); + let message = format!("{err:?}"); + assert!( + message.contains("must be a signer"), + "expected the self-service-only rejection, got a different error: {message}" + ); +} + +/// Two independent parties (Alice and Bob) control a private Token holding (via `GroupKeyHolder`). +/// Alice initializes the private Token account, and Bob burns tokens from the shared account. +#[test] +fn token_group_owned_holding_shared_control_burn() { + let mut state = state_for_token_tests(); + let shield_amount = 500_000_u128; + let burn_amount = 200_000_u128; + + // Alice creates the group and derives the shared account's keys; Bob is admitted via the + // real seal/unseal handshake and independently re-derives the same keys. + let alice = GroupOwner::new([7_u8; 32]); + let bob_nsk = alice.admit_member(); + let group_npk = alice.npk; + let group_vpk = alice.vpk; + let group_id = alice.id; + + // Alice shields tokens into the group-owned holding (mirrors `shielded_token_transfer`, + // parameterized by the group's npk/vpk instead of a personal one). + let sender_id = Ids::holder(); + let sender_account = state.get_account_by_id(sender_id); + let sender_nonce = sender_account.nonce; + let sender_pre = AccountWithMetadata::new(sender_account, true, sender_id); + let group_pre_shield = AccountWithMetadata::new(Account::default(), false, group_id); + + let shield_instruction = token_core::Instruction::Transfer { + amount_to_transfer: shield_amount, + }; + let (shield_output, shield_proof) = execute_and_prove( + vec![sender_pre, group_pre_shield], + Program::serialize_instruction(shield_instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_unauthorized_identity(group_npk, &group_vpk, 0), + ], + &token_program().into(), + ) + .unwrap(); + let shield_message = + Message::try_from_circuit_output(vec![sender_id], vec![sender_nonce], shield_output) + .unwrap(); + let shield_witness = + WitnessSet::for_message(&shield_message, shield_proof, &[&Keys::holder_key()]); + let shield_tx = PrivacyPreservingTransaction::new(shield_message, shield_witness); + state + .transition_from_privacy_preserving_transaction(&shield_tx, 0, 0) + .unwrap(); + + let group_account = + Accounts::token_holding(shield_amount, Nonce::private_account_nonce_init(&group_id)); + let group_commitment = Commitment::new(&group_id, &group_account); + assert!(state.get_proof_for_commitment(&group_commitment).is_some()); + + // Bob — who never touched Alice's `GroupKeyHolder` object, only the sealed GMS — burns + // from the group-owned holding using his independently derived nsk. + let membership_proof = state + .get_proof_for_commitment(&group_commitment) + .expect("group holding's commitment must be in the set"); + + let definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let group_pre_burn = AccountWithMetadata::new(group_account, true, group_id); + + let burn_instruction = token_core::Instruction::Burn { + amount_to_burn: burn_amount, + }; + let (burn_output, burn_proof) = execute_and_prove( + vec![definition_pre, group_pre_burn], + Program::serialize_instruction(burn_instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_update_identity(bob_nsk, &group_vpk, membership_proof, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let burn_message = + Message::try_from_circuit_output(vec![Ids::token_definition()], vec![], burn_output) + .unwrap(); + let burn_witness = WitnessSet::for_message(&burn_message, burn_proof, &[]); + let burn_tx = PrivacyPreservingTransaction::new(burn_message, burn_witness); + state + .transition_from_privacy_preserving_transaction(&burn_tx, 0, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 - burn_amount, + metadata_id: None, + authority: Some(Ids::token_definition()), + }), + nonce: Nonce(0), + } + ); + + let group_nonce_after = + Nonce::private_account_nonce_init(&group_id).private_account_nonce_increment(&bob_nsk); + let new_group_account = Accounts::token_holding(shield_amount - burn_amount, group_nonce_after); + assert!(state + .get_proof_for_commitment(&Commitment::new(&group_id, &new_group_account)) + .is_some()); +} + +/// Two independent parties (Alice and Bob) control a private Token holding (via `GroupKeyHolder`). +/// Alice initializes the private Token account, and Bob transfers tokens from the shared account. +#[test] +fn token_group_owned_holding_shared_control_transfer() { + let mut state = state_for_token_tests(); + let shield_amount = 500_000_u128; + let transfer_amount = 200_000_u128; + + // Alice creates the group and derives the shared account's keys; Bob is admitted via the + // real seal/unseal handshake and independently re-derives the same keys. + let alice = GroupOwner::new([7_u8; 32]); + let bob_nsk = alice.admit_member(); + let group_vpk = alice.vpk; + let group_id = alice.id; + + let group_account = + Accounts::token_holding(shield_amount, Nonce::private_account_nonce_init(&group_id)); + let group_commitment = Commitment::new(&group_id, &group_account); + state = state.with_private_accounts([( + group_commitment.clone(), + Nullifier::for_account_initialization(&group_id), + )]); + + // Bob spends via Transfer — not Burn — sending to a fresh private recipient. + let membership_proof = state + .get_proof_for_commitment(&group_commitment) + .expect("group holding's commitment must be in the set"); + + let recipient_npk = PrivateKeys::holder_npk(); + let recipient_vpk = PrivateKeys::holder_vpk(); + let recipient_id = PrivateKeys::holder_id(); + + let group_pre = AccountWithMetadata::new(group_account, true, group_id); + let recipient_pre = AccountWithMetadata::new(Account::default(), false, recipient_id); + + let instruction = token_core::Instruction::Transfer { + amount_to_transfer: transfer_amount, + }; + let (output, proof) = execute_and_prove( + vec![group_pre, recipient_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + private_authorized_update_identity(bob_nsk, &group_vpk, membership_proof, 0), + private_unauthorized_identity(recipient_npk, &recipient_vpk, 1), + ], + &token_program().into(), + ) + .unwrap(); + + let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let group_nonce_after = + Nonce::private_account_nonce_init(&group_id).private_account_nonce_increment(&bob_nsk); + let new_group_account = + Accounts::token_holding(shield_amount - transfer_amount, group_nonce_after); + assert!(state + .get_proof_for_commitment(&Commitment::new(&group_id, &new_group_account)) + .is_some()); + + let new_recipient_account = Accounts::token_holding( + transfer_amount, + Nonce::private_account_nonce_init(&recipient_id), + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &new_recipient_account)) + .is_some()); +} + +/// Two independent parties (Alice and Bob) control a private Token holding (via `GroupKeyHolder`). +/// Alice initializes the private Token account (`InitializeAccount` with `PrivateAuthorizedInit`)/ +#[test] +fn token_group_owned_holding_shared_control_initialize() { + let mut state = state_for_token_tests_without_recipient(); + + // Alice creates the group and derives the shared account's keys; Bob is admitted via the + // real seal/unseal handshake and independently re-derives the same keys. + let alice = GroupOwner::new([7_u8; 32]); + let group_id = alice.id; + let bob_nsk = alice.admit_member(); + + // Bob — who never created the group — self-initializes the shared holding directly. + let definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let group_pre = AccountWithMetadata::new(Account::default(), true, group_id); + + let instruction = token_core::Instruction::InitializeAccount; + let (output, proof) = execute_and_prove( + vec![definition_pre, group_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_authorized_init_identity(bob_nsk, &alice.vpk, 0), + ], + &token_program().into(), + ) + .unwrap(); + + let message = + Message::try_from_circuit_output(vec![Ids::token_definition()], vec![], output).unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[]); + state + .transition_from_privacy_preserving_transaction( + &PrivacyPreservingTransaction::new(message, witness_set), + 0, + 0, + ) + .unwrap(); + + let expected_account = Accounts::token_holding(0, Nonce::private_account_nonce_init(&group_id)); + assert!(state + .get_proof_for_commitment(&Commitment::new(&group_id, &expected_account)) + .is_some()); +} + +#[test] +fn token_new_fungible_definition_with_authority() { + let mut state = V03State::new(); + deploy_token(&mut state); + let authority_key: [u8; 32] = Ids::token_definition() + .as_ref() + .try_into() + .expect("AccountId is always 32 bytes"); + let instruction = token_core::Instruction::NewFungibleDefinition { + name: String::from("AuthCoin"), + total_supply: 1_000_000_u128, + mint_authority: Some(AccountId::new(authority_key)), + }; + let message = public_transaction::Message::try_new( + Ids::token_program(), + vec![Ids::token_definition(), Ids::holder()], + vec![Nonce(0), Nonce(0)], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&Keys::def_key(), &Keys::holder_key()], + ); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("AuthCoin"), + total_supply: 1_000_000_u128, + metadata_id: None, + authority: Some(AccountId::new(authority_key)), + }), + nonce: Nonce(1), + } + ); +} + +#[test] +fn token_set_authority_revoke() { + let mut state = V03State::new(); + deploy_token(&mut state); + let authority_key: [u8; 32] = Ids::token_definition() + .as_ref() + .try_into() + .expect("AccountId is always 32 bytes"); + // Create token with authority + let instruction = token_core::Instruction::NewFungibleDefinition { + name: String::from("AuthCoin"), + total_supply: 1_000_000_u128, + mint_authority: Some(AccountId::new(authority_key)), + }; + let message = public_transaction::Message::try_new( + Ids::token_program(), + vec![Ids::token_definition(), Ids::holder()], + vec![Nonce(0), Nonce(0)], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&Keys::def_key(), &Keys::holder_key()], + ); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + + // Seed the authority account so it can sign the revoke + state.force_insert_account(Ids::authority(), Accounts::authority_init()); + + // Revoke authority + let instruction = token_core::Instruction::SetAuthority { + new_authority: None, + }; + let message = public_transaction::Message::try_new( + Ids::token_program(), + vec![Ids::token_definition()], + vec![Nonce(1)], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[&Keys::def_key()]); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + assert_eq!( + state.get_account_by_id(Ids::token_definition()), Account { program_owner: Ids::token_program(), balance: 0_u128, @@ -1227,15 +2021,7 @@ fn token_rotate_authority_then_new_authority_can_mint() { ); assert_eq!( state.get_account_by_id(Ids::holder()), - Account { - program_owner: Ids::token_program(), - balance: 0_u128, - data: Data::from(&TokenHolding::Fungible { - definition_id: Ids::token_definition(), - balance: 1_500_000_u128, - }), - nonce: Nonce(0), - } + Accounts::token_holding(1_500_000_u128, Nonce(0)) ); // Step 4: OLD authority (def_key self-authority path) must be rejected after rotation. @@ -1257,3 +2043,105 @@ fn token_rotate_authority_then_new_authority_can_mint() { "Old authority must be rejected after rotation" ); } + +#[test] +fn token_mint_with_authority_to_private_holding() { + let mut state = V03State::new(); + deploy_token(&mut state); + + let authority_key: [u8; 32] = Ids::authority() + .as_ref() + .try_into() + .expect("AccountId is always 32 bytes"); + + // Create the definition with an external mint authority from the start — the rotation + // dance itself is already covered by `token_rotate_authority_then_new_authority_can_mint`. + let instruction = token_core::Instruction::NewFungibleDefinition { + name: String::from("Gold"), + total_supply: 1_000_000_u128, + mint_authority: Some(AccountId::new(authority_key)), + }; + let message = public_transaction::Message::try_new( + Ids::token_program(), + vec![Ids::token_definition(), Ids::holder()], + vec![Nonce(0), Nonce(0)], + instruction, + ) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message( + &message, + &[&Keys::def_key(), &Keys::holder_key()], + ); + let tx = PublicTransaction::new(message, witness_set); + state.transition_from_public_transaction(&tx, 0, 0).unwrap(); + + state.force_insert_account(Ids::authority(), Accounts::authority_init()); + + let amount_to_mint = 500_000_u128; + let recipient_npk = PrivateKeys::recipient_npk(); + let recipient_vpk = PrivateKeys::recipient_vpk(); + let recipient_id = PrivateKeys::recipient_id(); + + // Definition is `#[account(mut)]` only under external authority — it does not itself + // authorize the mint, so it goes in as an ordinary (unauthorized) public account. + let definition_pre = AccountWithMetadata::new( + state.get_account_by_id(Ids::token_definition()), + false, + Ids::token_definition(), + ); + let recipient_pre = AccountWithMetadata::new(Account::default(), false, recipient_id); + let authority_account = state.get_account_by_id(Ids::authority()); + let authority_nonce = authority_account.nonce; + let authority_pre = AccountWithMetadata::new(authority_account, true, Ids::authority()); + + let instruction = token_core::Instruction::MintWithAuthority { amount_to_mint }; + let (output, proof) = execute_and_prove( + vec![definition_pre, recipient_pre, authority_pre], + Program::serialize_instruction(instruction).unwrap(), + vec![ + InputAccountIdentity::Public, + private_unauthorized_identity(recipient_npk, &recipient_vpk, 0), + InputAccountIdentity::Public, + ], + &token_program().into(), + ) + .unwrap(); + + // `public_account_ids` carries every public account for post-state zipping (definition, + // then authority — their `execute_and_prove` input order); `nonces` carries only the + // signer(s), positionally matched to the witness keys below (just `authority` here). + let message = Message::try_from_circuit_output( + vec![Ids::token_definition(), Ids::authority()], + vec![authority_nonce], + output, + ) + .unwrap(); + let witness_set = WitnessSet::for_message(&message, proof, &[&Keys::authority_key()]); + let tx = PrivacyPreservingTransaction::new(message, witness_set); + state + .transition_from_privacy_preserving_transaction(&tx, 0, 0) + .unwrap(); + + assert_eq!( + state.get_account_by_id(Ids::token_definition()), + Account { + program_owner: Ids::token_program(), + balance: 0_u128, + data: Data::from(&TokenDefinition::Fungible { + name: String::from("Gold"), + total_supply: 1_000_000_u128 + amount_to_mint, + metadata_id: None, + authority: Some(AccountId::new(authority_key)), + }), + nonce: Nonce(1), + } + ); + + let recipient_account = Accounts::token_holding( + amount_to_mint, + Nonce::private_account_nonce_init(&recipient_id), + ); + assert!(state + .get_proof_for_commitment(&Commitment::new(&recipient_id, &recipient_account)) + .is_some()); +}