diff --git a/Cargo.lock b/Cargo.lock index 1e377a62766..e25a7c0c763 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -202,11 +202,11 @@ dependencies = [ [[package]] name = "base58-monero" -version = "0.1.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87c25c7705c81e36f14c293e67846819b1fa3ca7c5e9888ebf149c2bd59d06aa" +checksum = "465ba1f408efdef4d9379bdfa7340899b63e472d50c7fb666480ccfd5a893e53" dependencies = [ - "keccak-hash 0.1.2", + "thiserror", ] [[package]] @@ -258,7 +258,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f30d3a39baa26f9651f17b375061f3233dde33424a8b72b0dbe93a68a0bc896d" dependencies = [ "byteorder", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -302,16 +302,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e54f7b7a46d7b183eb41e2d82965261fa8a1597c68b50aced268ee1fc70272d" -[[package]] -name = "bitvec" -version = "0.17.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41262f11d771fd4a61aa3ce019fca363b4b6c282fca9da2a31186d3965a47a5c" -dependencies = [ - "either", - "radium", -] - [[package]] name = "blake2" version = "0.8.1" @@ -401,7 +391,7 @@ dependencies = [ "lazy_static 1.4.0", "memchr", "regex-automata", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -410,12 +400,6 @@ version = "3.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe" -[[package]] -name = "byte-slice-cast" -version = "0.3.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0a5e3906bcbf133e33c1d4d95afc664ad37fbdb9f6568d8043e7ea8c27d93d3" - [[package]] name = "byte-tools" version = "0.3.1" @@ -450,7 +434,7 @@ version = "0.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38" dependencies = [ - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -498,7 +482,7 @@ dependencies = [ "log 0.4.14", "proc-macro2 1.0.24", "quote 1.0.9", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "syn 1.0.60", "tempfile", @@ -567,7 +551,7 @@ dependencies = [ "libc", "num-integer", "num-traits 0.2.14", - "serde 1.0.123", + "serde 1.0.126", "time", "winapi 0.3.9", ] @@ -590,7 +574,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6316c62053228eddd526a5e6deb6344c80bf2bc1e9786e7f90b3083e73197c1" dependencies = [ "bitstring", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -670,7 +654,7 @@ dependencies = [ "lazy_static 1.4.0", "nom 4.2.3", "rust-ini", - "serde 1.0.123", + "serde 1.0.126", "serde-hjson", "serde_json", "toml 0.4.10", @@ -749,7 +733,7 @@ dependencies = [ "rand_xoshiro", "rayon", "rayon-core", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "serde_json", "tinytemplate", @@ -903,12 +887,6 @@ dependencies = [ "winapi 0.3.9", ] -[[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" @@ -945,7 +923,7 @@ dependencies = [ "csv-core", "itoa", "ryu", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -991,7 +969,21 @@ dependencies = [ "digest 0.8.1", "packed_simd_2", "rand_core 0.5.1", - "serde 1.0.123", + "serde 1.0.126", + "subtle 2.4.0", + "zeroize", +] + +[[package]] +name = "curve25519-dalek" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "639891fde0dbea823fc3d798a0fdf9d2f9440a42d64a78ab3488b0ca025117b3" +dependencies = [ + "byteorder", + "digest 0.9.0", + "rand_core 0.5.1", + "serde 1.0.126", "subtle 2.4.0", "zeroize", ] @@ -1168,42 +1160,6 @@ dependencies = [ "termcolor", ] -[[package]] -name = "ethbloom" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6294da962646baa738414e8e718d1a1f0360a51d92de89ccbf91870418f5360" -dependencies = [ - "crunchy 0.1.6", - "ethereum-types-serialize", - "fixed-hash 0.2.5", - "serde 1.0.123", - "tiny-keccak", -] - -[[package]] -name = "ethereum-types" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e742184dc63a01c8ea0637369f8faa27c40f537949908a237f95c05e68d2c96" -dependencies = [ - "crunchy 0.1.6", - "ethbloom", - "ethereum-types-serialize", - "fixed-hash 0.2.5", - "serde 1.0.123", - "uint 0.4.1", -] - -[[package]] -name = "ethereum-types-serialize" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1873d77b32bc1891a79dad925f2acbc318ee942b38b9110f9dbc5fbeffcea350" -dependencies = [ - "serde 1.0.123", -] - [[package]] name = "fake-simd" version = "0.1.2" @@ -1212,38 +1168,14 @@ checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" [[package]] name = "fixed-hash" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7afe6ce860afb14422711595a7b26ada9ed7de2f43c0b2ab79d09ee196287273" -dependencies = [ - "heapsize", - "rand 0.4.6", - "rustc-hex", -] - -[[package]] -name = "fixed-hash" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1a683d1234507e4f3bf2736eeddf0de1dc65996dc0164d57eba0a74bcf29489" -dependencies = [ - "byteorder", - "libc", - "rand 0.5.6", - "rustc-hex", - "static_assertions 0.2.5", -] - -[[package]] -name = "fixed-hash" -version = "0.4.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "516877b7b9a1cc2d0293cbce23cd6203f0edbfd4090e6ca4489fecb5aa73050e" +checksum = "cfcf0ed7fe52a17a03854ec54a9f76d6d84508d1c0e66bc1793301c73fc8493c" dependencies = [ "byteorder", - "rand 0.5.6", + "rand 0.8.3", "rustc-hex", - "static_assertions 0.2.5", + "static_assertions", ] [[package]] @@ -1670,15 +1602,6 @@ version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04" -[[package]] -name = "heapsize" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1679e6ea370dee694f91f1dc469bf94cf8f52051d147aec3e1f9497c6fc22461" -dependencies = [ - "winapi 0.3.9", -] - [[package]] name = "heck" version = "0.3.2" @@ -1699,9 +1622,15 @@ dependencies = [ [[package]] name = "hex" -version = "0.4.2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "hex-literal" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "644f9158b2f133fd50f5fb3242878846d9eb792e445c893805ff0e3824006e35" +checksum = "5af1f635ef1bc545d78392b136bfe1c9809e029023c84a3638a864a10b8819c8" [[package]] name = "http" @@ -1843,15 +1772,6 @@ dependencies = [ "num-traits 0.2.14", ] -[[package]] -name = "impl-codec" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1be51a921b067b0eaca2fad532d9400041561aa922221cc65f95a85641c6bf53" -dependencies = [ - "parity-scale-codec", -] - [[package]] name = "indexmap" version = "1.6.1" @@ -1908,7 +1828,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "436f3455a8a4e9c7b14de9f1206198ee5d0bdc2db1b560339d2141093d7dd389" dependencies = [ "hyper 0.10.16", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "serde_json", ] @@ -1928,26 +1848,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67c21572b4949434e4fc1e1978b99c5f77064153c59d998bf13ecd96fb5ecba7" -[[package]] -name = "keccak-hash" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "253bbe643c32c816bf58fa5a88248fafedeebb139705ad17a62add3517854a86" -dependencies = [ - "ethereum-types", - "tiny-keccak", -] - -[[package]] -name = "keccak-hash" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09364fca1db2974c2730ddfe2627b03a8def6128c9899303ca05e40ca6f20903" -dependencies = [ - "primitive-types", - "tiny-keccak", -] - [[package]] name = "kernel32-sys" version = "0.2.2" @@ -2118,7 +2018,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ "cfg-if 1.0.0", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -2142,7 +2042,7 @@ dependencies = [ "libc", "log 0.4.14", "log-mdc", - "serde 1.0.123", + "serde 1.0.126", "serde-value", "serde_derive", "serde_json", @@ -2318,19 +2218,19 @@ dependencies = [ [[package]] name = "monero" -version = "0.9.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d53d4207d0bd4d1eb3323e33a64f9ea99e5e3d257d5cd7a659fad5be48c8b9af" +checksum = "5a7038b6ba92588189248fbb4f8b2744d4918a9732f826e414814a50c168dca3" dependencies = [ "base58-monero", - "byteorder", - "curve25519-dalek", - "fixed-hash 0.3.2", + "curve25519-dalek 3.1.0", + "fixed-hash", "hex", - "keccak-hash 0.3.0", - "serde 1.0.123", + "hex-literal", + "serde 1.0.126", "serde-big-array", "thiserror", + "tiny-keccak", ] [[package]] @@ -2662,24 +2562,12 @@ dependencies = [ "data-encoding", "multihash", "percent-encoding 2.1.0", - "serde 1.0.123", - "static_assertions 1.1.0", + "serde 1.0.126", + "static_assertions", "unsigned-varint 0.6.0", "url 2.2.1", ] -[[package]] -name = "parity-scale-codec" -version = "1.3.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79602888a81ace83e3d1d4b2873286c1f5f906c84db667594e8db8da3506c383" -dependencies = [ - "arrayvec 0.5.2", - "bitvec", - "byte-slice-cast", - "serde 1.0.123", -] - [[package]] name = "parking_lot" version = "0.10.2" @@ -2819,17 +2707,6 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" -[[package]] -name = "primitive-types" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83ef7b3b965c0eadcb6838f34f827e1dfb2939bdd5ebd43f9647e009b12b0371" -dependencies = [ - "fixed-hash 0.4.0", - "impl-codec", - "uint 0.8.5", -] - [[package]] name = "proc-macro-crate" version = "0.1.5" @@ -2984,12 +2861,6 @@ dependencies = [ "proc-macro2 1.0.24", ] -[[package]] -name = "radium" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "def50a86306165861203e7f84ecffbbdfdea79f0e51039b33de1e952358c47ac" - [[package]] name = "radix_trie" version = "0.1.6" @@ -3000,19 +2871,6 @@ dependencies = [ "nibble_vec", ] -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi 0.3.9", -] - [[package]] name = "rand" version = "0.5.6" @@ -3288,7 +3146,7 @@ dependencies = [ "native-tls", "percent-encoding 2.1.0", "pin-project-lite 0.2.4", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "serde_urlencoded", "tokio", @@ -3333,7 +3191,7 @@ checksum = "011e1d58446e9fa3af7cdc1fb91295b10621d3ac4cb3a85cc86385ee9ca50cd3" dependencies = [ "byteorder", "rmp", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -3520,20 +3378,20 @@ checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8" [[package]] name = "serde" -version = "1.0.123" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d5161132722baa40d802cc70b15262b98258453e85e5d1d365c757c73869ae" +checksum = "ec7505abeacaec74ae4778d9d9328fe5a5d04253220a85c4ee022239fc996d03" dependencies = [ "serde_derive", ] [[package]] name = "serde-big-array" -version = "0.2.0" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "883eee5198ea51720eab8be52a36cf6c0164ac90eea0ed95b649d5e35382404e" +checksum = "18b20e7752957bbe9661cff4e0bb04d183d0948cdab2ea58cdb9df36a61dfe62" dependencies = [ - "serde 1.0.123", + "serde 1.0.126", "serde_derive", ] @@ -3557,14 +3415,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a663f873dedc4eac1a559d4c6bc0d0b2c34dc5ac4702e105014b8281489e44f" dependencies = [ "ordered-float", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] name = "serde_derive" -version = "1.0.123" +version = "1.0.126" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9391c295d64fc0abb2c556bad848f33cb8296276b1ad2677d1ae1ace4f258f31" +checksum = "963a7dbc9895aeac7ac90e74f34a5d5261828f79df35cbed41e10189d3804d43" dependencies = [ "proc-macro2 1.0.24", "quote 1.0.9", @@ -3579,7 +3437,7 @@ checksum = "ea1c6153794552ea7cf7cf63b1231a25de00ec90db326ba6264440fa08e31486" dependencies = [ "itoa", "ryu", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -3611,7 +3469,7 @@ dependencies = [ "form_urlencoded", "itoa", "ryu", - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -3622,7 +3480,7 @@ checksum = "15654ed4ab61726bf918a39cb8d98a2e2995b002387807fa6ba58fdf7f59bb23" dependencies = [ "dtoa", "linked-hash-map 0.5.4", - "serde 1.0.123", + "serde 1.0.126", "yaml-rust", ] @@ -3744,12 +3602,6 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" -[[package]] -name = "static_assertions" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19be23126415861cb3a23e501d34a708f7f9b2183c5252d690941c2e69199d5" - [[package]] name = "static_assertions" version = "1.1.0" @@ -3991,12 +3843,12 @@ checksum = "e4b6531abd01ccba175a24e7a72061156524f8f435e1532fe6295254f4c4e5b9" dependencies = [ "byteorder", "clear_on_drop", - "curve25519-dalek", + "curve25519-dalek 2.1.2", "digest 0.8.1", "merlin", "rand 0.7.3", "rand_core 0.5.1", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "sha3 0.8.2", "subtle 2.4.0", @@ -4017,7 +3869,7 @@ dependencies = [ "parity-multiaddr", "path-clean", "prost-build", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "sha2", "structopt", @@ -4033,7 +3885,7 @@ version = "0.8.11" dependencies = [ "futures 0.3.12", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "tari_crypto", "tokio", ] @@ -4062,7 +3914,7 @@ dependencies = [ "pin-project 0.4.27", "prost", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "serde_json", "snow", @@ -4106,7 +3958,7 @@ dependencies = [ "prost", "prost-types", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "serde_repr", "tari_common", @@ -4203,7 +4055,7 @@ dependencies = [ "prost-types", "rand 0.7.3", "randomx-rs", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "sha3 0.9.1", "strum_macros 0.17.1", @@ -4224,7 +4076,7 @@ dependencies = [ "tokio", "tokio-macros", "ttl_cache", - "uint 0.9.0", + "uint", ] [[package]] @@ -4237,14 +4089,14 @@ dependencies = [ "blake3", "cbindgen", "clear_on_drop", - "curve25519-dalek", + "curve25519-dalek 2.1.2", "digest 0.8.1", "k12", "lazy_static 1.4.0", "merlin", "rand 0.7.3", "rmp-serde", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "sha2", "sha3 0.9.1", @@ -4269,7 +4121,7 @@ version = "0.8.11" dependencies = [ "digest 0.8.1", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "serde_json", "sha2", @@ -4294,10 +4146,9 @@ dependencies = [ "hyper 0.13.10", "jsonrpc", "log 0.4.14", - "monero", "rand 0.7.3", "reqwest", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "structopt", "tari_app_grpc", @@ -4327,7 +4178,7 @@ dependencies = [ "num_cpus", "prost-types", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "sha3 0.9.1", "tari_app_grpc", "tari_app_utilities", @@ -4350,7 +4201,7 @@ dependencies = [ "digest 0.8.1", "log 0.4.14", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "tari_crypto", "tari_infra_derive", @@ -4377,7 +4228,7 @@ dependencies = [ "log4rs", "prost", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "stream-cancel", "tari_common", @@ -4435,7 +4286,7 @@ dependencies = [ "rand 0.5.6", "rmp", "rmp-serde", - "serde 1.0.123", + "serde 1.0.126", "serde_derive", "tari_utilities", "thiserror", @@ -4466,7 +4317,7 @@ dependencies = [ "clear_on_drop", "newtype-ops", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "thiserror", ] @@ -4492,7 +4343,7 @@ dependencies = [ "log4rs", "prost", "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "tari_common_types", "tari_comms", @@ -4568,7 +4419,7 @@ name = "test_faucet" version = "0.8.11" dependencies = [ "rand 0.7.3", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "tari_core", "tari_crypto", @@ -4638,11 +4489,11 @@ dependencies = [ [[package]] name = "tiny-keccak" -version = "1.5.0" +version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d8a021c69bb74a44ccedb824a046447e2c84a01df9e5c20779750acb38e11b2" +checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237" dependencies = [ - "crunchy 0.2.2", + "crunchy", ] [[package]] @@ -4651,7 +4502,7 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2ada8616fad06a2d0c455adc530de4ef57605a8120cc65da9653e0e9623ca74" dependencies = [ - "serde 1.0.123", + "serde 1.0.126", "serde_json", ] @@ -4759,7 +4610,7 @@ version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "758664fc71a3a69038656bee8b6be6477d2a6c315a6b81f7081f591bffa4111f" dependencies = [ - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -4768,7 +4619,7 @@ version = "0.5.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a31142970826733df8241ef35dc040ef98c679ab14d7c3e54d827099b3acecaa" dependencies = [ - "serde 1.0.123", + "serde 1.0.126", ] [[package]] @@ -5065,7 +4916,7 @@ version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fb65ea441fbb84f9f6748fd496cf7f63ec9af5bca94dd86456978d055e8eb28b" dependencies = [ - "serde 1.0.123", + "serde 1.0.126", "tracing-core", ] @@ -5080,7 +4931,7 @@ dependencies = [ "lazy_static 1.4.0", "matchers", "regex", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "sharded-slab", "smallvec", @@ -5190,30 +5041,6 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" -[[package]] -name = "uint" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "754ba11732b9161b94c41798e5197e5e75388d012f760c42adb5000353e98646" -dependencies = [ - "byteorder", - "crunchy 0.1.6", - "heapsize", - "rustc-hex", -] - -[[package]] -name = "uint" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9db035e67dfaf7edd9aebfe8676afcd63eed53c8a4044fed514c8cccf1835177" -dependencies = [ - "byteorder", - "crunchy 0.2.2", - "rustc-hex", - "static_assertions 1.1.0", -] - [[package]] name = "uint" version = "0.9.0" @@ -5221,9 +5048,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e11fe9a9348741cf134085ad57c249508345fe16411b3d7fb4ff2da2f1d6382e" dependencies = [ "byteorder", - "crunchy 0.2.2", + "crunchy", "hex", - "static_assertions 1.1.0", + "static_assertions", ] [[package]] @@ -5422,7 +5249,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55c0f7123de74f0dab9b7d00fd614e7b19349cd1e2f5252bbe9b1754b59433be" dependencies = [ "cfg-if 1.0.0", - "serde 1.0.123", + "serde 1.0.126", "serde_json", "wasm-bindgen-macro", ] @@ -5580,7 +5407,7 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "637ff90c9540fa3073bb577e65033069e4bae7c79d49d74aa3ffdf5342a53217" dependencies = [ - "curve25519-dalek", + "curve25519-dalek 2.1.2", "rand_core 0.5.1", "zeroize", ] @@ -5605,7 +5432,7 @@ dependencies = [ "nohash-hasher", "parking_lot", "rand 0.7.3", - "static_assertions 1.1.0", + "static_assertions", ] [[package]] diff --git a/applications/tari_merge_mining_proxy/Cargo.toml b/applications/tari_merge_mining_proxy/Cargo.toml index 78dc5555fba..f197a08927d 100644 --- a/applications/tari_merge_mining_proxy/Cargo.toml +++ b/applications/tari_merge_mining_proxy/Cargo.toml @@ -31,7 +31,6 @@ hex = "0.4.2" hyper = "0.13.7" jsonrpc = "0.11.0" log = { version = "0.4.8", features = ["std"] } -monero = {version = "^0.9.1", features = ["serde_support"]} rand = "0.7.2" reqwest = {version = "0.10.8", features=["json"]} serde = { version="1.0.106", features = ["derive"] } diff --git a/applications/tari_merge_mining_proxy/src/block_template_data.rs b/applications/tari_merge_mining_proxy/src/block_template_data.rs index 4066428a692..497de5ef868 100644 --- a/applications/tari_merge_mining_proxy/src/block_template_data.rs +++ b/applications/tari_merge_mining_proxy/src/block_template_data.rs @@ -23,6 +23,7 @@ use crate::error::MmProxyError; use chrono::{self, DateTime, Duration, Utc}; use std::{collections::HashMap, sync::Arc}; use tari_app_grpc::tari_rpc::{Block, MinerData}; +use tari_core::{crypto::tari_utilities::hex::Hex, proof_of_work::monero_rx::FixedByteArray}; use tokio::sync::RwLock; use tracing::trace; @@ -100,7 +101,7 @@ impl BlockTemplateRepository { #[derive(Clone, Debug)] pub struct BlockTemplateData { - pub monero_seed: String, + pub monero_seed: FixedByteArray, pub tari_block: Block, pub tari_miner_data: MinerData, pub monero_difficulty: u64, @@ -162,7 +163,7 @@ impl BlockTemplateDataBuilder { .ok_or_else(|| MmProxyError::MissingDataError("tari_difficulty not provided".to_string()))?; Ok(BlockTemplateData { - monero_seed, + monero_seed: FixedByteArray::from_hex(&monero_seed).map_err(|_| MmProxyError::InvalidRandomXSeed)?, tari_block, tari_miner_data, monero_difficulty, diff --git a/applications/tari_merge_mining_proxy/src/common/merge_mining.rs b/applications/tari_merge_mining_proxy/src/common/merge_mining.rs index 16c19f6a608..b4be5ee2236 100644 --- a/applications/tari_merge_mining_proxy/src/common/merge_mining.rs +++ b/applications/tari_merge_mining_proxy/src/common/merge_mining.rs @@ -21,46 +21,13 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::error::MmProxyError; -use monero::{ - blockdata::{transaction::SubField, Block}, - consensus::{deserialize, serialize}, - cryptonote::hash::Hash, -}; use std::convert::TryFrom; use tari_app_grpc::tari_rpc as grpc; use tari_core::{ blocks::NewBlockTemplate, - proof_of_work::{monero_rx, monero_rx::MoneroData}, transactions::transaction::{TransactionKernel, TransactionOutput}, }; -pub fn deserialize_monero_block_from_hex(data: T) -> Result -where T: AsRef<[u8]> { - let bytes = hex::decode(data)?; - let obj = deserialize::(&bytes) - .map_err(|_| MmProxyError::MissingDataError("blocktemplate blob invalid".to_string()))?; - Ok(obj) -} - -pub fn serialize_monero_block_to_hex(obj: &Block) -> Result { - let data = serialize::(obj); - let bytes = hex::encode(data); - Ok(bytes) -} - -pub fn construct_monero_data(block: Block, seed: String) -> Result { - let hashes = monero_rx::create_ordered_transaction_hashes_from_block(&block); - let root = monero_rx::tree_hash(&hashes)?; - Ok(MoneroData { - header: block.header, - key: seed, - count: hashes.len() as u16, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: hashes.into_iter().map(|h| h.to_fixed_bytes()).collect(), - coinbase_tx: block.miner_tx, - }) -} - pub fn add_coinbase( coinbase: Option, mut block: NewBlockTemplate, @@ -81,12 +48,3 @@ pub fn add_coinbase( Err(MmProxyError::MissingDataError("Coinbase Invalid".to_string())) } } - -pub fn extract_tari_hash(monero: &Block) -> Option<&Hash> { - for item in monero.miner_tx.prefix.extra.0.iter() { - if let SubField::MergeMining(_depth, merge_mining_hash) = item { - return Some(merge_mining_hash); - } - } - None -} diff --git a/applications/tari_merge_mining_proxy/src/error.rs b/applications/tari_merge_mining_proxy/src/error.rs index 257c4dd812c..abfef30ccde 100644 --- a/applications/tari_merge_mining_proxy/src/error.rs +++ b/applications/tari_merge_mining_proxy/src/error.rs @@ -74,6 +74,8 @@ pub enum MmProxyError { CoinbaseBuilderError(#[from] CoinbaseBuildError), #[error("Unexpected Tari base node response: {0}")] UnexpectedTariBaseNodeResponse(String), + #[error("Invalid RandomX seed")] + InvalidRandomXSeed, } impl From for MmProxyError { diff --git a/applications/tari_merge_mining_proxy/src/proxy.rs b/applications/tari_merge_mining_proxy/src/proxy.rs index c49bd1ee84c..9a3b9946ea1 100644 --- a/applications/tari_merge_mining_proxy/src/proxy.rs +++ b/applications/tari_merge_mining_proxy/src/proxy.rs @@ -233,9 +233,9 @@ impl InnerService { }; for param in params.iter().filter_map(|p| p.as_str()) { - let monero_block = merge_mining::deserialize_monero_block_from_hex(param)?; + let monero_block = monero_rx::deserialize_monero_block_from_hex(param)?; debug!(target: LOG_TARGET, "Monero block: {}", monero_block); - let hash = merge_mining::extract_tari_hash(&monero_block) + let hash = monero_rx::extract_tari_hash(&monero_block) .copied() .ok_or_else(|| MmProxyError::MissingDataError("Could not find Tari header in coinbase".to_string()))?; @@ -257,11 +257,11 @@ impl InnerService { }, }; - let monero_data = merge_mining::construct_monero_data(monero_block, block_data.monero_seed.clone())?; + let monero_data = monero_rx::construct_monero_data(monero_block, block_data.monero_seed.clone())?; let header_mut = block_data.tari_block.header.as_mut().unwrap(); let height = header_mut.height; - header_mut.pow.as_mut().unwrap().pow_data = bincode::serialize(&monero_data)?; + header_mut.pow.as_mut().unwrap().pow_data = monero_rx::serialize(&monero_data); let mut base_node_client = self.base_node_client.clone(); let start = Instant::now(); @@ -468,7 +468,7 @@ impl InnerService { .to_string() .replace("\"", ""); debug!(target: LOG_TARGET, "Deserializing Blocktemplate Blob into Monero Block",); - let mut monero_block = merge_mining::deserialize_monero_block_from_hex(block_template_blob)?; + let mut monero_block = monero_rx::deserialize_monero_block_from_hex(block_template_blob)?; debug!(target: LOG_TARGET, "Appending Merged Mining Tag",); // Add the Tari merge mining tag to the retrieved block template @@ -476,12 +476,12 @@ impl InnerService { debug!(target: LOG_TARGET, "Creating blockhashing blob from blocktemplate blob",); // Must be done after the tag is inserted since it will affect the hash of the miner tx - let blockhashing_blob = monero_rx::create_blockhashing_blob(&monero_block)?; + let blockhashing_blob = monero_rx::create_blockhashing_blob_from_block(&monero_block)?; debug!(target: LOG_TARGET, "blockhashing_blob:{}", blockhashing_blob); monerod_resp["result"]["blockhashing_blob"] = blockhashing_blob.into(); - let blocktemplate_blob = merge_mining::serialize_monero_block_to_hex(&monero_block)?; + let blocktemplate_blob = monero_rx::serialize_monero_block_to_hex(&monero_block)?; debug!(target: LOG_TARGET, "blocktemplate_blob:{}", block_template_blob); monerod_resp["result"]["blocktemplate_blob"] = blocktemplate_blob.into(); diff --git a/base_layer/core/Cargo.toml b/base_layer/core/Cargo.toml index f77b31cb97a..40908fbf320 100644 --- a/base_layer/core/Cargo.toml +++ b/base_layer/core/Cargo.toml @@ -44,7 +44,7 @@ fs2 = "0.3.0" hex = "0.4.2" lmdb-zero = "0.4.4" log = "0.4" -monero = { version = "^0.9.1", features= ["serde_support"], optional = true } +monero = { version = "^0.13.0", features= ["serde_support"], optional = true } newtype-ops = "0.1.4" num = "0.3" prost = "0.6.1" diff --git a/base_layer/core/src/blocks/genesis_block.rs b/base_layer/core/src/blocks/genesis_block.rs index 0ddbef114e4..b9af5939e92 100644 --- a/base_layer/core/src/blocks/genesis_block.rs +++ b/base_layer/core/src/blocks/genesis_block.rs @@ -230,7 +230,7 @@ pub fn get_weatherwax_genesis_block_raw() -> Block { prev_hash: vec![ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ], - timestamp: 1_622_550_256.into(), + timestamp: 1_623_151_562.into(), // Tue Jun 08 2021 11:26:02 GMT+0000 output_mr: from_hex("dcc44f39b65e5e1e526887e7d56f7b85e2ea44bd29bc5bc195e6e015d19e1c06").unwrap(), range_proof_mr: from_hex("e4d7dab49a66358379a901b9a36c10f070aa9d7bdc8ae752947b6fc4e55d255f").unwrap(), output_mmr_size: 1, diff --git a/base_layer/core/src/chain_storage/blockchain_backend.rs b/base_layer/core/src/chain_storage/blockchain_backend.rs index f49f291fbce..57783c728ca 100644 --- a/base_layer/core/src/chain_storage/blockchain_backend.rs +++ b/base_layer/core/src/chain_storage/blockchain_backend.rs @@ -148,7 +148,7 @@ pub trait BlockchainBackend: Send + Sync { ) -> Result<(), ChainStorageError>; /// This gets the monero seed_height. This will return 0, if the seed is unkown - fn fetch_monero_seed_first_seen_height(&self, seed: &str) -> Result; + fn fetch_monero_seed_first_seen_height(&self, seed: &[u8]) -> Result; fn fetch_horizon_data(&self) -> Result, ChainStorageError>; } diff --git a/base_layer/core/src/chain_storage/blockchain_database.rs b/base_layer/core/src/chain_storage/blockchain_database.rs index f3d5f0d643f..bb1d67865e9 100644 --- a/base_layer/core/src/chain_storage/blockchain_database.rs +++ b/base_layer/core/src/chain_storage/blockchain_database.rs @@ -44,7 +44,7 @@ use crate::{ }, common::rolling_vec::RollingVec, consensus::{chain_strength_comparer::ChainStrengthComparer, ConsensusConstants, ConsensusManager}, - proof_of_work::{monero_rx::MoneroData, PowAlgorithm, TargetDifficultyWindow}, + proof_of_work::{monero_rx::MoneroPowData, PowAlgorithm, TargetDifficultyWindow}, tari_utilities::epoch_time::EpochTime, transactions::{ transaction::{TransactionKernel, TransactionOutput}, @@ -1067,10 +1067,10 @@ fn insert_block(txn: &mut DbTransaction, block: Arc) -> Result<(), C block_hash.to_hex() ); if block.header().pow_algo() == PowAlgorithm::Monero { - let monero_seed = MoneroData::from_header(&block.header()) + let monero_seed = MoneroPowData::from_header(&block.header()) .map_err(|e| ValidationError::CustomError(e.to_string()))? - .key; - txn.insert_monero_seed_height(&monero_seed, block.height()); + .randomx_key; + txn.insert_monero_seed_height(monero_seed.to_vec(), block.height()); } let height = block.height(); @@ -1925,31 +1925,31 @@ mod test { #[test] fn lmdb_fetch_monero_seeds() { let db = create_test_blockchain_db(); - let seed = "test1".to_string(); + let seed = b"test1"; { let db_read = db.db_read_access().unwrap(); - assert_eq!(db_read.fetch_monero_seed_first_seen_height(&seed).unwrap(), 0); + assert_eq!(db_read.fetch_monero_seed_first_seen_height(&seed[..]).unwrap(), 0); } { let mut txn = DbTransaction::new(); - txn.insert_monero_seed_height(&seed, 5); + txn.insert_monero_seed_height(seed.to_vec(), 5); let mut db_write = db.test_db_write_access().unwrap(); assert!(db_write.write(txn).is_ok()); } { let db_read = db.db_read_access().unwrap(); - assert_eq!(db_read.fetch_monero_seed_first_seen_height(&seed).unwrap(), 5); + assert_eq!(db_read.fetch_monero_seed_first_seen_height(&seed[..]).unwrap(), 5); } { let mut txn = DbTransaction::new(); - txn.insert_monero_seed_height(&seed, 2); + txn.insert_monero_seed_height(seed.to_vec(), 2); let mut db_write = db.db_write_access().unwrap(); assert!(db_write.write(txn).is_ok()); } { let db_read = db.db_read_access().unwrap(); - assert_eq!(db_read.fetch_monero_seed_first_seen_height(&seed).unwrap(), 2); + assert_eq!(db_read.fetch_monero_seed_first_seen_height(&seed[..]).unwrap(), 2); } } diff --git a/base_layer/core/src/chain_storage/db_transaction.rs b/base_layer/core/src/chain_storage/db_transaction.rs index e865d11b959..6e3f908cfc1 100644 --- a/base_layer/core/src/chain_storage/db_transaction.rs +++ b/base_layer/core/src/chain_storage/db_transaction.rs @@ -260,9 +260,9 @@ impl DbTransaction { /// This will store the seed key with the height. This is called when a block is accepted into the main chain. /// This will only update the hieght of the seed, if its lower then currently stored. - pub fn insert_monero_seed_height(&mut self, monero_seed: &str, height: u64) { + pub fn insert_monero_seed_height(&mut self, monero_seed: Vec, height: u64) { self.operations - .push(WriteOperation::InsertMoneroSeedHeight(monero_seed.to_string(), height)); + .push(WriteOperation::InsertMoneroSeedHeight(monero_seed, height)); } } @@ -305,7 +305,7 @@ pub enum WriteOperation { DeleteBlock(HashOutput), DeleteOrphanChainTip(HashOutput), InsertOrphanChainTip(HashOutput), - InsertMoneroSeedHeight(String, u64), + InsertMoneroSeedHeight(Vec, u64), UpdatePrunedHashSet { mmr_tree: MmrTree, header_hash: HashOutput, @@ -395,7 +395,7 @@ impl fmt::Display for WriteOperation { InsertOrphanChainTip(hash) => write!(f, "InsertOrphanChainTip({})", hash.to_hex()), DeleteBlock(hash) => write!(f, "DeleteBlock({})", hash.to_hex()), InsertMoneroSeedHeight(data, height) => { - write!(f, "Insert Monero seed string {} for height: {}", data, height) + write!(f, "Insert Monero seed string {} for height: {}", data.to_hex(), height) }, InsertChainOrphanBlock(block) => write!(f, "InsertChainOrphanBlock({})", block.hash().to_hex()), UpdatePrunedHashSet { diff --git a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs index 0b6dbe78124..8e4ab59e054 100644 --- a/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs +++ b/base_layer/core/src/chain_storage/lmdb_db/lmdb_db.rs @@ -237,7 +237,7 @@ impl LMDBDatabase { self.delete_block_body(&write_txn, hash)?; }, InsertMoneroSeedHeight(data, height) => { - self.insert_monero_seed_height(&write_txn, data, height)?; + self.insert_monero_seed_height(&write_txn, &data, height)?; }, SetAccumulatedDataForOrphan(chain_header) => { self.set_accumulated_data_for_orphan( @@ -929,12 +929,12 @@ impl LMDBDatabase { fn insert_monero_seed_height( &self, write_txn: &WriteTransaction<'_>, - seed: String, + seed: &[u8], height: u64, ) -> Result<(), ChainStorageError> { - let current_height = lmdb_get(&write_txn, &self.monero_seed_height_db, seed.as_str())?.unwrap_or(std::u64::MAX); + let current_height = lmdb_get(&write_txn, &self.monero_seed_height_db, seed)?.unwrap_or(std::u64::MAX); if height < current_height { - lmdb_replace(&write_txn, &self.monero_seed_height_db, seed.as_str(), &height)?; + lmdb_replace(&write_txn, &self.monero_seed_height_db, seed, &height)?; }; Ok(()) } @@ -1844,7 +1844,7 @@ impl BlockchainBackend for LMDBDatabase { Ok(()) } - fn fetch_monero_seed_first_seen_height(&self, seed: &str) -> Result { + fn fetch_monero_seed_first_seen_height(&self, seed: &[u8]) -> Result { let txn = self.read_transaction()?; Ok(lmdb_get(&txn, &self.monero_seed_height_db, seed)?.unwrap_or(0)) } diff --git a/base_layer/core/src/lib.rs b/base_layer/core/src/lib.rs index 0b60f04c50f..cb3da6f513d 100644 --- a/base_layer/core/src/lib.rs +++ b/base_layer/core/src/lib.rs @@ -49,6 +49,8 @@ pub mod consensus; pub mod iterators; #[cfg(any(feature = "base_node", feature = "transactions"))] pub mod proof_of_work; +#[cfg(any(feature = "base_node", feature = "transactions"))] +pub use proof_of_work::monero_rx::FixedByteArray; #[cfg(feature = "base_node")] pub mod validation; diff --git a/base_layer/core/src/proof_of_work/error.rs b/base_layer/core/src/proof_of_work/error.rs index da3ed210423..6cf1c2083cf 100644 --- a/base_layer/core/src/proof_of_work/error.rs +++ b/base_layer/core/src/proof_of_work/error.rs @@ -20,7 +20,7 @@ // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -use crate::proof_of_work::Difficulty; +use crate::proof_of_work::{monero_rx::MergeMineError, Difficulty}; use thiserror::Error; #[derive(Debug, Error)] @@ -33,7 +33,7 @@ pub enum PowError { InvalidTargetDifficulty { expected: Difficulty, got: Difficulty }, #[cfg(feature = "base_node")] #[error("Invalid merge mining data or operation: {0}")] - MergeMineError(#[from] super::monero_rx::MergeMineError), + MergeMineError(#[from] MergeMineError), } #[derive(Debug, Error, Clone, PartialEq)] diff --git a/base_layer/core/src/proof_of_work/mod.rs b/base_layer/core/src/proof_of_work/mod.rs index 6792e81822e..58562a90f81 100644 --- a/base_layer/core/src/proof_of_work/mod.rs +++ b/base_layer/core/src/proof_of_work/mod.rs @@ -30,9 +30,9 @@ mod error; #[cfg(any(feature = "base_node", feature = "transactions"))] pub use error::{DifficultyAdjustmentError, PowError}; -#[cfg(feature = "base_node")] +#[cfg(any(feature = "base_node", feature = "transactions"))] pub mod monero_rx; -#[cfg(feature = "base_node")] +#[cfg(any(feature = "base_node", feature = "transactions"))] pub use monero_rx::monero_difficulty; #[cfg(any(feature = "base_node", feature = "transactions"))] diff --git a/base_layer/core/src/proof_of_work/monero_rx/error.rs b/base_layer/core/src/proof_of_work/monero_rx/error.rs new file mode 100644 index 00000000000..2bc71827a74 --- /dev/null +++ b/base_layer/core/src/proof_of_work/monero_rx/error.rs @@ -0,0 +1,42 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::crypto::tari_utilities::hex::HexError; +use randomx_rs::RandomXError; + +#[derive(Debug, thiserror::Error)] +pub enum MergeMineError { + #[error("Serialization error: {0}")] + SerializeError(String), + #[error("Error deserializing Monero data: {0}")] + DeserializeError(String), + #[error("Hashing of Monero data failed: {0}")] + HashingError(String), + #[error("RandomX error: {0}")] + RandomXError(#[from] RandomXError), + #[error("Validation error: {0}")] + ValidationError(String), + #[error("Hex conversion error: {0}")] + HexError(#[from] HexError), + #[error("Monero PoW data did not contain a valid merkle root")] + InvalidMerkleRoot, +} diff --git a/base_layer/core/src/proof_of_work/monero_rx/fixed_array.rs b/base_layer/core/src/proof_of_work/monero_rx/fixed_array.rs new file mode 100644 index 00000000000..ae2677fe75a --- /dev/null +++ b/base_layer/core/src/proof_of_work/monero_rx/fixed_array.rs @@ -0,0 +1,190 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use crate::crypto::tari_utilities::ByteArrayError; +use monero::{ + consensus::{encode, Decodable, Encodable}, + VarInt, +}; +use std::{convert::TryFrom, io, ops::Deref}; +use tari_crypto::tari_utilities::ByteArray; + +const MAX_ARR_SIZE: usize = 63; + +#[derive(Clone, Debug)] +pub struct FixedByteArray { + elems: [u8; MAX_ARR_SIZE], + len: u8, +} + +impl FixedByteArray { + pub fn new() -> Self { + Default::default() + } + + pub fn as_slice(&self) -> &[u8] { + &self[..self.len()] + } + + /// Pushes a byte to the end of the array. + /// + /// ## Panics + /// + /// Panics if the array is full. + // NOTE: This should be a private function + fn push(&mut self, elem: u8) { + assert_eq!(self.is_full(), false); + self.elems[self.len()] = elem; + self.len += 1; + } + + #[inline] + pub fn is_full(&self) -> bool { + self.len() == MAX_ARR_SIZE + } + + #[inline] + pub fn len(&self) -> usize { + self.len as usize + } +} + +impl Deref for FixedByteArray { + type Target = [u8]; + + fn deref(&self) -> &Self::Target { + &self.elems[..self.len as usize] + } +} + +impl Default for FixedByteArray { + fn default() -> Self { + Self { + elems: [0u8; MAX_ARR_SIZE], + len: 0, + } + } +} + +impl ByteArray for FixedByteArray { + fn from_bytes(bytes: &[u8]) -> Result { + let len = u8::try_from(bytes.len()).map_err(|_| ByteArrayError::IncorrectLength)?; + if len > MAX_ARR_SIZE as u8 { + return Err(ByteArrayError::IncorrectLength); + } + + let mut elems = [0u8; MAX_ARR_SIZE]; + elems[..len as usize].copy_from_slice(&bytes[..len as usize]); + Ok(Self { elems, len }) + } + + fn as_bytes(&self) -> &[u8] { + self.as_slice() + } +} + +impl Decodable for FixedByteArray { + fn consensus_decode(d: &mut D) -> Result { + let len = VarInt::consensus_decode(d)?.0 as usize; + if len > MAX_ARR_SIZE { + return Err(encode::Error::ParseFailed( + "length exceeded maximum of 64-bytes for FixedByteArray", + )); + } + let mut ret = FixedByteArray::new(); + for _ in 0..len { + // PANIC: Cannot happen because len has been checked + ret.push(Decodable::consensus_decode(d)?); + } + Ok(ret) + } +} + +impl Encodable for FixedByteArray { + fn consensus_encode(&self, e: &mut E) -> Result { + self.as_slice().consensus_encode(e) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::crypto::tari_utilities::hex::Hex; + use monero::consensus; + + #[test] + fn assert_size() { + assert_eq!(std::mem::size_of::(), MAX_ARR_SIZE + 1); + } + + #[test] + fn from_bytes() { + let arr = FixedByteArray::from_bytes(&[1u8][..]).unwrap(); + assert_eq!(arr.len(), 1); + assert_eq!(arr.iter().all(|b| *b == 1), true); + // Iterates only up to len + let mut used = false; + for _ in arr.iter() { + assert_eq!(used, false); + used = true; + } + assert!(used); + + let arr = FixedByteArray::from_bytes(&[1u8; 63][..]).unwrap(); + assert_eq!(arr.len(), 63); + assert_eq!(arr.iter().all(|b| *b == 1), true); + + FixedByteArray::from_bytes(&[1u8; 64][..]).unwrap_err(); + } + + #[test] + fn serialize_deserialize() { + let data = consensus::serialize(&FixedByteArray::from_hex("ffffffffffffffffffffffffff").unwrap()); + assert_eq!(data.len(), 13 + 1); + let arr = consensus::deserialize::(&data).unwrap(); + assert_eq!(arr.iter().all(|b| *b == 0xff), true); + } + + #[test] + fn length_check() { + let mut buf = [0u8; MAX_ARR_SIZE + 1]; + buf[0] = 63; + let arr = FixedByteArray::consensus_decode(&mut io::Cursor::new(buf.clone())).unwrap(); + assert_eq!(arr.len(), MAX_ARR_SIZE); + + buf[0] = 64; + let err = FixedByteArray::consensus_decode(&mut io::Cursor::new(buf.clone())).unwrap_err(); + assert!(matches!(err, encode::Error::ParseFailed(_))); + + // VarInt encoding that doesnt terminate, but _would_ represent a number < MAX_ARR_SIZE + buf[0] = 0b1000000; + buf[1] = 0b1000000; + let err = FixedByteArray::consensus_decode(&mut io::Cursor::new(buf.clone())).unwrap_err(); + assert!(matches!(err, encode::Error::ParseFailed(_))); + } + + #[test] + fn capacity_overflow_does_not_panic() { + let data = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]; + let _ = consensus::deserialize::(data); + } +} diff --git a/base_layer/core/src/proof_of_work/monero_rx/helpers.rs b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs new file mode 100644 index 00000000000..84cb1a9770f --- /dev/null +++ b/base_layer/core/src/proof_of_work/monero_rx/helpers.rs @@ -0,0 +1,593 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +use super::{FixedByteArray, MoneroPowData}; +use crate::{ + proof_of_work::monero_rx::{create_merkle_proof, merkle_tree::tree_hash, MergeMineError}, + tari_utilities::hex::HexError, +}; +use monero::{blockdata::transaction::SubField, consensus, cryptonote::hash::Hashable, VarInt}; +use std::iter; + +pub fn extract_tari_hash(monero: &monero::Block) -> Option<&monero::Hash> { + for item in monero.miner_tx.prefix.extra.0.iter() { + if let SubField::MergeMining(_depth, merge_mining_hash) = item { + return Some(merge_mining_hash); + } + } + None +} + +pub fn deserialize_monero_block_from_hex(data: T) -> Result +where T: AsRef<[u8]> { + let bytes = hex::decode(data).map_err(|_| HexError::HexConversionError)?; + let obj = consensus::deserialize::(&bytes) + .map_err(|_| MergeMineError::ValidationError("blocktemplate blob invalid".to_string()))?; + Ok(obj) +} + +pub fn serialize_monero_block_to_hex(obj: &monero::Block) -> Result { + let data = consensus::serialize::(obj); + let bytes = hex::encode(data); + Ok(bytes) +} + +pub fn construct_monero_data(block: monero::Block, seed: FixedByteArray) -> Result { + let hashes = create_ordered_transaction_hashes_from_block(&block); + let root = tree_hash(&hashes)?; + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).ok_or_else(|| { + MergeMineError::ValidationError( + "create_merkle_proof returned None because the block had no coinbase (which is impossible because the \ + Block type does not allow that)" + .to_string(), + ) + })?; + Ok(MoneroPowData { + header: block.header, + randomx_key: seed, + transaction_count: hashes.len() as u16, + merkle_root: root, + coinbase_merkle_proof, + coinbase_tx: block.miner_tx, + }) +} + +/// Creates a hex encoded Monero blockhashing_blob that's used by the pow hash +pub fn create_blockhashing_blob_from_block(block: &monero::Block) -> Result { + let tx_hashes = create_ordered_transaction_hashes_from_block(block); + let root = tree_hash(&tx_hashes)?; + let blob = create_block_hashing_blob(&block.header, &root, tx_hashes.len() as u64); + Ok(hex::encode(&blob)) +} + +pub fn create_ordered_transaction_hashes_from_block(block: &monero::Block) -> Vec { + iter::once(block.miner_tx.hash()) + .chain(block.tx_hashes.clone()) + .collect() +} + +/// Appends merge mining hash to a Monero block +pub fn append_merge_mining_tag>(block: &mut monero::Block, hash: T) -> Result<(), MergeMineError> { + if hash.as_ref().len() != monero::Hash::len_bytes() { + return Err(MergeMineError::HashingError(format!( + "Expected source to be {} bytes, but it was {} bytes", + monero::Hash::len_bytes(), + hash.as_ref().len() + ))); + } + let hash = monero::Hash::from_slice(hash.as_ref()); + let mm_tag = SubField::MergeMining(VarInt(0), hash); + block.miner_tx.prefix.extra.0.push(mm_tag); + Ok(()) +} + +/// Creates a hex encoded Monero blockhashing_blob +pub fn create_block_hashing_blob( + header: &monero::BlockHeader, + merkle_root: &monero::Hash, + transaction_count: u64, +) -> Vec { + let mut blockhashing_blob = consensus::serialize(header); + blockhashing_blob.extend_from_slice(merkle_root.as_bytes()); + let mut count = consensus::serialize(&VarInt(transaction_count)); + blockhashing_blob.append(&mut count); + blockhashing_blob +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + proof_of_work::{monero_rx::fixed_array::FixedByteArray, PowAlgorithm, ProofOfWork}, + tari_utilities::{ + hex::{from_hex, Hex}, + ByteArray, + }, + }; + use monero::{ + blockdata::transaction::{ExtraField, TxOutTarget}, + consensus::deserialize, + cryptonote::hash::Hashable, + util::ringct::{RctSig, RctSigBase, RctType}, + PublicKey, + Transaction, + TransactionPrefix, + TxIn, + TxOut, + }; + use tari_test_utils::unpack_enum; + + // This tests checks the hash of monero-rs + #[test] + fn test_monero_rs_miner_tx_hash() { + let tx = "f8ad7c58e6fce1792dd78d764ce88a11db0e3c3bb484d868ae05a7321fb6c6b0"; + + let pk_extra = vec![ + 179, 155, 220, 223, 213, 23, 81, 160, 95, 232, 87, 102, 151, 63, 70, 249, 139, 40, 110, 16, 51, 193, 175, + 208, 38, 120, 65, 191, 155, 139, 1, 4, + ]; + let transaction = Transaction { + prefix: TransactionPrefix { + version: VarInt(2), + unlock_time: VarInt(2143845), + inputs: vec![TxIn::Gen { + height: VarInt(2143785), + }], + outputs: vec![TxOut { + amount: VarInt(1550800739964), + target: TxOutTarget::ToKey { + key: PublicKey::from_slice( + hex::decode("e2e19d8badb15e77c8e1f441cf6acd9bcde34a07cae82bbe5ff9629bf88e6e81") + .unwrap() + .as_slice(), + ) + .unwrap(), + }, + }], + extra: ExtraField { + 0: vec![ + SubField::TxPublicKey(PublicKey::from_slice(pk_extra.as_slice()).unwrap()), + SubField::Nonce(vec![196, 37, 4, 0, 27, 37, 187, 163, 0, 0, 0, 0, 0, 0, 0, 0, 0]), + ], + }, + }, + signatures: vec![], + rct_signatures: RctSig { + sig: Option::from(RctSigBase { + rct_type: RctType::Null, + txn_fee: Default::default(), + pseudo_outs: vec![], + ecdh_info: vec![], + out_pk: vec![], + }), + p: None, + }, + }; + assert_eq!( + tx.as_bytes().to_vec(), + hex::encode(transaction.hash().0.to_vec()).as_bytes().to_vec() + ); + let hex = hex::encode(consensus::serialize::(&transaction)); + deserialize::(&hex::decode(&hex).unwrap()).unwrap(); + } + + // This tests checks the blockhashing blob of monero-rs + #[test] + fn test_monero_rs_block_serialize() { + // block with only the miner tx and no other transactions + let hex = "0c0c94debaf805beb3489c722a285c092a32e7c6893abfc7d069699c8326fc3445a749c5276b6200000000029b892201ffdf882201b699d4c8b1ec020223df524af2a2ef5f870adb6e1ceb03a475c39f8b9ef76aa50b46ddd2a18349402b012839bfa19b7524ec7488917714c216ca254b38ed0424ca65ae828a7c006aeaf10208f5316a7f6b99cca60000"; + // blockhashing blob for above block as accepted by monero + let hex_blockhash_blob="0c0c94debaf805beb3489c722a285c092a32e7c6893abfc7d069699c8326fc3445a749c5276b6200000000602d0d4710e2c2d38da0cce097accdf5dc18b1d34323880c1aae90ab8f6be6e201"; + let bytes = hex::decode(hex).unwrap(); + let block = deserialize::(&bytes[..]).unwrap(); + let header = consensus::serialize::(&block.header); + let tx_count = 1 + block.tx_hashes.len() as u64; + let mut count = consensus::serialize::(&VarInt(tx_count)); + let mut hashes = Vec::with_capacity(tx_count as usize); + hashes.push(block.miner_tx.hash()); + for item in block.clone().tx_hashes { + hashes.push(item); + } + let root = tree_hash(&hashes).unwrap(); + let mut encode2 = header; + encode2.extend_from_slice(root.as_bytes()); + encode2.append(&mut count); + assert_eq!(hex::encode(encode2), hex_blockhash_blob); + let bytes2 = consensus::serialize::(&block); + assert_eq!(bytes, bytes2); + let hex2 = hex::encode(bytes2); + assert_eq!(hex, hex2); + } + + #[test] + fn test_monero_data() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let hash = block_header.merged_mining_hash(); + append_merge_mining_tag(&mut block, hash).unwrap(); + let hashes = create_ordered_transaction_hashes_from_block(&block); + assert_eq!(hashes.len(), block.tx_hashes.len() + 1); + let root = tree_hash(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + + let monero_data = MoneroPowData { + header: block.header, + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: hashes.len() as u16, + merkle_root: root, + coinbase_merkle_proof, + coinbase_tx: block.miner_tx, + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + MoneroPowData::from_header(&block_header).unwrap(); + } + + #[test] + fn test_input_blob() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let block = deserialize::(&bytes[..]).unwrap(); + let input_blob = create_blockhashing_blob_from_block(&block).unwrap(); + assert_eq!(input_blob, "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000058b030b6800d433bbcb2b560afe2a08e4dc152fa77ead96d37aaf14897d3c09601"); + } + + #[test] + fn test_append_mm_tag() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let hash = block_header.merged_mining_hash(); + append_merge_mining_tag(&mut block, hash).unwrap(); + let count = 1 + (block.tx_hashes.len() as u16); + let mut hashes = Vec::with_capacity(count as usize); + hashes.push(block.miner_tx.hash()); + // Note: tx_hashes is empty, so |hashes| == 1 + for item in block.clone().tx_hashes { + hashes.push(item); + } + let root = tree_hash(&hashes).unwrap(); + assert_eq!(root, hashes[0]); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { + header: block.header, + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, + coinbase_tx: block.miner_tx, + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + verify_header(&block_header).unwrap(); + } + + #[test] + fn test_append_mm_tag_no_tag() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let block = deserialize::(&bytes[..]).unwrap(); + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let count = 1 + (block.tx_hashes.len() as u16); + let mut hashes = Vec::with_capacity(count as usize); + hashes.push(block.miner_tx.hash()); + for item in block.clone().tx_hashes { + hashes.push(item); + } + let root = tree_hash(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { + header: block.header, + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, + coinbase_tx: block.miner_tx, + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + let err = verify_header(&block_header).unwrap_err(); + unpack_enum!(MergeMineError::ValidationError(details) = err); + assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); + } + + #[test] + fn test_append_mm_tag_wrong_hash() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let hash = Hash::null_hash(); + append_merge_mining_tag(&mut block, hash).unwrap(); + let count = 1 + (block.tx_hashes.len() as u16); + let mut hashes = Vec::with_capacity(count as usize); + let mut proof = Vec::with_capacity(count as usize); + hashes.push(block.miner_tx.hash()); + proof.push(block.miner_tx.hash()); + for item in block.clone().tx_hashes { + hashes.push(item); + proof.push(item); + } + let root = tree_hash(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { + header: block.header, + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, + coinbase_tx: block.miner_tx, + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + let err = verify_header(&block_header).unwrap_err(); + unpack_enum!(MergeMineError::ValidationError(details) = err); + assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); + } + + #[test] + fn test_verify_header_no_coinbase() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let hash = block_header.merged_mining_hash(); + append_merge_mining_tag(&mut block, hash).unwrap(); + let count = 1 + (block.tx_hashes.len() as u16); + let mut hashes = Vec::with_capacity(count as usize); + let mut proof = Vec::with_capacity(count as usize); + hashes.push(block.miner_tx.hash()); + proof.push(block.miner_tx.hash()); + for item in block.clone().tx_hashes { + hashes.push(item); + proof.push(item); + } + let root = tree_hash(&hashes).unwrap(); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { + header: block.header, + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, + coinbase_tx: Default::default(), + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + let err = verify_header(&block_header).unwrap_err(); + unpack_enum!(MergeMineError::ValidationError(details) = err); + assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); + } + + #[test] + fn test_verify_header_no_data() { + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let monero_data = MoneroPowData { + header: Default::default(), + randomx_key: FixedByteArray::default(), + transaction_count: 1, + merkle_root: Default::default(), + coinbase_merkle_proof: create_merkle_proof(&[Hash::null_hash()], &Hash::null_hash()).unwrap(), + coinbase_tx: Default::default(), + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + let err = verify_header(&block_header).unwrap_err(); + unpack_enum!(MergeMineError::ValidationError(details) = err); + assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); + } + + #[test] + fn test_verify_invalid_root() { + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); + let bytes = hex::decode(blocktemplate_blob).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block_header = BlockHeader { + version: 0, + height: 0, + prev_hash: vec![0], + timestamp: Default::default(), + output_mr: vec![0], + range_proof_mr: vec![0], + output_mmr_size: 0, + kernel_mr: vec![0], + kernel_mmr_size: 0, + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), + nonce: 0, + pow: ProofOfWork::default(), + }; + let hash = block_header.merged_mining_hash(); + append_merge_mining_tag(&mut block, hash).unwrap(); + let count = 1 + (block.tx_hashes.len() as u16); + let mut hashes = Vec::with_capacity(count as usize); + let mut proof = Vec::with_capacity(count as usize); + hashes.push(block.miner_tx.hash()); + proof.push(block.miner_tx.hash()); + for item in block.clone().tx_hashes { + hashes.push(item); + proof.push(item); + } + + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { + header: block.header, + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: Hash::null_hash(), + coinbase_merkle_proof, + coinbase_tx: block.miner_tx, + }; + let serialized = consensus::serialize(&monero_data); + let pow = ProofOfWork { + pow_algo: PowAlgorithm::Monero, + pow_data: serialized, + }; + block_header.pow = pow; + let err = verify_header(&block_header).unwrap_err(); + unpack_enum!(MergeMineError::InvalidMerkleRoot = err); + } + + #[test] + fn test_difficulty() { + // Taken from block: https://stagenet.xmrchain.net/search?value=672576 + let versions = "0c0c"; + // Tool for encoding VarInts: + // https://gchq.github.io/CyberChef/#recipe=VarInt_Encode()To_Hex('Space',0)From_Hex('Auto'/disabled)VarInt_Decode(/disabled)&input=MTYwMTAzMTIwMg + let timestamp = "a298b7fb05"; // 1601031202 + let prev_block = "046f4fe371f9acdc27c377f4adee84e93b11f89246a74dd77f1bf0856141da5c"; + let nonce = "FE394F12"; // 307182078 + let tx_hash = "77139305ea53cfe95cf7235d2fed6fca477395b019b98060acdbc0f8fb0b8b92"; // miner tx + let count = "01"; + + let input = from_hex(&format!( + "{}{}{}{}{}{}", + versions, timestamp, prev_block, nonce, tx_hash, count + )) + .unwrap(); + let key = from_hex("2aca6501719a5c7ab7d4acbc7cc5d277b57ad8c27c6830788c2d5a596308e5b1").unwrap(); + let rx = RandomXFactory::default(); + + let (difficulty, hash) = get_random_x_difficulty(&input, &rx.create(&key).unwrap()).unwrap(); + assert_eq!( + hash.to_hex(), + "f68fbc8cc85bde856cd1323e9f8e6f024483038d728835de2f8c014ff6260000" + ); + assert_eq!(difficulty.as_u64(), 430603); + } +} diff --git a/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs new file mode 100644 index 00000000000..eeb7f2ea4a2 --- /dev/null +++ b/base_layer/core/src/proof_of_work/monero_rx/merkle_tree.rs @@ -0,0 +1,605 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +//! Port of monero's tree hash algorithm +//! +//! See https://github.com/monero-project/monero/blob/master/src/crypto/tree-hash.c + +use crate::proof_of_work::monero_rx::error::MergeMineError; +use monero::{ + consensus::{encode, Decodable, Encodable}, + Hash, +}; +use std::io; + +/// Returns the Keccak 256 hash of the byte input +fn cn_fast_hash(data: &[u8]) -> Hash { + Hash::hash(data) +} + +/// Returns the Keccak 256 hash of 2 hashes +fn cn_fast_hash2(hash1: &Hash, hash2: &Hash) -> Hash { + let mut tmp = [0u8; 64]; + tmp[..32].copy_from_slice(hash1.as_bytes()); + tmp[32..].copy_from_slice(hash2.as_bytes()); + cn_fast_hash(&tmp) +} + +/// Round down to power of two. Will return an error for count < 3 or if the count is unreasonably large for tree hash +/// calculations. +fn tree_hash_count(count: usize) -> Result { + if count < 3 { + return Err(MergeMineError::HashingError(format!( + "Cannot calculate tree hash root. Expected count to be greater than 3 but got {}", + count + ))); + } + + if count > 0x10000000 { + return Err(MergeMineError::HashingError(format!( + "Cannot calculate tree hash root. Expected count to be less than 0x10000000 but got {}", + count + ))); + } + + // Essentially we are doing 1 << floor(log2(count)) + let mut pow: usize = 2; + while pow < count { + pow <<= 1; + } + + Ok(pow >> 1) +} + +/// Tree hash algorithm in monero +pub fn tree_hash(hashes: &[Hash]) -> Result { + if hashes.is_empty() { + return Err(MergeMineError::HashingError( + "Cannot calculate merkle root, `hashes` is empty".to_string(), + )); + } + + match hashes.len() { + 1 => Ok(hashes[0]), + 2 => Ok(cn_fast_hash2(&hashes[0], &hashes[1])), + n => { + let mut cnt = tree_hash_count(n)?; + let mut buf = vec![Hash::null_hash(); cnt]; + + // c is the number of elements between the number of hashes and the next power of 2. + let c = 2 * cnt - hashes.len(); + + buf[..c].copy_from_slice(&hashes[..c]); + + // Hash the rest of the hashes together to + let mut i: usize = c; + for b in &mut buf[c..cnt] { + *b = cn_fast_hash2(&hashes[i], &hashes[i + 1]); + i += 2; + } + + if i != hashes.len() { + return Err(MergeMineError::HashingError( + "Cannot calculate the merkle root, hashes not equal to count".to_string(), + )); + } + + while cnt > 2 { + cnt >>= 1; + let mut i = 0; + for j in 0..cnt { + buf[j] = cn_fast_hash2(&buf[i], &buf[i + 1]); + i += 2; + } + } + + Ok(cn_fast_hash2(&buf[0], &buf[1])) + }, + } +} + +#[derive(Debug, Clone)] +pub struct MerkleProof { + branch: Vec, + depth: u16, + path_bitmap: u32, +} + +impl MerkleProof { + fn try_construct(branch: Vec, depth: u16, path_bitmap: u32) -> Option { + if branch.is_empty() { + return None; + } + + Some(Self { + branch, + depth, + path_bitmap, + }) + } + + #[inline] + pub fn branch(&self) -> &[Hash] { + &self.branch + } + + pub fn calculate_root(&self, hash: &Hash) -> Hash { + if self.depth == 0 { + return self.branch[0]; + } + + let mut root = *hash; + for d in 0..self.depth { + if (self.path_bitmap >> (self.depth - d - 1)) & 1 > 0 { + root = cn_fast_hash2(&self.branch[d as usize], &root); + } else { + root = cn_fast_hash2(&root, &self.branch[d as usize]); + } + } + + root + } +} + +impl Default for MerkleProof { + fn default() -> Self { + Self { + branch: vec![Hash::null_hash()], + depth: 0, + path_bitmap: 0, + } + } +} + +impl Decodable for MerkleProof { + fn consensus_decode(d: &mut D) -> Result { + Ok(Self { + branch: Decodable::consensus_decode(d)?, + depth: Decodable::consensus_decode(d)?, + path_bitmap: Decodable::consensus_decode(d)?, + }) + } +} + +impl Encodable for MerkleProof { + fn consensus_encode(&self, e: &mut E) -> Result { + let mut len = self.branch.consensus_encode(e)?; + len += self.depth.consensus_encode(e)?; + len += self.path_bitmap.consensus_encode(e)?; + Ok(len) + } +} + +/// Creates a merkle proof for the given hash within the set of hashes. This function returns None if the hash is not in +/// hashes. This is a port of Monero's tree_branch function +// TODO: Reduce the cognitive complexity of this function +#[allow(clippy::cognitive_complexity)] +pub fn create_merkle_proof(hashes: &[Hash], hash: &Hash) -> Option { + match hashes.len() { + 0 => None, + 1 => { + if hashes[0] != *hash { + return None; + } + MerkleProof::try_construct(vec![hashes[0]], 0, 0) + }, + 2 => hashes.iter().enumerate().find_map(|(pos, h)| { + if h != hash { + return None; + } + let i = if pos == 0 { 1 } else { 0 }; + MerkleProof::try_construct(vec![hashes[i]], 1, if pos == 0 { 0 } else { 1 }) + }), + len => { + let mut idx = hashes.iter().position(|node| node == hash)?; + let mut count = tree_hash_count(len).ok()?; + + let mut ints = vec![Hash::null_hash(); count]; + + let c = 2 * count - len; + ints[..c].copy_from_slice(&hashes[..c]); + + let mut branch = Vec::new(); + let mut depth = 0u16; + let mut path = 0u32; + let mut i = c; + for (j, val) in ints.iter_mut().enumerate().take(count).skip(c) { + // Left or right + if idx == i || idx == i + 1 { + let ii = if idx == i { i + 1 } else { i }; + branch.push(hashes[ii]); + depth += 1; + path = (path << 1) | (if idx == i { 0 } else { 1 }); + idx = j; + } + *val = cn_fast_hash2(&hashes[i], &hashes[i + 1]); + i += 2; + } + + debug_assert_eq!(i, len); + + while count > 2 { + count >>= 1; + let mut i = 0; + for j in 0..count { + if idx == i || idx == i + 1 { + let ii = if idx == i { i + 1 } else { i }; + branch.push(ints[ii]); + depth += 1; + path = (path << 1) | (if idx == i { 0 } else { 1 }); + idx = j; + } + ints[j] = cn_fast_hash2(&ints[i], &ints[i + 1]); + i += 2; + } + } + + if idx == 0 || idx == 1 { + let ii = if idx == 0 { 1 } else { 0 }; + branch.push(ints[ii]); + depth += 1; + path = (path << 1) | (if idx == 0 { 0 } else { 1 }); + } + + MerkleProof::try_construct(branch, depth, path) + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::{ + crypto::tari_utilities::hex::Hex, + proof_of_work::randomx_factory::RandomXFactory, + tari_utilities::hex::from_hex, + }; + use monero::{ + blockdata::block::BlockHeader, + consensus::encode::{serialize, VarInt}, + }; + use std::{iter, str::FromStr}; + use tari_test_utils::unpack_enum; + + mod tree_hash { + use super::*; + + fn randomx_hash(input: &[u8], key: &str) -> String { + let key = from_hex(key).unwrap(); + RandomXFactory::default() + .create(&key) + .unwrap() + .calculate_hash(input) + .unwrap() + .to_hex() + } + + #[test] + fn test_tree_hash() { + let tx_hash = [ + 88, 176, 48, 182, 128, 13, 67, 59, 188, 178, 181, 96, 175, 226, 160, 142, 77, 193, 82, 250, 119, 234, + 217, 109, 55, 170, 241, 72, 151, 211, 192, 150, + ]; + let mut hashes = vec![Hash::from(tx_hash)]; + + // Single hash + let mut root = tree_hash(&hashes).unwrap(); + assert_eq!(root.as_bytes(), tx_hash); + + // 2 hashes + hashes.push(Hash::from(tx_hash)); + root = tree_hash(&hashes).unwrap(); + let correct_root = [ + 187, 251, 201, 6, 70, 27, 80, 117, 95, 97, 244, 143, 194, 245, 73, 174, 158, 255, 98, 175, 74, 22, 173, + 223, 217, 17, 59, 183, 230, 39, 76, 202, + ]; + assert_eq!(root.as_bytes(), correct_root); + + // More than 2 hashes + hashes.push(Hash::from(tx_hash)); + root = tree_hash(&hashes).unwrap(); + let correct_root = [ + 37, 100, 243, 131, 133, 33, 135, 169, 23, 215, 243, 10, 213, 152, 21, 10, 89, 86, 217, 49, 245, 237, + 205, 194, 102, 162, 128, 225, 215, 192, 158, 251, + ]; + assert_eq!(root.as_bytes(), correct_root); + + hashes.push(Hash::from(tx_hash)); + root = tree_hash(&hashes).unwrap(); + let correct_root = [ + 52, 199, 248, 213, 213, 138, 52, 0, 145, 179, 81, 247, 174, 31, 183, 196, 124, 186, 100, 21, 36, 252, + 171, 66, 250, 247, 122, 64, 36, 127, 184, 46, + ]; + assert_eq!(root.as_bytes(), correct_root); + } + + #[test] + fn tree_hash_4_elements() { + let hashes = (1..=4).map(|i| Hash::from([i; 32])).collect::>(); + let h01 = cn_fast_hash2(&hashes[0], &hashes[1]); + let h23 = cn_fast_hash2(&hashes[2], &hashes[3]); + let expected = cn_fast_hash2(&h01, &h23); + + let root_hash = tree_hash(&hashes).unwrap(); + assert_eq!(root_hash, expected); + } + + #[test] + fn tree_hash_6_elements() { + // { root } + // / \ + // h01 h2345 + // / \ / \ + // 0 1 h23 h45 + // / \ / \ + // 2 3 4 5 + + let hashes = (1..=6).map(|i| Hash::from([i; 32])).collect::>(); + let h23 = cn_fast_hash2(&hashes[2], &hashes[3]); + let h45 = cn_fast_hash2(&hashes[4], &hashes[5]); + let h01 = cn_fast_hash2(&hashes[0], &hashes[1]); + let h2345 = cn_fast_hash2(&h23, &h45); + + let h012345 = cn_fast_hash2(&h01, &h2345); + + let root_hash = tree_hash(&hashes).unwrap(); + assert_eq!(root_hash, h012345); + } + + #[test] + fn check_tree_hash_against_mainnet_block() { + // Data from block https://xmrchain.net/search?value=2375600 + let header = BlockHeader { + major_version: VarInt(14), + minor_version: VarInt(14), + timestamp: VarInt(1622783559), + prev_id: Hash::from_str("fd3ce7d80ec86167f74e52cacc0eb8bd8c9e674786fc2cbbaee5879eab906986").unwrap(), + nonce: 16657, + }; + let tx_hashes = &[ + "d96756959949db23764592fea0bfe88c790e1fd131dabb676948b343aa9ecc24", + "77d1a87df131c36da4832a7ec382db9b8fe947576a60ec82cc1c66a220f6ee42", + "c723329b1036e4e05313c6ec3bdda3a2e1ab4db17661cad1a6a33512d9b86bcd", + "5d863b3d275bacd46dbe8a5f3edce86f88cbc01232bd2788b6f44684076ef8a8", + "16d945de6c96ea7f986b6c70ad373a9203a1ddd1c5d12effc3c69b8648826deb", + "ccec8f06c5bab1b87bb9af1a3cba94304f87dc037e03b5d2a00406d399316ff7", + "c8d52ed0712f0725531f8f72da029201b71e9e215884015f7050dde5f33269e7", + "4360ba7fe3872fa8bbc9655486a02738ee000d0c48bda84a15d4730fea178519", + "3c8c6b54dcffc75abff89d604ebf1e216bfcb2844b9720ab6040e8e49ae9743c", + "6dc19de81e509fba200b652fbdde8fe2aeb99bb9b17e0af79d0c682dff194e08", + "3ef031981bc4e2375eebd034ffda4e9e89936962ad2c94cfcc3e6d4cfa8a2e8c", + "9e4b865ebe51dcc9cfb09a9b81e354b8f423c59c902d5a866919f053bfbc374e", + "fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a", + ] + .iter() + .map(|hash| Hash::from_str(hash).unwrap()) + .collect::>(); + + let num_transactions = VarInt(tx_hashes.len() as u64); + + let tx_root = tree_hash(&tx_hashes).unwrap(); + let mut blob = Vec::new(); + blob.extend(serialize(&header)); + blob.extend_from_slice(tx_root.as_bytes()); + blob.extend(serialize(&num_transactions)); + + let pow_hash = randomx_hash( + &blob, + "85170d70e15e4035c3e664a8192f11d347d2939371d840e3f65db5a6645c571d", + ); + let expected_pow_hash = "53f9876405e60c1d37a67b4cf09670061c745a18c70f89dc2d61020100000000"; + assert_eq!(&pow_hash, expected_pow_hash); + } + + #[test] + fn check_tree_hash_against_empty_stagenet_block() { + // Taken from block: https://stagenet.xmrchain.net/search?value=672576 + let header = BlockHeader { + major_version: VarInt(12), + minor_version: VarInt(12), + timestamp: VarInt(1601031202), + prev_id: Hash::from_str("046f4fe371f9acdc27c377f4adee84e93b11f89246a74dd77f1bf0856141da5c").unwrap(), + nonce: 307182078, + }; + let num_transactions = VarInt(1); + let tx_hashes = &["77139305ea53cfe95cf7235d2fed6fca477395b019b98060acdbc0f8fb0b8b92"] + .iter() + .map(|hash| Hash::from_str(hash).unwrap()) + .collect::>(); + + let tx_root = tree_hash(&tx_hashes).unwrap(); + let mut blob = Vec::new(); + blob.extend(serialize(&header)); + blob.extend_from_slice(tx_root.as_bytes()); + blob.extend(serialize(&num_transactions)); + + // Key obtained by using the block hash at height `h - (h % 2048)` where `h` is the height if this block + // (672576) + let pow_hash = randomx_hash( + &blob, + "2aca6501719a5c7ab7d4acbc7cc5d277b57ad8c27c6830788c2d5a596308e5b1", + ); + let expected_pow_hash = "f68fbc8cc85bde856cd1323e9f8e6f024483038d728835de2f8c014ff6260000"; + assert_eq!(&pow_hash, expected_pow_hash); + } + + #[test] + fn test_tree_hash_fail() { + let err = tree_hash(&[]).unwrap_err(); + unpack_enum!(MergeMineError::HashingError(_e) = err); + } + } + + mod create_merkle_proof { + use super::*; + use rand::RngCore; + + #[test] + fn empty_hashset_has_no_proof() { + assert!(create_merkle_proof(&[], &Hash::null_hash()).is_none()); + } + + #[test] + fn single_hash_is_its_own_proof() { + let tx_hashes = + &[Hash::from_str("fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a").unwrap()]; + let proof = create_merkle_proof(&tx_hashes[..], &tx_hashes[0]).unwrap(); + assert_eq!(proof.depth, 0); + assert_eq!(proof.calculate_root(&tx_hashes[0]), tx_hashes[0]); + assert_eq!(proof.branch(), tx_hashes); + + assert!(create_merkle_proof(&tx_hashes[..], &Hash::null_hash()).is_none()); + } + + #[test] + fn two_hash_proof_construction() { + let tx_hashes = &[ + "d96756959949db23764592fea0bfe88c790e1fd131dabb676948b343aa9ecc24", + "77d1a87df131c36da4832a7ec382db9b8fe947576a60ec82cc1c66a220f6ee42", + ] + .iter() + .map(|hash| Hash::from_str(hash).unwrap()) + .collect::>(); + + let expected_root = cn_fast_hash2(&tx_hashes[0], &tx_hashes[1]); + let proof = create_merkle_proof(&tx_hashes, &tx_hashes[0]).unwrap(); + assert_eq!(proof.branch()[0], tx_hashes[1]); + assert_eq!(proof.calculate_root(&tx_hashes[0]), expected_root); + + let proof = create_merkle_proof(&tx_hashes, &tx_hashes[1]).unwrap(); + assert_eq!(proof.branch()[0], tx_hashes[0]); + assert_eq!(proof.calculate_root(&tx_hashes[1]), expected_root); + + assert!(create_merkle_proof(&tx_hashes, &Hash::null_hash()).is_none()); + } + + #[test] + fn simple_proof_construction() { + // { root } + // / \ + // h01 h2345 + // / \ / \ + // h0 h1 h23 h45 + // / \ / \ + // h2 h3 h4 h5 + + let hashes = (1..=6).map(|i| Hash::from([i; 32])).collect::>(); + let h23 = cn_fast_hash2(&hashes[2], &hashes[3]); + let h45 = cn_fast_hash2(&hashes[4], &hashes[5]); + let h01 = cn_fast_hash2(&hashes[0], &hashes[1]); + let h2345 = cn_fast_hash2(&h23, &h45); + let expected_root = cn_fast_hash2(&h01, &h2345); + + // Proof for h0 + let proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + assert_eq!(proof.calculate_root(&hashes[0]), expected_root); + assert_eq!(proof.depth, 2); + assert_eq!(proof.branch().len(), 2); + assert_eq!(proof.branch()[0], hashes[1]); + assert_eq!(proof.branch()[1], h2345); + assert_eq!(proof.path_bitmap, 0b00000000); + + // Proof for h2 + let proof = create_merkle_proof(&hashes, &hashes[2]).unwrap(); + assert_eq!(proof.calculate_root(&hashes[2]), expected_root); + assert_eq!(proof.path_bitmap, 0b00000001); + let branch = proof.branch(); + assert_eq!(branch[0], hashes[3]); + assert_eq!(branch[1], h45); + assert_eq!(branch[2], h01); + assert_eq!(branch.len(), 3); + + // Proof for h5 + let proof = create_merkle_proof(&hashes, &hashes[5]).unwrap(); + assert_eq!(proof.calculate_root(&hashes[5]), expected_root); + assert_eq!(proof.path_bitmap, 0b00000111); + let branch = proof.branch(); + assert_eq!(branch[0], hashes[4]); + assert_eq!(branch[1], h23); + assert_eq!(branch[2], h01); + assert_eq!(branch.len(), 3); + } + + #[test] + fn more_complex_proof_construction() { + let tx_hashes = &[ + "d96756959949db23764592fea0bfe88c790e1fd131dabb676948b343aa9ecc24", + "77d1a87df131c36da4832a7ec382db9b8fe947576a60ec82cc1c66a220f6ee42", + "c723329b1036e4e05313c6ec3bdda3a2e1ab4db17661cad1a6a33512d9b86bcd", + "5d863b3d275bacd46dbe8a5f3edce86f88cbc01232bd2788b6f44684076ef8a8", + "16d945de6c96ea7f986b6c70ad373a9203a1ddd1c5d12effc3c69b8648826deb", + "ccec8f06c5bab1b87bb9af1a3cba94304f87dc037e03b5d2a00406d399316ff7", + "c8d52ed0712f0725531f8f72da029201b71e9e215884015f7050dde5f33269e7", + "4360ba7fe3872fa8bbc9655486a02738ee000d0c48bda84a15d4730fea178519", + "3c8c6b54dcffc75abff89d604ebf1e216bfcb2844b9720ab6040e8e49ae9743c", + "6dc19de81e509fba200b652fbdde8fe2aeb99bb9b17e0af79d0c682dff194e08", + "3ef031981bc4e2375eebd034ffda4e9e89936962ad2c94cfcc3e6d4cfa8a2e8c", + "9e4b865ebe51dcc9cfb09a9b81e354b8f423c59c902d5a866919f053bfbc374e", + "fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a", + ] + .iter() + .map(|hash| Hash::from_str(hash).unwrap()) + .collect::>(); + + let expected_root = tree_hash(&tx_hashes).unwrap(); + + let hash = Hash::from_str("fa58575f7d1d377709f1621fac98c758860ca6dc5f2262be9ce5fd131c370d1a").unwrap(); + let proof = create_merkle_proof(&tx_hashes, &hash).unwrap(); + + assert_eq!(proof.depth, 4); + assert_eq!(proof.path_bitmap, 0b00001111); + + assert_eq!(proof.calculate_root(&hash), expected_root); + + assert_eq!(proof.branch().contains(&hash), false); + assert_eq!(proof.branch().contains(&expected_root), false); + } + + #[test] + fn big_proof_construction() { + // 65536 transactions is beyond what is reasonable to fit in a block + let mut thread_rng = rand::thread_rng(); + let tx_hashes = iter::repeat(()) + .take(0x10000) + .map(|_| { + let mut buf = [0u8; 32]; + thread_rng.fill_bytes(&mut buf[..]); + // Actually performing the keccak hash serves no purpose in this test + Hash::from_slice(&buf[..]) + }) + .collect::>(); + + let expected_root = tree_hash(&tx_hashes).unwrap(); + + let hash = tx_hashes.last().unwrap(); + let proof = create_merkle_proof(&tx_hashes, hash).unwrap(); + + assert_eq!(proof.depth, 16); + assert_eq!(proof.path_bitmap, 0b1111_1111_1111_1111); + + assert_eq!(proof.calculate_root(&hash), expected_root); + + assert_eq!(proof.branch().contains(&hash), false); + assert_eq!(proof.branch().contains(&expected_root), false); + } + } +} diff --git a/base_layer/core/src/proof_of_work/monero_rx.rs b/base_layer/core/src/proof_of_work/monero_rx/mod.rs similarity index 50% rename from base_layer/core/src/proof_of_work/monero_rx.rs rename to base_layer/core/src/proof_of_work/monero_rx/mod.rs index 665932781c9..ee11ed7f8ab 100644 --- a/base_layer/core/src/proof_of_work/monero_rx.rs +++ b/base_layer/core/src/proof_of_work/monero_rx/mod.rs @@ -1,24 +1,48 @@ -// Copyright 2019. The Tari Project +// Copyright 2021, The Tari Project // -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the -// following conditions are met: +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: // -// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following -// disclaimer. +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. // -// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the -// following disclaimer in the documentation and/or other materials provided with the distribution. +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. // -// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote -// products derived from this software without specific prior written permission. +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. // -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, -// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE -// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +mod error; +pub use error::MergeMineError; + +mod helpers; +pub use helpers::{ + append_merge_mining_tag, + construct_monero_data, + create_blockhashing_blob_from_block, + deserialize_monero_block_from_hex, + extract_tari_hash, + serialize_monero_block_to_hex, +}; + +mod fixed_array; +pub use fixed_array::FixedByteArray; + +mod pow_data; +pub use pow_data::MoneroPowData; + +mod merkle_tree; +pub use merkle_tree::create_merkle_proof; + +// Re-export +pub use monero::consensus::{deserialize, serialize}; use crate::{ blocks::BlockHeader, @@ -27,193 +51,23 @@ use crate::{ randomx_factory::{RandomXFactory, RandomXVMInstance}, Difficulty, }, - tari_utilities::ByteArray, }; use log::*; -use monero::{ - blockdata::{ - block::{Block as MoneroBlock, BlockHeader as MoneroBlockHeader}, - transaction::SubField, - Transaction as MoneroTransaction, - }, - consensus::{encode::VarInt, serialize}, - cryptonote::hash::{Hash, Hashable}, -}; -use randomx_rs::RandomXError; -use serde::{Deserialize, Serialize}; -use std::{ - fmt::{Display, Error, Formatter}, - iter, -}; -use tari_crypto::tari_utilities::hex::{from_hex, Hex, HexError}; -use thiserror::Error; +use monero::{blockdata::transaction::SubField, consensus::encode::VarInt}; pub const LOG_TARGET: &str = "c::pow::monero_rx"; -#[derive(Debug, Error)] -pub enum MergeMineError { - #[error("Serialization error: {0}")] - SerializeError(String), - #[error("Error deserializing Monero data: {0}")] - DeserializeError(String), - #[error("Hashing of Monero data failed: {0}")] - HashingError(String), - #[error("RandomX error: {0}")] - RandomXError(#[from] RandomXError), - #[error("Validation error: {0}")] - ValidationError(String), - #[error("Hex conversion error: {0}")] - HexError(#[from] HexError), -} - -/// This is a struct to deserialize the data from he pow field into data required for the randomX Monero merged mine -/// pow. -#[derive(Serialize, Deserialize, Clone, Debug, Default)] -pub struct MoneroData { - // Monero header fields - // #[serde(with = "HashMoneroHeader")] - pub header: MoneroBlockHeader, - // randomX vm key - pub key: String, - // transaction count - pub count: u16, - // transaction root - pub transaction_root: [u8; 32], - // Transaction proof of work. - pub transaction_hashes: Vec<[u8; 32]>, - // Coinbase tx from Monero - pub coinbase_tx: MoneroTransaction, -} - -impl Display for MoneroData { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), Error> { - writeln!(fmt, "MoneroBlockHeader: {} ", self.header)?; - writeln!(fmt, "RandomX vm key: {}", self.key)?; - writeln!(fmt, "Monero tx count: {}", self.count.to_string())?; - writeln!(fmt, "Monero tx root: {}", self.transaction_root.to_hex())?; - writeln!(fmt, "Monero coinbase tx: {}", self.coinbase_tx) - } -} - -// Hash algorithm in monero -pub fn cn_fast_hash(data: &[u8]) -> Hash { - Hash::hash(data) -} - -// Tree hash count in monero -fn tree_hash_cnt(count: usize) -> Result { - if count < 3 { - return Err(MergeMineError::HashingError( - "Cannot calculate Monero root, algorithm path error".to_string(), - )); - } - - if count > 0x10000000 { - return Err(MergeMineError::HashingError( - "Cannot calculate Monero root, hash count too large".to_string(), - )); - } - - let mut pow: usize = 2; - while pow < count { - pow <<= 1; - } - - Ok(pow >> 1) -} - -/// Tree hash algorithm in monero -#[allow(clippy::needless_range_loop)] -pub fn tree_hash(hashes: &[Hash]) -> Result { - if hashes.is_empty() { - return Err(MergeMineError::HashingError( - "Cannot calculate Monero root, hashes is empty".to_string(), - )); - } - - match hashes.len() { - 1 => Ok(hashes[0]), - 2 => { - let mut buf: [u8; 64] = [0; 64]; - buf[..32].copy_from_slice(&hashes[0].0.to_vec()); - buf[32..].copy_from_slice(&hashes[1].0.to_vec()); - Ok(cn_fast_hash(&buf)) - }, - _ => { - let mut cnt = tree_hash_cnt(hashes.len())?; - let mut buf: Vec = Vec::with_capacity(cnt * 32); - - for i in 0..(2 * cnt - hashes.len()) { - for val in &hashes[i].0.to_vec() { - buf.push(val.to_owned()); - } - } - - for _i in (2 * cnt - hashes.len())..(cnt * 32) { - buf.push(0); - } - - let mut i: usize = 2 * cnt - hashes.len(); - for j in (2 * cnt - hashes.len())..cnt { - let mut tmp: [u8; 64] = [0; 64]; - tmp[..32].copy_from_slice(&hashes[i].0.to_vec()); - tmp[32..].copy_from_slice(&hashes[i + 1].0.to_vec()); - let tmp = cn_fast_hash(&tmp); - buf[(j * 32)..((j + 1) * 32)].copy_from_slice(&tmp.0); - i += 2; - } - - if i != hashes.len() { - return Err(MergeMineError::HashingError( - "Cannot calculate Monero root, hashes not equal to count".to_string(), - )); - } - - while cnt > 2 { - cnt >>= 1; - let mut i = 0; - for j in (0..(cnt * 32)).step_by(32) { - let tmp = cn_fast_hash(&buf[i..(i + 64)]); - buf[j..(j + 32)].copy_from_slice(&tmp.0); - i += 64; - } - } - - Ok(cn_fast_hash(&buf[..64])) - }, - } -} - -impl MoneroData { - pub fn from_header(tari_header: &BlockHeader) -> Result { - bincode::deserialize(&tari_header.pow.pow_data).map_err(|e| MergeMineError::DeserializeError(e.to_string())) - } - - pub fn from_pow_data(pow_data: &[u8]) -> Result { - bincode::deserialize(pow_data).map_err(|e| MergeMineError::DeserializeError(e.to_string())) - } -} - -/// Internal function to calculate the difficulty attained for the given block Deserialized the Monero header from the -/// provided header +/// Calculates the achieved Monero difficulty for the `BlockHeader`. An error is returned if the BlockHeader does not +/// contain valid Monero PoW data. +/// +/// There are two possible reasons why data is invalid: +/// 1. The transactions pub fn monero_difficulty(header: &BlockHeader, randomx_factory: &RandomXFactory) -> Result { - let monero = MoneroData::from_header(header)?; - verify_header(&header, &monero)?; - - debug!(target: LOG_TARGET, "Deserialized Monero data: {:?}", monero); - let MoneroData { - key, - transaction_hashes, - .. - } = monero; - - let tx_hashes = transaction_hashes.into_iter().map(Into::into).collect::>(); - let input = create_input_blob_from_parts(&monero.header, &tx_hashes)?; - debug!(target: LOG_TARGET, "RandomX input: {}", input); - let input = from_hex(&input)?; - let key_bytes = from_hex(&key)?; - let vm = randomx_factory.create(&key_bytes)?; - get_random_x_difficulty(&input, &vm).map(|(diff, _)| diff) + let monero_pow_data = verify_header(&header)?; + debug!(target: LOG_TARGET, "Valid Monero data: {:?}", monero_pow_data); + let blockhashing_blob = monero_pow_data.to_blockhashing_blob(); + let vm = randomx_factory.create(monero_pow_data.randomx_key())?; + get_random_x_difficulty(&blockhashing_blob, &vm).map(|(diff, _)| diff) } fn get_random_x_difficulty(input: &[u8], vm: &RandomXVMInstance) -> Result<(Difficulty, Vec), MergeMineError> { @@ -222,67 +76,17 @@ fn get_random_x_difficulty(input: &[u8], vm: &RandomXVMInstance) -> Result<(Diff Ok((difficulty, hash)) } -/// Appends merge mining hash to a Monero block -pub fn append_merge_mining_tag>(block: &mut MoneroBlock, hash: T) -> Result<(), MergeMineError> { - if hash.as_ref().len() != Hash::len_bytes() { - return Err(MergeMineError::HashingError(format!( - "Expected source to be {} bytes, but it was {} bytes", - Hash::len_bytes(), - hash.as_ref().len() - ))); - } - let hash = Hash::from_slice(hash.as_ref()); - let mm_tag = SubField::MergeMining(VarInt(0), hash); - block.miner_tx.prefix.extra.0.push(mm_tag); - Ok(()) -} - -/// Creates a hex encoded Monero blockhashing_blob thats used by the pow hash -pub fn create_blockhashing_blob(block: &MoneroBlock) -> Result { - let tx_hashes = create_ordered_transaction_hashes_from_block(block); - create_input_blob_from_parts(&block.header, &tx_hashes) -} - -pub fn create_ordered_transaction_hashes_from_block(block: &MoneroBlock) -> Vec { - iter::once(block.miner_tx.hash()) - .chain(block.tx_hashes.clone()) - .collect() -} - -/// Creates a hex encoded Monero blockhashing_blob -fn create_input_blob_from_parts(header: &MoneroBlockHeader, tx_hashes: &[Hash]) -> Result { - let root = tree_hash(tx_hashes)?; - let mut header = serialize(header); - header.extend_from_slice(root.as_bytes()); - let mut count = serialize(&VarInt(tx_hashes.len() as u64)); - header.append(&mut count); - Ok(hex::encode(header)) -} - -/// Utility function to transform array of hash to fixed array of [u8; 32] -pub fn from_hashes_to_array>(hashes: T) -> Vec<[u8; 32]> { - hashes.into_iter().map(|h| h.to_fixed_bytes()).collect() -} - -fn verify_root(monero_data: &MoneroData) -> Result<(), MergeMineError> { - let hashes = monero_data - .transaction_hashes - .iter() - .map(Into::into) - .collect::>(); - let root = tree_hash(&hashes)?; - - if &monero_data.transaction_root != root.as_fixed_bytes() { - return Err(MergeMineError::ValidationError( - "Transaction root did not match".to_string(), - )); - } - Ok(()) -} - -fn verify_header(header: &BlockHeader, monero_data: &MoneroData) -> Result<(), MergeMineError> { +/// Validates the monero data contained in the given header, making these assetions: +/// 1. The MoneroPowData is well-formed (i.e. can be deserialized) +/// 1. The header's merge mining hash is included in the coinbase extra field +/// 1. The merkle proof and coinbase hash produce a matching merkle root +/// +/// If these assertions pass, a valid `MoneroPowData` instance is returned +fn verify_header(header: &BlockHeader) -> Result { + let monero_data = MoneroPowData::from_header(header)?; let expected_merge_mining_hash = header.merged_mining_hash(); + // Check that the Tari MM hash is found in the monero coinbase transaction let is_found = monero_data.coinbase_tx.prefix.extra.0.iter().any(|item| match item { SubField::MergeMining(depth, merge_mining_hash) => { depth == &VarInt(0) && merge_mining_hash.as_bytes() == expected_merge_mining_hash.as_slice() @@ -295,53 +99,35 @@ fn verify_header(header: &BlockHeader, monero_data: &MoneroData) -> Result<(), M "Expected merge mining tag was not found in Monero coinbase transaction".to_string(), )); } - verify_root(monero_data)?; - // TODO: add seed check here. - Ok(()) + if !monero_data.is_valid_merkle_root() { + return Err(MergeMineError::InvalidMerkleRoot); + } + + Ok(monero_data) } #[cfg(test)] mod test { + use super::*; use crate::{ - blocks::BlockHeader, - proof_of_work::{ - monero_rx::{ - append_merge_mining_tag, - create_blockhashing_blob, - create_ordered_transaction_hashes_from_block, - from_hashes_to_array, - get_random_x_difficulty, - tree_hash, - verify_header, - MergeMineError, - MoneroData, - }, - randomx_factory::RandomXFactory, - PowAlgorithm, - ProofOfWork, + proof_of_work::{monero_rx::fixed_array::FixedByteArray, PowAlgorithm, ProofOfWork}, + tari_utilities::{ + hex::{from_hex, Hex}, + ByteArray, }, - tari_utilities::ByteArray, }; use monero::{ - blockdata::{ - block::BlockHeader as MoneroHeader, - transaction::{ExtraField, SubField, TxOutTarget}, - Block as MoneroBlock, - TransactionPrefix, - TxIn, - }, - consensus::{deserialize, encode::VarInt, serialize}, - cryptonote::hash::{Hash, Hashable}, + blockdata::transaction::{ExtraField, TxOutTarget}, + consensus, + consensus::deserialize, util::ringct::{RctSig, RctSigBase, RctType}, PublicKey, Transaction, + TransactionPrefix, + TxIn, TxOut, }; - use tari_crypto::{ - ristretto::RistrettoSecretKey, - tari_utilities::hex::{from_hex, Hex}, - }; use tari_test_utils::unpack_enum; // This tests checks the hash of monero-rs @@ -394,9 +180,7 @@ mod test { tx.as_bytes().to_vec(), hex::encode(transaction.hash().0.to_vec()).as_bytes().to_vec() ); - println!("{:?}", tx.as_bytes().to_vec()); - println!("{:?}", hex::encode(transaction.hash().0.to_vec())); - let hex = hex::encode(serialize::(&transaction)); + let hex = hex::encode(consensus::serialize::(&transaction)); deserialize::(&hex::decode(&hex).unwrap()).unwrap(); } @@ -408,10 +192,10 @@ mod test { // blockhashing blob for above block as accepted by monero let hex_blockhash_blob="0c0c94debaf805beb3489c722a285c092a32e7c6893abfc7d069699c8326fc3445a749c5276b6200000000602d0d4710e2c2d38da0cce097accdf5dc18b1d34323880c1aae90ab8f6be6e201"; let bytes = hex::decode(hex).unwrap(); - let block = deserialize::(&bytes[..]).unwrap(); - let header = serialize::(&block.header); + let block = deserialize::(&bytes[..]).unwrap(); + let header = consensus::serialize::(&block.header); let tx_count = 1 + block.tx_hashes.len() as u64; - let mut count = serialize::(&VarInt(tx_count)); + let mut count = consensus::serialize::(&VarInt(tx_count)); let mut hashes = Vec::with_capacity(tx_count as usize); hashes.push(block.miner_tx.hash()); for item in block.clone().tx_hashes { @@ -422,7 +206,7 @@ mod test { encode2.extend_from_slice(root.as_bytes()); encode2.append(&mut count); assert_eq!(hex::encode(encode2), hex_blockhash_blob); - let bytes2 = serialize::(&block); + let bytes2 = consensus::serialize::(&block); assert_eq!(bytes, bytes2); let hex2 = hex::encode(bytes2); assert_eq!(hex, hex2); @@ -433,7 +217,7 @@ mod test { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); let mut block_header = BlockHeader { version: 0, height: 0, @@ -444,80 +228,41 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; let hash = block_header.merged_mining_hash(); append_merge_mining_tag(&mut block, hash).unwrap(); let hashes = create_ordered_transaction_hashes_from_block(&block); + assert_eq!(hashes.len(), block.tx_hashes.len() + 1); let root = tree_hash(&hashes).unwrap(); - let monero_data = MoneroData { + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + + let monero_data = MoneroPowData { header: block.header, - key: seed_hash, - count: hashes.len() as u16, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: hashes.into_iter().map(|h| h.to_fixed_bytes()).collect(), + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: hashes.len() as u16, + merkle_root: root, + coinbase_merkle_proof, coinbase_tx: block.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - MoneroData::from_header(&block_header).unwrap(); - } - - #[test] - fn test_tree_hash() { - let tx_hash = [ - 88, 176, 48, 182, 128, 13, 67, 59, 188, 178, 181, 96, 175, 226, 160, 142, 77, 193, 82, 250, 119, 234, 217, - 109, 55, 170, 241, 72, 151, 211, 192, 150, - ]; - let mut hashes = vec![Hash::from(tx_hash)]; - let mut root = tree_hash(&hashes).unwrap(); - assert_eq!(root.as_bytes(), tx_hash); - hashes.push(Hash::from(tx_hash)); - root = tree_hash(&hashes).unwrap(); - let mut correct_root = [ - 187, 251, 201, 6, 70, 27, 80, 117, 95, 97, 244, 143, 194, 245, 73, 174, 158, 255, 98, 175, 74, 22, 173, - 223, 217, 17, 59, 183, 230, 39, 76, 202, - ]; - assert_eq!(root.as_bytes(), correct_root); - - hashes.push(Hash::from(tx_hash)); - root = tree_hash(&hashes).unwrap(); - correct_root = [ - 37, 100, 243, 131, 133, 33, 135, 169, 23, 215, 243, 10, 213, 152, 21, 10, 89, 86, 217, 49, 245, 237, 205, - 194, 102, 162, 128, 225, 215, 192, 158, 251, - ]; - assert_eq!(root.as_bytes(), correct_root); - - hashes.push(Hash::from(tx_hash)); - root = tree_hash(&hashes).unwrap(); - correct_root = [ - 52, 199, 248, 213, 213, 138, 52, 0, 145, 179, 81, 247, 174, 31, 183, 196, 124, 186, 100, 21, 36, 252, 171, - 66, 250, 247, 122, 64, 36, 127, 184, 46, - ]; - assert_eq!(root.as_bytes(), correct_root); - } - - #[test] - fn test_tree_hash_fail() { - let hashes = Vec::new(); - let err = tree_hash(&hashes).unwrap_err(); - unpack_enum!(MergeMineError::HashingError(details) = err); - assert!(details.contains("Cannot calculate Monero root, hashes is empty")); + MoneroPowData::from_header(&block_header).unwrap(); } #[test] fn test_input_blob() { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let block = deserialize::(&bytes[..]).unwrap(); - let input_blob = create_blockhashing_blob(&block).unwrap(); + let block = deserialize::(&bytes[..]).unwrap(); + let input_blob = create_blockhashing_blob_from_block(&block).unwrap(); assert_eq!(input_blob, "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000058b030b6800d433bbcb2b560afe2a08e4dc152fa77ead96d37aaf14897d3c09601"); } @@ -526,7 +271,7 @@ mod test { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); let mut block_header = BlockHeader { version: 0, height: 0, @@ -537,8 +282,8 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; @@ -546,30 +291,29 @@ mod test { append_merge_mining_tag(&mut block, hash).unwrap(); let count = 1 + (block.tx_hashes.len() as u16); let mut hashes = Vec::with_capacity(count as usize); - let mut proof = Vec::with_capacity(count as usize); hashes.push(block.miner_tx.hash()); - proof.push(block.miner_tx.hash()); + // Note: tx_hashes is empty, so |hashes| == 1 for item in block.clone().tx_hashes { hashes.push(item); - proof.push(item); } let root = tree_hash(&hashes).unwrap(); - let monero_data = MoneroData { + assert_eq!(root, hashes[0]); + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { header: block.header, - key: seed_hash, - count, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: from_hashes_to_array(hashes), + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, coinbase_tx: block.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - let monero_data = MoneroData::from_header(&block_header).unwrap(); - verify_header(&block_header, &monero_data).unwrap(); + verify_header(&block_header).unwrap(); } #[test] @@ -577,7 +321,7 @@ mod test { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let block = deserialize::(&bytes[..]).unwrap(); + let block = deserialize::(&bytes[..]).unwrap(); let mut block_header = BlockHeader { version: 0, height: 0, @@ -588,37 +332,34 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; let count = 1 + (block.tx_hashes.len() as u16); let mut hashes = Vec::with_capacity(count as usize); - let mut proof = Vec::with_capacity(count as usize); hashes.push(block.miner_tx.hash()); - proof.push(block.miner_tx.hash()); for item in block.clone().tx_hashes { hashes.push(item); - proof.push(item); } let root = tree_hash(&hashes).unwrap(); - let monero_data = MoneroData { + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { header: block.header, - key: seed_hash, - count, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: from_hashes_to_array(hashes), + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, coinbase_tx: block.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - let monero_data = MoneroData::from_header(&block_header).unwrap(); - let err = verify_header(&block_header, &monero_data).unwrap_err(); + let err = verify_header(&block_header).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -628,7 +369,7 @@ mod test { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); let mut block_header = BlockHeader { version: 0, height: 0, @@ -639,8 +380,8 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; @@ -656,22 +397,22 @@ mod test { proof.push(item); } let root = tree_hash(&hashes).unwrap(); - let monero_data = MoneroData { + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { header: block.header, - key: seed_hash, - count, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: from_hashes_to_array(hashes), + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, coinbase_tx: block.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - let monero_data = MoneroData::from_header(&block_header).unwrap(); - let err = verify_header(&block_header, &monero_data).unwrap_err(); + let err = verify_header(&block_header).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -681,7 +422,7 @@ mod test { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); let mut block_header = BlockHeader { version: 0, height: 0, @@ -692,8 +433,8 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; @@ -709,22 +450,22 @@ mod test { proof.push(item); } let root = tree_hash(&hashes).unwrap(); - let monero_data = MoneroData { + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { header: block.header, - key: seed_hash, - count, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: from_hashes_to_array(hashes), + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof, coinbase_tx: Default::default(), }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - let monero_data = MoneroData::from_header(&block_header).unwrap(); - let err = verify_header(&block_header, &monero_data).unwrap_err(); + let err = verify_header(&block_header).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -741,20 +482,26 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; - let monero_data = MoneroData::default(); - let serialized = bincode::serialize(&monero_data).unwrap(); + let monero_data = MoneroPowData { + header: Default::default(), + randomx_key: FixedByteArray::default(), + transaction_count: 1, + merkle_root: Default::default(), + coinbase_merkle_proof: create_merkle_proof(&[Hash::null_hash()], &Hash::null_hash()).unwrap(), + coinbase_tx: Default::default(), + }; + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - let monero_data = MoneroData::from_header(&block_header).unwrap(); - let err = verify_header(&block_header, &monero_data).unwrap_err(); + let err = verify_header(&block_header).unwrap_err(); unpack_enum!(MergeMineError::ValidationError(details) = err); assert!(details.contains("Expected merge mining tag was not found in Monero coinbase transaction")); } @@ -764,7 +511,7 @@ mod test { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let mut block = deserialize::(&bytes[..]).unwrap(); + let mut block = deserialize::(&bytes[..]).unwrap(); let mut block_header = BlockHeader { version: 0, height: 0, @@ -775,8 +522,8 @@ mod test { output_mmr_size: 0, kernel_mr: vec![0], kernel_mmr_size: 0, - total_kernel_offset: RistrettoSecretKey::from(0), - total_script_offset: RistrettoSecretKey::from(0), + total_kernel_offset: Default::default(), + total_script_offset: Default::default(), nonce: 0, pow: ProofOfWork::default(), }; @@ -791,24 +538,24 @@ mod test { hashes.push(item); proof.push(item); } - let monero_data = MoneroData { + + let coinbase_merkle_proof = create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { header: block.header, - key: seed_hash, - count, - transaction_root: Hash::null_hash().0, - transaction_hashes: from_hashes_to_array(hashes), + randomx_key: FixedByteArray::from_bytes(&from_hex(&seed_hash).unwrap()).unwrap(), + transaction_count: count, + merkle_root: Hash::null_hash(), + coinbase_merkle_proof, coinbase_tx: block.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = consensus::serialize(&monero_data); let pow = ProofOfWork { pow_algo: PowAlgorithm::Monero, pow_data: serialized, }; block_header.pow = pow; - let monero_data = MoneroData::from_header(&block_header).unwrap(); - let err = verify_header(&block_header, &monero_data).unwrap_err(); - unpack_enum!(MergeMineError::ValidationError(details) = err); - assert!(details.contains("Transaction root did not match")); + let err = verify_header(&block_header).unwrap_err(); + unpack_enum!(MergeMineError::InvalidMerkleRoot = err); } #[test] @@ -831,11 +578,11 @@ mod test { let key = from_hex("2aca6501719a5c7ab7d4acbc7cc5d277b57ad8c27c6830788c2d5a596308e5b1").unwrap(); let rx = RandomXFactory::default(); - let difficulty = get_random_x_difficulty(&input, &rx.create(&key).unwrap()).unwrap(); + let (difficulty, hash) = get_random_x_difficulty(&input, &rx.create(&key).unwrap()).unwrap(); assert_eq!( - difficulty.1.to_hex(), + hash.to_hex(), "f68fbc8cc85bde856cd1323e9f8e6f024483038d728835de2f8c014ff6260000" ); - assert_eq!(difficulty.0, 430603.into()); + assert_eq!(difficulty.as_u64(), 430603); } } diff --git a/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs b/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs new file mode 100644 index 00000000000..80ab870c18e --- /dev/null +++ b/base_layer/core/src/proof_of_work/monero_rx/pow_data.rs @@ -0,0 +1,187 @@ +// Copyright 2021, The Tari Project +// +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the +// following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following +// disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the +// following disclaimer in the documentation and/or other materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote +// products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +use super::{deserialize, error::MergeMineError, fixed_array::FixedByteArray, merkle_tree::MerkleProof}; +use crate::{ + blocks::BlockHeader, + crypto::tari_utilities::hex::Hex, + proof_of_work::monero_rx::helpers::create_block_hashing_blob, + tari_utilities::hex::to_hex, +}; +use monero::{ + consensus::{encode, Decodable, Encodable}, + cryptonote::hash::Hashable, +}; +use std::{ + fmt, + fmt::{Display, Formatter}, + io, +}; + +/// This is a struct to deserialize the data from he pow field into data required for the randomX Monero merged mine +/// pow. +#[derive(Clone, Debug)] +pub struct MoneroPowData { + /// Monero header fields + pub header: monero::BlockHeader, + /// RandomX vm key - the key length varies to a maximum length of 60. We'll allow a up to 63 bytes represented in + /// fixed 64-byte struct (63 bytes + 1-byte length). + pub randomx_key: FixedByteArray, + /// The number of transactions included in this Monero block. This is used to produce the blockhashing_blob + pub transaction_count: u16, + /// Transaction root + pub merkle_root: monero::Hash, + /// Coinbase merkle proof hashes + pub coinbase_merkle_proof: MerkleProof, + /// Coinbase tx from Monero + pub coinbase_tx: monero::Transaction, +} + +impl MoneroPowData { + pub fn from_header(tari_header: &BlockHeader) -> Result { + deserialize(&tari_header.pow.pow_data).map_err(|e| MergeMineError::DeserializeError(format!("{:?}", e))) + } + + /// Returns true if the coinbase merkle proof produces the `merkle_root` hash, otherwise false + pub fn is_valid_merkle_root(&self) -> bool { + let coinbase_hash = self.coinbase_tx.hash(); + let merkle_root = self.coinbase_merkle_proof.calculate_root(&coinbase_hash); + self.merkle_root == merkle_root + } + + pub fn to_blockhashing_blob(&self) -> Vec { + create_block_hashing_blob(&self.header, &self.merkle_root, self.transaction_count as u64) + } + + pub fn randomx_key(&self) -> &[u8] { + self.randomx_key.as_slice() + } +} + +impl Display for MoneroPowData { + fn fmt(&self, fmt: &mut Formatter<'_>) -> fmt::Result { + writeln!(fmt, "MoneroBlockHeader: {} ", self.header)?; + writeln!(fmt, "RandomX vm key: {}", self.randomx_key.to_hex())?; + writeln!(fmt, "Monero tx count: {}", self.transaction_count.to_string())?; + writeln!(fmt, "Monero tx root: {}", to_hex(self.merkle_root.as_bytes()))?; + writeln!(fmt, "Monero coinbase tx: {}", self.coinbase_tx) + } +} + +impl Decodable for MoneroPowData { + fn consensus_decode(d: &mut D) -> Result { + Ok(Self { + header: Decodable::consensus_decode(d)?, + randomx_key: Decodable::consensus_decode(d)?, + transaction_count: Decodable::consensus_decode(d)?, + merkle_root: Decodable::consensus_decode(d)?, + coinbase_merkle_proof: Decodable::consensus_decode(d)?, + coinbase_tx: Decodable::consensus_decode(d)?, + }) + } +} + +impl Encodable for MoneroPowData { + fn consensus_encode(&self, e: &mut E) -> Result { + let mut len = self.header.consensus_encode(e)?; + len += self.randomx_key.consensus_encode(e)?; + len += self.transaction_count.consensus_encode(e)?; + + len += self.merkle_root.consensus_encode(e)?; + len += self.coinbase_merkle_proof.consensus_encode(e)?; + len += self.coinbase_tx.consensus_encode(e)?; + Ok(len) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::crypto::tari_utilities::hex::from_hex; + use monero::consensus; + + const POW_DATA_BLOB: &str = "0e0eff8a828606e62827cbb1c8f13eeddaae1d2c5dbb36c12a3d30d20d20b35a540bdba9d8e162604a0000202378cf4e85ef9a0629719e228c8c9807575469c3f45b3710c7960079a5dfdd661600b3cdc310a8f619ea2feadb178021ea0b853caa2f41749f7f039dcd4102d24f0504b4d72f22ca81245c538371a07331546cbd9935068637166d9cd627c521fb0e98d6161a7d971ee608b2b93719327d1cf5f95f9cc15beab7c6fb0894205c9218e4f9810873976eaf62d53ce631e8ad37bbaacc5da0267cd38342d66bdecce6541bb5c761b8ff66e7f6369cd3b0c2cb106a325c7342603516c77c9dcbb67388128a04000000000002fd873401ffc1873401c983eae58cd001026eb5be712030e2d49c9329f7f578325daa8ad7296a58985131544d8fe8a24c934d01ad27b94726423084ffc0f7eda31a8c9691836839c587664a036c3986b33f568f020861f4f1c2c37735680300916c27a920e462fbbfce5ac661ea9ef91fc78d620c61c43d5bb6a9644e3c17e000"; + + #[test] + fn consensus_serialization() { + let bytes = from_hex(POW_DATA_BLOB).unwrap(); + let data = consensus::deserialize::(&bytes).expect("If this fails then consensus has changed"); + assert_eq!(data.transaction_count, 22); + assert_eq!(data.coinbase_merkle_proof.branch().len(), 4); + assert_eq!(bytes.len(), 374); + let ser = consensus::serialize(&data); + assert_eq!(ser, bytes); + } + + #[test] + fn consensus_deserialize_reject_extra_bytes() { + let mut bytes = from_hex(POW_DATA_BLOB).unwrap(); + bytes.extend(&[0u8; 10]); + let err = consensus::deserialize::(&bytes).unwrap_err(); + // ParseFailed("data not consumed entirely when explicitly deserializing") + assert!(matches!(err, encode::Error::ParseFailed(_))); + + let mut bytes = from_hex(POW_DATA_BLOB).unwrap(); + bytes.push(1); + let err = consensus::deserialize::(&bytes).unwrap_err(); + assert!(matches!(err, encode::Error::ParseFailed(_))); + } + + mod fuzz { + use super::*; + use monero::TxIn; + + #[ignore] + #[test] + fn simple_capacity_overflow_panic() { + let data = &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f]; + let _ = deserialize::>(data); + } + + #[ignore] + #[test] + fn oom_moneroblock_deserialize() { + let data = [ + 0x09, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x95, 0x95, 0x95, + 0x95, 0x01, 0x00, 0x00, 0x00, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x38, 0x00, 0x00, 0x00, + 0x04, 0x00, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, 0x95, + ]; + let _ = deserialize::(&data); + } + + #[ignore] + #[test] + fn panic_alloc_capacity_overflow_moneroblock_deserialize() { + let data = [ + 0x0f, 0x9e, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, + 0x00, 0x08, 0x9e, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, 0x9e, + 0x9e, 0xe7, 0xaa, 0xfd, 0x8b, 0x47, 0x06, 0x8d, 0xed, 0xe3, 0x00, 0xed, 0x44, 0xfc, 0x77, 0xd6, 0x58, + 0xf6, 0xf2, 0x69, 0x06, 0x8d, 0xed, 0xe3, 0x00, 0xed, 0x44, 0xfc, 0x77, 0xd6, 0x58, 0xf6, 0xf2, 0x69, + 0x62, 0x38, 0xdb, 0x5e, 0x4d, 0x6d, 0x9c, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x00, + 0x8f, 0x74, 0x3c, 0xb3, 0x1b, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + ]; + let _ = deserialize::(&data); + } + } +} diff --git a/base_layer/core/src/test_helpers/blockchain.rs b/base_layer/core/src/test_helpers/blockchain.rs index 410b8dd990e..dfde3a1d138 100644 --- a/base_layer/core/src/test_helpers/blockchain.rs +++ b/base_layer/core/src/test_helpers/blockchain.rs @@ -315,8 +315,8 @@ impl BlockchainBackend for TempDatabase { self.db.delete_oldest_orphans(horizon_height, orphan_storage_capacity) } - fn fetch_monero_seed_first_seen_height(&self, seed: &str) -> Result { - self.db.fetch_monero_seed_first_seen_height(seed) + fn fetch_monero_seed_first_seen_height(&self, seed: &[u8]) -> Result { + self.db.fetch_monero_seed_first_seen_height(&seed) } fn fetch_horizon_data(&self) -> Result, ChainStorageError> { diff --git a/base_layer/core/src/validation/helpers.rs b/base_layer/core/src/validation/helpers.rs index 49f24cbd560..b05e9adbd32 100644 --- a/base_layer/core/src/validation/helpers.rs +++ b/base_layer/core/src/validation/helpers.rs @@ -30,7 +30,7 @@ use crate::{ consensus::{ConsensusConstants, ConsensusManager}, proof_of_work::{ monero_difficulty, - monero_rx::MoneroData, + monero_rx::MoneroPowData, randomx_factory::RandomXFactory, sha3_difficulty, AchievedTargetDifficulty, @@ -126,8 +126,8 @@ pub fn check_pow_data( match block_header.pow.pow_algo { Monero => { let monero_data = - MoneroData::from_header(block_header).map_err(|e| ValidationError::CustomError(e.to_string()))?; - let seed_height = db.fetch_monero_seed_first_seen_height(&monero_data.key)?; + MoneroPowData::from_header(block_header).map_err(|e| ValidationError::CustomError(e.to_string()))?; + let seed_height = db.fetch_monero_seed_first_seen_height(&monero_data.randomx_key)?; if (seed_height != 0) && (block_header.height - seed_height > rules.consensus_constants(block_header.height).max_randomx_seed_height()) diff --git a/base_layer/core/tests/block_validation.rs b/base_layer/core/tests/block_validation.rs index 4646743d188..f87b08df694 100644 --- a/base_layer/core/tests/block_validation.rs +++ b/base_layer/core/tests/block_validation.rs @@ -21,7 +21,7 @@ // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. use crate::helpers::block_builders::chain_block_with_new_coinbase; -use monero::{blockdata::block::Block as MoneroBlock, consensus::deserialize}; +use monero::blockdata::block::Block as MoneroBlock; use std::sync::Arc; use tari_core::{ blocks::{Block, BlockHeaderValidationError}, @@ -32,7 +32,12 @@ use tari_core::{ ConsensusManagerBuilder, Network, }, - proof_of_work::{monero_rx, monero_rx::MoneroData, PowAlgorithm}, + crypto::tari_utilities::hex::Hex, + proof_of_work::{ + monero_rx, + monero_rx::{FixedByteArray, MoneroPowData}, + PowAlgorithm, + }, test_helpers::blockchain::{create_store_with_consensus_and_validators, create_test_db}, transactions::types::CryptoFactories, validation::{ @@ -79,8 +84,8 @@ fn test_genesis_block() { #[test] fn test_monero_blocks() { // Create temporary test folder - let seed1 = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); - let seed2 = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad98".to_string(); + let seed1 = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97"; + let seed2 = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad98"; let factories = CryptoFactories::default(); let network = Network::Weatherwax; @@ -143,23 +148,24 @@ fn test_monero_blocks() { db.add_block(Arc::new(block_3)).unwrap().assert_added(); } -fn add_monero_data(tblock: &mut Block, seed_hash: String) { +fn add_monero_data(tblock: &mut Block, seed_key: &str) { let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); let bytes = hex::decode(blocktemplate_blob).unwrap(); - let mut mblock = deserialize::(&bytes[..]).unwrap(); + let mut mblock = monero_rx::deserialize::(&bytes[..]).unwrap(); let hash = tblock.header.merged_mining_hash(); monero_rx::append_merge_mining_tag(&mut mblock, hash).unwrap(); let hashes = monero_rx::create_ordered_transaction_hashes_from_block(&mblock); - let root = monero_rx::tree_hash(&hashes).unwrap(); - let monero_data = MoneroData { + let merkle_root = monero_rx::tree_hash(&hashes).unwrap(); + let coinbase_merkle_proof = monero_rx::create_merkle_proof(&hashes, &hashes[0]).unwrap(); + let monero_data = MoneroPowData { header: mblock.header, - key: seed_hash, - count: hashes.len() as u16, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: hashes.into_iter().map(|h| h.to_fixed_bytes()).collect(), + randomx_key: FixedByteArray::from_hex(seed_key).unwrap(), + transaction_count: hashes.len() as u16, + merkle_root, + coinbase_merkle_proof, coinbase_tx: mblock.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); + let serialized = monero_rx::serialize(&monero_data); tblock.header.pow.pow_algo = PowAlgorithm::Monero; tblock.header.pow.pow_data = serialized; } diff --git a/base_layer/core/tests/helpers/pow_blockchain.rs b/base_layer/core/tests/helpers/pow_blockchain.rs index c8fd451f28f..12c6b5d1902 100644 --- a/base_layer/core/tests/helpers/pow_blockchain.rs +++ b/base_layer/core/tests/helpers/pow_blockchain.rs @@ -23,6 +23,7 @@ use super::block_builders::chain_block; use monero::{ blockdata::Block as MoneroBlock, + consensus, consensus::deserialize, cryptonote::hash::{Hash as MoneroHash, Hashable as MoneroHashable}, }; @@ -30,9 +31,11 @@ use tari_core::{ blocks::Block, chain_storage::{BlockchainBackend, BlockchainDatabase}, consensus::{ConsensusConstants, ConsensusManager}, + crypto::tari_utilities::hex::Hex, proof_of_work::{ lwma_diff::LinearWeightedMovingAverage, - monero_rx::{append_merge_mining_tag, tree_hash, MoneroData}, + monero_rx, + monero_rx::{append_merge_mining_tag, tree_hash, FixedByteArray, MoneroPowData}, Difficulty, DifficultyAdjustment, PowAlgorithm, @@ -67,9 +70,9 @@ pub fn append_to_pow_blockchain( new_block.header.pow.pow_algo = pow_algo; if new_block.header.pow.pow_algo == PowAlgorithm::Monero { - let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000".to_string(); - let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97".to_string(); - let bytes = hex::decode(blocktemplate_blob.clone()).unwrap(); + let blocktemplate_blob = "0c0c8cd6a0fa057fe21d764e7abf004e975396a2160773b93712bf6118c3b4959ddd8ee0f76aad0000000002e1ea2701ffa5ea2701d5a299e2abb002028eb3066ced1b2cc82ea046f3716a48e9ae37144057d5fb48a97f941225a1957b2b0106225b7ec0a6544d8da39abe68d8bd82619b4a7c5bdae89c3783b256a8fa47820208f63aa86d2e857f070000"; + let seed_hash = "9f02e032f9b15d2aded991e0f68cc3c3427270b568b782e55fbd269ead0bad97"; + let bytes = hex::decode(&blocktemplate_blob).unwrap(); let mut block = deserialize::(&bytes[..]).unwrap(); let hash = MoneroHash::from_slice(new_block.header.merged_mining_hash().as_ref()); append_merge_mining_tag(&mut block, hash).unwrap(); @@ -83,16 +86,15 @@ pub fn append_to_pow_blockchain( proof.push(item); } let root = tree_hash(hashes.clone().as_ref()).unwrap(); - let monero_data = MoneroData { + let monero_data = MoneroPowData { header: block.header, - key: seed_hash.clone(), - count, - transaction_root: root.to_fixed_bytes(), - transaction_hashes: hashes.into_iter().map(|h| h.to_fixed_bytes()).collect(), + randomx_key: FixedByteArray::from_hex(seed_hash).unwrap(), + transaction_count: count, + merkle_root: root, + coinbase_merkle_proof: monero_rx::create_merkle_proof(&hashes, &hashes[0]).unwrap(), coinbase_tx: block.miner_tx, }; - let serialized = bincode::serialize(&monero_data).unwrap(); - new_block.header.pow.pow_data = serialized.clone(); + new_block.header.pow.pow_data = consensus::serialize(&monero_data); } db.add_block(new_block.clone().into()).unwrap(); diff --git a/common/config/presets/tari_config_example.toml b/common/config/presets/tari_config_example.toml index dcc94e9f843..a72d21235af 100644 --- a/common/config/presets/tari_config_example.toml +++ b/common/config/presets/tari_config_example.toml @@ -315,7 +315,7 @@ tor_control_auth = "none" # or "password=xxxxxx" base_node_tor_identity_file = "config/base_node_tor.json" # A path to the file that stores the console wallet's tor hidden service private key, if using the tor transport. -console_wallet_tor_identity_file = "config/cosole_wallet_tor.json" +console_wallet_tor_identity_file = "config/console_wallet_tor.json" ######################################################################################################################## # #