diff --git a/.changelog/unreleased/improvements/1093-signable-txs.md b/.changelog/unreleased/improvements/1093-signable-txs.md new file mode 100644 index 0000000000..e1e244bd0f --- /dev/null +++ b/.changelog/unreleased/improvements/1093-signable-txs.md @@ -0,0 +1,2 @@ +- Make Namada transactions signable on hardware constrained wallets by making + them smaller. ([#1093](https://github.com/anoma/namada/pull/1093)) \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 68075542f3..d9beb3f693 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -19,10 +19,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array 0.14.7", ] @@ -33,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", "opaque-debug 0.3.0", ] @@ -586,25 +587,36 @@ dependencies = [ "blake2s_simd 0.5.11", "byteorder", "crossbeam-channel 0.5.8", - "ff", - "group", + "ff 0.11.1", + "group 0.11.0", "lazy_static", "log 0.4.17", "num_cpus", - "pairing", + "pairing 0.21.0", "rand_core 0.6.4", "rayon", "subtle", ] [[package]] -name = "bigint" -version = "4.4.3" +name = "bellman" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807" dependencies = [ + "bitvec 1.0.1", + "blake2s_simd 1.0.1", "byteorder", - "crunchy 0.1.6", + "crossbeam-channel 0.5.8", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "log 0.4.17", + "num_cpus", + "pairing 0.22.0", + "rand_core 0.6.4", + "rayon", + "subtle", ] [[package]] @@ -683,7 +695,7 @@ checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32 0.9.1", "bitcoin_hashes", - "secp256k1 0.24.3", + "secp256k1", "serde 1.0.163", ] @@ -745,17 +757,6 @@ dependencies = [ "cty", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq 0.1.5", -] - [[package]] name = "blake2b_simd" version = "1.0.1" @@ -840,7 +841,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding 0.2.1", - "cipher", + "cipher 0.3.0", ] [[package]] @@ -879,9 +880,22 @@ version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ - "ff", - "group", - "pairing", + "ff 0.11.1", + "group 0.11.0", + "pairing 0.21.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", "rand_core 0.6.4", "subtle", ] @@ -1152,20 +1166,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", - "zeroize", ] [[package]] -name = "chacha20poly1305" +name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", - "cipher", + "chacha20 0.9.1", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -1197,6 +1221,17 @@ dependencies = [ "generic-array 0.14.7", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "circular-queue" version = "0.2.6" @@ -1515,12 +1550,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crunchy" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" - [[package]] name = "crunchy" version = "0.2.2" @@ -1569,21 +1598,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto_api" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" - -[[package]] -name = "crypto_api_chachapoly" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" -dependencies = [ - "crypto_api", -] - [[package]] name = "ct-codecs" version = "1.1.1" @@ -1935,9 +1949,9 @@ dependencies = [ "base16ct", "crypto-bigint", "der", - "ff", + "ff 0.11.1", "generic-array 0.14.7", - "group", + "group 0.11.0", "rand_core 0.6.4", "sec1", "subtle", @@ -1994,15 +2008,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "equihash" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "blake2b_simd 1.0.1", - "byteorder", -] - [[package]] name = "erased-serde" version = "0.3.25" @@ -2118,7 +2123,7 @@ dependencies = [ "ark-std", "bincode", "blake2", - "blake2b_simd 1.0.1", + "blake2b_simd", "borsh 0.9.4", "digest 0.10.6", "ed25519-dalek", @@ -2164,6 +2169,17 @@ dependencies = [ "subtle", ] +[[package]] +name = "ff" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" +dependencies = [ + "bitvec 1.0.1", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "file-lock" version = "2.1.9" @@ -2272,7 +2288,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm", "num-bigint", "num-integer", @@ -2465,8 +2481,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" dependencies = [ "cfg-if 1.0.0", + "js-sys", "libc", "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", ] [[package]] @@ -2526,7 +2544,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc5ac374b108929de78460075f3dc439fa66df9d8fc77e8f12caa5165fcf0c89" dependencies = [ "byteorder", - "ff", + "ff 0.11.1", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "group" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" +dependencies = [ + "ff 0.12.1", + "memuse", "rand_core 0.6.4", "subtle", ] @@ -2543,8 +2573,8 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "blake2b_simd 1.0.1", - "chacha20", + "blake2b_simd", + "chacha20 0.8.2", "hex", "itertools", "miracl_core", @@ -2600,20 +2630,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "halo2" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" -dependencies = [ - "blake2b_simd 0.5.11", - "ff", - "group", - "pasta_curves", - "rand 0.8.5", - "rayon", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -3070,7 +3086,7 @@ dependencies = [ "regex", "retry", "ripemd", - "secp256k1 0.24.3", + "secp256k1", "semver 1.0.17", "serde 1.0.163", "serde_derive", @@ -3197,9 +3213,9 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" dependencies = [ "serde 1.0.163", ] @@ -3230,6 +3246,15 @@ dependencies = [ "serde 1.0.163", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array 0.14.7", +] + [[package]] name = "input_buffer" version = "0.4.0" @@ -3312,14 +3337,14 @@ dependencies = [ [[package]] name = "jubjub" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" dependencies = [ - "bitvec 0.22.3", - "bls12_381", - "ff", - "group", + "bitvec 1.0.1", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", "rand_core 0.6.4", "subtle", ] @@ -3477,7 +3502,7 @@ name = "libsecp256k1-core" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "crunchy 0.2.2", + "crunchy", "digest 0.9.0", "subtle", ] @@ -3621,60 +3646,69 @@ dependencies = [ "serde_yaml", ] +[[package]] +name = "masp_note_encryption" +version = "0.2.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +dependencies = [ + "borsh 0.9.4", + "chacha20 0.9.1", + "chacha20poly1305", + "cipher 0.4.4", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "masp_primitives" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ "aes", "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", + "bitvec 1.0.1", + "blake2b_simd", "blake2s_simd 1.0.1", - "bls12_381", + "bls12_381 0.7.1", "borsh 0.9.4", "byteorder", - "chacha20poly1305", - "crypto_api_chachapoly", - "ff", + "ff 0.12.1", "fpe", - "group", + "group 0.12.1", "hex", "incrementalmerkletree", "jubjub", "lazy_static", + "masp_note_encryption", + "memuse", + "nonempty", "rand 0.8.5", "rand_core 0.6.4", - "ripemd160", - "secp256k1 0.20.3", - "serde 1.0.163", "sha2 0.9.9", "subtle", "zcash_encoding", - "zcash_primitives", ] [[package]] name = "masp_proofs" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", + "bellman 0.13.1", + "blake2b_simd", + "bls12_381 0.7.1", "directories", - "ff", - "group", + "getrandom 0.2.9", + "group 0.12.1", "itertools", "jubjub", "lazy_static", "masp_primitives", "minreq", "rand_core 0.6.4", + "redjubjub", + "tracing 0.1.37", "wagyu-zcash-parameters", - "zcash_primitives", - "zcash_proofs", ] [[package]] @@ -3752,9 +3786,6 @@ name = "memuse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" -dependencies = [ - "nonempty", -] [[package]] name = "merlin" @@ -3921,9 +3952,9 @@ dependencies = [ "assert_matches", "async-std", "async-trait", - "bellman", + "bellman 0.11.2", "bimap", - "bls12_381", + "bls12_381 0.6.1", "borsh 0.9.4", "byte-unit", "circular-queue", @@ -3952,6 +3983,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "ripemd", "rust_decimal", "rust_decimal_macros", "serde 1.0.163", @@ -4029,6 +4061,7 @@ dependencies = [ "rayon", "regex", "reqwest", + "ripemd", "rlimit", "rocksdb", "rpassword", @@ -4072,7 +4105,7 @@ dependencies = [ "ark-serialize", "assert_matches", "bech32 0.8.1", - "bellman", + "bellman 0.11.2", "borsh 0.9.4", "chrono", "data-encoding", @@ -4231,7 +4264,6 @@ dependencies = [ "borsh 0.9.4", "hex", "masp_primitives", - "masp_proofs", "namada_core", ] @@ -4541,33 +4573,6 @@ dependencies = [ "vcpkg", ] -[[package]] -name = "orchard" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" -dependencies = [ - "aes", - "arrayvec 0.7.2", - "bigint", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "ff", - "fpe", - "group", - "halo2", - "incrementalmerkletree", - "lazy_static", - "memuse", - "nonempty", - "pasta_curves", - "rand 0.8.5", - "reddsa", - "serde 1.0.163", - "subtle", - "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "orion" version = "0.16.1" @@ -4613,7 +4618,16 @@ version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f2e415e349a3006dd7d9482cdab1c980a845bed1377777d768cb693a44540b42" dependencies = [ - "group", + "group 0.11.0", +] + +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", ] [[package]] @@ -4714,21 +4728,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pasta_curves" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" -dependencies = [ - "blake2b_simd 0.5.11", - "ff", - "group", - "lazy_static", - "rand 0.8.5", - "static_assertions", - "subtle", -] - [[package]] name = "paste" version = "1.0.12" @@ -4873,9 +4872,9 @@ dependencies = [ [[package]] name = "poly1305" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug 0.3.0", @@ -5406,17 +5405,15 @@ dependencies = [ ] [[package]] -name = "reddsa" -version = "0.1.0" +name = "redjubjub" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d" dependencies = [ - "blake2b_simd 0.5.11", + "blake2b_simd", "byteorder", "digest 0.9.0", - "group", "jubjub", - "pasta_curves", "rand_core 0.6.4", "serde 1.0.163", "thiserror", @@ -5985,15 +5982,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" -dependencies = [ - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.24.3" @@ -6002,19 +5990,10 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.6.1", + "secp256k1-sys", "serde 1.0.163", ] -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -6930,7 +6909,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "crunchy 0.2.2", + "crunchy", ] [[package]] @@ -7543,7 +7522,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", "static_assertions", ] @@ -7607,11 +7586,11 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ - "generic-array 0.14.7", + "crypto-common", "subtle", ] @@ -8563,83 +8542,10 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "byteorder", - "nonempty", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f84ae538f05a8ac74c82527f06b77045ed9553a0871d9db036166a4c344e3a" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_primitives" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=43c18d0#43c18d000fcbe45363b2d53585d5102841eff99e" dependencies = [ - "aes", - "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "blake2s_simd 1.0.1", - "bls12_381", "byteorder", - "chacha20poly1305", - "equihash", - "ff", - "fpe", - "group", - "hex", - "incrementalmerkletree", - "jubjub", - "lazy_static", - "memuse", "nonempty", - "orchard", - "rand 0.8.5", - "rand_core 0.6.4", - "sha2 0.9.9", - "subtle", - "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", -] - -[[package]] -name = "zcash_proofs" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", - "directories", - "ff", - "group", - "jubjub", - "lazy_static", - "rand_core 0.6.4", - "zcash_primitives", ] [[package]] diff --git a/apps/Cargo.toml b/apps/Cargo.toml index a6439fbb35..323e2b2deb 100644 --- a/apps/Cargo.toml +++ b/apps/Cargo.toml @@ -113,6 +113,7 @@ rand_core = {version = "0.6", default-features = false} rayon = "=1.5.3" regex = "1.4.5" reqwest = "0.11.4" +ripemd = "0.1" rlimit = "0.5.4" rocksdb = {version = "0.21.0", features = ['zstd', 'jemalloc'], default-features = false} rpassword = "5.0.1" @@ -147,9 +148,9 @@ tracing-log = "0.1.2" tracing-subscriber = {version = "0.3.7", features = ["env-filter", "json"]} websocket = "0.26.2" winapi = "0.3.9" -#libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["transparent-inputs"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", features = ["bundled-prover", "download-params"] } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", features = ["transparent-inputs"] } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", features = ["bundled-prover", "download-params"] } bimap = {version = "0.6.2", features = ["serde"]} rust_decimal = "=1.26.1" rust_decimal_macros = "=1.26.1" diff --git a/apps/src/lib/cli.rs b/apps/src/lib/cli.rs index daecbf5eee..32c8fb5b6c 100644 --- a/apps/src/lib/cli.rs +++ b/apps/src/lib/cli.rs @@ -3181,9 +3181,14 @@ pub mod args { .about("Simulate the transaction application."), ) .arg(DUMP_TX.def().about("Dump transaction bytes to a file.")) - .arg(FORCE.def().about( - "Submit the transaction even if it doesn't pass client checks.", - )) + .arg( + FORCE + .def() + .about( + "Submit the transaction even if it doesn't pass \ + client checks.", + ) + ) .arg(BROADCAST_ONLY.def().about( "Do not wait for the transaction to be applied. This will \ return once the transaction is added to the mempool.", diff --git a/apps/src/lib/cli/context.rs b/apps/src/lib/cli/context.rs index 42c1bb6c94..630d329515 100644 --- a/apps/src/lib/cli/context.rs +++ b/apps/src/lib/cli/context.rs @@ -1,6 +1,6 @@ //! CLI input types can be used for command arguments -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::env; use std::marker::PhantomData; use std::path::{Path, PathBuf}; @@ -194,10 +194,31 @@ impl Context { wasm_loader::read_wasm_or_exit(self.wasm_dir(), file_name) } - /// Get address with vp type + /// Try to find an alias for a given address from the wallet. If not found, + /// formats the address into a string. + pub fn lookup_alias(&self, addr: &Address) -> String { + match self.wallet.find_alias(addr) { + Some(alias) => alias.to_string(), + None => addr.to_string(), + } + } + + /// Get addresses with tokens VP type. pub fn tokens(&self) -> HashSet
{ self.wallet.get_addresses_with_vp_type(AddressVpType::Token) } + + /// Get addresses with tokens VP type associated with their aliases. + pub fn tokens_with_aliases(&self) -> HashMap { + self.wallet + .get_addresses_with_vp_type(AddressVpType::Token) + .into_iter() + .map(|addr| { + let alias = self.lookup_alias(&addr); + (addr, alias) + }) + .collect() + } } /// Load global config from expected path in the `base_dir` or try to generate a diff --git a/apps/src/lib/client/rpc.rs b/apps/src/lib/client/rpc.rs index 109b0ffa2d..978dc58058 100644 --- a/apps/src/lib/client/rpc.rs +++ b/apps/src/lib/client/rpc.rs @@ -16,8 +16,7 @@ use data_encoding::HEXLOWER; use itertools::Either; use masp_primitives::asset_type::AssetType; use masp_primitives::merkle_tree::MerklePath; -use masp_primitives::primitives::ViewingKey; -use masp_primitives::sapling::Node; +use masp_primitives::sapling::{Node, ViewingKey}; use masp_primitives::transaction::components::Amount; use masp_primitives::zip32::ExtendedFullViewingKey; use namada::core::types::transaction::governance::ProposalType; @@ -35,6 +34,7 @@ use namada::ledger::pos::{ use namada::ledger::queries::RPC; use namada::ledger::rpc::{query_epoch, TxResponse}; use namada::ledger::storage::ConversionState; +use namada::proto::Tx; use namada::ledger::wallet::{AddressVpType, Wallet}; use namada::proof_of_stake::types::WeightedValidator; use namada::types::address::{masp, Address}; @@ -44,7 +44,13 @@ use namada::types::governance::{ use namada::types::hash::Hash; use namada::types::key::*; use namada::types::masp::{BalanceOwner, ExtendedViewingKey, PaymentAddress}; -use namada::types::storage::{BlockHeight, BlockResults, Epoch, Key, KeySeg}; +use namada::types::storage::{ + BlockHeight, BlockResults, Epoch, Key, KeySeg, PrefixValue, TxIndex, +}; +use namada::types::token::{balance_key, Transfer}; +use namada::types::transaction::{ + AffineCurve, EllipticCurve, PairingEngine, WrapperTx, +}; use namada::types::{storage, token}; use crate::cli::{self, args}; @@ -909,7 +915,10 @@ pub fn print_decoded_balance_with_epoch( let asset_value = token::Amount::from(*value as u64); println!( "{} | {} : {}", - tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + tokens + .get(addr) + .cloned() + .unwrap_or_else(|| addr.clone()), epoch, asset_value ); @@ -1651,7 +1660,10 @@ pub async fn query_conversions( // Print the asset to which the conversion applies print!( "{}[{}]: ", - tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + tokens + .get(addr) + .cloned() + .unwrap_or_else(|| addr.clone()), epoch, ); // Now print out the components of the allowed conversion @@ -1665,7 +1677,10 @@ pub async fn query_conversions( "{}{} {}[{}]", prefix, val, - tokens.get(addr).cloned().unwrap_or_else(|| addr.clone()), + tokens + .get(addr) + .cloned() + .unwrap_or_else(|| addr.clone()), epoch ); // Future iterations need to be prefixed with + diff --git a/apps/src/lib/client/signing.rs b/apps/src/lib/client/signing.rs index eec89b4f80..a5983fb4e1 100644 --- a/apps/src/lib/client/signing.rs +++ b/apps/src/lib/client/signing.rs @@ -1,16 +1,46 @@ //! Helpers for making digital signatures using cryptographic keys from the //! wallet. +use std::collections::{BTreeMap, HashMap}; +use std::env; +use std::fs::File; +use std::io::{Error, ErrorKind, Write}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER; +use itertools::Itertools; +use masp_primitives::asset_type::AssetType; +use masp_primitives::transaction::components::sapling::fees::{ + InputView, OutputView, +}; +use namada::ledger::parameters::storage as parameter_storage; +use namada::proof_of_stake::Epoch; +use namada::proto::{Section, Signature, Tx}; +use namada::types::address::{masp, Address, ImplicitAddress}; +use namada::types::key::*; +use namada::types::masp::{ExtendedViewingKey, PaymentAddress}; +use namada::types::token; +use namada::types::token::{Amount, Transfer}; +use namada::types::transaction::decrypted::DecryptedTx; +use namada::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; +use namada::types::transaction::{ + hash_tx, pos, Fee, InitAccount, InitValidator, TxType, UpdateVp, WrapperTx, + MIN_FEE, +}; +use serde::{Deserialize, Serialize}; + +use super::rpc; +use crate::cli::context::{WalletAddress, WalletKeypair}; +use crate::cli::{self, args, Context}; +use crate::facade::tendermint_config::net::Address as TendermintAddress; +use crate::facade::tendermint_rpc::HttpClient; use namada::ledger::rpc::TxBroadcastData; use namada::ledger::signing::TxSigningKey; use namada::ledger::tx; use namada::ledger::wallet::{Wallet, WalletUtils}; -use namada::proto::Tx; -use namada::types::address::Address; use namada::types::key::*; -use namada::types::storage::Epoch; - -use crate::cli::args; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. Panics if the key cannot be found or loaded. @@ -71,14 +101,18 @@ pub async fn sign_tx< #[cfg(not(feature = "mainnet"))] requires_pow, ) - .await + .await } /// Create a wrapper tx from a normal tx. Get the hash of the /// wrapper and its payload which is needed for monitoring its /// progress on chain. -pub async fn sign_wrapper( +pub async fn sign_wrapper< + C: namada::ledger::queries::Client + Sync, + U: WalletUtils, + >( client: &C, + wallet: &mut Wallet, args: &args::Tx, epoch: Epoch, tx: Tx, @@ -87,6 +121,7 @@ pub async fn sign_wrapper( ) -> TxBroadcastData { namada::ledger::signing::sign_wrapper( client, + wallet, args, epoch, tx, diff --git a/apps/src/lib/client/tx.rs b/apps/src/lib/client/tx.rs index ce334adb41..c71ca9d049 100644 --- a/apps/src/lib/client/tx.rs +++ b/apps/src/lib/client/tx.rs @@ -9,28 +9,76 @@ use async_std::io; use async_std::io::prelude::WriteExt; use borsh::{BorshDeserialize, BorshSerialize}; use data_encoding::HEXLOWER_PERMISSIVE; +use itertools::Either::*; +use masp_primitives::asset_type::AssetType; +use masp_primitives::consensus::TestNetwork; +use masp_primitives::convert::AllowedConversion; +use masp_primitives::ff::PrimeField; +use masp_primitives::memo::MemoBytes; +use masp_primitives::merkle_tree::{ + CommitmentTree, IncrementalWitness, MerklePath, +}; +use masp_primitives::sapling::keys::FullViewingKey; +use masp_primitives::sapling::note_encryption::*; +use masp_primitives::sapling::{ + Diversifier, Node, Note, Nullifier, ViewingKey, +}; +use masp_primitives::transaction::builder::{self, *}; +use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; +use masp_primitives::transaction::components::sapling::fees::{ + ConvertView, InputView as SaplingInputView, OutputView as SaplingOutputView, +}; +use masp_primitives::transaction::components::transparent::fees::{ + InputView as TransparentInputView, OutputView as TransparentOutputView, +}; +use masp_primitives::transaction::components::{ + Amount, OutputDescription, TxOut, +}; +use masp_primitives::transaction::fees::fixed::FeeRule; +use masp_primitives::transaction::{ + Authorization, Authorized, Transaction, TransparentAddress, +}; +use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::prover::LocalTxProver; use namada::ledger::governance::storage as gov_storage; +use namada::ledger::masp; +use namada::ledger::pos::{CommissionPair, PosParams}; +use namada::proto::{Code, Data, MaspBuilder, Section, Signature, Tx}; +use namada::types::address::{masp, masp_tx_key, Address}; use namada::ledger::rpc::{TxBroadcastData, TxResponse}; use namada::ledger::signing::TxSigningKey; use namada::ledger::wallet::{Wallet, WalletUtils}; -use namada::ledger::{masp, tx}; -use namada::proto::Tx; -use namada::types::address::Address; +use namada::ledger::tx; use namada::types::governance::{ OfflineProposal, OfflineVote, Proposal, ProposalVote, VoteType, }; +use namada::types::hash::Hash; use namada::types::key::*; -use namada::types::storage::{Epoch, Key}; -use namada::types::token; +use namada::types::masp::{PaymentAddress, TransferTarget}; +use namada::types::storage::{ + BlockHeight, Epoch, Key, KeySeg, TxIndex, RESERVED_ADDRESS_PREFIX, +}; +use namada::types::time::DateTimeUtc; +use namada::types::token::{ + Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, +}; +use namada::types::transaction::decrypted::DecryptedTx; use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; -use namada::types::transaction::InitValidator; +use namada::types::transaction::{ + pos, InitAccount, InitValidator, TxType, UpdateVp, +}; +use namada::types::{storage, token}; +use rand_core::{CryptoRng, OsRng, RngCore}; +use ripemd::Digest as RipemdDigest; use rust_decimal::Decimal; +use sha2::{Digest as Sha2Digest, Sha256}; +use tokio::time::{Duration, Instant}; use tendermint_rpc::HttpClient; use super::rpc; +use crate::client::tx::tx::ProcessTxResponse; use crate::cli::context::WalletAddress; use crate::cli::{args, safe_exit, Context}; use crate::client::rpc::query_wasm_code_hash; @@ -192,6 +240,12 @@ pub async fn submit_init_validator< .await .unwrap(); + let mut tx = Tx::new(TxType::Raw); + let extra = tx.add_section(Section::ExtraData(Code::from_hash( + validator_vp_code_hash, + ))); + let extra_hash = + Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitValidator { account_key, consensus_key: consensus_key.ref_to(), @@ -199,15 +253,14 @@ pub async fn submit_init_validator< dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash, + validator_vp_code_hash: extra_hash, }; let data = data.try_to_vec().expect("Encoding tx data shouldn't fail"); - let tx = Tx::new( - tx_code_hash.to_vec(), - Some(data), - tx_args.chain_id.clone().unwrap(), - tx_args.expiration, - ); + tx.header.chain_id = tx_args.chain_id.clone().unwrap(); + tx.header.expiration = tx_args.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); + let (mut ctx, result) = process_tx( client, ctx, @@ -315,7 +368,7 @@ impl CLIShieldedUtils { && output_path.exists()) { println!("MASP parameters not present, downloading..."); - masp_proofs::download_parameters() + masp_proofs::download_masp_parameters(None) .expect("MASP parameters not present or downloadable"); println!("MASP parameter download complete, resuming execution..."); } @@ -332,7 +385,7 @@ impl Default for CLIShieldedUtils { fn default() -> Self { Self { context_dir: PathBuf::from(FILE_NAME), - } + } } } @@ -349,7 +402,7 @@ impl masp::ShieldedUtils for CLIShieldedUtils { } else { LocalTxProver::with_default_location() .expect("unable to load MASP Parameters") - } + } } /// Try to load the last saved shielded context from the given context @@ -434,13 +487,13 @@ pub async fn submit_init_proposal( serde_json::from_reader(file).expect("JSON was not well-formatted"); let signer = WalletAddress::new(proposal.clone().author.to_string()); - let governance_parameters = rpc::get_governance_parameters(client).await; let current_epoch = rpc::query_and_print_epoch(client).await; - + + let governance_parameters = rpc::get_governance_parameters(client).await; if proposal.voting_start_epoch <= current_epoch || proposal.voting_start_epoch.0 - % governance_parameters.min_proposal_period - != 0 + % governance_parameters.min_proposal_period + != 0 { println!("{}", proposal.voting_start_epoch <= current_epoch); println!( @@ -450,8 +503,8 @@ pub async fn submit_init_proposal( == 0 ); eprintln!( - "Invalid proposal start epoch: {} must be greater than current \ - epoch {} and a multiple of {}", + "Invalid proposal start epoch: {} must be greater than \ + current epoch {} and a multiple of {}", proposal.voting_start_epoch, current_epoch, governance_parameters.min_proposal_period @@ -461,15 +514,15 @@ pub async fn submit_init_proposal( } } else if proposal.voting_end_epoch <= proposal.voting_start_epoch || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - < governance_parameters.min_proposal_period + < governance_parameters.min_proposal_period || proposal.voting_end_epoch.0 - proposal.voting_start_epoch.0 - > governance_parameters.max_proposal_period + > governance_parameters.max_proposal_period || proposal.voting_end_epoch.0 % 3 != 0 { eprintln!( - "Invalid proposal end epoch: difference between proposal start \ - and end epoch must be at least {} and at max {} and end epoch \ - must be a multiple of {}", + "Invalid proposal end epoch: difference between proposal \ + start and end epoch must be at least {} and at max {} and \ + end epoch must be a multiple of {}", governance_parameters.min_proposal_period, governance_parameters.max_proposal_period, governance_parameters.min_proposal_period @@ -479,11 +532,11 @@ pub async fn submit_init_proposal( } } else if proposal.grace_epoch <= proposal.voting_end_epoch || proposal.grace_epoch.0 - proposal.voting_end_epoch.0 - < governance_parameters.min_proposal_grace_epochs + < governance_parameters.min_proposal_grace_epochs { eprintln!( - "Invalid proposal grace epoch: difference between proposal grace \ - and end epoch must be at least {}", + "Invalid proposal grace epoch: difference between proposal \ + grace and end epoch must be at least {}", governance_parameters.min_proposal_grace_epochs ); if !args.tx.force { @@ -529,11 +582,11 @@ pub async fn submit_init_proposal( let balance = rpc::get_token_balance( client, - &args.native_token, + &ctx.native_token, &proposal.author, ) - .await - .unwrap_or_default(); + .await + .unwrap_or_default(); if balance < token::Amount::from(governance_parameters.min_proposal_fund) { @@ -551,18 +604,17 @@ pub async fn submit_init_proposal( safe_exit(1); } + let mut tx = Tx::new(TxType::Raw); let data = init_proposal_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); let tx_code_hash = query_wasm_code_hash(client, args::TX_INIT_PROPOSAL) .await .unwrap(); - let tx = Tx::new( - tx_code_hash.to_vec(), - Some(data), - ctx.config.ledger.chain_id.clone(), - args.tx.expiration, - ); + tx.header.chain_id = ctx.config.ledger.chain_id.clone(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); process_tx::( client, @@ -806,8 +858,13 @@ pub async fn submit_vote_proposal( let data = tx_data .try_to_vec() .expect("Encoding proposal data shouldn't fail"); + let tx_code = args.tx_code_path; - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = chain_id; + tx.header.expiration = expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::new(tx_code)); process_tx::( client, @@ -873,7 +930,7 @@ pub async fn submit_reveal_pk_aux( ctx: &mut Context, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(), tx::Error> { +) -> Result { let args = args::Tx { chain_id: args .clone() @@ -987,7 +1044,7 @@ pub async fn submit_validator_commission_change< &mut ctx.wallet, args, ) - .await + .await } /// Submit transaction and wait for result. Returns a list of addresses @@ -1001,7 +1058,7 @@ async fn process_tx( #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> Result<(Context, Vec
), tx::Error> { let args = args::Tx { - chain_id: args.clone().chain_id.or_else(|| Some(tx.chain_id.clone())), + chain_id: args.clone().chain_id.or_else(|| Some(tx.header.chain_id.clone())), ..args.clone() }; let res: Vec
= tx::process_tx::( @@ -1013,7 +1070,8 @@ async fn process_tx( #[cfg(not(feature = "mainnet"))] requires_pow, ) - .await?; + .await? + .initialized_accounts(); Ok((ctx, res)) } diff --git a/apps/src/lib/node/ledger/shell/finalize_block.rs b/apps/src/lib/node/ledger/shell/finalize_block.rs index f252b03cbc..9f88b579f4 100644 --- a/apps/src/lib/node/ledger/shell/finalize_block.rs +++ b/apps/src/lib/node/ledger/shell/finalize_block.rs @@ -127,26 +127,19 @@ where if ErrorCodes::from_u32(processed_tx.result.code).unwrap() == ErrorCodes::InvalidSig { - let mut tx_event = match process_tx(tx.clone()) { - Ok(tx @ TxType::Wrapper(_)) - | Ok(tx @ TxType::Protocol(_)) => { + let mut tx_event = match tx.header().tx_type { + TxType::Wrapper(_) | TxType::Protocol(_) => { Event::new_tx_event(&tx, height.0) } - _ => match TxType::try_from(tx) { - Ok(tx @ TxType::Wrapper(_)) - | Ok(tx @ TxType::Protocol(_)) => { - Event::new_tx_event(&tx, height.0) - } - _ => { - tracing::error!( - "Internal logic error: FinalizeBlock received \ - a tx with an invalid signature error code \ - that could not be deserialized to a \ - WrapperTx / ProtocolTx type" - ); - continue; - } - }, + _ => { + tracing::error!( + "Internal logic error: FinalizeBlock received a \ + tx with an invalid signature error code that \ + could not be deserialized to a WrapperTx / \ + ProtocolTx type" + ); + continue; + } }; tx_event["code"] = processed_tx.result.code.to_string(); tx_event["info"] = @@ -156,8 +149,8 @@ where continue; } - let tx_type = if let Ok(tx_type) = process_tx(tx) { - tx_type + let tx = if let Ok(()) = tx.validate_header() { + tx } else { tracing::error!( "Internal logic error: FinalizeBlock received tx that \ @@ -165,12 +158,13 @@ where ); continue; }; + let tx_type = tx.header(); // If [`process_proposal`] rejected a Tx, emit an event here and // move on to next tx if ErrorCodes::from_u32(processed_tx.result.code).unwrap() != ErrorCodes::Ok { - let mut tx_event = Event::new_tx_event(&tx_type, height.0); + let mut tx_event = Event::new_tx_event(&tx, height.0); tx_event["code"] = processed_tx.result.code.to_string(); tx_event["info"] = format!("Tx rejected: {}", &processed_tx.result.info); @@ -179,7 +173,7 @@ where // if the rejected tx was decrypted, remove it // from the queue of txs to be processed and remove the hash // from storage - if let TxType::Decrypted(_) = &tx_type { + if let TxType::Decrypted(_) = &tx_type.tx_type { let tx_hash = self .wl_storage .storage @@ -187,7 +181,9 @@ where .pop() .expect("Missing wrapper tx in queue") .tx - .tx_hash; + .clone() + .update_header(TxType::Raw) + .header_hash(); let tx_hash_key = replay_protection::get_tx_hash_key(&tx_hash); self.wl_storage @@ -198,23 +194,25 @@ where continue; } - let (mut tx_event, tx_unsigned_hash) = match &tx_type { + let (mut tx_event, tx_unsigned_hash) = match &tx_type.tx_type { TxType::Wrapper(wrapper) => { - let mut tx_event = Event::new_tx_event(&tx_type, height.0); + let mut tx_event = Event::new_tx_event(&tx, height.0); // Writes both txs hash to storage - let tx = Tx::try_from(processed_tx.tx.as_ref()).unwrap(); + let processed_tx = + Tx::try_from(processed_tx.tx.as_ref()).unwrap(); let wrapper_tx_hash_key = replay_protection::get_tx_hash_key(&hash::Hash( - tx.unsigned_hash(), + processed_tx.header_hash().0, )); self.wl_storage .storage .write(&wrapper_tx_hash_key, vec![]) .expect("Error while writing tx hash to storage"); - let inner_tx_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); + let inner_tx_hash_key = replay_protection::get_tx_hash_key( + &tx.clone().update_header(TxType::Raw).header_hash(), + ); self.wl_storage .storage .write(&inner_tx_hash_key, vec![]) @@ -275,8 +273,8 @@ where } } - self.wl_storage.storage.tx_queue.push(WrapperTxInQueue { - tx: wrapper.clone(), + self.wl_storage.storage.tx_queue.push(TxInQueue { + tx: processed_tx.clone(), #[cfg(not(feature = "mainnet"))] has_valid_pow, }); @@ -291,20 +289,23 @@ where .pop() .expect("Missing wrapper tx in queue") .tx - .tx_hash; - let mut event = Event::new_tx_event(&tx_type, height.0); + .clone() + .update_header(TxType::Raw) + .header_hash(); + let mut event = Event::new_tx_event(&tx, height.0); match inner { - DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } => { - stats.increment_tx_type( - namada::core::types::hash::Hash(tx.code_hash()) - .to_string(), - ); + DecryptedTx::Decrypted { has_valid_pow: _ } => { + if let Some(code_sec) = tx + .get_section(tx.code_sechash()) + .and_then(Section::code_sec) + { + stats.increment_tx_type( + code_sec.code.hash().to_string(), + ); + } } - DecryptedTx::Undecryptable(_) => { + DecryptedTx::Undecryptable => { event["log"] = "Transaction could not be decrypted.".into(); event["code"] = ErrorCodes::Undecryptable.into(); @@ -312,7 +313,7 @@ where } (event, Some(wrapper_hash)) } - TxType::Raw(_) => { + TxType::Raw => { tracing::error!( "Internal logic error: FinalizeBlock received a \ TxType::Raw transaction" @@ -329,7 +330,7 @@ where }; match protocol::apply_tx( - tx_type, + tx.clone(), tx_length, TxIndex( tx_index @@ -417,8 +418,8 @@ where .storage .delete(&tx_hash_key) .expect( - "Error while deleting tx hash key from storage", - ); + "Error while deleting tx hash key from storage", + ); } } @@ -897,6 +898,7 @@ mod test_finalize_block { rewards_accumulator_handle, validator_consensus_key_handle, validator_rewards_products_handle, }; + use namada::proto::{Code, Data, Section, Signature}; use namada::types::governance::ProposalVote; use namada::types::key::tm_consensus_key_raw_hash; use namada::types::storage::Epoch; @@ -904,7 +906,7 @@ mod test_finalize_block { use namada::types::transaction::governance::{ InitProposalData, ProposalType, VoteProposalData, }; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada::types::transaction::{Fee, WrapperTx, MIN_FEE}; use namada_test_utils::TestWasms; use rust_decimal_macros::dec; use test_log::test; @@ -939,31 +941,31 @@ mod test_finalize_block { // create some wrapper txs for i in 1u64..5 { - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( - Fee { - amount: MIN_FEE.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_data(Data::new("wasm_code".as_bytes().to_owned())); + wrapper.set_code(Code::new( + format!("transaction data: {}", i).as_bytes().to_owned(), + )); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let tx = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + ))); + wrapper.encrypt(&Default::default()); if i > 1 { processed_txs.push(ProcessedTx { - tx: tx.to_bytes(), + tx: wrapper.to_bytes(), result: TxResult { code: u32::try_from(i.rem_euclid(2)) .expect("Test failed"), @@ -999,10 +1001,9 @@ mod test_finalize_block { for wrapper in shell.iter_tx_queue() { // we cannot easily implement the PartialEq trait for WrapperTx // so we check the hashes of the inner txs for equality - assert_eq!( - wrapper.tx.tx_hash, - valid_tx.next().expect("Test failed").tx_hash - ); + let valid_tx = valid_tx.next().expect("Test failed"); + assert_eq!(wrapper.tx.header.code_hash, *valid_tx.code_sechash()); + assert_eq!(wrapper.tx.header.data_hash, *valid_tx.data_sechash()); counter += 1; } assert_eq!(counter, 3); @@ -1016,13 +1017,7 @@ mod test_finalize_block { fn test_process_proposal_rejected_decrypted_tx() { let (mut shell, _) = setup(1); let keypair = gen_keypair(); - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(String::from("transaction data").as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1030,25 +1025,28 @@ mod test_finalize_block { &keypair, Epoch(0), 0.into(), - raw_tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new( + String::from("transaction data").as_bytes().to_owned(), + )); + outer_tx.encrypt(&Default::default()); + shell.enqueue_tx(outer_tx.clone()); + + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })) - .to_bytes(), + tx: outer_tx.to_bytes(), result: TxResult { code: ErrorCodes::InvalidTx.into(), info: "".into(), }, }; - shell.enqueue_tx(wrapper); // check that the decrypted tx was not applied for event in shell @@ -1073,13 +1071,7 @@ mod test_finalize_block { let (mut shell, _) = setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let pubkey = EncryptionKey::default(); // not valid tx bytes - let tx = "garbage data".as_bytes().to_owned(); - let inner_tx = - namada::types::transaction::encrypted::EncryptedTx::encrypt( - &tx, pubkey, - ); let wrapper = WrapperTx { fee: Fee { amount: 0.into(), @@ -1088,23 +1080,20 @@ mod test_finalize_block { pk: keypair.ref_to(), epoch: Epoch(0), gas_limit: 0.into(), - inner_tx, - tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - wrapper.clone(), - ))) - .to_bytes(), + tx: Tx::new(TxType::Decrypted(DecryptedTx::Undecryptable)) + .to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), }, }; - shell.enqueue_tx(wrapper); + let tx = Tx::new(TxType::Wrapper(Box::new(wrapper))); + shell.enqueue_tx(tx); // check that correct error message is returned for event in shell @@ -1148,37 +1137,35 @@ mod test_finalize_block { // create two decrypted txs let tx_code = TestWasms::TxNoOp.read_bytes(); for i in 0..2 { - let raw_tx = Tx::new( - tx_code.clone(), - Some( - format!("Decrypted transaction data: {}", i) - .as_bytes() - .to_owned(), - ), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: MIN_FEE.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), + let mut outer_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code.clone())); + outer_tx.set_data(Data::new( + format!("Decrypted transaction data: {}", i) + .as_bytes() + .to_owned(), + )); + outer_tx.encrypt(&Default::default()); + shell.enqueue_tx(outer_tx.clone()); + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] - None, - ); - shell.enqueue_tx(wrapper_tx); + has_valid_pow: false, + })); + outer_tx.decrypt(::G2Affine::prime_subgroup_generator()) + .expect("Test failed"); processed_txs.push(ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })) - .to_bytes(), + tx: outer_tx.to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), @@ -1187,35 +1174,33 @@ mod test_finalize_block { } // create two wrapper txs for i in 0..2 { - let raw_tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some( - format!("Encrypted transaction data: {}", i) - .as_bytes() - .to_owned(), - ), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: MIN_FEE.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: MIN_FEE.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper_tx.set_data(Data::new( + format!("Encrypted transaction data: {}", i) + .as_bytes() + .to_owned(), + )); + wrapper_tx.add_section(Section::Signature(Signature::new( + &wrapper_tx.header_hash(), &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); - valid_txs.push(wrapper_tx); + ))); + wrapper_tx.encrypt(&Default::default()); + valid_txs.push(wrapper_tx.clone()); processed_txs.push(ProcessedTx { - tx: wrapper.to_bytes(), + tx: wrapper_tx.to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), @@ -1261,10 +1246,9 @@ mod test_finalize_block { let mut counter = 0; for wrapper in shell.iter_tx_queue() { - assert_eq!( - wrapper.tx.tx_hash, - txs.next().expect("Test failed").tx_hash - ); + let next = txs.next().expect("Test failed"); + assert_eq!(wrapper.tx.header.code_hash, *next.code_sechash()); + assert_eq!(wrapper.tx.header.data_hash, *next.data_sechash()); counter += 1; } assert_eq!(counter, 2); @@ -1700,29 +1684,35 @@ mod test_finalize_block { wasm_path.push("wasm_for_tests/tx_no_op.wasm"); let tx_code = std::fs::read(wasm_path) .expect("Expected a file at given code path"); - let raw_tx = Tx::new( - tx_code, - Some("Encrypted transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - raw_tx.clone(), - Default::default(), + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.set_code(Code::new(tx_code)); + wrapper_tx.set_data(Data::new( + "Encrypted transaction data".as_bytes().to_owned(), + )); + let mut decrypted_tx = wrapper_tx.clone(); + wrapper_tx.encrypt(&Default::default()); + + decrypted_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] - None, - ); + has_valid_pow: false, + })); // Write inner hash in storage - let inner_hash_key = - replay_protection::get_tx_hash_key(&wrapper_tx.tx_hash); + let inner_hash_key = replay_protection::get_tx_hash_key( + &wrapper_tx.clone().update_header(TxType::Raw).header_hash(), + ); shell .wl_storage .storage @@ -1730,12 +1720,7 @@ mod test_finalize_block { .expect("Test failed"); let processed_tx = ProcessedTx { - tx: Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx: raw_tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })) - .to_bytes(), + tx: decrypted_tx.to_bytes(), result: TxResult { code: ErrorCodes::Ok.into(), info: "".into(), diff --git a/apps/src/lib/node/ledger/shell/governance.rs b/apps/src/lib/node/ledger/shell/governance.rs index dfdae4d04e..9ff5ebc279 100644 --- a/apps/src/lib/node/ledger/shell/governance.rs +++ b/apps/src/lib/node/ledger/shell/governance.rs @@ -12,6 +12,7 @@ use namada::ledger::storage::types::encode; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::ledger::storage_api::{token, StorageWrite}; use namada::proof_of_stake::read_total_stake; +use namada::proto::{Code, Data}; use namada::types::address::Address; use namada::types::governance::{Council, Tally, TallyResult, VotePower}; use namada::types::storage::Epoch; @@ -147,17 +148,13 @@ where let proposal_code = shell.read_storage_key_bytes(&proposal_code_key); match proposal_code { Some(proposal_code) => { - let tx = Tx::new( - proposal_code, - Some(encode(&id)), - shell.chain_id.clone(), - None, - ); - let tx_type = TxType::Decrypted(DecryptedTx::Decrypted { - tx, + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: false, - }); + })); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_data(Data::new(encode(&id))); + tx.set_code(Code::new(proposal_code)); let pending_execution_key = gov_storage::get_proposal_execution_key(id); shell @@ -165,7 +162,7 @@ where .write(&pending_execution_key, ()) .expect("Should be able to write to storage."); let tx_result = protocol::apply_tx( - tx_type, + tx, 0, /* this is used to compute the fee * based on the code size. We dont * need it here. */ diff --git a/apps/src/lib/node/ledger/shell/mod.rs b/apps/src/lib/node/ledger/shell/mod.rs index 3f69be490e..0cd5d5ab1e 100644 --- a/apps/src/lib/node/ledger/shell/mod.rs +++ b/apps/src/lib/node/ledger/shell/mod.rs @@ -35,10 +35,10 @@ use namada::ledger::storage::{ use namada::ledger::storage_api::{self, StorageRead}; use namada::ledger::{ibc, pos, protocol, replay_protection}; use namada::proof_of_stake::{self, read_pos_params, slash}; -use namada::proto::{self, Tx}; +use namada::proto::{self, Section, Tx}; use namada::types::address::{masp, masp_tx_key, Address}; use namada::types::chain::ChainId; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use namada::types::key::*; use namada::types::storage::{BlockHeight, Key, TxIndex}; use namada::types::time::{DateTimeUtc, TimeZone, Utc}; @@ -46,7 +46,7 @@ use namada::types::token::{self}; #[cfg(not(feature = "mainnet"))] use namada::types::transaction::MIN_FEE; use namada::types::transaction::{ - hash_tx, process_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, + hash_tx, verify_decrypted_correctly, AffineCurve, DecryptedTx, EllipticCurve, PairingEngine, TxType, }; use namada::types::{address, hash}; @@ -411,7 +411,7 @@ where /// Iterate over the wrapper txs in order #[allow(dead_code)] - fn iter_tx_queue(&mut self) -> impl Iterator { + fn iter_tx_queue(&mut self) -> impl Iterator { self.wl_storage.storage.tx_queue.iter() } @@ -676,17 +676,17 @@ where }; // Tx chain id - if tx.chain_id != self.chain_id { + if tx.header.chain_id != self.chain_id { response.code = ErrorCodes::InvalidChainId.into(); response.log = format!( "Tx carries a wrong chain id: expected {}, found {}", - self.chain_id, tx.chain_id + self.chain_id, tx.header.chain_id ); return response; } // Tx expiration - if let Some(exp) = tx.expiration { + if let Some(exp) = tx.header.expiration { let last_block_timestamp = self.get_block_timestamp(None); if last_block_timestamp > exp { @@ -700,8 +700,8 @@ where } // Tx signature check - let tx_type = match process_tx(tx) { - Ok(ty) => ty, + let tx_type = match tx.validate_header() { + Ok(()) => tx.header(), Err(msg) => { response.code = ErrorCodes::InvalidSig.into(); response.log = msg.to_string(); @@ -710,10 +710,13 @@ where }; // Tx type check - if let TxType::Wrapper(wrapper) = tx_type { + if let TxType::Wrapper(wrapper) = tx_type.tx_type { // Replay protection check + let mut inner_tx = tx; + inner_tx.update_header(TxType::Raw); + let inner_tx_hash = &inner_tx.header_hash(); let inner_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); + replay_protection::get_tx_hash_key(inner_tx_hash); if self .wl_storage .storage @@ -725,14 +728,14 @@ where response.log = format!( "Inner transaction hash {} already in storage, replay \ attempt", - wrapper.tx_hash + inner_tx_hash ); return response; } let tx = Tx::try_from(tx_bytes).expect("Deserialization shouldn't fail"); - let wrapper_hash = hash::Hash(tx.unsigned_hash()); + let wrapper_hash = hash::Hash(tx.header_hash().0); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); if self @@ -796,13 +799,6 @@ where let mut tx_wasm_cache = self.tx_wasm_cache.read_only(); match Tx::try_from(tx_bytes) { Ok(tx) => { - let tx = TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - // To be able to dry-run testnet faucet withdrawal, pretend - // that we got a valid PoW - has_valid_pow: true, - }); match protocol::apply_tx( tx, tx_bytes.len(), @@ -933,6 +929,7 @@ mod test_utils { use namada::ledger::storage::mockdb::MockDB; use namada::ledger::storage::{update_allowed_conversions, Sha256Hasher}; + use namada::proto::{Code, Data}; use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; @@ -1089,16 +1086,12 @@ mod test_utils { /// Add a wrapper tx to the queue of txs to be decrypted /// in the current block proposal #[cfg(test)] - pub fn enqueue_tx(&mut self, wrapper: WrapperTx) { - self.shell - .wl_storage - .storage - .tx_queue - .push(WrapperTxInQueue { - tx: wrapper, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }); + pub fn enqueue_tx(&mut self, tx: Tx) { + self.shell.wl_storage.storage.tx_queue.push(TxInQueue { + tx, + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + }); } } @@ -1172,13 +1165,7 @@ mod test_utils { .expect("begin_block failed"); let keypair = gen_keypair(); // enqueue a wrapper tx - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: native_token, @@ -1186,12 +1173,15 @@ mod test_utils { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.wl_storage.storage.tx_queue.push(WrapperTxInQueue { + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); + + shell.wl_storage.storage.tx_queue.push(TxInQueue { tx: wrapper, #[cfg(not(feature = "mainnet"))] has_valid_pow: false, @@ -1229,7 +1219,7 @@ mod test_utils { #[cfg(test)] mod test_mempool_validate { use namada::proof_of_stake::Epoch; - use namada::proto::SignedTxData; + use namada::proto::{Code, Data, Section, Signature, Tx}; use namada::types::transaction::{Fee, WrapperTx}; use super::test_utils::TestShell; @@ -1242,41 +1232,23 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let mut wrapper = WrapperTx::new( - Fee { - amount: 100.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Wrapper signing failed"); - - let unsigned_wrapper = if let Some(Ok(SignedTxData { - data: Some(data), - sig: _, - })) = wrapper - .data - .take() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - Tx::new(vec![], Some(data), shell.chain_id.clone(), None) - } else { - panic!("Test failed") - }; + let mut unsigned_wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + unsigned_wrapper.header.chain_id = shell.chain_id.clone(); + unsigned_wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + unsigned_wrapper + .set_data(Data::new("transaction data".as_bytes().to_owned())); + unsigned_wrapper.encrypt(&Default::default()); let mut result = shell.mempool_validate( unsigned_wrapper.to_bytes().as_ref(), @@ -1297,67 +1269,33 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let mut wrapper = WrapperTx::new( - Fee { - amount: 100.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut invalid_wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 100.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + invalid_wrapper.header.chain_id = shell.chain_id.clone(); + invalid_wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + invalid_wrapper + .set_data(Data::new("transaction data".as_bytes().to_owned())); + invalid_wrapper.add_section(Section::Signature(Signature::new( + &invalid_wrapper.header_hash(), &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Wrapper signing failed"); - - let invalid_wrapper = if let Some(Ok(SignedTxData { - data: Some(data), - sig, - })) = wrapper - .data - .take() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - let mut new_wrapper = if let TxType::Wrapper(wrapper) = - ::deserialize(&mut data.as_ref()) - .expect("Test failed") - { - wrapper - } else { - panic!("Test failed") - }; + ))); + invalid_wrapper.encrypt(&Default::default()); - // we mount a malleability attack to try and remove the fee - new_wrapper.fee.amount = 0.into(); - let new_data = TxType::Wrapper(new_wrapper) - .try_to_vec() - .expect("Test failed"); - Tx::new( - vec![], - Some( - SignedTxData { - sig, - data: Some(new_data), - } - .try_to_vec() - .expect("Test failed"), - ), - shell.chain_id.clone(), - None, - ) - } else { - panic!("Test failed"); - }; + // we mount a malleability attack to try and remove the fee + let mut new_wrapper = + invalid_wrapper.header().wrapper().expect("Test failed"); + new_wrapper.fee.amount = 0.into(); + invalid_wrapper.update_header(TxType::Wrapper(Box::new(new_wrapper))); let mut result = shell.mempool_validate( invalid_wrapper.to_bytes().as_ref(), @@ -1377,12 +1315,9 @@ mod test_mempool_validate { let (shell, _) = TestShell::new(); // Test Raw TxType - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - None, - shell.chain_id.clone(), - None, - ); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -1400,14 +1335,7 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 100.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1415,27 +1343,26 @@ mod test_mempool_validate { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Wrapper signing failed"); - - let tx_type = match process_tx(wrapper.clone()).expect("Test failed") { - TxType::Wrapper(t) => t, - _ => panic!("Test failed"), - }; + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Write wrapper hash to storage - let wrapper_hash = hash::Hash(wrapper.unsigned_hash()); + let wrapper_hash = wrapper.header_hash(); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); shell .wl_storage .storage - .write(&wrapper_hash_key, &wrapper_hash) + .write(&wrapper_hash_key, wrapper_hash) .expect("Test failed"); // Try wrapper tx replay attack @@ -1467,13 +1394,14 @@ mod test_mempool_validate { ) ); + let inner_tx_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); // Write inner hash in storage - let inner_hash_key = - replay_protection::get_tx_hash_key(&tx_type.tx_hash); + let inner_hash_key = replay_protection::get_tx_hash_key(&inner_tx_hash); shell .wl_storage .storage - .write(&inner_hash_key, &tx_type.tx_hash) + .write(&inner_hash_key, inner_tx_hash) .expect("Test failed"); // Try inner tx replay attack @@ -1486,7 +1414,7 @@ mod test_mempool_validate { result.log, format!( "Inner transaction hash {} already in storage, replay attempt", - tx_type.tx_hash + inner_tx_hash ) ); @@ -1499,7 +1427,7 @@ mod test_mempool_validate { result.log, format!( "Inner transaction hash {} already in storage, replay attempt", - tx_type.tx_hash + inner_tx_hash ) ) } @@ -1512,13 +1440,14 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - wrong_chain_id.clone(), - None, - ) - .sign(&keypair); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wrong_chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); let result = shell.mempool_validate( tx.to_bytes().as_ref(), @@ -1541,13 +1470,15 @@ mod test_mempool_validate { let keypair = super::test_utils::gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - Some(DateTimeUtc::now()), - ) - .sign(&keypair); + let mut tx = Tx::new(TxType::Raw); + tx.header.expiration = Some(DateTimeUtc::now()); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); let result = shell.mempool_validate( tx.to_bytes().as_ref(), diff --git a/apps/src/lib/node/ledger/shell/prepare_proposal.rs b/apps/src/lib/node/ledger/shell/prepare_proposal.rs index d48d5cfcec..ad9bd38677 100644 --- a/apps/src/lib/node/ledger/shell/prepare_proposal.rs +++ b/apps/src/lib/node/ledger/shell/prepare_proposal.rs @@ -4,11 +4,12 @@ use namada::core::hints; use namada::ledger::storage::{DBIter, StorageHasher, DB}; use namada::proof_of_stake::pos_queries::PosQueries; use namada::proto::Tx; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use namada::types::time::DateTimeUtc; -use namada::types::transaction::tx_types::TxType; use namada::types::transaction::wrapper::wrapper_tx::PairingEngine; -use namada::types::transaction::{AffineCurve, DecryptedTx, EllipticCurve}; +use namada::types::transaction::{ + AffineCurve, DecryptedTx, EllipticCurve, TxType, +}; use super::super::*; #[allow(unused_imports)] @@ -21,8 +22,10 @@ use super::block_space_alloc::{AllocFailure, BlockSpaceAllocator}; #[cfg(feature = "abcipp")] use crate::facade::tendermint_proto::abci::ExtendedCommitInfo; use crate::facade::tendermint_proto::abci::RequestPrepareProposal; +#[cfg(feature = "abcipp")] +use crate::facade::tendermint_proto::abci::{tx_record::TxAction, TxRecord}; use crate::facade::tendermint_proto::google::protobuf::Timestamp; -use crate::node::ledger::shell::{process_tx, ShellMode}; +use crate::node::ledger::shell::ShellMode; use crate::node::ledger::shims::abcipp_shim_types::shim::{response, TxBytes}; impl Shell @@ -45,7 +48,6 @@ where let txs = if let ShellMode::Validator { .. } = self.mode { // start counting allotted space for txs let alloc = self.get_encrypted_txs_allocator(); - // add encrypted txs let (encrypted_txs, alloc) = self.build_encrypted_txs(alloc, &req.txs, &req.time); @@ -135,10 +137,10 @@ where // If tx doesn't have an expiration it is valid. If time cannot be // retrieved from block default to last block datetime which has // already been checked by mempool_validate, so it's valid - if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.expiration) { + if let (Some(block_time), Some(exp)) = (block_time.as_ref(), &tx.header.expiration) { if block_time > exp { return None } } - if let Ok(TxType::Wrapper(_)) = process_tx(tx) { + if tx.validate_header().is_ok() && tx.header().wrapper().is_some() { return Some(tx_bytes.clone()); } } @@ -195,7 +197,6 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - let pos_queries = self.wl_storage.pos_queries(); let txs = self .wl_storage @@ -203,20 +204,30 @@ where .tx_queue .iter() .map( - |WrapperTxInQueue { + |TxInQueue { tx, #[cfg(not(feature = "mainnet"))] has_valid_pow, - }| { - Tx::from(match tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: *has_valid_pow, + }| { + let mut tx = tx.clone(); + match tx.decrypt(privkey).ok() + { + Some(()) => { + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: *has_valid_pow, + })); + tx }, - _ => DecryptedTx::Undecryptable(tx.clone()), - }) - .to_bytes() + // An absent or undecryptable inner_tx are both + // treated as undecryptable + None => { + tx.update_header(TxType::Decrypted( + DecryptedTx::Undecryptable + )); + tx + }, + }.to_bytes() }, ) // TODO: make sure all decrypted txs are accepted @@ -272,6 +283,7 @@ mod test_prepare_proposal { use borsh::BorshSerialize; use namada::proof_of_stake::Epoch; + use namada::proto::{Code, Data, Header, Section, Signature}; use namada::types::transaction::{Fee, WrapperTx}; use super::*; @@ -283,12 +295,10 @@ mod test_prepare_proposal { #[test] fn test_prepare_proposal_rejects_non_wrapper_tx() { let (shell, _) = test_utils::setup(1); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction_data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + has_valid_pow: true, + })); + tx.header.chain_id = shell.chain_id.clone(); let req = RequestPrepareProposal { txs: vec![tx.to_bytes()], ..Default::default() @@ -303,36 +313,23 @@ mod test_prepare_proposal { fn test_error_in_processing_tx() { let (shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction_data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); // an unsigned wrapper will cause an error in processing - let wrapper = Tx::new( - "".as_bytes().to_owned(), - Some( - WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .try_to_vec() - .expect("Test failed"), - ), - shell.chain_id.clone(), + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] None, - ) - .to_bytes(); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction_data".as_bytes().to_owned())); + wrapper.encrypt(&Default::default()); + let wrapper = wrapper.to_bytes(); #[allow(clippy::redundant_clone)] let req = RequestPrepareProposal { txs: vec![wrapper.clone()], @@ -358,18 +355,7 @@ mod test_prepare_proposal { // create a request with two new wrappers from mempool and // two wrappers from the previous block to be decrypted for i in 0..2 { - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - expected_decrypted.push(Tx::from(DecryptedTx::Decrypted { - tx: tx.clone(), - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); - let wrapper_tx = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -377,39 +363,58 @@ mod test_prepare_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); - shell.enqueue_tx(wrapper_tx); - expected_wrapper.push(wrapper.clone()); - req.txs.push(wrapper.to_bytes()); + )))); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new( + format!("transaction data: {}", i).as_bytes().to_owned(), + )); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); + tx.encrypt(&Default::default()); + + shell.enqueue_tx(tx.clone()); + expected_wrapper.push(tx.clone()); + req.txs.push(tx.to_bytes()); + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + expected_decrypted.push(tx.clone()); } - let expected_txs: Vec = expected_wrapper + // we extract the inner data from the txs for testing + // equality since otherwise changes in timestamps would + // fail the test + let expected_txs: Vec
= expected_wrapper .into_iter() .chain(expected_decrypted.into_iter()) - // we extract the inner data from the txs for testing - // equality since otherwise changes in timestamps would - // fail the test - .map(|tx| tx.data.expect("Test failed")) + .map(|tx| tx.header) .collect(); - let received: Vec = shell + let received: Vec
= shell .prepare_proposal(req) .txs .into_iter() .map(|tx_bytes| { Tx::try_from(tx_bytes.as_slice()) .expect("Test failed") - .data - .expect("Test failed") + .header }) .collect(); // check that the order of the txs is correct - assert_eq!(received, expected_txs); + assert_eq!( + received + .iter() + .map(|x| x.try_to_vec().unwrap()) + .collect::>(), + expected_txs + .iter() + .map(|x| x.try_to_vec().unwrap()) + .collect::>(), + ); } /// Test that expired wrapper transactions are not included in the block @@ -418,28 +423,28 @@ mod test_prepare_proposal { let (shell, _) = test_utils::setup(1); let keypair = gen_keypair(); let tx_time = DateTimeUtc::now(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper_tx = WrapperTx::new( - Fee { - amount: 0.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, + let mut wrapper_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 0.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper_tx.header.chain_id = shell.chain_id.clone(); + wrapper_tx.header.expiration = Some(tx_time); + wrapper_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper_tx + .set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper_tx.add_section(Section::Signature(Signature::new( + &wrapper_tx.header_hash(), &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - let wrapper = wrapper_tx - .sign(&keypair, shell.chain_id.clone(), Some(tx_time)) - .expect("Test failed"); + ))); + wrapper_tx.encrypt(&Default::default()); let time = DateTimeUtc::now(); let block_time = @@ -448,7 +453,7 @@ mod test_prepare_proposal { nanos: time.0.timestamp_subsec_nanos() as i32, }; let req = RequestPrepareProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![wrapper_tx.to_bytes()], max_tx_bytes: 0, time: Some(block_time), ..Default::default() diff --git a/apps/src/lib/node/ledger/shell/process_proposal.rs b/apps/src/lib/node/ledger/shell/process_proposal.rs index acc57e5979..cee1d87532 100644 --- a/apps/src/lib/node/ledger/shell/process_proposal.rs +++ b/apps/src/lib/node/ledger/shell/process_proposal.rs @@ -4,10 +4,9 @@ use data_encoding::HEXUPPER; use namada::core::hints; use namada::core::ledger::storage::WlStorage; -use namada::core::types::hash::Hash; use namada::ledger::storage::TempWlStorage; use namada::proof_of_stake::pos_queries::PosQueries; -use namada::types::internal::WrapperTxInQueue; +use namada::types::internal::TxInQueue; use super::*; use crate::facade::tendermint_proto::abci::response_process_proposal::ProposalStatus; @@ -189,7 +188,7 @@ where pub(crate) fn process_single_tx<'a>( &self, tx_bytes: &[u8], - tx_queue_iter: &mut impl Iterator, + tx_queue_iter: &mut impl Iterator, metadata: &mut ValidationMeta, temp_wl_storage: &mut TempWlStorage, block_time: DateTimeUtc, @@ -225,17 +224,17 @@ where }) }, |tx| { - let tx_chain_id = tx.chain_id.clone(); - let tx_expiration = tx.expiration; - let tx_type = process_tx(tx).map_err(|err| { + let tx_chain_id = tx.header.chain_id.clone(); + let tx_expiration = tx.header.expiration; + if let Err(err) = tx.validate_header() { // This occurs if the wrapper / protocol tx signature is // invalid - TxResult { + return Err(TxResult { code: ErrorCodes::InvalidSig.into(), info: err.to_string(), - } - })?; - Ok((tx_chain_id, tx_expiration, tx_type)) + }); + } + Ok((tx_chain_id, tx_expiration, tx)) }, ); let (tx_chain_id, tx_expiration, tx) = match maybe_tx { @@ -246,9 +245,15 @@ where // TODO: This should not be hardcoded let privkey = ::G2Affine::prime_subgroup_generator(); - match tx { + if let Err(err) = tx.validate_header() { + return TxResult { + code: ErrorCodes::InvalidSig.into(), + info: err.to_string(), + }; + } + match tx.header().tx_type { // If it is a raw transaction, we do no further validation - TxType::Raw(_) => TxResult { + TxType::Raw => TxResult { code: ErrorCodes::InvalidTx.into(), info: "Transaction rejected: Non-encrypted transactions are \ not supported" @@ -286,11 +291,19 @@ where .into(), } } - TxType::Decrypted(tx) => { + TxType::Decrypted(tx_header) => { metadata.has_decrypted_txs = true; match tx_queue_iter.next() { Some(wrapper) => { - if wrapper.tx.tx_hash != tx.hash_commitment() { + let mut inner_tx = tx; + inner_tx.update_header(TxType::Raw); + if wrapper + .tx + .clone() + .update_header(TxType::Raw) + .header_hash() + != inner_tx.header_hash() + { TxResult { code: ErrorCodes::InvalidOrder.into(), info: "Process proposal rejected a decrypted \ @@ -298,41 +311,38 @@ where determined in the previous block" .into(), } - } else if verify_decrypted_correctly(&tx, privkey) { - if let DecryptedTx::Decrypted { - tx, - has_valid_pow: _, - } = tx - { - // Tx chain id - if tx.chain_id != self.chain_id { + } else if verify_decrypted_correctly( + &tx_header, + wrapper.tx.clone(), + privkey, + ) { + // Tx chain id + if wrapper.tx.header.chain_id != self.chain_id { + return TxResult { + code: ErrorCodes::InvalidDecryptedChainId + .into(), + info: format!( + "Decrypted tx carries a wrong chain \ + id: expected {}, found {}", + self.chain_id, + wrapper.tx.header.chain_id + ), + }; + } + + // Tx expiration + if let Some(exp) = wrapper.tx.header.expiration { + if block_time > exp { return TxResult { - code: - ErrorCodes::InvalidDecryptedChainId - .into(), + code: ErrorCodes::ExpiredDecryptedTx + .into(), info: format!( - "Decrypted tx carries a wrong \ - chain id: expected {}, found {}", - self.chain_id, tx.chain_id + "Decrypted tx expired at {:#?}, \ + block time: {:#?}", + exp, block_time ), }; } - - // Tx expiration - if let Some(exp) = tx.expiration { - if block_time > exp { - return TxResult { - code: - ErrorCodes::ExpiredDecryptedTx - .into(), - info: format!( - "Decrypted tx expired at \ - {:#?}, block time: {:#?}", - exp, block_time - ), - }; - } - } } TxResult { code: ErrorCodes::Ok.into(), @@ -419,7 +429,7 @@ where } // validate the ciphertext via Ferveo - if !wrapper.validate_ciphertext() { + if !tx.validate_ciphertext() { TxResult { code: ErrorCodes::InvalidTx.into(), info: format!( @@ -429,8 +439,11 @@ where } } else { // Replay protection checks + let mut inner_tx = tx; + inner_tx.update_header(TxType::Raw); + let inner_tx_hash = inner_tx.header_hash(); let inner_hash_key = - replay_protection::get_tx_hash_key(&wrapper.tx_hash); + replay_protection::get_tx_hash_key(&inner_tx_hash); if temp_wl_storage.has_key(&inner_hash_key).expect( "Error while checking inner tx hash key in storage", ) { @@ -439,7 +452,7 @@ where info: format!( "Inner transaction hash {} already in \ storage, replay attempt", - &wrapper.tx_hash + &inner_tx_hash ), }; } @@ -455,7 +468,7 @@ where let tx = Tx::try_from(tx_bytes) .expect("Deserialization shouldn't fail"); - let wrapper_hash = Hash(tx.unsigned_hash()); + let wrapper_hash = tx.header_hash(); let wrapper_hash_key = replay_protection::get_tx_hash_key(&wrapper_hash); if temp_wl_storage.has_key(&wrapper_hash_key).expect( @@ -537,16 +550,14 @@ where /// are covered by the e2e tests. #[cfg(test)] mod test_process_proposal { - use borsh::BorshDeserialize; use namada::ledger::parameters::storage::get_wrapper_tx_fees_key; - use namada::proto::SignedTxData; + use namada::proto::{Code, Data, Section, Signature}; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::Epoch; use namada::types::token::Amount; - use namada::types::transaction::encrypted::EncryptedTx; - use namada::types::transaction::protocol::ProtocolTxType; - use namada::types::transaction::{EncryptionKey, Fee, WrapperTx, MIN_FEE}; + use namada::types::transaction::protocol::{ProtocolTx, ProtocolTxType}; + use namada::types::transaction::{Fee, WrapperTx, MIN_FEE}; use super::*; use crate::node::ledger::shell::test_utils::{ @@ -559,13 +570,7 @@ mod test_process_proposal { fn test_unsigned_wrapper_rejected() { let (mut shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -573,18 +578,14 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let tx = Tx::new( - vec![], - Some(TxType::Wrapper(wrapper).try_to_vec().expect("Test failed")), - shell.chain_id.clone(), - None, - ) - .to_bytes(); + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.encrypt(&Default::default()); + let tx = outer_tx.to_bytes(); #[allow(clippy::redundant_clone)] let request = ProcessProposal { txs: vec![tx.clone()], @@ -599,7 +600,10 @@ mod test_process_proposal { ); assert_eq!( response[0].result.info, - String::from("Wrapper transactions must be signed") + String::from( + "WrapperTx signature verification failed: Transaction \ + doesn't have any data with a signature." + ) ); } } @@ -611,14 +615,7 @@ mod test_process_proposal { fn test_wrapper_bad_signature_rejected() { let (mut shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let timestamp = tx.timestamp; - let mut wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 100.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -626,51 +623,23 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); - let new_tx = if let Some(Ok(SignedTxData { - data: Some(data), - sig, - })) = wrapper - .data - .take() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - let mut new_wrapper = if let TxType::Wrapper(wrapper) = - ::deserialize(&mut data.as_ref()) - .expect("Test failed") - { - wrapper - } else { - panic!("Test failed") - }; - + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + &keypair, + ))); + outer_tx.encrypt(&Default::default()); + let mut new_tx = outer_tx.clone(); + if let TxType::Wrapper(wrapper) = &mut new_tx.header.tx_type { // we mount a malleability attack to try and remove the fee - new_wrapper.fee.amount = 0.into(); - let new_data = TxType::Wrapper(new_wrapper) - .try_to_vec() - .expect("Test failed"); - Tx { - code_or_hash: vec![], - data: Some( - SignedTxData { - sig, - data: Some(new_data), - } - .try_to_vec() - .expect("Test failed"), - ), - timestamp, - chain_id: shell.chain_id.clone(), - expiration: None, - } + wrapper.fee.amount = 0.into(); } else { - panic!("Test failed"); + panic!("Test failed") }; let request = ProcessProposal { txs: vec![new_tx.to_bytes()], @@ -679,8 +648,9 @@ mod test_process_proposal { match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { - let expected_error = - "Signature verification failed: Invalid signature"; + let expected_error = "WrapperTx signature verification \ + failed: Transaction doesn't have any \ + data with a signature."; assert_eq!( response[0].result.code, u32::from(ErrorCodes::InvalidSig) @@ -709,13 +679,7 @@ mod test_process_proposal { ) .unwrap(); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 1.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -723,15 +687,20 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + &keypair, + ))); + outer_tx.encrypt(&Default::default()); + let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![outer_tx.to_bytes()], }; match shell.process_proposal(request) { @@ -777,13 +746,7 @@ mod test_process_proposal { ) .unwrap(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut outer_tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: Amount::whole(1_000_100), token: shell.wl_storage.storage.native_token.clone(), @@ -791,16 +754,20 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + &keypair, + ))); + outer_tx.encrypt(&Default::default()); let request = ProcessProposal { - txs: vec![wrapper.to_bytes()], + txs: vec![outer_tx.to_bytes()], }; match shell.process_proposal(request) { @@ -829,34 +796,31 @@ mod test_process_proposal { let keypair = gen_keypair(); let mut txs = vec![]; for i in 0..3 { - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some(format!("transaction data: {}", i).as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( - Fee { - amount: i.into(), - token: shell.wl_storage.storage.native_token.clone(), - }, - &keypair, - Epoch(0), - 0.into(), - tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - shell.enqueue_tx(wrapper); - let mut decrypted_tx = - Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx, + let mut outer_tx = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: i.into(), + token: shell.wl_storage.storage.native_token.clone(), + }, + &keypair, + Epoch(0), + 0.into(), #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - })); - decrypted_tx.chain_id = shell.chain_id.clone(); - txs.push(decrypted_tx); + None, + )))); + outer_tx.header.chain_id = shell.chain_id.clone(); + outer_tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + outer_tx.set_data(Data::new( + format!("transaction data: {}", i).as_bytes().to_owned(), + )); + outer_tx.encrypt(&Default::default()); + shell.enqueue_tx(outer_tx.clone()); + + outer_tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + txs.push(outer_tx); } let response = { let request = ProcessProposal { @@ -892,13 +856,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = gen_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -906,17 +864,16 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - shell.enqueue_tx(wrapper.clone()); - - let mut tx = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable(wrapper))); - tx.chain_id = shell.chain_id.clone(); - + )))); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.encrypt(&Default::default()); + shell.enqueue_tx(tx.clone()); + + tx.header.tx_type = TxType::Decrypted(DecryptedTx::Undecryptable); let request = ProcessProposal { txs: vec![tx.to_bytes()], }; @@ -947,13 +904,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let mut wrapper = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -961,20 +912,19 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - wrapper.tx_hash = Hash([0; 32]); + )))); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.set_code_sechash(Hash([0u8; 32])); + tx.set_data_sechash(Hash([0u8; 32])); + tx.encrypt(&Default::default()); - shell.enqueue_tx(wrapper.clone()); - let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))); - tx.chain_id = shell.chain_id.clone(); + shell.enqueue_tx(tx.clone()); + tx.header.tx_type = TxType::Decrypted(DecryptedTx::Undecryptable); let request = ProcessProposal { txs: vec![tx.to_bytes()], }; @@ -997,10 +947,7 @@ mod test_process_proposal { fn test_undecryptable() { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let pubkey = EncryptionKey::default(); // not valid tx bytes - let tx = "garbage data".as_bytes().to_owned(); - let inner_tx = EncryptedTx::encrypt(&tx, pubkey); let wrapper = WrapperTx { fee: Fee { amount: 0.into(), @@ -1009,21 +956,18 @@ mod test_process_proposal { pk: keypair.ref_to(), epoch: Epoch(0), gas_limit: 0.into(), - inner_tx, - tx_hash: hash_tx(&tx), #[cfg(not(feature = "mainnet"))] pow_solution: None, }; - shell.enqueue_tx(wrapper.clone()); - let mut signed = - Tx::from(TxType::Decrypted(DecryptedTx::Undecryptable( - #[allow(clippy::redundant_clone)] - wrapper.clone(), - ))); - signed.chain_id = shell.chain_id.clone(); + let tx = Tx::new(TxType::Wrapper(Box::new(wrapper))); + let mut decrypted = tx.clone(); + decrypted.update_header(TxType::Decrypted(DecryptedTx::Undecryptable)); + + shell.enqueue_tx(tx); + let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![decrypted.to_bytes()], }; let response = if let [resp] = shell .process_proposal(request) @@ -1042,20 +986,13 @@ mod test_process_proposal { #[test] fn test_too_many_decrypted_txs() { let (mut shell, _) = test_utils::setup(1); - - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - - let mut tx = Tx::from(TxType::Decrypted(DecryptedTx::Decrypted { - tx, + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] has_valid_pow: false, })); - tx.chain_id = shell.chain_id.clone(); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); let request = ProcessProposal { txs: vec![tx.to_bytes()], @@ -1083,14 +1020,11 @@ mod test_process_proposal { fn test_raw_tx_rejected() { let (mut shell, _) = test_utils::setup(1); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let mut tx = Tx::from(TxType::Raw(tx)); - tx.chain_id = shell.chain_id.clone(); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = shell.chain_id.clone(); + tx.set_code(Code::new("wasm_code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + let request = ProcessProposal { txs: vec![tx.to_bytes()], }; @@ -1121,13 +1055,7 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1135,17 +1063,20 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Write wrapper hash to storage - let wrapper_unsigned_hash = Hash(signed.unsigned_hash()); + let wrapper_unsigned_hash = wrapper.header_hash(); let hash_key = replay_protection::get_tx_hash_key(&wrapper_unsigned_hash); shell @@ -1156,8 +1087,9 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], }; + match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), Err(TestError::RejectProposal(response)) => { @@ -1195,13 +1127,7 @@ mod test_process_proposal { .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1209,18 +1135,21 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes(); 2], + txs: vec![wrapper.to_bytes(); 2], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1238,7 +1167,10 @@ mod test_process_proposal { format!( "Inner transaction hash {} already in storage, replay \ attempt", - wrapper.tx_hash + wrapper + .clone() + .update_header(TxType::Raw) + .header_hash(), ) ); } @@ -1253,13 +1185,7 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1267,15 +1193,19 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); + let inner_unsigned_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); // Write inner hash to storage let hash_key = replay_protection::get_tx_hash_key(&inner_unsigned_hash); @@ -1287,7 +1217,7 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1339,13 +1269,7 @@ mod test_process_proposal { .write(&balance_key, Amount::whole(1000).try_to_vec().unwrap()) .unwrap(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1353,17 +1277,22 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let inner_unsigned_hash = wrapper.tx_hash.clone(); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + let mut new_wrapper = wrapper.clone(); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); + let inner_unsigned_hash = + wrapper.clone().update_header(TxType::Raw).header_hash(); - let new_wrapper = WrapperTx::new( + new_wrapper.update_header(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1371,18 +1300,18 @@ mod test_process_proposal { &keypair_2, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let new_signed = new_wrapper - .sign(&keypair, shell.chain_id.clone(), None) - .expect("Test failed"); + )))); + new_wrapper.add_section(Section::Signature(Signature::new( + &new_wrapper.header_hash(), + &keypair, + ))); + new_wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes(), new_signed.to_bytes()], + txs: vec![wrapper.to_bytes(), new_wrapper.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1411,13 +1340,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1425,25 +1348,32 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx.clone(), - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); + )))); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let signed = wrapper - .sign(&keypair, wrong_chain_id.clone(), None) - .expect("Test failed"); + wrapper.header.chain_id = wrong_chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + let mut protocol_tx = wrapper.clone(); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); - let protocol_tx = ProtocolTxType::EthereumStateUpdate(tx).sign( - &keypair.ref_to(), + protocol_tx.update_header(TxType::Protocol(Box::new(ProtocolTx { + pk: keypair.ref_to(), + tx: ProtocolTxType::EthereumStateUpdate, + }))); + protocol_tx.add_section(Section::Signature(Signature::new( + &protocol_tx.header_hash(), &keypair, - wrong_chain_id.clone(), - ); + ))); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes(), protocol_tx.to_bytes()], + txs: vec![wrapper.to_bytes(), protocol_tx.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1474,19 +1404,7 @@ mod test_process_proposal { let keypair = crate::wallet::defaults::daewon_keypair(); let wrong_chain_id = ChainId("Wrong chain id".to_string()); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("new transaction data".as_bytes().to_owned()), - wrong_chain_id.clone(), - None, - ); - let decrypted: Tx = DecryptedTx::Decrypted { - tx: tx.clone(), - has_valid_pow: false, - } - .into(); - let signed_decrypted = decrypted.sign(&keypair); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1494,12 +1412,24 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper_in_queue = WrapperTxInQueue { + )))); + wrapper.header.chain_id = wrong_chain_id.clone(); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper + .set_data(Data::new("new transaction data".as_bytes().to_owned())); + let mut decrypted = wrapper.clone(); + wrapper.encrypt(&Default::default()); + + decrypted.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + has_valid_pow: false, + })); + decrypted.add_section(Section::Signature(Signature::new( + &decrypted.header_hash(), + &keypair, + ))); + let wrapper_in_queue = TxInQueue { tx: wrapper, has_valid_pow: false, }; @@ -1507,7 +1437,7 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed_decrypted.to_bytes()], + txs: vec![decrypted.to_bytes()], }; match shell.process_proposal(request) { @@ -1535,13 +1465,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - None, - ); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1549,18 +1473,22 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let signed = wrapper - .sign(&keypair, shell.chain_id.clone(), Some(DateTimeUtc::now())) - .expect("Test failed"); + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.header.expiration = Some(DateTimeUtc::now()); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper.set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); // Run validation let request = ProcessProposal { - txs: vec![signed.to_bytes()], + txs: vec![wrapper.to_bytes()], }; match shell.process_proposal(request) { Ok(_) => panic!("Test failed"), @@ -1580,19 +1508,7 @@ mod test_process_proposal { let (mut shell, _) = test_utils::setup(1); let keypair = crate::wallet::defaults::daewon_keypair(); - let tx = Tx::new( - "wasm_code".as_bytes().to_owned(), - Some("new transaction data".as_bytes().to_owned()), - shell.chain_id.clone(), - Some(DateTimeUtc::now()), - ); - let decrypted: Tx = DecryptedTx::Decrypted { - tx: tx.clone(), - has_valid_pow: false, - } - .into(); - let signed_decrypted = decrypted.sign(&keypair); - let wrapper = WrapperTx::new( + let mut wrapper = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 0.into(), token: shell.wl_storage.storage.native_token.clone(), @@ -1600,12 +1516,25 @@ mod test_process_proposal { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ); - let wrapper_in_queue = WrapperTxInQueue { + )))); + wrapper.header.chain_id = shell.chain_id.clone(); + wrapper.header.expiration = Some(DateTimeUtc::now()); + wrapper.set_code(Code::new("wasm_code".as_bytes().to_owned())); + wrapper + .set_data(Data::new("new transaction data".as_bytes().to_owned())); + let mut decrypted = wrapper.clone(); + wrapper.encrypt(&Default::default()); + + decrypted.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + has_valid_pow: false, + })); + decrypted.add_section(Section::Signature(Signature::new( + &decrypted.header_hash(), + &keypair, + ))); + let wrapper_in_queue = TxInQueue { tx: wrapper, has_valid_pow: false, }; @@ -1613,7 +1542,7 @@ mod test_process_proposal { // Run validation let request = ProcessProposal { - txs: vec![signed_decrypted.to_bytes()], + txs: vec![decrypted.to_bytes()], }; match shell.process_proposal(request) { Ok(response) => { diff --git a/apps/src/lib/node/ledger/shims/abcipp_shim.rs b/apps/src/lib/node/ledger/shims/abcipp_shim.rs index 05ffbf8de0..7bb02a6aef 100644 --- a/apps/src/lib/node/ledger/shims/abcipp_shim.rs +++ b/apps/src/lib/node/ledger/shims/abcipp_shim.rs @@ -186,7 +186,7 @@ impl AbcippShim { let mut end_block_request: FinalizeBlock = begin_block_request.into(); let hash = self.get_hash(); - end_block_request.hash = BlockHash::from(hash.clone()); + end_block_request.hash = BlockHash::from(hash); end_block_request.txs = txs; self.service .call(Request::FinalizeBlock(end_block_request)) diff --git a/apps/src/lib/node/ledger/storage/mod.rs b/apps/src/lib/node/ledger/storage/mod.rs index 73c21ba6ca..e9222ab213 100644 --- a/apps/src/lib/node/ledger/storage/mod.rs +++ b/apps/src/lib/node/ledger/storage/mod.rs @@ -246,7 +246,7 @@ mod tests { // insert let vp1 = Hash::sha256("vp1".as_bytes()); - storage.write(&key, vp1.clone()).expect("write failed"); + storage.write(&key, vp1).expect("write failed"); // check let (vp_code_hash, gas) = diff --git a/core/Cargo.toml b/core/Cargo.toml index 0e98344216..2e37335bf3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -81,7 +81,8 @@ ics23 = "0.9.0" index-set = {git = "https://github.com/heliaxdev/index-set", tag = "v0.7.1", features = ["serialize-borsh", "serialize-serde"]} itertools = "0.10.0" libsecp256k1 = {git = "https://github.com/heliaxdev/libsecp256k1", rev = "bbb3bd44a49db361f21d9db80f9a087c194c0ae9", default-features = false, features = ["std", "static-context"]} -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35c0781bf9a00a4ee59833ffa2a1", optional = true} prost = "0.11.6" prost-types = "0.11.6" diff --git a/core/src/ledger/storage/merkle_tree.rs b/core/src/ledger/storage/merkle_tree.rs index 31fb40b1eb..046ebca4e6 100644 --- a/core/src/ledger/storage/merkle_tree.rs +++ b/core/src/ledger/storage/merkle_tree.rs @@ -723,7 +723,7 @@ mod test { let stores_write = tree.stores(); let mut stores_read = MerkleTreeStoresRead::default(); for st in StoreType::iter() { - stores_read.set_root(st, stores_write.root(st).clone()); + stores_read.set_root(st, *stores_write.root(st)); stores_read.set_store(stores_write.store(st).to_owned()); } let restored_tree = diff --git a/core/src/ledger/storage/mod.rs b/core/src/ledger/storage/mod.rs index cb3f9299df..3eafa8950b 100644 --- a/core/src/ledger/storage/mod.rs +++ b/core/src/ledger/storage/mod.rs @@ -919,7 +919,6 @@ where } } } - Ok(()) } } diff --git a/core/src/ledger/storage/write_log.rs b/core/src/ledger/storage/write_log.rs index bf4881aab0..df004b3371 100644 --- a/core/src/ledger/storage/write_log.rs +++ b/core/src/ledger/storage/write_log.rs @@ -442,7 +442,7 @@ impl WriteLog { .batch_write_subspace_val( batch, key, - vp_code_hash.clone(), + *vp_code_hash, ) .map_err(Error::StorageError)?; } @@ -609,7 +609,7 @@ mod tests { // init let init_vp = "initialized".as_bytes().to_vec(); let vp_hash = Hash::sha256(init_vp); - let (addr, gas) = write_log.init_account(&address_gen, vp_hash.clone()); + let (addr, gas) = write_log.init_account(&address_gen, vp_hash); let vp_key = storage::Key::validity_predicate(&addr); assert_eq!(gas, (vp_key.len() + vp_hash.len()) as u64); @@ -693,7 +693,7 @@ mod tests { // initialize an account let vp1 = Hash::sha256("vp1".as_bytes()); - let (addr1, _) = write_log.init_account(&address_gen, vp1.clone()); + let (addr1, _) = write_log.init_account(&address_gen, vp1); write_log.commit_tx(); // write values diff --git a/core/src/ledger/vp_env.rs b/core/src/ledger/vp_env.rs index f1d1210e28..149b71abe7 100644 --- a/core/src/ledger/vp_env.rs +++ b/core/src/ledger/vp_env.rs @@ -4,12 +4,11 @@ use borsh::BorshDeserialize; use super::storage_api::{self, StorageRead}; +use crate::proto::Tx; use crate::types::address::Address; use crate::types::hash::Hash; +use crate::types::storage::{BlockHash, BlockHeight, Epoch, Header, Key, TxIndex}; use crate::types::key::common; -use crate::types::storage::{ - BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, -}; /// Validity predicate's environment is available for native VPs and WASM VPs pub trait VpEnv<'view> @@ -91,20 +90,11 @@ where fn eval( &self, vp_code: Hash, - input_data: Vec, - ) -> Result; - - /// Verify a transaction signature. The signature is expected to have been - /// produced on the encoded transaction [`crate::proto::Tx`] - /// using [`crate::proto::Tx::sign`]. - fn verify_tx_signature( - &self, - pk: &common::PublicKey, - sig: &common::Signature, + input_data: Tx, ) -> Result; /// Get a tx hash - fn get_tx_code_hash(&self) -> Result; + fn get_tx_code_hash(&self) -> Result, storage_api::Error>; /// Verify a MASP transaction fn verify_masp(&self, tx: Vec) -> Result; diff --git a/core/src/proto/mod.rs b/core/src/proto/mod.rs index daeb0e9a49..2b7d5d1080 100644 --- a/core/src/proto/mod.rs +++ b/core/src/proto/mod.rs @@ -3,27 +3,23 @@ pub mod generated; mod types; -pub use types::{Dkg, Error, Signed, SignedTxData, Tx}; +pub use types::{ + Code, Commitment, Data, Dkg, Error, Header, MaspBuilder, Section, Signature, + Tx, TxError, +}; #[cfg(test)] mod tests { - use std::time::SystemTime; - use data_encoding::HEXLOWER; use generated::types::Tx; use prost::Message; use super::*; - use crate::types::chain::ChainId; #[test] fn encoding_round_trip() { let tx = Tx { - code_or_hash: "wasm code".as_bytes().to_owned(), - data: Some("arbitrary data".as_bytes().to_owned()), - timestamp: Some(SystemTime::now().into()), - chain_id: ChainId::default().0, - expiration: Some(SystemTime::now().into()), + data: "arbitrary data".as_bytes().to_owned(), }; let mut tx_bytes = vec![]; tx.encode(&mut tx_bytes).unwrap(); diff --git a/core/src/proto/types.rs b/core/src/proto/types.rs index f82fb7bd8b..273c4ef766 100644 --- a/core/src/proto/types.rs +++ b/core/src/proto/types.rs @@ -1,31 +1,47 @@ -use std::convert::{TryFrom, TryInto}; -use std::hash::{Hash, Hasher}; +use std::collections::HashSet; +use std::convert::TryFrom; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::AffineCurve; +#[cfg(feature = "ferveo-tpke")] +use ark_ec::PairingEngine; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; +use masp_primitives::transaction::builder::Builder; +use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; +use masp_primitives::transaction::Transaction; +use masp_primitives::zip32::ExtendedFullViewingKey; use prost::Message; +use serde::de::Error as SerdeError; use serde::{Deserialize, Serialize}; +use sha2::{Digest, Sha256}; use thiserror::Error; use super::generated::types; #[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] use crate::tendermint_proto::abci::ResponseDeliverTx; +use crate::types::address::Address; use crate::types::chain::ChainId; use crate::types::key::*; +use crate::types::storage::Epoch; use crate::types::time::DateTimeUtc; #[cfg(feature = "ferveo-tpke")] use crate::types::token::Transfer; -use crate::types::transaction::hash_tx; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::process_tx; +use crate::types::transaction::protocol::ProtocolTx; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::EllipticCurve; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::DecryptedTx; +use crate::types::transaction::EncryptionKey; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::TxType; +use crate::types::transaction::WrapperTxErr; +use crate::types::transaction::{hash_tx, DecryptedTx, TxType, WrapperTx}; #[derive(Error, Debug)] pub enum Error { #[error("Error decoding a transaction from bytes: {0}")] TxDecodingError(prost::DecodeError), + #[error("Error deserializing transaction field bytes: {0}")] + TxDeserializingError(std::io::Error), #[error("Error decoding an DkgGossipMessage from bytes: {0}")] DkgDecodingError(prost::DecodeError), #[error("Dkg is empty")] @@ -38,416 +54,1080 @@ pub enum Error { pub type Result = std::result::Result; -/// This can be used to sign an arbitrary tx. The signature is produced and -/// verified on the tx data concatenated with the tx code, however the tx code -/// itself is not part of this structure. -/// -/// Because the signature is not checked by the ledger, we don't inline it into -/// the `Tx` type directly. Instead, the signature is attached to the `tx.data`, -/// which can then be checked by a validity predicate wasm. -#[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] -pub struct SignedTxData { - /// The original tx data bytes, if any - pub data: Option>, - /// The signature is produced on the tx data concatenated with the tx code - /// and the timestamp. - pub sig: common::Signature, -} - -/// A generic signed data wrapper for Borsh encode-able data. +/// A section representing transaction data #[derive( - Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, )] -pub struct Signed { - /// Arbitrary data to be signed - pub data: T, - /// The signature of the data - pub sig: common::Signature, +pub struct Data { + pub salt: [u8; 8], + pub data: Vec, } -impl PartialEq for Signed -where - T: BorshSerialize + BorshDeserialize + PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.data == other.data && self.sig == other.sig +impl Data { + /// Make a new data section with the given bytes + pub fn new(data: Vec) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + data, + } + } + + /// Hash this data section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize data section"), + ); + hasher } } -impl Eq for Signed where - T: BorshSerialize + BorshDeserialize + Eq + PartialEq -{ +/// Error representing the case where the supplied code has incorrect hash +pub struct CommitmentError; + +/// Represents either some code bytes or their SHA-256 hash +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub enum Commitment { + /// Result of applying hash function to bytes + Hash(crate::types::hash::Hash), + /// Result of applying identity function to bytes + Id(Vec), } -impl Hash for Signed -where - T: BorshSerialize + BorshDeserialize + Hash, -{ - fn hash(&self, state: &mut H) { - self.data.hash(state); - self.sig.hash(state); +impl Commitment { + /// Substitute bytes with their SHA-256 hash + pub fn contract(&mut self) { + if let Self::Id(code) = self { + *self = Self::Hash(hash_tx(code)); + } + } + + /// Substitute a code hash with the supplied bytes if the hashes are + /// consistent, otherwise return an error + pub fn expand( + &mut self, + code: Vec, + ) -> std::result::Result<(), CommitmentError> { + match self { + Self::Id(c) if *c == code => Ok(()), + Self::Hash(hash) if *hash == hash_tx(&code) => { + *self = Self::Id(code); + Ok(()) + } + _ => Err(CommitmentError), + } + } + + /// Return the contained hash commitment + pub fn hash(&self) -> crate::types::hash::Hash { + match self { + Self::Id(code) => hash_tx(code), + Self::Hash(hash) => *hash, + } + } + + /// Return the result of applying identity function if there is any + pub fn id(&self) -> Option> { + if let Self::Id(code) = self { + Some(code.clone()) + } else { + None + } + } +} + +/// A section representing transaction code +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct Code { + /// Additional random data + pub salt: [u8; 8], + /// Actual transaction code + pub code: Commitment, +} + +impl Code { + /// Make a new code section with the given bytes + pub fn new(code: Vec) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + code: Commitment::Id(code), + } + } + + /// Make a new code section with the given hash + pub fn from_hash(hash: crate::types::hash::Hash) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + code: Commitment::Hash(hash), + } + } + + /// Hash this code section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update(self.salt); + hasher.update(self.code.hash()); + hasher + } +} + +/// A section representing the signature over another section +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct Signature { + /// Additional random data + salt: [u8; 8], + /// The hash of the section being signed + target: crate::types::hash::Hash, + /// The signature over the above has + pub signature: common::Signature, + /// The public key to verrify the above siggnature + pub_key: common::PublicKey, +} + +impl Signature { + /// Sign the given section hash with the given key and return a section + pub fn new( + target: &crate::types::hash::Hash, + sec_key: &common::SecretKey, + ) -> Self { + Self { + salt: DateTimeUtc::now().0.timestamp_millis().to_le_bytes(), + target: *target, + signature: common::SigScheme::sign(sec_key, target), + pub_key: sec_key.ref_to(), + } + } + + /// Hash this signature section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize signature section"), + ); + hasher + } +} + +/// Represents a section obtained by encrypting another section +#[derive(Clone, Debug, Serialize, Deserialize)] +#[cfg_attr(feature = "ferveo-tpke", serde(from = "SerializedCiphertext"))] +#[cfg_attr(feature = "ferveo-tpke", serde(into = "SerializedCiphertext"))] +#[cfg_attr( + not(feature = "ferveo-tpke"), + derive(BorshSerialize, BorshDeserialize, BorshSchema) +)] +pub struct Ciphertext { + /// The ciphertext corresponding to the original section serialization + #[cfg(feature = "ferveo-tpke")] + pub ciphertext: tpke::Ciphertext, + /// Ciphertext representation when ferveo not available + #[cfg(not(feature = "ferveo-tpke"))] + pub opaque: Vec, +} + +impl Ciphertext { + /// Make a ciphertext section based on the given sections. Note that this + /// encryption is not idempotent + #[cfg(feature = "ferveo-tpke")] + pub fn new(sections: Vec
, pubkey: &EncryptionKey) -> Self { + let mut rng = rand::thread_rng(); + let bytes = sections + .try_to_vec() + .expect("unable to serialize sections"); + Self { + ciphertext: tpke::encrypt(&bytes, pubkey.0, &mut rng), + } + } + + /// Decrypt this ciphertext back to the original plaintext sections. + #[cfg(feature = "ferveo-tpke")] + pub fn decrypt( + &self, + privkey: ::G2Affine, + ) -> std::io::Result> { + let bytes = tpke::decrypt(&self.ciphertext, privkey); + Vec::
::try_from_slice(&bytes) + } + + /// Get the hash of this ciphertext section. This operation is done in such + /// a way it matches the hash of the type pun + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize decrypted tx"), + ); + hasher + } +} + +#[cfg(feature = "ferveo-tpke")] +impl borsh::ser::BorshSerialize for Ciphertext { + fn serialize( + &self, + writer: &mut W, + ) -> std::io::Result<()> { + use ark_serialize::CanonicalSerialize; + let tpke::Ciphertext { + nonce, + ciphertext, + auth_tag, + } = &self.ciphertext; + // Serialize the nonce into bytes + let mut nonce_buffer = Vec::::new(); + nonce.serialize(&mut nonce_buffer).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?; + // serialize the auth_tag to bytes + let mut tag_buffer = Vec::::new(); + auth_tag.serialize(&mut tag_buffer).map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?; + let mut payload = Vec::new(); + // serialize the three byte arrays + BorshSerialize::serialize( + &(nonce_buffer, ciphertext, tag_buffer), + &mut payload, + )?; + // now serialize the ciphertext payload with length + BorshSerialize::serialize(&payload, writer) + } +} + +#[cfg(feature = "ferveo-tpke")] +impl borsh::BorshDeserialize for Ciphertext { + fn deserialize(buf: &mut &[u8]) -> std::io::Result { + type VecTuple = (u32, Vec, Vec, Vec); + let (_length, nonce, ciphertext, auth_tag): VecTuple = + BorshDeserialize::deserialize(buf)?; + Ok(Self { + ciphertext: tpke::Ciphertext { + nonce: ark_serialize::CanonicalDeserialize::deserialize( + &*nonce, + ) + .map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?, + ciphertext, + auth_tag: ark_serialize::CanonicalDeserialize::deserialize( + &*auth_tag, + ) + .map_err(|err| { + std::io::Error::new(std::io::ErrorKind::InvalidData, err) + })?, + }, + }) + } +} + +#[cfg(feature = "ferveo-tpke")] +impl borsh::BorshSchema for Ciphertext { + fn add_definitions_recursively( + definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + // Encoded as `(Vec, Vec, Vec)` + let elements = "u8".into(); + let definition = borsh::schema::Definition::Sequence { elements }; + definitions.insert("Vec".into(), definition); + let elements = + vec!["Vec".into(), "Vec".into(), "Vec".into()]; + let definition = borsh::schema::Definition::Tuple { elements }; + definitions.insert(Self::declaration(), definition); + } + + fn declaration() -> borsh::schema::Declaration { + "Ciphertext".into() + } +} + +/// A helper struct for serializing EncryptedTx structs +/// as an opaque blob +#[cfg(feature = "ferveo-tpke")] +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(transparent)] +struct SerializedCiphertext { + payload: Vec, +} + +#[cfg(feature = "ferveo-tpke")] +impl From for SerializedCiphertext { + fn from(tx: Ciphertext) -> Self { + SerializedCiphertext { + payload: tx + .try_to_vec() + .expect("Unable to serialize encrypted transaction"), + } + } +} + +#[cfg(feature = "ferveo-tpke")] +impl From for Ciphertext { + fn from(ser: SerializedCiphertext) -> Self { + BorshDeserialize::deserialize(&mut ser.payload.as_ref()) + .expect("Unable to deserialize encrypted transactions") + } +} + +#[derive(serde::Serialize, serde::Deserialize)] +struct TransactionSerde(Vec); + +impl From> for TransactionSerde { + fn from(tx: Vec) -> Self { + Self(tx) + } +} + +impl From for Vec { + fn from(tx: TransactionSerde) -> Vec { + tx.0 } } -impl PartialOrd for Signed +fn borsh_serde( + obj: &impl BorshSerialize, + ser: S, +) -> std::result::Result where - T: BorshSerialize + BorshDeserialize + PartialOrd, + S: serde::Serializer, + T: From>, + T: serde::Serialize, { - fn partial_cmp(&self, other: &Self) -> Option { - self.data.partial_cmp(&other.data) - } + Into::::into(obj.try_to_vec().unwrap()).serialize(ser) } -impl Signed +fn serde_borsh<'de, T, S, U>(ser: S) -> std::result::Result where - T: BorshSerialize + BorshDeserialize, + S: serde::Deserializer<'de>, + T: Into>, + T: serde::Deserialize<'de>, + U: BorshDeserialize, { - /// Initialize a new signed data. - pub fn new(keypair: &common::SecretKey, data: T) -> Self { - let to_sign = data - .try_to_vec() - .expect("Encoding data for signing shouldn't fail"); - let sig = common::SigScheme::sign(keypair, to_sign); - Self { data, sig } + BorshDeserialize::try_from_slice(&Into::>::into(T::deserialize( + ser, + )?)) + .map_err(S::Error::custom) +} + +/// A structure to facilitate Serde (de)serializations of Builders +#[derive(serde::Serialize, serde::Deserialize)] +struct BuilderSerde(Vec); + +impl From> for BuilderSerde { + fn from(tx: Vec) -> Self { + Self(tx) } +} - /// Verify that the data has been signed by the secret key - /// counterpart of the given public key. - pub fn verify( - &self, - pk: &common::PublicKey, - ) -> std::result::Result<(), VerifySigError> { - let bytes = self - .data - .try_to_vec() - .expect("Encoding data for verifying signature shouldn't fail"); - common::SigScheme::verify_signature_raw(pk, &bytes, &self.sig) +impl From for Vec { + fn from(tx: BuilderSerde) -> Vec { + tx.0 } } -/// A Tx with its code replaced by a hash salted with the Borsh -/// serialized timestamp of the transaction. This structure will almost -/// certainly be smaller than a Tx, yet in the usual cases it contains -/// enough information to confirm that the Tx is as intended and make a -/// non-malleable signature. +/// A structure to facilitate Serde (de)serializations of SaplingMetadata +#[derive(serde::Serialize, serde::Deserialize)] +pub struct SaplingMetadataSerde(Vec); + +impl From> for SaplingMetadataSerde { + fn from(tx: Vec) -> Self { + Self(tx) + } +} + +impl From for Vec { + fn from(tx: SaplingMetadataSerde) -> Vec { + tx.0 + } +} + +/// A section providing the auxiliary inputs used to construct a MASP +/// transaction #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, Debug, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] -pub struct SigningTx { - pub code_hash: [u8; 32], - pub data: Option>, - pub timestamp: DateTimeUtc, +pub struct MaspBuilder { + /// The MASP transaction that this section witnesses + pub target: crate::types::hash::Hash, + /// The decoded set of asset types used by the transaction. Useful for + /// offline wallets trying to display AssetTypes. + pub asset_types: HashSet<(Address, Epoch)>, + /// Track how Info objects map to descriptors and outputs + #[serde( + serialize_with = "borsh_serde::", + deserialize_with = "serde_borsh::" + )] + pub metadata: SaplingMetadata, + /// The data that was used to construct the target transaction + #[serde( + serialize_with = "borsh_serde::", + deserialize_with = "serde_borsh::" + )] + pub builder: Builder<(), (), ExtendedFullViewingKey, ()>, +} + +impl MaspBuilder { + /// Get the hash of this ciphertext section. This operation is done in such + /// a way it matches the hash of the type pun + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize MASP builder"), + ); + hasher + } +} + +impl borsh::BorshSchema for MaspBuilder { + fn add_definitions_recursively( + _definitions: &mut std::collections::HashMap< + borsh::schema::Declaration, + borsh::schema::Definition, + >, + ) { + } + + fn declaration() -> borsh::schema::Declaration { + "Builder".into() + } +} + +/// A section of a transaction. Carries an independent piece of information +/// necessary for the processing of a transaction. +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub enum Section { + /// Transaction data that needs to be sent to hardware wallets + Data(Data), + /// Transaction data that does not need to be sent to hardware wallets + ExtraData(Code), + /// Transaction code. Sending to hardware wallets optional + Code(Code), + /// A transaction signature. Often produced by hardware wallets + Signature(Signature), + /// Ciphertext obtained by encrypting arbitrary transaction sections + Ciphertext(Ciphertext), + /// Embedded MASP transaction section + #[serde( + serialize_with = "borsh_serde::", + deserialize_with = "serde_borsh::" + )] + MaspTx(Transaction), + /// A section providing the auxiliary inputs used to construct a MASP + /// transaction. Only send to wallet, never send to protocol. + MaspBuilder(MaspBuilder), +} + +impl Section { + /// Hash this section. Section hashes are useful for signatures and also for + /// allowing transaction sections to cross reference. + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + // Get the index corresponding to this variant + let discriminant = + self.try_to_vec().expect("sections should serialize")[0]; + // Use Borsh's discriminant in the Section's hash + hasher.update([discriminant]); + match self { + Self::Data(data) => data.hash(hasher), + Self::ExtraData(extra) => extra.hash(hasher), + Self::Code(code) => code.hash(hasher), + Self::Signature(sig) => sig.hash(hasher), + Self::Ciphertext(ct) => ct.hash(hasher), + Self::MaspBuilder(mb) => mb.hash(hasher), + Self::MaspTx(tx) => { + hasher.update(tx.txid().as_ref()); + hasher + } + } + } + + /// Sign over the hash of this section and return a signature section that + /// can be added to the container transaction + pub fn sign(&self, sec_key: &common::SecretKey) -> Signature { + let mut hasher = Sha256::new(); + self.hash(&mut hasher); + Signature::new( + &crate::types::hash::Hash(hasher.finalize().into()), + sec_key, + ) + } + + /// Extract the data from this section if possible + pub fn data(&self) -> Option { + if let Self::Data(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the extra data from this section if possible + pub fn extra_data_sec(&self) -> Option { + if let Self::ExtraData(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the extra data from this section if possible + pub fn extra_data(&self) -> Option> { + if let Self::ExtraData(data) = self { + data.code.id() + } else { + None + } + } + + /// Extract the code from this section is possible + pub fn code_sec(&self) -> Option { + if let Self::Code(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the code from this section is possible + pub fn code(&self) -> Option> { + if let Self::Code(data) = self { + data.code.id() + } else { + None + } + } + + /// Extract the signature from this section if possible + pub fn signature(&self) -> Option { + if let Self::Signature(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the ciphertext from this section if possible + pub fn ciphertext(&self) -> Option { + if let Self::Ciphertext(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the MASP transaction from this section if possible + pub fn masp_tx(&self) -> Option { + if let Self::MaspTx(data) = self { + Some(data.clone()) + } else { + None + } + } + + /// Extract the MASP builder from this section if possible + pub fn masp_builder(&self) -> Option { + if let Self::MaspBuilder(data) = self { + Some(data.clone()) + } else { + None + } + } +} + +/// A Namada transaction header indicating where transaction subcomponents can +/// be found +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub struct Header { + /// The chain which this transaction is being submitted to pub chain_id: ChainId, + /// The time at which this transaction expires pub expiration: Option, + /// A transaction timestamp + pub timestamp: DateTimeUtc, + /// The SHA-256 hash of the transaction's code section + pub code_hash: crate::types::hash::Hash, + /// The SHA-256 hash of the transaction's data section + pub data_hash: crate::types::hash::Hash, + /// The type of this transaction + pub tx_type: TxType, } -impl SigningTx { - pub fn hash(&self) -> [u8; 32] { - let timestamp = Some(self.timestamp.into()); - let expiration = self.expiration.map(|e| e.into()); - let mut bytes = vec![]; - types::Tx { - code_or_hash: self.code_hash.to_vec(), - data: self.data.clone(), - timestamp, - chain_id: self.chain_id.as_str().to_owned(), - expiration, - } - .encode(&mut bytes) - .expect("encoding a transaction failed"); - hash_tx(&bytes).0 - } - - /// Sign a transaction using [`SignedTxData`]. - pub fn sign(self, keypair: &common::SecretKey) -> Self { - let to_sign = self.hash(); - let sig = common::SigScheme::sign(keypair, to_sign); - let signed = SignedTxData { - data: self.data, - sig, - } - .try_to_vec() - .expect("Encoding transaction data shouldn't fail"); - SigningTx { - code_hash: self.code_hash, - data: Some(signed), - timestamp: self.timestamp, - chain_id: self.chain_id, - expiration: self.expiration, - } - } - - /// Verify that the transaction has been signed by the secret key - /// counterpart of the given public key. - pub fn verify_sig( - &self, - pk: &common::PublicKey, - sig: &common::Signature, - ) -> std::result::Result<(), VerifySigError> { - // Try to get the transaction data from decoded `SignedTxData` - let tx_data = self.data.clone().ok_or(VerifySigError::MissingData)?; - let signed_tx_data = SignedTxData::try_from_slice(&tx_data[..]) - .expect("Decoding transaction data shouldn't fail"); - let data = signed_tx_data.data; - let tx = SigningTx { - code_hash: self.code_hash, - data, - timestamp: self.timestamp, - chain_id: self.chain_id.clone(), - expiration: self.expiration, - }; - let signed_data = tx.hash(); - common::SigScheme::verify_signature_raw(pk, &signed_data, sig) - } - - /// Expand this reduced Tx using the supplied code only if the the code - /// hashes to the stored code hash - pub fn expand(self, code: Vec) -> Option { - if hash_tx(&code).0 == self.code_hash { - Some(Tx { - code_or_hash: code, - data: self.data, - timestamp: self.timestamp, - chain_id: self.chain_id, - expiration: self.expiration, - }) +impl Header { + /// Make a new header of the given transaction type + pub fn new(tx_type: TxType) -> Self { + Self { + tx_type, + chain_id: ChainId::default(), + expiration: None, + timestamp: DateTimeUtc::now(), + code_hash: crate::types::hash::Hash::default(), + data_hash: crate::types::hash::Hash::default(), + } + } + + /// Get the hash of this transaction header. + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec() + .expect("unable to serialize transaction header"), + ); + hasher + } + + /// Get the wrapper header if it is present + pub fn wrapper(&self) -> Option { + if let TxType::Wrapper(wrapper) = &self.tx_type { + Some(*wrapper.clone()) + } else { + None + } + } + + /// Get the decrypted header if it is present + pub fn decrypted(&self) -> Option { + if let TxType::Decrypted(decrypted) = &self.tx_type { + Some(decrypted.clone()) } else { None } } -} -impl From for SigningTx { - fn from(tx: Tx) -> SigningTx { - SigningTx { - code_hash: hash_tx(&tx.code_or_hash).0, - data: tx.data, - timestamp: tx.timestamp, - chain_id: tx.chain_id, - expiration: tx.expiration, + #[cfg(feature = "ferveo-tpke")] + /// Get the protocol header if it is present + pub fn protocol(&self) -> Option { + if let TxType::Protocol(protocol) = &self.tx_type { + Some(*protocol.clone()) + } else { + None } } } -/// A SigningTx but with the full code embedded. This structure will almost -/// certainly be bigger than SigningTxs and contains enough information to -/// execute the transaction. +/// Errors relating to decrypting a wrapper tx and its +/// encrypted payload from a Tx type +#[allow(missing_docs)] +#[derive(thiserror::Error, Debug, PartialEq)] +pub enum TxError { + #[error("{0}")] + Unsigned(String), + #[error("{0}")] + SigError(String), + #[error("Failed to deserialize Tx: {0}")] + Deserialization(String), +} + +/// A Namada transaction is represented as a header followed by a series of +/// seections providing additional details. #[derive( - Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema, Hash, + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, )] pub struct Tx { - pub code_or_hash: Vec, - pub data: Option>, - pub timestamp: DateTimeUtc, - pub chain_id: ChainId, - pub expiration: Option, + /// Type indicating how to process transaction + pub header: Header, + /// Additional details necessary to process transaction + pub sections: Vec
, } +/// Deserialize Tx from protobufs impl TryFrom<&[u8]> for Tx { type Error = Error; fn try_from(tx_bytes: &[u8]) -> Result { let tx = types::Tx::decode(tx_bytes).map_err(Error::TxDecodingError)?; - let timestamp = match tx.timestamp { - Some(t) => t.try_into().map_err(Error::InvalidTimestamp)?, - None => return Err(Error::NoTimestampError), - }; - let chain_id = ChainId(tx.chain_id); - let expiration = match tx.expiration { - Some(e) => Some(e.try_into().map_err(Error::InvalidTimestamp)?), - None => None, - }; - - Ok(Tx { - code_or_hash: tx.code_or_hash, - data: tx.data, - timestamp, - chain_id, - expiration, - }) + BorshDeserialize::try_from_slice(&tx.data) + .map_err(Error::TxDeserializingError) } } -impl From for types::Tx { - fn from(tx: Tx) -> Self { - let timestamp = Some(tx.timestamp.into()); - let expiration = tx.expiration.map(|e| e.into()); - - types::Tx { - code_or_hash: tx.code_or_hash, - data: tx.data, - timestamp, - chain_id: tx.chain_id.as_str().to_owned(), - expiration, +impl Tx { + /// Create a transaction of the given type + pub fn new(header: TxType) -> Self { + Tx { + header: Header::new(header), + sections: vec![], } } -} -#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] -impl From for ResponseDeliverTx { - #[cfg(not(feature = "ferveo-tpke"))] - fn from(_tx: Tx) -> ResponseDeliverTx { - Default::default() + /// Get the transaction header + pub fn header(&self) -> Header { + self.header.clone() } - /// Annotate the Tx with meta-data based on its contents - #[cfg(feature = "ferveo-tpke")] - fn from(tx: Tx) -> ResponseDeliverTx { - use crate::tendermint_proto::abci::{Event, EventAttribute}; + /// Get the transaction header hash + pub fn header_hash(&self) -> crate::types::hash::Hash { + crate::types::hash::Hash( + self.header.hash(&mut Sha256::new()).finalize_reset().into(), + ) + } - #[cfg(feature = "ABCI")] - fn encode_str(x: &str) -> Vec { - x.as_bytes().to_vec() - } - #[cfg(not(feature = "ABCI"))] - fn encode_str(x: &str) -> String { - x.to_string() - } - #[cfg(feature = "ABCI")] - fn encode_string(x: String) -> Vec { - x.into_bytes() - } - #[cfg(not(feature = "ABCI"))] - fn encode_string(x: String) -> String { - x - } - match process_tx(tx) { - Ok(TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - })) => { - let empty_vec = vec![]; - let tx_data = tx.data.as_ref().unwrap_or(&empty_vec); - let signed = - if let Ok(signed) = SignedTxData::try_from_slice(tx_data) { - signed - } else { - return Default::default(); - }; - if let Ok(transfer) = Transfer::try_from_slice( - signed.data.as_ref().unwrap_or(&empty_vec), - ) { - let events = vec![Event { - r#type: "transfer".to_string(), - attributes: vec![ - EventAttribute { - key: encode_str("source"), - value: encode_string(transfer.source.encode()), - index: true, - }, - EventAttribute { - key: encode_str("target"), - value: encode_string(transfer.target.encode()), - index: true, - }, - EventAttribute { - key: encode_str("token"), - value: encode_string(transfer.token.encode()), - index: true, - }, - EventAttribute { - key: encode_str("amount"), - value: encode_string( - transfer.amount.to_string(), - ), - index: true, - }, - ], - }]; - ResponseDeliverTx { - events, - info: "Transfer tx".to_string(), - ..Default::default() - } - } else { - Default::default() - } + /// Update the header whilst maintaining existing cross-references + pub fn update_header(&mut self, tx_type: TxType) -> &mut Self { + self.header.tx_type = tx_type; + self + } + + /// Get the transaction section with the given hash + pub fn get_section( + &self, + hash: &crate::types::hash::Hash, + ) -> Option<&Section> { + for section in &self.sections { + let sechash = crate::types::hash::Hash( + section.hash(&mut Sha256::new()).finalize_reset().into(), + ); + if sechash == *hash { + return Some(section); } - _ => Default::default(), } + None } -} -impl Tx { - /// Create a new transaction. `code_or_hash` should be set as the wasm code - /// bytes or hash. - pub fn new( - code_or_hash: Vec, - data: Option>, - chain_id: ChainId, - expiration: Option, - ) -> Self { - Tx { - code_or_hash, - data, - timestamp: DateTimeUtc::now(), - chain_id, - expiration, + /// Add a new section to the transaction + pub fn add_section(&mut self, section: Section) -> &mut Section { + self.sections.push(section); + self.sections.last_mut().unwrap() + } + + /// Get the hash of this transaction's code from the heeader + pub fn code_sechash(&self) -> &crate::types::hash::Hash { + &self.header.code_hash + } + + /// Set the transaction code hash stored in the header + pub fn set_code_sechash(&mut self, hash: crate::types::hash::Hash) { + self.header.code_hash = hash + } + + /// Get the code designated by the transaction code hash in the header + pub fn code(&self) -> Option> { + match self.get_section(self.code_sechash()) { + Some(Section::Code(section)) => section.code.id(), + _ => None, } } + /// Add the given code to the transaction and set code hash in the header + pub fn set_code(&mut self, code: Code) -> &mut Section { + let sec = Section::Code(code); + let mut hasher = Sha256::new(); + sec.hash(&mut hasher); + let hash = crate::types::hash::Hash(hasher.finalize().into()); + self.set_code_sechash(hash); + self.sections.push(sec); + self.sections.last_mut().unwrap() + } + + /// Get the transaction data hash stored in the header + pub fn data_sechash(&self) -> &crate::types::hash::Hash { + &self.header.data_hash + } + + /// Set the transaction data hash stored in the header + pub fn set_data_sechash(&mut self, hash: crate::types::hash::Hash) { + self.header.data_hash = hash + } + + /// Add the given code to the transaction and set the hash in the header + pub fn set_data(&mut self, data: Data) -> &mut Section { + let sec = Section::Data(data); + let mut hasher = Sha256::new(); + sec.hash(&mut hasher); + let hash = crate::types::hash::Hash(hasher.finalize().into()); + self.set_data_sechash(hash); + self.sections.push(sec); + self.sections.last_mut().unwrap() + } + + /// Get the data designated by the transaction data hash in the header + pub fn data(&self) -> Option> { + match self.get_section(self.data_sechash()) { + Some(Section::Data(data)) => Some(data.data.clone()), + _ => None, + } + } + + /// Convert this transaction into protobufs pub fn to_bytes(&self) -> Vec { let mut bytes = vec![]; - let tx: types::Tx = self.clone().into(); + let tx: types::Tx = types::Tx { + data: self.try_to_vec().expect("encoding a transaction failed"), + }; tx.encode(&mut bytes) .expect("encoding a transaction failed"); bytes } - pub fn hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).hash() - } - - pub fn unsigned_hash(&self) -> [u8; 32] { - match self.data { - Some(ref data) => { - match SignedTxData::try_from_slice(data) { - Ok(signed_data) => { - // Reconstruct unsigned tx - let unsigned_tx = Tx { - code_or_hash: self.code_or_hash.clone(), - data: signed_data.data, - timestamp: self.timestamp, - chain_id: self.chain_id.clone(), - expiration: self.expiration, - }; - unsigned_tx.hash() - } - Err(_) => { - // Unsigned tx - self.hash() - } + /// Verify that the section with the given hash has been signed by the given + /// public key + pub fn verify_signature( + &self, + pk: &common::PublicKey, + hash: &crate::types::hash::Hash, + ) -> std::result::Result<(), VerifySigError> { + for section in &self.sections { + if let Section::Signature(sig_sec) = section { + if sig_sec.pub_key == *pk && sig_sec.target == *hash { + return common::SigScheme::verify_signature_raw( + pk, + &hash.0, + &sig_sec.signature, + ); } } - None => { - // Unsigned tx - self.hash() + } + Err(VerifySigError::MissingData) + } + + /// Validate any and all ciphertexts stored in this transaction + #[cfg(feature = "ferveo-tpke")] + pub fn validate_ciphertext(&self) -> bool { + let mut valid = true; + for section in &self.sections { + if let Section::Ciphertext(ct) = section { + valid = valid && ct.ciphertext.check( + &::G1Prepared::from( + -::G1Affine::prime_subgroup_generator(), + ) + ); + } + } + valid + } + + /// Decrypt any and all ciphertexts stored in this transaction use the + /// given decryption key + #[cfg(feature = "ferveo-tpke")] + pub fn decrypt( + &mut self, + privkey: ::G2Affine, + ) -> std::result::Result<(), WrapperTxErr> { + // Iterate backwrds to sidestep the effects of deletion on indexing + for i in (0..self.sections.len()).rev() { + if let Section::Ciphertext(ct) = &self.sections[i] { + // Add all the deecrypted sections + self.sections.extend( + ct.decrypt(privkey).map_err(|_| WrapperTxErr::InvalidTx)? + ); + // Remove the original ciphertext + self.sections.remove(i); } } + self.data().ok_or(WrapperTxErr::DecryptedHash)?; + self.get_section(self.code_sechash()) + .ok_or(WrapperTxErr::DecryptedHash)?; + Ok(()) } - pub fn code_hash(&self) -> [u8; 32] { - SigningTx::from(self.clone()).code_hash + /// Encrypt all sections in this transaction other than the header and + /// signatures over it + #[cfg(feature = "ferveo-tpke")] + pub fn encrypt(&mut self, pubkey: &EncryptionKey) { + let header_hash = self.header_hash(); + let mut plaintexts = vec![]; + // Iterate backwrds to sidestep the effects of deletion on indexing + for i in (0..self.sections.len()).rev() { + match &self.sections[i] { + Section::Signature(sig) if sig.target == header_hash => {} + // Add eligible section to the list of sections to encrypt + _ => plaintexts.push(self.sections.remove(i)), + } + } + // Encrypt all eligible sections in one go + self.sections.push(Section::Ciphertext(Ciphertext::new( + plaintexts, + pubkey, + ))); } - /// Sign a transaction using [`SignedTxData`]. - pub fn sign(self, keypair: &common::SecretKey) -> Self { - let code = self.code_or_hash.clone(); - SigningTx::from(self) - .sign(keypair) - .expand(code) - .expect("code hashes to unexpected value") + /// Determines the type of the input Tx + /// + /// If it is a raw Tx, signed or not, the Tx is + /// returned unchanged inside an enum variant stating its type. + /// + /// If it is a decrypted tx, signing it adds no security so we + /// extract the signed data without checking the signature (if it + /// is signed) or return as is. Either way, it is returned in + /// an enum variant stating its type. + /// + /// If it is a WrapperTx, we extract the signed data of + /// the Tx and verify it is of the appropriate form. This means + /// 1. The wrapper tx is indeed signed + /// 2. The signature is valid + pub fn validate_header(&self) -> std::result::Result<(), TxError> { + match &self.header.tx_type { + // verify signature and extract signed data + TxType::Wrapper(wrapper) => { + self.verify_signature(&wrapper.pk, &self.header_hash()) + .map_err(|err| { + TxError::SigError(format!( + "WrapperTx signature verification failed: {}", + err + )) + })?; + Ok(()) + } + // verify signature and extract signed data + #[cfg(feature = "ferveo-tpke")] + TxType::Protocol(protocol) => { + self.verify_signature(&protocol.pk, &self.header_hash()) + .map_err(|err| { + TxError::SigError(format!( + "ProtocolTx signature verification failed: {}", + err + )) + })?; + Ok(()) + } + // we extract the signed data, but don't check the signature + TxType::Decrypted(_) => Ok(()), + // return as is + TxType::Raw => Ok(()), + } } - /// Verify that the transaction has been signed by the secret key - /// counterpart of the given public key. - pub fn verify_sig( - &self, - pk: &common::PublicKey, - sig: &common::Signature, - ) -> std::result::Result<(), VerifySigError> { - SigningTx::from(self.clone()).verify_sig(pk, sig) + /// Filter out all the sections that must not be submitted to the protocol + /// and return them. + pub fn protocol_filter(&mut self) -> Vec
{ + let mut filtered = Vec::new(); + for i in (0..self.sections.len()).rev() { + if let Section::MaspBuilder(_) = self.sections[i] { + // MASP Builders containin extended full viewing keys amongst + // other private information and must be removed prior to + // submission to protocol + filtered.push(self.sections.remove(i)); + } + } + filtered + } + + /// Filter out all the sections that need not be sent to the hardware wallet + /// and return them + pub fn wallet_filter(&mut self) -> Vec
{ + let mut filtered = Vec::new(); + for i in (0..self.sections.len()).rev() { + match &mut self.sections[i] { + // This section is known to be large and can be contracted + Section::Code(section) => { + filtered.push(Section::Code(section.clone())); + section.code.contract(); + } + // This section is known to be large and can be contracted + Section::ExtraData(section) => { + filtered.push(Section::ExtraData(section.clone())); + section.code.contract(); + } + // Everything else is fine to add + _ => {} + } + } + filtered + } +} + +#[cfg(any(feature = "tendermint", feature = "tendermint-abcipp"))] +impl From for ResponseDeliverTx { + #[cfg(not(feature = "ferveo-tpke"))] + fn from(_tx: Tx) -> ResponseDeliverTx { + Default::default() + } + + /// Annotate the Tx with meta-data based on its contents + #[cfg(feature = "ferveo-tpke")] + fn from(tx: Tx) -> ResponseDeliverTx { + use crate::tendermint_proto::abci::{Event, EventAttribute}; + + // If data cannot be extracteed, then attach no events + let tx_data = if let Some(data) = tx.data() { + data + } else { + return Default::default(); + }; + // If the data is not a Transfer, then attach no events + let transfer = if let Ok(transfer) = Transfer::try_from_slice(&tx_data) + { + transfer + } else { + return Default::default(); + }; + // Otherwise attach all Transfer events + let events = vec![Event { + r#type: "transfer".to_string(), + attributes: vec![ + EventAttribute { + key: "source".to_string(), + value: transfer.source.encode(), + index: true, + }, + EventAttribute { + key: "target".to_string(), + value: transfer.target.encode(), + index: true, + }, + EventAttribute { + key: "token".to_string(), + value: transfer.token.encode(), + index: true, + }, + EventAttribute { + key: "amount".to_string(), + value: transfer.amount.to_string(), + index: true, + }, + ], + }]; + ResponseDeliverTx { + events, + info: "Transfer tx".to_string(), + ..Default::default() + } } } @@ -529,34 +1209,6 @@ impl Dkg { mod tests { use super::*; - #[test] - fn test_tx() { - let code = "wasm code".as_bytes().to_owned(); - let data = "arbitrary data".as_bytes().to_owned(); - let chain_id = ChainId::default(); - let tx = - Tx::new(code.clone(), Some(data.clone()), chain_id.clone(), None); - - let bytes = tx.to_bytes(); - let tx_from_bytes = - Tx::try_from(bytes.as_ref()).expect("decoding failed"); - assert_eq!(tx_from_bytes, tx); - - let types_tx = types::Tx { - code_or_hash: code, - data: Some(data), - timestamp: None, - chain_id: chain_id.0, - expiration: None, - }; - let mut bytes = vec![]; - types_tx.encode(&mut bytes).expect("encoding failed"); - match Tx::try_from(bytes.as_ref()) { - Err(Error::NoTimestampError) => {} - _ => panic!("unexpected result"), - } - } - #[test] fn test_dkg_gossip_message() { let data = "arbitrary string".to_owned(); @@ -578,4 +1230,91 @@ mod tests { let dkg_from_types = Dkg::from(types_dkg); assert_eq!(dkg_from_types, dkg); } + + /// Test that encryption and decryption are inverses. + #[cfg(feature = "ferveo-tpke")] + #[test] + fn test_encrypt_decrypt() { + // The trivial public - private keypair + let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); + let privkey = ::G2Affine::prime_subgroup_generator(); + // generate encrypted payload + let plaintext = vec![ + Section::Data(Data::new("Super secret stuff".as_bytes().to_vec())), + ]; + let encrypted = Ciphertext::new(plaintext.clone(), &pubkey); + // check that encryption doesn't do trivial things + assert_ne!( + encrypted.ciphertext.ciphertext, + plaintext.try_to_vec().expect("Test failed") + ); + // decrypt the payload and check we got original data back + let decrypted = encrypted.decrypt(privkey); + assert_eq!( + decrypted + .expect("Test failed") + .try_to_vec() + .expect("Test failed"), + plaintext.try_to_vec().expect("Test failed"), + ); + } + + /// Test that serializing and deserializing again via Borsh produces + /// original payload + #[cfg(feature = "ferveo-tpke")] + #[test] + fn test_encrypted_tx_round_trip_borsh() { + // The trivial public - private keypair + let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); + let privkey = ::G2Affine::prime_subgroup_generator(); + // generate encrypted payload + let plaintext = vec![ + Section::Data(Data::new("Super secret stuff".as_bytes().to_vec())), + ]; + let encrypted = Ciphertext::new(plaintext.clone(), &pubkey); + // serialize via Borsh + let borsh = encrypted.try_to_vec().expect("Test failed"); + // deserialize again + let new_encrypted: Ciphertext = + BorshDeserialize::deserialize(&mut borsh.as_ref()) + .expect("Test failed"); + // check that decryption works as expected + let decrypted = new_encrypted.decrypt(privkey); + assert_eq!( + decrypted + .expect("Test failed") + .try_to_vec() + .expect("Test failed"), + plaintext.try_to_vec().expect("Test failed"), + ); + } + + /// Test that serializing and deserializing again via Serde produces + /// original payload + #[cfg(feature = "ferveo-tpke")] + #[test] + fn test_encrypted_tx_round_trip_serde() { + // The trivial public - private keypair + let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); + let privkey = ::G2Affine::prime_subgroup_generator(); + // generate encrypted payload + let plaintext = vec![ + Section::Data(Data::new("Super secret stuff".as_bytes().to_vec())), + ]; + let encrypted = Ciphertext::new(plaintext.clone(), &pubkey); + // serialize via Serde + let js = serde_json::to_string(&encrypted).expect("Test failed"); + // deserialize it again + let new_encrypted: Ciphertext = + serde_json::from_str(&js).expect("Test failed"); + let decrypted = new_encrypted.decrypt(privkey); + // check that decryption works as expected + assert_eq!( + decrypted + .expect("Test failed") + .try_to_vec() + .expect("Test failed"), + plaintext.try_to_vec().expect("Test failed"), + ); + } } diff --git a/core/src/types/hash.rs b/core/src/types/hash.rs index 080826a415..db45d2ab58 100644 --- a/core/src/types/hash.rs +++ b/core/src/types/hash.rs @@ -31,6 +31,7 @@ pub type HashResult = std::result::Result; #[derive( Clone, + Copy, Debug, Default, PartialOrd, diff --git a/core/src/types/internal.rs b/core/src/types/internal.rs index d13d392381..a15efc8105 100644 --- a/core/src/types/internal.rs +++ b/core/src/types/internal.rs @@ -48,12 +48,14 @@ impl From for HostEnvResult { mod tx_queue { use borsh::{BorshDeserialize, BorshSerialize}; + use crate::proto::Tx; + /// A wrapper for `crate::types::transaction::WrapperTx` to conditionally /// add `has_valid_pow` flag for only used in testnets. #[derive(Debug, Clone, BorshDeserialize, BorshSerialize)] - pub struct WrapperTxInQueue { + pub struct TxInQueue { /// Wrapper tx - pub tx: crate::types::transaction::WrapperTx, + pub tx: Tx, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. @@ -64,23 +66,21 @@ mod tx_queue { #[derive(Default, Debug, Clone, BorshDeserialize, BorshSerialize)] /// Wrapper txs to be decrypted in the next block proposal - pub struct TxQueue(std::collections::VecDeque); + pub struct TxQueue(std::collections::VecDeque); impl TxQueue { /// Add a new wrapper at the back of the queue - pub fn push(&mut self, wrapper: WrapperTxInQueue) { + pub fn push(&mut self, wrapper: TxInQueue) { self.0.push_back(wrapper); } /// Remove the wrapper at the head of the queue - pub fn pop(&mut self) -> Option { + pub fn pop(&mut self) -> Option { self.0.pop_front() } /// Get an iterator over the queue - pub fn iter( - &self, - ) -> impl std::iter::Iterator { + pub fn iter(&self) -> impl std::iter::Iterator { self.0.iter() } @@ -92,11 +92,11 @@ mod tx_queue { /// Get reference to the element at the given index. /// Returns [`None`] if index exceeds the queue lenght. - pub fn get(&self, index: usize) -> Option<&WrapperTxInQueue> { + pub fn get(&self, index: usize) -> Option<&TxInQueue> { self.0.get(index) } } } #[cfg(feature = "ferveo-tpke")] -pub use tx_queue::{TxQueue, WrapperTxInQueue}; +pub use tx_queue::{TxInQueue, TxQueue}; diff --git a/core/src/types/masp.rs b/core/src/types/masp.rs index c0ccb67d1e..5c2b3fb2d4 100644 --- a/core/src/types/masp.rs +++ b/core/src/types/masp.rs @@ -132,7 +132,7 @@ impl<'de> serde::Deserialize<'de> for ExtendedViewingKey { BorshSerialize, BorshDeserialize, )] -pub struct PaymentAddress(masp_primitives::primitives::PaymentAddress, bool); +pub struct PaymentAddress(masp_primitives::sapling::PaymentAddress, bool); impl PaymentAddress { /// Turn this PaymentAddress into a pinned/unpinned one @@ -157,14 +157,14 @@ impl PaymentAddress { } } -impl From for masp_primitives::primitives::PaymentAddress { +impl From for masp_primitives::sapling::PaymentAddress { fn from(addr: PaymentAddress) -> Self { addr.0 } } -impl From for PaymentAddress { - fn from(addr: masp_primitives::primitives::PaymentAddress) -> Self { +impl From for PaymentAddress { + fn from(addr: masp_primitives::sapling::PaymentAddress) -> Self { Self(addr, false) } } @@ -222,7 +222,7 @@ impl FromStr for PaymentAddress { }; let bytes: Vec = FromBase32::from_base32(&base32) .map_err(DecodeError::DecodeBase32)?; - masp_primitives::primitives::PaymentAddress::from_bytes( + masp_primitives::sapling::PaymentAddress::from_bytes( &bytes.try_into().map_err(addr_len_err)?, ) .ok_or_else(addr_data_err) @@ -366,6 +366,14 @@ impl TransferSource { _ => None, } } + + /// Get the contained Address, if any + pub fn address(&self) -> Option
{ + match self { + Self::Address(x) => Some(x.clone()), + _ => None, + } + } } impl Display for TransferSource { diff --git a/core/src/types/time.rs b/core/src/types/time.rs index af596db545..e06dbc21c7 100644 --- a/core/src/types/time.rs +++ b/core/src/types/time.rs @@ -96,7 +96,19 @@ impl From for DurationNanos { pub struct Rfc3339String(pub String); /// A duration in seconds precision. -#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive( + Clone, + Copy, + Debug, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + serde::Serialize, + serde::Deserialize, +)] +#[serde(try_from = "Rfc3339String", into = "Rfc3339String")] pub struct DateTimeUtc(pub DateTime); impl DateTimeUtc { @@ -154,7 +166,7 @@ impl BorshSerialize for DateTimeUtc { writer: &mut W, ) -> std::io::Result<()> { let raw = self.0.to_rfc3339(); - raw.serialize(writer) + BorshSerialize::serialize(&raw, writer) } } diff --git a/core/src/types/token.rs b/core/src/types/token.rs index 9c1433b464..e6d6aa924c 100644 --- a/core/src/types/token.rs +++ b/core/src/types/token.rs @@ -5,13 +5,13 @@ use std::ops::{Add, AddAssign, Mul, Sub, SubAssign}; use std::str::FromStr; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; -use masp_primitives::transaction::Transaction; use rust_decimal::prelude::{Decimal, ToPrimitive}; use serde::{Deserialize, Serialize}; use thiserror::Error; use crate::ibc::applications::transfer::Amount as IbcAmount; use crate::types::address::{masp, Address, DecodeError as AddressError}; +use crate::types::hash::Hash; use crate::types::storage::{DbKeySeg, Key, KeySeg}; /// Amount in micro units. For different granularity another representation @@ -491,7 +491,7 @@ pub struct Transfer { /// The unused storage location at which to place TxId pub key: Option, /// Shielded transaction part - pub shielded: Option, + pub shielded: Option, } #[allow(missing_docs)] diff --git a/core/src/types/transaction/decrypted.rs b/core/src/types/transaction/decrypted.rs index 3407179168..59b9965160 100644 --- a/core/src/types/transaction/decrypted.rs +++ b/core/src/types/transaction/decrypted.rs @@ -3,18 +3,21 @@ pub use ark_bls12_381::Bls12_381 as EllipticCurve; /// Integration of Ferveo cryptographic primitives /// to enable decrypting txs. /// *Not wasm compatible* -#[cfg(feature = "ferveo-tpke")] pub mod decrypted_tx { - + #[cfg(feature = "ferveo-tpke")] use ark_ec::PairingEngine; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; + use sha2::{Digest, Sha256}; - use super::EllipticCurve; - use crate::proto::Tx; - use crate::types::chain::ChainId; - use crate::types::transaction::{Hash, TxType, WrapperTx}; - - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + serde::Serialize, + serde::Deserialize, + )] #[allow(clippy::large_enum_variant)] /// Holds the result of attempting to decrypt /// a transaction and the data necessary for @@ -22,11 +25,6 @@ pub mod decrypted_tx { pub enum DecryptedTx { /// The decrypted payload Decrypted { - /// Inner tx. - // For some reason, we get `warning: fields `tx` and - // `has_valid_pow` are never read` even though they are being used! - #[allow(dead_code)] - tx: Tx, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet /// transactions. @@ -38,73 +36,33 @@ pub mod decrypted_tx { has_valid_pow: bool, }, /// The wrapper whose payload could not be decrypted - Undecryptable(WrapperTx), + Undecryptable, } impl DecryptedTx { - /// Convert the inner tx value to bytes - pub fn to_bytes(&self) -> Vec { - match self { - DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - } => tx.to_bytes(), - DecryptedTx::Undecryptable(wrapper) => { - wrapper.try_to_vec().unwrap() - } - } - } - - /// Return the hash used as a commitment to the tx's contents in the - /// wrapper tx that includes this tx as an encrypted payload. The - /// commitment is computed on the unsigned tx if tx is signed - pub fn hash_commitment(&self) -> Hash { - match self { - DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - } => Hash(tx.unsigned_hash()), - DecryptedTx::Undecryptable(wrapper) => wrapper.tx_hash.clone(), - } + /// Produce a SHA-256 hash of this header + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize decrypted tx"), + ); + hasher } } /// Verify that if the encrypted payload was marked /// "undecryptable", we should not be able to decrypt /// it + #[cfg(feature = "ferveo-tpke")] pub fn verify_decrypted_correctly( decrypted: &DecryptedTx, - privkey: ::G2Affine, + mut otx: crate::proto::Tx, + privkey: ::G2Affine, ) -> bool { match decrypted { DecryptedTx::Decrypted { .. } => true, - DecryptedTx::Undecryptable(tx) => tx.decrypt(privkey).is_err(), - } - } - - impl From for Tx { - fn from(decrypted: DecryptedTx) -> Self { - Tx::new( - vec![], - Some( - TxType::Decrypted(decrypted) - .try_to_vec() - .expect("Encrypting transaction should not fail"), - ), - // If undecrytable we cannot extract the ChainId and - // expiration. If instead the tx gets decrypted - // successfully, the correct chain id and - // expiration are serialized inside the data field - // of the Tx, while the ones available - // in the chain_id and expiration field are just placeholders - ChainId(String::new()), - None, - ) + DecryptedTx::Undecryptable => otx.decrypt(privkey).is_err(), } } } -#[cfg(feature = "ferveo-tpke")] pub use decrypted_tx::*; diff --git a/core/src/types/transaction/encrypted.rs b/core/src/types/transaction/encrypted.rs index b73868bbba..277ec6d3fd 100644 --- a/core/src/types/transaction/encrypted.rs +++ b/core/src/types/transaction/encrypted.rs @@ -8,8 +8,6 @@ pub mod encrypted_tx { use ark_ec::PairingEngine; use ark_serialize::{CanonicalDeserialize, CanonicalSerialize}; use borsh::{BorshDeserialize, BorshSerialize}; - use serde::{Deserialize, Serialize}; - use tpke::{encrypt, Ciphertext}; use crate::types::transaction::{AffineCurve, EllipticCurve}; /// The first group in our elliptic curve bilinear pairing @@ -42,180 +40,6 @@ pub mod encrypted_tx { )) } } - - /// We use a specific choice of two groups and bilinear pairing - /// We use a wrapper type to add traits - #[derive(Clone, Debug, Serialize, Deserialize)] - #[serde(from = "SerializedCiphertext")] - #[serde(into = "SerializedCiphertext")] - pub struct EncryptedTx(pub Ciphertext); - - impl EncryptedTx { - /// Encrypt a message to give a new ciphertext - pub fn encrypt(msg: &[u8], pubkey: EncryptionKey) -> Self { - let mut rng = rand::thread_rng(); - Self(encrypt(msg, pubkey.0, &mut rng)) - } - - /// Decrypt a message and return it as raw bytes - pub fn decrypt( - &self, - privkey: ::G2Affine, - ) -> Vec { - tpke::decrypt(&self.0, privkey) - } - } - - impl borsh::ser::BorshSerialize for EncryptedTx { - fn serialize(&self, writer: &mut W) -> std::io::Result<()> { - let Ciphertext { - nonce, - ciphertext, - auth_tag, - } = &self.0; - // Serialize the nonce into bytes - let mut nonce_buffer = Vec::::new(); - nonce - .serialize(&mut nonce_buffer) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - // serialize the auth_tag to bytes - let mut tag_buffer = Vec::::new(); - auth_tag - .serialize(&mut tag_buffer) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?; - // serialize the three byte arrays - BorshSerialize::serialize( - &(nonce_buffer, ciphertext, tag_buffer), - writer, - ) - } - } - - impl borsh::BorshDeserialize for EncryptedTx { - fn deserialize(buf: &mut &[u8]) -> std::io::Result { - type VecTuple = (Vec, Vec, Vec); - let (nonce, ciphertext, auth_tag): VecTuple = - BorshDeserialize::deserialize(buf)?; - Ok(EncryptedTx(Ciphertext { - nonce: CanonicalDeserialize::deserialize(&*nonce) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?, - ciphertext, - auth_tag: CanonicalDeserialize::deserialize(&*auth_tag) - .map_err(|err| Error::new(ErrorKind::InvalidData, err))?, - })) - } - } - - impl borsh::BorshSchema for EncryptedTx { - fn add_definitions_recursively( - definitions: &mut std::collections::HashMap< - borsh::schema::Declaration, - borsh::schema::Definition, - >, - ) { - // Encoded as `(Vec, Vec, Vec)` - let elements = "u8".into(); - let definition = borsh::schema::Definition::Sequence { elements }; - definitions.insert("Vec".into(), definition); - let elements = - vec!["Vec".into(), "Vec".into(), "Vec".into()]; - let definition = borsh::schema::Definition::Tuple { elements }; - definitions.insert(Self::declaration(), definition); - } - - fn declaration() -> borsh::schema::Declaration { - "EncryptedTx".into() - } - } - - /// A helper struct for serializing EncryptedTx structs - /// as an opaque blob - #[derive(Clone, Debug, Serialize, Deserialize)] - #[serde(transparent)] - struct SerializedCiphertext { - payload: Vec, - } - - impl From for SerializedCiphertext { - fn from(tx: EncryptedTx) -> Self { - SerializedCiphertext { - payload: tx - .try_to_vec() - .expect("Unable to serialize encrypted transaction"), - } - } - } - - impl From for EncryptedTx { - fn from(ser: SerializedCiphertext) -> Self { - BorshDeserialize::deserialize(&mut ser.payload.as_ref()) - .expect("Unable to deserialize encrypted transactions") - } - } - - #[cfg(test)] - mod test_encrypted_tx { - use ark_ec::AffineCurve; - - use super::*; - - /// Test that encryption and decryption are inverses. - #[test] - fn test_encrypt_decrypt() { - // The trivial public - private keypair - let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); - let privkey = ::G2Affine::prime_subgroup_generator(); - // generate encrypted payload - let encrypted = - EncryptedTx::encrypt("Super secret stuff".as_bytes(), pubkey); - // check that encryption doesn't do trivial things - assert_ne!(encrypted.0.ciphertext, "Super secret stuff".as_bytes()); - // decrypt the payload and check we got original data back - let decrypted = encrypted.decrypt(privkey); - assert_eq!(decrypted, "Super secret stuff".as_bytes()); - } - - /// Test that serializing and deserializing again via Borsh produces - /// original payload - #[test] - fn test_encrypted_tx_round_trip_borsh() { - // The trivial public - private keypair - let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); - let privkey = ::G2Affine::prime_subgroup_generator(); - // generate encrypted payload - let encrypted = - EncryptedTx::encrypt("Super secret stuff".as_bytes(), pubkey); - // serialize via Borsh - let borsh = encrypted.try_to_vec().expect("Test failed"); - // deserialize again - let new_encrypted: EncryptedTx = - BorshDeserialize::deserialize(&mut borsh.as_ref()) - .expect("Test failed"); - // check that decryption works as expected - let decrypted = new_encrypted.decrypt(privkey); - assert_eq!(decrypted, "Super secret stuff".as_bytes()); - } - - /// Test that serializing and deserializing again via Serde produces - /// original payload - #[test] - fn test_encrypted_tx_round_trip_serde() { - // The trivial public - private keypair - let pubkey = EncryptionKey(::G1Affine::prime_subgroup_generator()); - let privkey = ::G2Affine::prime_subgroup_generator(); - // generate encrypted payload - let encrypted = - EncryptedTx::encrypt("Super secret stuff".as_bytes(), pubkey); - // serialize via Serde - let js = serde_json::to_string(&encrypted).expect("Test failed"); - // deserialize it again - let new_encrypted: EncryptedTx = - serde_json::from_str(&js).expect("Test failed"); - let decrypted = new_encrypted.decrypt(privkey); - // check that decryption works as expected - assert_eq!(decrypted, "Super secret stuff".as_bytes()); - } - } } #[cfg(feature = "ferveo-tpke")] diff --git a/core/src/types/transaction/mod.rs b/core/src/types/transaction/mod.rs index e643a52662..5b9069b731 100644 --- a/core/src/types/transaction/mod.rs +++ b/core/src/types/transaction/mod.rs @@ -32,6 +32,8 @@ use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::key::*; use crate::types::storage; +#[cfg(feature = "ferveo-tpke")] +use crate::types::transaction::protocol::ProtocolTx; /// Get the hash of a transaction pub fn hash_tx(tx_bytes: &[u8]) -> Hash { @@ -198,398 +200,297 @@ pub struct InitValidator { pub validator_vp_code_hash: Hash, } -/// Module that includes helper functions for classifying -/// different types of transactions that the ledger -/// must support as well as conversion functions -/// between them. -#[cfg(feature = "ferveo-tpke")] -pub mod tx_types { - use std::convert::TryFrom; +/// Struct that classifies that kind of Tx +/// based on the contents of its data. +#[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, +)] +pub enum TxType { + /// An ordinary tx + Raw, + /// A Tx that contains an encrypted raw tx + Wrapper(Box), + /// An attempted decryption of a wrapper tx + Decrypted(DecryptedTx), + /// Txs issued by validators as part of internal protocols + #[cfg(feature = "ferveo-tpke")] + Protocol(Box), +} - use thiserror; +impl TxType { + /// Produce a SHA-256 hash of this header + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update(self.try_to_vec().expect("unable to serialize header")); + hasher + } +} +#[cfg(test)] +mod test_process_tx { use super::*; - use crate::proto::{SignedTxData, Tx}; - use crate::types::chain::ChainId; - use crate::types::transaction::protocol::ProtocolTx; + use crate::proto::{Code, Data, Section, Signature, Tx, TxError}; + use crate::types::address::nam; + use crate::types::storage::Epoch; - /// Errors relating to decrypting a wrapper tx and its - /// encrypted payload from a Tx type - #[allow(missing_docs)] - #[derive(thiserror::Error, Debug, PartialEq)] - pub enum TxError { - #[error("{0}")] - Unsigned(String), - #[error("{0}")] - SigError(String), - #[error("Failed to deserialize Tx: {0}")] - Deserialization(String), - } + fn gen_keypair() -> common::SecretKey { + use rand::prelude::ThreadRng; + use rand::thread_rng; - /// Struct that classifies that kind of Tx - /// based on the contents of its data. - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] - pub enum TxType { - /// An ordinary tx - Raw(Tx), - /// A Tx that contains an encrypted raw tx - Wrapper(WrapperTx), - /// An attempted decryption of a wrapper tx - Decrypted(DecryptedTx), - /// Txs issued by validators as part of internal protocols - Protocol(ProtocolTx), + let mut rng: ThreadRng = thread_rng(); + ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap() } - impl From for Tx { - fn from(ty: TxType) -> Self { - Tx::new( - vec![], - Some(ty.try_to_vec().unwrap()), - ChainId(String::new()), /* No need to provide a valid - * ChainId or expiration when - * casting back from - * TxType */ - None, - ) + /// Test that process_tx correctly identifies a raw tx with no + /// data and returns an identical copy + #[test] + fn test_process_tx_raw_tx_no_data() { + let mut outer_tx = Tx::new(TxType::Raw); + let code_sec = outer_tx + .set_code(Code::new("wasm code".as_bytes().to_owned())) + .clone(); + outer_tx.validate_header().expect("Test failed"); + match outer_tx.header().tx_type { + TxType::Raw => assert_eq!( + Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), + outer_tx.header.code_hash, + ), + _ => panic!("Test failed: Expected Raw Tx"), } } - /// We deserialize the Tx data; it should be a TxType which - /// tells us how to handle it. Otherwise, we return an error. - /// The exception is when the Tx data field is empty. We - /// allow this and type it as a Raw TxType. - impl TryFrom for TxType { - type Error = std::io::Error; - - fn try_from(tx: Tx) -> std::io::Result { - if let Some(ref data) = tx.data { - BorshDeserialize::deserialize(&mut data.as_ref()) - } else { - // We allow Txs with empty data fields, which we - // will assume to be of Raw TxType - Ok(TxType::Raw(tx)) + /// Test that process_tx correctly identifies tx containing + /// a raw tx with some data and returns an identical copy + /// of the inner data + #[test] + fn test_process_tx_raw_tx_some_data() { + let mut tx = Tx::new(TxType::Raw); + let code_sec = tx + .set_code(Code::new("wasm code".as_bytes().to_owned())) + .clone(); + let data_sec = tx + .set_data(Data::new("transaction data".as_bytes().to_owned())) + .clone(); + + tx.validate_header().expect("Test failed"); + match tx.header().tx_type { + TxType::Raw => { + assert_eq!( + Hash( + code_sec + .hash(&mut Sha256::new()) + .finalize_reset() + .into() + ), + tx.header().code_hash, + ); + assert_eq!( + Hash( + data_sec + .hash(&mut Sha256::new()) + .finalize_reset() + .into() + ), + tx.header().data_hash, + ); } + _ => panic!("Test failed: Expected Raw Tx"), } } - /// Determines the type of the input Tx - /// - /// If it is a raw Tx, signed or not, the Tx is - /// returned unchanged inside an enum variant stating its type. - /// - /// If it is a decrypted tx, signing it adds no security so we - /// extract the signed data without checking the signature (if it - /// is signed) or return as is. Either way, it is returned in - /// an enum variant stating its type. - /// - /// If it is a WrapperTx, we extract the signed data of - /// the Tx and verify it is of the appropriate form. This means - /// 1. The signed Tx data deserializes to a WrapperTx type - /// 2. The wrapper tx is indeed signed - /// 3. The signature is valid - /// - /// We modify the data of the WrapperTx to contain only the signed - /// data if valid and return it wrapped in a enum variant - /// indicating it is a wrapper. Otherwise, an error is - /// returned indicating the signature was not valid - pub fn process_tx(tx: Tx) -> Result { - if let Some(Ok(SignedTxData { - data: Some(data), - ref sig, - })) = tx - .data - .as_ref() - .map(|data| SignedTxData::try_from_slice(&data[..])) - { - let signed_hash = Tx { - code_or_hash: tx.code_or_hash, - data: Some(data.clone()), - timestamp: tx.timestamp, - chain_id: tx.chain_id.clone(), - expiration: tx.expiration, - } - .hash(); - match TxType::try_from(Tx { - code_or_hash: vec![], - data: Some(data), - timestamp: tx.timestamp, - chain_id: tx.chain_id, - expiration: tx.expiration, - }) - .map_err(|err| TxError::Deserialization(err.to_string()))? - { - // verify signature and extract signed data - TxType::Wrapper(wrapper) => { - wrapper.validate_sig(signed_hash, sig)?; - Ok(TxType::Wrapper(wrapper)) - } - // verify signature and extract signed data - TxType::Protocol(protocol) => { - protocol.validate_sig(signed_hash, sig)?; - Ok(TxType::Protocol(protocol)) - } - // we extract the signed data, but don't check the signature - decrypted @ TxType::Decrypted(_) => Ok(decrypted), - // return as is - raw @ TxType::Raw(_) => Ok(raw), - } - } else { - match TxType::try_from(tx) - .map_err(|err| TxError::Deserialization(err.to_string()))? - { - // we only accept signed wrappers - TxType::Wrapper(_) => Err(TxError::Unsigned( - "Wrapper transactions must be signed".into(), - )), - TxType::Protocol(_) => Err(TxError::Unsigned( - "Protocol transactions must be signed".into(), - )), - // return as is - val => Ok(val), + /// Test that process_tx correctly identifies a raw tx with some + /// signed data and returns an identical copy of the inner data + #[test] + fn test_process_tx_raw_tx_some_signed_data() { + let mut tx = Tx::new(TxType::Raw); + let code_sec = tx + .set_code(Code::new("wasm code".as_bytes().to_owned())) + .clone(); + let data_sec = tx + .set_data(Data::new("transaction data".as_bytes().to_owned())) + .clone(); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &gen_keypair(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &gen_keypair(), + ))); + + tx.validate_header().expect("Test failed"); + match tx.header().tx_type { + TxType::Raw => { + assert_eq!( + Hash( + code_sec + .hash(&mut Sha256::new()) + .finalize_reset() + .into() + ), + tx.header().code_hash, + ); + assert_eq!( + Hash( + data_sec + .hash(&mut Sha256::new()) + .finalize_reset() + .into() + ), + tx.header().data_hash, + ); } + _ => panic!("Test failed: Expected Raw Tx"), } } - #[cfg(test)] - mod test_process_tx { - use super::*; - use crate::types::address::nam; - use crate::types::storage::Epoch; - use crate::types::time::DateTimeUtc; - - fn gen_keypair() -> common::SecretKey { - use rand::prelude::ThreadRng; - use rand::thread_rng; - - let mut rng: ThreadRng = thread_rng(); - ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap() - } - - /// Test that process_tx correctly identifies a raw tx with no - /// data and returns an identical copy - #[test] - fn test_process_tx_raw_tx_no_data() { - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - None, - ChainId::default(), - None, - ); - - match process_tx(tx.clone()).expect("Test failed") { - TxType::Raw(raw) => assert_eq!(tx, raw), - _ => panic!("Test failed: Expected Raw Tx"), - } - } - - /// Test that process_tx correctly identifies tx containing - /// a raw tx with some data and returns an identical copy - /// of the inner data - #[test] - fn test_process_tx_raw_tx_some_data() { - let inner = Tx::new( - "code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - None, - ); - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some( - TxType::Raw(inner.clone()) - .try_to_vec() - .expect("Test failed"), - ), - inner.chain_id.clone(), - None, - ); - - match process_tx(tx).expect("Test failed") { - TxType::Raw(raw) => assert_eq!(inner, raw), - _ => panic!("Test failed: Expected Raw Tx"), - } - } - - /// Test that process_tx correctly identifies a raw tx with some - /// signed data and returns an identical copy of the inner data - #[test] - fn test_process_tx_raw_tx_some_signed_data() { - let inner = Tx::new( - "code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - None, - ); - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some( - TxType::Raw(inner.clone()) - .try_to_vec() - .expect("Test failed"), - ), - inner.chain_id.clone(), - None, - ) - .sign(&gen_keypair()); - - match process_tx(tx).expect("Test failed") { - TxType::Raw(raw) => assert_eq!(inner, raw), - _ => panic!("Test failed: Expected Raw Tx"), + /// Test that process_tx correctly identifies a wrapper tx with some + /// data and extracts the signed data. + #[test] + fn test_process_tx_wrapper_tx() { + let keypair = gen_keypair(); + // the signed tx + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 10.into(), + token: nam(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + tx.set_code(Code::new("wasm code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); + tx.encrypt(&Default::default()); + + tx.validate_header().expect("Test failed"); + match tx.header().tx_type { + TxType::Wrapper(_) => { + tx.decrypt(::G2Affine::prime_subgroup_generator()) + .expect("Test failed"); + // assert_eq!(tx, decrypted); } + _ => panic!("Test failed: Expected Wrapper Tx"), } + } - /// Test that process_tx correctly identifies a wrapper tx with some - /// data and extracts the signed data. - #[test] - fn test_process_tx_wrapper_tx() { - let keypair = gen_keypair(); - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - None, - ); - // the signed tx - let wrapper = WrapperTx::new( - Fee { - amount: 10.into(), - token: nam(), - }, - &keypair, - Epoch(0), - 0.into(), - tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ) - .sign(&keypair, tx.chain_id.clone(), Some(DateTimeUtc::now())) - .expect("Test failed"); - - match process_tx(wrapper).expect("Test failed") { - TxType::Wrapper(wrapper) => { - let decrypted = - wrapper.decrypt(::G2Affine::prime_subgroup_generator()) - .expect("Test failed"); - assert_eq!(tx, decrypted); - } - _ => panic!("Test failed: Expected Wrapper Tx"), - } - } + /// Test that process_tx correctly returns an error on a wrapper tx + /// with some unsigned data + #[test] + fn test_process_tx_wrapper_tx_unsigned() { + let keypair = gen_keypair(); + // the signed tx + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 10.into(), + token: nam(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + tx.set_code(Code::new("wasm code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.encrypt(&Default::default()); + let result = tx.validate_header().expect_err("Test failed"); + assert_matches!(result, TxError::SigError(_)); + } +} - /// Test that process_tx correctly returns an error on a wrapper tx - /// with some unsigned data - #[test] - fn test_process_tx_wrapper_tx_unsigned() { - let keypair = gen_keypair(); - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - None, - ); - // the signed tx - let wrapper = WrapperTx::new( - Fee { - amount: 10.into(), - token: nam(), - }, - &keypair, - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, +/// Test that process_tx correctly identifies a DecryptedTx +/// with some unsigned data and returns an identical copy +#[test] +fn test_process_tx_decrypted_unsigned() { + use crate::proto::{Code, Data, Tx}; + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + let code_sec = tx + .set_code(Code::new("transaction data".as_bytes().to_owned())) + .clone(); + let data_sec = tx + .set_data(Data::new("transaction data".as_bytes().to_owned())) + .clone(); + tx.validate_header().expect("Test failed"); + match tx.header().tx_type { + TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: _, + }) => { + assert_eq!( + tx.header().code_hash, + Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), ); - - let tx = Tx::new( - vec![], - Some( - TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), - ), - ChainId::default(), - None, + assert_eq!( + tx.header().data_hash, + Hash(data_sec.hash(&mut Sha256::new()).finalize_reset().into()), ); - let result = process_tx(tx).expect_err("Test failed"); - assert_matches!(result, TxError::Unsigned(_)); } + _ => panic!("Test failed"), } +} - /// Test that process_tx correctly identifies a DecryptedTx - /// with some unsigned data and returns an identical copy - #[test] - fn test_process_tx_decrypted_unsigned() { - let payload = Tx::new( - "transaction data".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - None, - ); - let decrypted = DecryptedTx::Decrypted { - tx: payload.clone(), - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }; - let tx = Tx::from(TxType::Decrypted(decrypted)); - match process_tx(tx).expect("Test failed") { - TxType::Decrypted(DecryptedTx::Decrypted { - tx: processed, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - }) => { - assert_eq!(payload, processed); - } - _ => panic!("Test failed"), - } +/// Test that process_tx correctly identifies a DecryptedTx +/// with some signed data and extracts it without checking +/// signature +#[test] +fn test_process_tx_decrypted_signed() { + use crate::proto::{Code, Data, Section, Signature, Tx}; + fn gen_keypair() -> common::SecretKey { + use rand::prelude::ThreadRng; + use rand::thread_rng; + + let mut rng: ThreadRng = thread_rng(); + ed25519::SigScheme::generate(&mut rng).try_to_sk().unwrap() } - /// Test that process_tx correctly identifies a DecryptedTx - /// with some signed data and extracts it without checking - /// signature - #[test] - fn test_process_tx_decrypted_signed() { - let payload = Tx::new( - "transaction data".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - None, - ); - let decrypted = DecryptedTx::Decrypted { - tx: payload.clone(), + use crate::types::key::Signature as S; + let mut decrypted = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + has_valid_pow: false, + })); + // Invalid signed data + let ed_sig = + ed25519::Signature::try_from_slice([0u8; 64].as_ref()).unwrap(); + let mut sig_sec = Signature::new(&decrypted.header_hash(), &gen_keypair()); + sig_sec.signature = common::Signature::try_from_sig(&ed_sig).unwrap(); + decrypted.add_section(Section::Signature(sig_sec)); + // create the tx with signed decrypted data + let code_sec = decrypted + .set_code(Code::new("transaction data".as_bytes().to_owned())) + .clone(); + let data_sec = decrypted + .set_data(Data::new("transaction data".as_bytes().to_owned())) + .clone(); + decrypted.validate_header().expect("Test failed"); + match decrypted.header().tx_type { + TxType::Decrypted(DecryptedTx::Decrypted { #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }; - // Invalid signed data - let ed_sig = - ed25519::Signature::try_from_slice([0u8; 64].as_ref()).unwrap(); - let signed = SignedTxData { - data: Some( - TxType::Decrypted(decrypted) - .try_to_vec() - .expect("Test failed"), - ), - sig: common::Signature::try_from_sig(&ed_sig).unwrap(), - }; - // create the tx with signed decrypted data - let tx = Tx::new( - vec![], - Some(signed.try_to_vec().expect("Test failed")), - ChainId::default(), - None, - ); - match process_tx(tx).expect("Test failed") { - TxType::Decrypted(DecryptedTx::Decrypted { - tx: processed, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - }) => { - assert_eq!(payload, processed); - } - _ => panic!("Test failed"), + has_valid_pow: _, + }) => { + assert_eq!( + decrypted.header.code_hash, + Hash(code_sec.hash(&mut Sha256::new()).finalize_reset().into()), + ); + assert_eq!( + decrypted.header.data_hash, + Hash(data_sec.hash(&mut Sha256::new()).finalize_reset().into()), + ); } + _ => panic!("Test failed"), } } - -#[cfg(feature = "ferveo-tpke")] -pub use tx_types::*; diff --git a/core/src/types/transaction/protocol.rs b/core/src/types/transaction/protocol.rs index a0aee560ed..910f401a66 100644 --- a/core/src/types/transaction/protocol.rs +++ b/core/src/types/transaction/protocol.rs @@ -32,14 +32,22 @@ mod protocol_txs { use serde_json; use super::*; - use crate::proto::Tx; + use crate::proto::{Code, Data, Section, Signature, Tx, TxError}; use crate::types::chain::ChainId; use crate::types::key::*; - use crate::types::transaction::{EllipticCurve, TxError, TxType}; + use crate::types::transaction::{Digest, EllipticCurve, Sha256, TxType}; const TX_NEW_DKG_KP_WASM: &str = "tx_update_dkg_session_keypair.wasm"; - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, + )] /// Txs sent by validators as part of internal protocols pub struct ProtocolTx { /// we require ProtocolTxs be signed @@ -63,49 +71,42 @@ mod protocol_txs { )) }) } + + /// Produce a SHA-256 hash of this section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize protocol"), + ); + hasher + } } /// DKG message wrapper type that adds Borsh encoding. - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Serialize, Deserialize)] pub struct DkgMessage(pub Message); - #[derive(Clone, Debug, BorshSerialize, BorshDeserialize, BorshSchema)] + #[derive( + Clone, + Debug, + BorshSerialize, + BorshDeserialize, + BorshSchema, + Serialize, + Deserialize, + )] #[allow(clippy::large_enum_variant)] /// Types of protocol messages to be sent pub enum ProtocolTxType { /// Messages to be given to the DKG state machine DKG(DkgMessage), /// Tx requesting a new DKG session keypair - NewDkgKeypair(Tx), + NewDkgKeypair, /// Aggregation of Ethereum state changes /// voted on by validators in last block - EthereumStateUpdate(Tx), + EthereumStateUpdate, } impl ProtocolTxType { - /// Sign a ProtocolTxType and wrap it up in a normal Tx - pub fn sign( - self, - pk: &common::PublicKey, - signing_key: &common::SecretKey, - chain_id: ChainId, - ) -> Tx { - Tx::new( - vec![], - Some( - TxType::Protocol(ProtocolTx { - pk: pk.clone(), - tx: self, - }) - .try_to_vec() - .expect("Could not serialize ProtocolTx"), - ), - chain_id, - None, - ) - .sign(signing_key) - } - /// Create a new tx requesting a new DKG session keypair pub fn request_new_dkg_keypair<'a, F>( data: UpdateDkgSessionKey, @@ -113,7 +114,7 @@ mod protocol_txs { wasm_dir: &'a Path, wasm_loader: F, chain_id: ChainId, - ) -> Self + ) -> Tx where F: FnOnce(&'a str, &'static str) -> Vec, { @@ -123,18 +124,22 @@ mod protocol_txs { .expect("Converting path to string should not fail"), TX_NEW_DKG_KP_WASM, ); - Self::NewDkgKeypair( - Tx::new( - code, - Some( - data.try_to_vec() - .expect("Serializing request should not fail"), - ), - chain_id, - None, - ) - .sign(signing_key), - ) + let mut outer_tx = + Tx::new(TxType::Protocol(Box::new(ProtocolTx { + pk: signing_key.ref_to(), + tx: Self::NewDkgKeypair, + }))); + outer_tx.header.chain_id = chain_id; + outer_tx.set_code(Code::new(code)); + outer_tx.set_data(Data::new( + data.try_to_vec() + .expect("Serializing request should not fail"), + )); + outer_tx.add_section(Section::Signature(Signature::new( + &outer_tx.header_hash(), + signing_key, + ))); + outer_tx } } diff --git a/core/src/types/transaction/wrapper.rs b/core/src/types/transaction/wrapper.rs index 5de138bacd..a92d5720ee 100644 --- a/core/src/types/transaction/wrapper.rs +++ b/core/src/types/transaction/wrapper.rs @@ -1,25 +1,19 @@ /// Integration of Ferveo cryptographic primitives /// to enable encrypted txs inside of normal txs. /// *Not wasm compatible* -#[cfg(feature = "ferveo-tpke")] pub mod wrapper_tx { - use std::convert::TryFrom; - pub use ark_bls12_381::Bls12_381 as EllipticCurve; + #[cfg(feature = "ferveo-tpke")] pub use ark_ec::{AffineCurve, PairingEngine}; use borsh::{BorshDeserialize, BorshSchema, BorshSerialize}; use serde::{Deserialize, Serialize}; + use sha2::{Digest, Sha256}; use thiserror::Error; - use crate::proto::Tx; use crate::types::address::Address; - use crate::types::chain::ChainId; use crate::types::key::*; use crate::types::storage::Epoch; - use crate::types::time::DateTimeUtc; use crate::types::token::Amount; - use crate::types::transaction::encrypted::EncryptedTx; - use crate::types::transaction::{EncryptionKey, Hash, TxError, TxType}; /// Minimum fee amount in micro NAMs pub const MIN_FEE: u64 = 100; @@ -56,6 +50,7 @@ pub mod wrapper_tx { BorshSchema, Serialize, Deserialize, + Eq, )] pub struct Fee { /// amount of the fee @@ -80,6 +75,7 @@ pub mod wrapper_tx { BorshSerialize, BorshDeserialize, BorshSchema, + Eq, )] #[serde(from = "u64")] #[serde(into = "u64")] @@ -173,11 +169,6 @@ pub mod wrapper_tx { pub epoch: Epoch, /// Max amount of gas that can be used when executing the inner tx pub gas_limit: GasLimit, - /// the encrypted payload - pub inner_tx: EncryptedTx, - /// sha-2 hash of the inner transaction acting as a commitment - /// the contents of the encrypted payload - pub tx_hash: Hash, #[cfg(not(feature = "mainnet"))] /// A PoW solution can be used to allow zero-fee testnet transactions pub pow_solution: Option, @@ -193,20 +184,15 @@ pub mod wrapper_tx { keypair: &common::SecretKey, epoch: Epoch, gas_limit: GasLimit, - tx: Tx, - encryption_key: EncryptionKey, #[cfg(not(feature = "mainnet"))] pow_solution: Option< crate::ledger::testnet_pow::Solution, >, ) -> WrapperTx { - let inner_tx = EncryptedTx::encrypt(&tx.to_bytes(), encryption_key); Self { fee, pk: keypair.ref_to(), epoch, gas_limit, - inner_tx, - tx_hash: Hash(tx.unsigned_hash()), #[cfg(not(feature = "mainnet"))] pow_solution, } @@ -218,71 +204,12 @@ pub mod wrapper_tx { Address::from(&self.pk) } - /// A validity check on the ciphertext. - pub fn validate_ciphertext(&self) -> bool { - self.inner_tx.0.check(&::G1Prepared::from( - -::G1Affine::prime_subgroup_generator(), - )) - } - - /// Decrypt the wrapped transaction. - /// - /// Will fail if the inner transaction doesn't match the - /// hash commitment or we are unable to recover a - /// valid Tx from the decoded byte stream. - pub fn decrypt( - &self, - privkey: ::G2Affine, - ) -> Result { - // decrypt the inner tx - let decrypted = self.inner_tx.decrypt(privkey); - let decrypted_tx = Tx::try_from(decrypted.as_ref()) - .map_err(|_| WrapperTxErr::InvalidTx)?; - - // check that the hash equals commitment - if decrypted_tx.unsigned_hash() != self.tx_hash.0 { - return Err(WrapperTxErr::DecryptedHash); - } - - Ok(decrypted_tx) - } - - /// Sign the wrapper transaction and convert to a normal Tx type - pub fn sign( - &self, - keypair: &common::SecretKey, - chain_id: ChainId, - expiration: Option, - ) -> Result { - if self.pk != keypair.ref_to() { - return Err(WrapperTxErr::InvalidKeyPair); - } - Ok(Tx::new( - vec![], - Some( - TxType::Wrapper(self.clone()) - .try_to_vec() - .expect("Could not serialize WrapperTx"), - ), - chain_id, - expiration, - ) - .sign(keypair)) - } - - /// Validate the signature of a wrapper tx - pub fn validate_sig( - &self, - signed_data: [u8; 32], - sig: &common::Signature, - ) -> Result<(), TxError> { - common::SigScheme::verify_signature(&self.pk, &signed_data, sig) - .map_err(|err| { - TxError::SigError(format!( - "WrapperTx signature verification failed: {}", - err - )) - }) + /// Produce a SHA-256 hash of this section + pub fn hash<'a>(&self, hasher: &'a mut Sha256) -> &'a mut Sha256 { + hasher.update( + self.try_to_vec().expect("unable to serialize wrapper"), + ); + hasher } } @@ -351,8 +278,9 @@ pub mod wrapper_tx { #[cfg(test)] mod test_wrapper_tx { use super::*; - use crate::proto::SignedTxData; + use crate::proto::{Code, Data, Section, Signature, Tx, TxError}; use crate::types::address::nam; + use crate::types::transaction::{Hash, TxType}; fn gen_keypair() -> common::SecretKey { use rand::prelude::ThreadRng; @@ -367,58 +295,62 @@ pub mod wrapper_tx { #[test] fn test_encryption_round_trip() { let keypair = gen_keypair(); - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - Some(DateTimeUtc::now()), - ); - - let wrapper = WrapperTx::new( - Fee { - amount: 10.into(), - token: nam(), - }, + let mut wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 10.into(), + token: nam(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper.set_code(Code::new("wasm code".as_bytes().to_owned())); + wrapper + .set_data(Data::new("transaction data".as_bytes().to_owned())); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), &keypair, - Epoch(0), - 0.into(), - tx.clone(), - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); - assert!(wrapper.validate_ciphertext()); + ))); + let mut encrypted_tx = wrapper.clone(); + encrypted_tx.encrypt(&Default::default()); + assert!(encrypted_tx.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); - let decrypted = wrapper.decrypt(privkey).expect("Test failed"); - assert_eq!(tx, decrypted); + encrypted_tx.decrypt(privkey).expect("Test failed"); + assert_eq!(wrapper.data(), encrypted_tx.data()); + assert_eq!(wrapper.code(), encrypted_tx.code()); } /// We test that when we try to decrypt a tx and it /// does not match the commitment, an error is returned #[test] fn test_decryption_invalid_hash() { - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - Some(DateTimeUtc::now()), - ); - - let mut wrapper = WrapperTx::new( - Fee { - amount: 10.into(), - token: nam(), - }, - &gen_keypair(), - Epoch(0), - 0.into(), - tx, - Default::default(), - #[cfg(not(feature = "mainnet"))] - None, - ); + let keypair = gen_keypair(); + let mut wrapper = + Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: 10.into(), + token: nam(), + }, + &keypair, + Epoch(0), + 0.into(), + #[cfg(not(feature = "mainnet"))] + None, + )))); + wrapper.set_code(Code::new("wasm code".as_bytes().to_owned())); + wrapper + .set_data(Data::new("transaction data".as_bytes().to_owned())); // give a incorrect commitment to the decrypted contents of the tx - wrapper.tx_hash = Hash([0u8; 32]); + wrapper.set_code_sechash(Hash([0u8; 32])); + wrapper.set_data_sechash(Hash([0u8; 32])); + wrapper.add_section(Section::Signature(Signature::new( + &wrapper.header_hash(), + &keypair, + ))); + wrapper.encrypt(&Default::default()); assert!(wrapper.validate_ciphertext()); let privkey = ::G2Affine::prime_subgroup_generator(); let err = wrapper.decrypt(privkey).expect_err("Test failed"); @@ -430,17 +362,9 @@ pub mod wrapper_tx { /// via the signature. #[test] fn test_malleability_attack_detection() { - let pubkey = ::G1Affine::prime_subgroup_generator(); let keypair = gen_keypair(); - // The intended tx - let tx = Tx::new( - "wasm code".as_bytes().to_owned(), - Some("transaction data".as_bytes().to_owned()), - ChainId::default(), - Some(DateTimeUtc::now()), - ); // the signed tx - let mut tx = WrapperTx::new( + let mut tx = Tx::new(TxType::Wrapper(Box::new(WrapperTx::new( Fee { amount: 10.into(), token: nam(), @@ -448,70 +372,42 @@ pub mod wrapper_tx { &keypair, Epoch(0), 0.into(), - tx, - Default::default(), #[cfg(not(feature = "mainnet"))] None, - ) - .sign(&keypair, ChainId::default(), None) - .expect("Test failed"); - - // we now try to alter the inner tx maliciously - let mut wrapper = if let TxType::Wrapper(wrapper) = - crate::types::transaction::process_tx(tx.clone()) - .expect("Test failed") - { - wrapper - } else { - panic!("Test failed") - }; + )))); - let mut signed_tx_data = - SignedTxData::try_from_slice(&tx.data.unwrap()[..]) - .expect("Test failed"); + tx.set_code(Code::new("wasm code".as_bytes().to_owned())); + tx.set_data(Data::new("transaction data".as_bytes().to_owned())); + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + &keypair, + ))); + // we now try to alter the inner tx maliciously // malicious transaction - let malicious = Tx::new( - "Give me all the money".as_bytes().to_owned(), - None, - ChainId::default(), - None, - ); - // We replace the inner tx with a malicious one - wrapper.inner_tx = EncryptedTx::encrypt( - &malicious.to_bytes(), - EncryptionKey(pubkey), - ); - // We change the commitment appropriately - wrapper.tx_hash = Hash(malicious.unsigned_hash()); + let malicious = "Give me all the money".as_bytes().to_owned(); + tx.set_data(Data::new(malicious.clone())); + tx.encrypt(&Default::default()); // we check ciphertext validity still passes - assert!(wrapper.validate_ciphertext()); + assert!(tx.validate_ciphertext()); // we check that decryption still succeeds - let decrypted = wrapper.decrypt( - ::G2Affine::prime_subgroup_generator() + tx.decrypt( + ::G2Affine::prime_subgroup_generator(), ) .expect("Test failed"); - assert_eq!(decrypted, malicious); - - // we substitute in the modified wrapper - signed_tx_data.data = Some( - TxType::Wrapper(wrapper).try_to_vec().expect("Test failed"), - ); - tx.data = Some(signed_tx_data.try_to_vec().expect("Test failed")); + assert_eq!(tx.data(), Some(malicious)); // check that the signature is not valid - tx.verify_sig(&keypair.ref_to(), &signed_tx_data.sig) + tx.verify_signature(&keypair.ref_to(), &tx.header_hash()) .expect_err("Test failed"); // check that the try from method also fails - let err = crate::types::transaction::process_tx(tx) - .expect_err("Test failed"); + let err = tx.validate_header().expect_err("Test failed"); assert_matches!(err, TxError::SigError(_)); } } } -#[cfg(feature = "ferveo-tpke")] pub use wrapper_tx::*; diff --git a/core/src/types/validity_predicate.rs b/core/src/types/validity_predicate.rs index a9bfb81168..a40fc1f99d 100644 --- a/core/src/types/validity_predicate.rs +++ b/core/src/types/validity_predicate.rs @@ -3,22 +3,17 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; +use crate::proto::Tx; use crate::types::hash::Hash; /// A validity predicate with an input that is intended to be invoked via `eval` /// host function. #[derive( - Debug, - Clone, - PartialEq, - BorshSerialize, - BorshDeserialize, - Serialize, - Deserialize, + Debug, Clone, BorshSerialize, BorshDeserialize, Serialize, Deserialize, )] pub struct EvalVp { /// The VP code hash to `eval` pub vp_code_hash: Hash, /// The input for the `eval`ed VP - pub input: Vec, + pub input: Tx, } diff --git a/encoding_spec/src/main.rs b/encoding_spec/src/main.rs index 5d2fb6db2f..b9e2b034ef 100644 --- a/encoding_spec/src/main.rs +++ b/encoding_spec/src/main.rs @@ -23,7 +23,6 @@ use itertools::Itertools; use lazy_static::lazy_static; use madato::types::TableRow; use namada::ledger::parameters::Parameters; -use namada::proto::SignedTxData; use namada::types::address::Address; use namada::types::key::ed25519::{PublicKey, Signature}; use namada::types::storage::{self, Epoch}; @@ -71,7 +70,6 @@ fn main() -> Result<(), Box> { let public_key_schema = PublicKey::schema_container(); // TODO update after let signature_schema = Signature::schema_container(); - let signed_tx_data_schema = SignedTxData::schema_container(); let init_account_schema = transaction::InitAccount::schema_container(); let init_validator_schema = transaction::InitValidator::schema_container(); let token_transfer_schema = token::Transfer::schema_container(); @@ -97,7 +95,6 @@ fn main() -> Result<(), Box> { definitions.extend(parameters_schema.definitions); definitions.extend(public_key_schema.definitions); definitions.extend(signature_schema.definitions); - definitions.extend(signed_tx_data_schema.definitions); definitions.extend(init_account_schema.definitions); definitions.extend(init_validator_schema.definitions); definitions.extend(token_transfer_schema.definitions); @@ -155,15 +152,6 @@ fn main() -> Result<(), Box> { "https://dev.namada.net/master/rustdoc/namada/types/key/ed25519/struct.Signature.html"); tables.push(signature_table); - let signed_tx_data_definition = definitions - .remove(&signed_tx_data_schema.declaration) - .unwrap(); - let signed_tx_data_table = - definition_to_table(signed_tx_data_schema.declaration, signed_tx_data_definition).with_rust_doc_link( - // TODO update after - "https://dev.namada.net/master/rustdoc/namada/types/key/ed25519/struct.SignedTxData.html"); - tables.push(signed_tx_data_table); - let init_account_definition = definitions .remove(&init_account_schema.declaration) .unwrap(); diff --git a/genesis/e2e-tests-single-node.toml b/genesis/e2e-tests-single-node.toml index 6a1cc634f3..add7242e56 100644 --- a/genesis/e2e-tests-single-node.toml +++ b/genesis/e2e-tests-single-node.toml @@ -200,7 +200,7 @@ light_client_attack_min_slash_rate = 0.001 # minimum amount of nam token to lock min_proposal_fund = 500 # proposal code size in bytes -max_proposal_code_size = 300000 +max_proposal_code_size = 500000 # min proposal period length in epochs min_proposal_period = 3 # max proposal period length in epochs diff --git a/macros/src/lib.rs b/macros/src/lib.rs index 50f14258d7..037cdee6e1 100644 --- a/macros/src/lib.rs +++ b/macros/src/lib.rs @@ -42,7 +42,7 @@ pub fn transaction(_attr: TokenStream, input: TokenStream) -> TokenStream { tx_data_len as _, ) }; - let tx_data = slice.to_vec(); + let tx_data = Tx::try_from_slice(slice).unwrap(); // The context on WASM side is only provided by the VM once its // being executed (in here it's implicit). But because we want to @@ -112,7 +112,7 @@ pub fn validity_predicate( tx_data_len as _, ) }; - let tx_data = slice.to_vec(); + let tx_data = Tx::try_from_slice(slice).unwrap(); let slice = unsafe { core::slice::from_raw_parts( diff --git a/proto/types.proto b/proto/types.proto index 710cfcd2ba..44ad70b77e 100644 --- a/proto/types.proto +++ b/proto/types.proto @@ -5,12 +5,7 @@ import "google/protobuf/timestamp.proto"; package types; message Tx { - bytes code_or_hash = 1; - // TODO this optional is useless because it's default on proto3 - optional bytes data = 2; - google.protobuf.Timestamp timestamp = 3; - string chain_id = 4; - optional google.protobuf.Timestamp expiration = 5; + bytes data = 1; } message Dkg { string data = 1; } diff --git a/scripts/generator.sh b/scripts/generator.sh new file mode 100755 index 0000000000..6b95030ae4 --- /dev/null +++ b/scripts/generator.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +# A script to generate some transaction test vectors. It must be executed at the +# root of the Namada repository. All transaction types except vote-proposal are +# tested. This is because vote-proposal needs to query RPC for delegation. This +# script assumes that the WASM scripts have already been built using +# `make build-wasm-scripts`. Run `./scripts/online_generator server` to start a +# server and then run `./scripts/online_generator client` to generate the test +# vectors. + +NAMADA_DIR="$(pwd)" +export NAMADA_LEDGER_LOG_PATH="$(pwd)/vectors.json" +export NAMADA_TX_LOG_PATH="$(pwd)/debugs.txt" + +echo '{ + "content": { + "title": "TheTitle", + "authors": "test@test.com", + "discussions-to": "www.github.com/anoma/aip/1", + "created": "2022-03-10T08:54:37Z", + "license": "MIT", + "abstract": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros. Nullam sed ex justo. Ut at placerat ipsum, sit amet rhoncus libero. Sed blandit non purus non suscipit. Phasellus sed quam nec augue bibendum bibendum ut vitae urna. Sed odio diam, ornare nec sapien eget, congue viverra enim.", + "motivation": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices.", + "details": "Ut convallis eleifend orci vel venenatis. Duis vulputate metus in lacus sollicitudin vestibulum. Suspendisse vel velit ac est consectetur feugiat nec ac urna. Ut faucibus ex nec dictum fermentum. Morbi aliquet purus at sollicitudin ultrices. Quisque viverra varius cursus. Praesent sed mauris gravida, pharetra turpis non, gravida eros.", + "requires": "2" + }, + "author": "atest1v4ehgw36xvcyyvejgvenxs34g3zygv3jxqunjd6rxyeyys3sxy6rwvfkx4qnj33hg9qnvse4lsfctw", + "voting_start_epoch": 12, + "voting_end_epoch": 24, + "grace_epoch": 30, + "type": { "Default": "'"$NAMADA_DIR"'/wasm_for_tests/tx_no_op.wasm" } +} +' > valid_proposal.json + +if [ "$#" -ne 1 ]; then + echo "Illegal number of parameters" +elif [ "$1" = "server" ]; then + cp genesis/e2e-tests-single-node.toml genesis/test-vectors-single-node.toml + + sed -i 's/^epochs_per_year = 31_536_000$/epochs_per_year = 262_800/' genesis/test-vectors-single-node.toml + + NAMADA_GENESIS_FILE=$(cargo run --bin namadac -- --mode validator utils init-network --genesis-path genesis/test-vectors-single-node.toml --wasm-checksums-path wasm/checksums.json --chain-prefix e2e-test --unsafe-dont-encrypt --localhost --allow-duplicate-ip | grep 'Genesis file generated at ' | sed 's/^Genesis file generated at //') + + rm genesis/test-vectors-single-node.toml + + NAMADA_BASE_DIR=${NAMADA_GENESIS_FILE%.toml} + + cp wasm/*.wasm $NAMADA_BASE_DIR/wasm/ + + cp wasm/*.wasm $NAMADA_BASE_DIR/setup/validator-0/.namada/$(basename $NAMADA_BASE_DIR)/wasm/ + + cp $NAMADA_BASE_DIR/setup/other/wallet.toml $NAMADA_BASE_DIR/wallet.toml + + cargo run --bin namadan -- --mode validator --base-dir $NAMADA_BASE_DIR/setup/validator-0/.namada/ ledger +elif [ "$1" = "client" ]; then + echo > $NAMADA_TX_LOG_PATH + + echo $'[' > $NAMADA_LEDGER_LOG_PATH + + cargo run --bin namadac -- transfer --source bertha --target christel --token btc --amount 23 --force --signing-key bertha-key --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- bond --validator bertha --amount 25 --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- reveal-pk --public-key albert-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- update --code-path vp_user.wasm --address bertha --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- init-validator --alias bertha-validator --source bertha --commission-rate 0.05 --max-commission-rate-change 0.01 --signing-key bertha-key --unsafe-dont-encrypt --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- unbond --validator christel --amount 5 --signing-key christel-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- withdraw --validator albert --signing-key albert-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- init-account --alias albert-account --source albert --public-key albert-key --signing-key albert-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- tx --code-path $NAMADA_DIR/wasm_for_tests/tx_no_op.wasm --data-path README.md --signing-key albert-key --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- ibc-transfer --source bertha --receiver christel --token btc --amount 24 --channel-id channel-141 --signing-key bertha-key --force --ledger-address 127.0.0.1:27657 + + #cargo run --bin namadac -- init-proposal --data-path valid_proposal.json --epoch 2 --force --signing-key bertha-key --ledger-address 127.0.0.1:27657 + + cargo run --bin namadaw -- masp add --alias a_spending_key --value xsktest1qqqqqqqqqqqqqq9v0sls5r5de7njx8ehu49pqgmqr9ygelg87l5x8y4s9r0pjlvu69au6gn3su5ewneas486hdccyayx32hxvt64p3d0hfuprpgcgv2q9gdx3jvxrn02f0nnp3jtdd6f5vwscfuyum083cvfv4jun75ak5sdgrm2pthzj3sflxc0jx0edrakx3vdcngrfjmru8ywkguru8mxss2uuqxdlglaz6undx5h8w7g70t2es850g48xzdkqay5qs0yw06rtxcvedhsv --unsafe-dont-encrypt + + cargo run --bin namadaw -- masp add --alias b_spending_key --value xsktest1qqqqqqqqqqqqqqpagte43rsza46v55dlz8cffahv0fnr6eqacvnrkyuf9lmndgal7c2k4r7f7zu2yr5rjwr374unjjeuzrh6mquzy6grfdcnnu5clzaq2llqhr70a8yyx0p62aajqvrqjxrht3myuyypsvm725uyt5vm0fqzrzuuedtf6fala4r4nnazm9y9hq5yu6pq24arjskmpv4mdgfn3spffxxv8ugvym36kmnj45jcvvmm227vqjm5fq8882yhjsq97p7xrwqqd82s0 --unsafe-dont-encrypt + + cargo run --bin namadaw -- masp add --alias ab_payment_address --value patest1dxj5kfjvm27rk5wg8ym0mjrhthz6whagdfj9krqfvyszffh4n0mx9f7cauvz6tr43vp22qgsefr + + cargo run --bin namadaw -- masp add --alias aa_payment_address --value patest1a8sfz9c6axdhn925e5qrgzz86msq6yj4uhmxayynucea7gssepk89dgqkx00srfkn4m6kt9jpau + + cargo run --bin namadaw -- masp add --alias bb_payment_address --value patest1vqe0vyxh6wmhahwa52gthgd6edgqxfmgyv8e94jtwn55mdvpvylcyqnp59595272qrz3zxn0ysg + + cargo run --bin namadac -- --mode full transfer --source albert --target aa_payment_address --token btc --amount 20 --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- --mode full transfer --source a_spending_key --target ab_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 + + until cargo run --bin namadac -- --mode full epoch --ledger-address 127.0.0.1:27657 | grep -m1 "Last committed epoch: 2" ; do sleep 10 ; done; + + cargo run --bin namadac -- --mode full transfer --source a_spending_key --target bb_payment_address --token btc --amount 7 --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- --mode full transfer --source a_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 + + cargo run --bin namadac -- --mode full transfer --source b_spending_key --target bb_payment_address --token btc --amount 6 --force --ledger-address 127.0.0.1:27657 + + perl -0777 -i.original -pe 's/,\s*$//igs' $NAMADA_LEDGER_LOG_PATH + + echo $'\n]' >> $NAMADA_LEDGER_LOG_PATH +fi + +rm valid_proposal.json diff --git a/shared/Cargo.toml b/shared/Cargo.toml index 277034ae9f..e2289d382f 100644 --- a/shared/Cargo.toml +++ b/shared/Cargo.toml @@ -118,6 +118,7 @@ proptest = {git = "https://github.com/heliaxdev/proptest", rev = "8f1b4abe7ebd35 prost = "0.11.6" pwasm-utils = {git = "https://github.com/heliaxdev/wasm-utils", tag = "v0.20.0", features = ["sign_ext"], optional = true} rayon = {version = "=1.5.3", optional = true} +ripemd = "0.1" rust_decimal = "=1.26.1" rust_decimal_macros = "=1.26.1" serde = {version = "1.0.125", features = ["derive"]} @@ -140,9 +141,9 @@ wasmer-engine-dylib = {version = "=2.2.0", optional = true} wasmer-engine-universal = {version = "=2.2.0", optional = true} wasmer-vm = {version = "2.2.0", optional = true} wasmparser = "0.83.0" -#libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } +masp_proofs = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } rand = {version = "0.8", default-features = false, optional = true} rand_core = {version = "0.6", default-features = false, optional = true} zeroize = "1.5.5" diff --git a/shared/src/ledger/eth_bridge/vp.rs b/shared/src/ledger/eth_bridge/vp.rs index 479541e181..a2c5588aa8 100644 --- a/shared/src/ledger/eth_bridge/vp.rs +++ b/shared/src/ledger/eth_bridge/vp.rs @@ -5,6 +5,7 @@ use std::collections::BTreeSet; use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::ledger::storage as ledger_storage; use crate::ledger::storage::StorageHasher; +use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::vm::WasmCacheAccess; @@ -42,12 +43,12 @@ where fn validate_tx( &self, - _tx_data: &[u8], + _tx_data: &Tx, _keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { tracing::debug!( - tx_data_len = _tx_data.len(), + tx_data_len = _tx_data.data().as_ref().map(Vec::len).unwrap_or(0), keys_changed_len = _keys_changed.len(), verifiers_len = _verifiers.len(), "Validity predicate triggered", diff --git a/shared/src/ledger/events.rs b/shared/src/ledger/events.rs index bc58d09aa5..44425b111e 100644 --- a/shared/src/ledger/events.rs +++ b/shared/src/ledger/events.rs @@ -13,7 +13,7 @@ use crate::ledger::native_vp::governance::utils::ProposalEvent; use crate::tendermint_proto::abci::EventAttribute; use crate::types::ibc::IbcEvent; #[cfg(feature = "ferveo-tpke")] -use crate::types::transaction::{hash_tx, TxType}; +use crate::types::transaction::TxType; /// Indicates if an event is emitted do to /// an individual Tx or the nature of a finalized block @@ -67,42 +67,37 @@ impl Event { /// Creates a new event with the hash and height of the transaction /// already filled in #[cfg(feature = "ferveo-tpke")] - pub fn new_tx_event(tx: &TxType, height: u64) -> Self { - let mut event = match tx { - TxType::Wrapper(wrapper) => { + pub fn new_tx_event(tx: &crate::proto::Tx, height: u64) -> Self { + let mut event = match tx.header().tx_type { + TxType::Wrapper(_) => { let mut event = Event { event_type: EventType::Accepted, level: EventLevel::Tx, attributes: HashMap::new(), }; - event["hash"] = hash_tx( - &wrapper - .try_to_vec() - .expect("Serializing wrapper should not fail"), - ) - .to_string(); + event["hash"] = tx.header_hash().to_string(); event } - TxType::Decrypted(decrypted) => { + TxType::Decrypted(_) => { let mut event = Event { event_type: EventType::Applied, level: EventLevel::Tx, attributes: HashMap::new(), }; - event["hash"] = decrypted.hash_commitment().to_string(); + event["hash"] = tx + .clone() + .update_header(TxType::Raw) + .header_hash() + .to_string(); event } - tx @ TxType::Protocol(_) => { + TxType::Protocol(_) => { let mut event = Event { event_type: EventType::Applied, level: EventLevel::Tx, attributes: HashMap::new(), }; - event["hash"] = hash_tx( - &tx.try_to_vec() - .expect("Serializing protocol tx should not fail"), - ) - .to_string(); + event["hash"] = tx.header_hash().to_string(); event } _ => unreachable!(), diff --git a/shared/src/ledger/ibc/vp/mod.rs b/shared/src/ledger/ibc/vp/mod.rs index dc0a021b6f..412c3ee268 100644 --- a/shared/src/ledger/ibc/vp/mod.rs +++ b/shared/src/ledger/ibc/vp/mod.rs @@ -9,6 +9,9 @@ use std::collections::{BTreeSet, HashSet}; use std::rc::Rc; use std::time::Duration; +use namada_core::ledger::ibc::storage::{ + client_id, ibc_prefix, is_client_counter_key, IbcPrefix, +}; use borsh::BorshDeserialize; use context::{PseudoExecutionContext, VpValidationContext}; use namada_core::ledger::ibc::storage::{is_ibc_denom_key, is_ibc_key}; @@ -17,7 +20,7 @@ use namada_core::ledger::ibc::{ }; use namada_core::ledger::storage::write_log::StorageModification; use namada_core::ledger::storage::{self as ledger_storage, StorageHasher}; -use namada_core::proto::SignedTxData; +use namada_core::proto::Tx; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; use namada_proof_of_stake::read_pos_params; @@ -73,23 +76,22 @@ where fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> VpResult { - let signed = - SignedTxData::try_from_slice(tx_data).map_err(Error::Decoding)?; - let tx_data = &signed.data.ok_or(Error::NoTxData)?; + let signed = tx_data; + let tx_data = signed.data().ok_or(Error::NoTxData)?; // Pseudo execution and compare them - self.validate_state(tx_data, keys_changed)?; + self.validate_state(&tx_data, keys_changed)?; // Validate the state according to the given IBC message - self.validate_with_msg(tx_data)?; + self.validate_with_msg(&tx_data)?; // Validate the denom store if a denom key has been changed if keys_changed.iter().any(is_ibc_denom_key) { - self.validate_denom(tx_data).map_err(Error::Denom)?; + self.validate_denom(&tx_data).map_err(Error::Denom)?; } Ok(true) @@ -253,6 +255,8 @@ mod tests { use core::time::Duration; use std::convert::TryFrom; use std::str::FromStr; + use crate::types::transaction::{TxType}; + use crate::proto::{Code, Data, Signature, Section}; use borsh::BorshSerialize; use prost::Message; @@ -710,23 +714,28 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); let verifiers = BTreeSet::new(); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let ctx = Ctx::new( &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -737,12 +746,8 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -791,13 +796,20 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -818,7 +830,7 @@ mod tests { let ibc = Ibc { ctx }; // this should fail because no state is stored let result = ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .validate_tx(&tx, &keys_changed, &verifiers) .unwrap_err(); assert_matches!(result, Error::StateChange(_)); } @@ -927,13 +939,20 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -954,7 +973,7 @@ mod tests { // this should return true because state has been stored assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -1037,13 +1056,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1053,7 +1077,7 @@ mod tests { &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -1063,12 +1087,8 @@ mod tests { let ibc = Ibc { ctx }; // this should return true because state has been stored assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -1135,13 +1155,20 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); + let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1161,7 +1188,7 @@ mod tests { let ibc = Ibc { ctx }; // this should fail because no event let result = ibc - .validate_tx(tx.data.as_ref().unwrap(), &keys_changed, &verifiers) + .validate_tx(&tx, &keys_changed, &verifiers) .unwrap_err(); assert_matches!(result, Error::IbcEvent(_)); } @@ -1262,13 +1289,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1289,7 +1321,7 @@ mod tests { // this should return true because state has been stored assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -1373,13 +1405,18 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1389,7 +1426,7 @@ mod tests { &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -1398,12 +1435,8 @@ mod tests { ); let ibc = Ibc { ctx }; assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -1461,13 +1494,18 @@ mod tests { let tx_index = TxIndex::default(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1477,7 +1515,7 @@ mod tests { &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -1486,12 +1524,8 @@ mod tests { ); let ibc = Ibc { ctx }; assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -1585,13 +1619,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1601,7 +1640,7 @@ mod tests { &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -1610,12 +1649,8 @@ mod tests { ); let ibc = Ibc { ctx }; assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -1710,13 +1745,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1726,7 +1766,7 @@ mod tests { &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -1735,12 +1775,8 @@ mod tests { ); let ibc = Ibc { ctx }; assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -1819,13 +1855,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.code_sechash(), + &keypair_1(), + ))); + outer_tx.add_section(Section::Signature(Signature::new( + outer_tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1835,7 +1876,7 @@ mod tests { &ADDRESS, &wl_storage.storage, &wl_storage.write_log, - &tx, + &outer_tx, &tx_index, gas_meter, &keys_changed, @@ -1844,12 +1885,8 @@ mod tests { ); let ibc = Ibc { ctx }; assert!( - ibc.validate_tx( - tx.data.as_ref().unwrap(), - &keys_changed, - &verifiers - ) - .expect("validation failed") + ibc.validate_tx(&outer_tx, &keys_changed, &verifiers) + .expect("validation failed") ); } @@ -1926,13 +1963,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -1952,7 +1994,7 @@ mod tests { let ibc = Ibc { ctx }; assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -2066,13 +2108,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2092,7 +2139,7 @@ mod tests { let ibc = Ibc { ctx }; assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -2244,13 +2291,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2270,7 +2322,7 @@ mod tests { let ibc = Ibc { ctx }; assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -2391,13 +2443,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2417,7 +2474,7 @@ mod tests { let ibc = Ibc { ctx }; assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -2543,13 +2600,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2569,7 +2631,7 @@ mod tests { let ibc = Ibc { ctx }; assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) @@ -2695,13 +2757,18 @@ mod tests { let tx_code = vec![]; let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx::new( - tx_code, - Some(tx_data), - wl_storage.storage.chain_id.clone(), - None, - ) - .sign(&keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair_1(), + ))); let gas_meter = VpGasMeter::new(0); let (vp_wasm_cache, _vp_cache_dir) = wasm::compilation_cache::common::testing::cache(); @@ -2721,7 +2788,7 @@ mod tests { let ibc = Ibc { ctx }; assert!( ibc.validate_tx( - tx.data.as_ref().unwrap(), + &tx, &keys_changed, &verifiers ) diff --git a/shared/src/ledger/ibc/vp/token.rs b/shared/src/ledger/ibc/vp/token.rs index b735ababe6..00d02bc10a 100644 --- a/shared/src/ledger/ibc/vp/token.rs +++ b/shared/src/ledger/ibc/vp/token.rs @@ -23,8 +23,10 @@ use crate::ibc_proto::google::protobuf::Any; use crate::ledger::ibc::storage as ibc_storage; use crate::ledger::native_vp::{self, Ctx, NativeVp, VpEnv}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; -use crate::proto::SignedTxData; -use crate::types::address::{Address, InternalAddress}; +use crate::proto::Tx; +use crate::types::address::{ + Address, DecodeError as AddressError, InternalAddress, +}; use crate::types::storage::Key; use crate::types::token::{self, Amount, AmountParseError}; use crate::vm::WasmCacheAccess; @@ -84,13 +86,12 @@ where fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { - let signed = - SignedTxData::try_from_slice(tx_data).map_err(Error::Decoding)?; - let tx_data = &signed.data.ok_or(Error::NoTxData)?; + let signed = tx_data; + let tx_data = signed.data().ok_or(Error::NoTxData)?; // Check the non-onwer balance updates let ibc_keys_changed: HashSet = keys_changed diff --git a/shared/src/ledger/masp.rs b/shared/src/ledger/masp.rs index 2c43028333..571fe51826 100644 --- a/shared/src/ledger/masp.rs +++ b/shared/src/ledger/masp.rs @@ -9,39 +9,38 @@ use std::fs::File; use std::ops::Deref; use std::path::PathBuf; +use masp_primitives::group::GroupEncoding; +use masp_primitives::sapling::redjubjub::PublicKey; +use masp_primitives::transaction::components::transparent::builder::TransparentBuilder; +use masp_primitives::transaction::components::{ + Amount, ConvertDescription, OutputDescription, SpendDescription, TxOut, +}; +use masp_primitives::transaction::sighash::{signature_hash, SignableInput}; +use masp_primitives::transaction::txid::TxIdDigester; use async_trait::async_trait; -use bellman::groth16::{prepare_verifying_key, PreparedVerifyingKey}; -use bls12_381::Bls12; +use masp_proofs::bellman::groth16::{ + prepare_verifying_key, PreparedVerifyingKey, +}; +use crate::ledger::rpc::query_storage_value; // use async_std::io::prelude::WriteExt; // use async_std::io::{self}; use borsh::{BorshDeserialize, BorshSerialize}; use itertools::Either; use masp_primitives::asset_type::AssetType; -use masp_primitives::consensus::BranchId::Sapling; use masp_primitives::consensus::{BranchId, TestNetwork}; use masp_primitives::convert::AllowedConversion; use masp_primitives::ff::PrimeField; use masp_primitives::group::cofactor::CofactorGroup; -use masp_primitives::keys::FullViewingKey; -#[cfg(feature = "masp-tx-gen")] -use masp_primitives::legacy::TransparentAddress; use masp_primitives::merkle_tree::{ CommitmentTree, IncrementalWitness, MerklePath, }; -use masp_primitives::note_encryption::*; -use masp_primitives::primitives::{Diversifier, Note, ViewingKey}; -use masp_primitives::redjubjub::PublicKey; use masp_primitives::sapling::Node; #[cfg(feature = "masp-tx-gen")] -use masp_primitives::transaction::builder::{self, secp256k1, *}; -use masp_primitives::transaction::components::{ - Amount, ConvertDescription, OutputDescription, SpendDescription, -}; -#[cfg(feature = "masp-tx-gen")] -use masp_primitives::transaction::components::{OutPoint, TxOut}; +use masp_primitives::transaction::builder::{self, *}; use masp_primitives::transaction::{ - signature_hash_data, Transaction, SIGHASH_ALL, + Authorization, Authorized, Transaction, TransactionData, Unauthorized, }; +use masp_proofs::bls12_381::Bls12; use masp_primitives::zip32::{ExtendedFullViewingKey, ExtendedSpendingKey}; use masp_proofs::prover::LocalTxProver; use masp_proofs::sapling::SaplingVerificationContext; @@ -50,10 +49,27 @@ use namada_core::types::transaction::AffineCurve; use rand_core::{CryptoRng, OsRng, RngCore}; #[cfg(feature = "masp-tx-gen")] use sha2::Digest; +use masp_primitives::memo::MemoBytes; +use masp_primitives::sapling::keys::FullViewingKey; +use masp_primitives::sapling::note_encryption::*; +use masp_primitives::sapling::{ + Diversifier, Note, Nullifier, ViewingKey, +}; +use masp_primitives::transaction::components::sapling::builder::SaplingMetadata; +use masp_primitives::transaction::components::sapling::fees::{ + ConvertView, InputView as SaplingInputView, OutputView as SaplingOutputView, +}; +use masp_primitives::transaction::components::transparent::fees::{ + InputView as TransparentInputView, OutputView as TransparentOutputView, +}; +use masp_primitives::transaction::fees::fixed::FeeRule; +use masp_primitives::transaction::{ + TransparentAddress, +}; use crate::ledger::queries::Client; use crate::ledger::{args, rpc}; -use crate::proto::{SignedTxData, Tx}; +use crate::proto::Tx; use crate::tendermint_rpc::query::Query; use crate::tendermint_rpc::Order; use crate::types::address::{masp, Address}; @@ -64,13 +80,20 @@ use crate::types::token::{ Transfer, HEAD_TX_KEY, PIN_KEY_PREFIX, TX_KEY_PREFIX, }; use crate::types::transaction::{ - process_tx, DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, + DecryptedTx, EllipticCurve, PairingEngine, TxType, WrapperTx, }; +use ripemd::Digest as RipemdDigest; /// Env var to point to a dir with MASP parameters. When not specified, /// the default OS specific path is used. pub const ENV_VAR_MASP_PARAMS_DIR: &str = "NAMADA_MASP_PARAMS_DIR"; +/// The network to use for MASP +#[cfg(feature = "mainnet")] +const NETWORK: MainNetwork = MainNetwork; +#[cfg(not(feature = "mainnet"))] +const NETWORK: TestNetwork = TestNetwork; + // TODO these could be exported from masp_proof crate /// Spend circuit name pub const SPEND_NAME: &str = "masp-spend.params"; @@ -81,8 +104,8 @@ pub const CONVERT_NAME: &str = "masp-convert.params"; /// Load Sapling spend params. pub fn load_spend_params() -> ( - bellman::groth16::Parameters, - bellman::groth16::PreparedVerifyingKey, + masp_proofs::bellman::groth16::Parameters, + masp_proofs::bellman::groth16::PreparedVerifyingKey, ) { let params_dir = get_params_dir(); let spend_path = params_dir.join(SPEND_NAME); @@ -94,15 +117,17 @@ pub fn load_spend_params() -> ( panic!("MASP parameters not present or downloadable"); } let param_f = File::open(spend_path).unwrap(); - let params = bellman::groth16::Parameters::read(¶m_f, false).unwrap(); + let params = + masp_proofs::bellman::groth16::Parameters::read(¶m_f, false) + .unwrap(); let vk = prepare_verifying_key(¶ms.vk); (params, vk) } /// Load Sapling convert params. pub fn load_convert_params() -> ( - bellman::groth16::Parameters, - bellman::groth16::PreparedVerifyingKey, + masp_proofs::bellman::groth16::Parameters, + masp_proofs::bellman::groth16::PreparedVerifyingKey, ) { let params_dir = get_params_dir(); let spend_path = params_dir.join(CONVERT_NAME); @@ -114,15 +139,17 @@ pub fn load_convert_params() -> ( panic!("MASP parameters not present or downloadable"); } let param_f = File::open(spend_path).unwrap(); - let params = bellman::groth16::Parameters::read(¶m_f, false).unwrap(); + let params = + masp_proofs::bellman::groth16::Parameters::read(¶m_f, false) + .unwrap(); let vk = prepare_verifying_key(¶ms.vk); (params, vk) } /// Load Sapling output params. pub fn load_output_params() -> ( - bellman::groth16::Parameters, - bellman::groth16::PreparedVerifyingKey, + masp_proofs::bellman::groth16::Parameters, + masp_proofs::bellman::groth16::PreparedVerifyingKey, ) { let params_dir = get_params_dir(); let output_path = params_dir.join(OUTPUT_NAME); @@ -134,27 +161,33 @@ pub fn load_output_params() -> ( panic!("MASP parameters not present or downloadable"); } let param_f = File::open(output_path).unwrap(); - let params = bellman::groth16::Parameters::read(¶m_f, false).unwrap(); + let params = + masp_proofs::bellman::groth16::Parameters::read(¶m_f, false) + .unwrap(); let vk = prepare_verifying_key(¶ms.vk); (params, vk) } /// check_spend wrapper pub fn check_spend( - spend: &SpendDescription, + spend: &SpendDescription<::SaplingAuth>, sighash: &[u8; 32], ctx: &mut SaplingVerificationContext, parameters: &PreparedVerifyingKey, ) -> bool { let zkproof = - bellman::groth16::Proof::read(spend.zkproof.as_slice()).unwrap(); + masp_proofs::bellman::groth16::Proof::read(spend.zkproof.as_slice()); + let zkproof = match zkproof { + Ok(zkproof) => zkproof, + _ => return false, + }; ctx.check_spend( spend.cv, spend.anchor, - &spend.nullifier, + &spend.nullifier.0, PublicKey(spend.rk.0), sighash, - spend.spend_auth_sig.unwrap(), + spend.spend_auth_sig, zkproof, parameters, ) @@ -162,59 +195,121 @@ pub fn check_spend( /// check_output wrapper pub fn check_output( - output: &OutputDescription, + output: &OutputDescription<<::SaplingAuth as masp_primitives::transaction::components::sapling::Authorization>::Proof>, ctx: &mut SaplingVerificationContext, parameters: &PreparedVerifyingKey, ) -> bool { let zkproof = - bellman::groth16::Proof::read(output.zkproof.as_slice()).unwrap(); - ctx.check_output( - output.cv, - output.cmu, - output.ephemeral_key, - zkproof, - parameters, - ) + masp_proofs::bellman::groth16::Proof::read(output.zkproof.as_slice()); + let zkproof = match zkproof { + Ok(zkproof) => zkproof, + _ => return false, + }; + let epk = + masp_proofs::jubjub::ExtendedPoint::from_bytes(&output.ephemeral_key.0); + let epk = match epk.into() { + Some(p) => p, + None => return false, + }; + ctx.check_output(output.cv, output.cmu, epk, zkproof, parameters) } /// check convert wrapper pub fn check_convert( - convert: &ConvertDescription, + convert: &ConvertDescription<<::SaplingAuth as masp_primitives::transaction::components::sapling::Authorization>::Proof>, ctx: &mut SaplingVerificationContext, parameters: &PreparedVerifyingKey, ) -> bool { let zkproof = - bellman::groth16::Proof::read(convert.zkproof.as_slice()).unwrap(); + masp_proofs::bellman::groth16::Proof::read(convert.zkproof.as_slice()); + let zkproof = match zkproof { + Ok(zkproof) => zkproof, + _ => return false, + }; ctx.check_convert(convert.cv, convert.anchor, zkproof, parameters) } +/// Represents an authorization where the Sapling bundle is authorized and the +/// transparent bundle is unauthorized. +pub struct PartialAuthorized; + +impl Authorization for PartialAuthorized { + type SaplingAuth = ::SaplingAuth; + type TransparentAuth = ::TransparentAuth; +} + +/// Partially deauthorize the transparent bundle +fn partial_deauthorize( + tx_data: &TransactionData, +) -> Option> { + let transp = tx_data.transparent_bundle().and_then(|x| { + let mut tb = TransparentBuilder::empty(); + for vin in &x.vin { + tb.add_input(TxOut { + asset_type: vin.asset_type, + value: vin.value, + address: vin.address, + }) + .ok()?; + } + for vout in &x.vout { + tb.add_output(&vout.address, vout.asset_type, vout.value) + .ok()?; + } + tb.build() + }); + if tx_data.transparent_bundle().is_some() != transp.is_some() { + return None; + } + Some(TransactionData::from_parts( + tx_data.version(), + tx_data.consensus_branch_id(), + tx_data.lock_time(), + tx_data.expiry_height(), + transp, + tx_data.sapling_bundle().cloned(), + )) +} + /// Verify a shielded transaction. pub fn verify_shielded_tx(transaction: &Transaction) -> bool { tracing::info!("entered verify_shielded_tx()"); - let mut ctx = SaplingVerificationContext::new(); + let sapling_bundle = if let Some(bundle) = transaction.sapling_bundle() { + bundle + } else { + return false; + }; let tx_data = transaction.deref(); - let (_, spend_pvk) = load_spend_params(); - let (_, convert_pvk) = load_convert_params(); - let (_, output_pvk) = load_output_params(); + // Partially deauthorize the transparent bundle + let unauth_tx_data = match partial_deauthorize(tx_data) { + Some(tx_data) => tx_data, + None => return false, + }; - let sighash: [u8; 32] = - signature_hash_data(tx_data, Sapling, SIGHASH_ALL, None) - .try_into() - .unwrap(); + let txid_parts = unauth_tx_data.digest(TxIdDigester); + // the commitment being signed is shared across all Sapling inputs; once + // V4 transactions are deprecated this should just be the txid, but + // for now we need to continue to compute it here. + let sighash = + signature_hash(&unauth_tx_data, &SignableInput::Shielded, &txid_parts); tracing::info!("sighash computed"); - let spends_valid = tx_data - .shielded_spends - .iter() - .all(|spend| check_spend(spend, &sighash, &mut ctx, &spend_pvk)); - let converts_valid = tx_data + let (_, spend_pvk) = load_spend_params(); + let (_, convert_pvk) = load_convert_params(); + let (_, output_pvk) = load_output_params(); + + let mut ctx = SaplingVerificationContext::new(true); + let spends_valid = sapling_bundle.shielded_spends.iter().all(|spend| { + check_spend(spend, sighash.as_ref(), &mut ctx, &spend_pvk) + }); + let converts_valid = sapling_bundle .shielded_converts .iter() .all(|convert| check_convert(convert, &mut ctx, &convert_pvk)); - let outputs_valid = tx_data + let outputs_valid = sapling_bundle .shielded_outputs .iter() .all(|output| check_output(output, &mut ctx, &output_pvk)); @@ -225,15 +320,17 @@ pub fn verify_shielded_tx(transaction: &Transaction) -> bool { tracing::info!("passed spend/output verification"); - let assets_and_values: Vec<(AssetType, i64)> = - tx_data.value_balance.clone().into_components().collect(); + let assets_and_values: Amount = sapling_bundle.value_balance.clone(); - tracing::info!("accumulated {} assets/values", assets_and_values.len()); + tracing::info!( + "accumulated {} assets/values", + assets_and_values.components().len() + ); let result = ctx.final_check( - assets_and_values.as_slice(), - &sighash, - tx_data.binding_sig.unwrap(), + assets_and_values, + sighash.as_ref(), + sapling_bundle.authorization.binding_sig, ); tracing::info!("final check result {result}"); result @@ -250,6 +347,42 @@ pub fn get_params_dir() -> PathBuf { } } +/// Freeze a Builder into the format necessary for inclusion in a Tx. This is +/// the format used by hardware wallets to validate a MASP Transaction. +struct WalletMap; + +impl + masp_primitives::transaction::components::sapling::builder::MapBuilder< + P1, + ExtendedSpendingKey, + (), + ExtendedFullViewingKey, + > for WalletMap +{ + fn map_params(&self, _s: P1) {} + + fn map_key(&self, s: ExtendedSpendingKey) -> ExtendedFullViewingKey { + (&s).into() + } +} + +impl + MapBuilder< + P1, + R1, + ExtendedSpendingKey, + N1, + (), + (), + ExtendedFullViewingKey, + (), + > for WalletMap +{ + fn map_rng(&self, _s: R1) {} + + fn map_notifier(&self, _s: N1) {} +} + /// Abstracts platform specific details away from the logic of shielded pool /// operations. #[async_trait] @@ -347,11 +480,11 @@ pub struct ShieldedContext { /// Maps viewing keys to applicable note positions pub pos_map: HashMap>, /// Maps a nullifier to the note position to which it applies - pub nf_map: HashMap<[u8; 32], usize>, + pub nf_map: HashMap, /// Maps note positions to their corresponding notes pub note_map: HashMap, /// Maps note positions to their corresponding memos - pub memo_map: HashMap, + pub memo_map: HashMap, /// Maps note positions to the diversifier of their payment address pub div_map: HashMap, /// Maps note positions to their witness (used to make merkle paths) @@ -475,8 +608,9 @@ impl ShieldedContext { } // Update this unknown shielded context until it is level with self while tx_ctx.last_txidx != self.last_txidx { - if let Some(((height, idx), (epoch, tx))) = tx_iter.next() { - tx_ctx.scan_tx(*height, *idx, *epoch, tx); + if let Some(((height, idx), (epoch, tx, stx))) = tx_iter.next() + { + tx_ctx.scan_tx(*height, *idx, *epoch, tx, stx); } else { break; } @@ -486,13 +620,15 @@ impl ShieldedContext { self.merge(tx_ctx); } else { // Load only transactions accepted from last_txid until this point - txs = Self::fetch_shielded_transfers(client, self.last_txidx).await; + txs = + Self::fetch_shielded_transfers(client, self.last_txidx) + .await; tx_iter = txs.iter(); } // Now that we possess the unspent notes corresponding to both old and // new keys up until tx_pos, proceed to scan the new transactions. - for ((height, idx), (epoch, tx)) in &mut tx_iter { - self.scan_tx(*height, *idx, *epoch, tx); + for ((height, idx), (epoch, tx, stx)) in &mut tx_iter { + self.scan_tx(*height, *idx, *epoch, tx, stx); } } @@ -504,7 +640,7 @@ impl ShieldedContext { pub async fn fetch_shielded_transfers( client: &U::C, last_txidx: u64, - ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer)> { + ) -> BTreeMap<(BlockHeight, TxIndex), (Epoch, Transfer, Transaction)> { // The address of the MASP account let masp_addr = masp(); // Construct the key where last transaction pointer is stored @@ -512,10 +648,9 @@ impl ShieldedContext { .push(&HEAD_TX_KEY.to_owned()) .expect("Cannot obtain a storage key"); // Query for the index of the last accepted transaction - let head_txidx = - rpc::query_storage_value::(client, &head_tx_key) - .await - .unwrap_or(0); + let head_txidx = query_storage_value::(&client, &head_tx_key) + .await + .unwrap_or(0); let mut shielded_txs = BTreeMap::new(); // Fetch all the transactions we do not have yet for i in last_txidx..head_txidx { @@ -524,15 +659,21 @@ impl ShieldedContext { .push(&(TX_KEY_PREFIX.to_owned() + &i.to_string())) .expect("Cannot obtain a storage key"); // Obtain the current transaction - let (tx_epoch, tx_height, tx_index, current_tx) = - rpc::query_storage_value::< - U::C, - (Epoch, BlockHeight, TxIndex, Transfer), - >(client, ¤t_tx_key) + let (tx_epoch, tx_height, tx_index, current_tx, current_stx) = + query_storage_value::(&client, ¤t_tx_key) .await .unwrap(); // Collect the current transaction - shielded_txs.insert((tx_height, tx_index), (tx_epoch, current_tx)); + shielded_txs.insert( + (tx_height, tx_index), + (tx_epoch, current_tx, current_stx), + ); } shielded_txs } @@ -551,17 +692,15 @@ impl ShieldedContext { index: TxIndex, epoch: Epoch, tx: &Transfer, + shielded: &Transaction, ) { - // Ignore purely transparent transactions - let shielded = if let Some(shielded) = &tx.shielded { - shielded - } else { - return; - }; // For tracking the account changes caused by this Transaction let mut transaction_delta = TransactionDelta::new(); // Listen for notes sent to our viewing keys - for so in &shielded.shielded_outputs { + for so in shielded + .sapling_bundle() + .map_or(&vec![], |x| &x.shielded_outputs) + { // Create merkle tree leaf node from note commitment let node = Node::new(so.cmu.to_repr()); // Update each merkle tree in the witness map with the latest @@ -580,12 +719,11 @@ impl ShieldedContext { // Let's try to see if any of our viewing keys can decrypt latest // note for (vk, notes) in self.pos_map.iter_mut() { - let decres = try_sapling_note_decryption::( - 0, - &vk.ivk().0, - &so.ephemeral_key.into_subgroup().unwrap(), - &so.cmu, - &so.enc_ciphertext, + let decres = try_sapling_note_decryption::<_, OutputDescription<<::SaplingAuth as masp_primitives::transaction::components::sapling::Authorization>::Proof>>( + &NETWORK, + 1.into(), + &PreparedIncomingViewingKey::new(&vk.ivk()), + so, ); // So this current viewing key does decrypt this current note... if let Some((note, pa, memo)) = decres { @@ -593,13 +731,13 @@ impl ShieldedContext { // key notes.insert(note_pos); // Compute the nullifier now to quickly recognize when spent - let nf = note.nf(vk, note_pos.try_into().unwrap()); + let nf = note.nf(&vk.nk, note_pos.try_into().unwrap()); self.note_map.insert(note_pos, note); self.memo_map.insert(note_pos, memo); // The payment address' diversifier is required to spend // note self.div_map.insert(note_pos, *pa.diversifier()); - self.nf_map.insert(nf.0, note_pos); + self.nf_map.insert(nf, note_pos); // Note the account changes let balance = transaction_delta .entry(*vk) @@ -615,7 +753,10 @@ impl ShieldedContext { } } // Cancel out those of our notes that have been spent - for ss in &shielded.shielded_spends { + for ss in shielded + .sapling_bundle() + .map_or(&vec![], |x| &x.shielded_spends) + { // If the shielded spend's nullifier is in our map, then target note // is rendered unusable if let Some(note_pos) = self.nf_map.get(&ss.nullifier) { @@ -952,7 +1093,7 @@ impl ShieldedContext { // Check that the supplied viewing key corresponds to given payment // address let counter_owner = viewing_key.to_payment_address( - *masp_primitives::primitives::PaymentAddress::diversifier( + *masp_primitives::sapling::PaymentAddress::diversifier( &owner.into(), ), ); @@ -967,7 +1108,7 @@ impl ShieldedContext { .push(&(PIN_KEY_PREFIX.to_owned() + &owner.hash())) .expect("Cannot obtain a storage key"); // Obtain the transaction pointer at the key - let txidx = rpc::query_storage_value::(client, &pin_key) + let txidx = rpc::query_storage_value::(&client, &pin_key) .await .ok_or(PinnedBalanceError::NoTransactionPinned)?; // Construct the key for where the pinned transaction is stored @@ -975,25 +1116,28 @@ impl ShieldedContext { .push(&(TX_KEY_PREFIX.to_owned() + &txidx.to_string())) .expect("Cannot obtain a storage key"); // Obtain the pointed to transaction - let (tx_epoch, _tx_height, _tx_index, tx) = rpc::query_storage_value::< - U::C, - (Epoch, BlockHeight, TxIndex, Transfer), - >(client, &tx_key) - .await - .expect("Ill-formed epoch, transaction pair"); + let (tx_epoch, _tx_height, _tx_index, _tx, shielded) = + rpc::query_storage_value::(&client, &tx_key) + .await + .expect("Ill-formed epoch, transaction pair"); // Accumulate the combined output note value into this Amount let mut val_acc = Amount::zero(); - let tx = tx - .shielded - .expect("Pinned Transfers should have shielded part"); - for so in &tx.shielded_outputs { + for so in shielded + .sapling_bundle() + .map_or(&vec![], |x| &x.shielded_outputs) + { // Let's try to see if our viewing key can decrypt current note - let decres = try_sapling_note_decryption::( - 0, - &viewing_key.ivk().0, - &so.ephemeral_key.into_subgroup().unwrap(), - &so.cmu, - &so.enc_ciphertext, + let decres = try_sapling_note_decryption::<_, OutputDescription<<::SaplingAuth as masp_primitives::transaction::components::sapling::Authorization>::Proof>>( + &NETWORK, + 1.into(), + &PreparedIncomingViewingKey::new(&viewing_key.ivk()), + so, ); match decres { // So the given viewing key does decrypt this current note... @@ -1090,49 +1234,61 @@ impl ShieldedContext { client: &U::C, args: args::TxTransfer, shielded_gas: bool, - ) -> Result, builder::Error> + ) -> Result< + Option<( + Builder<(), (), ExtendedFullViewingKey, ()>, + Transaction, + SaplingMetadata, + Epoch, + )>, + builder::Error, + > { // No shielded components are needed when neither source nor destination // are shielded let spending_key = args.source.spending_key(); let payment_address = args.target.payment_address(); + // No shielded components are needed when neither source nor + // destination are shielded if spending_key.is_none() && payment_address.is_none() { return Ok(None); } // We want to fund our transaction solely from supplied spending key let spending_key = spending_key.map(|x| x.into()); let spending_keys: Vec<_> = spending_key.into_iter().collect(); - // Load the current shielded context given the spending key we possess + // Load the current shielded context given the spending key we + // possess let _ = self.load(); self.fetch(client, &spending_keys, &[]).await; - // Save the update state so that future fetches can be short-circuited + // Save the update state so that future fetches can be + // short-circuited let _ = self.save(); // Determine epoch in which to submit potential shielded transaction let epoch = rpc::query_epoch(client).await; - // Context required for storing which notes are in the source's - // possesion - let consensus_branch_id = BranchId::Sapling; + // Context required for storing which notes are in the source's possesion let amt: u64 = args.amount.into(); - let memo: Option = None; + let memo = MemoBytes::empty(); // Now we build up the transaction within this object - let mut builder = Builder::::new(0u32); + let mut builder = Builder::<_, OsRng>::new(NETWORK, 1.into()); // Convert transaction amount into MASP types let (asset_type, amount) = convert_amount(epoch, &args.token, args.amount); + // The fee to be paid for the transaction + let tx_fee; - // Transactions with transparent input and shielded output - // may be affected if constructed close to epoch boundary - let mut epoch_sensitive: bool = false; // If there are shielded inputs if let Some(sk) = spending_key { // Transaction fees need to match the amount in the wrapper Transfer // when MASP source is used - let (_, fee) = - convert_amount(epoch, &args.tx.fee_token, args.tx.fee_amount); - builder.set_fee(fee.clone())?; - // If the gas is coming from the shielded pool, then our shielded - // inputs must also cover the gas fee + let (_, fee) = convert_amount( + epoch, + &args.tx.fee_token, + args.tx.fee_amount, + ); + tx_fee = fee.clone(); + // If the gas is coming from the shielded pool, then our shielded inputs + // must also cover the gas fee let required_amt = if shielded_gas { amount + fee } else { amount }; // Locate unspent notes that can help us meet the transaction amount let (_, unspent_notes, used_convs) = self @@ -1145,142 +1301,131 @@ impl ShieldedContext { .await; // Commit the notes found to our transaction for (diversifier, note, merkle_path) in unspent_notes { - builder.add_sapling_spend( - sk, - diversifier, - note, - merkle_path, - )?; + builder + .add_sapling_spend(sk, diversifier, note, merkle_path) + .map_err(builder::Error::SaplingBuild)?; } // Commit the conversion notes used during summation for (conv, wit, value) in used_convs.values() { if *value > 0 { - builder.add_convert( - conv.clone(), - *value as u64, - wit.clone(), - )?; + builder + .add_sapling_convert( + conv.clone(), + *value as u64, + wit.clone(), + ) + .map_err(builder::Error::SaplingBuild)?; } } } else { // No transfer fees come from the shielded transaction for non-MASP // sources - builder.set_fee(Amount::zero())?; - // We add a dummy UTXO to our transaction, but only the source of - // the parent Transfer object is used to validate fund - // availability - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = ripemd160::Ripemd160::digest( - sha2::Sha256::digest(&secp_pk).as_slice(), - ); - let script = TransparentAddress::PublicKey(hash.into()).script(); - epoch_sensitive = true; - builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { + tx_fee = Amount::zero(); + // We add a dummy UTXO to our transaction, but only the source of the + // parent Transfer object is used to validate fund availability + let source_enc = args + .source + .address() + .expect("source address should be transparent") + .try_to_vec() + .expect("source address encoding"); + let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( + source_enc.as_ref(), + )); + let script = TransparentAddress(hash.into()); + builder + .add_transparent_input(TxOut { asset_type, - value: amt, - script_pubkey: script, - }, - )?; + value: amt.try_into().expect("supplied amount too large"), + address: script, + }) + .map_err(builder::Error::TransparentBuild)?; } // Now handle the outputs of this transaction // If there is a shielded output if let Some(pa) = payment_address { let ovk_opt = spending_key.map(|x| x.expsk.ovk); - builder.add_sapling_output( - ovk_opt, - pa.into(), - asset_type, - amt, - memo.clone(), - )?; + builder + .add_sapling_output( + ovk_opt, + pa.into(), + asset_type, + amt, + memo.clone(), + ) + .map_err(builder::Error::SaplingBuild)?; } else { - epoch_sensitive = false; - // Embed the transparent target address into the shielded - // transaction so that it can be signed + // Embed the transparent target address into the shielded transaction so + // that it can be signed let target_enc = args .target .address() .expect("target address should be transparent") .try_to_vec() .expect("target address encoding"); - let hash = ripemd160::Ripemd160::digest( - sha2::Sha256::digest(target_enc.as_ref()).as_slice(), - ); - builder.add_transparent_output( - &TransparentAddress::PublicKey(hash.into()), - asset_type, - amt, - )?; + let hash = ripemd::Ripemd160::digest(sha2::Sha256::digest( + target_enc.as_ref(), + )); + builder + .add_transparent_output( + &TransparentAddress(hash.into()), + asset_type, + amt.try_into().expect("supplied amount too large"), + ) + .map_err(builder::Error::TransparentBuild)?; } - let prover = self.utils.local_tx_prover(); - // Build and return the constructed transaction - let mut tx = builder.build(consensus_branch_id, &prover); - - if epoch_sensitive { - let new_epoch = rpc::query_epoch(client).await; - - // If epoch has changed, recalculate shielded outputs to match new - // epoch - if new_epoch != epoch { - // Hack: build new shielded transfer with updated outputs - let mut replay_builder = - Builder::::new(0u32); - replay_builder.set_fee(Amount::zero())?; - let ovk_opt = spending_key.map(|x| x.expsk.ovk); - let (new_asset_type, _) = - convert_amount(new_epoch, &args.token, args.amount); - replay_builder.add_sapling_output( - ovk_opt, - payment_address.unwrap().into(), - new_asset_type, - amt, - memo, - )?; - - let secp_sk = secp256k1::SecretKey::from_slice(&[0xcd; 32]) - .expect("secret key"); - let secp_ctx = - secp256k1::Secp256k1::::gen_new(); - let secp_pk = - secp256k1::PublicKey::from_secret_key(&secp_ctx, &secp_sk) - .serialize(); - let hash = ripemd160::Ripemd160::digest( - sha2::Sha256::digest(&secp_pk).as_slice(), - ); - let script = - TransparentAddress::PublicKey(hash.into()).script(); - replay_builder.add_transparent_input( - secp_sk, - OutPoint::new([0u8; 32], 0), - TxOut { - asset_type: new_asset_type, - value: amt, - script_pubkey: script, - }, - )?; - - let (replay_tx, _) = - replay_builder.build(consensus_branch_id, &prover)?; - tx = tx.map(|(t, tm)| { - let mut temp = t.deref().clone(); - temp.shielded_outputs = replay_tx.shielded_outputs.clone(); - temp.value_balance = temp.value_balance.reject(asset_type) - - Amount::from_pair(new_asset_type, amt).unwrap(); - (temp.freeze().unwrap(), tm) - }); + + // Now add outputs representing the change from this payment + if let Some(sk) = spending_key { + // Represents the amount of inputs we are short by + let mut additional = Amount::zero(); + // The change left over from this transaction + let value_balance = builder + .value_balance() + .expect("unable to compute value balance") + - tx_fee.clone(); + for (asset_type, amt) in value_balance.components() { + if *amt >= 0 { + // Send the change in this asset type back to the sender + builder + .add_sapling_output( + Some(sk.expsk.ovk), + sk.default_address().1, + *asset_type, + *amt as u64, + memo.clone(), + ) + .map_err(builder::Error::SaplingBuild)?; + } else { + // Record how much of the current asset type we are short by + additional += + Amount::from_nonnegative(*asset_type, -*amt).unwrap(); + } + } + // If we are short by a non-zero amount, then we have insufficient funds + if additional != Amount::zero() { + return Err(builder::Error::InsufficientFunds(additional)); } } - tx.map(Some) + let prover = if let Ok(params_dir) = env::var(ENV_VAR_MASP_PARAMS_DIR) + { + let params_dir = PathBuf::from(params_dir); + let spend_path = params_dir.join(SPEND_NAME); + let convert_path = params_dir.join(CONVERT_NAME); + let output_path = params_dir.join(OUTPUT_NAME); + LocalTxProver::new(&spend_path, &output_path, &convert_path) + } else { + LocalTxProver::with_default_location() + .expect("unable to load MASP Parameters") + }; + // Build and return the constructed transaction + builder + .clone() + .build(&prover, &FeeRule::non_standard(tx_fee)) + .map(|(tx, metadata)| { + Some((builder.map_builder(WalletMap), tx, metadata, epoch)) + }) } /// Obtain the known effects of all accepted shielded and transparent @@ -1405,45 +1550,21 @@ impl ShieldedContext { /// Extract the payload from the given Tx object fn extract_payload( - tx: Tx, + mut tx: Tx, wrapper: &mut Option, transfer: &mut Option, ) { - match process_tx(tx) { - Ok(TxType::Wrapper(wrapper_tx)) => { - let privkey = ::G2Affine::prime_subgroup_generator(); - extract_payload( - Tx::from(match wrapper_tx.decrypt(privkey) { - Ok(tx) => DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: false, - }, - _ => DecryptedTx::Undecryptable(wrapper_tx.clone()), - }), - wrapper, - transfer, - ); - *wrapper = Some(wrapper_tx); - } - Ok(TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: _, - })) => { - let empty_vec = vec![]; - let tx_data = tx.data.as_ref().unwrap_or(&empty_vec); - let _ = SignedTxData::try_from_slice(tx_data).map(|signed| { - Transfer::try_from_slice(&signed.data.unwrap()[..]) - .map(|tfer| *transfer = Some(tfer)) - }); - } - _ => {} - } + let privkey = + ::G2Affine::prime_subgroup_generator(); + tx.decrypt(privkey).expect("unable to decrypt transaction"); + *wrapper = tx.header.wrapper(); + let _ = tx.data().map(|signed| { + Transfer::try_from_slice(&signed[..]).map(|tfer| *transfer = Some(tfer)) + }); } /// Make asset type corresponding to given address and epoch -fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { +pub fn make_asset_type(epoch: Epoch, token: &Address) -> AssetType { // Typestamp the chosen token with the current epoch let token_bytes = (token, epoch.0) .try_to_vec() diff --git a/shared/src/ledger/native_vp/governance/mod.rs b/shared/src/ledger/native_vp/governance/mod.rs index 2e0de7a18f..2c15a8d3a2 100644 --- a/shared/src/ledger/native_vp/governance/mod.rs +++ b/shared/src/ledger/native_vp/governance/mod.rs @@ -15,6 +15,7 @@ use utils::is_valid_validator_voting_period; use crate::ledger::native_vp::{Ctx, NativeVp}; use crate::ledger::storage_api::StorageRead; use crate::ledger::{native_vp, pos}; +use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Epoch, Key}; use crate::types::token; @@ -53,7 +54,7 @@ where fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, verifiers: &BTreeSet
, ) -> Result { @@ -100,7 +101,13 @@ where (KeyType::PROPOSAL_COMMIT, _) => { self.is_valid_proposal_commit() } - (KeyType::PARAMETER, _) => self.is_valid_parameter(tx_data), + (KeyType::PARAMETER, _) => self.is_valid_parameter( + if let Some(data) = &tx_data.data() { + data + } else { + return false; + }, + ), (KeyType::BALANCE, _) => self.is_valid_balance(&native_token), (KeyType::UNKNOWN_GOVERNANCE, _) => Ok(false), (KeyType::UNKNOWN, _) => Ok(true), diff --git a/shared/src/ledger/native_vp/mod.rs b/shared/src/ledger/native_vp/mod.rs index f1fbf9944a..82e94ceae6 100644 --- a/shared/src/ledger/native_vp/mod.rs +++ b/shared/src/ledger/native_vp/mod.rs @@ -42,7 +42,7 @@ pub trait NativeVp { /// Run the validity predicate fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, verifiers: &BTreeSet
, ) -> std::result::Result; @@ -473,7 +473,7 @@ where fn eval( &self, vp_code_hash: Hash, - input_data: Vec, + input_data: Tx, ) -> Result { #[cfg(feature = "wasm-runtime")] { @@ -532,19 +532,11 @@ where } } - fn verify_tx_signature( - &self, - pk: &crate::types::key::common::PublicKey, - sig: &crate::types::key::common::Signature, - ) -> Result { - Ok(self.tx.verify_sig(pk, sig).is_ok()) - } - fn verify_masp(&self, _tx: Vec) -> Result { unimplemented!("no masp native vp") } - fn get_tx_code_hash(&self) -> Result { + fn get_tx_code_hash(&self) -> Result, storage_api::Error> { vp_host_fns::get_tx_code_hash(&mut self.gas_meter.borrow_mut(), self.tx) .into_storage_result() } diff --git a/shared/src/ledger/native_vp/parameters.rs b/shared/src/ledger/native_vp/parameters.rs index 6c41ba8dc3..d367c16698 100644 --- a/shared/src/ledger/native_vp/parameters.rs +++ b/shared/src/ledger/native_vp/parameters.rs @@ -3,6 +3,7 @@ use std::collections::BTreeSet; use namada_core::ledger::storage; +use namada_core::proto::Tx; use namada_core::types::address::{Address, InternalAddress}; use namada_core::types::storage::Key; use thiserror::Error; @@ -44,16 +45,21 @@ where fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { let result = keys_changed.iter().all(|key| { let key_type: KeyType = key.into(); + let data = if let Some(data) = tx_data.data() { + data + } else { + return false; + }; match key_type { KeyType::PARAMETER => governance::utils::is_proposal_accepted( &self.ctx.pre(), - tx_data, + &data, ) .unwrap_or(false), KeyType::UNKNOWN_PARAMETER => false, diff --git a/shared/src/ledger/native_vp/replay_protection.rs b/shared/src/ledger/native_vp/replay_protection.rs index 3e3c4b7ca0..9b300e376b 100644 --- a/shared/src/ledger/native_vp/replay_protection.rs +++ b/shared/src/ledger/native_vp/replay_protection.rs @@ -8,6 +8,7 @@ use namada_core::types::storage::Key; use thiserror::Error; use crate::ledger::native_vp::{self, Ctx, NativeVp}; +use crate::proto::Tx; use crate::vm::WasmCacheAccess; #[allow(missing_docs)] @@ -43,7 +44,7 @@ where fn validate_tx( &self, - _tx_data: &[u8], + _tx_data: &Tx, _keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { diff --git a/shared/src/ledger/native_vp/slash_fund.rs b/shared/src/ledger/native_vp/slash_fund.rs index 4caf0f3401..bed71d3bd9 100644 --- a/shared/src/ledger/native_vp/slash_fund.rs +++ b/shared/src/ledger/native_vp/slash_fund.rs @@ -10,6 +10,7 @@ use thiserror::Error; use crate::ledger::native_vp::{self, governance, Ctx, NativeVp}; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::StorageRead; +use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::Key; use crate::types::token; @@ -48,7 +49,7 @@ where fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { @@ -60,9 +61,14 @@ where if addr.ne(&slash_fund::ADDRESS) { return true; } + let data = if let Some(data) = tx_data.data() { + data + } else { + return false; + }; governance::utils::is_proposal_accepted( &self.ctx.pre(), - tx_data, + &data, ) .unwrap_or(false) } diff --git a/shared/src/ledger/pos/vp.rs b/shared/src/ledger/pos/vp.rs index 1a3bf48fc5..099b6e1798 100644 --- a/shared/src/ledger/pos/vp.rs +++ b/shared/src/ledger/pos/vp.rs @@ -20,6 +20,7 @@ use crate::ledger::native_vp::{self, governance, Ctx, NativeVp}; // }; use crate::ledger::storage::{self as ledger_storage, StorageHasher}; use crate::ledger::storage_api::StorageRead; +use crate::proto::Tx; use crate::types::address::{Address, InternalAddress}; use crate::types::storage::{Key, KeySeg}; use crate::vm::WasmCacheAccess; @@ -93,7 +94,7 @@ where fn validate_tx( &self, - tx_data: &[u8], + tx_data: &Tx, keys_changed: &BTreeSet, _verifiers: &BTreeSet
, ) -> Result { @@ -110,9 +111,14 @@ where for key in keys_changed { // println!("KEY: {}\n", key); if is_params_key(key) { + let data = if let Some(data) = tx_data.data() { + data + } else { + return Ok(false); + }; if !governance::utils::is_proposal_accepted( &self.ctx.pre(), - tx_data, + &data, ) .map_err(Error::NativeVpError)? { diff --git a/shared/src/ledger/protocol/mod.rs b/shared/src/ledger/protocol/mod.rs index 4090c05a4d..94e63e542d 100644 --- a/shared/src/ledger/protocol/mod.rs +++ b/shared/src/ledger/protocol/mod.rs @@ -28,6 +28,8 @@ use crate::vm::{self, wasm, WasmCacheAccess}; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { + #[error("Missing wasm code error")] + MissingCode, #[error("Storage error: {0}")] StorageError(crate::ledger::storage::Error), #[error("Error decoding a transaction from bytes: {0}")] @@ -78,7 +80,7 @@ pub type Result = std::result::Result; /// but no further validations. #[allow(clippy::too_many_arguments)] pub fn apply_tx( - tx: TxType, + tx: Tx, tx_length: usize, tx_index: TxIndex, block_gas_meter: &mut BlockGasMeter, @@ -96,10 +98,9 @@ where block_gas_meter .add_base_transaction_fee(tx_length) .map_err(Error::GasError)?; - match tx { - TxType::Raw(_) => Err(Error::TxTypeError), + match tx.header().tx_type { + TxType::Raw => Err(Error::TxTypeError), TxType::Decrypted(DecryptedTx::Decrypted { - tx, #[cfg(not(feature = "mainnet"))] has_valid_pow, }) => { @@ -167,15 +168,12 @@ where H: 'static + StorageHasher + Sync, CA: 'static + WasmCacheAccess + Sync, { - let empty = vec![]; - let tx_data = tx.data.as_ref().unwrap_or(&empty); wasm::run::tx( storage, write_log, gas_meter, tx_index, - &tx.code_or_hash, - tx_data, + tx, vp_wasm_cache, tx_wasm_cache, ) @@ -295,10 +293,6 @@ where &verifiers, vp_wasm_cache.clone(), ); - let tx_data = match tx.data.as_ref() { - Some(data) => &data[..], - None => &[], - }; let accepted: Result = match internal_addr { InternalAddress::PoS => { @@ -313,7 +307,7 @@ where let result = match panic::catch_unwind(move || { pos_ref .validate_tx( - tx_data, + tx, keys_changed_ref, verifiers_addr_ref, ) @@ -335,7 +329,7 @@ where InternalAddress::Ibc => { let ibc = Ibc { ctx }; let result = ibc - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::IbcNativeVpError); // Take the gas meter back out of the context gas_meter = ibc.ctx.gas_meter.into_inner(); @@ -344,7 +338,7 @@ where InternalAddress::Parameters => { let parameters = ParametersVp { ctx }; let result = parameters - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::ParametersNativeVpError); // Take the gas meter back out of the context gas_meter = parameters.ctx.gas_meter.into_inner(); @@ -360,7 +354,7 @@ where InternalAddress::Governance => { let governance = GovernanceVp { ctx }; let result = governance - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::GovernanceNativeVpError); gas_meter = governance.ctx.gas_meter.into_inner(); result @@ -368,7 +362,7 @@ where InternalAddress::SlashFund => { let slash_fund = SlashFundVp { ctx }; let result = slash_fund - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::SlashFundNativeVpError); gas_meter = slash_fund.ctx.gas_meter.into_inner(); result @@ -380,7 +374,7 @@ where // validate the transfer let ibc_token = IbcToken { ctx }; let result = ibc_token - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::IbcTokenNativeVpError); gas_meter = ibc_token.ctx.gas_meter.into_inner(); result @@ -388,7 +382,7 @@ where InternalAddress::EthBridge => { let bridge = EthBridge { ctx }; let result = bridge - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::EthBridgeNativeVpError); gas_meter = bridge.ctx.gas_meter.into_inner(); result @@ -397,7 +391,7 @@ where let replay_protection_vp = ReplayProtectionVp { ctx }; let result = replay_protection_vp - .validate_tx(tx_data, &keys_changed, &verifiers) + .validate_tx(tx, &keys_changed, &verifiers) .map_err(Error::ReplayProtectionNativeVpError); gas_meter = replay_protection_vp.ctx.gas_meter.into_inner(); diff --git a/shared/src/ledger/queries/shell.rs b/shared/src/ledger/queries/shell.rs index 37e6fe38bb..daec69cfb6 100644 --- a/shared/src/ledger/queries/shell.rs +++ b/shared/src/ledger/queries/shell.rs @@ -74,16 +74,10 @@ where use crate::ledger::storage::write_log::WriteLog; use crate::proto::Tx; use crate::types::storage::TxIndex; - use crate::types::transaction::{DecryptedTx, TxType}; let mut gas_meter = BlockGasMeter::default(); let mut write_log = WriteLog::default(); let tx = Tx::try_from(&request.data[..]).into_storage_result()?; - let tx = TxType::Decrypted(DecryptedTx::Decrypted { - tx, - #[cfg(not(feature = "mainnet"))] - has_valid_pow: true, - }); let data = protocol::apply_tx( tx, request.data.len(), @@ -350,9 +344,11 @@ mod test { use crate::ledger::queries::testing::TestClient; use crate::ledger::queries::RPC; use crate::ledger::storage_api::{self, StorageWrite}; - use crate::proto::Tx; + use crate::proto::{Code, Data, Tx}; use crate::types::hash::Hash; use crate::types::storage::Key; + use crate::types::transaction::decrypted::DecryptedTx; + use crate::types::transaction::TxType; use crate::types::{address, token}; #[test] @@ -393,13 +389,16 @@ mod test { assert_eq!(current_epoch, read_epoch); // Request dry run tx - let tx = Tx::new( - tx_hash.to_vec(), - None, - client.wl_storage.storage.chain_id.clone(), - None, - ); - let tx_bytes = tx.to_bytes(); + let mut outer_tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + // To be able to dry-run testnet faucet withdrawal, pretend + // that we got a valid PoW + has_valid_pow: true, + })); + outer_tx.header.chain_id = client.wl_storage.storage.chain_id.clone(); + outer_tx.set_code(Code::from_hash(tx_hash)); + outer_tx.set_data(Data::new(vec![])); + let tx_bytes = outer_tx.to_bytes(); let result = RPC .shell() .dry_run_tx(&client, Some(tx_bytes), None, false) diff --git a/shared/src/ledger/signing.rs b/shared/src/ledger/signing.rs index e01e765613..2282c35782 100644 --- a/shared/src/ledger/signing.rs +++ b/shared/src/ledger/signing.rs @@ -1,18 +1,66 @@ //! Functions to sign transactions -use borsh::BorshSerialize; -use namada_core::ledger::parameters::storage as parameter_storage; use namada_core::types::address::{Address, ImplicitAddress}; use namada_core::types::token::{self, Amount}; -use namada_core::types::transaction::MIN_FEE; +use namada_core::types::transaction::{pos, MIN_FEE}; +use namada_core::types::address::masp; +use std::env; +use crate::ibc_proto::google::protobuf::Any; +use prost::Message; use crate::ledger::rpc::TxBroadcastData; use crate::ledger::tx::Error; use crate::ledger::wallet::{Wallet, WalletUtils}; use crate::ledger::{args, rpc}; -use crate::proto::Tx; +use crate::proto::{Section, Tx, Signature}; use crate::types::key::*; use crate::types::storage::Epoch; use crate::types::transaction::{hash_tx, Fee, WrapperTx}; +use crate::types::transaction::TxType; +use crate::ledger::rpc::query_wasm_code_hash; +use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; + +use std::collections::{BTreeMap, HashMap}; +use std::fs::File; +use std::io::{ErrorKind, Write}; +use crate::ibc::applications::transfer::msgs::transfer::{ + TYPE_URL as MSG_TRANSFER_TYPE_URL, +}; + +use borsh::{BorshDeserialize, BorshSerialize}; +use data_encoding::HEXLOWER; +use itertools::Itertools; +use masp_primitives::asset_type::AssetType; +use masp_primitives::transaction::components::sapling::fees::{ + InputView, OutputView, +}; +use crate::ledger::parameters::storage as parameter_storage; +use crate::types::key::*; +use crate::types::masp::{ExtendedViewingKey, PaymentAddress}; +use crate::types::token::Transfer; +use crate::types::transaction::decrypted::DecryptedTx; +use crate::types::transaction::governance::{ + InitProposalData, VoteProposalData, +}; +use crate::types::transaction::{ + InitAccount, InitValidator, UpdateVp, +}; +use serde::{Deserialize, Serialize}; + +pub use crate::ledger::wallet::store::AddressVpType; +use crate::ledger::masp::make_asset_type; +use crate::ledger::tx::{ + TX_BOND_WASM, TX_CHANGE_COMMISSION_WASM, TX_IBC_WASM, + TX_INIT_ACCOUNT_WASM, TX_INIT_PROPOSAL, TX_INIT_VALIDATOR_WASM, + TX_REVEAL_PK, TX_TRANSFER_WASM, TX_UNBOND_WASM, TX_UPDATE_VP_WASM, + TX_VOTE_PROPOSAL, TX_WITHDRAW_WASM, VP_USER_WASM, +}; +use crate::ledger::tx; +use crate::types::key::*; + +/// Env. var specifying where to store signing test vectors +const ENV_VAR_LEDGER_LOG_PATH: &str = "NAMADA_LEDGER_LOG_PATH"; +/// Env. var specifying where to store transaction debug outputs +const ENV_VAR_TX_LOG_PATH: &str = "NAMADA_TX_LOG_PATH"; /// Find the public key for the given address and try to load the keypair /// for it from the wallet. If the keypair is encrypted but a password is not @@ -122,13 +170,7 @@ pub async fn tx_signer< } Ok(signing_key) } - TxSigningKey::SecretKey(signing_key) => { - // Check if the signing key needs to reveal its PK first - let pk: common::PublicKey = signing_key.ref_to(); - super::tx::reveal_pk_if_needed::(client, wallet, &pk, args) - .await?; - Ok(signing_key) - } + TxSigningKey::SecretKey(signing_key) => Ok(signing_key), TxSigningKey::None => other_err( "All transactions must be signed; please either specify the key \ or the address from which to look up the signing key." @@ -151,21 +193,37 @@ pub async fn sign_tx< >( client: &C, wallet: &mut Wallet, - tx: Tx, + mut tx: Tx, args: &args::Tx, default: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> Result { let keypair = tx_signer::(client, wallet, args, default).await?; - let tx = tx.sign(&keypair); + // Sign over the transacttion data + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + // Sign over the transaction code + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair, + ))); let epoch = rpc::query_epoch(client).await; let broadcast_data = if args.dry_run { + tx.update_header(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + // To be able to dry-run testnet faucet withdrawal, pretend + // that we got a valid PoW + has_valid_pow: true, + })); TxBroadcastData::DryRun(tx) } else { sign_wrapper( client, + wallet, args, epoch, tx, @@ -182,11 +240,15 @@ pub async fn sign_tx< /// Create a wrapper tx from a normal tx. Get the hash of the /// wrapper and its payload which is needed for monitoring its /// progress on chain. -pub async fn sign_wrapper( +pub async fn sign_wrapper< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, + >( client: &C, + wallet: &mut Wallet, args: &args::Tx, epoch: Epoch, - tx: Tx, + mut tx: Tx, keypair: &common::SecretKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, ) -> TxBroadcastData { @@ -194,35 +256,46 @@ pub async fn sign_wrapper( Amount::whole(MIN_FEE) } else { let wrapper_tx_fees_key = parameter_storage::get_wrapper_tx_fees_key(); - rpc::query_storage_value::( - client, - &wrapper_tx_fees_key, - ) - .await - .unwrap_or_default() + rpc::query_storage_value::(&client, &wrapper_tx_fees_key) + .await + .unwrap_or_default() }; - let fee_token = args.fee_token.clone(); + let fee_token = &args.fee_token; let source = Address::from(&keypair.ref_to()); let balance_key = token::balance_key(&fee_token, &source); let balance = - rpc::query_storage_value::(client, &balance_key) - .await - .unwrap_or_default(); - - // todo: provide sdk clients an error if the fee balance is insufficient + rpc::query_storage_value::(&client, &balance_key) + .await + .unwrap_or_default(); + let is_bal_sufficient = fee_amount <= balance; + if !is_bal_sufficient { + eprintln!( + "The wrapper transaction source doesn't have enough balance \ + to pay fee {fee_amount}, got {balance}." + ); + if !args.force && cfg!(feature = "mainnet") { + panic!( + "The wrapper transaction source doesn't have enough balance \ + to pay fee {fee_amount}, got {balance}." + ); + } + } #[cfg(not(feature = "mainnet"))] // A PoW solution can be used to allow zero-fee testnet transactions - let pow_solution: Option = { + let pow_solution: Option = { // If the address derived from the keypair doesn't have enough balance // to pay for the fee, allow to find a PoW solution instead. - if requires_pow || balance < fee_amount { + if requires_pow || !is_bal_sufficient { println!( "The transaction requires the completion of a PoW challenge." ); // Obtain a PoW challenge for faucet withdrawal - let challenge = - rpc::get_testnet_pow_challenge(client, source).await; + let challenge = rpc::get_testnet_pow_challenge( + client, + source, + ) + .await; // Solve the solution, this blocks until a solution is found let solution = challenge.solve(); @@ -232,32 +305,74 @@ pub async fn sign_wrapper( } }; - let tx = { - WrapperTx::new( - Fee { - amount: fee_amount, - token: fee_token, - }, - keypair, - epoch, - args.gas_limit.clone(), - tx, - // TODO: Actually use the fetched encryption key - Default::default(), - #[cfg(not(feature = "mainnet"))] - pow_solution, - ) - }; + // This object governs how the payload will be processed + tx.update_header(TxType::Wrapper(Box::new(WrapperTx::new( + Fee { + amount: fee_amount, + token: fee_token.clone(), + }, + keypair, + epoch, + args.gas_limit.clone(), + #[cfg(not(feature = "mainnet"))] + pow_solution, + )))); + tx.header.chain_id = args.chain_id.clone().unwrap(); + tx.header.expiration = args.expiration; + // Then sign over the bound wrapper + tx.add_section(Section::Signature(Signature::new( + &tx.header_hash(), + keypair, + ))); + + // Attempt to decode the construction + if let Ok(path) = env::var(ENV_VAR_LEDGER_LOG_PATH) { + let mut tx = tx.clone(); + // Contract the large data blobs in the transaction + tx.wallet_filter(); + // Convert the transaction to Ledger format + let decoding = + to_ledger_vector(client, wallet, &tx).await.expect("unable to decode transaction"); + let output = serde_json::to_string(&decoding) + .expect("failed to serialize decoding"); + // Record the transaction at the identified path + let mut f = File::options() + .append(true) + .create(true) + .open(path) + .expect("failed to open test vector file"); + writeln!(f, "{},", output) + .expect("unable to write test vector to file"); + } + // Attempt to decode the construction + if let Ok(path) = env::var(ENV_VAR_TX_LOG_PATH) { + let mut tx = tx.clone(); + // Contract the large data blobs in the transaction + tx.wallet_filter(); + // Record the transaction at the identified path + let mut f = File::options() + .append(true) + .create(true) + .open(path) + .expect("failed to open test vector file"); + writeln!(f, "{:x?},", tx).expect("unable to write test vector to file"); + } + // Remove all the sensitive sections + tx.protocol_filter(); + // Encrypt all sections not relating to the header + tx.encrypt(&Default::default()); // We use this to determine when the wrapper tx makes it on-chain - let wrapper_hash = hash_tx(&tx.try_to_vec().unwrap()).to_string(); + let wrapper_hash = tx.header_hash().to_string(); // We use this to determine when the decrypted inner tx makes it // on-chain - let decrypted_hash = tx.tx_hash.to_string(); + let decrypted_hash = tx + .clone() + .update_header(TxType::Raw) + .header_hash() + .to_string(); TxBroadcastData::Wrapper { - tx: tx - .sign(keypair, args.chain_id.clone().unwrap(), args.expiration) - .expect("Wrapper tx signing keypair should be correct"), + tx, wrapper_hash, decrypted_hash, } @@ -266,3 +381,644 @@ pub async fn sign_wrapper( fn other_err(string: String) -> Result { Err(Error::Other(string)) } + +/// Represents the transaction data that is displayed on a Ledger device +#[derive(Default, Serialize, Deserialize)] +struct LedgerVector { + blob: String, + index: u64, + name: String, + output: Vec, + output_expert: Vec, + valid: bool, +} + +/// Adds a Ledger output line describing a given transaction amount and address +fn make_ledger_amount_addr( + tokens: &HashMap, + output: &mut Vec, + amount: Amount, + token: &Address, + prefix: &str, +) { + if let Some(token) = tokens.get(token) { + output.push(format!("{}Amount: {} {}", prefix, token, amount)); + } else { + output.extend(vec![ + format!("{}Token: {}", prefix, token), + format!("{}Amount: {}", prefix, amount), + ]); + } +} + +/// Adds a Ledger output line describing a given transaction amount and asset +/// type +fn make_ledger_amount_asset( + tokens: &HashMap, + output: &mut Vec, + amount: u64, + token: &AssetType, + assets: &HashMap, + prefix: &str, +) { + if let Some((token, _epoch)) = assets.get(token) { + // If the AssetType can be decoded, then at least display Addressees + if let Some(token) = tokens.get(token) { + output.push(format!( + "{}Amount: {} {}", + prefix, + token, + Amount::from(amount) + )); + } else { + output.extend(vec![ + format!("{}Token: {}", prefix, token), + format!("{}Amount: {}", prefix, Amount::from(amount)), + ]); + } + } else { + // Otherwise display the raw AssetTypes + output.extend(vec![ + format!("{}Token: {}", prefix, token), + format!("{}Amount: {}", prefix, Amount::from(amount)), + ]); + } +} + +/// Split the lines in the vector that are longer than the Ledger device's +/// character width +fn format_outputs(output: &mut Vec) { + const LEDGER_WIDTH: usize = 60; + + let mut i = 0; + let mut pos = 0; + // Break down each line that is too long one-by-one + while pos < output.len() { + let prefix_len = i.to_string().len() + 3; + let curr_line = output[pos].clone(); + if curr_line.len() + prefix_len < LEDGER_WIDTH { + // No need to split the line in this case + output[pos] = format!("{} | {}", i, curr_line); + pos += 1; + } else { + // Line is too long so split it up. Repeat the key on each line + let (mut key, mut value) = + curr_line.split_once(':').unwrap_or(("", &curr_line)); + key = key.trim(); + value = value.trim(); + if value.is_empty() { + value = "(none)" + } + + // First comput how many lines we will break the current one up into + let mut digits = 1; + let mut line_space; + let mut lines; + loop { + let prefix_len = prefix_len + 7 + 2 * digits + key.len(); + line_space = LEDGER_WIDTH - prefix_len; + lines = (value.len() + line_space - 1) / line_space; + if lines.to_string().len() <= digits { + break; + } else { + digits += 1; + } + } + + // Then break up this line according to the above plan + output.remove(pos); + for (idx, part) in + value.chars().chunks(line_space).into_iter().enumerate() + { + let line = format!( + "{} | {} [{}/{}] : {}", + i, + key, + idx + 1, + lines, + part.collect::(), + ); + output.insert(pos, line); + pos += 1; + } + } + i += 1; + } +} + +/// Converts the given transaction to the form that is displayed on the Ledger +/// device +async fn to_ledger_vector< + C: crate::ledger::queries::Client + Sync, + U: WalletUtils, +>( + client: &C, + wallet: &mut Wallet, + tx: &Tx, +) -> Result { + let init_account_hash = query_wasm_code_hash(client, TX_INIT_ACCOUNT_WASM).await.unwrap(); + let init_validator_hash = query_wasm_code_hash(client, TX_INIT_VALIDATOR_WASM).await.unwrap(); + let init_proposal_hash = query_wasm_code_hash(client, TX_INIT_PROPOSAL).await.unwrap(); + let vote_proposal_hash = query_wasm_code_hash(client, TX_VOTE_PROPOSAL).await.unwrap(); + let reveal_pk_hash = query_wasm_code_hash(client, TX_REVEAL_PK).await.unwrap(); + let update_vp_hash = query_wasm_code_hash(client, TX_UPDATE_VP_WASM).await.unwrap(); + let transfer_hash = query_wasm_code_hash(client, TX_TRANSFER_WASM).await.unwrap(); + let ibc_hash = query_wasm_code_hash(client, TX_IBC_WASM).await.unwrap(); + let bond_hash = query_wasm_code_hash(client, TX_BOND_WASM).await.unwrap(); + let unbond_hash = query_wasm_code_hash(client, TX_UNBOND_WASM).await.unwrap(); + let withdraw_hash = query_wasm_code_hash(client, TX_WITHDRAW_WASM).await.unwrap(); + let change_commission_hash = + query_wasm_code_hash(client, TX_CHANGE_COMMISSION_WASM).await.unwrap(); + let user_hash = query_wasm_code_hash(client, VP_USER_WASM).await.unwrap(); + + // To facilitate lookups of human-readable token names + let tokens: HashMap = wallet + .get_addresses_with_vp_type(AddressVpType::Token) + .into_iter() + .map(|addr| { + let alias = match wallet.find_alias(&addr) { + Some(alias) => alias.to_string(), + None => addr.to_string(), + }; + (addr, alias) + }) + .collect(); + + let mut tv = LedgerVector { + blob: HEXLOWER + .encode(&tx.try_to_vec().expect("unable to serialize transaction")), + index: 0, + valid: true, + name: "Custom 0".to_string(), + ..Default::default() + }; + + let code_hash = tx + .get_section(tx.code_sechash()) + .expect("expected tx code section to be present") + .code_sec() + .expect("expected section to have code tag") + .code + .hash(); + tv.output_expert + .push(format!("Code hash : {}", HEXLOWER.encode(&code_hash.0))); + + if code_hash == init_account_hash { + let init_account = InitAccount::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Init Account 0".to_string(); + + let extra = tx + .get_section(&init_account.vp_code_hash) + .and_then(Section::extra_data_sec) + .expect("unable to load vp code") + .code + .hash(); + let vp_code = if extra == user_hash { + "User".to_string() + } else { + HEXLOWER.encode(&extra.0) + }; + + tv.output.extend(vec![ + format!("Type : Init Account"), + format!("Public key : {}", init_account.public_key), + format!("VP type : {}", vp_code), + ]); + + tv.output_expert.extend(vec![ + format!("Public key : {}", init_account.public_key), + format!("VP type : {}", HEXLOWER.encode(&extra.0)), + ]); + } else if code_hash == init_validator_hash { + let init_validator = InitValidator::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Init Validator 0".to_string(); + + let extra = tx + .get_section(&init_validator.validator_vp_code_hash) + .and_then(Section::extra_data_sec) + .expect("unable to load vp code") + .code + .hash(); + let vp_code = if extra == user_hash { + "User".to_string() + } else { + HEXLOWER.encode(&extra.0) + }; + + tv.output.extend(vec![ + format!("Type : Init Validator"), + format!("Account key : {}", init_validator.account_key), + format!("Consensus key : {}", init_validator.consensus_key), + format!("Protocol key : {}", init_validator.protocol_key), + format!("DKG key : {}", init_validator.dkg_key), + format!("Commission rate : {}", init_validator.commission_rate), + format!( + "Maximum commission rate change : {}", + init_validator.max_commission_rate_change + ), + format!("Validator VP type : {}", vp_code,), + ]); + + tv.output_expert.extend(vec![ + format!("Account key : {}", init_validator.account_key), + format!("Consensus key : {}", init_validator.consensus_key), + format!("Protocol key : {}", init_validator.protocol_key), + format!("DKG key : {}", init_validator.dkg_key), + format!("Commission rate : {}", init_validator.commission_rate), + format!( + "Maximum commission rate change : {}", + init_validator.max_commission_rate_change + ), + format!("Validator VP type : {}", HEXLOWER.encode(&extra.0)), + ]); + } else if code_hash == init_proposal_hash { + let init_proposal_data = InitProposalData::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Init Proposal 0".to_string(); + + let init_proposal_data_id = init_proposal_data + .id + .as_ref() + .map(u64::to_string) + .unwrap_or_else(|| "(none)".to_string()); + tv.output.extend(vec![ + format!("Type : Init proposal"), + format!("ID : {}", init_proposal_data_id), + format!("Author : {}", init_proposal_data.author), + format!( + "Voting start epoch : {}", + init_proposal_data.voting_start_epoch + ), + format!( + "Voting end epoch : {}", + init_proposal_data.voting_end_epoch + ), + format!("Grace epoch : {}", init_proposal_data.grace_epoch), + ]); + let content: BTreeMap = + BorshDeserialize::try_from_slice(&init_proposal_data.content)?; + if !content.is_empty() { + for (key, value) in &content { + tv.output.push(format!("Content {} : {}", key, value)); + } + } else { + tv.output.push("Content : (none)".to_string()); + } + + tv.output_expert.extend(vec![ + format!("ID : {}", init_proposal_data_id), + format!("Author : {}", init_proposal_data.author), + format!( + "Voting start epoch : {}", + init_proposal_data.voting_start_epoch + ), + format!( + "Voting end epoch : {}", + init_proposal_data.voting_end_epoch + ), + format!("Grace epoch : {}", init_proposal_data.grace_epoch), + ]); + if !content.is_empty() { + for (key, value) in content { + tv.output_expert + .push(format!("Content {} : {}", key, value)); + } + } else { + tv.output_expert.push("Content : none".to_string()); + } + } else if code_hash == vote_proposal_hash { + let vote_proposal = VoteProposalData::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Vote Proposal 0".to_string(); + + tv.output.extend(vec![ + format!("Type : Vote Proposal"), + format!("ID : {}", vote_proposal.id), + format!("Vote : {}", vote_proposal.vote), + format!("Voter : {}", vote_proposal.voter), + ]); + for delegation in &vote_proposal.delegations { + tv.output.push(format!("Delegations : {}", delegation)); + } + + tv.output_expert.extend(vec![ + format!("ID : {}", vote_proposal.id), + format!("Vote : {}", vote_proposal.vote), + format!("Voter : {}", vote_proposal.voter), + ]); + for delegation in vote_proposal.delegations { + tv.output_expert + .push(format!("Delegations : {}", delegation)); + } + } else if code_hash == reveal_pk_hash { + let public_key = common::PublicKey::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Init Account 0".to_string(); + + tv.output.extend(vec![ + format!("Type : Reveal PK"), + format!("Public key : {}", public_key), + ]); + + tv.output_expert + .extend(vec![format!("Public key : {}", public_key)]); + } else if code_hash == update_vp_hash { + let transfer = UpdateVp::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Update VP 0".to_string(); + + let extra = tx + .get_section(&transfer.vp_code_hash) + .and_then(Section::extra_data_sec) + .expect("unable to load vp code") + .code + .hash(); + let vp_code = if extra == user_hash { + "User".to_string() + } else { + HEXLOWER.encode(&extra.0) + }; + + tv.output.extend(vec![ + format!("Type : Update VP"), + format!("Address : {}", transfer.addr), + format!("VP type : {}", vp_code), + ]); + + tv.output_expert.extend(vec![ + format!("Address : {}", transfer.addr), + format!("VP type : {}", HEXLOWER.encode(&extra.0)), + ]); + } else if code_hash == transfer_hash { + let transfer = Transfer::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + // To facilitate lookups of MASP AssetTypes + let mut asset_types = HashMap::new(); + let builder = if let Some(shielded_hash) = transfer.shielded { + tx.sections.iter().find_map(|x| match x { + Section::MaspBuilder(builder) + if builder.target == shielded_hash => + { + for (addr, epoch) in &builder.asset_types { + asset_types.insert( + make_asset_type(*epoch, addr), + (addr.clone(), *epoch), + ); + } + Some(builder) + } + _ => None, + }) + } else { + None + }; + + tv.name = "Transfer 0".to_string(); + + tv.output.push("Type : Transfer".to_string()); + if transfer.source != masp() { + tv.output.push(format!("Sender : {}", transfer.source)); + if transfer.target == masp() { + make_ledger_amount_addr( + &tokens, + &mut tv.output, + transfer.amount, + &transfer.token, + "Sending ", + ); + } + } else if let Some(builder) = builder { + for input in builder.builder.sapling_inputs() { + let vk = ExtendedViewingKey::from(*input.key()); + tv.output.push(format!("Sender : {}", vk)); + make_ledger_amount_asset( + &tokens, + &mut tv.output, + input.value(), + &input.asset_type(), + &asset_types, + "Sending ", + ); + } + } + if transfer.target != masp() { + tv.output.push(format!("Destination : {}", transfer.target)); + if transfer.source == masp() { + make_ledger_amount_addr( + &tokens, + &mut tv.output, + transfer.amount, + &transfer.token, + "Receiving ", + ); + } + } else if let Some(builder) = builder { + for output in builder.builder.sapling_outputs() { + let pa = PaymentAddress::from(output.address()); + tv.output.push(format!("Destination : {}", pa)); + make_ledger_amount_asset( + &tokens, + &mut tv.output, + output.value(), + &output.asset_type(), + &asset_types, + "Receiving ", + ); + } + } + if transfer.source != masp() && transfer.target != masp() { + make_ledger_amount_addr( + &tokens, + &mut tv.output, + transfer.amount, + &transfer.token, + "", + ); + } + + tv.output_expert.extend(vec![ + format!("Source : {}", transfer.source), + format!("Target : {}", transfer.target), + format!("Token : {}", transfer.token), + format!("Amount : {}", transfer.amount), + ]); + } else if code_hash == ibc_hash { + let msg = Any::decode( + tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))? + .as_ref(), + ) + .map_err(|x| std::io::Error::new(ErrorKind::Other, x))?; + + tv.name = "IBC 0".to_string(); + tv.output.push("Type : IBC".to_string()); + + match msg.type_url.as_str() { + MSG_TRANSFER_TYPE_URL => { + let transfer = + MsgTransfer::try_from(msg).map_err(|_| std::io::Error::from(ErrorKind::InvalidData))?; + let transfer_token = format!("{} {}", transfer.token.amount, transfer.token.denom); + tv.output.extend(vec![ + format!("Source port : {}", transfer.port_id_on_a), + format!("Source channel : {}", transfer.chan_id_on_a), + format!("Token : {}", transfer_token), + format!("Sender : {}", transfer.sender), + format!("Receiver : {}", transfer.receiver), + format!("Timeout height : {}", transfer.timeout_height_on_b), + format!("Timeout timestamp : {}", transfer.timeout_timestamp_on_b), + ]); + tv.output_expert.extend(vec![ + format!("Source port : {}", transfer.port_id_on_a), + format!("Source channel : {}", transfer.chan_id_on_a), + format!("Token : {}", transfer_token), + format!("Sender : {}", transfer.sender), + format!("Receiver : {}", transfer.receiver), + format!("Timeout height : {}", transfer.timeout_height_on_b), + format!("Timeout timestamp : {}", transfer.timeout_timestamp_on_b), + ]); + } + _ => { + for line in format!("{:#?}", msg).split('\n') { + let stripped = line.trim_start(); + tv.output.push(format!("Part : {}", stripped)); + tv.output_expert.push(format!("Part : {}", stripped)); + } + } + } + } else if code_hash == bond_hash { + let bond = pos::Bond::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Bond 0".to_string(); + + let bond_source = bond + .source + .as_ref() + .map(Address::to_string) + .unwrap_or_else(|| "(none)".to_string()); + tv.output.extend(vec![ + format!("Type : Bond"), + format!("Source : {}", bond_source), + format!("Validator : {}", bond.validator), + format!("Amount : {}", bond.amount), + ]); + + tv.output_expert.extend(vec![ + format!("Source : {}", bond_source), + format!("Validator : {}", bond.validator), + format!("Amount : {}", bond.amount), + ]); + } else if code_hash == unbond_hash { + let unbond = pos::Unbond::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Unbond 0".to_string(); + + let unbond_source = unbond + .source + .as_ref() + .map(Address::to_string) + .unwrap_or_else(|| "(none)".to_string()); + tv.output.extend(vec![ + format!("Code : Unbond"), + format!("Source : {}", unbond_source), + format!("Validator : {}", unbond.validator), + format!("Amount : {}", unbond.amount), + ]); + + tv.output_expert.extend(vec![ + format!("Source : {}", unbond_source), + format!("Validator : {}", unbond.validator), + format!("Amount : {}", unbond.amount), + ]); + } else if code_hash == withdraw_hash { + let withdraw = pos::Withdraw::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Withdraw 0".to_string(); + + let withdraw_source = withdraw + .source + .as_ref() + .map(Address::to_string) + .unwrap_or_else(|| "(none)".to_string()); + tv.output.extend(vec![ + format!("Type : Withdraw"), + format!("Source : {}", withdraw_source), + format!("Validator : {}", withdraw.validator), + ]); + + tv.output_expert.extend(vec![ + format!("Source : {}", withdraw_source), + format!("Validator : {}", withdraw.validator), + ]); + } else if code_hash == change_commission_hash { + let commission_change = pos::CommissionChange::try_from_slice( + &tx.data() + .ok_or_else(|| std::io::Error::from(ErrorKind::InvalidData))?, + )?; + + tv.name = "Change Commission 0".to_string(); + + tv.output.extend(vec![ + format!("Type : Change commission"), + format!("New rate : {}", commission_change.new_rate), + format!("Validator : {}", commission_change.validator), + ]); + + tv.output_expert.extend(vec![ + format!("New rate : {}", commission_change.new_rate), + format!("Validator : {}", commission_change.validator), + ]); + } + + if let Some(wrapper) = tx.header.wrapper() { + tv.output_expert.extend(vec![ + format!("Timestamp : {}", tx.header.timestamp.0), + format!("PK : {}", wrapper.pk), + format!("Epoch : {}", wrapper.epoch), + format!("Gas limit : {}", Amount::from(wrapper.gas_limit)), + format!("Fee token : {}", wrapper.fee.token), + ]); + if let Some(token) = tokens.get(&wrapper.fee.token) { + tv.output_expert + .push(format!("Fee amount : {} {}", token, wrapper.fee.amount)); + } else { + tv.output_expert + .push(format!("Fee amount : {}", wrapper.fee.amount)); + } + } + + // Finally, index each line and break those that are too long + format_outputs(&mut tv.output); + format_outputs(&mut tv.output_expert); + Ok(tv) +} diff --git a/shared/src/ledger/tx.rs b/shared/src/ledger/tx.rs index 8b2a9d85ed..d1883d7c10 100644 --- a/shared/src/ledger/tx.rs +++ b/shared/src/ledger/tx.rs @@ -13,8 +13,21 @@ use prost::EncodeError; use rust_decimal::Decimal; use thiserror::Error; use tokio::time::Duration; +use masp_primitives::asset_type::AssetType; +use std::collections::HashSet; +use masp_primitives::transaction::components::{ + Amount, OutputDescription, TxOut, +}; +use masp_primitives::transaction::builder::Builder; +use masp_primitives::transaction::components::sapling::fees::{ + ConvertView, InputView as SaplingInputView, OutputView as SaplingOutputView, +}; +use masp_primitives::transaction::components::transparent::fees::{ + InputView as TransparentInputView, OutputView as TransparentOutputView, +}; use super::rpc::query_wasm_code_hash; +use crate::proto::MaspBuilder; use crate::ibc::applications::transfer::msgs::transfer::MsgTransfer; use crate::ibc::core::ics04_channel::timeout::TimeoutHeight; use crate::ibc::signer::Signer; @@ -28,7 +41,7 @@ use crate::ledger::masp::{ShieldedContext, ShieldedUtils}; use crate::ledger::rpc::{self, TxBroadcastData, TxResponse}; use crate::ledger::signing::{find_keypair, sign_tx, tx_signer, TxSigningKey}; use crate::ledger::wallet::{Wallet, WalletUtils}; -use crate::proto::Tx; +use crate::proto::{Tx, Data, Code, Section, Signature}; use crate::tendermint_rpc::endpoint::broadcast::tx_sync::Response; use crate::tendermint_rpc::error::Error as RpcError; use crate::types::key::*; @@ -39,6 +52,25 @@ use crate::types::transaction::{pos, InitAccount, UpdateVp}; use crate::types::{storage, token}; use crate::vm; use crate::vm::WasmValidationError; +use crate::types::transaction::decrypted::DecryptedTx; +use crate::types::transaction::TxType; +use crate::types::hash::Hash; +use sha2::{Digest as Sha2Digest, Sha256}; + +pub const TX_INIT_ACCOUNT_WASM: &str = "tx_init_account.wasm"; +pub const TX_INIT_VALIDATOR_WASM: &str = "tx_init_validator.wasm"; +pub const TX_INIT_PROPOSAL: &str = "tx_init_proposal.wasm"; +pub const TX_VOTE_PROPOSAL: &str = "tx_vote_proposal.wasm"; +pub const TX_REVEAL_PK: &str = "tx_reveal_pk.wasm"; +pub const TX_UPDATE_VP_WASM: &str = "tx_update_vp.wasm"; +pub const TX_TRANSFER_WASM: &str = "tx_transfer.wasm"; +pub const TX_IBC_WASM: &str = "tx_ibc.wasm"; +pub const VP_USER_WASM: &str = "vp_user.wasm"; +pub const TX_BOND_WASM: &str = "tx_bond.wasm"; +pub const TX_UNBOND_WASM: &str = "tx_unbond.wasm"; +pub const TX_WITHDRAW_WASM: &str = "tx_withdraw.wasm"; +pub const TX_CHANGE_COMMISSION_WASM: &str = + "tx_change_validator_commission.wasm"; /// Default timeout in seconds for requests to the `/accepted` /// and `/applied` ABCI query endpoints. @@ -128,7 +160,7 @@ pub enum Error { ), /// No Balance found for token #[error("{0}")] - MaspError(builder::Error), + MaspError(builder::Error), /// Wasm validation failed #[error("Validity predicate code validation failed with {0}")] WasmValidationFailure(WasmValidationError), @@ -166,6 +198,26 @@ pub enum Error { Other(String), } +/// Capture the result of running a transaction +pub enum ProcessTxResponse { + /// Result of submitting a transaction to the blockchain + Applied(TxResponse), + /// Result of submitting a transaction to the mempool + Broadcast(Response), + /// Result of dry running transaction + DryRun, +} + +impl ProcessTxResponse { + /// Get the the accounts that were reported to be initialized + pub fn initialized_accounts(&self) -> Vec
{ + match self { + Self::Applied(result) => result.initialized_accounts.clone(), + _ => vec![], + } + } +} + /// Submit transaction and wait for result. Returns a list of addresses /// initialized in the transaction if any. In dry run, this is always empty. pub async fn process_tx< @@ -178,7 +230,7 @@ pub async fn process_tx< tx: Tx, default_signer: TxSigningKey, #[cfg(not(feature = "mainnet"))] requires_pow: bool, -) -> Result, Error> { +) -> Result { let to_broadcast = sign_tx::( client, wallet, @@ -200,7 +252,7 @@ pub async fn process_tx< // println!("HTTP request body: {}", request_body); if args.dry_run { - expect_dry_broadcast(to_broadcast, client, vec![]).await + expect_dry_broadcast(to_broadcast, client).await } else { // Either broadcast or submit transaction and collect result into // sum type @@ -212,8 +264,8 @@ pub async fn process_tx< // Return result based on executed operation, otherwise deal with // the encountered errors uniformly match result { - Right(Ok(result)) => Ok(result.initialized_accounts), - Left(Ok(_)) => Ok(Vec::default()), + Right(Ok(result)) => Ok(ProcessTxResponse::Applied(result)), + Left(Ok(result)) => Ok(ProcessTxResponse::Broadcast(result)), Right(Err(err)) => Err(err), Left(Err(err)) => Err(err), } @@ -281,34 +333,51 @@ pub async fn submit_reveal_pk_aux< wallet: &mut Wallet, public_key: &common::PublicKey, args: &args::Tx, -) -> Result<(), Error> { +) -> Result { let addr: Address = public_key.into(); println!("Submitting a tx to reveal the public key for address {addr}..."); - let tx_data = public_key.try_to_vec().map_err(Error::EncodeKeyFailure)?; - let tx_code = args.tx_code_path.clone(); - let tx = Tx::new( - tx_code, - Some(tx_data), - args.chain_id.clone().expect("value should be there"), - args.expiration, - ); + let tx_data = public_key + .try_to_vec() + .expect("Encoding a public key shouldn't fail"); + let tx_code_hash = + query_wasm_code_hash(client, TX_REVEAL_PK) + .await + .unwrap(); + let mut tx = Tx::new(TxType::Decrypted(DecryptedTx::Decrypted { + #[cfg(not(feature = "mainnet"))] + // To be able to dry-run testnet faucet withdrawal, pretend + // that we got a valid PoW + has_valid_pow: true, + })); + tx.header.chain_id = args.chain_id.clone().expect("value should be there"); + tx.header.expiration = args.expiration; + tx.set_data(Data::new(tx_data)); + tx.set_code(Code::from_hash(tx_code_hash)); // submit_tx without signing the inner tx let keypair = if let Some(signing_key) = &args.signing_key { Ok(signing_key.clone()) } else if let Some(signer) = args.signer.as_ref() { - let signer = signer; - find_keypair::(client, wallet, signer, args.password.clone()) + find_keypair(client, wallet, &signer, args.password.clone()) .await } else { - find_keypair::(client, wallet, &addr, args.password.clone()).await + find_keypair(client, wallet, &addr, args.password.clone()).await }?; + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair, + ))); let epoch = rpc::query_epoch(client).await; let to_broadcast = if args.dry_run { TxBroadcastData::DryRun(tx) } else { super::signing::sign_wrapper( client, + wallet, args, epoch, tx, @@ -319,9 +388,8 @@ pub async fn submit_reveal_pk_aux< .await }; - // Logic is the same as process_tx if args.dry_run { - expect_dry_broadcast(to_broadcast, client, ()).await + expect_dry_broadcast(to_broadcast, client).await } else { // Either broadcast or submit transaction and collect result into // sum type @@ -335,7 +403,8 @@ pub async fn submit_reveal_pk_aux< match result { Right(Err(err)) => Err(err), Left(Err(err)) => Err(err), - _ => Ok(()), + Right(Ok(response)) => Ok(ProcessTxResponse::Applied(response)), + Left(Ok(response)) => Ok(ProcessTxResponse::Broadcast(response)), } } } @@ -587,10 +656,12 @@ pub async fn submit_validator_commission_change< }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::new(tx_code)); + let default_signer = args.validator.clone(); process_tx::( client, @@ -650,10 +721,12 @@ pub async fn submit_withdraw< let data = pos::Withdraw { validator, source }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::new(tx_code)); + let default_signer = args.source.unwrap_or(args.validator); process_tx::( client, @@ -677,36 +750,37 @@ pub async fn submit_unbond< wallet: &mut Wallet, args: args::Unbond, ) -> Result<(), Error> { - let validator = - known_validator_or_err(args.validator.clone(), args.tx.force, client) - .await?; let source = args.source.clone(); - let tx_code = args.tx_code_path; - // Check the source's current bond amount - let bond_source = source.clone().unwrap_or_else(|| validator.clone()); - let bond_amount = - rpc::query_bond(client, &bond_source, &validator, None).await; - println!("Bond amount available for unbonding: {} NAM", bond_amount); + let bond_source = source.clone().unwrap_or_else(|| args.validator.clone()); + let tx_code = args.tx_code_path; + if !args.tx.force { + known_validator_or_err(args.validator.clone(), args.tx.force, client) + .await?; + + let bond_amount = + rpc::query_bond(client, &bond_source, &args.validator, None).await; + println!("Bond amount available for unbonding: {} NAM", bond_amount); - if args.amount > bond_amount { - eprintln!( - "The total bonds of the source {} is lower than the amount to be \ - unbonded. Amount to unbond is {} and the total bonds is {}.", - bond_source, args.amount, bond_amount - ); - if !args.tx.force { - return Err(Error::LowerBondThanUnbond( - bond_source, - args.amount, - bond_amount, - )); + if args.amount > bond_amount { + eprintln!( + "The total bonds of the source {} is lower than the amount to be \ + unbonded. Amount to unbond is {} and the total bonds is {}.", + bond_source, args.amount, bond_amount + ); + if !args.tx.force { + return Err(Error::LowerBondThanUnbond( + bond_source, + args.amount, + bond_amount, + )); + } } } // Query the unbonds before submitting the tx let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &validator).await; + rpc::query_unbond_with_slashing(client, &bond_source, &args.validator).await; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -715,17 +789,19 @@ pub async fn submit_unbond< let latest_withdrawal_pre = withdrawable.into_iter().last(); let data = pos::Unbond { - validator: validator.clone(), + validator: args.validator.clone(), amount: args.amount, source, }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::new(tx_code)); - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); - let default_signer = args.source.unwrap_or(args.validator); + let default_signer = args.source.unwrap_or(args.validator.clone()); process_tx::( client, wallet, @@ -739,7 +815,7 @@ pub async fn submit_unbond< // Query the unbonds post-tx let unbonds = - rpc::query_unbond_with_slashing(client, &bond_source, &validator).await; + rpc::query_unbond_with_slashing(client, &bond_source, &args.validator).await; let mut withdrawable = BTreeMap::::new(); for ((_start_epoch, withdraw_epoch), amount) in unbonds.into_iter() { let to_withdraw = withdrawable.entry(withdraw_epoch).or_default(); @@ -831,10 +907,12 @@ pub async fn submit_bond< }; let data = bond.try_to_vec().map_err(Error::EncodeTxFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::new(tx_code)); - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); let default_signer = args.source.unwrap_or(args.validator); process_tx::( client, @@ -965,10 +1043,12 @@ pub async fn submit_ibc_transfer< prost::Message::encode(&any_msg, &mut data) .map_err(Error::EncodeFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + tx.set_data(Data::new(data)); + tx.set_code(Code::new(tx_code)); - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); process_tx::( client, wallet, @@ -982,6 +1062,79 @@ pub async fn submit_ibc_transfer< Ok(()) } +/// Try to decode the given asset type and add its decoding to the supplied set. +/// Returns true only if a new decoding has been added to the given set. +async fn add_asset_type< + C: crate::ledger::queries::Client + Sync, + U: ShieldedUtils, + >( + asset_types: &mut HashSet<(Address, Epoch)>, + shielded: &mut ShieldedContext, + client: &C, + asset_type: AssetType, +) -> bool { + if let Some(asset_type) = shielded + .decode_asset_type(client.clone(), asset_type) + .await + { + asset_types.insert(asset_type) + } else { + false + } +} + +/// Collect the asset types used in the given Builder and decode them. This +/// function provides the data necessary for offline wallets to present asset +/// type information. +async fn used_asset_types< + C: crate::ledger::queries::Client + Sync, + U: ShieldedUtils, + P, + R, + K, + N, + >( + shielded: &mut ShieldedContext, + client: &C, + builder: &Builder, +) -> Result, RpcError> { + let mut asset_types = HashSet::new(); + // Collect all the asset types used in the Sapling inputs + for input in builder.sapling_inputs() { + add_asset_type(&mut asset_types, shielded, &client, input.asset_type()) + .await; + } + // Collect all the asset types used in the transparent inputs + for input in builder.transparent_inputs() { + add_asset_type( + &mut asset_types, + shielded, + &client, + input.coin().asset_type(), + ) + .await; + } + // Collect all the asset types used in the Sapling outputs + for output in builder.sapling_outputs() { + add_asset_type(&mut asset_types, shielded, &client, output.asset_type()) + .await; + } + // Collect all the asset types used in the transparent outputs + for output in builder.transparent_outputs() { + add_asset_type(&mut asset_types, shielded, &client, output.asset_type()) + .await; + } + // Collect all the asset types used in the Sapling converts + for output in builder.sapling_converts() { + for (asset_type, _) in + Amount::from(output.conversion().clone()).components() + { + add_asset_type(&mut asset_types, shielded, &client, *asset_type).await; + } + } + Ok(asset_types) +} + /// Submit an ordinary transfer pub async fn submit_transfer< C: crate::ledger::queries::Client + Sync, @@ -993,52 +1146,37 @@ pub async fn submit_transfer< shielded: &mut ShieldedContext, args: args::TxTransfer, ) -> Result<(), Error> { + let source = args.source.effective_address(); + let target = args.target.effective_address(); + let token = args.token.clone(); + // Check that the source address exists on chain - let force = args.tx.force; - let transfer_source = args.source.clone(); - let source = source_exists_or_err( - transfer_source.effective_address(), - force, - client, - ) - .await?; + source_exists_or_err(source.clone(), args.tx.force, client).await?; // Check that the target address exists on chain - let transfer_target = args.target.clone(); - let target = target_exists_or_err( - transfer_target.effective_address(), - force, - client, - ) - .await?; - + target_exists_or_err(target.clone(), args.tx.force, client).await?; // Check that the token address exists on chain - let token = - &(token_exists_or_err(args.token.clone(), force, client).await?); - + token_exists_or_err(token.clone(), args.tx.force, client).await?; // Check source balance let (sub_prefix, balance_key) = match &args.sub_prefix { Some(sub_prefix) => { let sub_prefix = storage::Key::parse(sub_prefix).unwrap(); - let prefix = token::multitoken_balance_prefix(token, &sub_prefix); + let prefix = token::multitoken_balance_prefix(&token, &sub_prefix); ( Some(sub_prefix), token::multitoken_balance_key(&prefix, &source), ) } - None => (None, token::balance_key(token, &source)), + None => (None, token::balance_key(&token, &source)), }; - - check_balance_too_low_err( - token, + check_balance_too_low_err::( + &token, &source, args.amount, balance_key, args.tx.force, client, - ) - .await?; + ).await?; - let tx_code = args.tx_code_path.clone(); let masp_addr = masp(); // For MASP sources, use a special sentinel key recognized by VPs as default // signer. Also, if the transaction is shielded, redact the amount and token @@ -1060,17 +1198,16 @@ pub async fn submit_transfer< ) } else { ( - TxSigningKey::WalletAddress(source.clone()), + TxSigningKey::WalletAddress(args.source.effective_address()), args.amount, - token.clone(), + token, ) }; // If our chosen signer is the MASP sentinel key, then our shielded inputs // will need to cover the gas fees. - let chosen_signer = - tx_signer::(client, wallet, &args.tx, default_signer.clone()) - .await? - .ref_to(); + let chosen_signer = tx_signer::(client, wallet, &args.tx, default_signer.clone()) + .await? + .ref_to(); let shielded_gas = masp_tx_key().ref_to() == chosen_signer; // Determine whether to pin this transaction to a storage key let key = match &args.target { @@ -1078,53 +1215,125 @@ pub async fn submit_transfer< _ => None, }; - let stx_result = shielded - .gen_shielded_transfer(client, args.clone(), shielded_gas) - .await; - let shielded = match stx_result { - Ok(stx) => Ok(stx.map(|x| x.0)), - Err(builder::Error::ChangeIsNegative(_)) => { - Err(Error::NegativeBalanceAfterTransfer( - source.clone(), - args.amount, - token.clone(), - args.tx.fee_amount, - args.tx.fee_token.clone(), - )) - } - Err(err) => Err(Error::MaspError(err)), - }?; + #[cfg(not(feature = "mainnet"))] + let is_source_faucet = rpc::is_faucet_account(client, &source).await; - let transfer = token::Transfer { - source: source.clone(), - target, - token, - sub_prefix, - amount, - key, - shielded, - }; - tracing::debug!("Transfer data {:?}", transfer); - let data = transfer.try_to_vec().map_err(Error::EncodeTxFailure)?; + let tx_code_hash = + query_wasm_code_hash(client, TX_TRANSFER_WASM) + .await + .unwrap(); - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; + // Loop twice in case the first submission attempt fails + for _ in 0..2 { + // Construct the shielded part of the transaction, if any + let stx_result = shielded + .gen_shielded_transfer(client, args.clone(), shielded_gas) + .await; - #[cfg(not(feature = "mainnet"))] - let is_source_faucet = rpc::is_faucet_account(client, &source).await; + let shielded_parts = match stx_result { + Ok(stx) => Ok(stx), + Err(builder::Error::InsufficientFunds(_)) => { + Err(Error::NegativeBalanceAfterTransfer( + source.clone(), + args.amount, + token.clone(), + args.tx.fee_amount, + args.tx.fee_token.clone(), + )) + } + Err(err) => Err(Error::MaspError(err)), + }?; - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); - let signing_address = TxSigningKey::WalletAddress(source); - process_tx::( - client, - wallet, - &args.tx, - tx, - signing_address, - #[cfg(not(feature = "mainnet"))] - is_source_faucet, - ) - .await?; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration.clone(); + // Add the MASP Transaction and its Builder to facilitate validation + let (masp_hash, shielded_tx_epoch) = if let Some(shielded_parts) = shielded_parts { + // Add a MASP Transaction section to the Tx + let masp_tx = tx.add_section(Section::MaspTx(shielded_parts.1)); + // Get the hash of the MASP Transaction section + let masp_hash = + Hash(masp_tx.hash(&mut Sha256::new()).finalize_reset().into()); + // Get the decoded asset types used in the transaction to give + // offline wallet users more information + let asset_types = used_asset_types( + shielded, + &client, + &shielded_parts.0, + ) + .await + .unwrap_or_default(); + // Add the MASP Transaction's Builder to the Tx + tx.add_section(Section::MaspBuilder(MaspBuilder { + asset_types, + // Store how the Info objects map to Descriptors/Outputs + metadata: shielded_parts.2, + // Store the data that was used to construct the Transaction + builder: shielded_parts.0, + // Link the Builder to the Transaction by hash code + target: masp_hash, + })); + // The MASP Transaction section hash will be used in Transfer + (Some(masp_hash), Some(shielded_parts.3)) + } else { + (None, None) + }; + // Construct the corresponding transparent Transfer object + let transfer = token::Transfer { + source: source.clone(), + target: target.clone(), + token: token.clone(), + sub_prefix: sub_prefix.clone(), + amount, + key: key.clone(), + // Link the Transfer to the MASP Transaction by hash code + shielded: masp_hash, + }; + tracing::debug!("Transfer data {:?}", transfer); + // Encode the Transfer and store it beside the MASP transaction + let data = transfer + .try_to_vec() + .expect("Encoding tx data shouldn't fail"); + tx.set_data(Data::new(data)); + // Finally store the Traansfer WASM code in the Tx + tx.set_code(Code::from_hash(tx_code_hash)); + + // Dry-run/broadcast/submit the transaction + let result = process_tx::( + client, + wallet, + &args.tx, + tx, + default_signer.clone(), + #[cfg(not(feature = "mainnet"))] + is_source_faucet, + ) + .await?; + + // Query the epoch in which the transaction was probably submitted + let submission_epoch = rpc::query_epoch(client).await; + + match result { + ProcessTxResponse::Applied(resp) if + // If a transaction is shielded + shielded_tx_epoch.is_some() && + // And it is rejected by a VP + resp.code == 1.to_string() && + // And the its submission epoch doesn't match construction epoch + shielded_tx_epoch.unwrap() != submission_epoch => + { + // Then we probably straddled an epoch boundary. Let's retry... + eprintln!( + "MASP transaction rejected and this may be due to the \ + epoch changing. Attempting to resubmit transaction.", + ); + continue; + }, + // Otherwise either the transaction was successful or it will not + // benefit from resubmission + _ => break, + } + } Ok(()) } @@ -1144,16 +1353,22 @@ pub async fn submit_init_account< let vp_code_path = String::from_utf8(args.vp_code_path).unwrap(); let vp_code_hash = query_wasm_code_hash(client, vp_code_path).await.unwrap(); - - let tx_code = args.tx_code_path; + + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + let extra = + tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); + let extra_hash = + Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); let data = InitAccount { public_key, - vp_code_hash, + vp_code_hash: extra_hash, }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - let tx = Tx::new(tx_code, Some(data), chain_id, expiration); + tx.set_data(Data::new(data)); + tx.set_code(Code::new(args.tx_code_path)); + // TODO Move unwrap to an either let initialized_accounts = process_tx::( client, @@ -1165,7 +1380,8 @@ pub async fn submit_init_account< false, ) .await - .unwrap(); + .unwrap() + .initialized_accounts(); save_initialized_accounts::(wallet, &args.tx, initialized_accounts) .await; Ok(()) @@ -1230,13 +1446,21 @@ pub async fn submit_update_vp< let tx_code_hash = query_wasm_code_hash(client, tx_code_path).await.unwrap(); - let data = UpdateVp { addr, vp_code_hash }; + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + let extra = + tx.add_section(Section::ExtraData(Code::from_hash(vp_code_hash))); + let extra_hash = + Hash(extra.hash(&mut Sha256::new()).finalize_reset().into()); + let data = UpdateVp { + addr, + vp_code_hash: extra_hash, + }; let data = data.try_to_vec().map_err(Error::EncodeTxFailure)?; + tx.set_data(Data::new(data)); + tx.set_code(Code::from_hash(tx_code_hash)); - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - - let tx = Tx::new(tx_code_hash.to_vec(), Some(data), chain_id, expiration); process_tx::( client, wallet, @@ -1259,11 +1483,12 @@ pub async fn submit_custom< wallet: &mut Wallet, args: args::TxCustom, ) -> Result<(), Error> { - let tx_code = args.code_path; - let data = args.data_path; - let chain_id = args.tx.chain_id.clone().unwrap(); - let expiration = args.tx.expiration; - let tx = Tx::new(tx_code, data, chain_id, expiration); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = args.tx.chain_id.clone().unwrap(); + tx.header.expiration = args.tx.expiration; + args.data_path.map(|data| tx.set_data(Data::new(data))); + tx.set_code(Code::new(args.code_path)); + let initialized_accounts = process_tx::( client, wallet, @@ -1273,21 +1498,21 @@ pub async fn submit_custom< #[cfg(not(feature = "mainnet"))] false, ) - .await?; + .await? + .initialized_accounts(); save_initialized_accounts::(wallet, &args.tx, initialized_accounts) .await; Ok(()) } -async fn expect_dry_broadcast( +async fn expect_dry_broadcast( to_broadcast: TxBroadcastData, client: &C, - ret: T, -) -> Result { +) -> Result { match to_broadcast { TxBroadcastData::DryRun(tx) => { rpc::dry_run_tx(client, tx.to_bytes()).await; - Ok(ret) + Ok(ProcessTxResponse::DryRun) } TxBroadcastData::Wrapper { tx, diff --git a/shared/src/ledger/vp_host_fns.rs b/shared/src/ledger/vp_host_fns.rs index de1d7b8c75..5d89e4006d 100644 --- a/shared/src/ledger/vp_host_fns.rs +++ b/shared/src/ledger/vp_host_fns.rs @@ -3,7 +3,7 @@ use std::num::TryFromIntError; use namada_core::types::address::Address; -use namada_core::types::hash::{Hash, HASH_LENGTH}; +use namada_core::types::hash::Hash; use namada_core::types::storage::{ BlockHash, BlockHeight, Epoch, Header, Key, TxIndex, }; @@ -14,7 +14,7 @@ use crate::ledger::gas; use crate::ledger::gas::VpGasMeter; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, write_log, Storage, StorageHasher}; -use crate::proto::Tx; +use crate::proto::{Section, Tx}; /// These runtime errors will abort VP execution immediately #[allow(missing_docs)] @@ -286,13 +286,11 @@ where pub fn get_tx_code_hash( gas_meter: &mut VpGasMeter, tx: &Tx, -) -> EnvResult { - let hash = if tx.code_or_hash.len() == HASH_LENGTH { - Hash::try_from(&tx.code_or_hash[..]) - .map_err(|_| RuntimeError::InvalidCodeHash)? - } else { - Hash(tx.code_hash()) - }; +) -> EnvResult> { + let hash = tx + .get_section(tx.code_sechash()) + .and_then(Section::code_sec) + .map(|x| x.code.hash()); add_gas(gas_meter, MIN_STORAGE_GAS)?; Ok(hash) } diff --git a/shared/src/vm/host_env.rs b/shared/src/vm/host_env.rs index 238ba58520..f900e3fcc6 100644 --- a/shared/src/vm/host_env.rs +++ b/shared/src/vm/host_env.rs @@ -22,13 +22,13 @@ use crate::types::address::{self, Address}; use crate::types::hash::Hash; use crate::types::ibc::IbcEvent; use crate::types::internal::HostEnvResult; +use crate::types::storage::{Key, TxIndex}; use crate::types::key::*; -use crate::types::storage::{BlockHeight, Key, TxIndex}; +use crate::types::storage::BlockHeight; use crate::vm::memory::VmMemory; use crate::vm::prefix_iter::{PrefixIteratorId, PrefixIterators}; use crate::vm::{HostRef, MutHostRef}; -const VERIFY_TX_SIG_GAS_COST: u64 = 1000; const WASM_VALIDATION_GAS_PER_BYTE: u64 = 1; /// These runtime errors will abort tx WASM execution immediately @@ -92,6 +92,8 @@ where pub iterators: MutHostRef<'a, &'a PrefixIterators<'a, DB>>, /// Transaction gas meter. pub gas_meter: MutHostRef<'a, &'a BlockGasMeter>, + /// The transaction code is used for signature verification + pub tx: HostRef<'a, &'a Tx>, /// The transaction index is used to identify a shielded transaction's /// parent pub tx_index: HostRef<'a, &'a TxIndex>, @@ -132,6 +134,7 @@ where write_log: &mut WriteLog, iterators: &mut PrefixIterators<'a, DB>, gas_meter: &mut BlockGasMeter, + tx: &Tx, tx_index: &TxIndex, verifiers: &mut BTreeSet
, result_buffer: &mut Option>, @@ -142,6 +145,7 @@ where let write_log = unsafe { MutHostRef::new(write_log) }; let iterators = unsafe { MutHostRef::new(iterators) }; let gas_meter = unsafe { MutHostRef::new(gas_meter) }; + let tx = unsafe { HostRef::new(tx) }; let tx_index = unsafe { HostRef::new(tx_index) }; let verifiers = unsafe { MutHostRef::new(verifiers) }; let result_buffer = unsafe { MutHostRef::new(result_buffer) }; @@ -154,6 +158,7 @@ where write_log, iterators, gas_meter, + tx, tx_index, verifiers, result_buffer, @@ -196,6 +201,7 @@ where write_log: self.write_log.clone(), iterators: self.iterators.clone(), gas_meter: self.gas_meter.clone(), + tx: self.tx.clone(), tx_index: self.tx_index.clone(), verifiers: self.verifiers.clone(), result_buffer: self.result_buffer.clone(), @@ -287,8 +293,8 @@ pub trait VpEvaluator { fn eval( &self, ctx: VpCtx<'static, Self::Db, Self::H, Self::Eval, Self::CA>, - vp_code: Vec, - input_data: Vec, + vp_code_hash: Hash, + input_data: Tx, ) -> HostEnvResult; } @@ -1481,9 +1487,9 @@ where Ok(height.0) } -/// Getting the block height function exposed to the wasm VM Tx -/// environment. The height is that of the block to which the current -/// transaction is being applied. +/// Getting the transaction index function exposed to the wasm VM Tx +/// environment. The index is that of the transaction being applied +/// in the current block. pub fn tx_get_tx_index( env: &TxVmEnv, ) -> TxResult @@ -1725,9 +1731,16 @@ where let gas_meter = unsafe { env.ctx.gas_meter.get() }; let tx = unsafe { env.ctx.tx.get() }; let hash = vp_host_fns::get_tx_code_hash(gas_meter, tx)?; + let mut result_bytes = vec![]; + if let Some(hash) = hash { + result_bytes.push(1); + result_bytes.extend(hash.0); + } else { + result_bytes.push(0); + }; let gas = env .memory - .write_bytes(result_ptr, hash.0) + .write_bytes(result_ptr, result_bytes) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; vp_host_fns::add_gas(gas_meter, gas) } @@ -1751,43 +1764,6 @@ where Ok(epoch.0) } -/// Verify a transaction signature. -pub fn vp_verify_tx_signature( - env: &VpVmEnv, - pk_ptr: u64, - pk_len: u64, - sig_ptr: u64, - sig_len: u64, -) -> vp_host_fns::EnvResult -where - MEM: VmMemory, - DB: storage::DB + for<'iter> storage::DBIter<'iter>, - H: StorageHasher, - EVAL: VpEvaluator, - CA: WasmCacheAccess, -{ - let (pk, gas) = env - .memory - .read_bytes(pk_ptr, pk_len as _) - .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - let gas_meter = unsafe { env.ctx.gas_meter.get() }; - vp_host_fns::add_gas(gas_meter, gas)?; - let pk: common::PublicKey = BorshDeserialize::try_from_slice(&pk) - .map_err(vp_host_fns::RuntimeError::EncodingError)?; - - let (sig, gas) = env - .memory - .read_bytes(sig_ptr, sig_len as _) - .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; - vp_host_fns::add_gas(gas_meter, gas)?; - let sig: common::Signature = BorshDeserialize::try_from_slice(&sig) - .map_err(vp_host_fns::RuntimeError::EncodingError)?; - - vp_host_fns::add_gas(gas_meter, VERIFY_TX_SIG_GAS_COST)?; - let tx = unsafe { env.ctx.tx.get() }; - Ok(HostEnvResult::from(tx.verify_sig(&pk, &sig).is_ok()).to_i64()) -} - /// Verify a ShieldedTransaction. pub fn vp_verify_masp( env: &VpVmEnv, @@ -1801,7 +1777,7 @@ where EVAL: VpEvaluator, CA: WasmCacheAccess, { - use crate::types::token::Transfer; + use masp_primitives::transaction::Transaction; let gas_meter = unsafe { env.ctx.gas_meter.get() }; let (tx_bytes, gas) = env @@ -1810,17 +1786,14 @@ where .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; vp_host_fns::add_gas(gas_meter, gas)?; - let full_tx: Transfer = + let shielded: Transaction = BorshDeserialize::try_from_slice(tx_bytes.as_slice()) .map_err(vp_host_fns::RuntimeError::EncodingError)?; - match full_tx.shielded { - Some(shielded_tx) => Ok(HostEnvResult::from( - crate::ledger::masp::verify_shielded_tx(&shielded_tx), - ) - .to_i64()), - None => Ok(HostEnvResult::Fail.to_i64()), - } + Ok( + HostEnvResult::from(crate::ledger::masp::verify_shielded_tx(&shielded)) + .to_i64(), + ) } /// Log a string from exposed to the wasm VM Tx environment. The message will be @@ -1893,10 +1866,10 @@ where EVAL: VpEvaluator, CA: WasmCacheAccess, { - let (vp_code, gas) = - env.memory - .read_bytes(vp_code_ptr, vp_code_len as _) - .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; + let (vp_code_hash, gas) = env + .memory + .read_bytes(vp_code_ptr, vp_code_len as _) + .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; let gas_meter = unsafe { env.ctx.gas_meter.get() }; vp_host_fns::add_gas(gas_meter, gas)?; @@ -1905,10 +1878,18 @@ where .read_bytes(input_data_ptr, input_data_len as _) .map_err(|e| vp_host_fns::RuntimeError::MemoryError(Box::new(e)))?; vp_host_fns::add_gas(gas_meter, gas)?; + let input_data: Tx = BorshDeserialize::try_from_slice(&input_data) + .map_err(vp_host_fns::RuntimeError::EncodingError)?; + let vp_code_hash = Hash(vp_code_hash.try_into().map_err(|e| { + vp_host_fns::RuntimeError::EncodingError(std::io::Error::new( + std::io::ErrorKind::InvalidData, + format!("Not a valid hash: {:?}", e), + )) + })?); let eval_runner = unsafe { env.ctx.eval_runner.get() }; Ok(eval_runner - .eval(env.ctx.clone(), vp_code, input_data) + .eval(env.ctx.clone(), vp_code_hash, input_data) .to_i64()) } @@ -2002,6 +1983,7 @@ pub mod testing { iterators: &mut PrefixIterators<'static, DB>, verifiers: &mut BTreeSet
, gas_meter: &mut BlockGasMeter, + tx: &Tx, tx_index: &TxIndex, result_buffer: &mut Option>, #[cfg(feature = "wasm-runtime")] vp_wasm_cache: &mut VpCache, @@ -2018,6 +2000,7 @@ pub mod testing { write_log, iterators, gas_meter, + tx, tx_index, verifiers, result_buffer, diff --git a/shared/src/vm/types.rs b/shared/src/vm/types.rs index 16e525f4be..893496f1e6 100644 --- a/shared/src/vm/types.rs +++ b/shared/src/vm/types.rs @@ -11,6 +11,7 @@ use std::collections::BTreeSet; +use crate::proto::Tx; use crate::types::address::Address; use crate::types::storage; @@ -19,7 +20,7 @@ pub struct VpInput<'a> { /// The address of the validity predicate's owning account pub addr: &'a Address, /// The input data as arbitrary bytes - pub data: &'a [u8], + pub data: &'a Tx, /// The storage changed keys from the write log of storage updates /// performed by the transaction for the account associated with the VP pub keys_changed: &'a BTreeSet, diff --git a/shared/src/vm/wasm/compilation_cache/common.rs b/shared/src/vm/wasm/compilation_cache/common.rs index 6402918031..5d38a7a993 100644 --- a/shared/src/vm/wasm/compilation_cache/common.rs +++ b/shared/src/vm/wasm/compilation_cache/common.rs @@ -164,8 +164,7 @@ impl Cache { hash.to_string() ); // Put into cache, ignore result if it's full - let _ = - in_memory.put_with_weight(hash.clone(), module.clone()); + let _ = in_memory.put_with_weight(*hash, module.clone()); return Ok(Some((module, store))); } @@ -196,13 +195,12 @@ impl Cache { // Update progress let mut progress = self.progress.write().unwrap(); - progress.insert(hash.clone(), Compilation::Done); + progress.insert(*hash, Compilation::Done); // Put into cache, ignore the result (fails if the module // cannot fit into the cache) let mut in_memory = self.in_memory.write().unwrap(); - let _ = - in_memory.put_with_weight(hash.clone(), module.clone()); + let _ = in_memory.put_with_weight(*hash, module.clone()); return Ok(Some((module, store))); } @@ -304,7 +302,7 @@ impl Cache { drop(progress); return self.fetch(&hash); } - progress.insert(hash.clone(), Compilation::Compiling); + progress.insert(hash, Compilation::Compiling); drop(progress); tracing::info!("Compiling {} {}.", N::name(), hash.to_string()); @@ -317,7 +315,7 @@ impl Cache { // Update progress let mut progress = self.progress.write().unwrap(); - progress.insert(hash.clone(), Compilation::Done); + progress.insert(hash, Compilation::Done); // Put into cache, ignore result if it's full let mut in_memory = self.in_memory.write().unwrap(); @@ -364,7 +362,7 @@ impl Cache { progress.insert(hash, Compilation::Done); return; } - progress.insert(hash.clone(), Compilation::Compiling); + progress.insert(hash, Compilation::Compiling); drop(progress); let progress = self.progress.clone(); let code = code.as_ref().to_vec(); @@ -378,10 +376,8 @@ impl Cache { Ok((module, store)) => { let mut progress = progress.write().unwrap(); - progress.insert( - hash.clone(), - Compilation::Done, - ); + progress + .insert(hash, Compilation::Done); file_write_module(&dir, &module, &hash); (module, store) } diff --git a/shared/src/vm/wasm/host_env.rs b/shared/src/vm/wasm/host_env.rs index bf438ddfcc..3212649624 100644 --- a/shared/src/vm/wasm/host_env.rs +++ b/shared/src/vm/wasm/host_env.rs @@ -118,7 +118,6 @@ where "namada_vp_get_block_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_hash), "namada_vp_get_tx_code_hash" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_tx_code_hash), "namada_vp_get_block_epoch" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_block_epoch), - "namada_vp_verify_tx_signature" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_verify_tx_signature), "namada_vp_verify_masp" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_verify_masp), "namada_vp_eval" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_eval), "namada_vp_get_native_token" => Function::new_native_with_env(wasm_store, env.clone(), host_env::vp_get_native_token), diff --git a/shared/src/vm/wasm/memory.rs b/shared/src/vm/wasm/memory.rs index 31f48fdbcd..7e79ab1be0 100644 --- a/shared/src/vm/wasm/memory.rs +++ b/shared/src/vm/wasm/memory.rs @@ -15,6 +15,7 @@ use wasmer_vm::{ MemoryStyle, TableStyle, VMMemoryDefinition, VMTableDefinition, }; +use crate::proto::Tx; use crate::vm::memory::VmMemory; use crate::vm::types::VpInput; @@ -81,10 +82,11 @@ pub struct TxCallInput { /// Write transaction inputs into wasm memory pub fn write_tx_inputs( memory: &wasmer::Memory, - tx_data_bytes: impl AsRef<[u8]>, + tx_data: &Tx, ) -> Result { let tx_data_ptr = 0; - let tx_data_len = tx_data_bytes.as_ref().len() as _; + let tx_data_bytes = tx_data.try_to_vec().map_err(Error::EncodingError)?; + let tx_data_len = tx_data_bytes.len() as _; write_memory_bytes(memory, tx_data_ptr, tx_data_bytes)?; @@ -129,8 +131,9 @@ pub fn write_vp_inputs( let addr_bytes = addr.try_to_vec().map_err(Error::EncodingError)?; let addr_len = addr_bytes.len() as _; + let data_bytes = data.try_to_vec().map_err(Error::EncodingError)?; let data_ptr = addr_ptr + addr_len; - let data_len = data.len() as _; + let data_len = data_bytes.len() as _; let keys_changed_bytes = keys_changed.try_to_vec().map_err(Error::EncodingError)?; @@ -144,7 +147,7 @@ pub fn write_vp_inputs( let bytes = [ &addr_bytes[..], - data, + &data_bytes[..], &keys_changed_bytes[..], &verifiers_bytes[..], ] diff --git a/shared/src/vm/wasm/run.rs b/shared/src/vm/wasm/run.rs index 35e7875af2..ae20b9e6d2 100644 --- a/shared/src/vm/wasm/run.rs +++ b/shared/src/vm/wasm/run.rs @@ -13,9 +13,9 @@ use super::TxCache; use crate::ledger::gas::{BlockGasMeter, VpGasMeter}; use crate::ledger::storage::write_log::WriteLog; use crate::ledger::storage::{self, Storage, StorageHasher}; -use crate::proto::Tx; +use crate::proto::{Commitment, Section, Tx}; use crate::types::address::Address; -use crate::types::hash::{Error as TxHashError, Hash, HASH_LENGTH}; +use crate::types::hash::{Error as TxHashError, Hash}; use crate::types::internal::HostEnvResult; use crate::types::storage::{Key, TxIndex}; use crate::vm::host_env::{TxVmEnv, VpCtx, VpEvaluator, VpVmEnv}; @@ -34,6 +34,8 @@ const WASM_STACK_LIMIT: u32 = u16::MAX as u32; #[allow(missing_docs)] #[derive(Error, Debug)] pub enum Error { + #[error("Missing wasm code error")] + MissingCode, #[error("Memory error: {0}")] MemoryError(memory::Error), #[error("Unable to inject gas meter")] @@ -84,8 +86,7 @@ pub fn tx( write_log: &mut WriteLog, gas_meter: &mut BlockGasMeter, tx_index: &TxIndex, - tx_code: impl AsRef<[u8]>, - tx_data: impl AsRef<[u8]>, + tx: &Tx, vp_wasm_cache: &mut VpCache, tx_wasm_cache: &mut TxCache, ) -> Result> @@ -94,15 +95,19 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let (module, store) = if tx_code.as_ref().len() == HASH_LENGTH { - // we assume that there is no wasm code with HASH_LENGTH - let code_hash = - Hash::try_from(tx_code.as_ref()).map_err(Error::CodeHash)?; - fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)? - } else { - match tx_wasm_cache.compile_or_fetch(tx_code)? { - Some((module, store)) => (module, store), - None => return Err(Error::NoCompiledWasmCode), + let tx_code = tx + .get_section(tx.code_sechash()) + .and_then(Section::code_sec) + .ok_or(Error::MissingCode)?; + let (module, store) = match tx_code.code { + Commitment::Hash(code_hash) => { + fetch_or_compile(tx_wasm_cache, &code_hash, write_log, storage)? + } + Commitment::Id(tx_code) => { + match tx_wasm_cache.compile_or_fetch(tx_code)? { + Some((module, store)) => (module, store), + None => return Err(Error::NoCompiledWasmCode), + } } }; @@ -116,6 +121,7 @@ where write_log, &mut iterators, gas_meter, + tx, tx_index, &mut verifiers, &mut result_buffer, @@ -140,7 +146,7 @@ where let memory::TxCallInput { tx_data_ptr, tx_data_len, - } = memory::write_tx_inputs(memory, tx_data).map_err(Error::MemoryError)?; + } = memory::write_tx_inputs(memory, tx).map_err(Error::MemoryError)?; // Get the module's entrypoint to be called let apply_tx = instance .exports @@ -187,11 +193,6 @@ where H: 'static + StorageHasher, CA: 'static + WasmCacheAccess, { - let input_data = match tx.data.as_ref() { - Some(data) => &data[..], - None => &[], - }; - // Compile the wasm module let (module, store) = fetch_or_compile(&mut vp_wasm_cache, vp_code_hash, write_log, storage)?; @@ -226,20 +227,13 @@ where memory::prepare_vp_memory(&store).map_err(Error::MemoryError)?; let imports = vp_imports(&store, initial_memory, env); - run_vp( - module, - imports, - input_data, - address, - keys_changed, - verifiers, - ) + run_vp(module, imports, tx, address, keys_changed, verifiers) } fn run_vp( module: wasmer::Module, vp_imports: wasmer::ImportObject, - input_data: &[u8], + input_data: &Tx, address: &Address, keys_changed: &BTreeSet, verifiers: &BTreeSet
, @@ -328,8 +322,8 @@ where fn eval( &self, ctx: VpCtx<'static, DB, H, Self, CA>, - vp_code_hash: Vec, - input_data: Vec, + vp_code_hash: Hash, + input_data: Tx, ) -> HostEnvResult { let vp_code_hash = match Hash::try_from(&vp_code_hash[..]) { Ok(hash) => hash, @@ -359,7 +353,7 @@ where &self, ctx: VpCtx<'static, DB, H, Self, CA>, vp_code_hash: Hash, - input_data: Vec, + input_data: Tx, ) -> Result { let address = unsafe { ctx.address.get() }; let keys_changed = unsafe { ctx.keys_changed.get() }; @@ -384,7 +378,7 @@ where run_vp( module, imports, - &input_data[..], + &input_data, address, keys_changed, verifiers, @@ -475,13 +469,15 @@ fn get_gas_rules() -> rules::Set { mod tests { use borsh::BorshSerialize; use itertools::Either; - use namada_core::types::chain::ChainId; use namada_test_utils::TestWasms; use test_log::test; use wasmer_vm::TrapCode; use super::*; use crate::ledger::storage::testing::TestStorage; + use crate::proto::{Code, Data}; + use crate::types::hash::Hash; + use crate::types::transaction::TxType; use crate::types::validity_predicate::EvalVp; use crate::vm::wasm; @@ -539,7 +535,7 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&tx_code); let key = Key::wasm_code(&code_hash); - write_log.write(&key, tx_code).unwrap(); + write_log.write(&key, tx_code.clone()).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -551,13 +547,15 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.set_code(Code::new(tx_code.clone())); + outer_tx.set_data(Data::new(tx_data)); let result = tx( &storage, &mut write_log, &mut gas_meter, &tx_index, - &code_hash, - tx_data, + &outer_tx, &mut vp_cache, &mut tx_cache, ); @@ -566,13 +564,15 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.set_code(Code::new(tx_code)); + outer_tx.set_data(Data::new(tx_data)); let error = tx( &storage, &mut write_log, &mut gas_meter, &tx_index, - &code_hash, - tx_data, + &outer_tx, &mut vp_cache, &mut tx_cache, ) @@ -613,18 +613,24 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let input = 2_usize.pow(23).try_to_vec().unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(input)); let eval_vp = EvalVp { - vp_code_hash: limit_code_hash.clone(), - input, + vp_code_hash: limit_code_hash, + input: tx, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_code(Code::new(vec![])); + outer_tx.set_data(Data::new(tx_data)); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); // When the `eval`ed VP doesn't run out of memory, it should return // `true` let passed = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -642,18 +648,24 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let input = 2_usize.pow(24).try_to_vec().unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(input)); let eval_vp = EvalVp { vp_code_hash: limit_code_hash, - input, + input: tx, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.set_code(Code::new(vec![])); // When the `eval`ed VP runs out of memory, its result should be // `false`, hence we should also get back `false` from the VP that // called `eval`. let passed = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -695,11 +707,14 @@ mod tests { // Allocating `2^23` (8 MiB) should be below the memory limit and // shouldn't fail let tx_data = 2_usize.pow(23).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.set_code(Code::new(vec![])); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -716,10 +731,12 @@ mod tests { // Allocating `2^24` (16 MiB) should be above the memory limit and // should fail let tx_data = 2_usize.pow(24).try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_data(Data::new(tx_data)); let error = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -749,7 +766,7 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&tx_no_op); let key = Key::wasm_code(&code_hash); - write_log.write(&key, tx_no_op).unwrap(); + write_log.write(&key, tx_no_op.clone()).unwrap(); // Assuming 200 pages, 12.8 MiB limit assert_eq!(memory::TX_MEMORY_MAX_PAGES, 200); @@ -762,13 +779,15 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.set_code(Code::new(tx_no_op)); + outer_tx.set_data(Data::new(tx_data)); let result = tx( &storage, &mut write_log, &mut gas_meter, &tx_index, - code_hash, - tx_data, + &outer_tx, &mut vp_cache, &mut tx_cache, ); @@ -816,11 +835,14 @@ mod tests { // limit and should fail let len = 2_usize.pow(24); let tx_data: Vec = vec![6_u8; len]; - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.set_code(Code::new(vec![])); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let result = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -867,7 +889,7 @@ mod tests { // store the wasm code let code_hash = Hash::sha256(&tx_read_key); let key = Key::wasm_code(&code_hash); - write_log.write(&key, tx_read_key).unwrap(); + write_log.write(&key, tx_read_key.clone()).unwrap(); // Allocating `2^24` (16 MiB) for a value in storage that the tx // attempts to read should be above the memory limit and should @@ -885,13 +907,15 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.set_code(Code::new(tx_read_key)); + outer_tx.set_data(Data::new(tx_data)); let error = tx( &storage, &mut write_log, &mut gas_meter, &tx_index, - code_hash, - tx_data, + &outer_tx, &mut vp_cache, &mut tx_cache, ) @@ -931,11 +955,14 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let tx_data = key.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.set_code(Code::new(vec![])); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let error = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -991,16 +1018,22 @@ mod tests { // Borsh. storage.write(&key, value.try_to_vec().unwrap()).unwrap(); let input = 2_usize.pow(23).try_to_vec().unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_data(Data::new(input)); + tx.set_code(Code::new(vec![])); let eval_vp = EvalVp { vp_code_hash: read_code_hash, - input, + input: tx, }; let tx_data = eval_vp.try_to_vec().unwrap(); - let tx = Tx::new(vec![], Some(tx_data), storage.chain_id.clone(), None); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.header.chain_id = storage.chain_id.clone(); + outer_tx.set_data(Data::new(tx_data)); + outer_tx.set_code(Code::new(vec![])); let (vp_cache, _) = wasm::compilation_cache::common::testing::cache(); let passed = vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -1060,18 +1093,22 @@ mod tests { wasm::compilation_cache::common::testing::cache(); let (mut tx_cache, _) = wasm::compilation_cache::common::testing::cache(); + // store the tx code let code_hash = Hash::sha256(&tx_code); let key = Key::wasm_code(&code_hash); write_log.write(&key, tx_code).unwrap(); + let mut outer_tx = Tx::new(TxType::Raw); + outer_tx.set_code(Code::from_hash(code_hash)); + outer_tx.set_data(Data::new(tx_data)); + tx( &storage, &mut write_log, &mut gas_meter, &tx_index, - code_hash, - tx_data, + &outer_tx, &mut vp_cache, &mut tx_cache, ) @@ -1104,9 +1141,9 @@ mod tests { (export "_validate_tx" (func $_validate_tx))) "#, loops).as_bytes(), ) - .expect("unexpected error converting wat2wasm").into_owned(); + .expect("unexpected error converting wat2wasm").into_owned(); - let tx = Tx::new(vec![], None, ChainId::default(), None); + let outer_tx = Tx::new(TxType::Raw); let tx_index = TxIndex::default(); let mut storage = TestStorage::default(); let addr = storage.address_gen.generate_address("rng seed"); @@ -1122,7 +1159,7 @@ mod tests { vp( &code_hash, - &tx, + &outer_tx, &tx_index, &addr, &storage, @@ -1154,10 +1191,10 @@ mod tests { assert!( // Universal engine error (currently used on mac) trap_code == - Either::Left(wasmer_vm::TrapCode::UnreachableCodeReached) || + Either::Left(wasmer_vm::TrapCode::UnreachableCodeReached) || // Dylib engine error (used elsewhere) - trap_code == - Either::Left(wasmer_vm::TrapCode::StackOverflow), + trap_code == + Either::Left(wasmer_vm::TrapCode::StackOverflow), ); } } diff --git a/tests/proptest-regressions/native_vp/pos.txt b/tests/proptest-regressions/native_vp/pos.txt index ad157e817b..c223b898a7 100644 --- a/tests/proptest-regressions/native_vp/pos.txt +++ b/tests/proptest-regressions/native_vp/pos.txt @@ -1,2 +1,2 @@ cc 65720acc67508ccd2fefc1ca42477075ae53a7d1e3c8f31324cfb8f06587457e -cc 45b2dd2ed9619ceef6135ee6ca34406621c8a6429ffa153bbda3ce79dd4e006c \ No newline at end of file +cc 45b2dd2ed9619ceef6135ee6ca34406621c8a6429ffa153bbda3ce79dd4e006c diff --git a/tests/src/native_vp/mod.rs b/tests/src/native_vp/mod.rs index 6baf214a3b..e46ee4b23c 100644 --- a/tests/src/native_vp/mod.rs +++ b/tests/src/native_vp/mod.rs @@ -58,9 +58,12 @@ impl TestNativeVpEnv { keys_changed: &self.keys_changed, verifiers: &self.verifiers, }; - let tx_data = self.tx_env.tx.data.as_ref().cloned().unwrap_or_default(); let native_vp = init_native_vp(ctx); - native_vp.validate_tx(&tx_data, &self.keys_changed, &self.verifiers) + native_vp.validate_tx( + &self.tx_env.tx, + &self.keys_changed, + &self.verifiers, + ) } } diff --git a/tests/src/vm_host_env/ibc.rs b/tests/src/vm_host_env/ibc.rs index 47ff3808fd..a442b5a6cd 100644 --- a/tests/src/vm_host_env/ibc.rs +++ b/tests/src/vm_host_env/ibc.rs @@ -105,7 +105,7 @@ pub struct TestIbcVp<'a> { impl<'a> TestIbcVp<'a> { pub fn validate( &self, - tx_data: &[u8], + tx_data: &Tx, ) -> std::result::Result { self.ibc.validate_tx( tx_data, @@ -122,7 +122,7 @@ pub struct TestIbcTokenVp<'a> { impl<'a> TestIbcTokenVp<'a> { pub fn validate( &self, - tx_data: &[u8], + tx_data: &Tx, ) -> std::result::Result { self.token.validate_tx( tx_data, @@ -164,7 +164,7 @@ pub fn validate_ibc_vp_from_tx<'a>( ); let ibc = Ibc { ctx }; - TestIbcVp { ibc }.validate(tx.data.as_ref().unwrap()) + TestIbcVp { ibc }.validate(tx) } /// Validate the native token VP for the given address @@ -200,7 +200,7 @@ pub fn validate_token_vp_from_tx<'a>( ); let token = IbcToken { ctx }; - TestIbcTokenVp { token }.validate(tx.data.as_ref().unwrap()) + TestIbcTokenVp { token }.validate(tx) } /// Initialize the test storage. Requires initialized [`tx_host_env::ENV`]. @@ -233,7 +233,7 @@ pub fn init_storage() -> (Address, Address) { }); // initialize a token - let token = tx::ctx().init_account(code_hash.clone()).unwrap(); + let token = tx::ctx().init_account(code_hash).unwrap(); // initialize an account let account = tx::ctx().init_account(code_hash).unwrap(); diff --git a/tests/src/vm_host_env/mod.rs b/tests/src/vm_host_env/mod.rs index 1ad4b22599..abde22dfae 100644 --- a/tests/src/vm_host_env/mod.rs +++ b/tests/src/vm_host_env/mod.rs @@ -27,20 +27,20 @@ mod tests { get_dummy_header as tm_dummy_header, Error as IbcError, }; use namada::ledger::tx_env::TxEnv; - use namada::proto::{SignedTxData, Tx}; + use namada::proto::{Code, Data, Section, Signature, Tx}; + use namada::tendermint_proto::Protobuf; use namada::types::chain::ChainId; use namada::types::hash::Hash; use namada::types::key::*; use namada::types::storage::{self, BlockHash, BlockHeight, Key, KeySeg}; use namada::types::time::DateTimeUtc; use namada::types::token::{self, Amount}; + use namada::types::transaction::TxType; use namada::types::{address, key}; use namada_core::ledger::ibc::context::transfer_mod::testing::DummyTransferModule; use namada_core::ledger::ibc::Error as IbcActionError; use namada_test_utils::TestWasms; - use namada_tx_prelude::{ - BorshDeserialize, BorshSerialize, StorageRead, StorageWrite, - }; + use namada_tx_prelude::{BorshSerialize, StorageRead, StorageWrite}; use namada_vp_prelude::VpEnv; use prost::Message; use test_log::test; @@ -452,38 +452,42 @@ mod tests { let expiration = Some(DateTimeUtc::now()); for data in &[ // Tx with some arbitrary data - Some(vec![1, 2, 3, 4].repeat(10)), + vec![1, 2, 3, 4].repeat(10), // Tx without any data - None, + vec![], ] { let signed_tx_data = vp_host_env::with(|env| { - env.tx = Tx::new( - code.clone(), - data.clone(), - env.wl_storage.storage.chain_id.clone(), - expiration, - ) - .sign(&keypair); - let tx_data = env.tx.data.as_ref().expect("data should exist"); - - SignedTxData::try_from_slice(&tx_data[..]) - .expect("decoding signed data we just signed") + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = env.wl_storage.storage.chain_id.clone(); + tx.header.expiration = expiration; + tx.set_code(Code::new(code.clone())); + tx.set_data(Data::new(data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &keypair, + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + env.tx = tx; + env.tx.clone() }); - assert_eq!(&signed_tx_data.data, data); + assert_eq!(signed_tx_data.data().as_ref(), Some(data)); assert!( - vp::CTX - .verify_tx_signature(&pk, &signed_tx_data.sig) - .unwrap() + signed_tx_data + .verify_signature(&pk, signed_tx_data.data_sechash()) + .is_ok() ); let other_keypair = key::testing::keypair_2(); assert!( - !vp::CTX - .verify_tx_signature( + signed_tx_data + .verify_signature( &other_keypair.ref_to(), - &signed_tx_data.sig + signed_tx_data.data_sechash() ) - .unwrap() + .is_err() ); } } @@ -535,7 +539,18 @@ mod tests { // evaluating without any code should fail let empty_code = Hash::zero(); let input_data = vec![]; - let result = vp::CTX.eval(empty_code, input_data).unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(input_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); + let result = vp::CTX.eval(empty_code, tx).unwrap(); assert!(!result); // evaluating the VP template which always returns `true` should pass @@ -547,7 +562,18 @@ mod tests { env.wl_storage.storage.write(&key, code.clone()).unwrap(); }); let input_data = vec![]; - let result = vp::CTX.eval(code_hash, input_data).unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(input_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); + let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(result); // evaluating the VP template which always returns `false` shouldn't @@ -560,7 +586,18 @@ mod tests { env.wl_storage.storage.write(&key, code.clone()).unwrap(); }); let input_data = vec![]; - let result = vp::CTX.eval(code_hash, input_data).unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(input_data)); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); + let result = vp::CTX.eval(code_hash, tx).unwrap(); assert!(!result); } @@ -570,19 +607,22 @@ mod tests { tx_host_env::init(); ibc::init_storage(); - + // Start a transaction to create a new client let msg = ibc::msg_create_client(); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // create a client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -612,14 +652,17 @@ mod tests { let msg = ibc::msg_update_client(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // update the client with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -652,14 +695,17 @@ mod tests { let msg = ibc::msg_connection_open_init(client_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // init a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -688,14 +734,17 @@ mod tests { let msg = ibc::msg_connection_open_ack(conn_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // open the connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -729,14 +778,17 @@ mod tests { let msg = ibc::msg_connection_open_try(client_id, client_state); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // open try a connection with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -765,14 +817,17 @@ mod tests { let msg = ibc::msg_connection_open_confirm(conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // open the connection with the mssage tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -802,20 +857,23 @@ mod tests { .expect("write error"); }); }); - + // Start a transaction for ChannelOpenInit let port_id = ibc::PortId::transfer(); let msg = ibc::msg_channel_open_init(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // init a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -844,14 +902,17 @@ mod tests { let msg = ibc::msg_channel_open_ack(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // open the channle with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -887,14 +948,17 @@ mod tests { let msg = ibc::msg_channel_open_try(port_id.clone(), conn_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // try open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -923,14 +987,18 @@ mod tests { let msg = ibc::msg_channel_open_confirm(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // open a channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -968,14 +1036,18 @@ mod tests { let msg = ibc::msg_channel_close_init(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // close the channel with the message let mut actions = tx_host_env::ibc::ibc_actions(tx::ctx()); // the dummy module closes the channel @@ -1021,14 +1093,18 @@ mod tests { let msg = ibc::msg_channel_close_confirm(port_id, channel_id); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // close the channel with the message tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1071,14 +1147,18 @@ mod tests { .to_any() .encode(&mut tx_data) .expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1120,14 +1200,18 @@ mod tests { let msg = ibc::msg_packet_ack(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // ack the packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1196,14 +1280,18 @@ mod tests { let msg = ibc::msg_transfer(port_id, channel_id, hashed_denom, &sender); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // send the token and a packet with the data tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1272,14 +1360,18 @@ mod tests { let msg = ibc::msg_packet_recv(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1361,14 +1453,17 @@ mod tests { let msg = ibc::msg_packet_recv(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1453,14 +1548,18 @@ mod tests { let msg = ibc::msg_packet_recv(packet); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // receive a packet with the message tx_host_env::ibc::ibc_actions(tx::ctx()) .execute(&tx_data) @@ -1549,14 +1648,17 @@ mod tests { let msg = ibc::msg_timeout(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) @@ -1635,14 +1737,17 @@ mod tests { let msg = ibc::msg_timeout_on_close(packet, ibc::Sequence::from(1)); let mut tx_data = vec![]; msg.to_any().encode(&mut tx_data).expect("encoding failed"); - let tx = Tx { - code_or_hash: vec![], - data: Some(tx_data.clone()), - timestamp: DateTimeUtc::now(), - chain_id: ChainId::default(), - expiration: None, - } - .sign(&key::testing::keypair_1()); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(vec![])); + tx.set_data(Data::new(tx_data.clone())); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key::testing::keypair_1(), + ))); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key::testing::keypair_1(), + ))); // timeout the packet tx_host_env::ibc::ibc_actions(tx::ctx()) diff --git a/tests/src/vm_host_env/tx.rs b/tests/src/vm_host_env/tx.rs index b67cac2416..c9ab18a08a 100644 --- a/tests/src/vm_host_env/tx.rs +++ b/tests/src/vm_host_env/tx.rs @@ -9,14 +9,15 @@ use namada::ledger::storage::write_log::WriteLog; use namada::ledger::storage::{Sha256Hasher, WlStorage}; use namada::proto::Tx; use namada::types::address::Address; +use namada::types::hash::Hash; use namada::types::storage::{Key, TxIndex}; use namada::types::time::DurationSecs; +pub use namada::types::transaction::TxType; use namada::types::{key, token}; use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::run::Error; use namada::vm::wasm::{self, TxCache, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; -use namada_core::types::hash::Hash; use namada_tx_prelude::{BorshSerialize, Ctx}; use tempfile::TempDir; @@ -62,12 +63,12 @@ impl Default for TestTxEnv { wasm::compilation_cache::common::testing::cache(); let (tx_wasm_cache, tx_cache_dir) = wasm::compilation_cache::common::testing::cache(); - let wl_storage = WlStorage { storage: TestStorage::default(), write_log: WriteLog::default(), }; - let chain_id = wl_storage.storage.chain_id.clone(); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); Self { wl_storage, iterators: PrefixIterators::default(), @@ -79,7 +80,7 @@ impl Default for TestTxEnv { vp_cache_dir, tx_wasm_cache, tx_cache_dir, - tx: Tx::new(vec![], None, chain_id, None), + tx, } } } @@ -208,14 +209,12 @@ impl TestTxEnv { /// Apply the tx changes to the write log. pub fn execute_tx(&mut self) -> Result<(), Error> { - let empty_data = vec![]; wasm::run::tx( &self.wl_storage.storage, &mut self.wl_storage.write_log, &mut self.gas_meter, &self.tx_index, - &self.tx.code_or_hash, - self.tx.data.as_ref().unwrap_or(&empty_data), + &self.tx, &mut self.vp_wasm_cache, &mut self.tx_wasm_cache, ) @@ -337,7 +336,7 @@ mod native_tx_host_env { vp_cache_dir: _, tx_wasm_cache, tx_cache_dir: _, - tx: _, + tx, }: &mut TestTxEnv| { let tx_env = vm::host_env::testing::tx_env( @@ -346,6 +345,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + tx, tx_index, result_buffer, vp_wasm_cache, @@ -376,7 +376,7 @@ mod native_tx_host_env { vp_cache_dir: _, tx_wasm_cache, tx_cache_dir: _, - tx: _, + tx, }: &mut TestTxEnv| { let tx_env = vm::host_env::testing::tx_env( @@ -385,6 +385,7 @@ mod native_tx_host_env { iterators, verifiers, gas_meter, + tx, tx_index, result_buffer, vp_wasm_cache, diff --git a/tests/src/vm_host_env/vp.rs b/tests/src/vm_host_env/vp.rs index 8220ce4c64..afad40a69d 100644 --- a/tests/src/vm_host_env/vp.rs +++ b/tests/src/vm_host_env/vp.rs @@ -8,6 +8,7 @@ use namada::ledger::storage::{Sha256Hasher, WlStorage}; use namada::proto::Tx; use namada::types::address::{self, Address}; use namada::types::storage::{self, Key, TxIndex}; +use namada::types::transaction::TxType; use namada::vm::prefix_iter::PrefixIterators; use namada::vm::wasm::{self, VpCache}; use namada::vm::{self, WasmCacheRwAccess}; @@ -68,13 +69,14 @@ impl Default for TestVpEnv { storage: TestStorage::default(), write_log: WriteLog::default(), }; - let chain_id = wl_storage.storage.chain_id.clone(); + let mut tx = Tx::new(TxType::Raw); + tx.header.chain_id = wl_storage.storage.chain_id.clone(); Self { addr: address::testing::established_address_1(), wl_storage, iterators: PrefixIterators::default(), gas_meter: VpGasMeter::default(), - tx: Tx::new(vec![], None, chain_id, None), + tx, tx_index: TxIndex::default(), keys_changed: BTreeSet::default(), verifiers: BTreeSet::default(), @@ -361,12 +363,6 @@ mod native_vp_host_env { native_host_fn!(vp_get_tx_code_hash(result_ptr: u64)); native_host_fn!(vp_get_block_epoch() -> u64); native_host_fn!(vp_get_native_token(result_ptr: u64)); - native_host_fn!(vp_verify_tx_signature( - pk_ptr: u64, - pk_len: u64, - sig_ptr: u64, - sig_len: u64, - ) -> i64); native_host_fn!(vp_eval( vp_code_ptr: u64, vp_code_len: u64, diff --git a/tx_prelude/Cargo.toml b/tx_prelude/Cargo.toml index 8e7cdb3f1b..71d4416b76 100644 --- a/tx_prelude/Cargo.toml +++ b/tx_prelude/Cargo.toml @@ -15,7 +15,8 @@ abciplus = [ ] [dependencies] -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } namada_core = {path = "../core", default-features = false} namada_macros = {path = "../macros"} namada_proof_of_stake = {path = "../proof_of_stake", default-features = false} diff --git a/tx_prelude/src/lib.rs b/tx_prelude/src/lib.rs index e4f991cad2..d9a92956a3 100644 --- a/tx_prelude/src/lib.rs +++ b/tx_prelude/src/lib.rs @@ -24,7 +24,7 @@ pub use namada_core::ledger::storage_api::{ ResultExt, StorageRead, StorageWrite, }; pub use namada_core::ledger::tx_env::TxEnv; -pub use namada_core::proto::{Signed, SignedTxData}; +pub use namada_core::proto::{Section, Tx}; pub use namada_core::types::address::Address; use namada_core::types::chain::CHAIN_ID_LENGTH; use namada_core::types::internal::HostEnvResult; diff --git a/tx_prelude/src/proof_of_stake.rs b/tx_prelude/src/proof_of_stake.rs index 22c00ca599..47d38e044c 100644 --- a/tx_prelude/src/proof_of_stake.rs +++ b/tx_prelude/src/proof_of_stake.rs @@ -1,5 +1,6 @@ //! Proof of Stake system integration with functions for transactions +use namada_core::types::hash::Hash; use namada_core::types::transaction::InitValidator; use namada_core::types::{key, token}; pub use namada_proof_of_stake::parameters::PosParams; @@ -72,12 +73,13 @@ impl Ctx { dkg_key, commission_rate, max_commission_rate_change, - validator_vp_code_hash, + validator_vp_code_hash: _, }: InitValidator, + validator_vp_code_hash: Hash, ) -> EnvResult
{ let current_epoch = self.get_block_epoch()?; // Init validator account - let validator_address = self.init_account(&validator_vp_code_hash)?; + let validator_address = self.init_account(validator_vp_code_hash)?; let pk_key = key::pk_key(&validator_address); self.write(&pk_key, &account_key)?; let protocol_pk_key = key::protocol_pk_key(&validator_address); diff --git a/tx_prelude/src/token.rs b/tx_prelude/src/token.rs index adf8fa92e9..251d9a21f9 100644 --- a/tx_prelude/src/token.rs +++ b/tx_prelude/src/token.rs @@ -1,5 +1,6 @@ use masp_primitives::transaction::Transaction; use namada_core::types::address::{Address, InternalAddress}; +use namada_core::types::hash::Hash; use namada_core::types::storage::KeySeg; use namada_core::types::token; pub use namada_core::types::token::*; @@ -16,6 +17,7 @@ pub fn transfer( sub_prefix: Option, amount: Amount, key: &Option, + shielded_hash: &Option, shielded: &Option, ) -> TxResult { if amount != Amount::default() { @@ -108,17 +110,16 @@ pub fn transfer( sub_prefix: None, amount, key: key.clone(), - shielded: Some(shielded.clone()), + shielded: *shielded_hash, }; - ctx.write( - ¤t_tx_key, - ( - ctx.get_block_epoch()?, - ctx.get_block_height()?, - ctx.get_tx_index()?, - transfer, - ), - )?; + let record: (Epoch, BlockHeight, TxIndex, Transfer, Transaction) = ( + ctx.get_block_epoch()?, + ctx.get_block_height()?, + ctx.get_tx_index()?, + transfer, + shielded.clone(), + ); + ctx.write(¤t_tx_key, record)?; ctx.write(&head_tx_key, current_tx_idx + 1)?; // If storage key has been supplied, then pin this transaction to it if let Some(key) = key { diff --git a/vm_env/Cargo.toml b/vm_env/Cargo.toml index 00141fcd8c..c56bd277c8 100644 --- a/vm_env/Cargo.toml +++ b/vm_env/Cargo.toml @@ -15,7 +15,6 @@ abciplus = [ [dependencies] namada_core = {path = "../core", default-features = false} borsh = "0.9.0" -#libmasp = { git = "https://github.com/anoma/masp", branch = "murisi/masp-incentive" } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c" } +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd" } hex = "0.4.3" diff --git a/vm_env/src/lib.rs b/vm_env/src/lib.rs index 9df39d6271..f51d92e62e 100644 --- a/vm_env/src/lib.rs +++ b/vm_env/src/lib.rs @@ -93,7 +93,7 @@ pub mod tx { // Get the current block epoch pub fn namada_tx_get_block_epoch() -> u64; - // Get the current tx id + // Get the current tx index pub fn namada_tx_get_tx_index() -> u32; // Get the native token address @@ -184,14 +184,6 @@ pub mod vp { // Get the native token address pub fn namada_vp_get_native_token(result_ptr: u64); - // Verify a transaction signature - pub fn namada_vp_verify_tx_signature( - pk_ptr: u64, - pk_len: u64, - sig_ptr: u64, - sig_len: u64, - ) -> i64; - // Requires a node running with "Info" log level pub fn namada_vp_log_string(str_ptr: u64, str_len: u64); diff --git a/vp_prelude/src/lib.rs b/vp_prelude/src/lib.rs index 57ac5a388f..8ec55ef7cd 100644 --- a/vp_prelude/src/lib.rs +++ b/vp_prelude/src/lib.rs @@ -23,12 +23,11 @@ pub use namada_core::ledger::storage_api::{ }; pub use namada_core::ledger::vp_env::VpEnv; pub use namada_core::ledger::{parameters, testnet_pow}; -pub use namada_core::proto::{Signed, SignedTxData}; +pub use namada_core::proto::{Section, Tx}; pub use namada_core::types::address::Address; use namada_core::types::chain::CHAIN_ID_LENGTH; use namada_core::types::hash::{Hash, HASH_LENGTH}; use namada_core::types::internal::HostEnvResult; -use namada_core::types::key::*; use namada_core::types::storage::{ BlockHash, BlockHeight, Epoch, Header, TxIndex, BLOCK_HASH_LENGTH, }; @@ -50,7 +49,9 @@ pub fn is_tx_whitelisted(ctx: &Ctx) -> VpResult { let whitelist: Vec = ctx.read_pre(&key)?.unwrap_or_default(); // if whitelist is empty, allow any transaction Ok(whitelist.is_empty() - || whitelist.contains(&tx_hash.to_string().to_lowercase())) + || (tx_hash.is_some() + && whitelist + .contains(&tx_hash.unwrap().to_string().to_lowercase()))) } pub fn is_vp_whitelisted(ctx: &Ctx, vp_hash: &[u8]) -> VpResult { @@ -82,11 +83,14 @@ pub fn is_proposal_accepted(ctx: &Ctx, proposal_id: u64) -> VpResult { /// - tx is whitelisted, or /// - tx is executed by an approved governance proposal (no need to be /// whitelisted) -pub fn is_valid_tx(ctx: &Ctx, tx_data: &[u8]) -> VpResult { +pub fn is_valid_tx(ctx: &Ctx, tx_data: &Tx) -> VpResult { if is_tx_whitelisted(ctx)? { accept() } else { - let proposal_id = u64::try_from_slice(tx_data).ok(); + let proposal_id = tx_data + .data() + .as_ref() + .and_then(|x| u64::try_from_slice(x).ok()); proposal_id.map_or(reject(), |id| is_proposal_accepted(ctx, id)) } @@ -272,44 +276,35 @@ impl<'view> VpEnv<'view> for Ctx { iter_prefix_pre_impl(prefix) } - fn eval(&self, vp_code: Hash, input_data: Vec) -> Result { + fn eval(&self, vp_code: Hash, input_data: Tx) -> Result { + let input_data_bytes = BorshSerialize::try_to_vec(&input_data).unwrap(); let result = unsafe { namada_vp_eval( - vp_code.as_ptr() as _, - vp_code.len() as _, - input_data.as_ptr() as _, - input_data.len() as _, + vp_code.0.as_ptr() as _, + vp_code.0.len() as _, + input_data_bytes.as_ptr() as _, + input_data_bytes.len() as _, ) }; Ok(HostEnvResult::is_success(result)) } - fn verify_tx_signature( - &self, - pk: &common::PublicKey, - sig: &common::Signature, - ) -> Result { - let pk = BorshSerialize::try_to_vec(pk).unwrap(); - let sig = BorshSerialize::try_to_vec(sig).unwrap(); - let valid = unsafe { - namada_vp_verify_tx_signature( - pk.as_ptr() as _, - pk.len() as _, - sig.as_ptr() as _, - sig.len() as _, - ) - }; - Ok(HostEnvResult::is_success(valid)) - } - - fn get_tx_code_hash(&self) -> Result { - let result = Vec::with_capacity(HASH_LENGTH); + fn get_tx_code_hash(&self) -> Result, Error> { + let result = Vec::with_capacity(HASH_LENGTH + 1); unsafe { namada_vp_get_tx_code_hash(result.as_ptr() as _); } let slice = - unsafe { slice::from_raw_parts(result.as_ptr(), HASH_LENGTH) }; - Ok(Hash::try_from(slice).expect("Cannot convert the hash")) + unsafe { slice::from_raw_parts(result.as_ptr(), HASH_LENGTH + 1) }; + Ok(if slice[0] == 1 { + Some(Hash( + slice[1..HASH_LENGTH + 1] + .try_into() + .expect("Cannot convert the hash"), + )) + } else { + None + }) } fn verify_masp(&self, tx: Vec) -> Result { diff --git a/wasm/Cargo.lock b/wasm/Cargo.lock index 5d8409ef24..3c377af40a 100644 --- a/wasm/Cargo.lock +++ b/wasm/Cargo.lock @@ -19,10 +19,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array", ] @@ -33,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", "opaque-debug", ] @@ -486,20 +487,31 @@ dependencies = [ "lazy_static", "log", "num_cpus", - "pairing", + "pairing 0.21.0", "rand_core 0.6.4", "rayon", "subtle", ] [[package]] -name = "bigint" -version = "4.4.3" +name = "bellman" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807" dependencies = [ + "bitvec 1.0.1", + "blake2s_simd 1.0.1", "byteorder", - "crunchy 0.1.6", + "crossbeam-channel 0.5.8", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "log", + "num_cpus", + "pairing 0.22.0", + "rand_core 0.6.4", + "rayon", + "subtle", ] [[package]] @@ -557,7 +569,7 @@ checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32 0.9.1", "bitcoin_hashes", - "secp256k1 0.24.3", + "secp256k1", "serde", ] @@ -609,17 +621,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq 0.1.5", -] - [[package]] name = "blake2b_simd" version = "1.0.1" @@ -692,7 +693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding", - "cipher", + "cipher 0.3.0", ] [[package]] @@ -724,7 +725,20 @@ checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ "ff 0.11.1", "group 0.11.0", - "pairing", + "pairing 0.21.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", "rand_core 0.6.4", "subtle", ] @@ -887,20 +901,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", - "zeroize", ] [[package]] -name = "chacha20poly1305" +name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", - "cipher", + "chacha20 0.9.1", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -926,6 +950,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "circular-queue" version = "0.2.6" @@ -1146,12 +1181,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crunchy" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" - [[package]] name = "crunchy" version = "0.2.2" @@ -1200,21 +1229,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto_api" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" - -[[package]] -name = "crypto_api_chachapoly" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" -dependencies = [ - "crypto_api", -] - [[package]] name = "ct-codecs" version = "1.1.1" @@ -1578,15 +1592,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "equihash" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "blake2b_simd 1.0.1", - "byteorder", -] - [[package]] name = "erased-serde" version = "0.3.25" @@ -1672,7 +1677,7 @@ dependencies = [ "ark-std", "bincode", "blake2", - "blake2b_simd 1.0.1", + "blake2b_simd", "borsh", "digest 0.10.6", "ed25519-dalek", @@ -1724,6 +1729,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ + "bitvec 1.0.1", "rand_core 0.6.4", "subtle", ] @@ -1778,7 +1784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm", "num-bigint", "num-integer", @@ -1988,6 +1994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff 0.12.1", + "memuse", "rand_core 0.6.4", "subtle", ] @@ -2004,8 +2011,8 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "blake2b_simd 1.0.1", - "chacha20", + "blake2b_simd", + "chacha20 0.8.2", "hex", "itertools", "miracl_core", @@ -2061,20 +2068,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "halo2" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" -dependencies = [ - "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", - "pasta_curves", - "rand 0.8.5", - "rayon", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -2438,7 +2431,7 @@ dependencies = [ "regex", "retry", "ripemd", - "secp256k1 0.24.3", + "secp256k1", "semver 1.0.17", "serde", "serde_derive", @@ -2554,9 +2547,9 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" dependencies = [ "serde", ] @@ -2587,6 +2580,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2636,14 +2638,14 @@ dependencies = [ [[package]] name = "jubjub" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" dependencies = [ - "bitvec 0.22.3", - "bls12_381", - "ff 0.11.1", - "group 0.11.0", + "bitvec 1.0.1", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", "rand_core 0.6.4", "subtle", ] @@ -2735,7 +2737,7 @@ name = "libsecp256k1-core" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "crunchy 0.2.2", + "crunchy", "digest 0.9.0", "subtle", ] @@ -2812,58 +2814,67 @@ dependencies = [ "libc", ] +[[package]] +name = "masp_note_encryption" +version = "0.2.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +dependencies = [ + "borsh", + "chacha20 0.9.1", + "chacha20poly1305", + "cipher 0.4.4", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "masp_primitives" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ "aes", "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", + "bitvec 1.0.1", + "blake2b_simd", "blake2s_simd 1.0.1", - "bls12_381", + "bls12_381 0.7.1", "borsh", "byteorder", - "chacha20poly1305", - "crypto_api_chachapoly", - "ff 0.11.1", + "ff 0.12.1", "fpe", - "group 0.11.0", + "group 0.12.1", "hex", "incrementalmerkletree", "jubjub", "lazy_static", + "masp_note_encryption", + "memuse", + "nonempty", "rand 0.8.5", "rand_core 0.6.4", - "ripemd160", - "secp256k1 0.20.3", - "serde", "sha2 0.9.9", "subtle", "zcash_encoding", - "zcash_primitives", ] [[package]] name = "masp_proofs" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", + "bellman 0.13.1", + "blake2b_simd", + "bls12_381 0.7.1", "directories", - "ff 0.11.1", - "group 0.11.0", + "getrandom 0.2.9", + "group 0.12.1", "itertools", "jubjub", "lazy_static", "masp_primitives", "rand_core 0.6.4", - "zcash_primitives", - "zcash_proofs", + "redjubjub", + "tracing", ] [[package]] @@ -2941,9 +2952,6 @@ name = "memuse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" -dependencies = [ - "nonempty", -] [[package]] name = "merlin" @@ -3031,9 +3039,9 @@ version = "0.16.0" dependencies = [ "async-std", "async-trait", - "bellman", + "bellman 0.11.2", "bimap", - "bls12_381", + "bls12_381 0.6.1", "borsh", "circular-queue", "clru", @@ -3056,6 +3064,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "ripemd", "rust_decimal", "rust_decimal_macros", "serde", @@ -3087,7 +3096,7 @@ dependencies = [ "ark-ec", "ark-serialize", "bech32 0.8.1", - "bellman", + "bellman 0.11.2", "borsh", "chrono", "data-encoding", @@ -3212,7 +3221,6 @@ dependencies = [ "borsh", "hex", "masp_primitives", - "masp_proofs", "namada_core", ] @@ -3236,7 +3244,6 @@ dependencies = [ "borsh", "getrandom 0.2.9", "masp_primitives", - "masp_proofs", "namada", "namada_test_utils", "namada_tests", @@ -3396,33 +3403,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "orchard" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" -dependencies = [ - "aes", - "arrayvec 0.7.2", - "bigint", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "ff 0.11.1", - "fpe", - "group 0.11.0", - "halo2", - "incrementalmerkletree", - "lazy_static", - "memuse", - "nonempty", - "pasta_curves", - "rand 0.8.5", - "reddsa", - "serde", - "subtle", - "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "orion" version = "0.16.1" @@ -3444,6 +3424,15 @@ dependencies = [ "group 0.11.0", ] +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + [[package]] name = "parity-scale-codec" version = "3.5.0" @@ -3516,21 +3505,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pasta_curves" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" -dependencies = [ - "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "rand 0.8.5", - "static_assertions", - "subtle", -] - [[package]] name = "paste" version = "1.0.12" @@ -3659,9 +3633,9 @@ dependencies = [ [[package]] name = "poly1305" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug", @@ -4019,17 +3993,15 @@ dependencies = [ ] [[package]] -name = "reddsa" -version = "0.1.0" +name = "redjubjub" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d" dependencies = [ - "blake2b_simd 0.5.11", + "blake2b_simd", "byteorder", "digest 0.9.0", - "group 0.11.0", "jubjub", - "pasta_curves", "rand_core 0.6.4", "serde", "thiserror", @@ -4479,15 +4451,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" -dependencies = [ - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.24.3" @@ -4496,19 +4459,10 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.6.1", + "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -5265,7 +5219,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "crunchy 0.2.2", + "crunchy", ] [[package]] @@ -5597,7 +5551,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", "static_assertions", ] @@ -5652,11 +5606,11 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ - "generic-array", + "crypto-common", "subtle", ] @@ -6389,83 +6343,10 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "byteorder", - "nonempty", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f84ae538f05a8ac74c82527f06b77045ed9553a0871d9db036166a4c344e3a" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_primitives" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=43c18d0#43c18d000fcbe45363b2d53585d5102841eff99e" dependencies = [ - "aes", - "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "blake2s_simd 1.0.1", - "bls12_381", "byteorder", - "chacha20poly1305", - "equihash", - "ff 0.11.1", - "fpe", - "group 0.11.0", - "hex", - "incrementalmerkletree", - "jubjub", - "lazy_static", - "memuse", "nonempty", - "orchard", - "rand 0.8.5", - "rand_core 0.6.4", - "sha2 0.9.9", - "subtle", - "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", -] - -[[package]] -name = "zcash_proofs" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", - "directories", - "ff 0.11.1", - "group 0.11.0", - "jubjub", - "lazy_static", - "rand_core 0.6.4", - "zcash_primitives", ] [[package]] diff --git a/wasm/checksums.json b/wasm/checksums.json index 80b216ed24..d8e1f0f3f2 100644 --- a/wasm/checksums.json +++ b/wasm/checksums.json @@ -1,20 +1,20 @@ { - "tx_bond.wasm": "tx_bond.4861430f580e9973c96e99f615d81950c088317768d1dbdb1088ca1889db13a0.wasm", - "tx_change_validator_commission.wasm": "tx_change_validator_commission.a6013f149f61e1d80b6858db12c6203b6dd78443c2d3006527e38617388337ae.wasm", - "tx_ibc.wasm": "tx_ibc.5adb9e98dd7930c6442eff946cec0eec50a447873a6e97cd79f1552639c0ca9a.wasm", - "tx_init_account.wasm": "tx_init_account.139590861c2c86459acbccf058ba7147be83bd7d90f914ac605dd71077937c5d.wasm", - "tx_init_proposal.wasm": "tx_init_proposal.8449c431543e02466d9284df81317a4e4cd451772a43a5f45062a144b2a41424.wasm", - "tx_init_validator.wasm": "tx_init_validator.a38b2664989e7e87f9016690b415b77e6b0f36443d46a947b04f809efc9caa59.wasm", - "tx_reveal_pk.wasm": "tx_reveal_pk.69aa3ad6305a26cbba8667efa923389afbb6db6feb025e7c88eac8f413c9e36b.wasm", - "tx_transfer.wasm": "tx_transfer.e68e6cb3336962c1f3e5d63054fb4e5fbca02b12125a71179aa86169e12174d7.wasm", - "tx_unbond.wasm": "tx_unbond.53119371c8f4b20424774d9312fa50333c4f8250b6018384142e0e8553009a31.wasm", - "tx_update_vp.wasm": "tx_update_vp.24b8501c7df4ee482fbab8411a5e8666d36267302783998e6dd3ccc92a2eb802.wasm", - "tx_vote_proposal.wasm": "tx_vote_proposal.45d73369f04f4261de0a89f1c6e398f26aed18ee921832bcfe845de169e8d21f.wasm", - "tx_withdraw.wasm": "tx_withdraw.f2acfee621b4805ef092143195067728312d60d30907d3f82be850ef295092d3.wasm", - "vp_implicit.wasm": "vp_implicit.5f4c7920478e2db6db63765f9af9800d2d9c164728e3eb48360a2617e173aefb.wasm", - "vp_masp.wasm": "vp_masp.f17ee14abb38853066fada237164faaf6cf49a0c121b4b2e9cfd48030c17d620.wasm", - "vp_testnet_faucet.wasm": "vp_testnet_faucet.79f768c6d0dd20c4b38b982afdc72645d319d94abe4c577e99b086a24ba94984.wasm", - "vp_token.wasm": "vp_token.b09a3aa221e995e82fe58082bfa428cdde161fdc444f17e135800b21b3c7d1cb.wasm", - "vp_user.wasm": "vp_user.c5a15735bfd5cf5dfc8019805f36e8438b68b2c1c1ba653518d465c7159ef5d3.wasm", - "vp_validator.wasm": "vp_validator.12d8726feaf1c9a3465be9f25ce6d3e2599cb79ab1f09df656326f73d1a577ff.wasm" + "tx_bond.wasm": "tx_bond.f2bf2342a3210f8ec1235851565317fe5c4d48193f0228e947832c5db8dbae6d.wasm", + "tx_change_validator_commission.wasm": "tx_change_validator_commission.4357d360f0ac04bd6b3f723a1e4a920c16ea1e8499e4bd6c63891c482685d30c.wasm", + "tx_ibc.wasm": "tx_ibc.f88ec7f4b7a1b6f8a420ba8620e4d206e27ed3905355d68f13f7813a73125208.wasm", + "tx_init_account.wasm": "tx_init_account.b024e3e9bb9632146c0be1af898440d9fe20380f19eb5e98ed03e68a966d36ab.wasm", + "tx_init_proposal.wasm": "tx_init_proposal.6060a32de5ac23ee8dd07c26f59de471924144028970197fe3cce2e2467e650b.wasm", + "tx_init_validator.wasm": "tx_init_validator.edfe0547c8a9b132dacecbfa3f1acaa98d28a1345bc7ee8c41718e95ff58d37b.wasm", + "tx_reveal_pk.wasm": "tx_reveal_pk.1a4423758d823b4166ce98e9087f7a694f072c413529790310e7de6e133518d0.wasm", + "tx_transfer.wasm": "tx_transfer.f69a2cc913078f0ae3e23ad028b52cd36f8f39a0595e5a5163606b18e860ac1c.wasm", + "tx_unbond.wasm": "tx_unbond.bd6e4802e4e9a2a4b35e5d69caa70164b57c82060a7133ef7d611bf3ec947031.wasm", + "tx_update_vp.wasm": "tx_update_vp.02c2b7c6b66b2dde89ab2ef0066a854ce9bc8d34418ccffbfa0601fade033f6e.wasm", + "tx_vote_proposal.wasm": "tx_vote_proposal.ee40a3ad9146f5ee27790c45217bba85d04ad40d3baa629334129a03a0cfd810.wasm", + "tx_withdraw.wasm": "tx_withdraw.23e18951bff3267ec1de67d455dab94216c7b7f8ec696e8df8603c744980d5fb.wasm", + "vp_implicit.wasm": "vp_implicit.d0394ae7e24cef6050ae674c1ccb8f73c4dbc2706a42e0b0be469d1b3e19a314.wasm", + "vp_masp.wasm": "vp_masp.e0ce8a185b136305e56367ae1e872125c5bf6b14028b2f7138d91472fb01d128.wasm", + "vp_testnet_faucet.wasm": "vp_testnet_faucet.c90b10bcc847873630b152594cd2140250943e72b77c4be3b16117f25d6b645e.wasm", + "vp_token.wasm": "vp_token.286b3fb9c38285c33512339c2a30f5421eb418ea55064ccfe9cdda70acd64d8e.wasm", + "vp_user.wasm": "vp_user.e2f4528d6b306bd1daeb13ccf6ed6e488ab3c25c4d199696cb1591736e433bef.wasm", + "vp_validator.wasm": "vp_validator.3729a5e35e2505bcf1f1e0b8a33517877290c132c9d5ea94174568bb189708e6.wasm" } \ No newline at end of file diff --git a/wasm/tx_template/src/lib.rs b/wasm/tx_template/src/lib.rs index 473984aa31..4a14783325 100644 --- a/wasm/tx_template/src/lib.rs +++ b/wasm/tx_template/src/lib.rs @@ -1,7 +1,7 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(_ctx: &mut Ctx, tx_data: Vec) -> TxResult { +fn apply_tx(_ctx: &mut Ctx, tx_data: Tx) -> TxResult { log_string(format!("apply_tx called with data: {:#?}", tx_data)); Ok(()) } @@ -19,8 +19,8 @@ mod tests { // The environment must be initialized first tx_host_env::init(); - let tx_data = vec![]; - apply_tx(ctx(), tx_data).unwrap(); + let tx = Tx::new(TxType::Raw); + apply_tx(ctx(), tx).unwrap(); let env = tx_host_env::take(); assert!(env.all_touched_storage_keys().is_empty()); diff --git a/wasm/vp_template/src/lib.rs b/wasm/vp_template/src/lib.rs index 35cdabd1c5..c4af9f31e0 100644 --- a/wasm/vp_template/src/lib.rs +++ b/wasm/vp_template/src/lib.rs @@ -3,7 +3,7 @@ use namada_vp_prelude::*; #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, diff --git a/wasm/wasm_source/Cargo.toml b/wasm/wasm_source/Cargo.toml index 85194462e6..5861d32d4f 100644 --- a/wasm/wasm_source/Cargo.toml +++ b/wasm/wasm_source/Cargo.toml @@ -25,7 +25,7 @@ tx_update_vp = ["namada_tx_prelude"] tx_vote_proposal = ["namada_tx_prelude"] tx_withdraw = ["namada_tx_prelude"] tx_change_validator_commission = ["namada_tx_prelude"] -vp_masp = ["namada_vp_prelude", "masp_proofs", "masp_primitives"] +vp_masp = ["namada_vp_prelude", "masp_primitives"] vp_implicit = ["namada_vp_prelude", "once_cell", "rust_decimal"] vp_testnet_faucet = ["namada_vp_prelude", "once_cell"] vp_token = ["namada_vp_prelude"] @@ -40,9 +40,9 @@ once_cell = {version = "1.8.0", optional = true} rust_decimal = {version = "=1.26.1", optional = true} wee_alloc = "0.4.5" getrandom = { version = "0.2", features = ["custom"] } -masp_proofs = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } -masp_primitives = { git = "https://github.com/anoma/masp", rev = "bee40fc465f6afbd10558d12fe96eb1742eee45c", optional = true } -ripemd = "0.1.3" +# branch = "murisi/namada-integration" +masp_primitives = { git = "https://github.com/anoma/masp", rev = "cfea8c95d3f73077ca3e25380fd27e5b46e828fd", optional = true } +ripemd = "0.1" [dev-dependencies] namada = {path = "../../shared"} diff --git a/wasm/wasm_source/src/tx_bond.rs b/wasm/wasm_source/src/tx_bond.rs index 8215045d5c..35e8ba7fac 100644 --- a/wasm/wasm_source/src/tx_bond.rs +++ b/wasm/wasm_source/src/tx_bond.rs @@ -3,11 +3,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData") - .unwrap(); - let data = signed.data.ok_or_err_msg("Missing data").unwrap(); +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let bond = transaction::pos::Bond::try_from_slice(&data[..]) .wrap_err("failed to decode Bond") .unwrap(); @@ -24,9 +22,9 @@ mod tests { bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, }; - use namada::proto::Tx; - use namada::types::chain::ChainId; + use namada::proto::{Code, Data, Signature, Tx}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -100,9 +98,18 @@ mod tests { let tx_code = vec![]; let tx_data = bond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); - let signed_tx = tx.sign(&key); - let tx_data = signed_tx.data.unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key, + ))); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key, + ))); + let signed_tx = tx.clone(); // Ensure that the initial stake of the sole validator is equal to the // PoS account balance @@ -145,7 +152,7 @@ mod tests { ); } - apply_tx(ctx(), tx_data)?; + apply_tx(ctx(), signed_tx)?; // Read the data after the tx is executed. let mut epoched_total_stake_post: Vec = Vec::new(); diff --git a/wasm/wasm_source/src/tx_change_validator_commission.rs b/wasm/wasm_source/src/tx_change_validator_commission.rs index 30ae263269..938a57236b 100644 --- a/wasm/wasm_source/src/tx_change_validator_commission.rs +++ b/wasm/wasm_source/src/tx_change_validator_commission.rs @@ -4,10 +4,9 @@ use namada_tx_prelude::transaction::pos::CommissionChange; use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let CommissionChange { validator, new_rate, @@ -22,9 +21,9 @@ mod tests { use namada::ledger::pos::{PosParams, PosVP}; use namada::proof_of_stake::validator_commission_rate_handle; - use namada::proto::Tx; - use namada::types::chain::ChainId; + use namada::proto::{Code, Data, Signature, Tx}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -79,9 +78,18 @@ mod tests { let tx_code = vec![]; let tx_data = commission_change.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); - let signed_tx = tx.sign(&key); - let tx_data = signed_tx.data.unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_data(Data::new(tx_data)); + tx.set_code(Code::new(tx_code)); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key, + ))); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key, + ))); + let signed_tx = tx.clone(); // Read the data before the tx is executed let commission_rate_handle = @@ -98,7 +106,7 @@ mod tests { assert_eq!(commission_rates_pre[0], Some(initial_rate)); - apply_tx(ctx(), tx_data)?; + apply_tx(ctx(), signed_tx)?; // Read the data after the tx is executed diff --git a/wasm/wasm_source/src/tx_ibc.rs b/wasm/wasm_source/src/tx_ibc.rs index b9473170b2..08c7678b07 100644 --- a/wasm/wasm_source/src/tx_ibc.rs +++ b/wasm/wasm_source/src/tx_ibc.rs @@ -6,10 +6,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; ibc::ibc_actions(ctx).execute(&data).into_storage_result() } diff --git a/wasm/wasm_source/src/tx_init_account.rs b/wasm/wasm_source/src/tx_init_account.rs index b1041c40a2..2e85b70ae9 100644 --- a/wasm/wasm_source/src/tx_init_account.rs +++ b/wasm/wasm_source/src/tx_init_account.rs @@ -4,15 +4,21 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let tx_data = transaction::InitAccount::try_from_slice(&data[..]) .wrap_err("failed to decode InitAccount")?; debug_log!("apply_tx called to init a new established account"); - let address = ctx.init_account(&tx_data.vp_code_hash)?; + let vp_code = signed + .get_section(&tx_data.vp_code_hash) + .ok_or_err_msg("vp code section not found")? + .extra_data_sec() + .ok_or_err_msg("vp code section must be tagged as extra")? + .code + .hash(); + let address = ctx.init_account(vp_code)?; let pk_key = key::pk_key(&address); ctx.write(&pk_key, &tx_data.public_key)?; Ok(()) diff --git a/wasm/wasm_source/src/tx_init_proposal.rs b/wasm/wasm_source/src/tx_init_proposal.rs index cb7fe9ffbb..2507a18d21 100644 --- a/wasm/wasm_source/src/tx_init_proposal.rs +++ b/wasm/wasm_source/src/tx_init_proposal.rs @@ -3,10 +3,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::InitProposalData::try_from_slice(&data[..]) .wrap_err("failed to decode InitProposalData")?; diff --git a/wasm/wasm_source/src/tx_init_validator.rs b/wasm/wasm_source/src/tx_init_validator.rs index 6a823faf3f..0cd37da111 100644 --- a/wasm/wasm_source/src/tx_init_validator.rs +++ b/wasm/wasm_source/src/tx_init_validator.rs @@ -5,16 +5,23 @@ use namada_tx_prelude::transaction::InitValidator; use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let init_validator = InitValidator::try_from_slice(&data[..]) .wrap_err("failed to decode InitValidator")?; debug_log!("apply_tx called to init a new validator account"); + // Get the validator vp code from the extra section + let validator_vp_code_hash = signed + .get_section(&init_validator.validator_vp_code_hash) + .ok_or_err_msg("validator vp section not found")? + .extra_data_sec() + .ok_or_err_msg("validator vp section must be tagged as extra")? + .code + .hash(); // Register the validator in PoS - match ctx.init_validator(init_validator) { + match ctx.init_validator(init_validator, validator_vp_code_hash) { Ok(validator_address) => { debug_log!("Created validator {}", validator_address.encode(),) } diff --git a/wasm/wasm_source/src/tx_reveal_pk.rs b/wasm/wasm_source/src/tx_reveal_pk.rs index be3bacce6d..e091b69a4c 100644 --- a/wasm/wasm_source/src/tx_reveal_pk.rs +++ b/wasm/wasm_source/src/tx_reveal_pk.rs @@ -7,8 +7,10 @@ use namada_tx_prelude::key::common; use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let pk = common::PublicKey::try_from_slice(&tx_data[..]) +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; + let pk = common::PublicKey::try_from_slice(&data[..]) .wrap_err("failed to decode common::PublicKey from tx_data")?; debug_log!("tx_reveal_pk called with pk: {pk}"); key::reveal_pk(ctx, &pk) diff --git a/wasm/wasm_source/src/tx_transfer.rs b/wasm/wasm_source/src/tx_transfer.rs index 293b7207c3..1727035d2f 100644 --- a/wasm/wasm_source/src/tx_transfer.rs +++ b/wasm/wasm_source/src/tx_transfer.rs @@ -5,10 +5,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let transfer = token::Transfer::try_from_slice(&data[..]) .wrap_err("failed to decode token::Transfer")?; debug_log!("apply_tx called with transfer: {:#?}", transfer); @@ -19,9 +18,26 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { sub_prefix, amount, key, - shielded, + shielded: shielded_hash, } = transfer; + let shielded = shielded_hash + .as_ref() + .map(|hash| { + signed + .get_section(hash) + .and_then(Section::masp_tx) + .ok_or_err_msg("unable to find shielded section") + }) + .transpose()?; token::transfer( - ctx, &source, &target, &token, sub_prefix, amount, &key, &shielded, + ctx, + &source, + &target, + &token, + sub_prefix, + amount, + &key, + &shielded_hash, + &shielded, ) } diff --git a/wasm/wasm_source/src/tx_unbond.rs b/wasm/wasm_source/src/tx_unbond.rs index 33cf9f56ea..49932fdcbf 100644 --- a/wasm/wasm_source/src/tx_unbond.rs +++ b/wasm/wasm_source/src/tx_unbond.rs @@ -4,10 +4,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let unbond = transaction::pos::Unbond::try_from_slice(&data[..]) .wrap_err("failed to decode Unbond")?; @@ -24,9 +23,9 @@ mod tests { bond_handle, read_consensus_validator_set_addresses_with_stake, read_total_stake, read_validator_stake, unbond_handle, }; - use namada::proto::Tx; - use namada::types::chain::ChainId; + use namada::proto::{Code, Data, Signature, Tx}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -122,9 +121,18 @@ mod tests { let tx_code = vec![]; let tx_data = unbond.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); - let signed_tx = tx.sign(&key); - let tx_data = signed_tx.data.unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_data(Data::new(tx_data)); + tx.set_code(Code::new(tx_code)); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key, + ))); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key, + ))); + let signed_tx = tx.clone(); let unbond_src = unbond .source @@ -182,7 +190,7 @@ mod tests { // dbg!(&epoched_bonds_pre); // Apply the unbond tx - apply_tx(ctx(), tx_data)?; + apply_tx(ctx(), signed_tx)?; // Read the data after the tx is executed. // The following storage keys should be updated: diff --git a/wasm/wasm_source/src/tx_update_vp.rs b/wasm/wasm_source/src/tx_update_vp.rs index fb0b80af40..7df5facde6 100644 --- a/wasm/wasm_source/src/tx_update_vp.rs +++ b/wasm/wasm_source/src/tx_update_vp.rs @@ -5,14 +5,19 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let update_vp = transaction::UpdateVp::try_from_slice(&data[..]) .wrap_err("failed to decode UpdateVp")?; debug_log!("update VP for: {:#?}", update_vp.addr); - - ctx.update_validity_predicate(&update_vp.addr, update_vp.vp_code_hash) + let vp_code_hash = signed + .get_section(&update_vp.vp_code_hash) + .ok_or_err_msg("vp code section not found")? + .extra_data_sec() + .ok_or_err_msg("vp code section must be tagged as extra")? + .code + .hash(); + ctx.update_validity_predicate(&update_vp.addr, vp_code_hash) } diff --git a/wasm/wasm_source/src/tx_vote_proposal.rs b/wasm/wasm_source/src/tx_vote_proposal.rs index 92c7af4c7f..3be6685bdc 100644 --- a/wasm/wasm_source/src/tx_vote_proposal.rs +++ b/wasm/wasm_source/src/tx_vote_proposal.rs @@ -3,10 +3,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let tx_data = transaction::governance::VoteProposalData::try_from_slice(&data[..]) .wrap_err("failed to decode VoteProposalData")?; diff --git a/wasm/wasm_source/src/tx_withdraw.rs b/wasm/wasm_source/src/tx_withdraw.rs index b00661261e..eaac9f57bf 100644 --- a/wasm/wasm_source/src/tx_withdraw.rs +++ b/wasm/wasm_source/src/tx_withdraw.rs @@ -4,10 +4,9 @@ use namada_tx_prelude::*; #[transaction] -fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = signed.data.ok_or_err_msg("Missing data")?; +fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = signed.data().ok_or_err_msg("Missing data")?; let withdraw = transaction::pos::Withdraw::try_from_slice(&data[..]) .wrap_err("failed to decode Withdraw")?; @@ -23,9 +22,9 @@ fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { mod tests { use namada::ledger::pos::{GenesisValidator, PosParams, PosVP}; use namada::proof_of_stake::unbond_handle; - use namada::proto::Tx; - use namada::types::chain::ChainId; + use namada::proto::{Code, Data, Signature, Tx}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; use namada_tests::native_vp::TestNativeVpEnv; @@ -155,9 +154,18 @@ mod tests { let tx_code = vec![]; let tx_data = withdraw.try_to_vec().unwrap(); - let tx = Tx::new(tx_code, Some(tx_data), ChainId::default(), None); - let signed_tx = tx.sign(&key); - let tx_data = signed_tx.data.unwrap(); + let mut tx = Tx::new(TxType::Raw); + tx.set_code(Code::new(tx_code)); + tx.set_data(Data::new(tx_data)); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &key, + ))); + tx.add_section(Section::Signature(Signature::new( + tx.code_sechash(), + &key, + ))); + let signed_tx = tx.clone(); // Read data before we apply tx: let pos_balance_key = token::balance_key( @@ -180,7 +188,7 @@ mod tests { assert_eq!(unbond_pre, Some(unbonded_amount)); - apply_tx(ctx(), tx_data)?; + apply_tx(ctx(), signed_tx)?; // Read the data after the tx is executed let unbond_post = diff --git a/wasm/wasm_source/src/vp_implicit.rs b/wasm/wasm_source/src/vp_implicit.rs index 26a88d84ea..e2fc7eb6ef 100644 --- a/wasm/wasm_source/src/vp_implicit.rs +++ b/wasm/wasm_source/src/vp_implicit.rs @@ -52,7 +52,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -64,23 +64,14 @@ fn validate_tx( verifiers ); - let signed_tx_data = - Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); - - let valid_sig = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => { - matches!( - ctx.verify_tx_signature(&pk, &signed_tx_data.sig), - Ok(true) - ) - } - _ => false, - } + let valid_sig = Lazy::new(|| { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => tx_data + .verify_signature(&pk, tx_data.data_sechash()) + .is_ok(), + _ => false, } - _ => false, }); if !is_valid_tx(ctx, &tx_data)? { @@ -202,7 +193,9 @@ fn validate_tx( mod tests { // Use this as `#[test]` annotation to enable logging use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::proto::{Code, Data, Signature}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_test_utils::TestWasms; use namada_tests::log::test; use namada_tests::native_vp::pos::init_pos; @@ -219,7 +212,8 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -251,7 +245,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -277,7 +272,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -313,7 +309,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -357,12 +354,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -426,7 +425,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -492,16 +492,19 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &secret_key, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -538,12 +541,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -588,21 +593,25 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &secret_key, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -641,12 +650,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -701,7 +712,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -741,15 +753,15 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &secret_key))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + assert!(validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers).unwrap()); } } @@ -775,12 +787,13 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -820,21 +833,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &secret_key, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + !validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -853,7 +870,7 @@ mod tests { tx_env.store_wasm_code(vp_code); // hardcoded hash of VP_ALWAYS_TRUE_WASM - tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); + tx_env.init_parameters(None, None, Some(vec!["C668E38915226859099F102F3BD98115764DB7530504EA3FE0BB91A098DFEC40".to_string()])); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -864,21 +881,26 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_code(Code::from_hash(vp_hash)); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &secret_key, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } diff --git a/wasm/wasm_source/src/vp_masp.rs b/wasm/wasm_source/src/vp_masp.rs index 958501c96c..b3ccda6286 100644 --- a/wasm/wasm_source/src/vp_masp.rs +++ b/wasm/wasm_source/src/vp_masp.rs @@ -1,7 +1,6 @@ use std::cmp::Ordering; use masp_primitives::asset_type::AssetType; -use masp_primitives::legacy::TransparentAddress::{PublicKey, Script}; use masp_primitives::transaction::components::{Amount, TxOut}; /// Multi-asset shielded pool VP. use namada_vp_prelude::address::masp; @@ -71,7 +70,7 @@ fn convert_amount( #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -79,22 +78,30 @@ fn validate_tx( debug_log!( "vp_masp called with {} bytes data, address {}, keys_changed {:?}, \ verifiers {:?}", - tx_data.len(), + tx_data.data().as_ref().map(|x| x.len()).unwrap_or(0), addr, keys_changed, verifiers, ); - let signed = SignedTxData::try_from_slice(&tx_data[..]).unwrap(); - // Also get the data as bytes for the VM. - let data = signed.data.as_ref().unwrap().clone(); + let signed = tx_data; let transfer = - token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); - - if let Some(shielded_tx) = transfer.shielded { + token::Transfer::try_from_slice(&signed.data().unwrap()[..]).unwrap(); + + let shielded = transfer + .shielded + .as_ref() + .map(|hash| { + signed + .get_section(hash) + .and_then(Section::masp_tx) + .ok_or_err_msg("unable to find shielded section") + }) + .transpose()?; + if let Some(shielded_tx) = shielded { let mut transparent_tx_pool = Amount::zero(); // The Sapling value balance adds to the transparent tx pool - transparent_tx_pool += shielded_tx.value_balance.clone(); + transparent_tx_pool += shielded_tx.sapling_value_balance(); if transfer.source != masp() { // Handle transparent input @@ -116,13 +123,15 @@ fn validate_tx( // 2. the transparent transaction value pool's amount must equal the // containing wrapper transaction's fee amount // Satisfies 1. - if !shielded_tx.vin.is_empty() { - debug_log!( - "Transparent input to a transaction from the masp must be \ - 0 but is {}", - shielded_tx.vin.len() - ); - return reject(); + if let Some(transp_bundle) = shielded_tx.transparent_bundle() { + if !transp_bundle.vin.is_empty() { + debug_log!( + "Transparent input to a transaction from the masp \ + must be 0 but is {}", + transp_bundle.vin.len() + ); + return reject(); + } } } @@ -136,16 +145,20 @@ fn validate_tx( // 4. Public key must be the hash of the target // Satisfies 1. - if shielded_tx.vout.len() != 1 { + let transp_bundle = + shielded_tx.transparent_bundle().ok_or_err_msg( + "Expected transparent outputs in unshielding transaction", + )?; + if transp_bundle.vout.len() != 1 { debug_log!( - "Transparent output to a transaction to the masp must be \ - 1 but is {}", - shielded_tx.vin.len() + "Transparent output to a transaction from the masp must \ + be 1 but is {}", + transp_bundle.vin.len() ); return reject(); } - let out: &TxOut = &shielded_tx.vout[0]; + let out: &TxOut = &transp_bundle.vout[0]; let expected_asset_type: AssetType = asset_type_from_epoched_address( @@ -155,7 +168,10 @@ fn validate_tx( // Satisfies 2. and 3. if !(valid_asset_type(&expected_asset_type, &out.asset_type) - && valid_transfer_amount(out.value, u64::from(transfer.amount))) + && valid_transfer_amount( + out.value as u64, + u64::from(transfer.amount), + )) { return reject(); } @@ -170,38 +186,34 @@ fn validate_tx( transparent_tx_pool -= transp_amt; // Satisfies 4. - match out.script_pubkey.address() { - None | Some(Script(_)) => {} - Some(PublicKey(pub_bytes)) => { - let target_enc = transfer - .target - .try_to_vec() - .expect("target address encoding"); - - let hash = - Ripemd160::digest(sha256(&target_enc).as_slice()); - - if <[u8; 20]>::from(hash) != pub_bytes { - debug_log!( - "the public key of the output account does not \ - match the transfer target" - ); - return reject(); - } - } + let target_enc = transfer + .target + .try_to_vec() + .expect("target address encoding"); + + let hash = Ripemd160::digest(sha256(&target_enc).as_slice()); + + if <[u8; 20]>::from(hash) != out.address.0 { + debug_log!( + "the public key of the output account does not match the \ + transfer target" + ); + return reject(); } } else { // Handle shielded output // The following boundary conditions must be satisfied // 1. Zero transparent output // Satisfies 1. - if !shielded_tx.vout.is_empty() { - debug_log!( - "Transparent output to a transaction from the masp must \ - be 0 but is {}", - shielded_tx.vin.len() - ); - return reject(); + if let Some(transp_bundle) = shielded_tx.transparent_bundle() { + if !transp_bundle.vout.is_empty() { + debug_log!( + "Transparent output to a transaction from the masp \ + must be 0 but is {}", + transp_bundle.vin.len() + ); + return reject(); + } } } @@ -218,8 +230,9 @@ fn validate_tx( } _ => {} } + // Do the expensive proof verification in the VM at the end. + ctx.verify_masp(shielded_tx.try_to_vec().unwrap()) + } else { + reject() } - - // Do the expensive proof verification in the VM at the end. - ctx.verify_masp(data) } diff --git a/wasm/wasm_source/src/vp_testnet_faucet.rs b/wasm/wasm_source/src/vp_testnet_faucet.rs index e7067e664f..430cb55b21 100644 --- a/wasm/wasm_source/src/vp_testnet_faucet.rs +++ b/wasm/wasm_source/src/vp_testnet_faucet.rs @@ -12,7 +12,7 @@ use once_cell::unsync::Lazy; #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -25,23 +25,14 @@ fn validate_tx( verifiers ); - let signed_tx_data = - Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); - - let valid_sig = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => { - matches!( - ctx.verify_tx_signature(&pk, &signed_tx_data.sig), - Ok(true) - ) - } - _ => false, - } + let valid_sig = Lazy::new(|| { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => tx_data + .verify_signature(&pk, tx_data.data_sechash()) + .is_ok(), + _ => false, } - _ => false, }); if !is_valid_tx(ctx, &tx_data)? { @@ -109,6 +100,8 @@ fn validate_tx( #[cfg(test)] mod tests { use address::testing::arb_non_internal_address; + use namada::proto::{Data, Signature}; + use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; @@ -116,7 +109,7 @@ mod tests { use namada_tests::vp::vp_host_env::storage::Key; use namada_tests::vp::*; use namada_tx_prelude::{StorageWrite, TxEnv}; - use namada_vp_prelude::key::{RefTo, SigScheme}; + use namada_vp_prelude::key::RefTo; use proptest::prelude::*; use storage::testing::arb_account_storage_key_no_vp; @@ -128,7 +121,8 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -171,12 +165,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -207,12 +203,13 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -247,21 +244,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -310,11 +311,12 @@ mod tests { // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None).unwrap(); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None, &None).unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -352,13 +354,6 @@ mod tests { let challenge = testnet_pow::Challenge::new(&mut tx_env.wl_storage, &vp_owner, target.clone()).unwrap(); let solution = challenge.solve(); let solution_bytes = solution.try_to_vec().unwrap(); - // The signature itself doesn't matter and is not being checked in this - // test, it's just used to construct `SignedTxData` - let sig = key::common::SigScheme::sign(&target_key, &solution_bytes); - let signed_solution = SignedTxData { - data: Some(solution_bytes), - sig, - }; // Initialize VP environment from a transaction vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { @@ -367,13 +362,15 @@ mod tests { let valid = solution.validate(tx::ctx(), address, target.clone()).unwrap(); assert!(valid); // Apply transfer in a transaction - tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None).unwrap(); + tx_host_env::token::transfer(tx::ctx(), address, &target, &token, None, amount, &None, &None, &None).unwrap(); }); let mut vp_env = vp_host_env::take(); // This is set by the protocol when the wrapper tx has a valid PoW vp_env.has_valid_pow = true; - let tx_data: Vec = signed_solution.try_to_vec().unwrap(); + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(solution_bytes)); + tx_data.add_section(Section::Signature(Signature::new(tx_data.data_sechash(), &target_key))); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -418,15 +415,16 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + assert!(validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers).unwrap()); } } } diff --git a/wasm/wasm_source/src/vp_token.rs b/wasm/wasm_source/src/vp_token.rs index cc7aee3311..8ccc2c9053 100644 --- a/wasm/wasm_source/src/vp_token.rs +++ b/wasm/wasm_source/src/vp_token.rs @@ -10,7 +10,7 @@ use namada_vp_prelude::{storage, token, *}; #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -113,6 +113,8 @@ fn token_checks( mod tests { // Use this as `#[test]` annotation to enable logging use namada::core::ledger::storage_api::token; + use namada::proto::Data; + use namada::types::transaction::TxType; use namada_tests::log::test; use namada_tests::tx::{self, TestTxEnv}; use namada_tests::vp::*; @@ -150,7 +152,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers = vp_env.get_verifiers(); @@ -203,7 +206,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers = vp_env.get_verifiers(); @@ -255,7 +259,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers = vp_env.get_verifiers(); diff --git a/wasm/wasm_source/src/vp_user.rs b/wasm/wasm_source/src/vp_user.rs index 0f5df00577..896d7ac818 100644 --- a/wasm/wasm_source/src/vp_user.rs +++ b/wasm/wasm_source/src/vp_user.rs @@ -52,7 +52,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -64,23 +64,14 @@ fn validate_tx( verifiers ); - let signed_tx_data = - Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); - - let valid_sig = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => { - matches!( - ctx.verify_tx_signature(&pk, &signed_tx_data.sig), - Ok(true) - ) - } - _ => false, - } + let valid_sig = Lazy::new(|| { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => tx_data + .verify_signature(&pk, tx_data.data_sechash()) + .is_ok(), + _ => false, } - _ => false, }); if !is_valid_tx(ctx, &tx_data)? { @@ -193,7 +184,9 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::proto::{Code, Data, Signature}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; @@ -211,7 +204,8 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -254,12 +248,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -300,12 +296,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -350,21 +348,26 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -422,7 +425,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -488,16 +492,20 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &secret_key, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -534,12 +542,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -594,7 +604,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -636,15 +647,16 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + assert!(validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers).unwrap()); } } @@ -668,12 +680,13 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -709,21 +722,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -752,21 +769,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + !validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -796,21 +817,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -844,21 +869,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + !validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -877,7 +906,7 @@ mod tests { tx_env.store_wasm_code(vp_code); // hardcoded hash of VP_ALWAYS_TRUE_WASM - tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); + tx_env.init_parameters(None, None, Some(vec!["C668E38915226859099F102F3BD98115764DB7530504EA3FE0BB91A098DFEC40".to_string()])); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -888,21 +917,26 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_code(Code::from_hash(vp_hash)); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } diff --git a/wasm/wasm_source/src/vp_validator.rs b/wasm/wasm_source/src/vp_validator.rs index d57d09b304..b5b08c514f 100644 --- a/wasm/wasm_source/src/vp_validator.rs +++ b/wasm/wasm_source/src/vp_validator.rs @@ -52,7 +52,7 @@ impl<'a> From<&'a storage::Key> for KeyType<'a> { #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, addr: Address, keys_changed: BTreeSet, verifiers: BTreeSet
, @@ -64,23 +64,14 @@ fn validate_tx( verifiers ); - let signed_tx_data = - Lazy::new(|| SignedTxData::try_from_slice(&tx_data[..])); - - let valid_sig = Lazy::new(|| match &*signed_tx_data { - Ok(signed_tx_data) => { - let pk = key::get(ctx, &addr); - match pk { - Ok(Some(pk)) => { - matches!( - ctx.verify_tx_signature(&pk, &signed_tx_data.sig), - Ok(true) - ) - } - _ => false, - } + let valid_sig = Lazy::new(|| { + let pk = key::get(ctx, &addr); + match pk { + Ok(Some(pk)) => tx_data + .verify_signature(&pk, tx_data.data_sechash()) + .is_ok(), + _ => false, } - _ => false, }); if !is_valid_tx(ctx, &tx_data)? { @@ -201,7 +192,9 @@ fn validate_tx( mod tests { use address::testing::arb_non_internal_address; use namada::ledger::pos::{GenesisValidator, PosParams}; + use namada::proto::{Code, Data, Signature}; use namada::types::storage::Epoch; + use namada::types::transaction::TxType; use namada_test_utils::TestWasms; // Use this as `#[test]` annotation to enable logging use namada_tests::log::test; @@ -220,7 +213,8 @@ mod tests { /// Test that no-op transaction (i.e. no storage modifications) accepted. #[test] fn test_no_op_transaction() { - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let addr: Address = address::testing::established_address_1(); let keys_changed: BTreeSet = BTreeSet::default(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -263,12 +257,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -309,12 +305,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -359,21 +357,26 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -437,7 +440,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -509,16 +513,20 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&secret_key); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &secret_key, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -555,12 +563,14 @@ mod tests { amount, &None, &None, + &None, ) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -615,7 +625,8 @@ mod tests { }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -657,15 +668,16 @@ mod tests { }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new(tx.data_sechash(), &keypair))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); - assert!(validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers).unwrap()); + assert!(validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers).unwrap()); } } @@ -689,12 +701,13 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let vp_env = vp_host_env::take(); - let tx_data: Vec = vec![]; + let mut tx_data = Tx::new(TxType::Raw); + tx_data.set_data(Data::new(vec![])); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); @@ -730,21 +743,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -773,21 +790,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + !validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -817,21 +838,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -865,21 +890,25 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - !validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + !validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } @@ -898,7 +927,7 @@ mod tests { tx_env.store_wasm_code(vp_code); // hardcoded hash of VP_ALWAYS_TRUE_WASM - tx_env.init_parameters(None, None, Some(vec!["E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855".to_string()])); + tx_env.init_parameters(None, None, Some(vec!["C668E38915226859099F102F3BD98115764DB7530504EA3FE0BB91A098DFEC40".to_string()])); // Spawn the accounts to be able to modify their storage tx_env.spawn_accounts([&vp_owner]); @@ -909,21 +938,26 @@ mod tests { vp_host_env::init_from_tx(vp_owner.clone(), tx_env, |address| { // Update VP in a transaction tx::ctx() - .update_validity_predicate(address, &vp_hash) + .update_validity_predicate(address, vp_hash) .unwrap(); }); let mut vp_env = vp_host_env::take(); - let tx = vp_env.tx.clone(); - let signed_tx = tx.sign(&keypair); - let tx_data: Vec = signed_tx.data.as_ref().cloned().unwrap(); - vp_env.tx = signed_tx; + let mut tx = vp_env.tx.clone(); + tx.set_code(Code::from_hash(vp_hash)); + tx.set_data(Data::new(vec![])); + tx.add_section(Section::Signature(Signature::new( + tx.data_sechash(), + &keypair, + ))); + let signed_tx = tx.clone(); + vp_env.tx = signed_tx.clone(); let keys_changed: BTreeSet = vp_env.all_touched_storage_keys(); let verifiers: BTreeSet
= BTreeSet::default(); vp_host_env::set(vp_env); assert!( - validate_tx(&CTX, tx_data, vp_owner, keys_changed, verifiers) + validate_tx(&CTX, signed_tx, vp_owner, keys_changed, verifiers) .unwrap() ); } diff --git a/wasm_for_tests/tx_memory_limit.wasm b/wasm_for_tests/tx_memory_limit.wasm index 330e342726..f074b51206 100755 Binary files a/wasm_for_tests/tx_memory_limit.wasm and b/wasm_for_tests/tx_memory_limit.wasm differ diff --git a/wasm_for_tests/tx_mint_tokens.wasm b/wasm_for_tests/tx_mint_tokens.wasm index 0a0d485325..61f15f8f69 100755 Binary files a/wasm_for_tests/tx_mint_tokens.wasm and b/wasm_for_tests/tx_mint_tokens.wasm differ diff --git a/wasm_for_tests/tx_no_op.wasm b/wasm_for_tests/tx_no_op.wasm index 105a68cd1b..99b0963fe9 100755 Binary files a/wasm_for_tests/tx_no_op.wasm and b/wasm_for_tests/tx_no_op.wasm differ diff --git a/wasm_for_tests/tx_proposal_code.wasm b/wasm_for_tests/tx_proposal_code.wasm index ec1656dbab..a948908ddd 100755 Binary files a/wasm_for_tests/tx_proposal_code.wasm and b/wasm_for_tests/tx_proposal_code.wasm differ diff --git a/wasm_for_tests/tx_read_storage_key.wasm b/wasm_for_tests/tx_read_storage_key.wasm index 9a511a0cc7..6816937d73 100755 Binary files a/wasm_for_tests/tx_read_storage_key.wasm and b/wasm_for_tests/tx_read_storage_key.wasm differ diff --git a/wasm_for_tests/tx_write.wasm b/wasm_for_tests/tx_write.wasm index ae2cb1c6e3..c05f04abf9 100755 Binary files a/wasm_for_tests/tx_write.wasm and b/wasm_for_tests/tx_write.wasm differ diff --git a/wasm_for_tests/tx_write_storage_key.wasm b/wasm_for_tests/tx_write_storage_key.wasm new file mode 100755 index 0000000000..5d600d185f Binary files /dev/null and b/wasm_for_tests/tx_write_storage_key.wasm differ diff --git a/wasm_for_tests/vp_always_false.wasm b/wasm_for_tests/vp_always_false.wasm index e770000aee..4876bfbfce 100755 Binary files a/wasm_for_tests/vp_always_false.wasm and b/wasm_for_tests/vp_always_false.wasm differ diff --git a/wasm_for_tests/vp_always_true.wasm b/wasm_for_tests/vp_always_true.wasm index 0ebbab310c..9a6baddbfa 100755 Binary files a/wasm_for_tests/vp_always_true.wasm and b/wasm_for_tests/vp_always_true.wasm differ diff --git a/wasm_for_tests/vp_eval.wasm b/wasm_for_tests/vp_eval.wasm index 08456c8fbe..2234ea42a0 100755 Binary files a/wasm_for_tests/vp_eval.wasm and b/wasm_for_tests/vp_eval.wasm differ diff --git a/wasm_for_tests/vp_memory_limit.wasm b/wasm_for_tests/vp_memory_limit.wasm index 19e8950beb..5894551c49 100755 Binary files a/wasm_for_tests/vp_memory_limit.wasm and b/wasm_for_tests/vp_memory_limit.wasm differ diff --git a/wasm_for_tests/vp_read_storage_key.wasm b/wasm_for_tests/vp_read_storage_key.wasm index e808459a13..186ffa11b9 100755 Binary files a/wasm_for_tests/vp_read_storage_key.wasm and b/wasm_for_tests/vp_read_storage_key.wasm differ diff --git a/wasm_for_tests/wasm_source/Cargo.lock b/wasm_for_tests/wasm_source/Cargo.lock index 2225bce9db..6f6cabf5dc 100644 --- a/wasm_for_tests/wasm_source/Cargo.lock +++ b/wasm_for_tests/wasm_source/Cargo.lock @@ -19,10 +19,11 @@ checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aead" -version = "0.4.3" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b613b8e1e3cf911a086f53f03bf286f52fd7a7258e4fa606f0ef220d39d8877" +checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0" dependencies = [ + "crypto-common", "generic-array", ] @@ -33,7 +34,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9e8b47f52ea9bae42228d07ec09eb676433d7c4ed1ebdf0f1d1c29ed446f1ab8" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", "opaque-debug", ] @@ -486,20 +487,31 @@ dependencies = [ "lazy_static", "log", "num_cpus", - "pairing", + "pairing 0.21.0", "rand_core 0.6.4", "rayon", "subtle", ] [[package]] -name = "bigint" -version = "4.4.3" +name = "bellman" +version = "0.13.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e8c8a600052b52482eff2cf4d810e462fdff1f656ac1ecb6232132a1ed7def" +checksum = "a4dd656ef4fdf7debb6d87d4dd92642fcbcdb78cbf6600c13e25c87e4d1a3807" dependencies = [ + "bitvec 1.0.1", + "blake2s_simd 1.0.1", "byteorder", - "crunchy 0.1.6", + "crossbeam-channel 0.5.8", + "ff 0.12.1", + "group 0.12.1", + "lazy_static", + "log", + "num_cpus", + "pairing 0.22.0", + "rand_core 0.6.4", + "rayon", + "subtle", ] [[package]] @@ -557,7 +569,7 @@ checksum = "0694ea59225b0c5f3cb405ff3f670e4828358ed26aec49dc352f730f0cb1a8a3" dependencies = [ "bech32 0.9.1", "bitcoin_hashes", - "secp256k1 0.24.3", + "secp256k1", "serde", ] @@ -609,17 +621,6 @@ dependencies = [ "digest 0.10.6", ] -[[package]] -name = "blake2b_simd" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" -dependencies = [ - "arrayref", - "arrayvec 0.5.2", - "constant_time_eq 0.1.5", -] - [[package]] name = "blake2b_simd" version = "1.0.1" @@ -692,7 +693,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb03d1bed155d89dce0f845b7899b18a9a163e148fd004e1c28421a783e2d8e" dependencies = [ "block-padding", - "cipher", + "cipher 0.3.0", ] [[package]] @@ -724,7 +725,20 @@ checksum = "a829c821999c06be34de314eaeb7dd1b42be38661178bc26ad47a4eacebdb0f9" dependencies = [ "ff 0.11.1", "group 0.11.0", - "pairing", + "pairing 0.21.0", + "rand_core 0.6.4", + "subtle", +] + +[[package]] +name = "bls12_381" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3c196a77437e7cc2fb515ce413a6401291578b5afc8ecb29a3c7ab957f05941" +dependencies = [ + "ff 0.12.1", + "group 0.12.1", + "pairing 0.22.0", "rand_core 0.6.4", "subtle", ] @@ -887,20 +901,30 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c80e5460aa66fe3b91d40bcbdab953a597b60053e34d684ac6903f863b680a6" dependencies = [ "cfg-if 1.0.0", - "cipher", + "cipher 0.3.0", "cpufeatures", - "zeroize", ] [[package]] -name = "chacha20poly1305" +name = "chacha20" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a18446b09be63d457bbec447509e85f662f32952b035ce892290396bc0b0cff5" +checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818" +dependencies = [ + "cfg-if 1.0.0", + "cipher 0.4.4", + "cpufeatures", +] + +[[package]] +name = "chacha20poly1305" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35" dependencies = [ "aead", - "chacha20", - "cipher", + "chacha20 0.9.1", + "cipher 0.4.4", "poly1305", "zeroize", ] @@ -926,6 +950,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", + "zeroize", +] + [[package]] name = "circular-queue" version = "0.2.6" @@ -1146,12 +1181,6 @@ dependencies = [ "cfg-if 1.0.0", ] -[[package]] -name = "crunchy" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f4a431c5c9f662e1200b7c7f02c34e91361150e382089a8f2dec3ba680cbda" - [[package]] name = "crunchy" version = "0.2.2" @@ -1200,21 +1229,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "crypto_api" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f855e87e75a4799e18b8529178adcde6fd4f97c1449ff4821e747ff728bb102" - -[[package]] -name = "crypto_api_chachapoly" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d930b6a026ce9d358a17f9c9046c55d90b14bb847f36b6ebb6b19365d4feffb8" -dependencies = [ - "crypto_api", -] - [[package]] name = "ct-codecs" version = "1.1.1" @@ -1578,15 +1592,6 @@ dependencies = [ "syn 2.0.15", ] -[[package]] -name = "equihash" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "blake2b_simd 1.0.1", - "byteorder", -] - [[package]] name = "erased-serde" version = "0.3.25" @@ -1672,7 +1677,7 @@ dependencies = [ "ark-std", "bincode", "blake2", - "blake2b_simd 1.0.1", + "blake2b_simd", "borsh", "digest 0.10.6", "ed25519-dalek", @@ -1724,6 +1729,7 @@ version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d013fc25338cc558c5c2cfbad646908fb23591e2404481826742b651c9af7160" dependencies = [ + "bitvec 1.0.1", "rand_core 0.6.4", "subtle", ] @@ -1778,7 +1784,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd910db5f9ca4dc3116f8c46367825807aa2b942f72565f16b4be0b208a00a9e" dependencies = [ "block-modes", - "cipher", + "cipher 0.3.0", "libm", "num-bigint", "num-integer", @@ -1988,6 +1994,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5dfbfb3a6cfbd390d5c9564ab283a0349b9b9fcd46a706c1eb10e0db70bfbac7" dependencies = [ "ff 0.12.1", + "memuse", "rand_core 0.6.4", "subtle", ] @@ -2004,8 +2011,8 @@ dependencies = [ "ark-poly", "ark-serialize", "ark-std", - "blake2b_simd 1.0.1", - "chacha20", + "blake2b_simd", + "chacha20 0.8.2", "hex", "itertools", "miracl_core", @@ -2061,20 +2068,6 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" -[[package]] -name = "halo2" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f186b85ed81082fb1cf59d52b0111f02915e89a4ac61d292b38d075e570f3a9" -dependencies = [ - "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", - "pasta_curves", - "rand 0.8.5", - "rayon", -] - [[package]] name = "hashbrown" version = "0.11.2" @@ -2438,7 +2431,7 @@ dependencies = [ "regex", "retry", "ripemd", - "secp256k1 0.24.3", + "secp256k1", "semver 1.0.17", "serde", "serde_derive", @@ -2554,9 +2547,9 @@ dependencies = [ [[package]] name = "incrementalmerkletree" -version = "0.2.0" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "186fd3ab92aeac865d4b80b410de9a7b341d31ba8281373caed0b6d17b2b5e96" +checksum = "d5ad43a3f5795945459d577f6589cf62a476e92c79b75e70cd954364e14ce17b" dependencies = [ "serde", ] @@ -2587,6 +2580,15 @@ dependencies = [ "serde", ] +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + [[package]] name = "instant" version = "0.1.12" @@ -2636,14 +2638,14 @@ dependencies = [ [[package]] name = "jubjub" -version = "0.8.0" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7baec19d4e83f9145d4891178101a604565edff9645770fc979804138b04c" +checksum = "a575df5f985fe1cd5b2b05664ff6accfc46559032b954529fd225a2168d27b0f" dependencies = [ - "bitvec 0.22.3", - "bls12_381", - "ff 0.11.1", - "group 0.11.0", + "bitvec 1.0.1", + "bls12_381 0.7.1", + "ff 0.12.1", + "group 0.12.1", "rand_core 0.6.4", "subtle", ] @@ -2735,7 +2737,7 @@ name = "libsecp256k1-core" version = "0.3.0" source = "git+https://github.com/heliaxdev/libsecp256k1?rev=bbb3bd44a49db361f21d9db80f9a087c194c0ae9#bbb3bd44a49db361f21d9db80f9a087c194c0ae9" dependencies = [ - "crunchy 0.2.2", + "crunchy", "digest 0.9.0", "subtle", ] @@ -2812,58 +2814,67 @@ dependencies = [ "libc", ] +[[package]] +name = "masp_note_encryption" +version = "0.2.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" +dependencies = [ + "borsh", + "chacha20 0.9.1", + "chacha20poly1305", + "cipher 0.4.4", + "rand_core 0.6.4", + "subtle", +] + [[package]] name = "masp_primitives" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ "aes", "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", + "bitvec 1.0.1", + "blake2b_simd", "blake2s_simd 1.0.1", - "bls12_381", + "bls12_381 0.7.1", "borsh", "byteorder", - "chacha20poly1305", - "crypto_api_chachapoly", - "ff 0.11.1", + "ff 0.12.1", "fpe", - "group 0.11.0", + "group 0.12.1", "hex", "incrementalmerkletree", "jubjub", "lazy_static", + "masp_note_encryption", + "memuse", + "nonempty", "rand 0.8.5", "rand_core 0.6.4", - "ripemd160", - "secp256k1 0.20.3", - "serde", "sha2 0.9.9", "subtle", "zcash_encoding", - "zcash_primitives", ] [[package]] name = "masp_proofs" -version = "0.5.0" -source = "git+https://github.com/anoma/masp?rev=bee40fc465f6afbd10558d12fe96eb1742eee45c#bee40fc465f6afbd10558d12fe96eb1742eee45c" +version = "0.9.0" +source = "git+https://github.com/anoma/masp?rev=cfea8c95d3f73077ca3e25380fd27e5b46e828fd#cfea8c95d3f73077ca3e25380fd27e5b46e828fd" dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", + "bellman 0.13.1", + "blake2b_simd", + "bls12_381 0.7.1", "directories", - "ff 0.11.1", - "group 0.11.0", + "getrandom 0.2.9", + "group 0.12.1", "itertools", "jubjub", "lazy_static", "masp_primitives", "rand_core 0.6.4", - "zcash_primitives", - "zcash_proofs", + "redjubjub", + "tracing", ] [[package]] @@ -2941,9 +2952,6 @@ name = "memuse" version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2145869435ace5ea6ea3d35f59be559317ec9a0d04e1812d5f185a87b6d36f1a" -dependencies = [ - "nonempty", -] [[package]] name = "merlin" @@ -3031,9 +3039,9 @@ version = "0.16.0" dependencies = [ "async-std", "async-trait", - "bellman", + "bellman 0.11.2", "bimap", - "bls12_381", + "bls12_381 0.6.1", "borsh", "circular-queue", "clru", @@ -3056,6 +3064,7 @@ dependencies = [ "rand 0.8.5", "rand_core 0.6.4", "rayon", + "ripemd", "rust_decimal", "rust_decimal_macros", "serde", @@ -3087,7 +3096,7 @@ dependencies = [ "ark-ec", "ark-serialize", "bech32 0.8.1", - "bellman", + "bellman 0.11.2", "borsh", "chrono", "data-encoding", @@ -3212,7 +3221,6 @@ dependencies = [ "borsh", "hex", "masp_primitives", - "masp_proofs", "namada_core", ] @@ -3387,33 +3395,6 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" -[[package]] -name = "orchard" -version = "0.1.0-beta.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e31f03b6d0aee6d993cac35388b818e04f076ded0ab284979e4d7cd5a8b3c2be" -dependencies = [ - "aes", - "arrayvec 0.7.2", - "bigint", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "ff 0.11.1", - "fpe", - "group 0.11.0", - "halo2", - "incrementalmerkletree", - "lazy_static", - "memuse", - "nonempty", - "pasta_curves", - "rand 0.8.5", - "reddsa", - "serde", - "subtle", - "zcash_note_encryption 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "orion" version = "0.16.1" @@ -3435,6 +3416,15 @@ dependencies = [ "group 0.11.0", ] +[[package]] +name = "pairing" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135590d8bdba2b31346f9cd1fb2a912329f5135e832a4f422942eb6ead8b6b3b" +dependencies = [ + "group 0.12.1", +] + [[package]] name = "parity-scale-codec" version = "3.5.0" @@ -3507,21 +3497,6 @@ dependencies = [ "subtle", ] -[[package]] -name = "pasta_curves" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d647d91972bad78120fd61e06b225fcda117805c9bbf17676b51bd03a251278b" -dependencies = [ - "blake2b_simd 0.5.11", - "ff 0.11.1", - "group 0.11.0", - "lazy_static", - "rand 0.8.5", - "static_assertions", - "subtle", -] - [[package]] name = "paste" version = "1.0.12" @@ -3650,9 +3625,9 @@ dependencies = [ [[package]] name = "poly1305" -version = "0.7.2" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "048aeb476be11a4b6ca432ca569e375810de9294ae78f4774e78ea98a9246ede" +checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf" dependencies = [ "cpufeatures", "opaque-debug", @@ -4010,17 +3985,15 @@ dependencies = [ ] [[package]] -name = "reddsa" -version = "0.1.0" +name = "redjubjub" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a90e2c94bca3445cae0d55dff7370e29c24885d2403a1665ce19c106c79455e6" +checksum = "6039ff156887caf92df308cbaccdc058c9d3155a913da046add6e48c4cdbd91d" dependencies = [ - "blake2b_simd 0.5.11", + "blake2b_simd", "byteorder", "digest 0.9.0", - "group 0.11.0", "jubjub", - "pasta_curves", "rand_core 0.6.4", "serde", "thiserror", @@ -4470,15 +4443,6 @@ dependencies = [ "zeroize", ] -[[package]] -name = "secp256k1" -version = "0.20.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97d03ceae636d0fed5bae6a7f4f664354c5f4fcedf6eef053fef17e49f837d0a" -dependencies = [ - "secp256k1-sys 0.4.2", -] - [[package]] name = "secp256k1" version = "0.24.3" @@ -4487,19 +4451,10 @@ checksum = "6b1629c9c557ef9b293568b338dddfc8208c98a18c59d722a9d53f859d9c9b62" dependencies = [ "bitcoin_hashes", "rand 0.8.5", - "secp256k1-sys 0.6.1", + "secp256k1-sys", "serde", ] -[[package]] -name = "secp256k1-sys" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "957da2573cde917463ece3570eab4a0b3f19de6f1646cde62e6fd3868f566036" -dependencies = [ - "cc", -] - [[package]] name = "secp256k1-sys" version = "0.6.1" @@ -5256,7 +5211,7 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "crunchy 0.2.2", + "crunchy", ] [[package]] @@ -5577,7 +5532,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76f64bba2c53b04fcab63c01a7d7427eadc821e3bc48c34dc9ba29c501164b52" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", "static_assertions", ] @@ -5632,11 +5587,11 @@ checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "universal-hash" -version = "0.4.1" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f214e8f697e925001e66ec2c6e37a4ef93f0f78c2eed7814394e10c62025b05" +checksum = "7d3160b73c9a19f7e2939a2fdad446c57c1bbbbf4d919d3213ff1267a580d8b5" dependencies = [ - "generic-array", + "crypto-common", "subtle", ] @@ -6358,83 +6313,10 @@ dependencies = [ [[package]] name = "zcash_encoding" version = "0.0.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "byteorder", - "nonempty", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33f84ae538f05a8ac74c82527f06b77045ed9553a0871d9db036166a4c344e3a" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_note_encryption" -version = "0.1.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "chacha20", - "chacha20poly1305", - "rand_core 0.6.4", - "subtle", -] - -[[package]] -name = "zcash_primitives" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" +source = "git+https://github.com/zcash/librustzcash?rev=43c18d0#43c18d000fcbe45363b2d53585d5102841eff99e" dependencies = [ - "aes", - "bip0039", - "bitvec 0.22.3", - "blake2b_simd 1.0.1", - "blake2s_simd 1.0.1", - "bls12_381", "byteorder", - "chacha20poly1305", - "equihash", - "ff 0.11.1", - "fpe", - "group 0.11.0", - "hex", - "incrementalmerkletree", - "jubjub", - "lazy_static", - "memuse", "nonempty", - "orchard", - "rand 0.8.5", - "rand_core 0.6.4", - "sha2 0.9.9", - "subtle", - "zcash_encoding", - "zcash_note_encryption 0.1.0 (git+https://github.com/zcash/librustzcash/?rev=2425a08)", -] - -[[package]] -name = "zcash_proofs" -version = "0.5.0" -source = "git+https://github.com/zcash/librustzcash/?rev=2425a08#2425a0869098e3b0588ccd73c42716bcf418612c" -dependencies = [ - "bellman", - "blake2b_simd 1.0.1", - "bls12_381", - "byteorder", - "directories", - "ff 0.11.1", - "group 0.11.0", - "jubjub", - "lazy_static", - "rand_core 0.6.4", - "zcash_primitives", ] [[package]] diff --git a/wasm_for_tests/wasm_source/src/lib.rs b/wasm_for_tests/wasm_source/src/lib.rs index f271acc97a..a37039e9c4 100644 --- a/wasm_for_tests/wasm_source/src/lib.rs +++ b/wasm_for_tests/wasm_source/src/lib.rs @@ -4,7 +4,7 @@ pub mod main { use namada_tx_prelude::*; #[transaction] - fn apply_tx(_ctx: &mut Ctx, _tx_data: Vec) -> TxResult { + fn apply_tx(_ctx: &mut Ctx, _tx_data: Tx) -> TxResult { Ok(()) } } @@ -15,8 +15,8 @@ pub mod main { use namada_tx_prelude::*; #[transaction] - fn apply_tx(_ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let len = usize::try_from_slice(&tx_data[..]).unwrap(); + fn apply_tx(_ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away @@ -31,7 +31,7 @@ pub mod main { use namada_tx_prelude::*; #[transaction] - fn apply_tx(ctx: &mut Ctx, _tx_data: Vec) -> TxResult { + fn apply_tx(ctx: &mut Ctx, _tx_data: Tx) -> TxResult { // governance let target_key = gov_storage::get_min_proposal_grace_epoch_key(); ctx.write(&target_key, 9_u64)?; @@ -49,9 +49,9 @@ pub mod main { use namada_tx_prelude::*; #[transaction] - fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { + fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = storage::Key::try_from_slice(&tx_data[..]).unwrap(); + let key = storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); log_string(format!("key {}", key)); let _result: Vec = ctx.read(&key)?.unwrap(); Ok(()) @@ -64,8 +64,7 @@ pub mod main { use borsh::BorshDeserialize; use namada_test_utils::tx_data::TxWriteData; use namada_tx_prelude::{ - log_string, transaction, Ctx, ResultExt, SignedTxData, StorageRead, - StorageWrite, TxResult, + log_string, transaction, Ctx, StorageRead, StorageWrite, TxResult, Tx, }; const TX_NAME: &str = "tx_write"; @@ -85,10 +84,9 @@ pub mod main { } #[transaction] - fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; - let data = match signed.data { + fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; + let data = match signed.data() { Some(data) => { log(&format!("got data ({} bytes)", data.len())); data @@ -134,11 +132,10 @@ pub mod main { use namada_tx_prelude::*; #[transaction] - fn apply_tx(ctx: &mut Ctx, tx_data: Vec) -> TxResult { - let signed = SignedTxData::try_from_slice(&tx_data[..]) - .wrap_err("failed to decode SignedTxData")?; + fn apply_tx(ctx: &mut Ctx, tx_data: Tx) -> TxResult { + let signed = tx_data; let transfer = - token::Transfer::try_from_slice(&signed.data.unwrap()[..]).unwrap(); + token::Transfer::try_from_slice(&signed.data().unwrap()[..]).unwrap(); log_string(format!("apply_tx called to mint tokens: {:#?}", transfer)); let token::Transfer { source: _, @@ -166,7 +163,7 @@ pub mod main { #[validity_predicate] fn validate_tx( _ctx: &Ctx, - _tx_data: Vec, + _tx_data: Tx, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, @@ -183,7 +180,7 @@ pub mod main { #[validity_predicate] fn validate_tx( _ctx: &Ctx, - _tx_data: Vec, + _tx_data: Tx, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, @@ -201,14 +198,14 @@ pub mod main { #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, ) -> VpResult { use validity_predicate::EvalVp; let EvalVp { vp_code_hash, input }: EvalVp = - EvalVp::try_from_slice(&tx_data[..]).unwrap(); + EvalVp::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); ctx.eval(vp_code_hash, input) } } @@ -222,12 +219,12 @@ pub mod main { #[validity_predicate] fn validate_tx( _ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, ) -> VpResult { - let len = usize::try_from_slice(&tx_data[..]).unwrap(); + let len = usize::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); log_string(format!("allocate len {}", len)); let bytes: Vec = vec![6_u8; len]; // use the variable to prevent it from compiler optimizing it away @@ -245,13 +242,13 @@ pub mod main { #[validity_predicate] fn validate_tx( ctx: &Ctx, - tx_data: Vec, + tx_data: Tx, _addr: Address, _keys_changed: BTreeSet, _verifiers: BTreeSet
, ) -> VpResult { // Allocates a memory of size given from the `tx_data (usize)` - let key = storage::Key::try_from_slice(&tx_data[..]).unwrap(); + let key = storage::Key::try_from_slice(&tx_data.data().as_ref().unwrap()[..]).unwrap(); log_string(format!("key {}", key)); let _result: Vec = ctx.read_pre(&key)?.unwrap(); accept()