diff --git a/Cargo.lock b/Cargo.lock index 4eee1486775..66f0ba61607 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,16 @@ version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" +[[package]] +name = "aead" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" +dependencies = [ + "crypto-common", + "generic-array 0.14.7", +] + [[package]] name = "aes" version = "0.8.4" @@ -569,6 +579,17 @@ dependencies = [ "wyz", ] +[[package]] +name = "blake2b_simd" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b79834656f71332577234b50bfc009996f7449e0c056884e6a02492ded0ca2f3" +dependencies = [ + "arrayref", + "arrayvec", + "constant_time_eq 0.4.2", +] + [[package]] name = "blake3" version = "1.8.3" @@ -622,6 +643,17 @@ dependencies = [ "serde", ] +[[package]] +name = "bls12_381" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7bc6d6292be3a19e6379786dac800f551e5865a5bb51ebbe3064ab80433f403" +dependencies = [ + "ff", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "blsful" version = "3.0.0" @@ -865,6 +897,30 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chacha20" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" +dependencies = [ + "aead", + "chacha20", + "cipher", + "poly1305", + "zeroize", +] + [[package]] name = "check-features" version = "3.1.0-dev.1" @@ -943,6 +999,7 @@ checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" dependencies = [ "crypto-common", "inout", + "zeroize", ] [[package]] @@ -1122,6 +1179,15 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "core2" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "239fa3ae9b63c2dc74bd3fa852d4792b8b305ae64eeede946265b6af62f1fff3" +dependencies = [ + "memchr", +] + [[package]] name = "core2" version = "0.4.0" @@ -1849,6 +1915,7 @@ dependencies = [ "dpp", "env_logger 0.11.9", "getrandom 0.2.17", + "grovedb-commitment-tree", "hex", "indexmap 2.13.0", "integer-encoding", @@ -2334,6 +2401,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fpe" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c4b37de5ae15812a764c958297cfc50f5c010438f60c6ce75d11b802abd404" +dependencies = [ + "cbc", + "cipher", + "libm", + "num-bigint", + "num-integer", + "num-traits", +] + [[package]] name = "fraction" version = "0.15.3" @@ -2512,6 +2593,18 @@ dependencies = [ "wasip3", ] +[[package]] +name = "getset" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9cf0fc11e47561d47397154977bc219f4cf809b2974facc3ccb3b89e2436f912" +dependencies = [ + "proc-macro-error2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "glob" version = "0.3.3" @@ -2550,6 +2643,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" dependencies = [ "ff", + "memuse", "rand 0.8.5", "rand_core 0.6.4", "rand_xorshift", @@ -2559,16 +2653,21 @@ dependencies = [ [[package]] name = "grovedb" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "axum 0.8.8", "bincode", "bincode_derive", "blake3", + "grovedb-bulk-append-tree", + "grovedb-commitment-tree", "grovedb-costs", + "grovedb-dense-fixed-sized-merkle-tree", "grovedb-element", "grovedb-merk", + "grovedb-merkle-mountain-range", "grovedb-path", + "grovedb-query", "grovedb-storage", "grovedb-version", "grovedb-visualize", @@ -2589,20 +2688,63 @@ dependencies = [ "zip-extensions", ] +[[package]] +name = "grovedb-bulk-append-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-dense-fixed-sized-merkle-tree", + "grovedb-merkle-mountain-range", + "grovedb-query", + "grovedb-storage", + "hex", + "thiserror 2.0.18", +] + +[[package]] +name = "grovedb-commitment-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "blake3", + "grovedb-bulk-append-tree", + "grovedb-costs", + "grovedb-storage", + "incrementalmerkletree", + "orchard", + "thiserror 2.0.18", +] + [[package]] name = "grovedb-costs" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "integer-encoding", "intmap", "thiserror 2.0.18", ] +[[package]] +name = "grovedb-dense-fixed-sized-merkle-tree" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-query", + "grovedb-storage", + "thiserror 2.0.18", +] + [[package]] name = "grovedb-element" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "bincode", "bincode_derive", @@ -2617,7 +2759,7 @@ dependencies = [ [[package]] name = "grovedb-epoch-based-storage-flags" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "grovedb-costs", "hex", @@ -2629,7 +2771,7 @@ dependencies = [ [[package]] name = "grovedb-merk" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "bincode", "bincode_derive", @@ -2640,6 +2782,7 @@ dependencies = [ "grovedb-costs", "grovedb-element", "grovedb-path", + "grovedb-query", "grovedb-storage", "grovedb-version", "grovedb-visualize", @@ -2651,18 +2794,45 @@ dependencies = [ "thiserror 2.0.18", ] +[[package]] +name = "grovedb-merkle-mountain-range" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "bincode", + "blake3", + "grovedb-costs", + "grovedb-storage", +] + [[package]] name = "grovedb-path" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" +dependencies = [ + "hex", +] + +[[package]] +name = "grovedb-query" +version = "4.0.0" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ + "bincode", + "byteorder", + "ed", + "grovedb-costs", + "grovedb-storage", "hex", + "indexmap 2.13.0", + "integer-encoding", + "thiserror 2.0.18", ] [[package]] name = "grovedb-storage" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "blake3", "grovedb-costs", @@ -2681,7 +2851,7 @@ dependencies = [ [[package]] name = "grovedb-version" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "thiserror 2.0.18", "versioned-feature-core 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2690,7 +2860,7 @@ dependencies = [ [[package]] name = "grovedb-visualize" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "hex", "itertools 0.14.0", @@ -2699,7 +2869,7 @@ dependencies = [ [[package]] name = "grovedbg-types" version = "4.0.0" -source = "git+https://github.com/dashpay/grovedb?rev=33dfd48a1718160cb333fa95424be491785f1897#33dfd48a1718160cb333fa95424be491785f1897" +source = "git+https://github.com/dashpay/grovedb?rev=7ecb8465fad750c7cddd5332adb6f97fcceb498b#7ecb8465fad750c7cddd5332adb6f97fcceb498b" dependencies = [ "serde", "serde_with 3.17.0", @@ -2735,6 +2905,61 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "halo2_gadgets" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45824ce0dd12e91ec0c68ebae2a7ed8ae19b70946624c849add59f1d1a62a143" +dependencies = [ + "arrayvec", + "bitvec", + "ff", + "group", + "halo2_poseidon", + "halo2_proofs", + "lazy_static", + "pasta_curves", + "rand 0.8.5", + "sinsemilla", + "subtle", + "uint", +] + +[[package]] +name = "halo2_legacy_pdqsort" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47716fe1ae67969c5e0b2ef826f32db8c3be72be325e1aa3c1951d06b5575ec5" + +[[package]] +name = "halo2_poseidon" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa3da60b81f02f9b33ebc6252d766f843291fb4d2247a07ae73d20b791fc56f" +dependencies = [ + "bitvec", + "ff", + "group", + "pasta_curves", +] + +[[package]] +name = "halo2_proofs" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05713f117155643ce10975e0bee44a274bcda2f4bb5ef29a999ad67c1fa8d4d3" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "halo2_legacy_pdqsort", + "indexmap 1.9.3", + "maybe-rayon", + "pasta_curves", + "rand_core 0.6.4", + "tracing", +] + [[package]] name = "hash32" version = "0.3.1" @@ -3233,6 +3458,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "incrementalmerkletree" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30821f91f0fa8660edca547918dc59812893b497d07c1144f326f07fdd94aba9" +dependencies = [ + "either", +] + [[package]] name = "indexmap" version = "1.9.3" @@ -3473,6 +3707,20 @@ dependencies = [ "uuid", ] +[[package]] +name = "jubjub" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8499f7a74008aafbecb2a2e608a3e13e4dd3e84df198b604451efe93f2de6e61" +dependencies = [ + "bitvec", + "bls12_381", + "ff", + "group", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "keccak" version = "0.1.6" @@ -3560,6 +3808,9 @@ name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +dependencies = [ + "spin 0.9.8", +] [[package]] name = "lazycell" @@ -3725,12 +3976,28 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3" +[[package]] +name = "maybe-rayon" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea1f30cedd69f0a2954655f7188c6a834246d2bcf1e315e2ac40c4b24dc9519" +dependencies = [ + "cfg-if", + "rayon", +] + [[package]] name = "memchr" version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8ca58f447f06ed17d5fc4043ce1b10dd205e060fb3ce5b979b8ed8e59ff3f79" +[[package]] +name = "memuse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d97bbf43eb4f088f8ca469930cde17fa036207c9a5e02ccc5107c4e8b17c964" + [[package]] name = "merlin" version = "3.0.0" @@ -3956,6 +4223,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "nonempty" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "549e471b99ccaf2f89101bec68f4d244457d5a95a9c3d0672e9564124397741d" + [[package]] name = "nu-ansi-term" version = "0.50.3" @@ -4142,6 +4415,12 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "opaque-debug" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" + [[package]] name = "openssl" version = "0.10.75" @@ -4186,6 +4465,41 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "orchard" +version = "0.12.0" +source = "git+https://github.com/dashpay/orchard.git?rev=41c8f7169f2683c99cf0e0c63e8d25ec12c47a79#41c8f7169f2683c99cf0e0c63e8d25ec12c47a79" +dependencies = [ + "aes", + "bitvec", + "blake2b_simd", + "core2 0.3.3", + "ff", + "fpe", + "getset", + "group", + "halo2_gadgets", + "halo2_poseidon", + "halo2_proofs", + "hex", + "incrementalmerkletree", + "lazy_static", + "memuse", + "nonempty", + "pasta_curves", + "rand 0.8.5", + "rand_core 0.6.4", + "reddsa", + "serde", + "sinsemilla", + "subtle", + "tracing", + "visibility", + "zcash_note_encryption", + "zcash_spec", + "zip32", +] + [[package]] name = "pairing" version = "0.23.0" @@ -4233,6 +4547,21 @@ dependencies = [ "regex", ] +[[package]] +name = "pasta_curves" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e57598f73cc7e1b2ac63c79c517b31a0877cd7c402cdcaa311b5208de7a095" +dependencies = [ + "blake2b_simd", + "ff", + "group", + "lazy_static", + "rand 0.8.5", + "static_assertions", + "subtle", +] + [[package]] name = "paste" version = "1.0.15" @@ -4495,6 +4824,17 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "poly1305" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" +dependencies = [ + "cpufeatures", + "opaque-debug", + "universal-hash", +] + [[package]] name = "portable-atomic" version = "1.13.1" @@ -4599,6 +4939,28 @@ dependencies = [ "toml_edit 0.23.10+spec-1.0.0", ] +[[package]] +name = "proc-macro-error-attr2" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96de42df36bb9bba5542fe9f1a054b8cc87e172759a1868aa05c1f3acc89dfc5" +dependencies = [ + "proc-macro2", + "quote", +] + +[[package]] +name = "proc-macro-error2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11ec05c52be0a07b08061f7dd003e7d7092e0472bc731b4af7bb1ef876109802" +dependencies = [ + "proc-macro-error-attr2", + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "proc-macro2" version = "1.0.106" @@ -4977,6 +5339,24 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "reddsa" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78a5191930e84973293aa5f532b513404460cd2216c1cfb76d08748c15b40b02" +dependencies = [ + "blake2b_simd", + "byteorder", + "group", + "hex", + "jubjub", + "pasta_curves", + "rand_core 0.6.4", + "serde", + "thiserror 1.0.69", + "zeroize", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -5997,6 +6377,17 @@ dependencies = [ "tracing", ] +[[package]] +name = "sinsemilla" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d268ae0ea06faafe1662e9967cd4f9022014f5eeb798e0c302c876df8b7af9c" +dependencies = [ + "group", + "pasta_curves", + "subtle", +] + [[package]] name = "siphasher" version = "1.0.2" @@ -6041,6 +6432,12 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + [[package]] name = "spin" version = "0.10.0" @@ -6072,6 +6469,12 @@ version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + [[package]] name = "std-shims" version = "0.1.5" @@ -6080,7 +6483,7 @@ checksum = "227c4f8561598188d0df96dbe749824576174bba278b5b6bb2eacff1066067d0" dependencies = [ "hashbrown 0.16.1", "rustversion", - "spin", + "spin 0.10.0", ] [[package]] @@ -7037,13 +7440,25 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "562d481066bde0658276a35467c4af00bdc6ee726305698a55b86e61d7ad82bb" +[[package]] +name = "uint" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + [[package]] name = "uint-zigzag" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "abbf77aed65cb885a8ba07138c365879be3d9a93dce82bf6cc50feca9138ec15" dependencies = [ - "core2", + "core2 0.4.0", ] [[package]] @@ -7079,6 +7494,16 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" +[[package]] +name = "universal-hash" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea" +dependencies = [ + "crypto-common", + "subtle", +] + [[package]] name = "untrusted" version = "0.9.0" @@ -7203,6 +7628,17 @@ version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" +[[package]] +name = "visibility" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d674d135b4a8c1d7e813e2f8d1c9a58308aee4a680323066025e53132218bd91" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.117", +] + [[package]] name = "vsss-rs" version = "5.1.0" @@ -8088,6 +8524,27 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zcash_note_encryption" +version = "0.4.1" +source = "git+https://github.com/dashpay/zcash_note_encryption?rev=9f7e93d#9f7e93d42cef839d02b9d75918117941d453f8cb" +dependencies = [ + "chacha20", + "chacha20poly1305", + "cipher", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "zcash_spec" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ded3f58b93486aa79b85acba1001f5298f27a46489859934954d262533ee2915" +dependencies = [ + "blake2b_simd", +] + [[package]] name = "zerocopy" version = "0.8.39" @@ -8252,6 +8709,19 @@ dependencies = [ "zip 6.0.0", ] +[[package]] +name = "zip32" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b64bf5186a8916f7a48f2a98ef599bf9c099e2458b36b819e393db1c0e768c4b" +dependencies = [ + "bech32 0.11.1", + "blake2b_simd", + "memuse", + "subtle", + "zcash_spec", +] + [[package]] name = "zlib-rs" version = "0.6.2" diff --git a/Cargo.toml b/Cargo.toml index 1080580a12f..a32bdea8c26 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -53,6 +53,35 @@ key-wallet = { git = "https://github.com/dashpay/rust-dashcore", rev = "0bc6592b key-wallet-manager = { git = "https://github.com/dashpay/rust-dashcore", rev = "0bc6592bd41037ffc532d813d4c0828bea7cf882" } dashcore-rpc = { git = "https://github.com/dashpay/rust-dashcore", rev = "0bc6592bd41037ffc532d813d4c0828bea7cf882" } +# Optimize heavy crypto crates even in dev/test builds so that +# Halo 2 proof generation and verification run at near-release speed. +# Without this, ZK operations are 10-100x slower (debug field arithmetic). +[profile.dev.package.halo2_proofs] +opt-level = 3 +[profile.dev.package.halo2_gadgets] +opt-level = 3 +[profile.dev.package.halo2_poseidon] +opt-level = 3 +[profile.dev.package.orchard] +opt-level = 3 +[profile.dev.package.pasta_curves] +opt-level = 3 +[profile.dev.package.grovedb-commitment-tree] +opt-level = 3 + +[profile.test.package.halo2_proofs] +opt-level = 3 +[profile.test.package.halo2_gadgets] +opt-level = 3 +[profile.test.package.halo2_poseidon] +opt-level = 3 +[profile.test.package.orchard] +opt-level = 3 +[profile.test.package.pasta_curves] +opt-level = 3 +[profile.test.package.grovedb-commitment-tree] +opt-level = 3 + [workspace.package] version = "3.1.0-dev.1" diff --git a/packages/rs-dpp/Cargo.toml b/packages/rs-dpp/Cargo.toml index 09643a4a23b..074329c2a23 100644 --- a/packages/rs-dpp/Cargo.toml +++ b/packages/rs-dpp/Cargo.toml @@ -71,6 +71,7 @@ strum = { version = "0.26", features = ["derive"] } json-schema-compatibility-validator = { path = '../rs-json-schema-compatibility-validator', optional = true } once_cell = "1.19.0" tracing = { version = "0.1.41" } +grovedb-commitment-tree = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } [dev-dependencies] tokio = { version = "1.40", features = ["full"] } @@ -327,5 +328,13 @@ extended-document = [ ] token-reward-explanations = ["dep:chrono-tz"] +# Gates client-side Orchard helpers (address encoding, bundle building, proving). +# Clients that don't need to create shielded transactions can omit this to avoid +# compiling the Orchard/Halo 2 dependency tree. The shielded state transition +# types themselves are always available — only the client tooling is behind this gate. +shielded-client = [ + "state-transition-signing", + "dep:grovedb-commitment-tree", +] factories = [] client = ["factories", "state-transitions"] diff --git a/packages/rs-dpp/src/address_funds/mod.rs b/packages/rs-dpp/src/address_funds/mod.rs index 68508382934..979ef7970ec 100644 --- a/packages/rs-dpp/src/address_funds/mod.rs +++ b/packages/rs-dpp/src/address_funds/mod.rs @@ -1,9 +1,13 @@ pub mod fee_strategy; +#[cfg(feature = "shielded-client")] +mod orchard_address; mod platform_address; mod witness; mod witness_verification_operations; pub use fee_strategy::*; +#[cfg(feature = "shielded-client")] +pub use orchard_address::*; pub use platform_address::*; pub use witness::*; pub use witness_verification_operations::*; diff --git a/packages/rs-dpp/src/address_funds/orchard_address.rs b/packages/rs-dpp/src/address_funds/orchard_address.rs new file mode 100644 index 00000000000..02173a0134a --- /dev/null +++ b/packages/rs-dpp/src/address_funds/orchard_address.rs @@ -0,0 +1,286 @@ +use bech32::{Bech32m, Hrp}; +use dashcore::Network; + +use crate::address_funds::platform_address::{PLATFORM_HRP_MAINNET, PLATFORM_HRP_TESTNET}; +use crate::address_funds::PlatformAddress; +use crate::ProtocolError; + +/// Size of the Orchard diversifier (11 bytes). +pub const ORCHARD_DIVERSIFIER_SIZE: usize = 11; +/// Size of the Orchard diversified transmission key pk_d (32 bytes, Pallas curve point). +pub const ORCHARD_PKD_SIZE: usize = 32; +/// Total size of a raw Orchard payment address (43 bytes = diversifier + pk_d). +pub const ORCHARD_ADDRESS_SIZE: usize = ORCHARD_DIVERSIFIER_SIZE + ORCHARD_PKD_SIZE; + +/// An Orchard shielded payment address. +/// +/// Composed of a diversifier (11 bytes) and a diversified transmission key (32 bytes). +/// The diversifier enables a single spending key to derive an unlimited number of +/// unlinkable payment addresses. Only the holder of the corresponding FullViewingKey +/// (or IncomingViewingKey) can link diversified addresses to the same wallet. +/// +/// Bech32m encoding uses type byte `0x10`, producing addresses that start with `z`: +/// - Mainnet: `dash1z...` +/// - Testnet: `tdash1z...` +/// +/// The raw Orchard address format matches Zcash Orchard (43 bytes), but the +/// string encoding is Dash-specific (no F4Jumble, no Unified Address wrapper). +/// +/// Wraps `grovedb_commitment_tree::PaymentAddress`. Use [`From`] +/// to convert from the orchard crate's native type, or [`inner()`](OrchardAddress::inner) +/// / [`into_inner()`](OrchardAddress::into_inner) to access the wrapped address. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct OrchardAddress(grovedb_commitment_tree::PaymentAddress); + +impl OrchardAddress { + /// Type byte for Orchard addresses in bech32m encoding (user-facing). + /// Produces 'z' as the first bech32 character. + pub const ORCHARD_TYPE: u8 = 0x10; + + /// Returns the inner [`PaymentAddress`](grovedb_commitment_tree::PaymentAddress). + pub fn inner(&self) -> &grovedb_commitment_tree::PaymentAddress { + &self.0 + } + + /// Consumes the wrapper and returns the inner `PaymentAddress`. + pub fn into_inner(self) -> grovedb_commitment_tree::PaymentAddress { + self.0 + } + + /// Creates an OrchardAddress from a 43-byte raw address. + /// + /// The first 11 bytes are the diversifier, the next 32 are pk_d. + /// Returns an error if `pk_d` is not a valid Pallas curve point. + pub fn from_raw_bytes(bytes: &[u8; ORCHARD_ADDRESS_SIZE]) -> Result { + let addr = + Option::from(grovedb_commitment_tree::PaymentAddress::from_raw_address_bytes(bytes)) + .ok_or_else(|| { + ProtocolError::DecodingError( + "OrchardAddress pk_d is not a valid Pallas curve point".to_string(), + ) + })?; + Ok(Self(addr)) + } + + /// Returns the raw 43-byte address (diversifier || pk_d). + pub fn to_raw_bytes(&self) -> [u8; ORCHARD_ADDRESS_SIZE] { + self.0.to_raw_address_bytes() + } + + /// Encodes the OrchardAddress as a bech32m string for the specified network. + /// + /// Format: `1` + /// - Data: type_byte (0x10) || diversifier (11 bytes) || pk_d (32 bytes) + /// - Total payload: 44 bytes + /// - Checksum: bech32m (BIP-350) + pub fn to_bech32m_string(&self, network: Network) -> String { + let hrp_str = PlatformAddress::hrp_for_network(network); + let hrp = Hrp::parse(hrp_str).expect("HRP is valid"); + + let raw = self.to_raw_bytes(); + let mut payload = Vec::with_capacity(1 + ORCHARD_ADDRESS_SIZE); + payload.push(Self::ORCHARD_TYPE); + payload.extend_from_slice(&raw); + + bech32::encode::(hrp, &payload).expect("encoding should succeed") + } + + /// Decodes a bech32m-encoded Orchard address string. + /// + /// # Returns + /// - `Ok((OrchardAddress, Network))` - The decoded address and its network + /// - `Err(ProtocolError)` - If the address is invalid + pub fn from_bech32m_string(s: &str) -> Result<(Self, Network), ProtocolError> { + let (hrp, data) = + bech32::decode(s).map_err(|e| ProtocolError::DecodingError(format!("{}", e)))?; + + let hrp_lower = hrp.as_str().to_ascii_lowercase(); + let network = match hrp_lower.as_str() { + s if s == PLATFORM_HRP_MAINNET => Network::Dash, + s if s == PLATFORM_HRP_TESTNET => Network::Testnet, + _ => { + return Err(ProtocolError::DecodingError(format!( + "invalid HRP '{}': expected '{}' or '{}'", + hrp, PLATFORM_HRP_MAINNET, PLATFORM_HRP_TESTNET + ))) + } + }; + + // Validate payload: 1 type byte + 11 diversifier + 32 pk_d = 44 bytes + if data.len() != 1 + ORCHARD_ADDRESS_SIZE { + return Err(ProtocolError::DecodingError(format!( + "invalid Orchard address length: expected {} bytes, got {}", + 1 + ORCHARD_ADDRESS_SIZE, + data.len() + ))); + } + + if data[0] != Self::ORCHARD_TYPE { + return Err(ProtocolError::DecodingError(format!( + "invalid Orchard address type byte: expected 0x{:02x}, got 0x{:02x}", + Self::ORCHARD_TYPE, + data[0] + ))); + } + + let mut raw = [0u8; ORCHARD_ADDRESS_SIZE]; + raw.copy_from_slice(&data[1..]); + Self::from_raw_bytes(&raw).map(|addr| (addr, network)) + } +} + +/// Infallible conversion from the orchard crate's `PaymentAddress` to `OrchardAddress`. +impl From for OrchardAddress { + fn from(addr: grovedb_commitment_tree::PaymentAddress) -> Self { + Self(addr) + } +} + +/// Infallible conversion from a reference to `PaymentAddress`. +impl From<&grovedb_commitment_tree::PaymentAddress> for OrchardAddress { + fn from(addr: &grovedb_commitment_tree::PaymentAddress) -> Self { + Self(*addr) + } +} + +impl std::fmt::Display for OrchardAddress { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let raw = self.to_raw_bytes(); + write!( + f, + "Orchard(d={}, pk_d={})", + hex::encode(&raw[..ORCHARD_DIVERSIFIER_SIZE]), + hex::encode(&raw[ORCHARD_DIVERSIFIER_SIZE..]) + ) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use bech32::Hrp; + + fn test_orchard_address() -> OrchardAddress { + use grovedb_commitment_tree::{FullViewingKey, Scope, SpendingKey}; + let sk = SpendingKey::from_bytes([42u8; 32]).unwrap(); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + OrchardAddress::from(payment_address) + } + + #[test] + fn test_orchard_address_raw_bytes_roundtrip() { + let address = test_orchard_address(); + let raw = address.to_raw_bytes(); + assert_eq!(raw.len(), 43); + + let recovered = OrchardAddress::from_raw_bytes(&raw).unwrap(); + assert_eq!(recovered, address); + } + + #[test] + fn test_orchard_bech32m_mainnet_roundtrip() { + let address = test_orchard_address(); + + let encoded = address.to_bech32m_string(Network::Dash); + assert!( + encoded.starts_with("dash1z"), + "Orchard mainnet address should start with 'dash1z', got: {}", + encoded + ); + + let (decoded, network) = + OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); + assert_eq!(decoded, address); + assert_eq!(network, Network::Dash); + } + + #[test] + fn test_orchard_bech32m_testnet_roundtrip() { + let address = test_orchard_address(); + + let encoded = address.to_bech32m_string(Network::Testnet); + assert!( + encoded.starts_with("tdash1z"), + "Orchard testnet address should start with 'tdash1z', got: {}", + encoded + ); + + let (decoded, network) = + OrchardAddress::from_bech32m_string(&encoded).expect("decoding should succeed"); + assert_eq!(decoded, address); + assert_eq!(network, Network::Testnet); + } + + #[test] + fn test_orchard_bech32m_wrong_type_byte_fails() { + // Manually construct an address with P2PKH type byte (0xb0) but 44-byte payload + let hrp = Hrp::parse("dash").unwrap(); + let mut payload = vec![PlatformAddress::P2PKH_TYPE]; // Wrong type byte + payload.extend_from_slice(&[0u8; 43]); + let encoded = bech32::encode::(hrp, &payload).unwrap(); + + let result = OrchardAddress::from_bech32m_string(&encoded); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("invalid Orchard address type byte")); + } + + #[test] + fn test_orchard_bech32m_wrong_length_fails() { + // Too short (only 20 bytes instead of 43) + let hrp = Hrp::parse("dash").unwrap(); + let mut payload = vec![OrchardAddress::ORCHARD_TYPE]; + payload.extend_from_slice(&[0u8; 20]); + let encoded = bech32::encode::(hrp, &payload).unwrap(); + + let result = OrchardAddress::from_bech32m_string(&encoded); + assert!(result.is_err()); + assert!(result + .unwrap_err() + .to_string() + .contains("invalid Orchard address length")); + } + + #[test] + fn test_orchard_and_platform_addresses_are_distinguishable() { + let p2pkh = PlatformAddress::P2pkh([0xAB; 20]); + let p2sh = PlatformAddress::P2sh([0xAB; 20]); + let orchard = test_orchard_address(); + + let p2pkh_enc = p2pkh.to_bech32m_string(Network::Dash); + let p2sh_enc = p2sh.to_bech32m_string(Network::Dash); + let orchard_enc = orchard.to_bech32m_string(Network::Dash); + + // All three start with "dash1" but have different type-byte characters + assert!(p2pkh_enc.starts_with("dash1k"), "P2PKH: {}", p2pkh_enc); + assert!(p2sh_enc.starts_with("dash1s"), "P2SH: {}", p2sh_enc); + assert!( + orchard_enc.starts_with("dash1z"), + "Orchard: {}", + orchard_enc + ); + + // Cross-decoding should fail + assert!(PlatformAddress::from_bech32m_string(&orchard_enc).is_err()); + assert!(OrchardAddress::from_bech32m_string(&p2pkh_enc).is_err()); + } + + #[test] + fn test_orchard_address_from_raw_bytes_invalid_pk_d() { + // All zeros for pk_d is not a valid Pallas curve point + let mut raw = [0u8; 43]; + raw[0] = 0x01; // non-zero diversifier + assert!(OrchardAddress::from_raw_bytes(&raw).is_err()); + } + + #[test] + fn test_orchard_address_display() { + let address = test_orchard_address(); + let display = format!("{}", address); + assert!(display.starts_with("Orchard(d=")); + assert!(display.contains("pk_d=")); + } +} diff --git a/packages/rs-dpp/src/address_funds/platform_address.rs b/packages/rs-dpp/src/address_funds/platform_address.rs index 8d6f3e7a722..3775accefc9 100644 --- a/packages/rs-dpp/src/address_funds/platform_address.rs +++ b/packages/rs-dpp/src/address_funds/platform_address.rs @@ -1303,7 +1303,7 @@ mod tests { assert_eq!(p2pkh.to_bytes()[0], 0x00); assert_eq!(p2sh.to_bytes()[0], 0x01); - // Bech32m encoding uses 0xb0/0x80 (verified by successful roundtrip) + // Bech32m encoding uses 0xb0/0xb8 (verified by successful roundtrip) let p2pkh_encoded = p2pkh.to_bech32m_string(Network::Dash); let p2sh_encoded = p2sh.to_bech32m_string(Network::Dash); diff --git a/packages/rs-dpp/src/asset_lock/mod.rs b/packages/rs-dpp/src/asset_lock/mod.rs index 5133348df5b..3fa57ebb2c3 100644 --- a/packages/rs-dpp/src/asset_lock/mod.rs +++ b/packages/rs-dpp/src/asset_lock/mod.rs @@ -6,6 +6,7 @@ pub type PastAssetLockStateTransitionHashes = Vec>; /// An enumeration of the possible states when querying platform to get the stored state of an outpoint /// representing if the asset lock was already used or not. +#[derive(Debug, serde::Serialize, serde::Deserialize)] pub enum StoredAssetLockInfo { /// The asset lock was fully consumed in the past FullyConsumed, diff --git a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs index 5b18311013a..7ccabcd9c96 100644 --- a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs +++ b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs @@ -11,7 +11,18 @@ mod v0; pub use v0::{AssetLockValueGettersV0, AssetLockValueSettersV0}; -#[derive(Debug, Clone, Encode, Decode, PlatformSerialize, PlatformDeserialize, From, PartialEq)] +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + From, + PartialEq, + serde::Serialize, + serde::Deserialize, +)] #[platform_serialize(unversioned)] pub enum AssetLockValue { V0(AssetLockValueV0), diff --git a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs index bc733e03f48..6ad46de91e8 100644 --- a/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs +++ b/packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/v0/mod.rs @@ -2,7 +2,7 @@ use crate::fee::Credits; use bincode::{Decode, Encode}; use platform_value::Bytes32; -#[derive(Debug, Clone, Encode, Decode, PartialEq)] +#[derive(Debug, Clone, Encode, Decode, PartialEq, serde::Serialize, serde::Deserialize)] pub struct AssetLockValueV0 { pub(super) initial_credit_value: Credits, pub(super) tx_out_script: Vec, diff --git a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs index db5d518eb93..05dee2b8ab2 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/basic_error.rs @@ -77,10 +77,11 @@ use crate::consensus::basic::state_transition::{ InputWitnessCountMismatchError, InputsNotLessThanOutputsError, InsufficientFundingAmountError, InvalidRemainderOutputCountError, InvalidStateTransitionTypeError, MissingStateTransitionTypeError, OutputAddressAlsoInputError, OutputBelowMinimumError, - OutputsNotGreaterThanInputsError, StateTransitionMaxSizeExceededError, - StateTransitionNotActiveError, TransitionNoInputsError, TransitionNoOutputsError, - TransitionOverMaxInputsError, TransitionOverMaxOutputsError, WithdrawalBalanceMismatchError, - WithdrawalBelowMinAmountError, + OutputsNotGreaterThanInputsError, ShieldedEmptyProofError, ShieldedInvalidValueBalanceError, + ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedZeroAnchorError, + StateTransitionMaxSizeExceededError, StateTransitionNotActiveError, TransitionNoInputsError, + TransitionNoOutputsError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, + WithdrawalBalanceMismatchError, WithdrawalBelowMinAmountError, }; use crate::consensus::basic::{ IncompatibleProtocolVersionError, UnsupportedFeatureError, UnsupportedProtocolVersionError, @@ -657,6 +658,21 @@ pub enum BasicError { #[error(transparent)] OutputAddressAlsoInputError(OutputAddressAlsoInputError), + + #[error(transparent)] + ShieldedNoActionsError(ShieldedNoActionsError), + + #[error(transparent)] + ShieldedTooManyActionsError(ShieldedTooManyActionsError), + + #[error(transparent)] + ShieldedEmptyProofError(ShieldedEmptyProofError), + + #[error(transparent)] + ShieldedZeroAnchorError(ShieldedZeroAnchorError), + + #[error(transparent)] + ShieldedInvalidValueBalanceError(ShieldedInvalidValueBalanceError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs index a7c02e2db76..b9acc33f2a7 100644 --- a/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/mod.rs @@ -13,6 +13,11 @@ mod missing_state_transition_type_error; mod output_address_also_input_error; mod output_below_minimum_error; mod outputs_not_greater_than_inputs_error; +mod shielded_empty_proof_error; +mod shielded_invalid_value_balance_error; +mod shielded_no_actions_error; +mod shielded_too_many_actions_error; +mod shielded_zero_anchor_error; mod state_transition_max_size_exceeded_error; mod state_transition_not_active_error; mod transition_no_inputs_error; @@ -37,6 +42,11 @@ pub use missing_state_transition_type_error::*; pub use output_address_also_input_error::*; pub use output_below_minimum_error::*; pub use outputs_not_greater_than_inputs_error::*; +pub use shielded_empty_proof_error::*; +pub use shielded_invalid_value_balance_error::*; +pub use shielded_no_actions_error::*; +pub use shielded_too_many_actions_error::*; +pub use shielded_zero_anchor_error::*; pub use state_transition_max_size_exceeded_error::*; pub use state_transition_not_active_error::*; pub use transition_no_inputs_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs new file mode 100644 index 00000000000..93204cdd3bf --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_empty_proof_error.rs @@ -0,0 +1,37 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Shielded transition proof must not be empty")] +#[platform_serialize(unversioned)] +pub struct ShieldedEmptyProofError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl ShieldedEmptyProofError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ShieldedEmptyProofError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: ShieldedEmptyProofError) -> Self { + Self::BasicError(BasicError::ShieldedEmptyProofError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs new file mode 100644 index 00000000000..77cc5176e88 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_invalid_value_balance_error.rs @@ -0,0 +1,41 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Invalid shielded value_balance: {message}")] +#[platform_serialize(unversioned)] +pub struct ShieldedInvalidValueBalanceError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + /// A human-readable description of the validation failure (e.g. "must be negative", + /// "must be positive", "must be zero"). Ideally this would be typed fields such as + /// `value_balance: i64` and `reason: &'static str`, but because this struct derives + /// `PlatformSerialize`/`Encode` and is part of the consensus protocol (error code 10822), + /// changing the field layout would break wire compatibility. Use a new version instead. + message: String, +} + +impl ShieldedInvalidValueBalanceError { + pub fn new(message: String) -> Self { + Self { message } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for ConsensusError { + fn from(err: ShieldedInvalidValueBalanceError) -> Self { + Self::BasicError(BasicError::ShieldedInvalidValueBalanceError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs new file mode 100644 index 00000000000..c1ebe75b5f7 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_no_actions_error.rs @@ -0,0 +1,37 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Shielded transition must have at least one action")] +#[platform_serialize(unversioned)] +pub struct ShieldedNoActionsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl ShieldedNoActionsError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ShieldedNoActionsError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: ShieldedNoActionsError) -> Self { + Self::BasicError(BasicError::ShieldedNoActionsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_error.rs new file mode 100644 index 00000000000..7b7218a79fd --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_too_many_actions_error.rs @@ -0,0 +1,46 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error( + "Shielded transition has {actual_actions} actions, which exceeds the maximum allowed {max_actions}" +)] +#[platform_serialize(unversioned)] +pub struct ShieldedTooManyActionsError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + actual_actions: u16, + max_actions: u16, +} + +impl ShieldedTooManyActionsError { + pub fn new(actual_actions: u16, max_actions: u16) -> Self { + Self { + actual_actions, + max_actions, + } + } + + pub fn actual_actions(&self) -> u16 { + self.actual_actions + } + + pub fn max_actions(&self) -> u16 { + self.max_actions + } +} + +impl From for ConsensusError { + fn from(err: ShieldedTooManyActionsError) -> Self { + Self::BasicError(BasicError::ShieldedTooManyActionsError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs new file mode 100644 index 00000000000..10761119e9c --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/basic/state_transition/shielded_zero_anchor_error.rs @@ -0,0 +1,37 @@ +use crate::consensus::basic::BasicError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[error("Shielded transition anchor must not be all zeros")] +#[platform_serialize(unversioned)] +pub struct ShieldedZeroAnchorError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ +} + +impl ShieldedZeroAnchorError { + pub fn new() -> Self { + Self {} + } +} + +impl Default for ShieldedZeroAnchorError { + fn default() -> Self { + Self::new() + } +} + +impl From for ConsensusError { + fn from(err: ShieldedZeroAnchorError) -> Self { + Self::BasicError(BasicError::ShieldedZeroAnchorError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/codes.rs b/packages/rs-dpp/src/errors/consensus/codes.rs index 1758f022ac8..ed24cbc15aa 100644 --- a/packages/rs-dpp/src/errors/consensus/codes.rs +++ b/packages/rs-dpp/src/errors/consensus/codes.rs @@ -232,6 +232,12 @@ impl ErrorWithCode for BasicError { Self::OutputAddressAlsoInputError(_) => 10816, Self::InvalidRemainderOutputCountError(_) => 10817, Self::WithdrawalBelowMinAmountError(_) => 10818, + // Shielded transition errors (10819-10825) + Self::ShieldedNoActionsError(_) => 10819, + Self::ShieldedEmptyProofError(_) => 10820, + Self::ShieldedZeroAnchorError(_) => 10821, + Self::ShieldedInvalidValueBalanceError(_) => 10822, + Self::ShieldedTooManyActionsError(_) => 10825, } } } @@ -370,6 +376,13 @@ impl ErrorWithCode for StateError { Self::GroupActionAlreadyCompletedError(_) => 40802, Self::GroupActionAlreadySignedByIdentityError(_) => 40803, Self::ModificationOfGroupActionMainParametersNotPermittedError(_) => 40804, + + // Shielded errors: 40900-40999 + Self::InvalidAnchorError(_) => 40900, + Self::NullifierAlreadySpentError(_) => 40901, + Self::InvalidShieldedProofError(_) => 40902, + Self::InsufficientPoolNotesError(_) => 40903, + Self::InsufficientShieldedFeeError(_) => 40904, } } } diff --git a/packages/rs-dpp/src/errors/consensus/state/mod.rs b/packages/rs-dpp/src/errors/consensus/state/mod.rs index b0e64d6bc80..57585a79711 100644 --- a/packages/rs-dpp/src/errors/consensus/state/mod.rs +++ b/packages/rs-dpp/src/errors/consensus/state/mod.rs @@ -5,6 +5,7 @@ pub mod document; pub mod group; pub mod identity; pub mod prefunded_specialized_balances; +pub mod shielded; pub mod state_error; pub mod token; pub mod voting; diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs new file mode 100644 index 00000000000..3358017ab95 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_pool_notes_error.rs @@ -0,0 +1,46 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[platform_serialize(unversioned)] +#[error( + "Shielded pool has insufficient notes for outgoing transition: pool has {current_count} notes but minimum {minimum_required} required" +)] +pub struct InsufficientPoolNotesError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + current_count: u64, + minimum_required: u64, +} + +impl InsufficientPoolNotesError { + pub fn new(current_count: u64, minimum_required: u64) -> Self { + Self { + current_count, + minimum_required, + } + } + + pub fn current_count(&self) -> u64 { + self.current_count + } + + pub fn minimum_required(&self) -> u64 { + self.minimum_required + } +} + +impl From for ConsensusError { + fn from(err: InsufficientPoolNotesError) -> Self { + Self::StateError(StateError::InsufficientPoolNotesError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs new file mode 100644 index 00000000000..4bfcd288404 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/insufficient_shielded_fee_error.rs @@ -0,0 +1,36 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[platform_serialize(unversioned)] +#[error("Insufficient shielded transaction fee: {message}")] +pub struct InsufficientShieldedFeeError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + message: String, +} + +impl InsufficientShieldedFeeError { + pub fn new(message: String) -> Self { + Self { message } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for ConsensusError { + fn from(err: InsufficientShieldedFeeError) -> Self { + Self::StateError(StateError::InsufficientShieldedFeeError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs new file mode 100644 index 00000000000..f0839902d57 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_anchor_error.rs @@ -0,0 +1,39 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[platform_serialize(unversioned)] +#[error( + "Anchor not found in the recorded anchors tree: {}", + hex::encode(anchor) +)] +pub struct InvalidAnchorError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + anchor: [u8; 32], +} + +impl InvalidAnchorError { + pub fn new(anchor: [u8; 32]) -> Self { + Self { anchor } + } + + pub fn anchor(&self) -> [u8; 32] { + self.anchor + } +} + +impl From for ConsensusError { + fn from(err: InvalidAnchorError) -> Self { + Self::StateError(StateError::InvalidAnchorError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs new file mode 100644 index 00000000000..b18b8c30f58 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/invalid_shielded_proof_error.rs @@ -0,0 +1,36 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[platform_serialize(unversioned)] +#[error("Invalid shielded transaction proof: {message}")] +pub struct InvalidShieldedProofError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + message: String, +} + +impl InvalidShieldedProofError { + pub fn new(message: String) -> Self { + Self { message } + } + + pub fn message(&self) -> &str { + &self.message + } +} + +impl From for ConsensusError { + fn from(err: InvalidShieldedProofError) -> Self { + Self::StateError(StateError::InvalidShieldedProofError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs new file mode 100644 index 00000000000..816eb366ac1 --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/mod.rs @@ -0,0 +1,11 @@ +pub mod insufficient_pool_notes_error; +pub mod insufficient_shielded_fee_error; +pub mod invalid_anchor_error; +pub mod invalid_shielded_proof_error; +pub mod nullifier_already_spent_error; + +pub use insufficient_pool_notes_error::*; +pub use insufficient_shielded_fee_error::*; +pub use invalid_anchor_error::*; +pub use invalid_shielded_proof_error::*; +pub use nullifier_already_spent_error::*; diff --git a/packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs b/packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs new file mode 100644 index 00000000000..a82430c6d0f --- /dev/null +++ b/packages/rs-dpp/src/errors/consensus/state/shielded/nullifier_already_spent_error.rs @@ -0,0 +1,36 @@ +use crate::consensus::state::state_error::StateError; +use crate::consensus::ConsensusError; +use crate::errors::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; +use thiserror::Error; + +#[derive( + Error, Debug, Clone, PartialEq, Eq, Encode, Decode, PlatformSerialize, PlatformDeserialize, +)] +#[platform_serialize(unversioned)] +#[error("Nullifier has already been spent: {}", hex::encode(nullifier))] +pub struct NullifierAlreadySpentError { + /* + + DO NOT CHANGE ORDER OF FIELDS WITHOUT INTRODUCING OF NEW VERSION + + */ + nullifier: [u8; 32], +} + +impl NullifierAlreadySpentError { + pub fn new(nullifier: [u8; 32]) -> Self { + Self { nullifier } + } + + pub fn nullifier(&self) -> [u8; 32] { + self.nullifier + } +} + +impl From for ConsensusError { + fn from(err: NullifierAlreadySpentError) -> Self { + Self::StateError(StateError::NullifierAlreadySpentError(err)) + } +} diff --git a/packages/rs-dpp/src/errors/consensus/state/state_error.rs b/packages/rs-dpp/src/errors/consensus/state/state_error.rs index f3d3e9ad731..672c709198a 100644 --- a/packages/rs-dpp/src/errors/consensus/state/state_error.rs +++ b/packages/rs-dpp/src/errors/consensus/state/state_error.rs @@ -4,6 +4,11 @@ use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize}; use thiserror::Error; use crate::consensus::state::address_funds::{AddressDoesNotExistError, AddressInvalidNonceError, AddressNotEnoughFundsError, AddressesNotEnoughFundsError}; +use crate::consensus::state::shielded::insufficient_pool_notes_error::InsufficientPoolNotesError; +use crate::consensus::state::shielded::insufficient_shielded_fee_error::InsufficientShieldedFeeError; +use crate::consensus::state::shielded::invalid_anchor_error::InvalidAnchorError; +use crate::consensus::state::shielded::invalid_shielded_proof_error::InvalidShieldedProofError; +use crate::consensus::state::shielded::nullifier_already_spent_error::NullifierAlreadySpentError; use crate::consensus::state::data_contract::data_contract_already_present_error::DataContractAlreadyPresentError; use crate::consensus::state::data_contract::data_contract_config_update_error::DataContractConfigUpdateError; use crate::consensus::state::data_contract::data_contract_is_readonly_error::DataContractIsReadonlyError; @@ -334,6 +339,21 @@ pub enum StateError { #[error(transparent)] AddressInvalidNonceError(AddressInvalidNonceError), + + #[error(transparent)] + InvalidAnchorError(InvalidAnchorError), + + #[error(transparent)] + NullifierAlreadySpentError(NullifierAlreadySpentError), + + #[error(transparent)] + InvalidShieldedProofError(InvalidShieldedProofError), + + #[error(transparent)] + InsufficientPoolNotesError(InsufficientPoolNotesError), + + #[error(transparent)] + InsufficientShieldedFeeError(InsufficientShieldedFeeError), } impl From for ConsensusError { diff --git a/packages/rs-dpp/src/lib.rs b/packages/rs-dpp/src/lib.rs index d160ad35b99..e73fb05c251 100644 --- a/packages/rs-dpp/src/lib.rs +++ b/packages/rs-dpp/src/lib.rs @@ -73,6 +73,7 @@ pub mod core_types; pub mod address_funds; pub mod group; +pub mod shielded; pub mod withdrawal; pub use async_trait; diff --git a/packages/rs-dpp/src/serialization/mod.rs b/packages/rs-dpp/src/serialization/mod.rs index bc5db88925f..ab497baffd7 100644 --- a/packages/rs-dpp/src/serialization/mod.rs +++ b/packages/rs-dpp/src/serialization/mod.rs @@ -1,2 +1,4 @@ +#[cfg(feature = "state-transition-serde-conversion")] +pub(crate) mod serde_bytes_64; pub(crate) mod serialization_traits; pub use serialization_traits::*; diff --git a/packages/rs-dpp/src/serialization/serde_bytes_64.rs b/packages/rs-dpp/src/serialization/serde_bytes_64.rs new file mode 100644 index 00000000000..6fc830deb30 --- /dev/null +++ b/packages/rs-dpp/src/serialization/serde_bytes_64.rs @@ -0,0 +1,33 @@ +//! Serde helper for `[u8; 64]` fields. +//! +//! Serde's built-in array support only covers arrays up to 32 elements. +//! This module provides custom serialize/deserialize for 64-byte arrays +//! (e.g. RedPallas binding signatures, spend auth signatures). +//! +//! - **Human-readable** formats (JSON): hex-encoded string +//! - **Binary** formats (bincode, CBOR): raw byte sequence + +use serde::{Deserialize, Deserializer, Serializer}; + +pub fn serialize(bytes: &[u8; 64], serializer: S) -> Result { + if serializer.is_human_readable() { + serializer.serialize_str(&hex::encode(bytes)) + } else { + serializer.serialize_bytes(bytes) + } +} + +pub fn deserialize<'de, D: Deserializer<'de>>(deserializer: D) -> Result<[u8; 64], D::Error> { + if deserializer.is_human_readable() { + let s = ::deserialize(deserializer)?; + let vec = hex::decode(&s).map_err(serde::de::Error::custom)?; + vec.try_into().map_err(|v: Vec| { + serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len())) + }) + } else { + let vec = >::deserialize(deserializer)?; + vec.try_into().map_err(|v: Vec| { + serde::de::Error::custom(format!("expected 64 bytes, got {}", v.len())) + }) + } +} diff --git a/packages/rs-dpp/src/shielded/builder/mod.rs b/packages/rs-dpp/src/shielded/builder/mod.rs new file mode 100644 index 00000000000..543c818ffaa --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/mod.rs @@ -0,0 +1,300 @@ +//! Convenience builders for constructing shielded state transitions. +//! +//! These functions encapsulate the full Orchard bundle construction pipeline: +//! builder configuration, proof generation, signature application, +//! and serialization into platform state transitions. +//! +//! Requires the `shielded-client` feature, which pulls in +//! `grovedb-commitment-tree` (and transitively the `orchard` crate). +//! +//! # Example +//! +//! ```ignore +//! use dpp::shielded::builder::*; +//! use grovedb_commitment_tree::{SpendingKey, FullViewingKey, Scope, ProvingKey}; +//! +//! // Derive recipient address +//! let sk = SpendingKey::from_bytes(seed)?; +//! let fvk = FullViewingKey::from(&sk); +//! let recipient = OrchardAddress::from_raw_bytes( +//! &fvk.address_at(0, Scope::External).to_raw_address_bytes(), +//! ); +//! +//! // Build a shield transition +//! let pk = ProvingKey::build(); +//! let st = build_shield_transition( +//! &recipient, shield_amount, inputs, fee_strategy, +//! &signer, 0, &pk, [0u8; 36], platform_version, +//! )?; +//! ``` + +mod shield; +mod shield_from_asset_lock; +mod shielded_transfer; +mod shielded_withdrawal; +mod unshield; + +pub use self::shield::build_shield_transition; +pub use shield_from_asset_lock::build_shield_from_asset_lock_transition; +pub use shielded_transfer::build_shielded_transfer_transition; +pub use shielded_withdrawal::build_shielded_withdrawal_transition; +pub use unshield::build_unshield_transition; + +use grovedb_commitment_tree::{ + Anchor, Authorized, Builder, Bundle, BundleType, DashMemo, Flags as OrchardFlags, + FullViewingKey, MerklePath, Note, NoteValue, PaymentAddress, ProvingKey, SpendAuthorizingKey, +}; +use rand::rngs::OsRng; + +use crate::address_funds::OrchardAddress; +use crate::shielded::{compute_platform_sighash, SerializedAction}; +use crate::ProtocolError; + +/// Trait abstracting over Orchard proof generation. +/// +/// This follows the same pattern as `Signer` — callers provide an implementation +/// that holds (and potentially caches) the expensive `ProvingKey`, and the builder +/// functions use it via this trait. +pub trait OrchardProver { + /// Returns a reference to the Halo 2 proving key for the Orchard circuit. + fn proving_key(&self) -> &ProvingKey; +} + +/// A note that can be spent in a shielded transaction, paired with its +/// Merkle inclusion path in the commitment tree. +pub struct SpendableNote { + /// The Orchard note to spend. + pub note: Note, + /// Merkle path proving the note's commitment exists in the tree. + pub merkle_path: MerklePath, +} + +/// The serialized fields extracted from an authorized Orchard bundle, +/// ready for use by state transition constructors. +pub struct SerializedBundle { + /// Serialized Orchard actions (spends + outputs). + pub actions: Vec, + /// Bundle flags byte. + pub flags: u8, + /// Net value balance (positive = value leaving the shielded pool). + pub value_balance: i64, + /// Sinsemilla root of the Orchard note commitment tree (32 bytes). + /// This is the Orchard `Anchor` — the root hash of the depth-32 Sinsemilla + /// Merkle tree over extracted note commitments (cmx values). + pub anchor: [u8; 32], + /// Halo 2 proof bytes. + pub proof: Vec, + /// Binding signature (64 bytes). + pub binding_signature: [u8; 64], +} + +impl From<&OrchardAddress> for PaymentAddress { + fn from(address: &OrchardAddress) -> Self { + *address.inner() + } +} + +/// Serializes an authorized Orchard bundle into the raw fields used by +/// state transition constructors. +pub fn serialize_authorized_bundle(bundle: &Bundle) -> SerializedBundle { + let actions: Vec = bundle + .actions() + .iter() + .map(|action| { + let enc = action.encrypted_note(); + let mut encrypted_note = Vec::with_capacity(216); + encrypted_note.extend_from_slice(&enc.epk_bytes); + encrypted_note.extend_from_slice(enc.enc_ciphertext.as_ref()); + encrypted_note.extend_from_slice(&enc.out_ciphertext); + SerializedAction { + nullifier: action.nullifier().to_bytes(), + rk: <[u8; 32]>::from(action.rk()), + cmx: action.cmx().to_bytes(), + encrypted_note, + cv_net: action.cv_net().to_bytes(), + spend_auth_sig: <[u8; 64]>::from(action.authorization()), + } + }) + .collect(); + let flags = bundle.flags().to_byte(); + let value_balance = *bundle.value_balance(); + let anchor = bundle.anchor().to_bytes(); + let proof = bundle.authorization().proof().as_ref().to_vec(); + let binding_signature = <[u8; 64]>::from(bundle.authorization().binding_signature()); + SerializedBundle { + actions, + flags, + value_balance, + anchor, + proof, + binding_signature, + } +} + +// --------------------------------------------------------------------------- +// Internal helpers +// --------------------------------------------------------------------------- + +/// Builds an output-only Orchard bundle (no spends). +/// +/// Used by Shield and ShieldFromAssetLock transitions where funds enter +/// the shielded pool from transparent sources. +pub(crate) fn build_output_only_bundle( + recipient: &OrchardAddress, + amount: u64, + memo: [u8; 36], + prover: &P, +) -> Result, ProtocolError> { + let payment_address = PaymentAddress::from(recipient); + let anchor = Anchor::empty_tree(); + let mut builder = Builder::::new( + BundleType::Transactional { + flags: OrchardFlags::SPENDS_DISABLED, + bundle_required: false, + }, + anchor, + ); + + builder + .add_output(None, payment_address, NoteValue::from_raw(amount), memo) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + prove_and_sign_bundle(builder, prover, &[], &[]) +} + +/// Builds a spend+output Orchard bundle. +/// +/// Used by ShieldedTransfer, Unshield, and ShieldedWithdrawal where funds +/// are spent from existing notes. +#[allow(clippy::too_many_arguments)] +pub(crate) fn build_spend_bundle( + spends: Vec, + recipient: &OrchardAddress, + output_amount: u64, + memo: [u8; 36], + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + prover: &P, + extra_sighash_data: &[u8], +) -> Result, ProtocolError> { + let payment_address = PaymentAddress::from(recipient); + + let mut builder = Builder::::new(BundleType::DEFAULT, anchor); + + for spend in spends { + builder + .add_spend(fvk.clone(), spend.note, spend.merkle_path) + .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + } + + builder + .add_output( + None, + payment_address, + NoteValue::from_raw(output_amount), + memo, + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + prove_and_sign_bundle( + builder, + prover, + std::slice::from_ref(ask), + extra_sighash_data, + ) +} + +/// Takes a configured Builder, generates the proof, computes the platform +/// sighash, and applies signatures. +pub(crate) fn prove_and_sign_bundle( + builder: Builder, + prover: &P, + signing_keys: &[SpendAuthorizingKey], + extra_sighash_data: &[u8], +) -> Result, ProtocolError> { + let mut rng = OsRng; + + let (unauthorized, _) = builder + .build::(&mut rng) + .map_err(|e| ProtocolError::Generic(format!("failed to build bundle: {:?}", e)))? + .ok_or_else(|| ProtocolError::Generic("bundle was empty after build".to_string()))?; + + let bundle_commitment: [u8; 32] = unauthorized.commitment().into(); + let sighash = compute_platform_sighash(&bundle_commitment, extra_sighash_data); + + let proven = unauthorized + .create_proof(prover.proving_key(), &mut rng) + .map_err(|e| ProtocolError::Generic(format!("failed to create proof: {:?}", e)))?; + + proven + .apply_signatures(rng, sighash, signing_keys) + .map_err(|e| ProtocolError::Generic(format!("failed to apply signatures: {:?}", e))) +} + +/// Shared test utilities for builder tests. +#[cfg(test)] +pub(crate) mod test_helpers { + use super::*; + use grovedb_commitment_tree::{ + FullViewingKey, Hashable, MerkleHashOrchard, Note, NoteValue, ProvingKey, RandomSeed, Rho, + Scope, SpendingKey, NOTE_COMMITMENT_TREE_DEPTH, + }; + use std::sync::OnceLock; + + static PROVING_KEY: OnceLock = OnceLock::new(); + + /// Returns a cached ProvingKey (~30s to build on first call). + pub fn proving_key() -> &'static ProvingKey { + PROVING_KEY.get_or_init(ProvingKey::build) + } + + /// Test implementation of `OrchardProver` backed by the cached proving key. + pub struct TestProver; + + impl super::OrchardProver for TestProver { + fn proving_key(&self) -> &ProvingKey { + proving_key() + } + } + + /// Creates a test OrchardAddress from a deterministic spending key. + pub fn test_orchard_address() -> OrchardAddress { + let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + OrchardAddress::from_raw_bytes(&payment_address.to_raw_address_bytes()) + .expect("valid orchard address bytes") + } + + /// Creates a SpendableNote with the given value. + /// + /// The note is cryptographically valid (has a valid commitment) but uses + /// an all-zeros Merkle path, so it will only pass the Orchard circuit when + /// paired with `Anchor::empty_tree()`. Suitable for both error-path tests + /// (where the proving key is never reached) and happy-path tests. + pub fn test_spendable_note(value: u64) -> SpendableNote { + let sk = SpendingKey::from_bytes([42u8; 32]).expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let payment_address = fvk.address_at(0u32, Scope::External); + + // Construct a valid Rho from the zero element (always valid in pallas) + let rho: Rho = + Option::from(Rho::from_bytes(&[0u8; 32])).expect("zero is valid pallas::Base"); + let rseed: RandomSeed = + Option::from(RandomSeed::from_bytes([1u8; 32], &rho)).expect("valid random seed"); + let note: Note = Option::from(Note::from_parts( + payment_address, + NoteValue::from_raw(value), + rho, + rseed, + )) + .expect("note commitment should be valid"); + + // All-zeros merkle path at position 0 — consistent with Anchor::empty_tree() + let auth_path = [MerkleHashOrchard::empty_leaf(); NOTE_COMMITMENT_TREE_DEPTH]; + let merkle_path = MerklePath::from_parts(0, auth_path); + + SpendableNote { note, merkle_path } + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shield.rs b/packages/rs-dpp/src/shielded/builder/shield.rs new file mode 100644 index 00000000000..3f41b67ded3 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shield.rs @@ -0,0 +1,153 @@ +use std::collections::BTreeMap; + +use crate::address_funds::AddressFundsFeeStrategy; +use crate::address_funds::{OrchardAddress, PlatformAddress}; +use crate::fee::Credits; +use crate::identity::signer::Signer; +use crate::prelude::{AddressNonce, UserFeeIncrease}; +use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver}; + +/// Builds a Shield state transition (transparent platform addresses -> shielded pool). +/// +/// Constructs an output-only Orchard bundle (no spends), proves it, signs the +/// transparent input witnesses, and returns a ready-to-broadcast `StateTransition`. +/// +/// # Parameters +/// - `recipient` - Orchard address to receive the shielded note +/// - `shield_amount` - Amount of credits to shield +/// - `inputs` - Platform address inputs with their nonces and balances +/// - `fee_strategy` - How to deduct fees from the transparent inputs +/// - `signer` - Signs each input address witness (ECDSA) +/// - `user_fee_increase` - Fee multiplier (0 = 100% base fee) +/// - `prover` - Orchard prover (holds the Halo 2 proving key; cache with `OnceLock` — ~30s to build) +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shield_transition, P: OrchardProver>( + recipient: &OrchardAddress, + shield_amount: u64, + inputs: BTreeMap, + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + prover: &P, + memo: [u8; 36], + platform_version: &PlatformVersion, +) -> Result { + if fee_strategy.is_empty() { + return Err(ProtocolError::Generic( + "fee_strategy must have at least one step".to_string(), + )); + } + + let bundle = build_output_only_bundle(recipient, shield_amount, memo, prover)?; + let sb = serialize_authorized_bundle(&bundle); + + ShieldTransition::try_from_bundle_with_signer( + inputs, + sb.actions, + sb.value_balance.unsigned_abs(), + sb.anchor, + sb.proof, + sb.binding_signature, + fee_strategy, + signer, + user_fee_increase, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address_funds::AddressFundsFeeStrategyStep; + use crate::address_funds::AddressWitness; + use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver}; + use platform_value::BinaryData; + + /// A dummy signer that produces a fake 65-byte signature. + /// Only used to test the builder pipeline — the signature is not validated here. + #[derive(Debug)] + struct DummySigner; + + impl Signer for DummySigner { + fn sign(&self, _key: &PlatformAddress, _data: &[u8]) -> Result { + Ok(BinaryData::new(vec![0u8; 65])) + } + + fn sign_create_witness( + &self, + _key: &PlatformAddress, + _data: &[u8], + ) -> Result { + Ok(AddressWitness::P2pkh { + signature: BinaryData::new(vec![0u8; 65]), + }) + } + + fn can_sign_with(&self, _key: &PlatformAddress) -> bool { + true + } + } + + #[test] + fn test_build_shield_empty_fee_strategy() { + let recipient = test_orchard_address(); + let platform_version = PlatformVersion::latest(); + let result = build_shield_transition( + &recipient, + 1000, + BTreeMap::new(), + vec![], // empty fee strategy + &DummySigner, + 0, + &TestProver, + [0u8; 36], + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("fee_strategy must have at least one step"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_build_shield_transition_valid() { + let recipient = test_orchard_address(); + let platform_version = PlatformVersion::latest(); + // Create a P2PKH address as input + let input_address = PlatformAddress::P2pkh([1u8; 20]); + let mut inputs = BTreeMap::new(); + inputs.insert(input_address, (0u32, 100_000u64)); + + let fee_strategy = vec![AddressFundsFeeStrategyStep::DeductFromInput(0)]; + + let result = build_shield_transition( + &recipient, + 50_000, + inputs, + fee_strategy, + &DummySigner, + 0, + &TestProver, + [0u8; 36], + platform_version, + ); + + assert!(result.is_ok(), "expected Ok, got: {:?}", result.err()); + match result.unwrap() { + StateTransition::Shield(_) => {} // correct variant + other => panic!("expected Shield variant, got {:?}", other), + } + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs new file mode 100644 index 00000000000..d91fa6d6b1c --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shield_from_asset_lock.rs @@ -0,0 +1,93 @@ +use crate::address_funds::OrchardAddress; +use crate::prelude::AssetLockProof; +use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_output_only_bundle, serialize_authorized_bundle, OrchardProver}; + +/// Builds a ShieldFromAssetLock state transition (core asset lock -> shielded pool). +/// +/// Like Shield, constructs an output-only Orchard bundle. The funds come from +/// a core asset lock proof rather than platform address inputs. +/// +/// # Parameters +/// - `recipient` - Orchard address to receive the shielded note +/// - `shield_amount` - Amount of credits to shield (from the asset lock) +/// - `asset_lock_proof` - Proof that funds are locked on core chain +/// - `asset_lock_private_key` - Private key for the asset lock (signs the transition) +/// - `prover` - Orchard prover (holds the Halo 2 proving key) +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shield_from_asset_lock_transition( + recipient: &OrchardAddress, + shield_amount: u64, + asset_lock_proof: AssetLockProof, + asset_lock_private_key: &[u8], + prover: &P, + memo: [u8; 36], + platform_version: &PlatformVersion, +) -> Result { + let bundle = build_output_only_bundle(recipient, shield_amount, memo, prover)?; + let sb = serialize_authorized_bundle(&bundle); + + // For output-only bundles, Orchard value_balance is negative (value flowing in). + // Convert to u64 (absolute amount entering the pool). + let value_balance = sb + .value_balance + .checked_neg() + .and_then(|v| u64::try_from(v).ok()) + .ok_or_else(|| { + ProtocolError::Generic( + "shield_from_asset_lock: bundle value_balance is not negative".to_string(), + ) + })?; + + ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle( + asset_lock_proof, + asset_lock_private_key, + sb.actions, + value_balance, + sb.anchor, + sb.proof, + sb.binding_signature, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::super::{build_output_only_bundle, serialize_authorized_bundle}; + use crate::shielded::builder::test_helpers::{test_orchard_address, TestProver}; + + /// Verifies that an output-only bundle produces a negative value_balance + /// (value flowing into the pool), which is the precondition for + /// shield_from_asset_lock's value_balance conversion. + #[test] + fn test_output_only_bundle_value_balance_is_negative() { + let recipient = test_orchard_address(); + let amount = 50_000u64; + + let bundle = build_output_only_bundle(&recipient, amount, [0u8; 36], &TestProver) + .expect("bundle should build successfully"); + let sb = serialize_authorized_bundle(&bundle); + + // Output-only bundles have negative value_balance (value entering the pool) + assert!( + sb.value_balance < 0, + "expected negative value_balance, got {}", + sb.value_balance + ); + + // The absolute value should match the shield amount + let abs_balance = sb + .value_balance + .checked_neg() + .and_then(|v| u64::try_from(v).ok()) + .expect("value_balance should be safely negatable"); + assert_eq!(abs_balance, amount); + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs new file mode 100644 index 00000000000..03217df0cef --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shielded_transfer.rs @@ -0,0 +1,214 @@ +use grovedb_commitment_tree::{ + Anchor, Builder, BundleType, DashMemo, FullViewingKey, NoteValue, PaymentAddress, + SpendAuthorizingKey, +}; + +use crate::address_funds::OrchardAddress; +use crate::fee::Credits; +use crate::shielded::compute_minimum_shielded_fee; +use crate::state_transition::shielded_transfer_transition::methods::ShieldedTransferTransitionMethodsV0; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{prove_and_sign_bundle, serialize_authorized_bundle, OrchardProver, SpendableNote}; + +/// Builds a ShieldedTransfer state transition (shielded pool -> shielded pool). +/// +/// Spends existing notes and creates a new note for the recipient. The shielded +/// fee is deducted from the spent notes. Any remaining change is returned to +/// the `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `recipient` - Orchard address to receive the transferred note +/// - `transfer_amount` - Amount to transfer to the recipient +/// - `change_address` - Orchard address for change output (if any) +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) +/// - `prover` - Orchard prover (holds the Halo 2 proving key) +/// - `memo` - 36-byte structured memo for the recipient (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shielded_transfer_transition( + spends: Vec, + recipient: &OrchardAddress, + transfer_amount: u64, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + prover: &P, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 2) since we always have + // a recipient output and likely a change output. + let num_actions = spends.len().max(2); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) if f > min_fee.saturating_mul(1000) => { + return Err(ProtocolError::Generic(format!( + "fee {} exceeds 1000x the minimum fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = transfer_amount + .checked_add(effective_fee) + .ok_or_else(|| ProtocolError::Generic("fee + transfer_amount overflows u64".to_string()))?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "transfer amount {} + fee {} = {} exceeds total spendable value {}", + transfer_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + let recipient_payment = PaymentAddress::from(recipient); + + let mut builder = Builder::::new(BundleType::DEFAULT, anchor); + + for spend in spends { + builder + .add_spend(fvk.clone(), spend.note, spend.merkle_path) + .map_err(|e| ProtocolError::Generic(format!("failed to add spend: {:?}", e)))?; + } + + // Primary output to recipient + builder + .add_output( + None, + recipient_payment, + NoteValue::from_raw(transfer_amount), + memo, + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add output: {:?}", e)))?; + + // Change output (if any) + if change_amount > 0 { + let change_payment = PaymentAddress::from(change_address); + builder + .add_output( + None, + change_payment, + NoteValue::from_raw(change_amount), + [0u8; 36], + ) + .map_err(|e| ProtocolError::Generic(format!("failed to add change output: {:?}", e)))?; + } + + // ShieldedTransfer has no extra_data in sighash + let bundle = prove_and_sign_bundle(builder, prover, std::slice::from_ref(ask), &[])?; + let sb = serialize_authorized_bundle(&bundle); + + // value_balance = effective_fee (the amount leaving the shielded pool as fee) + ShieldedTransferTransition::try_from_bundle( + sb.actions, + sb.value_balance as u64, + sb.anchor, + sb.proof, + sb.binding_signature, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shielded::builder::test_helpers::{ + test_orchard_address, test_spendable_note, TestProver, + }; + + #[test] + fn test_shielded_transfer_fee_below_minimum() { + let platform_version = PlatformVersion::latest(); + let recipient = test_orchard_address(); + let change_address = test_orchard_address(); + + let note = test_spendable_note(1_000_000); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_transfer_transition( + spends, + &recipient, + 100, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + &TestProver, + [0u8; 36], + Some(1), // fee = 1, should be below minimum + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("below minimum required fee"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_shielded_transfer_insufficient_funds() { + let platform_version = PlatformVersion::latest(); + let recipient = test_orchard_address(); + let change_address = test_orchard_address(); + + // Note with only 100 credits + let note = test_spendable_note(100); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_transfer_transition( + spends, + &recipient, + 1_000_000, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + &TestProver, + [0u8; 36], + None, + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("exceeds total spendable value"), + "unexpected error: {}", + err + ); + } +} diff --git a/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs new file mode 100644 index 00000000000..750b7428e03 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/shielded_withdrawal.rs @@ -0,0 +1,203 @@ +use grovedb_commitment_tree::{Anchor, FullViewingKey, SpendAuthorizingKey}; + +use crate::address_funds::OrchardAddress; +use crate::fee::Credits; +use crate::identity::core_script::CoreScript; +use crate::shielded::compute_minimum_shielded_fee; +use crate::state_transition::shielded_withdrawal_transition::methods::ShieldedWithdrawalTransitionMethodsV0; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::StateTransition; +use crate::withdrawal::Pooling; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_spend_bundle, serialize_authorized_bundle, OrchardProver, SpendableNote}; + +/// Builds a ShieldedWithdrawal state transition (shielded pool -> core L1 address). +/// +/// Spends existing notes and withdraws value to a core chain script output. +/// The shielded fee is deducted from the spent notes. Any remaining value is +/// returned to the shielded `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `withdrawal_amount` - Amount to withdraw to the core chain +/// - `output_script` - Core chain script to receive the funds +/// - `core_fee_per_byte` - Core chain fee rate +/// - `pooling` - Withdrawal pooling strategy +/// - `change_address` - Orchard address for change output +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) +/// - `prover` - Orchard prover (holds the Halo 2 proving key) +/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_shielded_withdrawal_transition( + spends: Vec, + withdrawal_amount: u64, + output_script: CoreScript, + core_fee_per_byte: u32, + pooling: Pooling, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + prover: &P, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + if withdrawal_amount > i64::MAX as u64 { + return Err(ProtocolError::Generic(format!( + "withdrawal amount {} exceeds maximum allowed value {}", + withdrawal_amount, + i64::MAX as u64 + ))); + } + + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 1) since we have a change output. + let num_actions = spends.len().max(1); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = withdrawal_amount + .checked_add(effective_fee) + .ok_or_else(|| { + ProtocolError::Generic("fee + withdrawal_amount overflows u64".to_string()) + })?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "withdrawal amount {} + fee {} = {} exceeds total spendable value {}", + withdrawal_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + // ShieldedWithdrawal extra_data = output_script.as_bytes() + let extra_sighash_data = output_script.as_bytes().to_vec(); + + let bundle = build_spend_bundle( + spends, + change_address, + change_amount, + memo, + fvk, + ask, + anchor, + prover, + &extra_sighash_data, + )?; + + let sb = serialize_authorized_bundle(&bundle); + + ShieldedWithdrawalTransition::try_from_bundle( + sb.actions, + sb.value_balance as u64, + sb.anchor, + sb.proof, + sb.binding_signature, + core_fee_per_byte, + pooling, + output_script, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shielded::builder::test_helpers::{ + test_orchard_address, test_spendable_note, TestProver, + }; + + #[test] + fn test_shielded_withdrawal_fee_below_minimum() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + + let note = test_spendable_note(1_000_000); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_withdrawal_transition( + spends, + 100, + CoreScript::new_p2pkh([1u8; 20]), // minimal P2PKH prefix + 1, + Pooling::Never, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + &TestProver, + [0u8; 36], + Some(1), // fee = 1, should be below minimum + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("below minimum required fee"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_shielded_withdrawal_insufficient_funds() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + + let note = test_spendable_note(100); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_shielded_withdrawal_transition( + spends, + 1_000_000, + CoreScript::new_p2pkh([1u8; 20]), + 1, + Pooling::Never, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + &TestProver, + [0u8; 36], + None, + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("exceeds total spendable value"), + "unexpected error: {}", + err + ); + } +} diff --git a/packages/rs-dpp/src/shielded/builder/unshield.rs b/packages/rs-dpp/src/shielded/builder/unshield.rs new file mode 100644 index 00000000000..d7589ad1720 --- /dev/null +++ b/packages/rs-dpp/src/shielded/builder/unshield.rs @@ -0,0 +1,191 @@ +use grovedb_commitment_tree::{Anchor, FullViewingKey, SpendAuthorizingKey}; + +use crate::address_funds::{OrchardAddress, PlatformAddress}; +use crate::fee::Credits; +use crate::shielded::compute_minimum_shielded_fee; +use crate::state_transition::unshield_transition::methods::UnshieldTransitionMethodsV0; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransition; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +use super::{build_spend_bundle, serialize_authorized_bundle, OrchardProver, SpendableNote}; + +/// Builds an Unshield state transition (shielded pool -> platform address). +/// +/// Spends existing notes and sends part of the value to a transparent platform +/// address. The shielded fee is deducted from the spent notes. Any remaining +/// value is returned to the shielded `change_address`. +/// +/// # Parameters +/// - `spends` - Notes to spend with their Merkle paths +/// - `output_address` - Platform address to receive the unshielded funds +/// - `unshield_amount` - Amount to unshield to the platform address +/// - `change_address` - Orchard address for change output +/// - `fvk` - Full viewing key for spend authorization +/// - `ask` - Spend authorizing key for RedPallas signatures +/// - `anchor` - Sinsemilla root of the note commitment tree (Orchard Anchor) +/// - `prover` - Orchard prover (holds the Halo 2 proving key) +/// - `memo` - 36-byte structured memo for the change output (4-byte type tag + 32-byte payload) +/// - `fee` - Optional fee override; if `None`, the minimum fee is computed automatically. +/// If `Some`, must be >= the minimum fee. +/// - `platform_version` - Protocol version +#[allow(clippy::too_many_arguments)] +pub fn build_unshield_transition( + spends: Vec, + output_address: PlatformAddress, + unshield_amount: u64, + change_address: &OrchardAddress, + fvk: &FullViewingKey, + ask: &SpendAuthorizingKey, + anchor: Anchor, + prover: &P, + memo: [u8; 36], + fee: Option, + platform_version: &PlatformVersion, +) -> Result { + if unshield_amount > i64::MAX as u64 { + return Err(ProtocolError::Generic(format!( + "unshield amount {} exceeds maximum allowed value {}", + unshield_amount, + i64::MAX as u64 + ))); + } + + let total_spent: u64 = spends.iter().map(|s| s.note.value().inner()).sum(); + + // Conservative action count: at least (spends, 1) since we have a change output. + let num_actions = spends.len().max(1); + let min_fee = compute_minimum_shielded_fee(num_actions, platform_version); + let effective_fee = match fee { + Some(f) if f < min_fee => { + return Err(ProtocolError::Generic(format!( + "fee {} is below minimum required fee {}", + f, min_fee + ))); + } + Some(f) => f, + None => min_fee, + }; + + let required = unshield_amount + .checked_add(effective_fee) + .ok_or_else(|| ProtocolError::Generic("fee + unshield_amount overflows u64".to_string()))?; + if required > total_spent { + return Err(ProtocolError::Generic(format!( + "unshield amount {} + fee {} = {} exceeds total spendable value {}", + unshield_amount, effective_fee, required, total_spent + ))); + } + + let change_amount = total_spent - required; + + // Unshield extra_data = output_address.to_bytes() + let extra_sighash_data = output_address.to_bytes(); + + let bundle = build_spend_bundle( + spends, + change_address, + change_amount, + memo, + fvk, + ask, + anchor, + prover, + &extra_sighash_data, + )?; + + let sb = serialize_authorized_bundle(&bundle); + + UnshieldTransition::try_from_bundle( + output_address, + sb.actions, + sb.value_balance as u64, + sb.anchor, + sb.proof, + sb.binding_signature, + platform_version, + ) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::shielded::builder::test_helpers::{ + test_orchard_address, test_spendable_note, TestProver, + }; + + #[test] + fn test_unshield_fee_below_minimum() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + let output_address = PlatformAddress::P2pkh([1u8; 20]); + + let note = test_spendable_note(1_000_000); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_unshield_transition( + spends, + output_address, + 100, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + &TestProver, + [0u8; 36], + Some(1), // fee = 1, should be below minimum + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("below minimum required fee"), + "unexpected error: {}", + err + ); + } + + #[test] + fn test_unshield_insufficient_funds() { + let platform_version = PlatformVersion::latest(); + let change_address = test_orchard_address(); + let output_address = PlatformAddress::P2pkh([1u8; 20]); + + let note = test_spendable_note(100); + let spends = vec![note]; + + let sk = grovedb_commitment_tree::SpendingKey::from_bytes([42u8; 32]) + .expect("valid spending key bytes"); + let fvk = FullViewingKey::from(&sk); + let ask = SpendAuthorizingKey::from(&sk); + + let result = build_unshield_transition( + spends, + output_address, + 1_000_000, + &change_address, + &fvk, + &ask, + Anchor::empty_tree(), + &TestProver, + [0u8; 36], + None, + platform_version, + ); + + assert!(result.is_err()); + let err = result.unwrap_err().to_string(); + assert!( + err.contains("exceeds total spendable value"), + "unexpected error: {}", + err + ); + } +} diff --git a/packages/rs-dpp/src/shielded/mod.rs b/packages/rs-dpp/src/shielded/mod.rs new file mode 100644 index 00000000000..58f01096b30 --- /dev/null +++ b/packages/rs-dpp/src/shielded/mod.rs @@ -0,0 +1,156 @@ +#[cfg(feature = "shielded-client")] +pub mod builder; + +use bincode::{Decode, Encode}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; + +use crate::fee::Credits; +use platform_version::version::PlatformVersion; + +/// Permanent storage bytes per shielded action: +/// 280 bytes in BulkAppendTree (32 cmx + 32 rho + 216 encrypted note) +/// + 32 bytes in nullifier tree = 312 bytes total. +pub const SHIELDED_STORAGE_BYTES_PER_ACTION: u64 = 312; + +/// Domain separator for Platform sighash computation. +const SIGHASH_DOMAIN: &[u8] = b"DashPlatformSighash"; + +/// Computes the platform sighash from an Orchard bundle commitment and optional +/// transparent field data. +/// +/// The sighash is computed as: +/// `SHA-256(SIGHASH_DOMAIN || bundle_commitment || extra_data)` +/// +/// This binds transparent state transition fields (like `output_address` in unshield +/// or `output_script` in shielded withdrawal) to the Orchard signatures, preventing +/// replay attacks where an attacker substitutes transparent fields while reusing a +/// valid Orchard bundle. +/// +/// The same computation must be used on both the signing (client) and verification +/// (platform) sides. For transitions without transparent fields (shield and +/// shielded_transfer), `extra_data` is empty. +pub fn compute_platform_sighash(bundle_commitment: &[u8; 32], extra_data: &[u8]) -> [u8; 32] { + let mut hasher = Sha256::new(); + hasher.update(SIGHASH_DOMAIN); + hasher.update(bundle_commitment); + hasher.update(extra_data); + hasher.finalize().into() +} + +/// Computes the minimum fee (in credits) for a shielded state transition. +/// +/// The fee formula mirrors the on-chain validation in `validate_minimum_shielded_fee`: +/// `min_fee = proof_verification_fee + num_actions × (processing_fee + storage_fee)` +/// +/// where `storage_fee = SHIELDED_STORAGE_BYTES_PER_ACTION × (disk + processing) credits/byte`. +/// +/// # Parameters +/// - `num_actions` — number of Orchard actions in the bundle +/// - `platform_version` — protocol version (determines fee constants) +pub fn compute_minimum_shielded_fee( + num_actions: usize, + platform_version: &PlatformVersion, +) -> Credits { + let constants = &platform_version + .drive_abci + .validation_and_processing + .event_constants; + let storage = &platform_version.fee_version.storage; + let storage_fee = SHIELDED_STORAGE_BYTES_PER_ACTION + * (storage.storage_disk_usage_credit_per_byte + storage.storage_processing_credit_per_byte); + let per_action = constants.shielded_per_action_processing_fee + storage_fee; + constants.shielded_proof_verification_fee + num_actions as u64 * per_action +} + +/// Common Orchard bundle parameters shared across all shielded transition types. +/// +/// Groups the fields that every shielded transition carries identically: +/// the serialized actions, Sinsemilla anchor, Halo 2 proof, and RedPallas +/// binding signature. Using this struct reduces parameter counts in SDK +/// helper functions from 10-12 down to 5-8. +pub struct OrchardBundleParams { + /// The serialized Orchard actions (spends + outputs). + pub actions: Vec, + /// Sinsemilla root of the note commitment tree at bundle creation time (32 bytes). + /// This is the Orchard Anchor — the root of the depth-32 Sinsemilla Merkle + /// tree over extracted note commitments (cmx values), NOT the GroveDB + /// commitment tree state root. + pub anchor: [u8; 32], + /// Halo 2 zero-knowledge proof bytes. + pub proof: Vec, + /// RedPallas binding signature (64 bytes) over the bundle's value balance. + pub binding_signature: [u8; 64], +} + +/// A serialized Orchard action extracted from a bundle. +/// +/// Each Orchard action structurally contains one spend and one output. The spend +/// consumes a previously created note (revealing its nullifier), while the output +/// creates a new note (publishing its commitment). Although paired in the same struct, +/// observers cannot link which prior note was spent or what value the new note holds — +/// the zero-knowledge proof ensures privacy. +/// +/// These fields are raw bytes suitable for network serialization. During validation, +/// they are parsed back into typed Orchard structs and verified via `BatchValidator` +/// (Halo 2 proof + RedPallas signatures). +/// +/// All fields except `spend_auth_sig` are covered by the Orchard bundle commitment +/// (BLAKE2b-256 per ZIP-244), which feeds into the platform sighash. The signatures +/// and proof are verified separately and are not part of the commitment. +#[derive(Debug, Clone, Encode, Decode, PartialEq)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +pub struct SerializedAction { + /// Unique tag derived from the spent note's position and spending key. + /// Published on-chain to prevent double-spends: if this nullifier already + /// exists in the nullifier set, the transaction is rejected. The nullifier + /// is deterministic for a given note but unlinkable to the note's commitment, + /// preserving sender privacy. + pub nullifier: [u8; 32], + + /// Randomized spend validating key (RedPallas verification key). + /// Derived from the spender's full viewing key with per-action randomness. + /// Used to verify `spend_auth_sig`, proving the spender controls the spending + /// key for the consumed note without revealing which key it is. + pub rk: [u8; 32], + + /// Extracted note commitment for the newly created output note. + /// This is added to the commitment tree after the transition is applied, + /// allowing the recipient to later spend it. The commitment hides the note's + /// value, recipient, and randomness — only the recipient (who knows the + /// decryption key) can identify and spend this note. + pub cmx: [u8; 32], + + /// Encrypted note ciphertext (216 bytes = epk 32 + enc_ciphertext 104 + out_ciphertext 80). + /// Contains the `TransmittedNoteCiphertext` fields packed contiguously: + /// - `epk`: ephemeral public key for Diffie-Hellman key agreement (32 bytes) + /// - `enc_ciphertext`: note plaintext encrypted to the recipient (104 bytes = 52 compact + 36 memo + 16 AEAD tag) + /// - `out_ciphertext`: encrypted to the sender for wallet recovery (80 bytes) + /// + /// Stored on-chain so recipients can scan and decrypt notes addressed to them. + /// Only the intended recipient (or sender) can decrypt; all others see random bytes. + pub encrypted_note: Vec, + + /// Value commitment (Pedersen commitment to the note's value). + /// Commits to the value flowing through this action without revealing it. + /// The binding signature later proves that the sum of all `cv_net` commitments + /// across actions is consistent with the declared `value_balance`, ensuring + /// no credits are created or destroyed. + pub cv_net: [u8; 32], + + /// RedPallas spend authorization signature over the platform sighash. + /// Proves the spender authorized this specific bundle (including all actions, + /// value_balance, anchor, and any bound transparent fields). Verified against + /// `rk` during batch validation. This prevents replay attacks — a valid + /// signature from one transition cannot be reused in another. + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::serialization::serde_bytes_64") + )] + pub spend_auth_sig: [u8; 64], +} diff --git a/packages/rs-dpp/src/state_transition/mod.rs b/packages/rs-dpp/src/state_transition/mod.rs index e925929d7a9..d6097b50427 100644 --- a/packages/rs-dpp/src/state_transition/mod.rs +++ b/packages/rs-dpp/src/state_transition/mod.rs @@ -136,8 +136,21 @@ use crate::state_transition::identity_update_transition::{ }; use crate::state_transition::masternode_vote_transition::MasternodeVoteTransition; use crate::state_transition::masternode_vote_transition::MasternodeVoteTransitionSignable; +use crate::state_transition::shield_from_asset_lock_transition::{ + ShieldFromAssetLockTransition, ShieldFromAssetLockTransitionSignable, +}; +use crate::state_transition::shield_transition::{ShieldTransition, ShieldTransitionSignable}; +use crate::state_transition::shielded_transfer_transition::{ + ShieldedTransferTransition, ShieldedTransferTransitionSignable, +}; +use crate::state_transition::shielded_withdrawal_transition::{ + ShieldedWithdrawalTransition, ShieldedWithdrawalTransitionSignable, +}; #[cfg(feature = "state-transition-signing")] use crate::state_transition::state_transitions::document::batch_transition::methods::v0::DocumentsBatchTransitionMethodsV0; +use crate::state_transition::unshield_transition::{ + UnshieldTransition, UnshieldTransitionSignable, +}; use state_transitions::document::batch_transition::batched_transition::token_transition::TokenTransition; pub use state_transitions::*; @@ -162,6 +175,11 @@ macro_rules! call_method { StateTransition::AddressFundsTransfer(st) => st.$method($args), StateTransition::AddressFundingFromAssetLock(st) => st.$method($args), StateTransition::AddressCreditWithdrawal(st) => st.$method($args), + StateTransition::Shield(st) => st.$method($args), + StateTransition::ShieldedTransfer(st) => st.$method($args), + StateTransition::Unshield(st) => st.$method($args), + StateTransition::ShieldFromAssetLock(st) => st.$method($args), + StateTransition::ShieldedWithdrawal(st) => st.$method($args), } }; ($state_transition:expr, $method:ident ) => { @@ -181,6 +199,11 @@ macro_rules! call_method { StateTransition::AddressFundsTransfer(st) => st.$method(), StateTransition::AddressFundingFromAssetLock(st) => st.$method(), StateTransition::AddressCreditWithdrawal(st) => st.$method(), + StateTransition::Shield(st) => st.$method(), + StateTransition::ShieldedTransfer(st) => st.$method(), + StateTransition::Unshield(st) => st.$method(), + StateTransition::ShieldFromAssetLock(st) => st.$method(), + StateTransition::ShieldedWithdrawal(st) => st.$method(), } }; } @@ -203,6 +226,11 @@ macro_rules! call_getter_method_identity_signed { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(_) => None, StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } }; ($state_transition:expr, $method:ident ) => { @@ -222,6 +250,11 @@ macro_rules! call_getter_method_identity_signed { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(_) => None, StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } }; } @@ -244,6 +277,11 @@ macro_rules! call_method_identity_signed { StateTransition::AddressFundsTransfer(_) => {} StateTransition::AddressFundingFromAssetLock(_) => {} StateTransition::AddressCreditWithdrawal(_) => {} + StateTransition::Shield(_) => {} + StateTransition::ShieldedTransfer(_) => {} + StateTransition::Unshield(_) => {} + StateTransition::ShieldFromAssetLock(_) => {} + StateTransition::ShieldedWithdrawal(_) => {} } }; ($state_transition:expr, $method:ident ) => { @@ -263,6 +301,11 @@ macro_rules! call_method_identity_signed { StateTransition::AddressFundsTransfer(_) => {} StateTransition::AddressFundingFromAssetLock(_) => {} StateTransition::AddressCreditWithdrawal(_) => {} + StateTransition::Shield(_) => {} + StateTransition::ShieldedTransfer(_) => {} + StateTransition::Unshield(_) => {} + StateTransition::ShieldFromAssetLock(_) => {} + StateTransition::ShieldedWithdrawal(_) => {} } }; } @@ -300,6 +343,21 @@ macro_rules! call_errorable_method_identity_signed { StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( "address credit withdrawal can not be called for identity signing".to_string(), )), + StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded transfer transition can not be called for identity signing".to_string(), + )), + StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution( + "unshield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield from asset lock transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded withdrawal transition can not be called for identity signing".to_string(), + )), } }; ($state_transition:expr, $method:ident) => { @@ -333,6 +391,21 @@ macro_rules! call_errorable_method_identity_signed { StateTransition::AddressCreditWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( "address credit withdrawal can not be called for identity signing".to_string(), )), + StateTransition::Shield(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedTransfer(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded transfer transition can not be called for identity signing".to_string(), + )), + StateTransition::Unshield(_) => Err(ProtocolError::CorruptedCodeExecution( + "unshield transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldFromAssetLock(_) => Err(ProtocolError::CorruptedCodeExecution( + "shield from asset lock transition can not be called for identity signing".to_string(), + )), + StateTransition::ShieldedWithdrawal(_) => Err(ProtocolError::CorruptedCodeExecution( + "shielded withdrawal transition can not be called for identity signing".to_string(), + )), } }; } @@ -371,6 +444,11 @@ pub enum StateTransition { AddressFundsTransfer(AddressFundsTransferTransition), AddressFundingFromAssetLock(AddressFundingFromAssetLockTransition), AddressCreditWithdrawal(AddressCreditWithdrawalTransition), + Shield(ShieldTransition), + ShieldedTransfer(ShieldedTransferTransition), + Unshield(UnshieldTransition), + ShieldFromAssetLock(ShieldFromAssetLockTransition), + ShieldedWithdrawal(ShieldedWithdrawalTransition), } impl OptionallyAssetLockProved for StateTransition { @@ -378,6 +456,7 @@ impl OptionallyAssetLockProved for StateTransition { match self { StateTransition::IdentityCreate(st) => st.optional_asset_lock_proof(), StateTransition::IdentityTopUp(st) => st.optional_asset_lock_proof(), + StateTransition::ShieldFromAssetLock(st) => st.optional_asset_lock_proof(), _ => None, } } @@ -453,13 +532,24 @@ impl StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => 11..=LATEST_VERSION, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => 12..=LATEST_VERSION, } } pub fn is_identity_signed(&self) -> bool { !matches!( self, - StateTransition::IdentityCreate(_) | StateTransition::IdentityTopUp(_) + StateTransition::IdentityCreate(_) + | StateTransition::IdentityTopUp(_) + | StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) ) } @@ -477,6 +567,9 @@ impl StateTransition { StateTransition::AddressFundingFromAssetLock(st) => { st.calculate_min_required_fee(platform_version) } + StateTransition::ShieldFromAssetLock(st) => { + st.calculate_min_required_fee(platform_version) + } st => Err(ProtocolError::CorruptedCodeExecution(format!("{} is not an asset lock transaction, but we are calling required_asset_lock_balance_for_processing_start", st.name()))), } } @@ -556,6 +649,11 @@ impl StateTransition { Self::AddressFundsTransfer(_) => "AddressFundsTransfer".to_string(), Self::AddressFundingFromAssetLock(_) => "AddressFundingFromAssetLock".to_string(), Self::AddressCreditWithdrawal(_) => "AddressCreditWithdrawal".to_string(), + Self::Shield(_) => "Shield".to_string(), + Self::ShieldedTransfer(_) => "ShieldedTransfer".to_string(), + Self::Unshield(_) => "Unshield".to_string(), + Self::ShieldFromAssetLock(_) => "ShieldFromAssetLock".to_string(), + Self::ShieldedWithdrawal(_) => "ShieldedWithdrawal".to_string(), } } @@ -577,6 +675,11 @@ impl StateTransition { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(st) => Some(st.signature()), StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(st) => Some(st.signature()), + StateTransition::ShieldedWithdrawal(_) => None, } } @@ -587,6 +690,11 @@ impl StateTransition { StateTransition::IdentityTopUpFromAddresses(st) => st.inputs().len() as u16, StateTransition::AddressFundsTransfer(st) => st.inputs().len() as u16, StateTransition::AddressCreditWithdrawal(st) => st.inputs().len() as u16, + StateTransition::Shield(st) => st.inputs().len() as u16, + StateTransition::ShieldedTransfer(_) => 0, + StateTransition::Unshield(_) => 0, + StateTransition::ShieldFromAssetLock(_) => 0, + StateTransition::ShieldedWithdrawal(_) => 0, _ => 1, } } @@ -608,8 +716,13 @@ impl StateTransition { StateTransition::AddressFundsTransfer(st) => st.user_fee_increase(), StateTransition::AddressFundingFromAssetLock(st) => st.user_fee_increase(), StateTransition::AddressCreditWithdrawal(st) => st.user_fee_increase(), + StateTransition::Shield(st) => st.user_fee_increase(), // These transitions don't support user fee adjustment + StateTransition::ShieldFromAssetLock(_) => 0, StateTransition::MasternodeVote(_) => 0, + StateTransition::ShieldedTransfer(_) => 0, + StateTransition::Unshield(_) => 0, + StateTransition::ShieldedWithdrawal(_) => 0, } } @@ -672,6 +785,11 @@ impl StateTransition { StateTransition::AddressFundsTransfer(_) => None, StateTransition::AddressFundingFromAssetLock(_) => None, StateTransition::AddressCreditWithdrawal(_) => None, + StateTransition::Shield(_) => None, + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } } @@ -693,6 +811,11 @@ impl StateTransition { StateTransition::AddressFundsTransfer(st) => Some(st.inputs()), StateTransition::AddressFundingFromAssetLock(st) => Some(st.inputs()), StateTransition::AddressCreditWithdrawal(st) => Some(st.inputs()), + StateTransition::Shield(st) => Some(st.inputs()), + StateTransition::ShieldedTransfer(_) => None, + StateTransition::Unshield(_) => None, + StateTransition::ShieldFromAssetLock(_) => None, + StateTransition::ShieldedWithdrawal(_) => None, } } @@ -751,11 +874,19 @@ impl StateTransition { } StateTransition::IdentityCreateFromAddresses(_) | StateTransition::IdentityTopUpFromAddresses(_) - | StateTransition::AddressFundsTransfer(_) => false, + | StateTransition::AddressFundsTransfer(_) + | StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldedWithdrawal(_) => false, StateTransition::AddressFundingFromAssetLock(st) => { st.set_signature(signature); true } + StateTransition::ShieldFromAssetLock(st) => { + st.set_signature(signature); + true + } StateTransition::AddressCreditWithdrawal(_) => false, } } @@ -793,8 +924,13 @@ impl StateTransition { StateTransition::AddressCreditWithdrawal(st) => { st.set_user_fee_increase(user_fee_increase) } + StateTransition::Shield(st) => st.set_user_fee_increase(user_fee_increase), // These transitions don't support user fee adjustment — no-op + StateTransition::ShieldFromAssetLock(_) => {} StateTransition::MasternodeVote(_) => {} + StateTransition::ShieldedTransfer(_) => {} + StateTransition::Unshield(_) => {} + StateTransition::ShieldedWithdrawal(_) => {} } } @@ -941,6 +1077,34 @@ impl StateTransition { .to_string(), )) } + StateTransition::Shield(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shield transition can not be called for identity signing".to_string(), + )) + } + StateTransition::ShieldedTransfer(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shielded transfer transition can not be called for identity signing" + .to_string(), + )) + } + StateTransition::Unshield(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "unshield transition can not be called for identity signing".to_string(), + )) + } + StateTransition::ShieldFromAssetLock(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shield from asset lock transition can not be called for identity signing" + .to_string(), + )) + } + StateTransition::ShieldedWithdrawal(_) => { + return Err(ProtocolError::CorruptedCodeExecution( + "shielded withdrawal transition can not be called for identity signing" + .to_string(), + )) + } } let data = self.signable_bytes()?; self.set_signature(signer.sign(identity_public_key, data.as_slice())?); @@ -1278,6 +1442,19 @@ impl StateTransitionStructureValidation for StateTransition { StateTransition::AddressCreditWithdrawal(transition) => { transition.validate_structure(platform_version) } + StateTransition::Shield(transition) => transition.validate_structure(platform_version), + StateTransition::ShieldedTransfer(transition) => { + transition.validate_structure(platform_version) + } + StateTransition::Unshield(transition) => { + transition.validate_structure(platform_version) + } + StateTransition::ShieldFromAssetLock(transition) => { + transition.validate_structure(platform_version) + } + StateTransition::ShieldedWithdrawal(transition) => { + transition.validate_structure(platform_version) + } } } } diff --git a/packages/rs-dpp/src/state_transition/proof_result.rs b/packages/rs-dpp/src/state_transition/proof_result.rs index 9500e824e7b..0dcd6a661a2 100644 --- a/packages/rs-dpp/src/state_transition/proof_result.rs +++ b/packages/rs-dpp/src/state_transition/proof_result.rs @@ -1,4 +1,5 @@ use crate::address_funds::PlatformAddress; +use crate::asset_lock::StoredAssetLockInfo; use crate::balances::credits::TokenAmount; use crate::data_contract::group::GroupSumPower; use crate::data_contract::DataContract; @@ -55,4 +56,14 @@ pub enum StateTransitionProofResult { PartialIdentity, BTreeMap>, ), + VerifiedAssetLockConsumed(StoredAssetLockInfo), + VerifiedShieldedNullifiers(Vec<(Vec, bool)>), + VerifiedShieldedNullifiersWithAddressInfos( + Vec<(Vec, bool)>, + BTreeMap>, + ), + VerifiedShieldedNullifiersWithWithdrawalDocument( + Vec<(Vec, bool)>, + BTreeMap>, + ), } diff --git a/packages/rs-dpp/src/state_transition/state_transition_types.rs b/packages/rs-dpp/src/state_transition/state_transition_types.rs index f8ffaba25d7..e38071b4b70 100644 --- a/packages/rs-dpp/src/state_transition/state_transition_types.rs +++ b/packages/rs-dpp/src/state_transition/state_transition_types.rs @@ -35,6 +35,11 @@ pub enum StateTransitionType { AddressFundsTransfer = 12, AddressFundingFromAssetLock = 13, AddressCreditWithdrawal = 14, + Shield = 15, + ShieldedTransfer = 16, + Unshield = 17, + ShieldFromAssetLock = 18, + ShieldedWithdrawal = 19, } impl std::fmt::Display for StateTransitionType { diff --git a/packages/rs-dpp/src/state_transition/state_transitions/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/mod.rs index cc748f9bb4d..1ee962d0587 100644 --- a/packages/rs-dpp/src/state_transition/state_transitions/mod.rs +++ b/packages/rs-dpp/src/state_transition/state_transitions/mod.rs @@ -9,3 +9,6 @@ pub use address_funds::*; pub use contract::*; pub use document::*; pub use identity::*; + +pub mod shielded; +pub use shielded::*; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs new file mode 100644 index 00000000000..9daf7c0e598 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/common_validation.rs @@ -0,0 +1,51 @@ +use crate::consensus::basic::state_transition::{ + ShieldedEmptyProofError, ShieldedNoActionsError, ShieldedTooManyActionsError, + ShieldedZeroAnchorError, +}; +use crate::consensus::basic::BasicError; +use crate::shielded::SerializedAction; +use crate::validation::SimpleConsensusValidationResult; + +/// Validate that the actions list is not empty and does not exceed the maximum. +pub fn validate_actions_count( + actions: &[SerializedAction], + max_actions: u16, +) -> SimpleConsensusValidationResult { + if actions.is_empty() { + SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedNoActionsError(ShieldedNoActionsError::new()).into(), + ) + } else if actions.len() > max_actions as usize { + SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedTooManyActionsError(ShieldedTooManyActionsError::new( + actions.len().min(u16::MAX as usize) as u16, + max_actions, + )) + .into(), + ) + } else { + SimpleConsensusValidationResult::new() + } +} + +/// Validate that the proof is not empty. +pub fn validate_proof_not_empty(proof: &[u8]) -> SimpleConsensusValidationResult { + if proof.is_empty() { + SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedEmptyProofError(ShieldedEmptyProofError::new()).into(), + ) + } else { + SimpleConsensusValidationResult::new() + } +} + +/// Validate that the anchor is not all zeros (for transitions that consume notes). +pub fn validate_anchor_not_zero(anchor: &[u8; 32]) -> SimpleConsensusValidationResult { + if *anchor == [0u8; 32] { + SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedZeroAnchorError(ShieldedZeroAnchorError::new()).into(), + ) + } else { + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs new file mode 100644 index 00000000000..cb79e3f690f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/mod.rs @@ -0,0 +1,6 @@ +pub(crate) mod common_validation; +pub mod shield_from_asset_lock_transition; +pub mod shield_transition; +pub mod shielded_transfer_transition; +pub mod shielded_withdrawal_transition; +pub mod unshield_transition; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs new file mode 100644 index 00000000000..4b00879060f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/fields.rs @@ -0,0 +1,3 @@ +pub use crate::state_transition::state_transitions::common_fields::property_names::SIGNATURE; + +pub const PROOF: &str = "proof"; diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs new file mode 100644 index 00000000000..a71e402d4ca --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/mod.rs @@ -0,0 +1,56 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::prelude::AssetLockProof; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{ + shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0, StateTransition, + }, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_asset_lock_with_bundle( + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + actions: Vec, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shield_from_asset_lock_state_transition + .default_current_version + { + 0 => ShieldFromAssetLockTransitionV0::try_from_asset_lock_with_bundle( + asset_lock_proof, + asset_lock_proof_private_key, + actions, + value_balance, + anchor, + proof, + binding_signature, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldFromAssetLockTransition::try_from_asset_lock_with_bundle" + .to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..3ac5404bda8 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/methods/v0/mod.rs @@ -0,0 +1,29 @@ +#[cfg(feature = "state-transition-signing")] +use crate::prelude::AssetLockProof; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldFromAssetLockTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_asset_lock_with_bundle( + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + actions: Vec, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::ShieldFromAssetLock + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs new file mode 100644 index 00000000000..0ac7cbafc28 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/mod.rs @@ -0,0 +1,63 @@ +pub mod fields; +pub mod methods; +mod proved; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shield_from_asset_lock_transition::fields::{PROOF, SIGNATURE}; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldFromAssetLockTransitionLatest = ShieldFromAssetLockTransitionV0; + +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shield_from_asset_lock_state_transition" +)] +pub enum ShieldFromAssetLockTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldFromAssetLockTransitionV0), +} + +impl StateTransitionFieldTypes for ShieldFromAssetLockTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![SIGNATURE] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![SIGNATURE, PROOF] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs new file mode 100644 index 00000000000..4c06ae7ea68 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/proved.rs @@ -0,0 +1,27 @@ +use crate::identity::state_transition::{AssetLockProved, OptionallyAssetLockProved}; +use crate::prelude::AssetLockProof; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::ProtocolError; + +impl OptionallyAssetLockProved for ShieldFromAssetLockTransition { + fn optional_asset_lock_proof(&self) -> Option<&AssetLockProof> { + Some(self.asset_lock_proof()) + } +} + +impl AssetLockProved for ShieldFromAssetLockTransition { + fn set_asset_lock_proof( + &mut self, + asset_lock_proof: AssetLockProof, + ) -> Result<(), ProtocolError> { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.set_asset_lock_proof(asset_lock_proof), + } + } + + fn asset_lock_proof(&self) -> &AssetLockProof { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.asset_lock_proof(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..1141003afc6 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,22 @@ +use crate::balances::credits::CREDITS_PER_DUFF; +use crate::fee::Credits; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldFromAssetLockTransition { + fn calculate_min_required_fee( + &self, + platform_version: &PlatformVersion, + ) -> Result { + let asset_lock_base_cost = platform_version + .dpp + .state_transitions + .identities + .asset_locks + .required_asset_lock_duff_balance_for_processing_start_for_address_funding + * CREDITS_PER_DUFF; + Ok(asset_lock_base_cost) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs new file mode 100644 index 00000000000..be94599a6ad --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_like.rs @@ -0,0 +1,58 @@ +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::{ + StateTransitionLike, StateTransitionSingleSigned, StateTransitionType, +}; +use crate::version::FeatureVersion; +use platform_value::{BinaryData, Identifier}; + +impl StateTransitionLike for ShieldFromAssetLockTransition { + /// Returns IDs of the modified data + fn modified_data_ids(&self) -> Vec { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldFromAssetLockTransition::V0(_) => 0, + } + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.state_transition_type(), + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.unique_identifiers(), + } + } +} + +impl StateTransitionSingleSigned for ShieldFromAssetLockTransition { + /// returns the signature as a byte-array + fn signature(&self) -> &BinaryData { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.signature(), + } + } + + /// set a new signature + fn set_signature(&mut self, signature: BinaryData) { + match self { + ShieldFromAssetLockTransition::V0(transition) => transition.set_signature(signature), + } + } + + fn set_signature_bytes(&mut self, signature: Vec) { + match self { + ShieldFromAssetLockTransition::V0(transition) => { + transition.set_signature_bytes(signature) + } + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs new file mode 100644 index 00000000000..bf6bf059c24 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldFromAssetLockTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs new file mode 100644 index 00000000000..dd65db3d64a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/mod.rs @@ -0,0 +1,101 @@ +mod proved; +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::identity::state_transition::asset_lock_proof::AssetLockProof; +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_value::BinaryData; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldFromAssetLockTransitionV0 { + /// Asset lock proof from L1 + pub asset_lock_proof: AssetLockProof, + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Amount of credits flowing into the shielded pool from the asset lock. + /// Must be > 0 and <= i64::MAX. + pub value_balance: u64, + /// Sinsemilla root of the note commitment tree (Orchard Anchor) + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::serialization::serde_bytes_64") + )] + pub binding_signature: [u8; 64], + /// ECDSA signature over the signable bytes (excluded from sig hash) + #[platform_signable(exclude_from_sig_hash)] + pub signature: BinaryData, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::identity::state_transition::asset_lock_proof::chain::ChainAssetLockProof; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use dashcore::OutPoint; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shield_from_asset_lock_transition_v0_serialization_round_trip() { + let chain_proof = ChainAssetLockProof { + core_chain_locked_height: 100, + out_point: OutPoint::from([11u8; 36]), + }; + + let transition = ShieldFromAssetLockTransitionV0 { + asset_lock_proof: AssetLockProof::Chain(chain_proof), + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + value_balance: 1000u64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + signature: BinaryData::new(vec![10u8; 65]), + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs new file mode 100644 index 00000000000..9eccf52473e --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/proved.rs @@ -0,0 +1,18 @@ +use crate::identity::state_transition::AssetLockProved; +use crate::prelude::AssetLockProof; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::ProtocolError; + +impl AssetLockProved for ShieldFromAssetLockTransitionV0 { + fn set_asset_lock_proof( + &mut self, + asset_lock_proof: AssetLockProof, + ) -> Result<(), ProtocolError> { + self.asset_lock_proof = asset_lock_proof; + Ok(()) + } + + fn asset_lock_proof(&self) -> &AssetLockProof { + &self.asset_lock_proof + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..27f72e2989f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_like.rs @@ -0,0 +1,65 @@ +use base64::prelude::BASE64_STANDARD; +use base64::Engine; +use platform_value::BinaryData; + +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::{StateTransition, StateTransitionSingleSigned}; +use crate::version::FeatureVersion; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +impl From for StateTransition { + fn from(value: ShieldFromAssetLockTransitionV0) -> Self { + let transition: ShieldFromAssetLockTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for ShieldFromAssetLockTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + StateTransitionType::ShieldFromAssetLock + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// Returns unique identifier based on the asset lock proof. + /// The asset lock can only be consumed once, making it the natural deduplication key. + fn unique_identifiers(&self) -> Vec { + let identifier = self.asset_lock_proof.create_identifier(); + match identifier { + Ok(identifier) => { + vec![BASE64_STANDARD.encode(identifier)] + } + Err(_) => { + vec![String::default()] + } + } + } +} + +impl StateTransitionSingleSigned for ShieldFromAssetLockTransitionV0 { + /// returns the signature as a byte-array + fn signature(&self) -> &BinaryData { + &self.signature + } + + /// set a new signature + fn set_signature(&mut self, signature: BinaryData) { + self.signature = signature + } + + fn set_signature_bytes(&mut self, signature: Vec) { + self.signature = BinaryData::new(signature) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs new file mode 100644 index 00000000000..d29bf1316c5 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/state_transition_validation.rs @@ -0,0 +1,65 @@ +use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; +use crate::consensus::basic::BasicError; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::state_transitions::shielded::common_validation::{ + validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldFromAssetLockTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + let result = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ); + if !result.is_valid() { + return result; + } + + // value_balance must be > 0 (credits flowing into pool) + if self.value_balance == 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shield_from_asset_lock value_balance must be greater than 0".to_string(), + ), + ) + .into(), + ); + } + + // value_balance must fit in i64 (Orchard protocol uses i64 internally) + if self.value_balance > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shield_from_asset_lock value_balance exceeds i64::MAX".to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; + } + + // Anchor must not be all zeros + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs new file mode 100644 index 00000000000..a65cb0fde42 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/types.rs @@ -0,0 +1,17 @@ +use crate::state_transition::shield_from_asset_lock_transition::fields::{PROOF, SIGNATURE}; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldFromAssetLockTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![SIGNATURE] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![SIGNATURE, PROOF] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..c10d1907a9b --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/v0_methods.rs @@ -0,0 +1,49 @@ +#[cfg(feature = "state-transition-signing")] +use crate::prelude::AssetLockProof; +#[cfg(feature = "state-transition-signing")] +use crate::serialization::Signable; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_from_asset_lock_transition::methods::ShieldFromAssetLockTransitionMethodsV0; +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use dashcore::signer; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldFromAssetLockTransitionMethodsV0 for ShieldFromAssetLockTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_asset_lock_with_bundle( + asset_lock_proof: AssetLockProof, + asset_lock_proof_private_key: &[u8], + actions: Vec, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + _platform_version: &PlatformVersion, + ) -> Result { + // Create the unsigned transition + let mut transition = ShieldFromAssetLockTransitionV0 { + asset_lock_proof, + actions, + value_balance, + anchor, + proof, + binding_signature, + signature: Default::default(), + }; + + // Compute signable bytes (signature field is excluded from sig hash) + let state_transition: StateTransition = transition.clone().into(); + let signable_bytes = state_transition.signable_bytes()?; + + // Sign with the asset lock private key (ECDSA) + let signature = signer::sign(&signable_bytes, asset_lock_proof_private_key)?; + transition.signature = signature.to_vec().into(); + + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs new file mode 100644 index 00000000000..4ffb283d990 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shield_from_asset_lock_transition::v0::ShieldFromAssetLockTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldFromAssetLockTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs new file mode 100644 index 00000000000..949a31fb04a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_from_asset_lock_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shield_from_asset_lock_transition::ShieldFromAssetLockTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldFromAssetLockTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldFromAssetLockTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs new file mode 100644 index 00000000000..d87fb75333d --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/mod.rs @@ -0,0 +1,64 @@ +mod v0; + +#[cfg(feature = "state-transition-signing")] +use std::collections::BTreeMap; +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; +#[cfg(feature = "state-transition-signing")] +use crate::fee::Credits; +#[cfg(feature = "state-transition-signing")] +use crate::identity::signer::Signer; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_transition::ShieldTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::{AddressNonce, UserFeeIncrease}, + state_transition::{shield_transition::v0::ShieldTransitionV0, StateTransition}, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldTransitionMethodsV0 for ShieldTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle_with_signer>( + inputs: BTreeMap, + actions: Vec, + amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shield_state_transition + .default_current_version + { + 0 => ShieldTransitionV0::try_from_bundle_with_signer( + inputs, + actions, + amount, + anchor, + proof, + binding_signature, + fee_strategy, + signer, + user_fee_increase, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldTransition::try_from_bundle_with_signer".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..ea69b2a435a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/methods/v0/mod.rs @@ -0,0 +1,42 @@ +#[cfg(feature = "state-transition-signing")] +use std::collections::BTreeMap; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::{AddressFundsFeeStrategy, PlatformAddress}; +#[cfg(feature = "state-transition-signing")] +use crate::fee::Credits; +#[cfg(feature = "state-transition-signing")] +use crate::identity::signer::Signer; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::{AddressNonce, UserFeeIncrease}, + state_transition::StateTransition, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_bundle_with_signer>( + inputs: BTreeMap, + actions: Vec, + amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::Shield + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs new file mode 100644 index 00000000000..720b637ebfe --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/mod.rs @@ -0,0 +1,63 @@ +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldTransitionLatest = ShieldTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shield_state_transition" +)] +pub enum ShieldTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldTransitionV0), +} + +impl OptionallyAssetLockProved for ShieldTransition {} + +impl StateTransitionFieldTypes for ShieldTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..d8a5d6ce98b --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,20 @@ +use crate::fee::Credits; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::{ + StateTransitionEstimatedFeeValidation, StateTransitionWitnessSigned, +}; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldTransition { + fn calculate_min_required_fee( + &self, + platform_version: &PlatformVersion, + ) -> Result { + let min_fees = &platform_version.fee_version.state_transition_min_fees; + let input_count = self.inputs().len(); + Ok(min_fees + .address_funds_transfer_input_cost + .saturating_mul(input_count as u64)) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs new file mode 100644 index 00000000000..017a143bc2e --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_like.rs @@ -0,0 +1,99 @@ +use crate::address_funds::AddressWitness; +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::StateTransitionHasUserFeeIncrease; +use crate::state_transition::{ + StateTransitionLike, StateTransitionType, StateTransitionWitnessSigned, +}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for ShieldTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + ShieldTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldTransition::V0(transition) => transition.state_transition_type(), + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldTransition::V0(transition) => transition.unique_identifiers(), + } + } +} + +impl StateTransitionHasUserFeeIncrease for ShieldTransition { + /// returns the fee multiplier + fn user_fee_increase(&self) -> UserFeeIncrease { + match self { + ShieldTransition::V0(transition) => transition.user_fee_increase(), + } + } + /// set a fee multiplier + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + match self { + ShieldTransition::V0(transition) => transition.set_user_fee_increase(user_fee_increase), + } + } +} + +impl StateTransitionWitnessSigned for ShieldTransition { + fn inputs( + &self, + ) -> &std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + match self { + ShieldTransition::V0(transition) => transition.inputs(), + } + } + + fn inputs_mut( + &mut self, + ) -> &mut std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + match self { + ShieldTransition::V0(transition) => transition.inputs_mut(), + } + } + + fn set_inputs( + &mut self, + inputs: std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + >, + ) { + match self { + ShieldTransition::V0(transition) => transition.set_inputs(inputs), + } + } + + fn witnesses(&self) -> &Vec { + match self { + ShieldTransition::V0(transition) => transition.witnesses(), + } + } + + fn set_witnesses(&mut self, witnesses: Vec) { + match self { + ShieldTransition::V0(transition) => transition.set_witnesses(witnesses), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs new file mode 100644 index 00000000000..71d92296f55 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/state_transition_validation.rs @@ -0,0 +1,19 @@ +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::{ + StateTransitionStructureValidation, StateTransitionWitnessValidation, +}; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} + +impl StateTransitionWitnessValidation for ShieldTransition {} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs new file mode 100644 index 00000000000..0ded39fb887 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/mod.rs @@ -0,0 +1,110 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +#[cfg(feature = "state-transition-signing")] +pub(super) mod v0_methods; +mod version; + +use std::collections::BTreeMap; + +use crate::address_funds::{AddressFundsFeeStrategy, AddressWitness, PlatformAddress}; +use crate::fee::Credits; +use crate::prelude::{AddressNonce, UserFeeIncrease}; +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldTransitionV0 { + /// Address inputs funding the shield (address -> nonce + max contribution). + /// The total across all inputs must cover |value_balance| + fees. + /// Excess credits remain in the source addresses. + pub inputs: BTreeMap, + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Amount of credits being shielded (entering the shielded pool). + pub amount: u64, + /// Sinsemilla root of the note commitment tree (Orchard Anchor) + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::serialization::serde_bytes_64") + )] + pub binding_signature: [u8; 64], + /// Fee payment strategy + pub fee_strategy: AddressFundsFeeStrategy, + /// Fee multiplier + pub user_fee_increase: UserFeeIncrease, + /// Address witness signatures (excluded from sig hash) + #[platform_signable(exclude_from_sig_hash)] + pub input_witnesses: Vec, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shield_transition_v0_serialization_round_trip() { + let mut inputs = BTreeMap::new(); + let address = PlatformAddress::P2pkh([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + ]); + inputs.insert(address, (1u32, 1000u64)); // nonce, credits + + let transition = ShieldTransitionV0 { + inputs, + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + amount: 1000u64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + fee_strategy: vec![], + user_fee_increase: 0u16, + input_witnesses: vec![], + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..a2ee4c0ac43 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_like.rs @@ -0,0 +1,92 @@ +use crate::address_funds::AddressWitness; +use crate::prelude::UserFeeIncrease; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::StateTransitionHasUserFeeIncrease; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransitionType::Shield; +use crate::state_transition::{StateTransition, StateTransitionWitnessSigned}; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: ShieldTransitionV0) -> Self { + let shield_transition: ShieldTransition = value.into(); + shield_transition.into() + } +} + +impl StateTransitionLike for ShieldTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + Shield + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// State transitions with the same inputs should not be allowed to overlap + fn unique_identifiers(&self) -> Vec { + self.inputs + .iter() + .map(|(key, (nonce, _))| key.base64_string_with_nonce(*nonce)) + .collect() + } +} + +impl StateTransitionHasUserFeeIncrease for ShieldTransitionV0 { + fn user_fee_increase(&self) -> UserFeeIncrease { + self.user_fee_increase + } + + fn set_user_fee_increase(&mut self, user_fee_increase: UserFeeIncrease) { + self.user_fee_increase = user_fee_increase + } +} + +impl StateTransitionWitnessSigned for ShieldTransitionV0 { + fn inputs( + &self, + ) -> &std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + &self.inputs + } + + fn inputs_mut( + &mut self, + ) -> &mut std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + > { + &mut self.inputs + } + + fn set_inputs( + &mut self, + inputs: std::collections::BTreeMap< + crate::address_funds::PlatformAddress, + (crate::prelude::AddressNonce, crate::fee::Credits), + >, + ) { + self.inputs = inputs; + } + + fn witnesses(&self) -> &Vec { + &self.input_witnesses + } + + fn set_witnesses(&mut self, witnesses: Vec) { + self.input_witnesses = witnesses; + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs new file mode 100644 index 00000000000..ff1b4805936 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/state_transition_validation.rs @@ -0,0 +1,124 @@ +use crate::consensus::basic::state_transition::{ + FeeStrategyDuplicateError, FeeStrategyEmptyError, FeeStrategyTooManyStepsError, + InputBelowMinimumError, InputWitnessCountMismatchError, ShieldedInvalidValueBalanceError, + TransitionNoInputsError, +}; +use crate::consensus::basic::BasicError; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::state_transitions::shielded::common_validation::{ + validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; +use std::collections::HashSet; + +impl StateTransitionStructureValidation for ShieldTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + let result = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ); + if !result.is_valid() { + return result; + } + + // Inputs must not be empty (shield requires address funding) + if self.inputs.is_empty() { + return SimpleConsensusValidationResult::new_with_error( + BasicError::TransitionNoInputsError(TransitionNoInputsError::new()).into(), + ); + } + + // Input witnesses must match inputs count + if self.inputs.len() != self.input_witnesses.len() { + return SimpleConsensusValidationResult::new_with_error( + BasicError::InputWitnessCountMismatchError(InputWitnessCountMismatchError::new( + self.inputs.len().min(u16::MAX as usize) as u16, + self.input_witnesses.len().min(u16::MAX as usize) as u16, + )) + .into(), + ); + } + + // Validate each input amount is > 0 + let min_input_amount = platform_version + .dpp + .state_transitions + .address_funds + .min_input_amount; + for (_nonce, amount) in self.inputs.values() { + if *amount < min_input_amount { + return SimpleConsensusValidationResult::new_with_error( + BasicError::InputBelowMinimumError(InputBelowMinimumError::new( + *amount, + min_input_amount, + )) + .into(), + ); + } + } + + // amount must be positive (credits flowing into pool) + if self.amount == 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shield amount must be greater than zero".to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; + } + + // Anchor must not be all zeros + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; + } + + // Fee strategy validation (reuse address funds patterns) + if self.fee_strategy.is_empty() { + return SimpleConsensusValidationResult::new_with_error( + BasicError::FeeStrategyEmptyError(FeeStrategyEmptyError::new()).into(), + ); + } + + let max_fee_strategies = platform_version + .dpp + .state_transitions + .max_address_fee_strategies as usize; + if self.fee_strategy.len() > max_fee_strategies { + return SimpleConsensusValidationResult::new_with_error( + BasicError::FeeStrategyTooManyStepsError(FeeStrategyTooManyStepsError::new( + self.fee_strategy.len().min(u8::MAX as usize) as u8, + max_fee_strategies.min(u8::MAX as usize) as u8, + )) + .into(), + ); + } + + let mut seen = HashSet::with_capacity(self.fee_strategy.len()); + for step in &self.fee_strategy { + if !seen.insert(step) { + return SimpleConsensusValidationResult::new_with_error( + BasicError::FeeStrategyDuplicateError(FeeStrategyDuplicateError::new()).into(), + ); + } + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs new file mode 100644 index 00000000000..ed94df14858 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..28565600d97 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/v0_methods.rs @@ -0,0 +1,64 @@ +#[cfg(feature = "state-transition-signing")] +use std::collections::BTreeMap; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::{AddressFundsFeeStrategy, AddressWitness, PlatformAddress}; +#[cfg(feature = "state-transition-signing")] +use crate::fee::Credits; +#[cfg(feature = "state-transition-signing")] +use crate::identity::signer::Signer; +#[cfg(feature = "state-transition-signing")] +use crate::serialization::Signable; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shield_transition::methods::ShieldTransitionMethodsV0; +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{ + prelude::{AddressNonce, UserFeeIncrease}, + state_transition::StateTransition, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldTransitionMethodsV0 for ShieldTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle_with_signer>( + inputs: BTreeMap, + actions: Vec, + amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + fee_strategy: AddressFundsFeeStrategy, + signer: &S, + user_fee_increase: UserFeeIncrease, + _platform_version: &PlatformVersion, + ) -> Result { + // Create the unsigned transition (empty witnesses) + let mut shield_transition = ShieldTransitionV0 { + inputs: inputs.clone(), + actions, + amount, + anchor, + proof, + binding_signature, + fee_strategy, + user_fee_increase, + input_witnesses: Vec::new(), + }; + + // Compute signable bytes (excludes input_witnesses which are marked exclude_from_sig_hash) + let state_transition: StateTransition = shield_transition.clone().into(); + let signable_bytes = state_transition.signable_bytes()?; + + // Sign each input address + shield_transition.input_witnesses = inputs + .keys() + .map(|address| signer.sign_create_witness(address, &signable_bytes)) + .collect::, ProtocolError>>()?; + + Ok(shield_transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs new file mode 100644 index 00000000000..27429523509 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shield_transition::v0::ShieldTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs new file mode 100644 index 00000000000..8f5de0b6b7a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shield_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shield_transition::ShieldTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs new file mode 100644 index 00000000000..e12d9f4e66f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/mod.rs @@ -0,0 +1,14 @@ +mod v0; + +pub use v0::*; + +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; + +impl ShieldedTransferTransitionAccessorsV0 for ShieldedTransferTransition { + fn actions(&self) -> &[SerializedAction] { + match self { + ShieldedTransferTransition::V0(v0) => &v0.actions, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs new file mode 100644 index 00000000000..869c6167d28 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/accessors/v0/mod.rs @@ -0,0 +1,15 @@ +use crate::shielded::SerializedAction; + +pub trait ShieldedTransferTransitionAccessorsV0 { + /// Get the serialized Orchard actions + fn actions(&self) -> &[SerializedAction]; + + /// Extract nullifier bytes from each action. + /// Generic over the element type: use `Vec` or `[u8; 32]` as needed. + fn nullifiers>(&self) -> Vec { + self.actions() + .iter() + .map(|a| T::from(a.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs new file mode 100644 index 00000000000..b3190031954 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/mod.rs @@ -0,0 +1,49 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{ + shielded_transfer_transition::v0::ShieldedTransferTransitionV0, StateTransition, + }, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shielded_transfer_state_transition + .default_current_version + { + 0 => ShieldedTransferTransitionV0::try_from_bundle( + actions, + value_balance, + anchor, + proof, + binding_signature, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldedTransferTransition::try_from_bundle".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..90b8fbeff2a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/methods/v0/mod.rs @@ -0,0 +1,24 @@ +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldedTransferTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::ShieldedTransfer + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs new file mode 100644 index 00000000000..e9a183f249a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/mod.rs @@ -0,0 +1,64 @@ +pub mod accessors; +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldedTransferTransitionLatest = ShieldedTransferTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shielded_transfer_state_transition" +)] +pub enum ShieldedTransferTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldedTransferTransitionV0), +} + +impl OptionallyAssetLockProved for ShieldedTransferTransition {} + +impl StateTransitionFieldTypes for ShieldedTransferTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..d706caa4327 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,18 @@ +use crate::fee::Credits; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldedTransferTransition { + fn calculate_min_required_fee( + &self, + _platform_version: &PlatformVersion, + ) -> Result { + // TODO: revisit when drive/drive-abci integration lands — the shielded fee is + // embedded in the bundle's value_balance and validated on-chain via + // `validate_minimum_shielded_fee`, but this client-side estimate currently + // returns 0 which means mempool pre-checks won't reject under-fee'd txns. + Ok(0) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs new file mode 100644 index 00000000000..6ba20092d1a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_like.rs @@ -0,0 +1,31 @@ +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::{StateTransitionLike, StateTransitionType}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for ShieldedTransferTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + ShieldedTransferTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldedTransferTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldedTransferTransition::V0(transition) => transition.state_transition_type(), + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldedTransferTransition::V0(transition) => transition.unique_identifiers(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs new file mode 100644 index 00000000000..9d3bbbe6ee3 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedTransferTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldedTransferTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs new file mode 100644 index 00000000000..7ee5c1e8cc2 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/mod.rs @@ -0,0 +1,83 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldedTransferTransitionV0 { + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Net value balance (fee amount extracted from shielded pool) + pub value_balance: u64, + /// Sinsemilla root of the note commitment tree (Orchard Anchor) + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::serialization::serde_bytes_64") + )] + pub binding_signature: [u8; 64], +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shielded_transfer_transition_v0_serialization_round_trip() { + let transition = ShieldedTransferTransitionV0 { + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + value_balance: 0u64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..8163b81b8ce --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_like.rs @@ -0,0 +1,42 @@ +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransition; +use crate::state_transition::StateTransitionType::ShieldedTransfer; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: ShieldedTransferTransitionV0) -> Self { + let transition: ShieldedTransferTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for ShieldedTransferTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + ShieldedTransfer + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// For ZK-only transitions, uniqueness comes from nullifiers in the actions. + /// Each nullifier can only be used once, making them natural unique identifiers. + fn unique_identifiers(&self) -> Vec { + self.actions + .iter() + .map(|action| hex::encode(action.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs new file mode 100644 index 00000000000..7329f6c8706 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/state_transition_validation.rs @@ -0,0 +1,53 @@ +use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; +use crate::consensus::basic::BasicError; +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::state_transitions::shielded::common_validation::{ + validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedTransferTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + let result = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ); + if !result.is_valid() { + return result; + } + + // value_balance must fit in i64 (required for Orchard protocol) + if self.value_balance > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shielded transfer value_balance exceeds maximum allowed value".to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; + } + + // Anchor must not be all zeros + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs new file mode 100644 index 00000000000..9c3dc80f9d2 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldedTransferTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..b9971987196 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/v0_methods.rs @@ -0,0 +1,29 @@ +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_transfer_transition::methods::ShieldedTransferTransitionMethodsV0; +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedTransferTransitionMethodsV0 for ShieldedTransferTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + value_balance: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + _platform_version: &PlatformVersion, + ) -> Result { + let transition = ShieldedTransferTransitionV0 { + actions, + value_balance, + anchor, + proof, + binding_signature, + }; + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs new file mode 100644 index 00000000000..fb6f180b029 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shielded_transfer_transition::v0::ShieldedTransferTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedTransferTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs new file mode 100644 index 00000000000..0688f51e27d --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_transfer_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shielded_transfer_transition::ShieldedTransferTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedTransferTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldedTransferTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs new file mode 100644 index 00000000000..d2bdd82cf71 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/mod.rs @@ -0,0 +1,21 @@ +mod v0; + +pub use v0::*; + +use crate::identity::core_script::CoreScript; +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; + +impl ShieldedWithdrawalTransitionAccessorsV0 for ShieldedWithdrawalTransition { + fn actions(&self) -> &[SerializedAction] { + match self { + ShieldedWithdrawalTransition::V0(v0) => &v0.actions, + } + } + + fn output_script(&self) -> &CoreScript { + match self { + ShieldedWithdrawalTransition::V0(v0) => &v0.output_script, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs new file mode 100644 index 00000000000..c6fd48b4a1f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/accessors/v0/mod.rs @@ -0,0 +1,19 @@ +use crate::identity::core_script::CoreScript; +use crate::shielded::SerializedAction; + +pub trait ShieldedWithdrawalTransitionAccessorsV0 { + /// Get the serialized Orchard actions + fn actions(&self) -> &[SerializedAction]; + + /// Get the output script receiving withdrawn funds + fn output_script(&self) -> &CoreScript; + + /// Extract nullifier bytes from each action. + /// Generic over the element type: use `Vec` or `[u8; 32]` as needed. + fn nullifiers>(&self) -> Vec { + self.actions() + .iter() + .map(|a| T::from(a.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs new file mode 100644 index 00000000000..579ed9db47a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/mod.rs @@ -0,0 +1,59 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::identity::core_script::CoreScript; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +#[cfg(feature = "state-transition-signing")] +use crate::withdrawal::Pooling; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{ + shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0, StateTransition, + }, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + unshielding_amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .shielded_withdrawal_state_transition + .default_current_version + { + 0 => ShieldedWithdrawalTransitionV0::try_from_bundle( + actions, + unshielding_amount, + anchor, + proof, + binding_signature, + core_fee_per_byte, + pooling, + output_script, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "ShieldedWithdrawalTransition::try_from_bundle".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..4824b45f783 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/methods/v0/mod.rs @@ -0,0 +1,32 @@ +#[cfg(feature = "state-transition-signing")] +use crate::identity::core_script::CoreScript; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::withdrawal::Pooling; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait ShieldedWithdrawalTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_bundle( + actions: Vec, + unshielding_amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::ShieldedWithdrawal + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs new file mode 100644 index 00000000000..9c1209411d6 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/mod.rs @@ -0,0 +1,64 @@ +pub mod accessors; +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type ShieldedWithdrawalTransitionLatest = ShieldedWithdrawalTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.shielded_withdrawal_state_transition" +)] +pub enum ShieldedWithdrawalTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(ShieldedWithdrawalTransitionV0), +} + +impl OptionallyAssetLockProved for ShieldedWithdrawalTransition {} + +impl StateTransitionFieldTypes for ShieldedWithdrawalTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..d41a8a3c130 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,18 @@ +use crate::fee::Credits; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for ShieldedWithdrawalTransition { + fn calculate_min_required_fee( + &self, + _platform_version: &PlatformVersion, + ) -> Result { + // TODO: revisit when drive/drive-abci integration lands — the shielded fee is + // embedded in unshielding_amount and validated on-chain via + // `validate_minimum_shielded_fee`, but this client-side estimate currently + // returns 0 which means mempool pre-checks won't reject under-fee'd txns. + Ok(0) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs new file mode 100644 index 00000000000..699d0e7a1ea --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_like.rs @@ -0,0 +1,31 @@ +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::{StateTransitionLike, StateTransitionType}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for ShieldedWithdrawalTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + ShieldedWithdrawalTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + ShieldedWithdrawalTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + ShieldedWithdrawalTransition::V0(transition) => transition.state_transition_type(), + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + ShieldedWithdrawalTransition::V0(transition) => transition.unique_identifiers(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs new file mode 100644 index 00000000000..c00c5ea9157 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedWithdrawalTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + ShieldedWithdrawalTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs new file mode 100644 index 00000000000..f8f0615d001 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/mod.rs @@ -0,0 +1,94 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::identity::core_script::CoreScript; +use crate::shielded::SerializedAction; +use crate::withdrawal::Pooling; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct ShieldedWithdrawalTransitionV0 { + /// Orchard actions (spends + change outputs) + pub actions: Vec, + /// Total credits leaving the shielded pool (recipient amount + fee) + pub unshielding_amount: u64, + /// Sinsemilla root of the note commitment tree (Orchard Anchor) + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::serialization::serde_bytes_64") + )] + pub binding_signature: [u8; 64], + /// Core transaction fee rate + pub core_fee_per_byte: u32, + /// Withdrawal pooling strategy + pub pooling: Pooling, + /// Core address receiving withdrawn funds + pub output_script: CoreScript, +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_shielded_withdrawal_transition_v0_serialization_round_trip() { + let transition = ShieldedWithdrawalTransitionV0 { + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + unshielding_amount: 1000u64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + core_fee_per_byte: 1u32, + pooling: Pooling::Never, + output_script: CoreScript::new_p2pkh([11u8; 20]), + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..77cd8ccbb05 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_like.rs @@ -0,0 +1,42 @@ +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransition; +use crate::state_transition::StateTransitionType::ShieldedWithdrawal; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: ShieldedWithdrawalTransitionV0) -> Self { + let transition: ShieldedWithdrawalTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for ShieldedWithdrawalTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + ShieldedWithdrawal + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// For ZK-only transitions, uniqueness comes from nullifiers in the actions. + /// Each nullifier can only be used once, making them natural unique identifiers. + fn unique_identifiers(&self) -> Vec { + self.actions + .iter() + .map(|action| hex::encode(action.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs new file mode 100644 index 00000000000..41821b3e258 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/state_transition_validation.rs @@ -0,0 +1,65 @@ +use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; +use crate::consensus::basic::BasicError; +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::state_transitions::shielded::common_validation::{ + validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, +}; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for ShieldedWithdrawalTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + let result = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ); + if !result.is_valid() { + return result; + } + + // unshielding_amount must be positive and within i64::MAX + if self.unshielding_amount == 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shielded withdrawal unshielding_amount must be positive".to_string(), + ), + ) + .into(), + ); + } + + if self.unshielding_amount > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "shielded withdrawal unshielding_amount exceeds maximum allowed value" + .to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; + } + + // Anchor must not be all zeros + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs new file mode 100644 index 00000000000..5786bdd0554 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for ShieldedWithdrawalTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..31fe6a01bbf --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/v0_methods.rs @@ -0,0 +1,39 @@ +#[cfg(feature = "state-transition-signing")] +use crate::identity::core_script::CoreScript; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::shielded_withdrawal_transition::methods::ShieldedWithdrawalTransitionMethodsV0; +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::withdrawal::Pooling; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl ShieldedWithdrawalTransitionMethodsV0 for ShieldedWithdrawalTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + actions: Vec, + unshielding_amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + core_fee_per_byte: u32, + pooling: Pooling, + output_script: CoreScript, + _platform_version: &PlatformVersion, + ) -> Result { + let transition = ShieldedWithdrawalTransitionV0 { + actions, + unshielding_amount, + anchor, + proof, + binding_signature, + core_fee_per_byte, + pooling, + output_script, + }; + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs new file mode 100644 index 00000000000..89d4dfa7750 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::shielded_withdrawal_transition::v0::ShieldedWithdrawalTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedWithdrawalTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs new file mode 100644 index 00000000000..da339c67331 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/shielded_withdrawal_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::shielded_withdrawal_transition::ShieldedWithdrawalTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for ShieldedWithdrawalTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + ShieldedWithdrawalTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs new file mode 100644 index 00000000000..2c0619d2159 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/mod.rs @@ -0,0 +1,21 @@ +mod v0; + +pub use v0::*; + +use crate::address_funds::PlatformAddress; +use crate::shielded::SerializedAction; +use crate::state_transition::unshield_transition::UnshieldTransition; + +impl UnshieldTransitionAccessorsV0 for UnshieldTransition { + fn actions(&self) -> &[SerializedAction] { + match self { + UnshieldTransition::V0(v0) => &v0.actions, + } + } + + fn output_address(&self) -> &PlatformAddress { + match self { + UnshieldTransition::V0(v0) => &v0.output_address, + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs new file mode 100644 index 00000000000..583448b4568 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/accessors/v0/mod.rs @@ -0,0 +1,19 @@ +use crate::address_funds::PlatformAddress; +use crate::shielded::SerializedAction; + +pub trait UnshieldTransitionAccessorsV0 { + /// Get the serialized Orchard actions + fn actions(&self) -> &[SerializedAction]; + + /// Get the output address receiving unshielded funds + fn output_address(&self) -> &PlatformAddress; + + /// Extract nullifier bytes from each action. + /// Generic over the element type: use `Vec` or `[u8; 32]` as needed. + fn nullifiers>(&self) -> Vec { + self.actions() + .iter() + .map(|a| T::from(a.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs new file mode 100644 index 00000000000..45e189e6d1c --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/mod.rs @@ -0,0 +1,51 @@ +mod v0; + +pub use v0::*; + +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::PlatformAddress; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::unshield_transition::UnshieldTransition; +#[cfg(feature = "state-transition-signing")] +use crate::{ + state_transition::{unshield_transition::v0::UnshieldTransitionV0, StateTransition}, + ProtocolError, +}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl UnshieldTransitionMethodsV0 for UnshieldTransition { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + output_address: PlatformAddress, + actions: Vec, + unshielding_amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result { + match platform_version + .dpp + .state_transition_serialization_versions + .unshield_state_transition + .default_current_version + { + 0 => UnshieldTransitionV0::try_from_bundle( + output_address, + actions, + unshielding_amount, + anchor, + proof, + binding_signature, + platform_version, + ), + version => Err(ProtocolError::UnknownVersionMismatch { + method: "UnshieldTransition::try_from_bundle".to_string(), + known_versions: vec![0], + received: version, + }), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs new file mode 100644 index 00000000000..d68e385aaf2 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/methods/v0/mod.rs @@ -0,0 +1,28 @@ +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::PlatformAddress; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::StateTransitionType; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +pub trait UnshieldTransitionMethodsV0 { + #[cfg(feature = "state-transition-signing")] + #[allow(clippy::too_many_arguments)] + fn try_from_bundle( + output_address: PlatformAddress, + actions: Vec, + unshielding_amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + platform_version: &PlatformVersion, + ) -> Result; + + /// Get State Transition Type + fn get_type() -> StateTransitionType { + StateTransitionType::Unshield + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs new file mode 100644 index 00000000000..410405bb159 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/mod.rs @@ -0,0 +1,64 @@ +pub mod accessors; +pub mod methods; +mod state_transition_estimated_fee_validation; +mod state_transition_like; +mod state_transition_validation; +pub mod v0; +mod version; + +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0Signable; +use crate::state_transition::StateTransitionFieldTypes; + +pub type UnshieldTransitionLatest = UnshieldTransitionV0; + +use crate::identity::state_transition::OptionallyAssetLockProved; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use derive_more::From; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +use platform_versioning::PlatformVersioned; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformDeserialize, + PlatformSerialize, + PlatformSignable, + PlatformVersioned, + From, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(tag = "$version") +)] +#[platform_serialize(unversioned)] //versioned directly, no need to use platform_version +#[platform_version_path_bounds( + "dpp.state_transition_serialization_versions.unshield_state_transition" +)] +pub enum UnshieldTransition { + #[cfg_attr(feature = "state-transition-serde-conversion", serde(rename = "0"))] + V0(UnshieldTransitionV0), +} + +impl OptionallyAssetLockProved for UnshieldTransition {} + +impl StateTransitionFieldTypes for UnshieldTransition { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs new file mode 100644 index 00000000000..93559c7e7a5 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_estimated_fee_validation.rs @@ -0,0 +1,18 @@ +use crate::fee::Credits; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransitionEstimatedFeeValidation; +use crate::ProtocolError; +use platform_version::version::PlatformVersion; + +impl StateTransitionEstimatedFeeValidation for UnshieldTransition { + fn calculate_min_required_fee( + &self, + _platform_version: &PlatformVersion, + ) -> Result { + // TODO: revisit when drive/drive-abci integration lands — the shielded fee is + // embedded in unshielding_amount and validated on-chain via + // `validate_minimum_shielded_fee`, but this client-side estimate currently + // returns 0 which means mempool pre-checks won't reject under-fee'd txns. + Ok(0) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs new file mode 100644 index 00000000000..4b5e674f5ec --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_like.rs @@ -0,0 +1,31 @@ +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::{StateTransitionLike, StateTransitionType}; +use crate::version::FeatureVersion; +use platform_value::Identifier; + +impl StateTransitionLike for UnshieldTransition { + /// Returns ID of the created contract + fn modified_data_ids(&self) -> Vec { + match self { + UnshieldTransition::V0(transition) => transition.modified_data_ids(), + } + } + + fn state_transition_protocol_version(&self) -> FeatureVersion { + match self { + UnshieldTransition::V0(_) => 0, + } + } + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + match self { + UnshieldTransition::V0(transition) => transition.state_transition_type(), + } + } + + fn unique_identifiers(&self) -> Vec { + match self { + UnshieldTransition::V0(transition) => transition.unique_identifiers(), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs new file mode 100644 index 00000000000..bfc5be90735 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/state_transition_validation.rs @@ -0,0 +1,15 @@ +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for UnshieldTransition { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + match self { + UnshieldTransition::V0(v0) => v0.validate_structure(platform_version), + } + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs new file mode 100644 index 00000000000..08544d5752a --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/mod.rs @@ -0,0 +1,90 @@ +mod state_transition_like; +mod state_transition_validation; +mod types; +pub(super) mod v0_methods; +mod version; + +use crate::address_funds::PlatformAddress; +use crate::shielded::SerializedAction; +use crate::ProtocolError; +use bincode::{Decode, Encode}; +use platform_serialization_derive::{PlatformDeserialize, PlatformSerialize, PlatformSignable}; +#[cfg(feature = "state-transition-serde-conversion")] +use serde::{Deserialize, Serialize}; + +#[derive( + Debug, + Clone, + Encode, + Decode, + PlatformSerialize, + PlatformDeserialize, + PlatformSignable, + PartialEq, +)] +#[cfg_attr( + feature = "state-transition-serde-conversion", + derive(Serialize, Deserialize), + serde(rename_all = "camelCase") +)] +#[platform_serialize(unversioned)] +pub struct UnshieldTransitionV0 { + /// Address receiving the unshielded funds + pub output_address: PlatformAddress, + /// Orchard actions (spend-output pairs) + pub actions: Vec, + /// Total credits leaving the shielded pool (recipient amount + fee) + pub unshielding_amount: u64, + /// Sinsemilla root of the note commitment tree (Orchard Anchor) + pub anchor: [u8; 32], + /// Halo2 proof bytes + pub proof: Vec, + /// RedPallas binding signature + #[cfg_attr( + feature = "state-transition-serde-conversion", + serde(with = "crate::serialization::serde_bytes_64") + )] + pub binding_signature: [u8; 64], +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::address_funds::PlatformAddress; + use crate::serialization::{PlatformDeserializable, PlatformSerializable}; + use std::fmt::Debug; + + fn test_round_trip( + transition: T, + ) where + ::Error: std::fmt::Debug, + { + let serialized = T::serialize_to_bytes(&transition).expect("expected to serialize"); + let deserialized = + T::deserialize_from_bytes(serialized.as_slice()).expect("expected to deserialize"); + assert_eq!(transition, deserialized); + } + + #[test] + fn test_unshield_transition_v0_serialization_round_trip() { + let transition = UnshieldTransitionV0 { + output_address: PlatformAddress::P2pkh([ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + ]), + actions: vec![SerializedAction { + nullifier: [1u8; 32], + rk: [2u8; 32], + cmx: [3u8; 32], + encrypted_note: vec![4u8; 692], + cv_net: [5u8; 32], + spend_auth_sig: [6u8; 64], + }], + unshielding_amount: 1000u64, + anchor: [7u8; 32], + proof: vec![8u8; 100], + binding_signature: [9u8; 64], + }; + + test_round_trip(transition); + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs new file mode 100644 index 00000000000..1bce7f2e56e --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_like.rs @@ -0,0 +1,42 @@ +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::{ + prelude::Identifier, + state_transition::{StateTransitionLike, StateTransitionType}, +}; + +use crate::state_transition::StateTransition; +use crate::state_transition::StateTransitionType::Unshield; +use crate::version::FeatureVersion; + +impl From for StateTransition { + fn from(value: UnshieldTransitionV0) -> Self { + let transition: UnshieldTransition = value.into(); + transition.into() + } +} + +impl StateTransitionLike for UnshieldTransitionV0 { + fn state_transition_protocol_version(&self) -> FeatureVersion { + 0 + } + + /// returns the type of State Transition + fn state_transition_type(&self) -> StateTransitionType { + Unshield + } + + /// Returns IDs of modified data (none for shielded transitions) + fn modified_data_ids(&self) -> Vec { + vec![] + } + + /// For ZK-only transitions, uniqueness comes from nullifiers in the actions. + /// Each nullifier can only be used once, making them natural unique identifiers. + fn unique_identifiers(&self) -> Vec { + self.actions + .iter() + .map(|action| hex::encode(action.nullifier)) + .collect() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs new file mode 100644 index 00000000000..8715b82cd67 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/state_transition_validation.rs @@ -0,0 +1,64 @@ +use crate::consensus::basic::state_transition::ShieldedInvalidValueBalanceError; +use crate::consensus::basic::BasicError; +use crate::state_transition::state_transitions::shielded::common_validation::{ + validate_actions_count, validate_anchor_not_zero, validate_proof_not_empty, +}; +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::StateTransitionStructureValidation; +use crate::validation::SimpleConsensusValidationResult; +use platform_version::version::PlatformVersion; + +impl StateTransitionStructureValidation for UnshieldTransitionV0 { + fn validate_structure( + &self, + platform_version: &PlatformVersion, + ) -> SimpleConsensusValidationResult { + // Actions count must be in [1, max] + let result = validate_actions_count( + &self.actions, + platform_version + .system_limits + .max_shielded_transition_actions, + ); + if !result.is_valid() { + return result; + } + + // unshielding_amount must be positive and within i64::MAX + if self.unshielding_amount == 0 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "unshield unshielding_amount must be positive".to_string(), + ), + ) + .into(), + ); + } + + if self.unshielding_amount > i64::MAX as u64 { + return SimpleConsensusValidationResult::new_with_error( + BasicError::ShieldedInvalidValueBalanceError( + ShieldedInvalidValueBalanceError::new( + "unshield unshielding_amount exceeds maximum allowed value".to_string(), + ), + ) + .into(), + ); + } + + // Proof must not be empty + let result = validate_proof_not_empty(&self.proof); + if !result.is_valid() { + return result; + } + + // Anchor must not be all zeros + let result = validate_anchor_not_zero(&self.anchor); + if !result.is_valid() { + return result; + } + + SimpleConsensusValidationResult::new() + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs new file mode 100644 index 00000000000..928054d5a4f --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/types.rs @@ -0,0 +1,16 @@ +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::StateTransitionFieldTypes; + +impl StateTransitionFieldTypes for UnshieldTransitionV0 { + fn signature_property_paths() -> Vec<&'static str> { + vec![] + } + + fn identifiers_property_paths() -> Vec<&'static str> { + vec![] + } + + fn binary_property_paths() -> Vec<&'static str> { + vec![] + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs new file mode 100644 index 00000000000..8c8701e47c9 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/v0_methods.rs @@ -0,0 +1,33 @@ +#[cfg(feature = "state-transition-signing")] +use crate::address_funds::PlatformAddress; +#[cfg(feature = "state-transition-signing")] +use crate::shielded::SerializedAction; +use crate::state_transition::unshield_transition::methods::UnshieldTransitionMethodsV0; +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +#[cfg(feature = "state-transition-signing")] +use crate::{state_transition::StateTransition, ProtocolError}; +#[cfg(feature = "state-transition-signing")] +use platform_version::version::PlatformVersion; + +impl UnshieldTransitionMethodsV0 for UnshieldTransitionV0 { + #[cfg(feature = "state-transition-signing")] + fn try_from_bundle( + output_address: PlatformAddress, + actions: Vec, + unshielding_amount: u64, + anchor: [u8; 32], + proof: Vec, + binding_signature: [u8; 64], + _platform_version: &PlatformVersion, + ) -> Result { + let transition = UnshieldTransitionV0 { + output_address, + actions, + unshielding_amount, + anchor, + proof, + binding_signature, + }; + Ok(transition.into()) + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs new file mode 100644 index 00000000000..540c8bc9e45 --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/v0/version.rs @@ -0,0 +1,9 @@ +use crate::state_transition::unshield_transition::v0::UnshieldTransitionV0; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for UnshieldTransitionV0 { + fn feature_version(&self) -> FeatureVersion { + 0 + } +} diff --git a/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs new file mode 100644 index 00000000000..83c92c77caa --- /dev/null +++ b/packages/rs-dpp/src/state_transition/state_transitions/shielded/unshield_transition/version.rs @@ -0,0 +1,11 @@ +use crate::state_transition::unshield_transition::UnshieldTransition; +use crate::state_transition::FeatureVersioned; +use crate::version::FeatureVersion; + +impl FeatureVersioned for UnshieldTransition { + fn feature_version(&self) -> FeatureVersion { + match self { + UnshieldTransition::V0(v0) => v0.feature_version(), + } + } +} diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs index e614e861e1d..21ce47e8173 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_balances_and_nonces.rs @@ -175,6 +175,13 @@ impl StateTransitionAddressBalancesAndNoncesValidation for StateTransition { | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -234,6 +241,13 @@ impl StateTransitionAddressBalancesAndNoncesValidation for StateTransition { | StateTransition::IdentityCreditTransferToAddresses(_) => { Ok(ConsensusValidationResult::new_with_data(BTreeMap::new())) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs index 8aede4de054..4ba9f70cdd1 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/address_witnesses.rs @@ -81,6 +81,13 @@ impl StateTransitionAddressWitnessValidationV0 for StateTransition { | StateTransition::IdentityCreditTransferToAddresses(_) => { return Ok(SimpleConsensusValidationResult::new()); } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; // Add operations to execution context for fee calculation @@ -191,6 +198,13 @@ impl StateTransitionHasAddressWitnessValidationV0 for StateTransition { | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(has_address_witness_validation) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs index c76b79953d5..589c90bd428 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/addresses_minimum_balance.rs @@ -72,6 +72,13 @@ impl StateTransitionAddressesMinimumBalanceValidationV0 for StateTransition { | StateTransition::MasternodeVote(_) => { return Ok(SimpleConsensusValidationResult::new()); } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }?; // Convert ConsensusValidationResult to SimpleConsensusValidationResult @@ -97,6 +104,13 @@ impl StateTransitionAddressesMinimumBalanceValidationV0 for StateTransition { | StateTransition::Batch(_) | StateTransition::MasternodeVote(_) | StateTransition::AddressFundingFromAssetLock(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs index b9d1c491e7a..0eefb307c9b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/basic_structure.rs @@ -237,6 +237,13 @@ impl StateTransitionBasicStructureValidationV0 for StateTransition { })), } } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } fn has_basic_structure_validation(&self, platform_version: &PlatformVersion) -> bool { @@ -274,6 +281,13 @@ impl StateTransitionBasicStructureValidationV0 for StateTransition { | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => true, StateTransition::MasternodeVote(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs index e9bd2e6dd34..d9bab90f70b 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_balance.rs @@ -79,6 +79,13 @@ impl StateTransitionIdentityBalanceValidationV0 for StateTransition { | StateTransition::AddressCreditWithdrawal(_) => { Ok(SimpleConsensusValidationResult::new()) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs index e7854d46439..289f087f664 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_based_signature.rs @@ -131,6 +131,13 @@ impl StateTransitionIdentityBasedSignatureValidationV0 for StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => Ok(ConsensusValidationResult::new()), + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -176,6 +183,13 @@ impl StateTransitionIdentityBasedSignatureValidationV0 for StateTransition { | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) | StateTransition::IdentityTopUpFromAddresses(_) => true, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -197,6 +211,13 @@ impl StateTransitionIdentityBasedSignatureValidationV0 for StateTransition { | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) | StateTransition::IdentityCreditTransferToAddresses(_) => true, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs index d54e789757e..2803fda3947 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/identity_nonces.rs @@ -115,6 +115,13 @@ impl StateTransitionIdentityNonceValidationV0 for StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::IdentityCreate(_) | StateTransition::IdentityTopUp(_) => Ok(SimpleConsensusValidationResult::new()), + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } @@ -161,6 +168,13 @@ impl StateTransitionHasIdentityNonceValidationV0 for StateTransition { | StateTransition::AddressFundsTransfer(_) | StateTransition::AddressFundingFromAssetLock(_) | StateTransition::AddressCreditWithdrawal(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(has_nonce_validation) diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs index 935b725bca3..22af15ae8f0 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/is_allowed.rs @@ -38,6 +38,13 @@ impl StateTransitionIsAllowedValidationV0 for StateTransition { | StateTransition::IdentityUpdate(_) | StateTransition::IdentityCreditTransfer(_) | StateTransition::MasternodeVote(_) => Ok(false), + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs index f9d176acc19..5c426029c23 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/processor/traits/state.rs @@ -185,6 +185,13 @@ impl StateTransitionStateValidation for StateTransition { "address credit withdrawal should not have state validation", ))) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } @@ -205,6 +212,13 @@ impl StateTransitionStateValidation for StateTransition { | StateTransition::IdentityCreditWithdrawal(_) | StateTransition::AddressCreditWithdrawal(_) | StateTransition::IdentityCreditTransferToAddresses(_) => false, + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs b/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs index 3ea8b849437..f8b8d7e0e75 100644 --- a/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs +++ b/packages/rs-drive-abci/src/execution/validation/state_transition/transformer/mod.rs @@ -229,6 +229,13 @@ impl StateTransitionActionTransformer for StateTransition { remaining_address_input_balances.clone(), ) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } } } } diff --git a/packages/rs-drive/Cargo.toml b/packages/rs-drive/Cargo.toml index 2fd0676d080..42f2c41c5aa 100644 --- a/packages/rs-drive/Cargo.toml +++ b/packages/rs-drive/Cargo.toml @@ -52,12 +52,12 @@ enum-map = { version = "2.0.3", optional = true } intmap = { version = "3.0.1", features = ["serde"], optional = true } chrono = { version = "0.4.35", optional = true } itertools = { version = "0.13", optional = true } -grovedb = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897", optional = true, default-features = false } -grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897", optional = true } -grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } -grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897", optional = true } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } -grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } +grovedb = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true, default-features = false } +grovedb-costs = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } +grovedb-path = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } +grovedb-storage = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b", optional = true } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } +grovedb-epoch-based-storage-flags = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } [dev-dependencies] criterion = "0.5" diff --git a/packages/rs-drive/src/drive/credit_pools/mod.rs b/packages/rs-drive/src/drive/credit_pools/mod.rs index e43660706d0..6d48db1634b 100644 --- a/packages/rs-drive/src/drive/credit_pools/mod.rs +++ b/packages/rs-drive/src/drive/credit_pools/mod.rs @@ -260,7 +260,10 @@ mod tests { assert_eq!(batch.len(), TO_EPOCH_INDEX as usize); for (i, operation) in batch.into_iter().enumerate() { - assert_eq!(operation.key.get_key(), KEY_POOL_STORAGE_FEES); + assert_eq!( + operation.key.expect("key should be present").get_key(), + KEY_POOL_STORAGE_FEES + ); assert_eq!( operation.path.to_path(), diff --git a/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs b/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs index 146333b63ce..14107e8d863 100644 --- a/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs +++ b/packages/rs-drive/src/drive/credit_pools/pending_epoch_refunds/methods/add_delete_pending_epoch_refunds_except_specified/v0/mod.rs @@ -128,7 +128,7 @@ mod tests { assert_eq!(operation.path.to_path(), pending_epoch_refunds_path_vec()); - let epoch_index_key = operation.key.get_key(); + let epoch_index_key = operation.key.expect("key should be present").get_key(); let epoch_index = u16::from_be_bytes( epoch_index_key .try_into() diff --git a/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs b/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs index 55fc9ba8013..a8b2a949f83 100644 --- a/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/contract_info/keys/add_potential_contract_info_for_contract_bounded_key/v0/mod.rs @@ -142,6 +142,7 @@ impl Drive { storage_cost: Default::default(), storage_loaded_bytes: 100, hash_node_calls: 0, + sinsemilla_hash_calls: 0, }, )); None diff --git a/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs b/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs index 25f20f62385..eddd0c9f029 100644 --- a/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs +++ b/packages/rs-drive/src/drive/identity/contract_info/keys/refresh_potential_contract_info_key_references/v0/mod.rs @@ -113,6 +113,7 @@ impl Drive { storage_cost: Default::default(), storage_loaded_bytes: 100, hash_node_calls: 0, + sinsemilla_hash_calls: 0, }, )); None diff --git a/packages/rs-drive/src/drive/initialization/mod.rs b/packages/rs-drive/src/drive/initialization/mod.rs index d9d8add1817..f08df6e01c3 100644 --- a/packages/rs-drive/src/drive/initialization/mod.rs +++ b/packages/rs-drive/src/drive/initialization/mod.rs @@ -4,6 +4,7 @@ mod genesis_core_height; mod v0; mod v1; mod v2; +mod v3; use crate::drive::Drive; use crate::error::drive::DriveError; @@ -28,9 +29,10 @@ impl Drive { 0 => self.create_initial_state_structure_v0(transaction, platform_version), 1 => self.create_initial_state_structure_v1(transaction, platform_version), 2 => self.create_initial_state_structure_v2(transaction, platform_version), + 3 => self.create_initial_state_structure_v3(transaction, platform_version), version => Err(Error::Drive(DriveError::UnknownVersionMismatch { method: "create_initial_state_structure".to_string(), - known_versions: vec![0, 1, 2], + known_versions: vec![0, 1, 2, 3], received: version, })), } diff --git a/packages/rs-drive/src/drive/initialization/v3/mod.rs b/packages/rs-drive/src/drive/initialization/v3/mod.rs new file mode 100644 index 00000000000..c53a438148d --- /dev/null +++ b/packages/rs-drive/src/drive/initialization/v3/mod.rs @@ -0,0 +1,23 @@ +//! Drive Initialization v3 — adds shielded pool trees. + +use crate::drive::Drive; +use crate::error::Error; +use dpp::version::PlatformVersion; +use grovedb::TransactionArg; + +impl Drive { + /// Creates the initial state structure (v3). + /// + /// Extends v2 by initializing shielded pool trees (commitment tree, + /// nullifier set, anchor history). + pub(super) fn create_initial_state_structure_v3( + &self, + transaction: TransactionArg, + platform_version: &PlatformVersion, + ) -> Result<(), Error> { + // v3 currently delegates to v2. + // Shielded pool tree initialization will be added here once the + // RootTree variants for the shielded pool are defined. + self.create_initial_state_structure_v2(transaction, platform_version) + } +} diff --git a/packages/rs-drive/src/fees/op.rs b/packages/rs-drive/src/fees/op.rs index 9364d8f473b..7406591e031 100644 --- a/packages/rs-drive/src/fees/op.rs +++ b/packages/rs-drive/src/fees/op.rs @@ -579,6 +579,7 @@ impl LowLevelDriveOperationTreeTypeConverter for TreeType { TreeType::ProvableCountSumTree => { Element::empty_provable_count_sum_tree_with_flags(element_flags) } + _ => todo!("new tree types not yet implemented"), }; LowLevelDriveOperation::insert_for_known_path_key_element(path, key, element) @@ -599,6 +600,7 @@ impl DriveCost for OperationCost { storage_cost, storage_loaded_bytes, hash_node_calls, + sinsemilla_hash_calls: _, } = self; let epoch_cost_for_processing_credit_per_byte = fee_version.storage.storage_processing_credit_per_byte; diff --git a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs index 6f3837e0201..97d4647c957 100644 --- a/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs +++ b/packages/rs-drive/src/prove/prove_state_transition/v0/mod.rs @@ -301,6 +301,13 @@ impl Drive { .chain(st.output().into_iter().map(|(address, _)| address)); Drive::balances_for_clear_addresses_query(addresses_to_check) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in prove_state_transition") + } }; let proof = self.grove_get_proved_path_query( diff --git a/packages/rs-drive/src/query/mod.rs b/packages/rs-drive/src/query/mod.rs index ef57daaa180..f3e29653687 100644 --- a/packages/rs-drive/src/query/mod.rs +++ b/packages/rs-drive/src/query/mod.rs @@ -2781,7 +2781,7 @@ mod tests { .construct_path_query(None, platform_version) .expect("expected to create path query"); - assert_eq!(path_query.to_string(), "PathQuery { path: [@, 0x1da29f488023e306ff9a680bc9837153fb0778c8ee9c934a87dc0de1d69abd3c, 0x01, domain, 0x7265636f7264732e6964656e74697479], query: SizedQuery { query: Query {\n items: [\n RangeTo(.. 8dc201fd7ad7905f8a84d66218e2b387daea7fe4739ae0e21e8c3ee755e6a2c0),\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: [00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n} },\n conditional_subquery_branches: {\n Key(): SubqueryBranch { subquery_path: [00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n} },\n },\n left_to_right: false,\n}, limit: 6 } }"); + assert_eq!(path_query.to_string(), "PathQuery { path: [@, 0x1da29f488023e306ff9a680bc9837153fb0778c8ee9c934a87dc0de1d69abd3c, 0x01, domain, 0x7265636f7264732e6964656e74697479], query: SizedQuery { query: Query {\n items: [\n RangeTo(.. 0x8dc201fd7ad7905f8a84d66218e2b387daea7fe4739ae0e21e8c3ee755e6a2c0),\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: [0x00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n add_parent_tree_on_subquery: false,\n} },\n conditional_subquery_branches: {\n Key(): SubqueryBranch { subquery_path: [0x00], subquery: Query {\n items: [\n RangeFull,\n ],\n default_subquery_branch: SubqueryBranch { subquery_path: None subquery: None },\n left_to_right: false,\n add_parent_tree_on_subquery: false,\n} },\n },\n left_to_right: false,\n add_parent_tree_on_subquery: false,\n}, limit: 6 } }"); // Serialize the PathQuery to a Vec let encoded = bincode::encode_to_vec(&path_query, bincode::config::standard()) diff --git a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs index faf068d9ac7..52aec081552 100644 --- a/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs +++ b/packages/rs-drive/src/util/batch/grovedb_op_batch/mod.rs @@ -332,7 +332,11 @@ impl fmt::Display for GroveDbOpBatch { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for op in &self.operations { let (path_string, known_path) = readable_path(&op.path); - let (key_string, _) = readable_key_info(known_path, &op.key); + let (key_string, _) = if let Some(ref key) = op.key { + readable_key_info(known_path, key) + } else { + ("None".to_string(), None) + }; writeln!(f, "{{")?; writeln!(f, " Path: {}", path_string)?; writeln!(f, " Key: {}", key_string)?; @@ -622,7 +626,7 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { ); self.operations.iter().find_map(|op| { - if op.path == path && op.key == KeyInfo::KnownKey(key.to_vec()) { + if op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec())) { Some(&op.op) } else { None @@ -654,7 +658,7 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { if let Some(index) = self .operations .iter() - .position(|op| op.path == path && op.key == KeyInfo::KnownKey(key.to_vec())) + .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec()))) { Some(self.operations.remove(index).op) } else { @@ -684,7 +688,7 @@ impl GroveDbOpBatchV0Methods for GroveDbOpBatch { if let Some(index) = self .operations .iter() - .position(|op| op.path == path && op.key == KeyInfo::KnownKey(key.to_vec())) + .position(|op| op.path == path && op.key == Some(KeyInfo::KnownKey(key.to_vec()))) { let op = &self.operations[index].op; let op = if matches!( diff --git a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs index 2b3e5750f39..aa84e0e2646 100644 --- a/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/batch_insert_empty_tree_if_not_exists/v0/mod.rs @@ -10,6 +10,7 @@ use crate::util::object_size_info::PathKeyInfo::{ }; use crate::util::storage_flags::StorageFlags; use dpp::version::drive_versions::DriveVersion; +use grovedb::batch::key_info::KeyInfo; use grovedb::batch::GroveOp; use grovedb::{TransactionArg, TreeType}; @@ -49,7 +50,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { @@ -116,7 +117,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { @@ -181,7 +182,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { @@ -246,7 +247,7 @@ impl Drive { found = true; break; } else if let GroveOperation(grove_op) = previous_drive_operation { - if grove_op.key == key + if grove_op.key == Some(KeyInfo::KnownKey(key.to_vec())) && grove_op.path == path && matches!(grove_op.op, GroveOp::DeleteTree(_)) { diff --git a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs index 2cdfa09e3f3..6abf5bdef4b 100644 --- a/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs +++ b/packages/rs-drive/src/util/grove_operations/grove_insert_empty_tree/v0/mod.rs @@ -28,6 +28,7 @@ impl Drive { TreeType::CountSumTree => Element::empty_count_sum_tree(), TreeType::ProvableCountTree => Element::empty_provable_count_tree(), TreeType::ProvableCountSumTree => Element::empty_provable_count_sum_tree(), + _ => todo!("new tree types not yet implemented"), }; let cost_context = self.grove.insert( path, diff --git a/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs b/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs index ff260a535bb..87d7382bd7a 100644 --- a/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs +++ b/packages/rs-drive/src/util/operations/apply_partial_batch_grovedb_operations/v0/mod.rs @@ -52,6 +52,7 @@ impl Drive { }, storage_loaded_bytes: 1, hash_node_calls: 1, + sinsemilla_hash_calls: 0, }, &None, )?; diff --git a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs index 57a3222a97d..fef26e40c80 100644 --- a/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs +++ b/packages/rs-drive/src/verify/address_funds/verify_compacted_address_balance_changes/v0/mod.rs @@ -77,6 +77,7 @@ impl Drive { // Path: SavedBlockTransactions ('$' = 0x24) -> CompactedAddressBalances ('c' = 0x63) let root_layer = match &grovedb_proof { GroveDBProof::V0(v0) => &v0.root_layer, + _ => todo!("GroveDBProof::V1 not yet implemented"), }; let saved_block_key = vec![RootTree::SavedBlockTransactions as u8]; diff --git a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs index e04c54f25ea..4f238162684 100644 --- a/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs +++ b/packages/rs-drive/src/verify/state_transition/verify_state_transition_was_executed_with_proof/v0/mod.rs @@ -1115,6 +1115,13 @@ impl Drive { Ok((root_hash, VerifiedAddressInfos(balances))) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in verify_state_transition_was_executed_with_proof") + } } } } diff --git a/packages/rs-platform-version/Cargo.toml b/packages/rs-platform-version/Cargo.toml index f7b7e5d2e8b..3593be0ef15 100644 --- a/packages/rs-platform-version/Cargo.toml +++ b/packages/rs-platform-version/Cargo.toml @@ -11,7 +11,7 @@ license = "MIT" thiserror = { version = "2.0.12" } bincode = { version = "=2.0.1" } versioned-feature-core = { git = "https://github.com/dashpay/versioned-feature-core", version = "1.0.0" } -grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "33dfd48a1718160cb333fa95424be491785f1897" } +grovedb-version = { git = "https://github.com/dashpay/grovedb", rev = "7ecb8465fad750c7cddd5332adb6f97fcceb498b" } [features] mock-versions = [] diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs index b1a027dc430..5b6a458dc38 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/mod.rs @@ -28,6 +28,11 @@ pub struct DPPStateTransitionSerializationVersions { pub address_funds_transfer_state_transition: FeatureVersionBounds, pub address_funding_from_asset_lock_state_transition: FeatureVersionBounds, pub address_credit_withdrawal_state_transition: FeatureVersionBounds, + pub shield_state_transition: FeatureVersionBounds, + pub shielded_transfer_state_transition: FeatureVersionBounds, + pub unshield_state_transition: FeatureVersionBounds, + pub shield_from_asset_lock_state_transition: FeatureVersionBounds, + pub shielded_withdrawal_state_transition: FeatureVersionBounds, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs index 88cd1d3c661..478f87dee89 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v1.rs @@ -132,4 +132,29 @@ pub const STATE_TRANSITION_SERIALIZATION_VERSIONS_V1: DPPStateTransitionSerializ max_version: 0, default_current_version: 0, }, + shield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_transfer_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + unshield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shield_from_asset_lock_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_withdrawal_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }; diff --git a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs index b3b5292c76d..b2b12cae1af 100644 --- a/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs +++ b/packages/rs-platform-version/src/version/dpp_versions/dpp_state_transition_serialization_versions/v2.rs @@ -132,4 +132,29 @@ pub const STATE_TRANSITION_SERIALIZATION_VERSIONS_V2: DPPStateTransitionSerializ max_version: 0, default_current_version: 0, }, + shield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_transfer_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + unshield_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shield_from_asset_lock_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + shielded_withdrawal_state_transition: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs index ea5c2dc17e7..e6b418f8d44 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/mod.rs @@ -134,6 +134,7 @@ pub struct DriveAbciBlockEndMethodVersions { pub validator_set_update: FeatureVersion, pub should_checkpoint: OptionalFeatureVersion, pub update_checkpoints: OptionalFeatureVersion, + pub record_shielded_pool_anchor: OptionalFeatureVersion, } #[derive(Clone, Debug, Default)] @@ -177,4 +178,6 @@ pub struct DriveAbciStateTransitionProcessingMethodVersions { pub validate_fees_of_event: FeatureVersion, pub store_address_balances_to_recent_block_storage: OptionalFeatureVersion, pub cleanup_recent_block_storage_address_balances: OptionalFeatureVersion, + pub store_nullifiers_to_recent_block_storage: OptionalFeatureVersion, + pub cleanup_recent_block_storage_nullifiers: OptionalFeatureVersion, } diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs index 652a29ca66f..b3d426cc8ff 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v1.rs @@ -107,6 +107,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V1: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -121,6 +123,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V1: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 0, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs index ca20542a937..077f5a7eb4e 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v2.rs @@ -108,6 +108,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V2: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -122,6 +124,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V2: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 0, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs index 40d6b8d3a17..74f40dfe23c 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v3.rs @@ -107,6 +107,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V3: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -121,6 +123,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V3: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 1, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs index 87e70731216..ae7233761b9 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v4.rs @@ -107,6 +107,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -121,6 +123,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V4: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs index faf90d1b788..ea35cd1fefb 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v5.rs @@ -111,6 +111,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V5: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -125,6 +127,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V5: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs index ed161b042c7..0e8a9e55068 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v6.rs @@ -109,6 +109,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V6: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -123,6 +125,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V6: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs index 718555743ab..72ce4a00251 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_method_versions/v7.rs @@ -108,6 +108,8 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V7: DriveAbciMethodVersions = DriveAbciMeth validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: Some(0), // changed cleanup_recent_block_storage_address_balances: Some(0), // cleanup enabled when store is enabled + store_nullifiers_to_recent_block_storage: Some(0), + cleanup_recent_block_storage_nullifiers: Some(0), }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -122,6 +124,7 @@ pub const DRIVE_ABCI_METHOD_VERSIONS_V7: DriveAbciMethodVersions = DriveAbciMeth validator_set_update: 2, should_checkpoint: Some(0), update_checkpoints: Some(0), + record_shielded_pool_anchor: Some(0), }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs index df96bd63440..20b93183e74 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/mod.rs @@ -17,6 +17,7 @@ pub struct DriveAbciQueryVersions { pub system: DriveAbciQuerySystemVersions, pub group_queries: DriveAbciQueryGroupVersions, pub address_funds_queries: DriveAbciQueryAddressFundsVersions, + pub shielded_queries: DriveAbciQueryShieldedVersions, } #[derive(Clone, Debug, Default)] @@ -92,6 +93,21 @@ pub struct DriveAbciQueryDataContractVersions { pub data_contracts: FeatureVersionBounds, } +#[derive(Clone, Debug, Default)] +pub struct DriveAbciQueryShieldedVersions { + pub encrypted_notes: FeatureVersionBounds, + pub anchors: FeatureVersionBounds, + pub pool_state: FeatureVersionBounds, + pub nullifiers: FeatureVersionBounds, + pub nullifiers_trunk_state: FeatureVersionBounds, + pub nullifiers_branch_state: FeatureVersionBounds, + pub recent_nullifier_changes: FeatureVersionBounds, + pub recent_compacted_nullifier_changes: FeatureVersionBounds, + /// Maximum number of encrypted notes returned per query. + /// Should match the BulkAppendTree buffer capacity (2^chunk_power). + pub max_encrypted_notes_per_query: u16, +} + #[derive(Clone, Debug, Default)] pub struct DriveAbciQuerySystemVersions { pub version_upgrade_state: FeatureVersionBounds, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs index 9a9652733fa..3554ff647be 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_query_versions/v1.rs @@ -1,9 +1,9 @@ use crate::version::drive_abci_versions::drive_abci_query_versions::{ DriveAbciQueryAddressFundsVersions, DriveAbciQueryDataContractVersions, DriveAbciQueryGroupVersions, DriveAbciQueryIdentityVersions, - DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQuerySystemVersions, - DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, DriveAbciQueryVersions, - DriveAbciQueryVotingVersions, + DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQueryShieldedVersions, + DriveAbciQuerySystemVersions, DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, + DriveAbciQueryVersions, DriveAbciQueryVotingVersions, }; use versioned_feature_core::FeatureVersionBounds; @@ -247,6 +247,49 @@ pub const DRIVE_ABCI_QUERY_VERSIONS_V1: DriveAbciQueryVersions = DriveAbciQueryV default_current_version: 0, }, }, + shielded_queries: DriveAbciQueryShieldedVersions { + encrypted_notes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + anchors: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + pool_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_trunk_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_branch_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_compacted_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + max_encrypted_notes_per_query: 2048, + }, address_funds_queries: DriveAbciQueryAddressFundsVersions { addresses_infos: FeatureVersionBounds { min_version: 0, diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs index fc8364f8201..24e6f1cf8cc 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/mod.rs @@ -15,6 +15,8 @@ pub struct DriveAbciValidationVersions { pub has_nonce_validation: FeatureVersion, pub has_address_witness_validation: FeatureVersion, pub validate_address_witnesses: FeatureVersion, + pub validate_shielded_proof: FeatureVersion, + pub validate_minimum_shielded_fee: FeatureVersion, pub process_state_transition: FeatureVersion, pub state_transition_to_execution_event_for_check_tx: FeatureVersion, pub penalties: PenaltyAmounts, @@ -25,6 +27,16 @@ pub struct DriveAbciValidationVersions { pub struct DriveAbciValidationConstants { pub maximum_vote_polls_to_process: u16, pub maximum_contenders_to_consider: u16, + /// Minimum number of encrypted notes in the shielded pool before outgoing + /// transitions (Unshield, ShieldedWithdrawal) are allowed. This ensures a + /// sufficient anonymity set before funds can leave the pool. + pub minimum_pool_notes_for_outgoing: u64, + /// Per-bundle fee (in credits) for Halo 2 ZK proof verification. + /// Benchmarked at ~30x per-action signature verification cost. + pub shielded_proof_verification_fee: u64, + /// Per-action fee (in credits) for processing: RedPallas spend auth signature + /// verification, nullifier duplicate check, and tree insertion. + pub shielded_per_action_processing_fee: u64, } #[derive(Clone, Debug, Default)] @@ -60,6 +72,12 @@ pub struct DriveAbciStateTransitionValidationVersions { pub address_credit_withdrawal: DriveAbciStateTransitionValidationVersion, pub address_funds_from_asset_lock: DriveAbciStateTransitionValidationVersion, pub address_funds_transfer: DriveAbciStateTransitionValidationVersion, + + pub shield_state_transition: DriveAbciStateTransitionValidationVersion, + pub shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion, + pub unshield_state_transition: DriveAbciStateTransitionValidationVersion, + pub shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion, + pub shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion, } #[derive(Clone, Debug, Default)] @@ -84,6 +102,8 @@ pub struct PenaltyAmounts { pub validation_of_added_keys_proof_of_possession_failure: u64, /// Penalty for address funding with insufficient funds for outputs pub address_funds_insufficient_balance: u64, + /// Penalty for submitting a shield transition with an invalid ZK proof + pub shielded_proof_verification_failure: u64, } #[derive(Clone, Copy, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs index 5fe30a0fb84..45dce6261b5 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v1.rs @@ -200,10 +200,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V1: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 0, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -212,9 +254,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V1: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs index e3fa0aa7649..4e6dbad38e7 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v2.rs @@ -200,10 +200,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V2: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 0, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -212,9 +254,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V2: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs index 2fd5cc1d3bd..2d72c6cc872 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v3.rs @@ -200,10 +200,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V3: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 0, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -212,9 +254,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V3: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs index 796f8615dca..3af5abca025 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v4.rs @@ -203,10 +203,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V4: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, // <---- changed this has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -215,9 +257,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V4: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs index c9d4e774c33..8d207dd4a26 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v5.rs @@ -204,10 +204,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V5: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -216,9 +258,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V5: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs index 3b656d6c1e4..e6851d78904 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v6.rs @@ -207,10 +207,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V6: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -219,9 +261,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V6: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs index 5f2ceb620c7..c8900b408b6 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v7.rs @@ -201,10 +201,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V7: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -213,9 +255,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V7: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs index 421541526c8..a9936441ff8 100644 --- a/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs +++ b/packages/rs-platform-version/src/version/drive_abci_versions/drive_abci_validation_versions/v8.rs @@ -205,10 +205,52 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V8: DriveAbciValidationVersions = state: 0, transform_into_action: 0, }, + shield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_transfer_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + unshield_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: Some(0), + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shield_from_asset_lock_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, + shielded_withdrawal_state_transition: DriveAbciStateTransitionValidationVersion { + basic_structure: None, + advanced_structure: None, + identity_signatures: None, + nonce: None, + state: 0, + transform_into_action: 0, + }, }, has_nonce_validation: 1, has_address_witness_validation: 0, validate_address_witnesses: 0, + validate_shielded_proof: 0, + validate_minimum_shielded_fee: 0, process_state_transition: 0, state_transition_to_execution_event_for_check_tx: 0, penalties: PenaltyAmounts { @@ -217,9 +259,13 @@ pub const DRIVE_ABCI_VALIDATION_VERSIONS_V8: DriveAbciValidationVersions = validation_of_added_keys_structure_failure: 10000000, validation_of_added_keys_proof_of_possession_failure: 50000000, address_funds_insufficient_balance: 10000000, + shielded_proof_verification_failure: 50000000, }, event_constants: DriveAbciValidationConstants { maximum_vote_polls_to_process: 2, maximum_contenders_to_consider: 100, + minimum_pool_notes_for_outgoing: 250, + shielded_proof_verification_fee: 100_000_000, + shielded_per_action_processing_fee: 3_000_000, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs index 4766055d3af..0d7a67b58f7 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_group_method_versions/mod.rs @@ -63,3 +63,11 @@ pub struct DriveGroupCostEstimationMethodVersions { pub struct DriveAddressFundsCostEstimationMethodVersions { pub for_address_balance_update: FeatureVersion, } + +#[derive(Clone, Debug, Default)] +pub struct DriveShieldedMethodVersions { + pub prove_nullifiers_trunk_query: FeatureVersion, + pub prove_nullifiers_branch_query: FeatureVersion, + pub nullifiers_query_min_depth: u8, + pub nullifiers_query_max_depth: u8, +} diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs index dd0d2b64b6b..4c0712225c8 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/mod.rs @@ -38,6 +38,8 @@ pub struct DriveGroveBasicMethodVersions { pub grove_get_optional_sum_tree_total_value: FeatureVersion, pub grove_get_raw_optional_item: FeatureVersion, pub grove_get_big_sum_tree_total_value: FeatureVersion, + pub grove_get_proved_path_query_v1: FeatureVersion, + pub grove_commitment_tree_count: FeatureVersion, } #[derive(Clone, Debug, Default)] @@ -60,6 +62,7 @@ pub struct DriveGroveBatchMethodVersions { pub batch_insert_empty_sum_tree: FeatureVersion, pub batch_move: FeatureVersion, pub batch_insert_item_with_sum_item_if_not_exists: FeatureVersion, + pub batch_insert_auto_incremented_items_in_count_tree: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs index f6376001473..2df9f00f055 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_grove_method_versions/v1.rs @@ -31,6 +31,8 @@ pub const DRIVE_GROVE_METHOD_VERSIONS_V1: DriveGroveMethodVersions = DriveGroveM grove_get_optional_sum_tree_total_value: 0, grove_get_raw_optional_item: 0, grove_get_big_sum_tree_total_value: 0, + grove_get_proved_path_query_v1: 0, + grove_commitment_tree_count: 0, }, batch: DriveGroveBatchMethodVersions { batch_insert_empty_tree: 0, @@ -51,6 +53,7 @@ pub const DRIVE_GROVE_METHOD_VERSIONS_V1: DriveGroveMethodVersions = DriveGroveM batch_insert_empty_sum_tree: 0, batch_move: 0, batch_insert_item_with_sum_item_if_not_exists: 0, + batch_insert_auto_incremented_items_in_count_tree: 0, }, apply: DriveGroveApplyMethodVersions { grove_apply_operation: 0, diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs index 5391ca01e1f..9c1ec2942ba 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/mod.rs @@ -48,6 +48,11 @@ pub struct DriveStateTransitionActionConvertToHighLevelOperationsMethodVersions pub address_funds_transfer_transition: FeatureVersion, pub address_credit_withdrawal_transition: FeatureVersion, pub address_funding_from_asset_lock_transition: FeatureVersion, + pub shield_transition: FeatureVersion, + pub shield_from_asset_lock_transition: FeatureVersion, + pub shielded_transfer_transition: FeatureVersion, + pub unshield_transition: FeatureVersion, + pub shielded_withdrawal_transition: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs index 0e1b6602175..0a9930b133b 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v1.rs @@ -49,5 +49,10 @@ pub const DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1: DriveStateTransitionMethodV address_funds_transfer_transition: 0, address_credit_withdrawal_transition: 0, address_funding_from_asset_lock_transition: 0, + shield_transition: 0, + shield_from_asset_lock_transition: 0, + shielded_transfer_transition: 0, + unshield_transition: 0, + shielded_withdrawal_transition: 0, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs index 29e7442262a..7bc67bbbbde 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_state_transition_method_versions/v2.rs @@ -50,5 +50,10 @@ pub const DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2: DriveStateTransitionMethodV address_funds_transfer_transition: 0, address_credit_withdrawal_transition: 0, address_funding_from_asset_lock_transition: 0, + shield_transition: 0, + shield_from_asset_lock_transition: 0, + shielded_transfer_transition: 0, + unshield_transition: 0, + shielded_withdrawal_transition: 0, }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs index 8e979270ce8..a5448463776 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/mod.rs @@ -14,6 +14,19 @@ pub struct DriveVerifyMethodVersions { pub voting: DriveVerifyVoteMethodVersions, pub address_funds: DriveVerifyAddressFundsMethodVersions, pub state_transition: DriveVerifyStateTransitionMethodVersions, + pub shielded: DriveVerifyShieldedMethodVersions, +} + +#[derive(Clone, Debug, Default)] +pub struct DriveVerifyShieldedMethodVersions { + pub verify_shielded_pool_state: FeatureVersion, + pub verify_shielded_anchors: FeatureVersion, + pub verify_shielded_encrypted_notes: FeatureVersion, + pub verify_shielded_nullifiers: FeatureVersion, + pub verify_nullifiers_trunk_query: FeatureVersion, + pub verify_nullifiers_branch_query: FeatureVersion, + pub verify_recent_nullifier_changes: FeatureVersion, + pub verify_compacted_nullifier_changes: FeatureVersion, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs index 7c255e83a78..1ad8c7d77cb 100644 --- a/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/drive_verify_method_versions/v1.rs @@ -2,8 +2,9 @@ use crate::version::drive_versions::drive_verify_method_versions::{ DriveVerifyAddressFundsMethodVersions, DriveVerifyContractMethodVersions, DriveVerifyDocumentMethodVersions, DriveVerifyGroupMethodVersions, DriveVerifyIdentityMethodVersions, DriveVerifyMethodVersions, - DriveVerifySingleDocumentMethodVersions, DriveVerifyStateTransitionMethodVersions, - DriveVerifySystemMethodVersions, DriveVerifyTokenMethodVersions, DriveVerifyVoteMethodVersions, + DriveVerifyShieldedMethodVersions, DriveVerifySingleDocumentMethodVersions, + DriveVerifyStateTransitionMethodVersions, DriveVerifySystemMethodVersions, + DriveVerifyTokenMethodVersions, DriveVerifyVoteMethodVersions, }; pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVerifyMethodVersions { @@ -90,4 +91,14 @@ pub const DRIVE_VERIFY_METHOD_VERSIONS_V1: DriveVerifyMethodVersions = DriveVeri state_transition: DriveVerifyStateTransitionMethodVersions { verify_state_transition_was_executed_with_proof: 0, }, + shielded: DriveVerifyShieldedMethodVersions { + verify_shielded_pool_state: 0, + verify_shielded_anchors: 0, + verify_shielded_encrypted_notes: 0, + verify_shielded_nullifiers: 0, + verify_nullifiers_trunk_query: 0, + verify_nullifiers_branch_query: 0, + verify_recent_nullifier_changes: 0, + verify_compacted_nullifier_changes: 0, + }, }; diff --git a/packages/rs-platform-version/src/version/drive_versions/mod.rs b/packages/rs-platform-version/src/version/drive_versions/mod.rs index e7eba236c55..2f547c3c391 100644 --- a/packages/rs-platform-version/src/version/drive_versions/mod.rs +++ b/packages/rs-platform-version/src/version/drive_versions/mod.rs @@ -1,4 +1,6 @@ -use crate::version::drive_versions::drive_group_method_versions::DriveAddressFundsMethodVersions; +use crate::version::drive_versions::drive_group_method_versions::{ + DriveAddressFundsMethodVersions, DriveShieldedMethodVersions, +}; use crate::version::FeatureVersion; use drive_contract_method_versions::DriveContractMethodVersions; use drive_credit_pool_method_versions::DriveCreditPoolMethodVersions; @@ -31,6 +33,7 @@ pub mod v3; pub mod v4; pub mod v5; pub mod v6; +pub mod v7; #[derive(Clone, Debug, Default)] pub struct DriveVersion { @@ -65,6 +68,7 @@ pub struct DriveMethodVersions { pub platform_state: DrivePlatformStateMethodVersions, pub group: DriveGroupMethodVersions, pub address_funds: DriveAddressFundsMethodVersions, + pub shielded: DriveShieldedMethodVersions, pub saved_block_transactions: DriveSavedBlockTransactionsMethodVersions, } @@ -84,6 +88,14 @@ pub struct DriveSavedBlockTransactionsMethodVersions { pub max_blocks_before_compaction: u16, /// Maximum number of address balance entries before compaction is triggered pub max_addresses_before_compaction: u32, + pub store_nullifiers: FeatureVersion, + pub fetch_nullifiers: FeatureVersion, + pub compact_nullifiers: FeatureVersion, + pub cleanup_expired_nullifiers: FeatureVersion, + /// Maximum number of blocks to store before nullifier compaction is triggered + pub max_blocks_before_nullifier_compaction: u16, + /// Maximum number of nullifier entries before compaction is triggered + pub max_nullifiers_before_compaction: u32, } #[derive(Clone, Debug, Default)] diff --git a/packages/rs-platform-version/src/version/drive_versions/v1.rs b/packages/rs-platform-version/src/version/drive_versions/v1.rs index e6c0a2722db..227c6dbb5ca 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v1.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v1.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V1: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v2.rs b/packages/rs-platform-version/src/version/drive_versions/v2.rs index b8dce7adf7f..203f1afa59f 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v2.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v2.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V2: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v3.rs b/packages/rs-platform-version/src/version/drive_versions/v3.rs index d48d17bc84f..408a49cab6e 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v3.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v3.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V3: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v4.rs b/packages/rs-platform-version/src/version/drive_versions/v4.rs index 510942261b6..4ad9c1ee18c 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v4.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v4.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -103,6 +104,12 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -110,6 +117,12 @@ pub const DRIVE_VERSION_V4: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v5.rs b/packages/rs-platform-version/src/version/drive_versions/v5.rs index 76710f4a9f7..85808b0f126 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v5.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v5.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -105,6 +106,12 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -112,6 +119,12 @@ pub const DRIVE_VERSION_V5: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v6.rs b/packages/rs-platform-version/src/version/drive_versions/v6.rs index 75131aba245..768caeb5580 100644 --- a/packages/rs-platform-version/src/version/drive_versions/v6.rs +++ b/packages/rs-platform-version/src/version/drive_versions/v6.rs @@ -3,6 +3,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v2::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2; @@ -107,6 +108,12 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, @@ -114,6 +121,12 @@ pub const DRIVE_VERSION_V6: DriveVersion = DriveVersion { cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, diff --git a/packages/rs-platform-version/src/version/drive_versions/v7.rs b/packages/rs-platform-version/src/version/drive_versions/v7.rs new file mode 100644 index 00000000000..f6a0abbc499 --- /dev/null +++ b/packages/rs-platform-version/src/version/drive_versions/v7.rs @@ -0,0 +1,131 @@ +use crate::version::drive_versions::drive_address_funds_method_versions::v1::DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_contract_method_versions::v2::DRIVE_CONTRACT_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_document_method_versions::v2::DRIVE_DOCUMENT_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; +use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_state_transition_method_versions::v2::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2; +use crate::version::drive_versions::drive_structure_version::v1::DRIVE_STRUCTURE_V1; +use crate::version::drive_versions::drive_token_method_versions::v1::DRIVE_TOKEN_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_verify_method_versions::v1::DRIVE_VERIFY_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_vote_method_versions::v2::DRIVE_VOTE_METHOD_VERSIONS_V2; +use crate::version::drive_versions::{ + DriveAssetLockMethodVersions, DriveBalancesMethodVersions, DriveBatchOperationsMethodVersion, + DriveEstimatedCostsMethodVersions, DriveFeesMethodVersions, DriveFetchMethodVersions, + DriveInitializationMethodVersions, DriveMethodVersions, DriveOperationsMethodVersion, + DrivePlatformStateMethodVersions, DrivePlatformSystemMethodVersions, + DrivePrefundedSpecializedMethodVersions, DriveProtocolUpgradeVersions, + DriveProveMethodVersions, DriveSavedBlockTransactionsMethodVersions, + DriveSystemEstimationCostsMethodVersions, DriveVersion, +}; +use grovedb_version::version::v2::GROVE_V2; + +/// This was introduced in protocol v12 for shielded transactions. +pub const DRIVE_VERSION_V7: DriveVersion = DriveVersion { + structure: DRIVE_STRUCTURE_V1, + methods: DriveMethodVersions { + initialization: DriveInitializationMethodVersions { + create_initial_state_structure: 3, // changed: adds shielded pool trees (commitment tree, nullifiers, anchors) + }, + credit_pools: CREDIT_POOL_METHOD_VERSIONS_V1, + protocol_upgrade: DriveProtocolUpgradeVersions { + clear_version_information: 0, + fetch_versions_with_counter: 0, + fetch_proved_versions_with_counter: 0, + fetch_validator_version_votes: 0, + fetch_proved_validator_version_votes: 0, + remove_validators_proposed_app_versions: 0, + update_validator_proposed_app_version: 0, + }, + prove: DriveProveMethodVersions { + prove_elements: 0, + prove_multiple_state_transition_results: 0, + prove_state_transition: 0, + }, + balances: DriveBalancesMethodVersions { + add_to_system_credits: 0, + add_to_system_credits_operations: 0, + remove_from_system_credits: 0, + remove_from_system_credits_operations: 0, + calculate_total_credits_balance: 1, + }, + document: DRIVE_DOCUMENT_METHOD_VERSIONS_V2, + vote: DRIVE_VOTE_METHOD_VERSIONS_V2, + contract: DRIVE_CONTRACT_METHOD_VERSIONS_V2, + fees: DriveFeesMethodVersions { calculate_fee: 0 }, + estimated_costs: DriveEstimatedCostsMethodVersions { + add_estimation_costs_for_levels_up_to_contract: 0, + add_estimation_costs_for_levels_up_to_contract_document_type_excluded: 0, + add_estimation_costs_for_contested_document_tree_levels_up_to_contract: 0, + add_estimation_costs_for_contested_document_tree_levels_up_to_contract_document_type_excluded: 0, + }, + asset_lock: DriveAssetLockMethodVersions { + add_asset_lock_outpoint: 0, + add_estimation_costs_for_adding_asset_lock: 0, + fetch_asset_lock_outpoint_info: 0, + }, + verify: DRIVE_VERIFY_METHOD_VERSIONS_V1, + identity: DRIVE_IDENTITY_METHOD_VERSIONS_V1, + token: DRIVE_TOKEN_METHOD_VERSIONS_V1, + platform_system: DrivePlatformSystemMethodVersions { + estimation_costs: DriveSystemEstimationCostsMethodVersions { + for_total_system_credits_update: 0, + }, + }, + operations: DriveOperationsMethodVersion { + rollback_transaction: 0, + drop_cache: 0, + commit_transaction: 0, + apply_partial_batch_low_level_drive_operations: 0, + apply_partial_batch_grovedb_operations: 0, + apply_batch_low_level_drive_operations: 0, + apply_batch_grovedb_operations: 0, + }, + state_transitions: DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V2, + batch_operations: DriveBatchOperationsMethodVersion { + convert_drive_operations_to_grove_operations: 0, + apply_drive_operations: 0, + }, + platform_state: DrivePlatformStateMethodVersions { + fetch_platform_state_bytes: 0, + store_platform_state_bytes: 0, + }, + fetch: DriveFetchMethodVersions { fetch_elements: 0 }, + prefunded_specialized_balances: DrivePrefundedSpecializedMethodVersions { + fetch_single: 0, + prove_single: 0, + add_prefunded_specialized_balance: 0, + add_prefunded_specialized_balance_operations: 1, + deduct_from_prefunded_specialized_balance: 1, + deduct_from_prefunded_specialized_balance_operations: 0, + estimated_cost_for_prefunded_specialized_balance_update: 0, + empty_prefunded_specialized_balance: 0, + }, + group: DRIVE_GROUP_METHOD_VERSIONS_V1, + address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, + saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { + store_address_balances: 0, + fetch_address_balances: 0, + compact_address_balances: 0, + cleanup_expired_address_balances: 0, + max_blocks_before_compaction: 64, + max_addresses_before_compaction: 2048, + store_nullifiers: 0, + fetch_nullifiers: 0, + compact_nullifiers: 0, + cleanup_expired_nullifiers: 0, + max_blocks_before_nullifier_compaction: 64, + max_nullifiers_before_compaction: 2048, + }, + }, + grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, + grove_version: GROVE_V2, +}; diff --git a/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs b/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs index 4607cf71d20..e004582a10e 100644 --- a/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs +++ b/packages/rs-platform-version/src/version/feature_initial_protocol_versions.rs @@ -1,3 +1,4 @@ use crate::version::ProtocolVersion; pub const ADDRESS_FUNDS_INITIAL_PROTOCOL_VERSION: ProtocolVersion = 11; +pub const SHIELDED_POOL_INITIAL_PROTOCOL_VERSION: ProtocolVersion = 12; diff --git a/packages/rs-platform-version/src/version/fee/hashing/mod.rs b/packages/rs-platform-version/src/version/fee/hashing/mod.rs index 8e42863d3bb..0a1c42e535f 100644 --- a/packages/rs-platform-version/src/version/fee/hashing/mod.rs +++ b/packages/rs-platform-version/src/version/fee/hashing/mod.rs @@ -10,6 +10,8 @@ pub struct FeeHashingVersion { pub sha256_ripe_md160_base: u64, pub single_sha256_base: u64, pub ripemd160_per_block: u64, + /// Cost per Sinsemilla hash operation (elliptic curve, ~100x more expensive than Blake3) + pub sinsemilla_base: u64, } #[derive(Clone, Debug, Encode, Decode, Default, PartialEq, Eq)] diff --git a/packages/rs-platform-version/src/version/fee/hashing/v1.rs b/packages/rs-platform-version/src/version/fee/hashing/v1.rs index 7bdd3052142..15ff6967eaf 100644 --- a/packages/rs-platform-version/src/version/fee/hashing/v1.rs +++ b/packages/rs-platform-version/src/version/fee/hashing/v1.rs @@ -7,4 +7,5 @@ pub const FEE_HASHING_VERSION1: FeeHashingVersion = FeeHashingVersion { sha256_per_block: 5000, blake3_per_block: 300, ripemd160_per_block: 5000, // RIPEMD160 has 64-byte blocks, similar cost to SHA256 + sinsemilla_base: 40_000, // Sinsemilla is an elliptic curve hash (~100x Blake3) }; diff --git a/packages/rs-platform-version/src/version/mocks/v2_test.rs b/packages/rs-platform-version/src/version/mocks/v2_test.rs index 05610c204f6..e622afc4e0c 100644 --- a/packages/rs-platform-version/src/version/mocks/v2_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v2_test.rs @@ -19,9 +19,9 @@ use crate::version::drive_abci_versions::drive_abci_method_versions::v1::DRIVE_A use crate::version::drive_abci_versions::drive_abci_query_versions::{ DriveAbciQueryAddressFundsVersions, DriveAbciQueryDataContractVersions, DriveAbciQueryGroupVersions, DriveAbciQueryIdentityVersions, - DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQuerySystemVersions, - DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, DriveAbciQueryVersions, - DriveAbciQueryVotingVersions, + DriveAbciQueryPrefundedSpecializedBalancesVersions, DriveAbciQueryShieldedVersions, + DriveAbciQuerySystemVersions, DriveAbciQueryTokenVersions, DriveAbciQueryValidatorVersions, + DriveAbciQueryVersions, DriveAbciQueryVotingVersions, }; use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIVE_ABCI_STRUCTURE_VERSIONS_V1; use crate::version::drive_abci_versions::drive_abci_validation_versions::v1::DRIVE_ABCI_VALIDATION_VERSIONS_V1; @@ -32,6 +32,7 @@ use crate::version::drive_versions::drive_contract_method_versions::v1::DRIVE_CO use crate::version::drive_versions::drive_credit_pool_method_versions::v1::CREDIT_POOL_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_document_method_versions::v1::DRIVE_DOCUMENT_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_group_method_versions::v1::DRIVE_GROUP_METHOD_VERSIONS_V1; +use crate::version::drive_versions::drive_group_method_versions::DriveShieldedMethodVersions; use crate::version::drive_versions::drive_grove_method_versions::v1::DRIVE_GROVE_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_identity_method_versions::v1::DRIVE_IDENTITY_METHOD_VERSIONS_V1; use crate::version::drive_versions::drive_state_transition_method_versions::v1::DRIVE_STATE_TRANSITION_METHOD_VERSIONS_V1; @@ -141,7 +142,13 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { }, group: DRIVE_GROUP_METHOD_VERSIONS_V1, address_funds: DRIVE_ADDRESS_FUNDS_METHOD_VERSIONS_V1, - saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, compact_address_balances: 0, cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048 }, + shielded: DriveShieldedMethodVersions { + prove_nullifiers_trunk_query: 0, + prove_nullifiers_branch_query: 0, + nullifiers_query_min_depth: 6, + nullifiers_query_max_depth: 10, + }, + saved_block_transactions: DriveSavedBlockTransactionsMethodVersions { store_address_balances: 0, fetch_address_balances: 0, compact_address_balances: 0, cleanup_expired_address_balances: 0, max_blocks_before_compaction: 64, max_addresses_before_compaction: 2048, store_nullifiers: 0, fetch_nullifiers: 0, compact_nullifiers: 0, cleanup_expired_nullifiers: 0, max_blocks_before_nullifier_compaction: 64, max_nullifiers_before_compaction: 2048 }, }, grove_methods: DRIVE_GROVE_METHOD_VERSIONS_V1, grove_version: GROVE_V1, @@ -391,6 +398,49 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { default_current_version: 0, }, }, + shielded_queries: DriveAbciQueryShieldedVersions { + encrypted_notes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + anchors: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + pool_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_trunk_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + nullifiers_branch_state: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + recent_compacted_nullifier_changes: FeatureVersionBounds { + min_version: 0, + max_version: 0, + default_current_version: 0, + }, + max_encrypted_notes_per_query: 2048, + }, address_funds_queries: DriveAbciQueryAddressFundsVersions { addresses_infos: FeatureVersionBounds { min_version: 0, @@ -454,6 +504,7 @@ pub const TEST_PLATFORM_V2: PlatformVersion = PlatformVersion { max_withdrawal_amount: 50_000_000_000_000, max_contract_group_size: 256, max_token_redemption_cycles: 128, + max_shielded_transition_actions: 100, }, consensus: ConsensusVersions { tenderdash_consensus_version: 0, diff --git a/packages/rs-platform-version/src/version/mocks/v3_test.rs b/packages/rs-platform-version/src/version/mocks/v3_test.rs index abb3281b626..4cfc803e1ff 100644 --- a/packages/rs-platform-version/src/version/mocks/v3_test.rs +++ b/packages/rs-platform-version/src/version/mocks/v3_test.rs @@ -142,6 +142,8 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { validate_fees_of_event: 0, store_address_balances_to_recent_block_storage: None, cleanup_recent_block_storage_address_balances: None, + store_nullifiers_to_recent_block_storage: None, + cleanup_recent_block_storage_nullifiers: None, }, epoch: DriveAbciEpochMethodVersions { gather_epoch_info: 0, @@ -156,6 +158,7 @@ pub const TEST_PLATFORM_V3: PlatformVersion = PlatformVersion { validator_set_update: 0, should_checkpoint: None, update_checkpoints: None, + record_shielded_pool_anchor: None, }, platform_state_storage: DriveAbciPlatformStateStorageMethodVersions { fetch_platform_state: 0, diff --git a/packages/rs-platform-version/src/version/system_limits/mod.rs b/packages/rs-platform-version/src/version/system_limits/mod.rs index 35929bac5de..f5531de6762 100644 --- a/packages/rs-platform-version/src/version/system_limits/mod.rs +++ b/packages/rs-platform-version/src/version/system_limits/mod.rs @@ -18,4 +18,5 @@ pub struct SystemLimits { // For other distributions we much calculate at each cycle the rewards, so we don't want to // do this that much pub max_token_redemption_cycles: u32, + pub max_shielded_transition_actions: u16, } diff --git a/packages/rs-platform-version/src/version/system_limits/v1.rs b/packages/rs-platform-version/src/version/system_limits/v1.rs index 2379b62d6c6..9b11657710c 100644 --- a/packages/rs-platform-version/src/version/system_limits/v1.rs +++ b/packages/rs-platform-version/src/version/system_limits/v1.rs @@ -10,4 +10,5 @@ pub const SYSTEM_LIMITS_V1: SystemLimits = SystemLimits { max_withdrawal_amount: 50_000_000_000_000, //500 Dash max_contract_group_size: 256, max_token_redemption_cycles: 128, + max_shielded_transition_actions: 100, }; diff --git a/packages/rs-platform-version/src/version/v12.rs b/packages/rs-platform-version/src/version/v12.rs index eacb417eca5..c14b80facb6 100644 --- a/packages/rs-platform-version/src/version/v12.rs +++ b/packages/rs-platform-version/src/version/v12.rs @@ -21,7 +21,7 @@ use crate::version::drive_abci_versions::drive_abci_structure_versions::v1::DRIV use crate::version::drive_abci_versions::drive_abci_validation_versions::v8::DRIVE_ABCI_VALIDATION_VERSIONS_V8; use crate::version::drive_abci_versions::drive_abci_withdrawal_constants::v2::DRIVE_ABCI_WITHDRAWAL_CONSTANTS_V2; use crate::version::drive_abci_versions::DriveAbciVersion; -use crate::version::drive_versions::v6::DRIVE_VERSION_V6; +use crate::version::drive_versions::v7::DRIVE_VERSION_V7; use crate::version::fee::v2::FEE_VERSION2; use crate::version::protocol_version::PlatformVersion; use crate::version::system_data_contract_versions::v1::SYSTEM_DATA_CONTRACT_VERSIONS_V1; @@ -33,7 +33,7 @@ pub const PROTOCOL_VERSION_12: ProtocolVersion = 12; /// This version is for Platform release 3.1.0 pub const PLATFORM_V12: PlatformVersion = PlatformVersion { protocol_version: PROTOCOL_VERSION_12, - drive: DRIVE_VERSION_V6, + drive: DRIVE_VERSION_V7, // changed: shielded pool (commitment tree, nullifiers, anchors, address funds, sinsemilla hashing) drive_abci: DriveAbciVersion { structs: DRIVE_ABCI_STRUCTURE_VERSIONS_V1, methods: DRIVE_ABCI_METHOD_VERSIONS_V7, diff --git a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs index 47e1399c210..432c4353d08 100644 --- a/packages/rs-sdk-ffi/src/system/queries/path_elements.rs +++ b/packages/rs-sdk-ffi/src/system/queries/path_elements.rs @@ -156,6 +156,12 @@ fn get_path_elements( Element::ProvableCountSumTree(_, count, sum, _) => { format!("provable_count_sum_tree:{}:{}", count, sum) } + Element::CommitmentTree(_, _, _) => "commitment_tree".to_string(), + Element::MmrTree(_, _) => "mmr_tree".to_string(), + Element::BulkAppendTree(_, _, _) => "bulk_append_tree".to_string(), + Element::DenseAppendOnlyFixedSizeTree(_, _, _) => { + "dense_append_only_fixed_size_tree".to_string() + } }; format!( @@ -176,6 +182,11 @@ fn get_path_elements( Element::ProvableCountSumTree(_, _, _, _) => { "provable_count_sum_tree" } + Element::CommitmentTree(_, _, _) => "commitment_tree", + Element::MmrTree(_, _) => "mmr_tree", + Element::BulkAppendTree(_, _, _) => "bulk_append_tree", + Element::DenseAppendOnlyFixedSizeTree(_, _, _) => + "dense_append_only_fixed_size_tree", } ) }) diff --git a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs index 429a94b1a4f..d32586c219f 100644 --- a/packages/wasm-dpp/src/errors/consensus/consensus_error.rs +++ b/packages/wasm-dpp/src/errors/consensus/consensus_error.rs @@ -88,7 +88,12 @@ use dpp::consensus::state::prefunded_specialized_balances::prefunded_specialized use dpp::consensus::state::prefunded_specialized_balances::prefunded_specialized_balance_not_found_error::PrefundedSpecializedBalanceNotFoundError; use dpp::consensus::state::token::{IdentityDoesNotHaveEnoughTokenBalanceError, IdentityTokenAccountNotFrozenError, IdentityTokenAccountFrozenError, TokenIsPausedError, IdentityTokenAccountAlreadyFrozenError, UnauthorizedTokenActionError, TokenSettingMaxSupplyToLessThanCurrentSupplyError, TokenMintPastMaxSupplyError, NewTokensDestinationIdentityDoesNotExistError, NewAuthorizedActionTakerIdentityDoesNotExistError, NewAuthorizedActionTakerGroupDoesNotExistError, NewAuthorizedActionTakerMainGroupNotSetError, InvalidGroupPositionError, TokenAlreadyPausedError, TokenNotPausedError, InvalidTokenClaimPropertyMismatch, InvalidTokenClaimNoCurrentRewards, InvalidTokenClaimWrongClaimant, TokenTransferRecipientIdentityNotExistError, PreProgrammedDistributionTimestampInPastError, IdentityHasNotAgreedToPayRequiredTokenAmountError, RequiredTokenPaymentInfoNotSetError, IdentityTryingToPayWithWrongTokenError, TokenDirectPurchaseUserPriceTooLow, TokenAmountUnderMinimumSaleAmount, TokenNotForDirectSale, InvalidTokenPositionStateError}; use dpp::consensus::state::address_funds::{AddressDoesNotExistError, AddressInvalidNonceError, AddressNotEnoughFundsError, AddressesNotEnoughFundsError}; -use dpp::consensus::basic::state_transition::{StateTransitionNotActiveError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, InputWitnessCountMismatchError, TransitionNoInputsError, TransitionNoOutputsError, FeeStrategyEmptyError, FeeStrategyDuplicateError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, OutputBelowMinimumError, InputOutputBalanceMismatchError, OutputsNotGreaterThanInputsError, WithdrawalBalanceMismatchError, InsufficientFundingAmountError, InputsNotLessThanOutputsError, OutputAddressAlsoInputError, InvalidRemainderOutputCountError, WithdrawalBelowMinAmountError}; +use dpp::consensus::state::shielded::insufficient_pool_notes_error::InsufficientPoolNotesError; +use dpp::consensus::state::shielded::insufficient_shielded_fee_error::InsufficientShieldedFeeError; +use dpp::consensus::state::shielded::invalid_anchor_error::InvalidAnchorError; +use dpp::consensus::state::shielded::invalid_shielded_proof_error::InvalidShieldedProofError; +use dpp::consensus::state::shielded::nullifier_already_spent_error::NullifierAlreadySpentError; +use dpp::consensus::basic::state_transition::{StateTransitionNotActiveError, TransitionOverMaxInputsError, TransitionOverMaxOutputsError, InputWitnessCountMismatchError, TransitionNoInputsError, TransitionNoOutputsError, FeeStrategyEmptyError, FeeStrategyDuplicateError, FeeStrategyIndexOutOfBoundsError, FeeStrategyTooManyStepsError, InputBelowMinimumError, OutputBelowMinimumError, InputOutputBalanceMismatchError, OutputsNotGreaterThanInputsError, WithdrawalBalanceMismatchError, InsufficientFundingAmountError, InputsNotLessThanOutputsError, OutputAddressAlsoInputError, InvalidRemainderOutputCountError, WithdrawalBelowMinAmountError, ShieldedNoActionsError, ShieldedTooManyActionsError, ShieldedEmptyProofError, ShieldedZeroAnchorError, ShieldedInvalidValueBalanceError}; use dpp::consensus::state::voting::masternode_incorrect_voter_identity_id_error::MasternodeIncorrectVoterIdentityIdError; use dpp::consensus::state::voting::masternode_incorrect_voting_address_error::MasternodeIncorrectVotingAddressError; use dpp::consensus::state::voting::masternode_not_found_error::MasternodeNotFoundError; @@ -443,6 +448,19 @@ pub fn from_state_error(state_error: &StateError) -> JsValue { StateError::AddressInvalidNonceError(e) => { generic_consensus_error!(AddressInvalidNonceError, e).into() } + StateError::InvalidAnchorError(e) => generic_consensus_error!(InvalidAnchorError, e).into(), + StateError::NullifierAlreadySpentError(e) => { + generic_consensus_error!(NullifierAlreadySpentError, e).into() + } + StateError::InvalidShieldedProofError(e) => { + generic_consensus_error!(InvalidShieldedProofError, e).into() + } + StateError::InsufficientPoolNotesError(e) => { + generic_consensus_error!(InsufficientPoolNotesError, e).into() + } + StateError::InsufficientShieldedFeeError(e) => { + generic_consensus_error!(InsufficientShieldedFeeError, e).into() + } } } @@ -923,6 +941,21 @@ fn from_basic_error(basic_error: &BasicError) -> JsValue { BasicError::WithdrawalBelowMinAmountError(e) => { generic_consensus_error!(WithdrawalBelowMinAmountError, e).into() } + BasicError::ShieldedNoActionsError(e) => { + generic_consensus_error!(ShieldedNoActionsError, e).into() + } + BasicError::ShieldedTooManyActionsError(e) => { + generic_consensus_error!(ShieldedTooManyActionsError, e).into() + } + BasicError::ShieldedEmptyProofError(e) => { + generic_consensus_error!(ShieldedEmptyProofError, e).into() + } + BasicError::ShieldedZeroAnchorError(e) => { + generic_consensus_error!(ShieldedZeroAnchorError, e).into() + } + BasicError::ShieldedInvalidValueBalanceError(e) => { + generic_consensus_error!(ShieldedInvalidValueBalanceError, e).into() + } } } diff --git a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs index 37fe3d2626f..29d2274dffe 100644 --- a/packages/wasm-dpp/src/identity/state_transition/transition_types.rs +++ b/packages/wasm-dpp/src/identity/state_transition/transition_types.rs @@ -55,6 +55,7 @@ impl From for StateTransitionTypeWasm { StateTransitionType::AddressCreditWithdrawal => { StateTransitionTypeWasm::AddressCreditWithdrawal } + _ => todo!("shielded state transition types not yet implemented in wasm"), } } } diff --git a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs index c140f0e7ea6..2b929d17a15 100644 --- a/packages/wasm-dpp/src/state_transition/state_transition_factory.rs +++ b/packages/wasm-dpp/src/state_transition/state_transition_factory.rs @@ -79,6 +79,13 @@ impl StateTransitionFactoryWasm { StateTransition::AddressCreditWithdrawal(st) => { serde_wasm_bindgen::to_value(&st).map_err(|e| JsValue::from(e.to_string())) } + StateTransition::Shield(_) + | StateTransition::ShieldedTransfer(_) + | StateTransition::Unshield(_) + | StateTransition::ShieldFromAssetLock(_) + | StateTransition::ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented in state_transition_factory") + } }, Err(dpp::ProtocolError::StateTransitionError(e)) => match e { StateTransitionError::InvalidStateTransitionError { diff --git a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs index 2cd140ac40c..ac29a07750d 100644 --- a/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs +++ b/packages/wasm-dpp2/src/state_transitions/base/state_transition.rs @@ -308,6 +308,11 @@ impl StateTransitionWasm { AddressFundsTransfer(_) => 12, AddressFundingFromAssetLock(_) => 13, AddressCreditWithdrawal(_) => 14, + Shield(_) => 15, + ShieldedTransfer(_) => 16, + Unshield(_) => 17, + ShieldFromAssetLock(_) => 18, + ShieldedWithdrawal(_) => 19, } } @@ -392,6 +397,11 @@ impl StateTransitionWasm { | AddressFundsTransfer(_) | AddressFundingFromAssetLock(_) | AddressCreditWithdrawal(_) => None, + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => todo!("shielded transitions not yet implemented"), } } @@ -414,6 +424,11 @@ impl StateTransitionWasm { AddressFundsTransfer(_) | AddressFundingFromAssetLock(_) | AddressCreditWithdrawal(_) => None, + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => todo!("shielded transitions not yet implemented"), } } @@ -551,6 +566,13 @@ impl StateTransitionWasm { "Cannot set owner for address funds transfer transition", )); } + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(()) @@ -619,6 +641,13 @@ impl StateTransitionWasm { "Cannot set identity contract nonce for address-related transition types", )); } + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(()) @@ -707,6 +736,13 @@ impl StateTransitionWasm { "Cannot set identity nonce for address-related transition types", )); } + Shield(_) + | ShieldedTransfer(_) + | Unshield(_) + | ShieldFromAssetLock(_) + | ShieldedWithdrawal(_) => { + todo!("shielded transitions not yet implemented") + } }; Ok(()) diff --git a/packages/wasm-dpp2/src/state_transitions/proof_result.rs b/packages/wasm-dpp2/src/state_transitions/proof_result.rs index ec8d2011634..afc3338f181 100644 --- a/packages/wasm-dpp2/src/state_transitions/proof_result.rs +++ b/packages/wasm-dpp2/src/state_transitions/proof_result.rs @@ -894,6 +894,13 @@ pub fn convert_proof_result( } .into() } + + StateTransitionProofResult::VerifiedAssetLockConsumed(_) + | StateTransitionProofResult::VerifiedShieldedNullifiers(_) + | StateTransitionProofResult::VerifiedShieldedNullifiersWithAddressInfos(_, _) + | StateTransitionProofResult::VerifiedShieldedNullifiersWithWithdrawalDocument(_, _) => { + todo!("shielded proof results not yet implemented in wasm") + } }; Ok(js_value.into())