diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38161a0f..a8efedd9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -99,7 +99,7 @@ jobs: run: rustup install - name: Install nextest - run: cargo install cargo-nextest + run: cargo install --locked cargo-nextest - name: Run tests env: diff --git a/Cargo.lock b/Cargo.lock index 2b5e93cd..16baf5a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -376,18 +376,6 @@ dependencies = [ "triomphe", ] -[[package]] -name = "ark-bls12-381" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c775f0d12169cba7aae4caeb547bb6a50781c7449a8aa53793827c9ec4abf488" -dependencies = [ - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-serialize 0.4.2", - "ark-std 0.4.0", -] - [[package]] name = "ark-bn254" version = "0.4.0" @@ -644,23 +632,6 @@ dependencies = [ "hashbrown 0.15.5", ] -[[package]] -name = "ark-poly-commit" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a741492629ffcd228337676dc223a28551aa6792eedb8a2a22c767f00df6c89" -dependencies = [ - "ark-crypto-primitives 0.4.0", - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-poly 0.4.2", - "ark-relations 0.4.0", - "ark-serialize 0.4.2", - "ark-std 0.4.0", - "derivative", - "digest", -] - [[package]] name = "ark-r1cs-std" version = "0.5.0" @@ -956,8 +927,8 @@ name = "bedrock_client" version = "0.1.0" dependencies = [ "anyhow", - "common-http-client", - "nomos-core", + "logos-blockchain-common-http-client", + "logos-blockchain-core", "reqwest", ] @@ -1057,18 +1028,6 @@ dependencies = [ "generic-array 0.14.7", ] -[[package]] -name = "blst" -version = "0.3.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcdb4c7013139a150f9fc55d123186dbfaba0d912817466282c73ac49e71fb45" -dependencies = [ - "cc", - "glob", - "threadpool", - "zeroize", -] - [[package]] name = "bonsai-sdk" version = "1.4.1" @@ -1105,22 +1064,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "broadcast-service" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "derivative", - "futures", - "nomos-core", - "overwatch", - "serde", - "tokio", - "tokio-stream", - "tracing", -] - [[package]] name = "bs58" version = "0.5.1" @@ -1277,35 +1220,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "chain-service" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "broadcast-service", - "bytes", - "cryptarchia-engine", - "cryptarchia-sync", - "futures", - "groth16", - "nomos-core", - "nomos-ledger", - "nomos-network", - "nomos-storage", - "nomos-utils", - "num-bigint", - "overwatch", - "serde", - "serde_with", - "services-utils", - "strum", - "thiserror 1.0.69", - "tokio", - "tracing", - "tracing-futures", -] - [[package]] name = "chrono" version = "0.4.42" @@ -1330,23 +1244,6 @@ dependencies = [ "inout", ] -[[package]] -name = "circuits-prover" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "circuits-utils", - "tempfile", -] - -[[package]] -name = "circuits-utils" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "dirs", -] - [[package]] name = "clang-sys" version = "1.8.1" @@ -1431,24 +1328,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "common-http-client" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "broadcast-service", - "chain-service", - "futures", - "nomos-core", - "nomos-da-messages", - "nomos-http-api-common", - "reqwest", - "serde", - "serde_json", - "thiserror 1.0.69", - "url", -] - [[package]] name = "console" version = "0.16.2" @@ -1576,37 +1455,6 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "460fbee9c2c2f33933d720630a6a0bac33ba7053db5344fac858d4b8952d77d5" -[[package]] -name = "cryptarchia-engine" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "cfg_eval", - "nomos-utils", - "serde", - "serde_with", - "thiserror 1.0.69", - "time", - "tracing", -] - -[[package]] -name = "cryptarchia-sync" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "bytes", - "cryptarchia-engine", - "futures", - "nomos-core", - "rand 0.8.5", - "serde", - "serde_with", - "thiserror 1.0.69", - "tokio", - "tracing", -] - [[package]] name = "crypto-bigint" version = "0.5.5" @@ -2407,24 +2255,6 @@ dependencies = [ "spinning_top", ] -[[package]] -name = "groth16" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-bn254 0.4.0", - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-groth16 0.4.0", - "ark-serialize 0.4.2", - "generic-array 1.3.5", - "hex", - "num-bigint", - "serde", - "serde_json", - "thiserror 2.0.17", -] - [[package]] name = "group" version = "0.13.0" @@ -2976,15 +2806,6 @@ dependencies = [ "either", ] -[[package]] -name = "itertools" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" @@ -3078,56 +2899,6 @@ dependencies = [ "cpufeatures", ] -[[package]] -name = "key-management-system-keys" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "bytes", - "ed25519-dalek", - "generic-array 1.3.5", - "groth16", - "key-management-system-macros", - "nomos-blend-proofs", - "nomos-utils", - "num-bigint", - "poseidon2", - "rand_core 0.6.4", - "serde", - "subtle", - "thiserror 2.0.17", - "tokio", - "tracing", - "zeroize", - "zksign", -] - -[[package]] -name = "key-management-system-macros" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.111", -] - -[[package]] -name = "key-management-system-service" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "key-management-system-keys", - "log", - "overwatch", - "serde", - "thiserror 2.0.17", - "tokio", - "tracing", -] - [[package]] name = "key_protocol" version = "0.1.0" @@ -3149,41 +2920,6 @@ dependencies = [ "thiserror 2.0.17", ] -[[package]] -name = "kzgrs" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-bls12-381", - "ark-ec 0.4.2", - "ark-ff 0.4.2", - "ark-poly 0.4.2", - "ark-poly-commit", - "ark-serialize 0.4.2", - "blake2", - "blst", - "num-bigint", - "num-traits", - "rand 0.8.5", - "thiserror 1.0.69", -] - -[[package]] -name = "kzgrs-backend" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-ff 0.4.2", - "ark-poly 0.4.2", - "ark-serialize 0.4.2", - "blake2", - "itertools 0.12.1", - "kzgrs", - "nomos-core", - "rand 0.8.5", - "serde", -] - [[package]] name = "language-tags" version = "0.3.2" @@ -3253,7 +2989,6 @@ dependencies = [ "bs58", "hkdf", "multihash", - "serde", "sha2", "thiserror 2.0.17", "tracing", @@ -3349,6 +3084,483 @@ version = "0.4.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" +[[package]] +name = "logos-blockchain-blend-crypto" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "blake2", + "logos-blockchain-groth16", + "logos-blockchain-poq", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "rs-merkle-tree", + "thiserror 1.0.69", +] + +[[package]] +name = "logos-blockchain-blend-message" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "blake2", + "derivative", + "itertools 0.14.0", + "logos-blockchain-blend-crypto", + "logos-blockchain-blend-proofs", + "logos-blockchain-core", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-utils", + "serde", + "serde-big-array", + "serde_with", + "thiserror 1.0.69", + "tracing", + "zeroize", +] + +[[package]] +name = "logos-blockchain-blend-proofs" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ed25519-dalek", + "generic-array 1.3.5", + "logos-blockchain-blend-crypto", + "logos-blockchain-groth16", + "logos-blockchain-poq", + "num-bigint", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "logos-blockchain-chain-broadcast-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "derivative", + "futures", + "logos-blockchain-core", + "overwatch", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-chain-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "bytes", + "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-utils", + "num-bigint", + "overwatch", + "serde", + "serde_with", + "strum", + "thiserror 1.0.69", + "tokio", + "tracing", + "tracing-futures", +] + +[[package]] +name = "logos-blockchain-circuits-prover" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-utils", + "tempfile", +] + +[[package]] +name = "logos-blockchain-circuits-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "dirs", +] + +[[package]] +name = "logos-blockchain-common-http-client" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "futures", + "logos-blockchain-chain-broadcast-service", + "logos-blockchain-chain-service", + "logos-blockchain-core", + "logos-blockchain-http-api-common", + "reqwest", + "serde", + "serde_json", + "thiserror 1.0.69", + "url", +] + +[[package]] +name = "logos-blockchain-core" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-ff 0.4.2", + "bincode", + "blake2", + "bytes", + "const-hex", + "futures", + "generic-array 1.3.5", + "hex", + "logos-blockchain-blend-proofs", + "logos-blockchain-cryptarchia-engine", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-keys", + "logos-blockchain-pol", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "multiaddr", + "nom 8.0.0", + "num-bigint", + "serde", + "strum", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "logos-blockchain-cryptarchia-engine" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "cfg_eval", + "logos-blockchain-utils", + "serde", + "serde_with", + "thiserror 1.0.69", + "time", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-cryptarchia-sync" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "bytes", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "rand 0.8.5", + "serde", + "serde_with", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-groth16" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ec 0.4.2", + "ark-ff 0.4.2", + "ark-groth16 0.4.0", + "ark-serialize 0.4.2", + "generic-array 1.3.5", + "hex", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "logos-blockchain-http-api-common" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "axum", + "governor", + "logos-blockchain-core", + "logos-blockchain-key-management-system-keys", + "serde", + "serde_json", + "serde_with", + "tower_governor", +] + +[[package]] +name = "logos-blockchain-key-management-system-keys" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "bytes", + "ed25519-dalek", + "generic-array 1.3.5", + "logos-blockchain-blend-proofs", + "logos-blockchain-groth16", + "logos-blockchain-key-management-system-macros", + "logos-blockchain-poseidon2", + "logos-blockchain-utils", + "logos-blockchain-zksign", + "num-bigint", + "rand_core 0.6.4", + "serde", + "subtle", + "thiserror 2.0.17", + "tokio", + "tracing", + "x25519-dalek", + "zeroize", +] + +[[package]] +name = "logos-blockchain-key-management-system-macros" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "logos-blockchain-key-management-system-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "log", + "logos-blockchain-key-management-system-keys", + "overwatch", + "serde", + "thiserror 2.0.17", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-ledger" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +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-utils", + "logos-blockchain-utxotree", + "num-bigint", + "rand 0.8.5", + "rpds", + "serde", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "logos-blockchain-mmr" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-ff 0.4.2", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "rpds", + "serde", +] + +[[package]] +name = "logos-blockchain-network-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-sync", + "overwatch", + "serde", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-pol" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-utils", + "logos-blockchain-groth16", + "logos-blockchain-witness-generator", + "num-bigint", + "num-traits", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "logos-blockchain-poq" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-utils", + "logos-blockchain-groth16", + "logos-blockchain-pol", + "logos-blockchain-witness-generator", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.17", +] + +[[package]] +name = "logos-blockchain-poseidon2" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-bn254 0.4.0", + "ark-ff 0.4.2", + "jf-poseidon2", + "num-bigint", +] + +[[package]] +name = "logos-blockchain-services-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "futures", + "log", + "overwatch", + "serde", + "serde_json", + "thiserror 1.0.69", + "tracing", +] + +[[package]] +name = "logos-blockchain-storage-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "bytes", + "futures", + "logos-blockchain-core", + "logos-blockchain-cryptarchia-engine", + "overwatch", + "serde", + "thiserror 1.0.69", + "tokio", + "tracing", +] + +[[package]] +name = "logos-blockchain-time-service" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "futures", + "log", + "logos-blockchain-cryptarchia-engine", + "overwatch", + "sntpc", + "thiserror 2.0.17", + "time", + "tokio", + "tokio-stream", + "tracing", +] + +[[package]] +name = "logos-blockchain-utils" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "async-trait", + "blake2", + "cipher", + "const-hex", + "humantime", + "overwatch", + "rand 0.8.5", + "serde", + "serde_with", + "time", +] + +[[package]] +name = "logos-blockchain-utxotree" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "ark-ff 0.4.2", + "logos-blockchain-core", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "num-bigint", + "rpds", + "serde", + "thiserror 1.0.69", +] + +[[package]] +name = "logos-blockchain-witness-generator" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "tempfile", +] + +[[package]] +name = "logos-blockchain-zksign" +version = "0.1.0" +source = "git+https://github.com/logos-blockchain/logos-blockchain.git#451df112f8574aea2840d04fffb7e16e76d24f42" +dependencies = [ + "logos-blockchain-circuits-prover", + "logos-blockchain-circuits-utils", + "logos-blockchain-groth16", + "logos-blockchain-poseidon2", + "logos-blockchain-witness-generator", + "num-bigint", + "serde", + "serde_json", + "thiserror 2.0.17", +] + [[package]] name = "lru-slab" version = "0.1.2" @@ -3456,18 +3668,6 @@ dependencies = [ "windows-sys 0.61.2", ] -[[package]] -name = "mmr" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-ff 0.4.2", - "groth16", - "poseidon2", - "rpds", - "serde", -] - [[package]] name = "multiaddr" version = "0.18.2" @@ -3574,194 +3774,6 @@ dependencies = [ "memchr", ] -[[package]] -name = "nomos-blend-crypto" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "blake2", - "groth16", - "nomos-utils", - "poq", - "poseidon2", - "rs-merkle-tree", - "serde", - "subtle", - "thiserror 1.0.69", - "x25519-dalek", - "zeroize", -] - -[[package]] -name = "nomos-blend-message" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "blake2", - "derivative", - "groth16", - "itertools 0.14.0", - "key-management-system-keys", - "nomos-blend-crypto", - "nomos-blend-proofs", - "nomos-core", - "nomos-utils", - "serde", - "serde-big-array", - "serde_with", - "thiserror 1.0.69", - "tracing", - "zeroize", -] - -[[package]] -name = "nomos-blend-proofs" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ed25519-dalek", - "generic-array 1.3.5", - "groth16", - "nomos-blend-crypto", - "num-bigint", - "poq", - "serde", - "thiserror 1.0.69", -] - -[[package]] -name = "nomos-core" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-ff 0.4.2", - "async-trait", - "bincode", - "blake2", - "bytes", - "const-hex", - "cryptarchia-engine", - "futures", - "generic-array 1.3.5", - "groth16", - "hex", - "key-management-system-keys", - "multiaddr", - "nom 8.0.0", - "nomos-blend-proofs", - "nomos-utils", - "num-bigint", - "pol", - "poseidon2", - "serde", - "strum", - "thiserror 1.0.69", - "tracing", -] - -[[package]] -name = "nomos-da-messages" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "blake2", - "futures", - "kzgrs-backend", - "nomos-core", - "serde", - "tokio", -] - -[[package]] -name = "nomos-http-api-common" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "axum", - "governor", - "key-management-system-keys", - "nomos-core", - "serde", - "serde_json", - "serde_with", - "tower_governor", -] - -[[package]] -name = "nomos-ledger" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "cryptarchia-engine", - "groth16", - "key-management-system-keys", - "mmr", - "nomos-blend-crypto", - "nomos-blend-message", - "nomos-blend-proofs", - "nomos-core", - "nomos-utils", - "num-bigint", - "rand 0.8.5", - "rpds", - "serde", - "thiserror 1.0.69", - "tracing", - "utxotree", -] - -[[package]] -name = "nomos-network" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "cryptarchia-sync", - "futures", - "nomos-core", - "overwatch", - "serde", - "tokio", - "tokio-stream", - "tracing", -] - -[[package]] -name = "nomos-storage" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "bytes", - "cryptarchia-engine", - "futures", - "libp2p-identity", - "multiaddr", - "nomos-core", - "overwatch", - "serde", - "thiserror 1.0.69", - "tokio", - "tracing", -] - -[[package]] -name = "nomos-utils" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "blake2", - "cipher", - "const-hex", - "humantime", - "overwatch", - "rand 0.8.5", - "serde", - "serde_with", - "time", -] - [[package]] name = "nonempty" version = "0.7.0" @@ -3792,6 +3804,7 @@ dependencies = [ "secp256k1", "serde", "sha2", + "test-case", "test_program_methods", "thiserror 2.0.17", ] @@ -3874,16 +3887,6 @@ dependencies = [ "libm", ] -[[package]] -name = "num_cpus" -version = "1.17.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91df4bbde75afed763b708b7eee1e8e7651e02d97f6d5dd763e89367e957b23b" -dependencies = [ - "hermit-abi", - "libc", -] - [[package]] name = "num_enum" version = "0.7.5" @@ -4123,22 +4126,6 @@ version = "0.3.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" -[[package]] -name = "pol" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "circuits-prover", - "circuits-utils", - "groth16", - "num-bigint", - "num-traits", - "serde", - "serde_json", - "thiserror 2.0.17", - "witness-generator", -] - [[package]] name = "polyval" version = "0.6.2" @@ -4151,39 +4138,12 @@ dependencies = [ "universal-hash", ] -[[package]] -name = "poq" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "circuits-prover", - "circuits-utils", - "groth16", - "num-bigint", - "pol", - "serde", - "serde_json", - "thiserror 2.0.17", - "witness-generator", -] - [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" -[[package]] -name = "poseidon2" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-bn254 0.4.0", - "ark-ff 0.4.2", - "jf-poseidon2", - "num-bigint", -] - [[package]] name = "postcard" version = "1.1.3" @@ -5162,10 +5122,10 @@ dependencies = [ "chrono", "common", "futures", - "key-management-system-service", "log", + "logos-blockchain-core", + "logos-blockchain-key-management-system-service", "mempool", - "nomos-core", "nssa", "nssa_core", "rand 0.8.5", @@ -5342,21 +5302,6 @@ dependencies = [ "serde", ] -[[package]] -name = "services-utils" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "async-trait", - "futures", - "log", - "overwatch", - "serde", - "serde_json", - "thiserror 1.0.69", - "tracing", -] - [[package]] name = "sha1" version = "0.10.6" @@ -5416,6 +5361,16 @@ version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" +[[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.4.10" @@ -5621,6 +5576,39 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-case" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb2550dd13afcd286853192af8601920d959b14c401fcece38071d53bf0768a8" +dependencies = [ + "test-case-macros", +] + +[[package]] +name = "test-case-core" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adcb7fd841cd518e279be3d5a3eb0636409487998a4aff22f3de87b81e88384f" +dependencies = [ + "cfg-if", + "proc-macro2", + "quote", + "syn 2.0.111", +] + +[[package]] +name = "test-case-macros" +version = "3.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c89e72a01ed4c579669add59014b9a524d609c0c88c6a585ce37485879f6ffb" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.111", + "test-case-core", +] + [[package]] name = "test_program_methods" version = "0.1.0" @@ -5676,15 +5664,6 @@ dependencies = [ "syn 2.0.111", ] -[[package]] -name = "threadpool" -version = "1.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa" -dependencies = [ - "num_cpus", -] - [[package]] name = "time" version = "0.3.44" @@ -6128,21 +6107,6 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" -[[package]] -name = "utxotree" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "ark-ff 0.4.2", - "groth16", - "nomos-core", - "num-bigint", - "poseidon2", - "rpds", - "serde", - "thiserror 1.0.69", -] - [[package]] name = "valuable" version = "0.1.1" @@ -6587,15 +6551,6 @@ version = "0.46.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -[[package]] -name = "witness-generator" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "circuits-utils", - "tempfile", -] - [[package]] name = "writeable" version = "0.6.2" @@ -6741,19 +6696,3 @@ dependencies = [ "quote", "syn 2.0.111", ] - -[[package]] -name = "zksign" -version = "0.1.0" -source = "git+https://github.com/logos-blockchain/logos-blockchain.git#b89238be3ad8111b9975e1023b87d8672d0edd74" -dependencies = [ - "circuits-prover", - "circuits-utils", - "groth16", - "num-bigint", - "poseidon2", - "serde", - "serde_json", - "thiserror 2.0.17", - "witness-generator", -] diff --git a/Cargo.toml b/Cargo.toml index 35b6597a..f14f2559 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -79,9 +79,9 @@ base58 = "0.2.0" itertools = "0.14.0" url = "2.5.4" -common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } -key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } -nomos-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +logos-blockchain-common-http-client = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +logos-blockchain-key-management-system-service = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } +logos-blockchain-core = { git = "https://github.com/logos-blockchain/logos-blockchain.git" } rocksdb = { version = "0.24.0", default-features = false, features = [ "snappy", diff --git a/README.md b/README.md index cc20d220..f7885be0 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,8 @@ cargo install --path wallet --force Run `wallet help` to check everything went well. +Some completion scripts exists, see the [completions](./completions/README.md) folder. + ## Tutorial This tutorial walks you through creating accounts and executing NSSA programs in both public and private contexts. diff --git a/artifacts/program_methods/amm.bin b/artifacts/program_methods/amm.bin index 354e6556..1a2fabca 100644 Binary files a/artifacts/program_methods/amm.bin and b/artifacts/program_methods/amm.bin differ diff --git a/artifacts/program_methods/authenticated_transfer.bin b/artifacts/program_methods/authenticated_transfer.bin index 1b72c464..73d5fec1 100644 Binary files a/artifacts/program_methods/authenticated_transfer.bin and b/artifacts/program_methods/authenticated_transfer.bin differ diff --git a/artifacts/program_methods/pinata.bin b/artifacts/program_methods/pinata.bin index 91d2b934..278e88f4 100644 Binary files a/artifacts/program_methods/pinata.bin and b/artifacts/program_methods/pinata.bin differ diff --git a/artifacts/program_methods/pinata_token.bin b/artifacts/program_methods/pinata_token.bin index 8da8b894..4a4c8bb6 100644 Binary files a/artifacts/program_methods/pinata_token.bin and b/artifacts/program_methods/pinata_token.bin differ diff --git a/artifacts/program_methods/privacy_preserving_circuit.bin b/artifacts/program_methods/privacy_preserving_circuit.bin index 404f32b1..d8f5915e 100644 Binary files a/artifacts/program_methods/privacy_preserving_circuit.bin and b/artifacts/program_methods/privacy_preserving_circuit.bin differ diff --git a/artifacts/program_methods/token.bin b/artifacts/program_methods/token.bin index 22693413..5f6d3781 100644 Binary files a/artifacts/program_methods/token.bin and b/artifacts/program_methods/token.bin differ diff --git a/artifacts/test_program_methods/burner.bin b/artifacts/test_program_methods/burner.bin index 6bbc5071..96e339c2 100644 Binary files a/artifacts/test_program_methods/burner.bin and b/artifacts/test_program_methods/burner.bin differ diff --git a/artifacts/test_program_methods/chain_caller.bin b/artifacts/test_program_methods/chain_caller.bin index 56b017e7..731d2dc7 100644 Binary files a/artifacts/test_program_methods/chain_caller.bin and b/artifacts/test_program_methods/chain_caller.bin differ diff --git a/artifacts/test_program_methods/changer_claimer.bin b/artifacts/test_program_methods/changer_claimer.bin new file mode 100644 index 00000000..692d152b Binary files /dev/null and b/artifacts/test_program_methods/changer_claimer.bin differ diff --git a/artifacts/test_program_methods/claimer.bin b/artifacts/test_program_methods/claimer.bin index 0f42aaed..da0a9bef 100644 Binary files a/artifacts/test_program_methods/claimer.bin and b/artifacts/test_program_methods/claimer.bin differ diff --git a/artifacts/test_program_methods/data_changer.bin b/artifacts/test_program_methods/data_changer.bin index 99089d33..86fde894 100644 Binary files a/artifacts/test_program_methods/data_changer.bin and b/artifacts/test_program_methods/data_changer.bin differ diff --git a/artifacts/test_program_methods/extra_output.bin b/artifacts/test_program_methods/extra_output.bin index ffb622f5..1ae1cd98 100644 Binary files a/artifacts/test_program_methods/extra_output.bin and b/artifacts/test_program_methods/extra_output.bin differ diff --git a/artifacts/test_program_methods/malicious_authorization_changer.bin b/artifacts/test_program_methods/malicious_authorization_changer.bin new file mode 100644 index 00000000..8f80ab58 Binary files /dev/null and b/artifacts/test_program_methods/malicious_authorization_changer.bin differ diff --git a/artifacts/test_program_methods/minter.bin b/artifacts/test_program_methods/minter.bin index 08a57ea4..50199403 100644 Binary files a/artifacts/test_program_methods/minter.bin and b/artifacts/test_program_methods/minter.bin differ diff --git a/artifacts/test_program_methods/missing_output.bin b/artifacts/test_program_methods/missing_output.bin index 891acd58..4994ae3f 100644 Binary files a/artifacts/test_program_methods/missing_output.bin and b/artifacts/test_program_methods/missing_output.bin differ diff --git a/artifacts/test_program_methods/modified_transfer.bin b/artifacts/test_program_methods/modified_transfer.bin index 17793836..796de2d3 100644 Binary files a/artifacts/test_program_methods/modified_transfer.bin and b/artifacts/test_program_methods/modified_transfer.bin differ diff --git a/artifacts/test_program_methods/nonce_changer.bin b/artifacts/test_program_methods/nonce_changer.bin index 9dcb36a9..017de8b3 100644 Binary files a/artifacts/test_program_methods/nonce_changer.bin and b/artifacts/test_program_methods/nonce_changer.bin differ diff --git a/artifacts/test_program_methods/noop.bin b/artifacts/test_program_methods/noop.bin index fba84578..23195ef2 100644 Binary files a/artifacts/test_program_methods/noop.bin and b/artifacts/test_program_methods/noop.bin differ diff --git a/artifacts/test_program_methods/program_owner_changer.bin b/artifacts/test_program_methods/program_owner_changer.bin index 6daa6a11..db1f87fa 100644 Binary files a/artifacts/test_program_methods/program_owner_changer.bin and b/artifacts/test_program_methods/program_owner_changer.bin differ diff --git a/artifacts/test_program_methods/simple_balance_transfer.bin b/artifacts/test_program_methods/simple_balance_transfer.bin index 4fa42b26..17c95475 100644 Binary files a/artifacts/test_program_methods/simple_balance_transfer.bin and b/artifacts/test_program_methods/simple_balance_transfer.bin differ diff --git a/bedrock_client/Cargo.toml b/bedrock_client/Cargo.toml index e6f6f02a..50a54815 100644 --- a/bedrock_client/Cargo.toml +++ b/bedrock_client/Cargo.toml @@ -6,5 +6,5 @@ edition = "2024" [dependencies] reqwest.workspace = true anyhow.workspace = true -common-http-client.workspace = true -nomos-core.workspace = true +logos-blockchain-common-http-client.workspace = true +logos-blockchain-core.workspace = true diff --git a/bedrock_client/src/lib.rs b/bedrock_client/src/lib.rs index a5e0a049..530fdfc2 100644 --- a/bedrock_client/src/lib.rs +++ b/bedrock_client/src/lib.rs @@ -1,7 +1,6 @@ use anyhow::Result; -use common_http_client::CommonHttpClient; -pub use common_http_client::{BasicAuthCredentials, Error}; -use nomos_core::mantle::SignedMantleTx; +pub use logos_blockchain_common_http_client::{BasicAuthCredentials, CommonHttpClient, Error}; +use logos_blockchain_core::mantle::SignedMantleTx; use reqwest::{Client, Url}; // Simple wrapper @@ -14,7 +13,7 @@ pub struct BedrockClient { impl BedrockClient { pub fn new(auth: Option, node_url: Url) -> Result { let client = Client::builder() - //Add more fiedls if needed + //Add more fields if needed .timeout(std::time::Duration::from_secs(60)) .build()?; diff --git a/completions/README.md b/completions/README.md new file mode 100644 index 00000000..4695c9e6 --- /dev/null +++ b/completions/README.md @@ -0,0 +1,135 @@ +# Wallet CLI Completion + +Completion scripts for the LSSA `wallet` command. + +## ZSH + +Works with both vanilla zsh and oh-my-zsh. + +### Features + +- Full completion for all wallet subcommands +- Contextual option completion for each command +- Dynamic account ID completion via `wallet account list` +- Descriptions for all commands and options + +Note that only accounts created by the user auto-complete. +Preconfigured accounts and accounts only with `/` (no number) are not completed. + +e.g.: + +``` +▶ wallet account list +Preconfigured Public/Gj1mJy5W7J5pfmLRujmQaLfLMWidNxQ6uwnhb666ZwHw, +Preconfigured Public/BLgCRDXYdQPMMWVHYRFGQZbgeHx9frkipa8GtpG2Syqy, +Preconfigured Private/3oCG8gqdKLMegw4rRfyaMQvuPHpcASt7xwttsmnZLSkw, +Preconfigured Private/AKTcXgJ1xoynta1Ec7y6Jso1z1JQtHqd7aPQ1h9er6xX, +/ Public/8DstRgMQrB2N9a7ymv98RDDbt8nctrP9ZzaNRSpKDZSu, +/0 Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH, +/ Private/Bcv15B36bs1VqvQAdY6ZGFM1KioByNQQsB92KTNAx6u2 +``` + +Only `Public/2gJJjtG9UivBGEhA1Jz6waZQx1cwfYupC5yvKEweHaeH` is used for completion. + +### Supported Commands + +| Command | Description | +|------------------------|-------------------------------------------------------------| +| `wallet auth-transfer` | Authenticated transfer (init, send) | +| `wallet chain-info` | Chain info queries (current-block-id, block, transaction) | +| `wallet account` | Account management (get, list, new, sync-private) | +| `wallet pinata` | Piñata faucet (claim) | +| `wallet token` | Token operations (new, send) | +| `wallet amm` | AMM operations (new, swap, add-liquidity, remove-liquidity) | +| `wallet check-health` | Health check | + +### Installation + +#### Vanilla Zsh + +1. Create a completions directory: + + ```sh + mkdir -p ~/.zsh/completions + ``` + +2. Copy the completion file: + + ```sh + cp ./zsh/_wallet ~/.zsh/completions/ + ``` + +3. Add to your `~/.zshrc` (before any `compinit` call, or add these lines if you don't have one): + + ```sh + fpath=(~/.zsh/completions $fpath) + autoload -Uz compinit && compinit + ``` + +4. Reload your shell: + + ```sh + exec zsh + ``` + +#### Oh-My-Zsh + +1. Create the plugin directory and copy the file: + + ```sh + mkdir -p ~/.oh-my-zsh/custom/plugins/wallet + cp _wallet ~/.oh-my-zsh/custom/plugins/wallet/ + ``` + +2. Add `wallet` to your plugins array in `~/.zshrc`: + + ```sh + plugins=(... wallet) + ``` + +3. Reload your shell: + + ```sh + exec zsh + ``` + +### Requirements + +The completion script calls `wallet account list` to dynamically fetch account IDs. Ensure the `wallet` command is in your `$PATH`. + +### Usage + +```sh +# Main commands +wallet + +# Account subcommands +wallet account + +# Options for auth-transfer send +wallet auth-transfer send -- + +# Account types when creating +wallet account new +# Shows: public private + +# Account IDs (fetched dynamically) +wallet account get --account-id +# Shows: Public/... Private/... +``` + +## Troubleshooting + +### Completions not appearing + +1. Check that `compinit` is called in your `.zshrc` +2. Rebuild the completion cache: + + ```sh + rm -f ~/.zcompdump* + exec zsh + ``` + +### Account IDs not completing + +Ensure `wallet account list` works from your command line. diff --git a/completions/zsh/_wallet b/completions/zsh/_wallet new file mode 100644 index 00000000..d2831ef0 --- /dev/null +++ b/completions/zsh/_wallet @@ -0,0 +1,435 @@ +#compdef wallet + +# Zsh completion script for the wallet CLI +# See instructions in ../README.md + +_wallet() { + local -a commands + local -a subcommands + local curcontext="$curcontext" state line + typeset -A opt_args + + _arguments -C \ + '(-c --continuous-run)'{-c,--continuous-run}'[Continuous run flag]' \ + '--auth[Basic authentication in the format user or user\:password]:auth:' \ + '1: :->command' \ + '*:: :->args' + + case $state in + command) + commands=( + 'auth-transfer:Authenticated transfer subcommand' + 'chain-info:Generic chain info subcommand' + 'account:Account view and sync subcommand' + 'pinata:Pinata program interaction subcommand' + 'token:Token program interaction subcommand' + 'amm:AMM program interaction subcommand' + 'check-health:Check the wallet can connect to the node and builtin local programs match the remote versions' + 'config:Command to setup config, get and set config fields' + 'restore-keys:Restoring keys from given password at given depth' + 'deploy-program:Deploy a program' + 'help:Print help message or the help of the given subcommand(s)' + ) + _describe -t commands 'wallet commands' commands + ;; + args) + case $line[1] in + auth-transfer) + _wallet_auth_transfer + ;; + chain-info) + _wallet_chain_info + ;; + account) + _wallet_account + ;; + pinata) + _wallet_pinata + ;; + token) + _wallet_token + ;; + amm) + _wallet_amm + ;; + config) + _wallet_config + ;; + restore-keys) + _wallet_restore_keys + ;; + deploy-program) + _wallet_deploy_program + ;; + help) + _wallet_help + ;; + esac + ;; + esac +} + +# auth-transfer subcommand +_wallet_auth_transfer() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'init:Initialize account under authenticated transfer program' + 'send:Send native tokens from one account to another with variable privacy' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'auth-transfer subcommands' subcommands + ;; + args) + case $line[1] in + init) + _arguments \ + '--account-id[Account ID to initialize]:account_id:_wallet_account_ids' + ;; + send) + _arguments \ + '--from[Source account ID]:from_account:_wallet_account_ids' \ + '--to[Destination account ID (for owned accounts)]:to_account:_wallet_account_ids' \ + '--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \ + '--to-ipk[Destination viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of native tokens to send]:amount:' + ;; + esac + ;; + esac +} + +# chain-info subcommand +_wallet_chain_info() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'current-block-id:Get current block id from sequencer' + 'block:Get block at id from sequencer' + 'transaction:Get transaction at hash from sequencer' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'chain-info subcommands' subcommands + ;; + args) + case $line[1] in + block) + _arguments \ + '--id[Block ID to retrieve]:block_id:' + ;; + transaction) + _arguments \ + '--hash[Transaction hash to retrieve]:tx_hash:' + ;; + esac + ;; + esac +} + +# account subcommand +_wallet_account() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'get:Get account data' + 'list:List all accounts' + 'ls:List all accounts (alias for list)' + 'new:Produce new public or private account' + 'sync-private:Sync private accounts' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'account subcommands' subcommands + ;; + args) + case $line[1] in + get) + _arguments \ + '(-r --raw)'{-r,--raw}'[Get raw account data]' \ + '(-k --keys)'{-k,--keys}'[Display keys (pk for public accounts, npk/ipk for private accounts)]' \ + '(-a --account-id)'{-a,--account-id}'[Account ID to query]:account_id:_wallet_account_ids' + ;; + list|ls) + _arguments \ + '(-l --long)'{-l,--long}'[Display detailed account information]' + ;; + new) + _arguments -C \ + '1: :->account_type' \ + '*:: :->new_args' + case $state in + account_type) + compadd public private + ;; + new_args) + _arguments \ + '--cci[Chain index of a parent node]:chain_index:' + ;; + esac + ;; + esac + ;; + esac +} + +# pinata subcommand +_wallet_pinata() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'claim:Claim tokens from the Piñata faucet' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'pinata subcommands' subcommands + ;; + args) + case $line[1] in + claim) + _arguments \ + '--to[Destination account ID to receive claimed tokens]:to_account:_wallet_account_ids' + ;; + esac + ;; + esac +} + +# token subcommand +_wallet_token() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'new:Produce a new token' + 'send:Send tokens from one account to another with variable privacy' + 'burn:Burn tokens on holder, modify definition' + 'mint:Mint tokens on holder, modify definition' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'token subcommands' subcommands + ;; + args) + case $line[1] in + new) + _arguments \ + '--name[Token name]:name:' \ + '--total-supply[Total supply of tokens to mint]:total_supply:' \ + '--definition-account-id[Account ID for token definition]:definition_account:_wallet_account_ids' \ + '--supply-account-id[Account ID to receive initial supply]:supply_account:_wallet_account_ids' + ;; + send) + _arguments \ + '--from[Source holding account ID]:from_account:_wallet_account_ids' \ + '--to[Destination holding account ID (for owned accounts)]:to_account:_wallet_account_ids' \ + '--to-npk[Destination nullifier public key (for foreign private accounts)]:npk:' \ + '--to-ipk[Destination viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of tokens to send]:amount:' + ;; + burn) + _arguments \ + '--definition[Definition account ID]:definition_account:_wallet_account_ids' \ + '--holder[Holder account ID]:holder_account:_wallet_account_ids' \ + '--amount[Amount of tokens to burn]:amount:' + ;; + mint) + _arguments \ + '--definition[Definition account ID]:definition_account:_wallet_account_ids' \ + '--holder[Holder account ID (for owned accounts)]:holder_account:_wallet_account_ids' \ + '--holder-npk[Holder nullifier public key (for foreign private accounts)]:npk:' \ + '--holder-ipk[Holder viewing public key (for foreign private accounts)]:ipk:' \ + '--amount[Amount of tokens to mint]:amount:' + ;; + esac + ;; + esac +} + +# amm subcommand +_wallet_amm() { + local -a subcommands + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'new:Create a new liquidity pool' + 'swap:Swap tokens using the AMM' + 'add-liquidity:Add liquidity to an existing pool' + 'remove-liquidity:Remove liquidity from a pool' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'amm subcommands' subcommands + ;; + args) + case $line[1] in + new) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--balance-a[Amount of token A to deposit]:balance_a:' \ + '--balance-b[Amount of token B to deposit]:balance_b:' + ;; + swap) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--amount-in[Amount of tokens to swap]:amount_in:' \ + '--min-amount-out[Minimum tokens expected in return]:min_amount_out:' \ + '--token-definition[Definition ID of the token being provided]:token_def:' + ;; + add-liquidity) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--max-amount-a[Maximum amount of token A to deposit]:max_amount_a:' \ + '--max-amount-b[Maximum amount of token B to deposit]:max_amount_b:' \ + '--min-amount-lp[Minimum LP tokens to receive]:min_amount_lp:' + ;; + remove-liquidity) + _arguments \ + '--user-holding-a[User token A holding account ID]:holding_a:_wallet_account_ids' \ + '--user-holding-b[User token B holding account ID]:holding_b:_wallet_account_ids' \ + '--user-holding-lp[User LP token holding account ID]:holding_lp:_wallet_account_ids' \ + '--balance-lp[Amount of LP tokens to burn]:balance_lp:' \ + '--min-amount-a[Minimum token A to receive]:min_amount_a:' \ + '--min-amount-b[Minimum token B to receive]:min_amount_b:' + ;; + esac + ;; + esac +} + +# config subcommand +_wallet_config() { + local -a subcommands + local -a config_keys + + config_keys=( + 'all' + 'override_rust_log' + 'sequencer_addr' + 'seq_poll_timeout_millis' + 'seq_tx_poll_max_blocks' + 'seq_poll_max_retries' + 'seq_block_poll_max_amount' + 'initial_accounts' + 'basic_auth' + ) + + _arguments -C \ + '1: :->subcommand' \ + '*:: :->args' + + case $state in + subcommand) + subcommands=( + 'get:Getter of config fields' + 'set:Setter of config fields' + 'description:Prints description of corresponding field' + 'help:Print this message or the help of the given subcommand(s)' + ) + _describe -t subcommands 'config subcommands' subcommands + ;; + args) + case $line[1] in + get|description) + compadd -a config_keys + ;; + set) + _arguments \ + '1:key:compadd -a config_keys' \ + '2:value:' + ;; + esac + ;; + esac +} + +# restore-keys subcommand +_wallet_restore_keys() { + _arguments \ + '(-d --depth)'{-d,--depth}'[How deep in tree accounts may be]:depth:' +} + +# deploy-program subcommand +_wallet_deploy_program() { + _arguments \ + '1:binary filepath:_files' +} + +# help subcommand +_wallet_help() { + local -a commands + commands=( + 'auth-transfer:Authenticated transfer subcommand' + 'chain-info:Generic chain info subcommand' + 'account:Account view and sync subcommand' + 'pinata:Pinata program interaction subcommand' + 'token:Token program interaction subcommand' + 'amm:AMM program interaction subcommand' + 'check-health:Check the wallet can connect to the node' + 'config:Command to setup config, get and set config fields' + 'restore-keys:Restoring keys from given password at given depth' + 'deploy-program:Deploy a program' + ) + _describe -t commands 'wallet commands' commands +} + +# Helper function to complete account IDs +# Uses `wallet account list` to get available accounts +# Only includes accounts with /N prefix (where N is a number) +_wallet_account_ids() { + local -a accounts + local line + + # Try to get accounts from wallet account list command + # Filter to lines starting with /N (numbered accounts) and extract the account ID + if command -v wallet &>/dev/null; then + while IFS= read -r line; do + # Remove trailing comma if present and add to array + [[ -n "$line" ]] && accounts+=("${line%,}") + done < <(wallet account list 2>/dev/null | grep '^/[0-9]' | awk '{print $2}') + fi + + # Provide type prefixes as fallback if command fails or returns nothing + if (( ${#accounts} == 0 )); then + compadd -S '' -- 'Public/' 'Private/' + return + fi + + _multi_parts / accounts +} + +_wallet "$@" diff --git a/examples/program_deployment/src/bin/run_hello_world_private.rs b/examples/program_deployment/src/bin/run_hello_world_private.rs index cb66d42f..27ac2079 100644 --- a/examples/program_deployment/src/bin/run_hello_world_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_private.rs @@ -50,7 +50,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(greeting).unwrap(), + Program::serialize_instruction(greeting).unwrap(), &program.into(), ) .await diff --git a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs index bd67cfe6..9b3619cb 100644 --- a/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs +++ b/examples/program_deployment/src/bin/run_hello_world_through_tail_call_private.rs @@ -58,7 +58,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &program_with_dependencies, ) .await diff --git a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs index d879ab6f..fc116241 100644 --- a/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs +++ b/examples/program_deployment/src/bin/run_hello_world_with_move_function.rs @@ -101,7 +101,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &program.into(), ) .await @@ -142,7 +142,7 @@ async fn main() { wallet_core .send_privacy_preserving_tx( accounts, - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &program.into(), ) .await diff --git a/integration_tests/tests/pinata.rs b/integration_tests/tests/pinata.rs index c627cea2..a3f2b4b7 100644 --- a/integration_tests/tests/pinata.rs +++ b/integration_tests/tests/pinata.rs @@ -11,11 +11,13 @@ use tokio::test; use wallet::cli::{ Command, SubcommandReturnValue, account::{AccountSubcommand, NewSubcommand}, - programs::pinata::PinataProgramAgnosticSubcommand, + programs::{ + native_token_transfer::AuthTransferSubcommand, pinata::PinataProgramAgnosticSubcommand, + }, }; #[test] -async fn claim_pinata_to_public_account() -> Result<()> { +async fn claim_pinata_to_existing_public_account() -> Result<()> { let mut ctx = TestContext::new().await?; let pinata_prize = 150; @@ -120,8 +122,26 @@ async fn claim_pinata_to_new_private_account() -> Result<()> { anyhow::bail!("Expected RegisterAccount return value"); }; + let winner_account_id_formatted = format_private_account_id(&winner_account_id.to_string()); + + // Initialize account under auth transfer program + let command = Command::AuthTransfer(AuthTransferSubcommand::Init { + account_id: winner_account_id_formatted.clone(), + }); + wallet::cli::execute_subcommand(ctx.wallet_mut(), command).await?; + + info!("Waiting for next block creation"); + tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; + + let new_commitment = ctx + .wallet() + .get_private_account_commitment(&winner_account_id) + .context("Failed to get private account commitment")?; + assert!(verify_commitment_is_in_state(new_commitment, ctx.sequencer_client()).await); + + // Claim pinata to the new private account let command = Command::Pinata(PinataProgramAgnosticSubcommand::Claim { - to: format_private_account_id(&winner_account_id.to_string()), + to: winner_account_id_formatted, }); let pinata_balance_pre = ctx diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 87f7e45a..3fdc8ac8 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -235,16 +235,16 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { ]], ); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_npk.clone(), sender_ss), (recipient_npk.clone(), recipient_ss), ], - &[sender_nsk], - &[Some(proof)], + vec![sender_nsk], + vec![Some(proof)], &program.into(), ) .unwrap(); diff --git a/nssa/Cargo.toml b/nssa/Cargo.toml index f1c7709b..a508cc08 100644 --- a/nssa/Cargo.toml +++ b/nssa/Cargo.toml @@ -24,8 +24,9 @@ risc0-binfmt = "3.0.2" [dev-dependencies] test_program_methods.workspace = true -hex-literal = "1.0.0" env_logger.workspace = true +hex-literal = "1.0.0" +test-case = "3.3.1" [features] default = [] diff --git a/nssa/core/src/account.rs b/nssa/core/src/account.rs index 51467afe..55ab0ded 100644 --- a/nssa/core/src/account.rs +++ b/nssa/core/src/account.rs @@ -15,9 +15,8 @@ pub type Nonce = u128; /// Account to be used both in public and private contexts #[derive( - Clone, Default, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, + Debug, Default, Clone, Eq, PartialEq, Serialize, Deserialize, BorshSerialize, BorshDeserialize, )] -#[cfg_attr(any(feature = "host", test), derive(Debug))] pub struct Account { pub program_owner: ProgramId, pub balance: u128, @@ -25,8 +24,7 @@ pub struct Account { pub nonce: Nonce, } -#[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] -#[cfg_attr(any(feature = "host", test), derive(Debug))] +#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] pub struct AccountWithMetadata { pub account: Account, pub is_authorized: bool, @@ -45,6 +43,7 @@ impl AccountWithMetadata { } #[derive( + Debug, Default, Copy, Clone, @@ -56,7 +55,7 @@ impl AccountWithMetadata { BorshSerialize, BorshDeserialize, )] -#[cfg_attr(any(feature = "host", test), derive(Debug, PartialOrd, Ord))] +#[cfg_attr(any(feature = "host", test), derive(PartialOrd, Ord))] pub struct AccountId { value: [u8; 32], } diff --git a/nssa/core/src/account/data.rs b/nssa/core/src/account/data.rs index 974cb06c..396bbe6e 100644 --- a/nssa/core/src/account/data.rs +++ b/nssa/core/src/account/data.rs @@ -5,8 +5,7 @@ use serde::{Deserialize, Serialize}; pub const DATA_MAX_LENGTH_IN_BYTES: usize = 100 * 1024; // 100 KiB -#[derive(Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] -#[cfg_attr(any(feature = "host", test), derive(Debug))] +#[derive(Debug, Default, Clone, PartialEq, Eq, Serialize, BorshSerialize)] pub struct Data(Vec); impl Data { diff --git a/nssa/core/src/nullifier.rs b/nssa/core/src/nullifier.rs index ec30700b..8d9d59fa 100644 --- a/nssa/core/src/nullifier.rs +++ b/nssa/core/src/nullifier.rs @@ -4,8 +4,8 @@ use serde::{Deserialize, Serialize}; use crate::{Commitment, account::AccountId}; -#[derive(Serialize, Deserialize, PartialEq, Eq)] -#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, Hash))] +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +#[cfg_attr(any(feature = "host", test), derive(Clone, Hash))] pub struct NullifierPublicKey(pub [u8; 32]); impl From<&NullifierPublicKey> for AccountId { diff --git a/nssa/core/src/program.rs b/nssa/core/src/program.rs index 357a4a58..32b3e2c0 100644 --- a/nssa/core/src/program.rs +++ b/nssa/core/src/program.rs @@ -30,6 +30,20 @@ impl PdaSeed { } } +pub fn compute_authorized_pdas( + caller_program_id: Option, + pda_seeds: &[PdaSeed], +) -> HashSet { + caller_program_id + .map(|caller_program_id| { + pda_seeds + .iter() + .map(|pda_seed| AccountId::from((&caller_program_id, pda_seed))) + .collect() + }) + .unwrap_or_default() +} + impl From<(&ProgramId, &PdaSeed)> for AccountId { fn from(value: (&ProgramId, &PdaSeed)) -> Self { use risc0_zkvm::sha::{Impl, Sha256}; @@ -93,6 +107,13 @@ impl AccountPostState { } } + /// Creates a post state that requests ownership of the account + /// if the account's program owner is the default program ID. + pub fn new_claimed_if_default(account: Account) -> Self { + let claim = account.program_owner == DEFAULT_PROGRAM_ID; + Self { account, claim } + } + /// Returns `true` if this post state requests that the account /// be claimed (owned) by the executing program. pub fn requires_claim(&self) -> bool { @@ -108,6 +129,11 @@ impl AccountPostState { pub fn account_mut(&mut self) -> &mut Account { &mut self.account } + + /// Consumes the post state and returns the underlying account + pub fn into_account(self) -> Account { + self.account + } } #[derive(Serialize, Deserialize, Clone)] diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index f29eb1c5..1b490de8 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -1,11 +1,11 @@ -use std::collections::HashMap; +use std::collections::{HashMap, VecDeque}; use borsh::{BorshDeserialize, BorshSerialize}; use nssa_core::{ MembershipProof, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, SharedSecretKey, account::AccountWithMetadata, - program::{InstructionData, ProgramId, ProgramOutput}, + program::{ChainedCall, InstructionData, ProgramId, ProgramOutput}, }; use risc0_zkvm::{ExecutorEnv, InnerReceipt, Receipt, default_prover}; @@ -43,27 +43,44 @@ impl From for ProgramWithDependencies { } /// Generates a proof of the execution of a NSSA program inside the privacy preserving execution -/// circuit +/// circuit. #[expect(clippy::too_many_arguments, reason = "TODO: fix later")] pub fn execute_and_prove( - pre_states: &[AccountWithMetadata], - instruction_data: &InstructionData, - visibility_mask: &[u8], - private_account_nonces: &[u128], - private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], - private_account_nsks: &[NullifierSecretKey], - private_account_membership_proofs: &[Option], + pre_states: Vec, + instruction_data: InstructionData, + visibility_mask: Vec, + private_account_nonces: Vec, + private_account_keys: Vec<(NullifierPublicKey, SharedSecretKey)>, + private_account_nsks: Vec, + private_account_membership_proofs: Vec>, program_with_dependencies: &ProgramWithDependencies, ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { - let mut program = &program_with_dependencies.program; - let dependencies = &program_with_dependencies.dependencies; - let mut instruction_data = instruction_data.clone(); - let mut pre_states = pre_states.to_vec(); + let ProgramWithDependencies { + program, + dependencies, + } = program_with_dependencies; let mut env_builder = ExecutorEnv::builder(); let mut program_outputs = Vec::new(); - for _i in 0..MAX_NUMBER_CHAINED_CALLS { - let inner_receipt = execute_and_prove_program(program, &pre_states, &instruction_data)?; + let initial_call = ChainedCall { + program_id: program.id(), + instruction_data: instruction_data.clone(), + pre_states, + pda_seeds: vec![], + }; + + let mut chained_calls = VecDeque::from_iter([(initial_call, program)]); + let mut chain_calls_counter = 0; + while let Some((chained_call, program)) = chained_calls.pop_front() { + if chain_calls_counter >= MAX_NUMBER_CHAINED_CALLS { + return Err(NssaError::MaxChainedCallsDepthExceeded); + } + + let inner_receipt = execute_and_prove_program( + program, + &chained_call.pre_states, + &chained_call.instruction_data, + )?; let program_output: ProgramOutput = inner_receipt .journal @@ -76,39 +93,23 @@ pub fn execute_and_prove( // Prove circuit. env_builder.add_assumption(inner_receipt); - // TODO: Remove when multi-chain calls are supported in the circuit - assert!(program_output.chained_calls.len() <= 1); - // TODO: Modify when multi-chain calls are supported in the circuit - if let Some(next_call) = program_output.chained_calls.first() { - program = dependencies - .get(&next_call.program_id) + for new_call in program_output.chained_calls.into_iter().rev() { + let next_program = dependencies + .get(&new_call.program_id) .ok_or(NssaError::InvalidProgramBehavior)?; - instruction_data = next_call.instruction_data.clone(); - // Build post states with metadata for next call - let mut post_states_with_metadata = Vec::new(); - for (pre, post) in program_output - .pre_states - .iter() - .zip(program_output.post_states) - { - let mut post_with_metadata = pre.clone(); - post_with_metadata.account = post.account().clone(); - post_states_with_metadata.push(post_with_metadata); - } - - pre_states = next_call.pre_states.clone(); - } else { - break; + chained_calls.push_front((new_call, next_program)); } + + chain_calls_counter += 1; } let circuit_input = PrivacyPreservingCircuitInput { program_outputs, - visibility_mask: visibility_mask.to_vec(), - private_account_nonces: private_account_nonces.to_vec(), - private_account_keys: private_account_keys.to_vec(), - private_account_nsks: private_account_nsks.to_vec(), - private_account_membership_proofs: private_account_membership_proofs.to_vec(), + visibility_mask, + private_account_nonces, + private_account_keys, + private_account_nsks, + private_account_membership_proofs, program_id: program_with_dependencies.program.id(), }; @@ -215,13 +216,13 @@ mod tests { let shared_secret = SharedSecretKey::new(&esk, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret)], - &[], - &[None], + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdeadbeef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -311,16 +312,16 @@ mod tests { let shared_secret_2 = SharedSecretKey::new(&esk_2, &recipient_keys.ivk()); let (output, proof) = execute_and_prove( - &[sender_pre.clone(), recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![sender_pre.clone(), recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_keys.npk(), shared_secret_1), (recipient_keys.npk(), shared_secret_2), ], - &[sender_keys.nsk], - &[commitment_set.get_proof_for(&commitment_sender), None], + vec![sender_keys.nsk], + vec![commitment_set.get_proof_for(&commitment_sender), None], &program.into(), ) .unwrap(); diff --git a/nssa/src/program.rs b/nssa/src/program.rs index 69cb02c5..943b16ed 100644 --- a/nssa/src/program.rs +++ b/nssa/src/program.rs @@ -226,6 +226,15 @@ mod tests { } } + pub fn changer_claimer() -> Self { + use test_program_methods::{CHANGER_CLAIMER_ELF, CHANGER_CLAIMER_ID}; + + Program { + id: CHANGER_CLAIMER_ID, + elf: CHANGER_CLAIMER_ELF.to_vec(), + } + } + pub fn noop() -> Self { use test_program_methods::{NOOP_ELF, NOOP_ID}; @@ -235,6 +244,17 @@ mod tests { } } + pub fn malicious_authorization_changer() -> Self { + use test_program_methods::{ + MALICIOUS_AUTHORIZATION_CHANGER_ELF, MALICIOUS_AUTHORIZATION_CHANGER_ID, + }; + + Program { + id: MALICIOUS_AUTHORIZATION_CHANGER_ID, + elf: MALICIOUS_AUTHORIZATION_CHANGER_ELF.to_vec(), + } + } + pub fn modified_transfer_program() -> Self { use test_program_methods::MODIFIED_TRANSFER_ELF; // This unwrap won't panic since the `MODIFIED_TRANSFER_ELF` comes from risc0 build of diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index 68437e46..f5badb6a 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -4,7 +4,7 @@ use borsh::{BorshDeserialize, BorshSerialize}; use log::debug; use nssa_core::{ account::{Account, AccountId, AccountWithMetadata}, - program::{ChainedCall, DEFAULT_PROGRAM_ID, PdaSeed, ProgramId, validate_execution}, + program::{ChainedCall, DEFAULT_PROGRAM_ID, validate_execution}, }; use sha2::{Digest, digest::FixedOutput}; @@ -119,7 +119,7 @@ impl PublicTransaction { return Err(NssaError::MaxChainedCallsDepthExceeded); } - // Check the `program_id` corresponds to a deployed program + // Check that the `program_id` corresponds to a deployed program let Some(program) = state.programs().get(&chained_call.program_id) else { return Err(NssaError::InvalidInput("Unknown program".into())); }; @@ -135,12 +135,14 @@ impl PublicTransaction { chained_call.program_id, program_output ); - let authorized_pdas = - self.compute_authorized_pdas(&caller_program_id, &chained_call.pda_seeds); + let authorized_pdas = nssa_core::program::compute_authorized_pdas( + caller_program_id, + &chained_call.pda_seeds, + ); for pre in &program_output.pre_states { let account_id = pre.account_id; - // Check that the program output pre_states coinicide with the values in the public + // Check that the program output pre_states coincide with the values in the public // state or with any modifications to those values during the chain of calls. let expected_pre = state_diff .get(&account_id) @@ -198,22 +200,23 @@ impl PublicTransaction { chain_calls_counter += 1; } - Ok(state_diff) - } - - fn compute_authorized_pdas( - &self, - caller_program_id: &Option, - pda_seeds: &[PdaSeed], - ) -> HashSet { - if let Some(caller_program_id) = caller_program_id { - pda_seeds - .iter() - .map(|pda_seed| AccountId::from((caller_program_id, pda_seed))) - .collect() - } else { - HashSet::new() + // Check that all modified uninitialized accounts where claimed + for post in state_diff.iter().filter_map(|(account_id, post)| { + let pre = state.get_account_by_id(account_id); + if pre.program_owner != DEFAULT_PROGRAM_ID { + return None; + } + if pre == *post { + return None; + } + Some(post) + }) { + if post.program_owner == DEFAULT_PROGRAM_ID { + return Err(NssaError::InvalidProgramBehavior); + } } + + Ok(state_diff) } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index bc0ff62e..1a384b2f 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -504,6 +504,7 @@ pub mod tests { self.insert_program(Program::chain_caller()); self.insert_program(Program::amm()); self.insert_program(Program::claimer()); + self.insert_program(Program::changer_claimer()); self } @@ -865,13 +866,13 @@ pub mod tests { let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( - &[sender, recipient], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[0, 2], - &[0xdeadbeef], - &[(recipient_keys.npk(), shared_secret)], - &[], - &[None], + vec![sender, recipient], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![0, 2], + vec![0xdeadbeef], + vec![(recipient_keys.npk(), shared_secret)], + vec![], + vec![None], &Program::authenticated_transfer_program().into(), ) .unwrap(); @@ -912,16 +913,16 @@ pub mod tests { let epk_2 = EphemeralPublicKey::from_scalar(esk_2); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 2], - &new_nonces, - &[ + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 2], + new_nonces.to_vec(), + vec![ (sender_keys.npk(), shared_secret_1), (recipient_keys.npk(), shared_secret_2), ], - &[sender_keys.nsk], - &[state.get_proof_for_commitment(&sender_commitment), None], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment), None], &program.into(), ) .unwrap(); @@ -965,13 +966,13 @@ pub mod tests { let epk = EphemeralPublicKey::from_scalar(esk); let (output, proof) = circuit::execute_and_prove( - &[sender_pre, recipient_pre], - &Program::serialize_instruction(balance_to_move).unwrap(), - &[1, 0], - &[new_nonce], - &[(sender_keys.npk(), shared_secret)], - &[sender_keys.nsk], - &[state.get_proof_for_commitment(&sender_commitment)], + vec![sender_pre, recipient_pre], + Program::serialize_instruction(balance_to_move).unwrap(), + vec![1, 0], + vec![new_nonce], + vec![(sender_keys.npk(), shared_secret)], + vec![sender_keys.nsk], + vec![state.get_proof_for_commitment(&sender_commitment)], &program.into(), ) .unwrap(); @@ -1179,13 +1180,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(10u128).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(10u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1206,13 +1207,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(10u128).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(10u128).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1233,13 +1234,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1260,13 +1261,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(vec![0]).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(vec![0]).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1289,13 +1290,13 @@ pub mod tests { let large_data: Vec = vec![0; nssa_core::account::data::DATA_MAX_LENGTH_IN_BYTES + 1]; let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(large_data).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(large_data).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.to_owned().into(), ); @@ -1316,13 +1317,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1352,13 +1353,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(()).unwrap(), - &[0, 0], - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(()).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1379,13 +1380,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account], - &Program::serialize_instruction(()).unwrap(), - &[0], - &[], - &[], - &[], - &[], + vec![public_account], + Program::serialize_instruction(()).unwrap(), + vec![0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1415,13 +1416,13 @@ pub mod tests { ); let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[0, 0], - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![0, 0], + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1453,13 +1454,13 @@ pub mod tests { // Setting only one visibility mask for a circuit execution with two pre_state accounts. let visibility_mask = [0]; let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1486,11 +1487,11 @@ pub mod tests { // Setting only one nonce for an execution with two private accounts. let private_account_nonces = [0xdeadbeef1]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &private_account_nonces, - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1500,8 +1501,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1530,13 +1531,13 @@ pub mod tests { SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), )]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1563,11 +1564,11 @@ pub mod tests { // Setting no second commitment proof. let private_account_membership_proofs = [Some((0, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1577,8 +1578,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &private_account_membership_proofs, + vec![sender_keys.nsk], + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1605,11 +1606,11 @@ pub mod tests { // Setting no auth key for an execution with one non default private accounts. let private_account_nsks = []; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1619,8 +1620,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &private_account_nsks, - &[], + private_account_nsks.to_vec(), + vec![], &program.into(), ); @@ -1663,13 +1664,13 @@ pub mod tests { let private_account_nsks = [recipient_keys.nsk]; let private_account_membership_proofs = [Some((0, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &private_account_nsks, - &private_account_membership_proofs, + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -1701,11 +1702,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1715,8 +1716,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1749,11 +1750,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1763,8 +1764,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1796,11 +1797,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1810,8 +1811,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1843,11 +1844,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1857,8 +1858,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1888,11 +1889,11 @@ pub mod tests { ); let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1902,8 +1903,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -1927,13 +1928,13 @@ pub mod tests { let visibility_mask = [0, 3]; let result = execute_and_prove( - &[public_account_1, public_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[], - &[], - &[], - &[], + vec![public_account_1, public_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![], + vec![], + vec![], + vec![], &program.into(), ); @@ -1961,11 +1962,11 @@ pub mod tests { // accounts. let private_account_nonces = [0xdeadbeef1, 0xdeadbeef2, 0xdeadbeef3]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &private_account_nonces, - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + private_account_nonces.to_vec(), + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -1975,8 +1976,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2017,13 +2018,13 @@ pub mod tests { ), ]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &[1, 2], - &[0xdeadbeef1, 0xdeadbeef2], - &private_account_keys, - &[sender_keys.nsk], - &[Some((0, vec![]))], + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + vec![1, 2], + vec![0xdeadbeef1, 0xdeadbeef2], + private_account_keys.to_vec(), + vec![sender_keys.nsk], + vec![Some((0, vec![]))], &program.into(), ); @@ -2053,11 +2054,11 @@ pub mod tests { let private_account_nsks = [sender_keys.nsk, recipient_keys.nsk]; let private_account_membership_proofs = [Some((0, vec![])), Some((1, vec![]))]; let result = execute_and_prove( - &[private_account_1, private_account_2], - &Program::serialize_instruction(10u128).unwrap(), - &visibility_mask, - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1, private_account_2], + Program::serialize_instruction(10u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ ( sender_keys.npk(), SharedSecretKey::new(&[55; 32], &sender_keys.ivk()), @@ -2067,8 +2068,8 @@ pub mod tests { SharedSecretKey::new(&[56; 32], &recipient_keys.ivk()), ), ], - &private_account_nsks, - &private_account_membership_proofs, + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -2149,16 +2150,16 @@ pub mod tests { let private_account_membership_proofs = [Some((1, vec![])), Some((1, vec![]))]; let shared_secret = SharedSecretKey::new(&[55; 32], &sender_keys.ivk()); let result = execute_and_prove( - &[private_account_1.clone(), private_account_1], - &Program::serialize_instruction(100u128).unwrap(), - &visibility_mask, - &[0xdeadbeef1, 0xdeadbeef2], - &[ + vec![private_account_1.clone(), private_account_1], + Program::serialize_instruction(100u128).unwrap(), + visibility_mask.to_vec(), + vec![0xdeadbeef1, 0xdeadbeef2], + vec![ (sender_keys.npk(), shared_secret), (sender_keys.npk(), shared_secret), ], - &private_account_nsks, - &private_account_membership_proofs, + private_account_nsks.to_vec(), + private_account_membership_proofs.to_vec(), &program.into(), ); @@ -3941,8 +3942,9 @@ pub mod tests { assert_eq!(to_post, expected_to_post); } - #[test] - fn test_private_chained_call() { + #[test_case::test_case(1; "single call")] + #[test_case::test_case(2; "two calls")] + fn test_private_chained_call(number_of_calls: u32) { // Arrange let chain_caller = Program::chain_caller(); let auth_transfers = Program::authenticated_transfer_program(); @@ -3978,7 +3980,7 @@ pub mod tests { let instruction: (u128, ProgramId, u32, Option) = ( amount, Program::authenticated_transfer_program().id(), - 1, + number_of_calls, None, ); @@ -3999,14 +4001,14 @@ pub mod tests { let to_new_nonce = 0xdeadbeef2; let from_expected_post = Account { - balance: initial_balance - amount, + balance: initial_balance - number_of_calls as u128 * amount, nonce: from_new_nonce, ..from_account.account.clone() }; let from_expected_commitment = Commitment::new(&from_keys.npk(), &from_expected_post); let to_expected_post = Account { - balance: amount, + balance: number_of_calls as u128 * amount, nonce: to_new_nonce, ..to_account.account.clone() }; @@ -4014,13 +4016,13 @@ pub mod tests { // Act let (output, proof) = execute_and_prove( - &[to_account, from_account], - &Program::serialize_instruction(instruction).unwrap(), - &[1, 1], - &[from_new_nonce, to_new_nonce], - &[(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], - &[from_keys.nsk, to_keys.nsk], - &[ + vec![to_account, from_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1, 1], + vec![from_new_nonce, to_new_nonce], + vec![(from_keys.npk(), to_ss), (to_keys.npk(), from_ss)], + vec![from_keys.nsk, to_keys.nsk], + vec![ state.get_proof_for_commitment(&from_commitment), state.get_proof_for_commitment(&to_commitment), ], @@ -4255,13 +4257,13 @@ pub mod tests { // Execute and prove the circuit with the authorized account but no commitment proof let (output, proof) = execute_and_prove( - std::slice::from_ref(&authorized_account), - &Program::serialize_instruction(balance).unwrap(), - &[1], - &[nonce], - &[(private_keys.npk(), shared_secret)], - &[private_keys.nsk], - &[None], + vec![authorized_account], + Program::serialize_instruction(balance).unwrap(), + vec![1], + vec![nonce], + vec![(private_keys.npk(), shared_secret)], + vec![private_keys.nsk], + vec![None], &program.into(), ) .unwrap(); @@ -4308,13 +4310,13 @@ pub mod tests { // Step 2: Execute claimer program to claim the account with authentication let (output, proof) = execute_and_prove( - std::slice::from_ref(&authorized_account), - &Program::serialize_instruction(balance).unwrap(), - &[1], - &[nonce], - &[(private_keys.npk(), shared_secret)], - &[private_keys.nsk], - &[None], + vec![authorized_account.clone()], + Program::serialize_instruction(balance).unwrap(), + vec![1], + vec![nonce], + vec![(private_keys.npk(), shared_secret)], + vec![private_keys.nsk], + vec![None], &claimer_program.into(), ) .unwrap(); @@ -4356,16 +4358,174 @@ pub mod tests { // Step 3: Try to execute noop program with authentication but without initialization let res = execute_and_prove( - std::slice::from_ref(&account_metadata), - &Program::serialize_instruction(()).unwrap(), - &[1], - &[nonce2], - &[(private_keys.npk(), shared_secret2)], - &[private_keys.nsk], - &[None], + vec![account_metadata], + Program::serialize_instruction(()).unwrap(), + vec![1], + vec![nonce2], + vec![(private_keys.npk(), shared_secret2)], + vec![private_keys.nsk], + vec![None], &noop_program.into(), ); assert!(matches!(res, Err(NssaError::CircuitProvingError(_)))); } + + #[test] + fn test_public_changer_claimer_no_data_change_no_claim_succeeds() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::changer_claimer().id(); + // Don't change data (None) and don't claim (false) + let instruction: (Option>, bool) = (None, false); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + // Should succeed - no changes made, no claim needed + assert!(result.is_ok()); + // Account should remain default/unclaimed + assert_eq!(state.get_account_by_id(&account_id), Account::default()); + } + + #[test] + fn test_public_changer_claimer_data_change_no_claim_fails() { + let initial_data = []; + let mut state = + V02State::new_with_genesis_accounts(&initial_data, &[]).with_test_programs(); + let account_id = AccountId::new([1; 32]); + let program_id = Program::changer_claimer().id(); + // Change data but don't claim (false) - should fail + let new_data = vec![1, 2, 3, 4, 5]; + let instruction: (Option>, bool) = (Some(new_data), false); + + let message = + public_transaction::Message::try_new(program_id, vec![account_id], vec![], instruction) + .unwrap(); + let witness_set = public_transaction::WitnessSet::for_message(&message, &[]); + let tx = PublicTransaction::new(message, witness_set); + + let result = state.transition_from_public_transaction(&tx); + + // Should fail - cannot modify data without claiming the account + assert!(matches!(result, Err(NssaError::InvalidProgramBehavior))); + } + + #[test] + fn test_private_changer_claimer_no_data_change_no_claim_succeeds() { + let program = Program::changer_claimer(); + let sender_keys = test_private_account_keys_1(); + let private_account = + AccountWithMetadata::new(Account::default(), true, &sender_keys.npk()); + // Don't change data (None) and don't claim (false) + let instruction: (Option>, bool) = (None, false); + + let result = execute_and_prove( + vec![private_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1], + vec![2], + vec![( + sender_keys.npk(), + SharedSecretKey::new(&[3; 32], &sender_keys.ivk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + // Should succeed - no changes made, no claim needed + assert!(result.is_ok()); + } + + #[test] + fn test_private_changer_claimer_data_change_no_claim_fails() { + let program = Program::changer_claimer(); + let sender_keys = test_private_account_keys_1(); + let private_account = + AccountWithMetadata::new(Account::default(), true, &sender_keys.npk()); + // Change data but don't claim (false) - should fail + let new_data = vec![1, 2, 3, 4, 5]; + let instruction: (Option>, bool) = (Some(new_data), false); + + let result = execute_and_prove( + vec![private_account], + Program::serialize_instruction(instruction).unwrap(), + vec![1], + vec![2], + vec![( + sender_keys.npk(), + SharedSecretKey::new(&[3; 32], &sender_keys.ivk()), + )], + vec![sender_keys.nsk], + vec![Some((0, vec![]))], + &program.into(), + ); + + // Should fail - cannot modify data without claiming the account + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } + + #[test] + fn test_malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() { + // Arrange + let malicious_program = Program::malicious_authorization_changer(); + let auth_transfers = Program::authenticated_transfer_program(); + let sender_keys = test_public_account_keys_1(); + let recipient_keys = test_private_account_keys_1(); + + let sender_account = AccountWithMetadata::new( + Account { + program_owner: auth_transfers.id(), + balance: 100, + ..Default::default() + }, + false, + sender_keys.account_id(), + ); + let recipient_account = + AccountWithMetadata::new(Account::default(), true, &recipient_keys.npk()); + + let recipient_commitment = + Commitment::new(&recipient_keys.npk(), &recipient_account.account); + let state = V02State::new_with_genesis_accounts( + &[(sender_account.account_id, sender_account.account.balance)], + std::slice::from_ref(&recipient_commitment), + ) + .with_test_programs(); + + let balance_to_transfer = 10u128; + let instruction = (balance_to_transfer, auth_transfers.id()); + + let recipient_esk = [3; 32]; + let recipient = SharedSecretKey::new(&recipient_esk, &recipient_keys.ivk()); + + let mut dependencies = HashMap::new(); + dependencies.insert(auth_transfers.id(), auth_transfers); + let program_with_deps = ProgramWithDependencies::new(malicious_program, dependencies); + + let recipient_new_nonce = 0xdeadbeef1; + + // Act - execute the malicious program - this should fail during proving + let result = execute_and_prove( + vec![sender_account, recipient_account], + Program::serialize_instruction(instruction).unwrap(), + vec![0, 1], + vec![recipient_new_nonce], + vec![(recipient_keys.npk(), recipient)], + vec![recipient_keys.nsk], + vec![state.get_proof_for_commitment(&recipient_commitment)], + &program_with_deps, + ); + + // Assert - should fail because the malicious program tries to manipulate is_authorized + assert!(matches!(result, Err(NssaError::CircuitProvingError(_)))); + } } diff --git a/program_methods/guest/src/bin/pinata.rs b/program_methods/guest/src/bin/pinata.rs index a0c46a1a..0dc3c108 100644 --- a/program_methods/guest/src/bin/pinata.rs +++ b/program_methods/guest/src/bin/pinata.rs @@ -77,7 +77,7 @@ fn main() { instruction_words, vec![pinata, winner], vec![ - AccountPostState::new(pinata_post), + AccountPostState::new_claimed_if_default(pinata_post), AccountPostState::new(winner_post), ], ); diff --git a/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/program_methods/guest/src/bin/privacy_preserving_circuit.rs index ffe4b130..4bbd895f 100644 --- a/program_methods/guest/src/bin/privacy_preserving_circuit.rs +++ b/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -1,12 +1,18 @@ -use std::collections::HashMap; +use std::{ + collections::{HashMap, HashSet, VecDeque, hash_map::Entry}, + convert::Infallible, +}; use nssa_core::{ - Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, Nullifier, - NullifierPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, - account::{Account, AccountId, AccountWithMetadata}, + Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptionScheme, MembershipProof, + Nullifier, NullifierPublicKey, NullifierSecretKey, PrivacyPreservingCircuitInput, + PrivacyPreservingCircuitOutput, SharedSecretKey, + account::{Account, AccountId, AccountWithMetadata, Nonce}, compute_digest_for_path, - encryption::Ciphertext, - program::{DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, validate_execution}, + program::{ + AccountPostState, ChainedCall, DEFAULT_PROGRAM_ID, MAX_NUMBER_CHAINED_CALLS, ProgramId, + ProgramOutput, validate_execution, + }, }; use risc0_zkvm::{guest::env, serde::to_vec}; @@ -18,118 +24,224 @@ fn main() { private_account_keys, private_account_nsks, private_account_membership_proofs, - mut program_id, + program_id, } = env::read(); - let mut pre_states: Vec = Vec::new(); - let mut state_diff: HashMap = HashMap::new(); + let execution_state = ExecutionState::derive_from_outputs(program_id, program_outputs); - let num_calls = program_outputs.len(); - if num_calls > MAX_NUMBER_CHAINED_CALLS { - panic!("Max chained calls depth is exceeded"); - } + let output = compute_circuit_output( + execution_state, + &visibility_mask, + &private_account_nonces, + &private_account_keys, + &private_account_nsks, + &private_account_membership_proofs, + ); - let Some(last_program_call) = program_outputs.last() else { - panic!("Program outputs is empty") - }; + env::commit(&output); +} - if !last_program_call.chained_calls.is_empty() { - panic!("Call stack is incomplete"); - } +/// State of the involved accounts before and after program execution. +struct ExecutionState { + pre_states: Vec, + post_states: HashMap, +} - for window in program_outputs.windows(2) { - let caller = &window[0]; - let callee = &window[1]; - - if caller.chained_calls.len() > 1 { - panic!("Privacy Multi-chained calls are not supported yet"); - } - - // TODO: Modify when multi-chain calls are supported in the circuit - let Some(caller_chained_call) = &caller.chained_calls.first() else { - panic!("Expected chained call"); +impl ExecutionState { + /// Validate program outputs and derive the overall execution state. + pub fn derive_from_outputs(program_id: ProgramId, program_outputs: Vec) -> Self { + let Some(first_output) = program_outputs.first() else { + panic!("No program outputs provided"); }; - // Check that instruction data in caller is the instruction data in callee - if caller_chained_call.instruction_data != callee.instruction_data { - panic!("Invalid instruction data"); - } - - // Check that account pre_states in caller are the ones in calle - if caller_chained_call.pre_states != callee.pre_states { - panic!("Invalid pre states"); - } - } - - for (i, program_output) in program_outputs.iter().enumerate() { - let mut program_output = program_output.clone(); - - // Check that `program_output` is consistent with the execution of the corresponding - // program. - let program_output_words = - &to_vec(&program_output).expect("program_output must be serializable"); - env::verify(program_id, program_output_words) - .expect("program output must match the program's execution"); - - // Check that the program is well behaved. - // See the # Programs section for the definition of the `validate_execution` method. - if !validate_execution( - &program_output.pre_states, - &program_output.post_states, + let initial_call = ChainedCall { program_id, - ) { - panic!("Bad behaved program"); - } + instruction_data: first_output.instruction_data.clone(), + pre_states: first_output.pre_states.clone(), + pda_seeds: Vec::new(), + }; + let mut chained_calls = VecDeque::from_iter([(initial_call, None)]); - // The invoked program claims the accounts with default program id. - for post in program_output - .post_states - .iter_mut() - .filter(|post| post.requires_claim()) - { - // The invoked program can only claim accounts with default program id. - if post.account().program_owner == DEFAULT_PROGRAM_ID { - post.account_mut().program_owner = program_id; - } else { - panic!("Cannot claim an initialized account") + let mut execution_state = ExecutionState { + pre_states: Vec::new(), + post_states: HashMap::new(), + }; + + let mut program_outputs_iter = program_outputs.into_iter(); + let mut chain_calls_counter = 0; + + while let Some((chained_call, caller_program_id)) = chained_calls.pop_front() { + assert!( + chain_calls_counter <= MAX_NUMBER_CHAINED_CALLS, + "Max chained calls depth is exceeded" + ); + + let Some(program_output) = program_outputs_iter.next() else { + panic!("Insufficient program outputs for chained calls"); + }; + + // Check that instruction data in chained call is the instruction data in program output + assert_eq!( + chained_call.instruction_data, program_output.instruction_data, + "Mismatched instruction data between chained call and program output" + ); + + // Check that `program_output` is consistent with the execution of the corresponding + // program. + let program_output_words = + &to_vec(&program_output).expect("program_output must be serializable"); + env::verify(chained_call.program_id, program_output_words).unwrap_or_else( + |_: Infallible| unreachable!("Infallible error is never constructed"), + ); + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + let execution_valid = validate_execution( + &program_output.pre_states, + &program_output.post_states, + chained_call.program_id, + ); + assert!(execution_valid, "Bad behaved program"); + + for next_call in program_output.chained_calls.iter().rev() { + chained_calls.push_front((next_call.clone(), Some(chained_call.program_id))); } + + let authorized_pdas = nssa_core::program::compute_authorized_pdas( + caller_program_id, + &chained_call.pda_seeds, + ); + execution_state.validate_and_sync_states( + chained_call.program_id, + authorized_pdas, + program_output.pre_states, + program_output.post_states, + ); + chain_calls_counter += 1; } - for (pre, post) in program_output + assert!( + program_outputs_iter.next().is_none(), + "Inner call without a chained call found", + ); + + // Check that all modified uninitialized accounts were claimed + for (account_id, post) in execution_state .pre_states .iter() - .zip(&program_output.post_states) + .filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID) + .map(|a| { + let post = execution_state + .post_states + .get(&a.account_id) + .expect("Post state must exist for pre state"); + (a, post) + }) + .filter(|(pre_default, post)| pre_default.account != **post) + .map(|(pre, post)| (pre.account_id, post)) { - if let Some(account_pre) = state_diff.get(&pre.account_id) { - if account_pre != &pre.account { - panic!("Invalid input"); - } - } else { - pre_states.push(pre.clone()); - } - state_diff.insert(pre.account_id, post.account().clone()); + assert_ne!( + post.program_owner, DEFAULT_PROGRAM_ID, + "Account {account_id:?} was modified but not claimed" + ); } - // TODO: Modify when multi-chain calls are supported in the circuit - if let Some(next_chained_call) = &program_output.chained_calls.first() { - program_id = next_chained_call.program_id; - } else if i != program_outputs.len() - 1 { - panic!("Inner call without a chained call found") - }; + execution_state } - let n_accounts = pre_states.len(); - if visibility_mask.len() != n_accounts { - panic!("Invalid visibility mask length"); + /// Validate program pre and post states and populate the execution state. + fn validate_and_sync_states( + &mut self, + program_id: ProgramId, + authorized_pdas: HashSet, + pre_states: Vec, + post_states: Vec, + ) { + for (pre, mut post) in pre_states.into_iter().zip(post_states) { + let pre_account_id = pre.account_id; + let post_states_entry = self.post_states.entry(pre.account_id); + match &post_states_entry { + Entry::Occupied(occupied) => { + // Ensure that new pre state is the same as known post state + assert_eq!( + occupied.get(), + &pre.account, + "Inconsistent pre state for account {pre_account_id:?}", + ); + + let previous_is_authorized = self + .pre_states + .iter() + .find(|acc| acc.account_id == pre_account_id) + .map(|acc| acc.is_authorized) + .unwrap_or_else(|| { + panic!( + "Pre state must exist in execution state for account {pre_account_id:?}", + ) + }); + + let is_authorized = + previous_is_authorized || authorized_pdas.contains(&pre_account_id); + + assert_eq!( + pre.is_authorized, is_authorized, + "Inconsistent authorization for account {pre_account_id:?}", + ); + } + Entry::Vacant(_) => { + self.pre_states.push(pre); + } + } + + if post.requires_claim() { + // The invoked program can only claim accounts with default program id. + if post.account().program_owner == DEFAULT_PROGRAM_ID { + post.account_mut().program_owner = program_id; + } else { + panic!("Cannot claim an initialized account {pre_account_id:?}"); + } + } + + post_states_entry.insert_entry(post.into_account()); + } } - // These lists will be the public outputs of this circuit - // and will be populated next. - let mut public_pre_states: Vec = Vec::new(); - let mut public_post_states: Vec = Vec::new(); - let mut ciphertexts: Vec = Vec::new(); - let mut new_commitments: Vec = Vec::new(); - let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new(); + /// Get an iterator over pre and post states of each account involved in the execution. + pub fn into_states_iter( + mut self, + ) -> impl ExactSizeIterator { + self.pre_states.into_iter().map(move |pre| { + let post = self + .post_states + .remove(&pre.account_id) + .expect("Account from pre states should exist in state diff"); + (pre, post) + }) + } +} + +fn compute_circuit_output( + execution_state: ExecutionState, + visibility_mask: &[u8], + private_account_nonces: &[Nonce], + private_account_keys: &[(NullifierPublicKey, SharedSecretKey)], + private_account_nsks: &[NullifierSecretKey], + private_account_membership_proofs: &[Option], +) -> PrivacyPreservingCircuitOutput { + let mut output = PrivacyPreservingCircuitOutput { + public_pre_states: Vec::new(), + public_post_states: Vec::new(), + ciphertexts: Vec::new(), + new_commitments: Vec::new(), + new_nullifiers: Vec::new(), + }; + + let states_iter = execution_state.into_states_iter(); + assert_eq!( + visibility_mask.len(), + states_iter.len(), + "Invalid visibility mask length" + ); let mut private_nonces_iter = private_account_nonces.iter(); let mut private_keys_iter = private_account_keys.iter(); @@ -137,141 +249,156 @@ fn main() { let mut private_membership_proofs_iter = private_account_membership_proofs.iter(); let mut output_index = 0; - for i in 0..n_accounts { - match visibility_mask[i] { + for (visibility_mask, (pre_state, post_state)) in + visibility_mask.iter().copied().zip(states_iter) + { + match visibility_mask { 0 => { // Public account - public_pre_states.push(pre_states[i].clone()); - - let mut post = state_diff.get(&pre_states[i].account_id).unwrap().clone(); - - if post.program_owner == DEFAULT_PROGRAM_ID { - // Claim account - post.program_owner = program_id; - } - public_post_states.push(post); + output.public_pre_states.push(pre_state); + output.public_post_states.push(post_state); } 1 | 2 => { - let new_nonce = private_nonces_iter.next().expect("Missing private nonce"); - let (npk, shared_secret) = private_keys_iter.next().expect("Missing keys"); + let Some((npk, shared_secret)) = private_keys_iter.next() else { + panic!("Missing private account key"); + }; - if AccountId::from(npk) != pre_states[i].account_id { - panic!("AccountId mismatch"); - } + assert_eq!( + AccountId::from(npk), + pre_state.account_id, + "AccountId mismatch" + ); - if visibility_mask[i] == 1 { + let new_nullifier = if visibility_mask == 1 { // Private account with authentication - let nsk = private_nsks_iter.next().expect("Missing nsk"); + + let Some(nsk) = private_nsks_iter.next() else { + panic!("Missing private account nullifier secret key"); + }; // Verify the nullifier public key - let expected_npk = NullifierPublicKey::from(nsk); - if &expected_npk != npk { - panic!("Nullifier public key mismatch"); - } + assert_eq!( + npk, + &NullifierPublicKey::from(nsk), + "Nullifier public key mismatch" + ); // Check pre_state authorization - if !pre_states[i].is_authorized { - panic!("Pre-state not authorized"); - } + assert!( + pre_state.is_authorized, + "Pre-state not authorized for authenticated private account" + ); - let membership_proof_opt = private_membership_proofs_iter - .next() - .expect("Missing membership proof"); - let (nullifier, set_digest) = membership_proof_opt - .as_ref() - .map(|membership_proof| { - // Compute commitment set digest associated with provided auth path - let commitment_pre = Commitment::new(npk, &pre_states[i].account); - let set_digest = - compute_digest_for_path(&commitment_pre, membership_proof); + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; - // Compute update nullifier - let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); - (nullifier, set_digest) - }) - .unwrap_or_else(|| { - if pre_states[i].account != Account::default() { - panic!("Found new private account with non default values."); - } - - // Compute initialization nullifier - let nullifier = Nullifier::for_account_initialization(npk); - (nullifier, DUMMY_COMMITMENT_HASH) - }); - new_nullifiers.push((nullifier, set_digest)); + compute_nullifier_and_set_digest( + membership_proof_opt.as_ref(), + &pre_state.account, + npk, + nsk, + ) } else { // Private account without authentication - if pre_states[i].account != Account::default() { - panic!("Found new private account with non default values."); - } - if pre_states[i].is_authorized { - panic!("Found new private account marked as authorized."); - } + assert_eq!( + pre_state.account, + Account::default(), + "Found new private account with non default values", + ); + + assert!( + !pre_state.is_authorized, + "Found new private account marked as authorized." + ); + + let Some(membership_proof_opt) = private_membership_proofs_iter.next() else { + panic!("Missing membership proof"); + }; - let membership_proof_opt = private_membership_proofs_iter - .next() - .expect("Missing membership proof"); assert!( membership_proof_opt.is_none(), "Membership proof must be None for unauthorized accounts" ); + let nullifier = Nullifier::for_account_initialization(npk); - new_nullifiers.push((nullifier, DUMMY_COMMITMENT_HASH)); - } + (nullifier, DUMMY_COMMITMENT_HASH) + }; + output.new_nullifiers.push(new_nullifier); // Update post-state with new nonce - let mut post_with_updated_values = - state_diff.get(&pre_states[i].account_id).unwrap().clone(); - post_with_updated_values.nonce = *new_nonce; - - if post_with_updated_values.program_owner == DEFAULT_PROGRAM_ID { - // Claim account - post_with_updated_values.program_owner = program_id; - } + let mut post_with_updated_nonce = post_state; + let Some(new_nonce) = private_nonces_iter.next() else { + panic!("Missing private account nonce"); + }; + post_with_updated_nonce.nonce = *new_nonce; // Compute commitment - let commitment_post = Commitment::new(npk, &post_with_updated_values); + let commitment_post = Commitment::new(npk, &post_with_updated_nonce); // Encrypt and push post state let encrypted_account = EncryptionScheme::encrypt( - &post_with_updated_values, + &post_with_updated_nonce, shared_secret, &commitment_post, output_index, ); - new_commitments.push(commitment_post); - ciphertexts.push(encrypted_account); + output.new_commitments.push(commitment_post); + output.ciphertexts.push(encrypted_account); output_index += 1; } _ => panic!("Invalid visibility mask value"), } } - if private_nonces_iter.next().is_some() { - panic!("Too many nonces"); - } + assert!(private_nonces_iter.next().is_none(), "Too many nonces"); - if private_keys_iter.next().is_some() { - panic!("Too many private account keys"); - } + assert!( + private_keys_iter.next().is_none(), + "Too many private account keys" + ); - if private_nsks_iter.next().is_some() { - panic!("Too many private account authentication keys"); - } + assert!( + private_nsks_iter.next().is_none(), + "Too many private account nullifier secret keys" + ); - if private_membership_proofs_iter.next().is_some() { - panic!("Too many private account membership proofs"); - } + assert!( + private_membership_proofs_iter.next().is_none(), + "Too many private account membership proofs" + ); - let output = PrivacyPreservingCircuitOutput { - public_pre_states, - public_post_states, - ciphertexts, - new_commitments, - new_nullifiers, - }; - - env::commit(&output); + output +} + +fn compute_nullifier_and_set_digest( + membership_proof_opt: Option<&MembershipProof>, + pre_account: &Account, + npk: &NullifierPublicKey, + nsk: &NullifierSecretKey, +) -> (Nullifier, CommitmentSetDigest) { + membership_proof_opt + .as_ref() + .map(|membership_proof| { + // Compute commitment set digest associated with provided auth path + let commitment_pre = Commitment::new(npk, pre_account); + let set_digest = compute_digest_for_path(&commitment_pre, membership_proof); + + // Compute update nullifier + let nullifier = Nullifier::for_account_update(&commitment_pre, nsk); + (nullifier, set_digest) + }) + .unwrap_or_else(|| { + assert_eq!( + *pre_account, + Account::default(), + "Found new private account with non default values" + ); + + // Compute initialization nullifier + let nullifier = Nullifier::for_account_initialization(npk); + (nullifier, DUMMY_COMMITMENT_HASH) + }) } diff --git a/sequencer_core/Cargo.toml b/sequencer_core/Cargo.toml index 3f46f7ca..8d2886ce 100644 --- a/sequencer_core/Cargo.toml +++ b/sequencer_core/Cargo.toml @@ -18,8 +18,8 @@ tempfile.workspace = true chrono.workspace = true log.workspace = true bedrock_client.workspace = true -key-management-system-service.workspace = true -nomos-core.workspace=true +logos-blockchain-key-management-system-service.workspace = true +logos-blockchain-core.workspace = true rand.workspace = true reqwest.workspace = true borsh.workspace = true diff --git a/sequencer_core/src/block_settlement_client.rs b/sequencer_core/src/block_settlement_client.rs index 58f4d7f4..c41efa52 100644 --- a/sequencer_core/src/block_settlement_client.rs +++ b/sequencer_core/src/block_settlement_client.rs @@ -3,11 +3,13 @@ use std::{fs, path::Path}; use anyhow::{Result, anyhow}; use bedrock_client::BedrockClient; use common::block::HashableBlockData; -use key_management_system_service::keys::{ED25519_SECRET_KEY_SIZE, Ed25519Key, Ed25519PublicKey}; -use nomos_core::mantle::{ +use logos_blockchain_core::mantle::{ MantleTx, Op, OpProof, SignedMantleTx, Transaction, TxHash, ledger, ops::channel::{ChannelId, MsgId, inscribe::InscriptionOp}, }; +use logos_blockchain_key_management_system_service::keys::{ + ED25519_SECRET_KEY_SIZE, Ed25519Key, Ed25519PublicKey, +}; use crate::config::BedrockConfig; @@ -63,7 +65,9 @@ impl BlockSettlementClient { .sign_payload(tx_hash.as_signing_bytes().as_ref()) .to_bytes(); let signature = - key_management_system_service::keys::Ed25519Signature::from_bytes(&signature_bytes); + logos_blockchain_key_management_system_service::keys::Ed25519Signature::from_bytes( + &signature_bytes, + ); let signed_mantle_tx = SignedMantleTx { ops_proofs: vec![OpProof::Ed25519Sig(signature)], @@ -103,7 +107,9 @@ fn load_or_create_signing_key(path: &Path) -> Result { } } -fn empty_ledger_signature(tx_hash: &TxHash) -> key_management_system_service::keys::ZkSignature { - key_management_system_service::keys::ZkKey::multi_sign(&[], tx_hash.as_ref()) +fn empty_ledger_signature( + tx_hash: &TxHash, +) -> logos_blockchain_key_management_system_service::keys::ZkSignature { + logos_blockchain_key_management_system_service::keys::ZkKey::multi_sign(&[], tx_hash.as_ref()) .expect("multi-sign with empty key set works") } diff --git a/test_program_methods/guest/src/bin/changer_claimer.rs b/test_program_methods/guest/src/bin/changer_claimer.rs new file mode 100644 index 00000000..8d28a490 --- /dev/null +++ b/test_program_methods/guest/src/bin/changer_claimer.rs @@ -0,0 +1,38 @@ +use nssa_core::program::{AccountPostState, ProgramInput, read_nssa_inputs, write_nssa_outputs}; + +type Instruction = (Option>, bool); + +/// A program that optionally modifies the account data and optionally claims it. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (data_opt, should_claim), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [pre] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + let account_pre = &pre.account; + let mut account_post = account_pre.clone(); + + // Update data if provided + if let Some(data) = data_opt { + account_post.data = data + .try_into() + .expect("provided data should fit into data limit"); + } + + // Claim or not based on the boolean flag + let post_state = if should_claim { + AccountPostState::new_claimed(account_post) + } else { + AccountPostState::new(account_post) + }; + + write_nssa_outputs(instruction_words, vec![pre], vec![post_state]); +} diff --git a/test_program_methods/guest/src/bin/malicious_authorization_changer.rs b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs new file mode 100644 index 00000000..7dc0ac68 --- /dev/null +++ b/test_program_methods/guest/src/bin/malicious_authorization_changer.rs @@ -0,0 +1,53 @@ +use nssa_core::{ + account::AccountWithMetadata, + program::{ + AccountPostState, ChainedCall, ProgramId, ProgramInput, read_nssa_inputs, + write_nssa_outputs_with_chained_call, + }, +}; +use risc0_zkvm::serde::to_vec; + +type Instruction = (u128, ProgramId); + +/// A malicious test program that attempts to change authorization status. +/// It accepts two accounts and executes a native token transfer program via chain call, +/// but sets the `is_authorized` field of the first account to true. +fn main() { + let ( + ProgramInput { + pre_states, + instruction: (balance, transfer_program_id), + }, + instruction_words, + ) = read_nssa_inputs::(); + + let [sender, receiver] = match pre_states.try_into() { + Ok(array) => array, + Err(_) => return, + }; + + // Maliciously set is_authorized to true for the first account + let authorised_sender = AccountWithMetadata { + is_authorized: true, + ..sender.clone() + }; + + let instruction_data = to_vec(&balance).unwrap(); + + let chained_call = ChainedCall { + program_id: transfer_program_id, + instruction_data, + pre_states: vec![authorised_sender.clone(), receiver.clone()], + pda_seeds: vec![], + }; + + write_nssa_outputs_with_chained_call( + instruction_words, + vec![sender.clone(), receiver.clone()], + vec![ + AccountPostState::new(sender.account), + AccountPostState::new(receiver.account), + ], + vec![chained_call], + ); +} diff --git a/wallet/src/cli/account.rs b/wallet/src/cli/account.rs index 58e19847..21e59366 100644 --- a/wallet/src/cli/account.rs +++ b/wallet/src/cli/account.rs @@ -3,7 +3,7 @@ use base58::ToBase58; use clap::Subcommand; use itertools::Itertools as _; use key_protocol::key_management::key_tree::chain_index::ChainIndex; -use nssa::{Account, program::Program}; +use nssa::{Account, PublicKey, program::Program}; use serde::Serialize; use crate::{ @@ -20,6 +20,9 @@ pub enum AccountSubcommand { /// Flag to get raw account data #[arg(short, long)] raw: bool, + /// Display keys (pk for public accounts, npk/ipk for private accounts) + #[arg(short, long)] + keys: bool, /// Valid 32 byte base58 string with privacy prefix #[arg(short, long)] account_id: String, @@ -64,9 +67,18 @@ impl WalletSubcommand for NewSubcommand { NewSubcommand::Public { cci } => { let (account_id, chain_index) = wallet_core.create_new_account_public(cci); + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .unwrap(); + + let public_key = PublicKey::new_from_private_key(private_key); + println!( "Generated new account with account_id Public/{account_id} at path {chain_index}" ); + println!("With pk {}", hex::encode(public_key.value())); wallet_core.store_persistent_data().await?; @@ -201,7 +213,11 @@ impl WalletSubcommand for AccountSubcommand { wallet_core: &mut WalletCore, ) -> Result { match self { - AccountSubcommand::Get { raw, account_id } => { + AccountSubcommand::Get { + raw, + keys, + account_id, + } => { let (account_id, addr_kind) = parse_addr_with_privacy_prefix(&account_id)?; let account_id = account_id.parse()?; @@ -215,9 +231,43 @@ impl WalletSubcommand for AccountSubcommand { .ok_or(anyhow::anyhow!("Private account not found in storage"))?, }; + // Helper closure to display keys for the account + let display_keys = |wallet_core: &WalletCore| -> Result<()> { + match addr_kind { + AccountPrivacyKind::Public => { + let private_key = wallet_core + .storage + .user_data + .get_pub_account_signing_key(&account_id) + .ok_or(anyhow::anyhow!("Public account not found in storage"))?; + + let public_key = PublicKey::new_from_private_key(private_key); + println!("pk {}", hex::encode(public_key.value())); + } + AccountPrivacyKind::Private => { + let (key, _) = wallet_core + .storage + .user_data + .get_private_account(&account_id) + .ok_or(anyhow::anyhow!("Private account not found in storage"))?; + + println!("npk {}", hex::encode(key.nullifer_public_key.0)); + println!( + "ipk {}", + hex::encode(key.incoming_viewing_public_key.to_bytes()) + ); + } + } + Ok(()) + }; + if account == Account::default() { println!("Account is Uninitialized"); + if keys { + display_keys(wallet_core)?; + } + return Ok(SubcommandReturnValue::Empty); } @@ -232,6 +282,10 @@ impl WalletSubcommand for AccountSubcommand { println!("{description}"); println!("{json_view}"); + if keys { + display_keys(wallet_core)?; + } + Ok(SubcommandReturnValue::Empty) } AccountSubcommand::New(new_subcommand) => { @@ -288,7 +342,7 @@ impl WalletSubcommand for AccountSubcommand { .iter() .map(|(id, chain_index)| format!("{chain_index} Private/{id}")), ) - .format(",\n"); + .format("\n"); println!("{accounts}"); return Ok(SubcommandReturnValue::Empty); diff --git a/wallet/src/cli/chain.rs b/wallet/src/cli/chain.rs index 419fa5e2..17ecd020 100644 --- a/wallet/src/cli/chain.rs +++ b/wallet/src/cli/chain.rs @@ -19,7 +19,7 @@ pub enum ChainSubcommand { /// Get transaction at hash from sequencer Transaction { /// hash - valid 32 byte hex string - #[arg(short, long)] + #[arg(short = 't', long)] hash: String, }, } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 7c3da737..45709d05 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -375,7 +375,7 @@ impl WalletCore { pub async fn send_privacy_preserving_tx( &self, accounts: Vec, - instruction_data: &InstructionData, + instruction_data: InstructionData, program: &ProgramWithDependencies, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { self.send_privacy_preserving_tx_with_pre_check(accounts, instruction_data, program, |_| { @@ -387,7 +387,7 @@ impl WalletCore { pub async fn send_privacy_preserving_tx_with_pre_check( &self, accounts: Vec, - instruction_data: &InstructionData, + instruction_data: InstructionData, program: &ProgramWithDependencies, tx_pre_check: impl FnOnce(&[&Account]) -> Result<(), ExecutionFailureKind>, ) -> Result<(SendTxResponse, Vec), ExecutionFailureKind> { @@ -403,16 +403,16 @@ impl WalletCore { let private_account_keys = acc_manager.private_account_keys(); let (output, proof) = nssa::privacy_preserving_transaction::circuit::execute_and_prove( - &pre_states, + pre_states, instruction_data, - acc_manager.visibility_mask(), - &produce_random_nonces(private_account_keys.len()), - &private_account_keys + acc_manager.visibility_mask().to_vec(), + produce_random_nonces(private_account_keys.len()), + private_account_keys .iter() .map(|keys| (keys.npk.clone(), keys.ssk)) .collect::>(), - &acc_manager.private_account_auth(), - &acc_manager.private_account_membership_proofs(), + acc_manager.private_account_auth(), + acc_manager.private_account_membership_proofs(), &program.to_owned(), ) .unwrap(); diff --git a/wallet/src/program_facades/native_token_transfer/deshielded.rs b/wallet/src/program_facades/native_token_transfer/deshielded.rs index df604c65..7b774595 100644 --- a/wallet/src/program_facades/native_token_transfer/deshielded.rs +++ b/wallet/src/program_facades/native_token_transfer/deshielded.rs @@ -19,7 +19,7 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::Public(to), ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) diff --git a/wallet/src/program_facades/native_token_transfer/private.rs b/wallet/src/program_facades/native_token_transfer/private.rs index da98aed1..eabf1b60 100644 --- a/wallet/src/program_facades/native_token_transfer/private.rs +++ b/wallet/src/program_facades/native_token_transfer/private.rs @@ -17,7 +17,7 @@ impl NativeTokenTransfer<'_> { self.0 .send_privacy_preserving_tx( vec![PrivacyPreservingAccount::PrivateOwned(from)], - &Program::serialize_instruction(instruction).unwrap(), + Program::serialize_instruction(instruction).unwrap(), &Program::authenticated_transfer_program().into(), ) .await @@ -46,7 +46,7 @@ impl NativeTokenTransfer<'_> { ipk: to_ipk, }, ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) @@ -73,7 +73,7 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::PrivateOwned(from), PrivacyPreservingAccount::PrivateOwned(to), ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) diff --git a/wallet/src/program_facades/native_token_transfer/shielded.rs b/wallet/src/program_facades/native_token_transfer/shielded.rs index 6abd2d2c..85c8145c 100644 --- a/wallet/src/program_facades/native_token_transfer/shielded.rs +++ b/wallet/src/program_facades/native_token_transfer/shielded.rs @@ -20,7 +20,7 @@ impl NativeTokenTransfer<'_> { PrivacyPreservingAccount::Public(from), PrivacyPreservingAccount::PrivateOwned(to), ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) @@ -52,7 +52,7 @@ impl NativeTokenTransfer<'_> { ipk: to_ipk, }, ], - &instruction_data, + instruction_data, &program.into(), tx_pre_check, ) diff --git a/wallet/src/program_facades/pinata.rs b/wallet/src/program_facades/pinata.rs index fdd5d700..6036a603 100644 --- a/wallet/src/program_facades/pinata.rs +++ b/wallet/src/program_facades/pinata.rs @@ -37,7 +37,7 @@ impl Pinata<'_> { PrivacyPreservingAccount::Public(pinata_account_id), PrivacyPreservingAccount::PrivateOwned(winner_account_id), ], - &nssa::program::Program::serialize_instruction(solution).unwrap(), + nssa::program::Program::serialize_instruction(solution).unwrap(), &nssa::program::Program::pinata().into(), ) .await diff --git a/wallet/src/program_facades/token.rs b/wallet/src/program_facades/token.rs index fc03a0ac..0d3f79d7 100644 --- a/wallet/src/program_facades/token.rs +++ b/wallet/src/program_facades/token.rs @@ -52,7 +52,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -82,7 +82,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(supply_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -112,7 +112,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(supply_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -180,7 +180,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -212,7 +212,7 @@ impl Token<'_> { ipk: recipient_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -240,7 +240,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(sender_account_id), PrivacyPreservingAccount::Public(recipient_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -269,7 +269,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(sender_account_id), PrivacyPreservingAccount::PrivateOwned(recipient_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -302,7 +302,7 @@ impl Token<'_> { ipk: recipient_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -365,7 +365,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -393,7 +393,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -422,7 +422,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -491,7 +491,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -523,7 +523,7 @@ impl Token<'_> { ipk: holder_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -551,7 +551,7 @@ impl Token<'_> { PrivacyPreservingAccount::PrivateOwned(definition_account_id), PrivacyPreservingAccount::Public(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -580,7 +580,7 @@ impl Token<'_> { PrivacyPreservingAccount::Public(definition_account_id), PrivacyPreservingAccount::PrivateOwned(holder_account_id), ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await @@ -613,7 +613,7 @@ impl Token<'_> { ipk: holder_ipk, }, ], - &instruction_data, + instruction_data, &Program::token().into(), ) .await