diff --git a/Cargo.lock b/Cargo.lock index 7c2d8cc..e89142c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,27 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7a2e47a1fbe209ee101dd6d61285226744c6c8d3c21c8dc878ba6cb9f467f3a" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - -[[package]] -name = "android-tzdata" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" - [[package]] name = "android_system_properties" version = "0.1.5" @@ -34,36 +13,38 @@ dependencies = [ [[package]] name = "arrayref" -version = "0.3.8" +version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d151e35f61089500b617991b791fc8bfd237ae50cd5950803758a179b41e67a" +checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" [[package]] name = "arrayvec" -version = "0.5.2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8" + +[[package]] +name = "async-trait" +version = "0.1.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "531b97fb4cd3dfdce92c35dedbfdc1f0b9d8091c8ca943d6dae340ef5012d514" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] [[package]] name = "autocfg" -version = "1.3.0" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" [[package]] -name = "backtrace" -version = "0.3.59" +name = "base64" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4717cfcbfaa661a0fd48f8453951837ae7e8f81e481fbb136e3202d72805a744" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" [[package]] name = "base64" @@ -79,9 +60,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitvec" -version = "0.19.6" +version = "0.19.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55f93d0ef3363c364d5976646a38f04cf67cfe1d4c8d160cdea02cab2c116b33" +checksum = "8942c8d352ae1838c9dda0b0ca2ab657696ef2232a20147cf1b30ae1a9cb4321" dependencies = [ "funty", "radium", @@ -91,9 +72,9 @@ dependencies = [ [[package]] name = "blake2b_simd" -version = "0.5.11" +version = "0.5.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" +checksum = "d8fb2d74254a3a0b5cac33ac9f8ed0e44aa50378d9dbb2e5d83bd21ed1dc2c8a" dependencies = [ "arrayref", "arrayvec", @@ -111,30 +92,33 @@ dependencies = [ [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" [[package]] name = "byteorder" -version = "1.5.0" +version = "1.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" [[package]] name = "bytes" -version = "1.7.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" +checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" [[package]] name = "cc" -version = "1.1.15" +version = "1.0.74" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57b6a275aa2903740dc87da01c62040406b8812552e97129a63ea8850a17c6e6" -dependencies = [ - "shlex", -] +checksum = "581f5dba903aac52ea3feb5ec4810848460ee833876f1f9b0fdeab1f19091574" + +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cfg-if" @@ -144,61 +128,66 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" -version = "0.4.38" +version = "0.4.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +checksum = "16b0a3d9ed01224b22057780a37bb8c5dbfe1be8ba48678e7bf57ec4b385411f" dependencies = [ - "android-tzdata", "iana-time-zone", "js-sys", + "num-integer", "num-traits", "serde", + "time", "wasm-bindgen", - "windows-targets 0.52.6", + "winapi", ] [[package]] -name = "constant_time_eq" -version = "0.1.5" +name = "codespan-reporting" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" +checksum = "3538270d33cc669650c4b093848450d380def10c331d38c768e34cac80576e6e" +dependencies = [ + "termcolor", + "unicode-width", +] [[package]] -name = "convert_case" -version = "0.4.0" +name = "console_error_panic_hook" +version = "0.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] [[package]] -name = "core-foundation" -version = "0.9.4" +name = "constant_time_eq" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] +checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" [[package]] name = "core-foundation-sys" -version = "0.8.7" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +checksum = "5827cebf4670468b8772dd191856768aedcb1b0278a04f989f7766351917b9dc" [[package]] name = "cpufeatures" -version = "0.2.13" +version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51e852e6dc9a5bed1fae92dd2375037bf2b768725bf3be87811edee3249d09ad" +checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" dependencies = [ "libc", ] [[package]] name = "curve25519-dalek" -version = "3.2.1" +version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90f9d052967f590a76e62eb387bd0bbb1b000182c3cefe5364db6b7211651bc0" +checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61" dependencies = [ "byteorder", "digest", @@ -208,51 +197,58 @@ dependencies = [ ] [[package]] -name = "darling" -version = "0.13.4" +name = "cxx" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +checksum = "97abf9f0eca9e52b7f81b945524e76710e6cb2366aead23b7d4fbf72e281f888" dependencies = [ - "darling_core", - "darling_macro", + "cc", + "cxxbridge-flags", + "cxxbridge-macro", + "link-cplusplus", ] [[package]] -name = "darling_core" -version = "0.13.4" +name = "cxx-build" +version = "1.0.62" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +checksum = "8b49af8e551e84f85d6def97c42b8d93bc5bb0817cce96b56a472b1b19b5bfc2" dependencies = [ - "fnv", - "ident_case", + "cc", + "codespan-reporting", + "lazy_static", "proc-macro2", "quote", - "strsim", - "syn 1.0.109", + "scratch", + "syn 1.0.95", ] [[package]] -name = "darling_macro" -version = "0.13.4" +name = "cxxbridge-flags" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca220e4794c934dc6b1207c3b42856ad4c302f2df1712e9f8d2eec5afaacf1f" + +[[package]] +name = "cxxbridge-macro" +version = "1.0.81" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +checksum = "b846f081361125bfc8dc9d3940c84e1fd83ba54bbca7b17cd29483c828be0704" dependencies = [ - "darling_core", + "proc-macro2", "quote", - "syn 1.0.109", + "syn 1.0.95", ] [[package]] name = "derive_more" -version = "0.99.18" +version = "0.99.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" +checksum = "41cb0e6161ad61ed084a36ba71fbba9e3ac5aee3606fb607fe08da6acbcf3d8c" dependencies = [ - "convert_case", "proc-macro2", "quote", - "rustc_version", - "syn 2.0.76", + "syn 1.0.95", ] [[package]] @@ -266,9 +262,9 @@ dependencies = [ [[package]] name = "ed25519" -version = "1.5.3" +version = "1.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91cff35c70bba8a626e3185d8cd48cc11b5437e1a5bcd15b9b5fa3c64b6dfee7" +checksum = "1e9c280362032ea4203659fc489832d0204ef09f247a0506f170dafcac08c369" dependencies = [ "serde", "signature", @@ -291,11 +287,11 @@ dependencies = [ [[package]] name = "encoding_rs" -version = "0.8.34" +version = "0.8.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" +checksum = "80df024fbc5ac80f87dfef0d9f5209a252f2a497f7f42944cff24d8253cac065" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", ] [[package]] @@ -312,10 +308,11 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "form_urlencoded" -version = "1.2.1" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" +checksum = "5fc25a87fa4fd2094bffb06925852034d90a17f0d1e05197d4956d3555752191" dependencies = [ + "matches", "percent-encoding", ] @@ -325,43 +322,93 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fed34cd105917e91daa4da6b3728c47b068749d6a62c59811f06ed2ac71d9da7" +[[package]] +name = "futures" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23342abe12aba583913b2e62f22225ff9c950774065e4bfb61a19cd9770fec40" +dependencies = [ + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + [[package]] name = "futures-channel" -version = "0.3.30" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" +checksum = "955518d47e09b25bbebc7a18df10b81f0c766eaf4c4f1cccef2fca5f2a4fb5f2" dependencies = [ "futures-core", + "futures-sink", ] [[package]] name = "futures-core" -version = "0.3.30" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c" + +[[package]] +name = "futures-executor" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" +checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964" + +[[package]] +name = "futures-macro" +version = "0.3.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] [[package]] name = "futures-sink" -version = "0.3.30" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" +checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e" [[package]] name = "futures-task" -version = "0.3.30" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" +checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65" [[package]] name = "futures-util" -version = "0.3.30" +version = "0.3.28" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" +checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533" dependencies = [ + "futures-channel", "futures-core", + "futures-io", + "futures-macro", + "futures-sink", "futures-task", + "memchr", "pin-project-lite", "pin-utils", + "slab", ] [[package]] @@ -376,26 +423,33 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.1.16" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb" dependencies = [ - "cfg-if", + "cfg-if 0.1.10", "libc", "wasi 0.9.0+wasi-snapshot-preview1", ] [[package]] -name = "gimli" -version = "0.24.0" +name = "getrandom" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0e4075386626662786ddb0ec9081e7c7eeb1ba31951f447ca780ef9f5d568189" +checksum = "c85e1d9ab2eadba7e5040d4e09cbd6d072b76a557ad64e797c2cb9d4da21d7e4" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", + "wasm-bindgen", +] [[package]] name = "h2" -version = "0.3.26" +version = "0.3.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81fe527a889e1532da5c525686d96d4c2e74cdd345badf8dfef9f6b39dd5f5e8" +checksum = "bb2c4422095b67ee78da96fbb51a4cc413b3b25883c7717ff7ca1ab31022c9c9" dependencies = [ "bytes", "fnv", @@ -403,7 +457,7 @@ dependencies = [ "futures-sink", "futures-util", "http", - "indexmap", + "indexmap 2.2.3", "slab", "tokio", "tokio-util", @@ -412,21 +466,24 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.5" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" [[package]] -name = "hermit-abi" -version = "0.3.9" +name = "hashbrown" +version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" +checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" [[package]] name = "hex" version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +dependencies = [ + "serde", +] [[package]] name = "http" @@ -441,9 +498,9 @@ dependencies = [ [[package]] name = "http-body" -version = "0.4.6" +version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ceab25649e9960c0311ea418d17bee82c0dcec1bd053b5f9a66e265a693bed2" +checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1" dependencies = [ "bytes", "http", @@ -452,21 +509,21 @@ dependencies = [ [[package]] name = "httparse" -version = "1.9.4" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" [[package]] name = "httpdate" -version = "1.0.3" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" +checksum = "6456b8a6c8f33fee7d958fcd1b60d55b11940a79e63ae87013e6d22e26034440" [[package]] name = "hyper" -version = "0.14.30" +version = "0.14.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a152ddd61dfaec7273fe8419ab357f33aee0d914c5f4efbf0d96fa749eea5ec9" +checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4" dependencies = [ "bytes", "futures-channel", @@ -486,76 +543,101 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87c48c02e0dc5e3b849a2041db3029fd066650f8f717c07bf8ed78ccb895cac" +dependencies = [ + "http", + "hyper", + "rustls", + "tokio", + "tokio-rustls", +] + [[package]] name = "iana-time-zone" -version = "0.1.60" +version = "0.1.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +checksum = "64c122667b287044802d6ce17ee2ddf13207ed924c712de9a66a5814d5b64765" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", - "windows-core", + "winapi", ] [[package]] name = "iana-time-zone-haiku" -version = "0.1.2" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "0703ae284fc167426161c2e3f1da3ea71d94b21bedbcc9494e92b28e334e3dca" dependencies = [ - "cc", + "cxx", + "cxx-build", ] -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - [[package]] name = "idna" -version = "0.5.0" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8" dependencies = [ + "matches", "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" -version = "2.4.0" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.1", +] + +[[package]] +name = "indexmap" +version = "2.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93ead53efc7ea8ed3cfb0c79fc8023fbb782a5432b52830b6518941cebe6505c" +checksum = "233cf39063f058ea2caae4091bf4a3ef70a653afbc026f5c4a4135d114e3c177" dependencies = [ "equivalent", - "hashbrown", + "hashbrown 0.14.3", ] [[package]] name = "ipnet" -version = "2.9.0" +version = "2.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" +checksum = "12b6ee2129af8d4fb011108c73d99a1b83a85977f23b82460c0ae2e25bb4b57f" [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.64" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "c5f195fe497f702db0f318b07fdd68edb16955aed830df8363d837542f8f935a" dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "lexical-core" version = "0.7.6" @@ -564,62 +646,67 @@ checksum = "6607c62aa161d23d17a9072cc5da0be67cdfc89d3afb1e8d9c842bebc2525ffe" dependencies = [ "arrayvec", "bitflags", - "cfg-if", + "cfg-if 1.0.0", "ryu", "static_assertions", ] [[package]] name = "libc" -version = "0.2.158" +version = "0.2.150" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "link-cplusplus" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9272ab7b96c9046fbc5bc56c06c117cb639fe2d509df0c421cad82d2915cf369" +dependencies = [ + "cc", +] [[package]] name = "log" -version = "0.4.22" +version = "0.4.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "b06a4cde4c0f271a446782e3eff8de789548ce57dbc8eca9292c27f4a42004b4" [[package]] -name = "memchr" -version = "2.3.4" +name = "matches" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ee1c47aaa256ecabcaea351eae4a9b01ef39ed810004e298d2511ed284b1525" +checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08" [[package]] -name = "mime" -version = "0.3.17" +name = "memchr" +version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" [[package]] -name = "miniz_oxide" -version = "0.4.4" +name = "mime" +version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" -dependencies = [ - "adler", - "autocfg", -] +checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mio" -version = "1.0.2" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80e04d1dcff3aae0704555fe5fee3bcfaf3d1fdf8a7e521d5b9d2b42acb52cec" +checksum = "5b9d9a46eff5b4ff64b45a9e316a6d1e0bc719ef429cbec4dc630684212bfdf9" dependencies = [ - "hermit-abi", "libc", + "log", "wasi 0.11.0+wasi-snapshot-preview1", - "windows-sys 0.52.0", + "windows-sys 0.45.0", ] [[package]] name = "nom" -version = "6.2.2" +version = "6.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6a7a9657c84d5814c6196b68bb4429df09c18b1573806259fba397ea4ad0d44" +checksum = "e7413f999671bd4745a7b624bd370a569fb6bc574b23c83a3c5ed2e453f3d5e2" dependencies = [ "bitvec", "funty", @@ -629,43 +716,47 @@ dependencies = [ ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "num-integer" +version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ "autocfg", + "num-traits", ] [[package]] -name = "object" -version = "0.24.0" +name = "num-traits" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a5b3dd1c072ee7963717671d1ca129f1048fda25edea6b752bfc71ac8854170" +checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +dependencies = [ + "autocfg", +] [[package]] name = "once_cell" -version = "1.19.0" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" [[package]] name = "opaque-debug" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c08d65885ee38876c4f86fa503fb49d7b507c2b62552df7c70b2fce627e06381" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" [[package]] name = "percent-encoding" -version = "2.3.1" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" +checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] name = "pin-project-lite" -version = "0.2.14" +version = "0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" [[package]] name = "pin-utils" @@ -675,27 +766,24 @@ checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "ppv-lite86" -version = "0.2.20" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] +checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea" [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.69" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.37" +version = "1.0.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" dependencies = [ "proc-macro2", ] @@ -712,7 +800,7 @@ version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" dependencies = [ - "getrandom", + "getrandom 0.1.14", "libc", "rand_chacha", "rand_core", @@ -735,7 +823,7 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" dependencies = [ - "getrandom", + "getrandom 0.1.14", ] [[package]] @@ -749,11 +837,11 @@ dependencies = [ [[package]] name = "reqwest" -version = "0.11.27" +version = "0.11.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd67538700a17451e7cba03ac727fb961abb7607553461627b97de0b89cf4a62" +checksum = "87f242f1488a539a79bac6dbe7c8609ae43b7914b7736210f239a37cccb32525" dependencies = [ - "base64", + "base64 0.13.0", "bytes", "encoding_rs", "futures-core", @@ -762,32 +850,43 @@ dependencies = [ "http", "http-body", "hyper", + "hyper-rustls", "ipnet", "js-sys", + "lazy_static", "log", "mime", - "once_cell", "percent-encoding", "pin-project-lite", + "rustls", + "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", - "sync_wrapper", - "system-configuration", "tokio", - "tower-service", + "tokio-rustls", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", + "webpki-roots", "winreg", ] [[package]] -name = "rustc-demangle" -version = "0.1.24" +name = "ring" +version = "0.16.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" +checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc" +dependencies = [ + "cc", + "libc", + "once_cell", + "spin", + "untrusted", + "web-sys", + "winapi", +] [[package]] name = "rustc-hex" @@ -796,64 +895,102 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" [[package]] -name = "rustc_version" -version = "0.4.0" +name = "rustls" +version = "0.20.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fbfeb8d0ddb84706bc597a5574ab8912817c52a397f819e5b614e2265206921" +dependencies = [ + "log", + "ring", + "sct", + "webpki", +] + +[[package]] +name = "rustls-pemfile" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +checksum = "5eebeaeb360c87bfb72e84abdb3447159c0eaececf1bef2aecd65a8be949d1c9" dependencies = [ - "semver", + "base64 0.13.0", ] [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e" [[package]] -name = "semver" -version = "1.0.23" +name = "scoped-tls" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +checksum = "ea6a9290e3c9cf0f18145ef7ffa62d68ee0bf5fcd651017e586dc7fd5da448c2" + +[[package]] +name = "scratch" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8132065adcfd6e02db789d9285a0deb2f3fcb04002865ab67d5fb103533898" + +[[package]] +name = "sct" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d53dcdb7c9f8158937a7981b48accfd39a43af418591a5d008c7b22b5e1b7ca4" +dependencies = [ + "ring", + "untrusted", +] [[package]] name = "serde" -version = "1.0.209" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99fce0ffe7310761ca6bf9faf5115afbc19688edd00171d81b1bb1b116c63e09" +checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfc62771e7b829b517cb213419236475f434fb480eddd76112ae182d274434a" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_bytes" -version = "0.11.15" +version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "387cc504cb06bb40a96c8e04e951fe01854cf6bc921053c954e4a606d9675c6a" +checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" dependencies = [ "serde", ] [[package]] name = "serde_derive" -version = "1.0.209" +version = "1.0.189" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a5831b979fd7b5439637af1752d535ff49f4860c0f341d1baeb6faf0f4242170" +checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.38", ] [[package]] name = "serde_json" -version = "1.0.127" +version = "1.0.79" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8043c06d9f82bd7271361ed64f415fe5e12a77fdb52e573e7f06a516dea329ad" +checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" dependencies = [ - "indexmap", + "indexmap 1.9.3", "itoa", - "memchr", "ryu", "serde", ] @@ -870,28 +1007,6 @@ dependencies = [ "serde", ] -[[package]] -name = "serde_with" -version = "1.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678b5a069e50bf00ecd22d0cd8ddf7c236f68581b03db652061ed5eb13a312ff" -dependencies = [ - "serde", - "serde_with_macros", -] - -[[package]] -name = "serde_with_macros" -version = "1.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e182d6ec6f05393cc0e5ed1bf81ad6db3a8feedf8ee515ecdd369809bcce8082" -dependencies = [ - "darling", - "proc-macro2", - "quote", - "syn 1.0.109", -] - [[package]] name = "sha2" version = "0.9.9" @@ -899,85 +1014,94 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" dependencies = [ "block-buffer", - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "digest", "opaque-debug", ] -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - [[package]] name = "sia-rust" version = "0.1.0" dependencies = [ - "base64", + "async-trait", + "base64 0.21.7", "blake2b_simd", "chrono", + "curve25519-dalek", "derive_more", "ed25519-dalek", + "futures", + "getrandom 0.2.9", "hex", + "http", + "js-sys", + "log", "nom", + "once_cell", + "percent-encoding", "reqwest", "rustc-hex", "serde", + "serde-wasm-bindgen", "serde_json", - "serde_with", + "thiserror", + "tokio", "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test", + "web-sys", ] [[package]] name = "signature" -version = "1.6.4" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c" +checksum = "02658e48d89f2bec991f9a78e69cfa4c316f8d6a6c4ec12fae1aeb263d486788" [[package]] name = "slab" -version = "0.4.9" +version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +checksum = "6528351c9bc8ab22353f9d776db39a20288e8d6c37ef8cfe3317cf875eecfc2d" dependencies = [ "autocfg", ] [[package]] name = "socket2" -version = "0.5.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" +checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662" dependencies = [ "libc", - "windows-sys 0.52.0", + "winapi", ] [[package]] -name = "static_assertions" -version = "1.1.0" +name = "spin" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" +checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d" [[package]] -name = "strsim" -version = "0.10.0" +name = "static_assertions" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" [[package]] name = "subtle" -version = "2.6.1" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "1e81da0851ada1f3e9d4312c704aa4f8806f0f9d69faaf8df2f3464b4a9437c2" [[package]] name = "syn" -version = "1.0.109" +version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +checksum = "fbaf6116ab8924f39d52792136fb74fd60a80194cf1b1c6ffa6453eef1c3f942" dependencies = [ "proc-macro2", "quote", @@ -986,9 +1110,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.76" +version = "2.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578e081a14e0cefc3279b0472138c513f37b41a08d5a3cca9b6e4e8ceb6cd525" +checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" dependencies = [ "proc-macro2", "quote", @@ -996,163 +1120,247 @@ dependencies = [ ] [[package]] -name = "sync_wrapper" -version = "0.1.2" +name = "synstructure" +version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160" +checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.95", + "unicode-xid", +] [[package]] -name = "system-configuration" -version = "0.5.1" +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "termcolor" +version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" dependencies = [ - "bitflags", - "core-foundation", - "system-configuration-sys", + "winapi-util", ] [[package]] -name = "system-configuration-sys" -version = "0.5.0" +name = "thiserror" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" +checksum = "978c9a314bd8dc99be594bc3c175faaa9794be04a5a5e153caba6915336cebac" dependencies = [ - "core-foundation-sys", - "libc", + "thiserror-impl", ] [[package]] -name = "tap" -version = "1.0.1" +name = "thiserror-impl" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +checksum = "f9456a42c5b0d803c8cd86e73dd7cc9edd429499f37a3550d286d5e86720569f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "time" +version = "0.1.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438" +dependencies = [ + "libc", + "winapi", +] [[package]] name = "tinyvec" -version = "1.8.0" +version = "1.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" -version = "0.1.1" +version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" [[package]] name = "tokio" -version = "1.39.3" +version = "1.28.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9babc99b9923bfa4804bd74722ff02c0381021eafa4db9949217e3be8e84fff5" +checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2" dependencies = [ - "backtrace", + "autocfg", "bytes", "libc", "mio", "pin-project-lite", "socket2", - "windows-sys 0.52.0", + "tokio-macros", + "windows-sys 0.48.0", +] + +[[package]] +name = "tokio-macros" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + +[[package]] +name = "tokio-rustls" +version = "0.23.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a27d5f2b839802bd8267fa19b0530f5a08b9c08cd417976be2a65d130fe1c11b" +dependencies = [ + "rustls", + "tokio", + "webpki", ] [[package]] name = "tokio-util" -version = "0.7.11" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" +checksum = "f988a1a1adc2fb21f9c12aa96441da33a1728193ae0b95d2be22dbd17fcb4e5c" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", + "tracing", ] [[package]] name = "tower-service" -version = "0.3.3" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" +checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" dependencies = [ + "cfg-if 1.0.0", "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f4f31f56159e98206da9efd823404b79b6ef3143b4a7ab76e67b1751b25a4ab" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.38", +] + [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "0955b8137a1df6f1a2e9a37d8a6656291ff0297c1a97c24e0d8425fe2312f79a" dependencies = [ "once_cell", ] [[package]] name = "try-lock" -version = "0.2.5" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" +checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" [[package]] name = "typenum" -version = "1.17.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" [[package]] name = "unicode-bidi" -version = "0.3.15" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" +checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" +dependencies = [ + "matches", +] [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" [[package]] name = "unicode-normalization" -version = "0.1.23" +version = "0.1.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c" + +[[package]] +name = "untrusted" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a" + [[package]] name = "url" -version = "2.5.2" +version = "2.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" +checksum = "a507c383b2d33b5fc35d1861e77e6b383d158b2da5e14fe51b83dfedf6fd578c" dependencies = [ "form_urlencoded", "idna", + "matches", "percent-encoding", "serde", ] [[package]] name = "version_check" -version = "0.9.5" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" [[package]] name = "want" -version = "0.3.1" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ + "log", "try-lock", ] @@ -1170,37 +1378,36 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "7706a72ab36d8cb1f80ffbf0e071533974a60d0a308d01a5d0375bf60499a342" dependencies = [ - "cfg-if", - "once_cell", + "cfg-if 1.0.0", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "5ef2b6d3c510e9625e5fe6f509ab07d66a760f0885d858736483c32ed7809abd" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.38", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" -version = "0.4.43" +version = "0.4.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61e9300f63a621e96ed275155c108eb6f843b6a26d053f122ab69724559dc8ed" +checksum = "c02dbc21516f9f1f04f187958890d7e6026df8d16540b7ad9492bc34a67cea03" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "js-sys", "wasm-bindgen", "web-sys", @@ -1208,9 +1415,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "dee495e55982a3bd48105a7b947fd2a9b4a8ae3010041b9e0faab3f9cd028f1d" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -1218,189 +1425,246 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.87" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "54681b18a46765f095758388f2d0cf16eb8d4169b639ab575a8f5693af210c7b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 2.0.38", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca6ad05a4870b2bf5fe995117d3728437bd27d7cd5f06f13c17443ef369775a1" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "0f0dfda4d3b3f8acbc3c291b09208081c203af457fb14a229783b06e2f128aa7" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c2e18093f11c19ca4e188c177fecc7c372304c311189f12c2f9bea5b7324ac7" +dependencies = [ + "proc-macro2", + "quote", +] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "38eb105f1c59d9eaa6b5cdc92b859d85b926e82cb2e0945cd0c9259faa6fe9fb" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] -name = "windows-core" -version = "0.52.0" +name = "webpki" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f095d78192e208183081cc07bc5515ef55216397af48b873e5edcd72637fa1bd" +dependencies = [ + "ring", + "untrusted", +] + +[[package]] +name = "webpki-roots" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d8de8415c823c8abd270ad483c6feeac771fad964890779f9a8cb24fbbc1bf" +dependencies = [ + "webpki", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" dependencies = [ - "windows-targets 0.52.6", + "winapi", ] +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-sys" -version = "0.48.0" +version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.42.1", ] [[package]] name = "windows-sys" -version = "0.52.0" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ - "windows-targets 0.52.6", + "windows-targets 0.48.0", ] [[package]] name = "windows-targets" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7" dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", + "windows_aarch64_gnullvm 0.42.1", + "windows_aarch64_msvc 0.42.1", + "windows_i686_gnu 0.42.1", + "windows_i686_msvc 0.42.1", + "windows_x86_64_gnu 0.42.1", + "windows_x86_64_gnullvm 0.42.1", + "windows_x86_64_msvc 0.42.1", ] [[package]] name = "windows-targets" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5" dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", + "windows_aarch64_gnullvm 0.48.0", + "windows_aarch64_msvc 0.48.0", + "windows_i686_gnu 0.48.0", + "windows_i686_msvc 0.48.0", + "windows_x86_64_gnu 0.48.0", + "windows_x86_64_gnullvm 0.48.0", + "windows_x86_64_msvc 0.48.0", ] [[package]] name = "windows_aarch64_gnullvm" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" +checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608" [[package]] name = "windows_aarch64_gnullvm" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc" [[package]] name = "windows_aarch64_msvc" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" +checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7" [[package]] name = "windows_aarch64_msvc" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3" [[package]] name = "windows_i686_gnu" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" +checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640" [[package]] name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241" [[package]] name = "windows_i686_msvc" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" +checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605" [[package]] name = "windows_i686_msvc" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00" [[package]] name = "windows_x86_64_gnu" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" +checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45" [[package]] name = "windows_x86_64_gnu" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1" [[package]] name = "windows_x86_64_gnullvm" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" +checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463" [[package]] name = "windows_x86_64_gnullvm" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953" [[package]] name = "windows_x86_64_msvc" -version = "0.48.5" +version = "0.42.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" +checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd" [[package]] name = "windows_x86_64_msvc" -version = "0.52.6" +version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a" [[package]] name = "winreg" -version = "0.50.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" +checksum = "0120db82e8a1e0b9fb3345a539c478767c0048d842860994d96113d5b667bd69" dependencies = [ - "cfg-if", - "windows-sys 0.48.0", + "winapi", ] [[package]] @@ -1409,43 +1673,23 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85e60b0d1b5f99db2556934e21937020776a5d31520bf169e851ac44e6420214" -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.76", -] - [[package]] name = "zeroize" -version = "1.3.0" +version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4756f7db3f7b5574938c3eb1c117038b8e07f95ee6718c0efad4ac21508f1efd" +checksum = "525b4ec142c6b68a2d10f01f7bbf6755599ca3f81ea53b8431b7dd348f5fdb2d" dependencies = [ "zeroize_derive", ] [[package]] name = "zeroize_derive" -version = "1.4.2" +version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "3f8f187641dad4f680d25c4bfc4225b418165984179f26ca76ec4fb6441d3a17" dependencies = [ "proc-macro2", "quote", - "syn 2.0.76", + "syn 1.0.95", + "synstructure", ] diff --git a/Cargo.toml b/Cargo.toml index d6d0e7f..4eef62d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,32 +3,32 @@ name = "sia-rust" version = "0.1.0" edition = "2018" -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +# FIXME Optional fields missing from Cargo.toml: 'description', 'repository', and 'license'. These are not necessary, but recommended + +[lib] +crate-type = ["cdylib", "rlib"] [dependencies] ed25519-dalek = { version = "1.0.1", features = ["serde"] } +curve25519-dalek = "3.2.0" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1", features = ["preserve_order", "raw_value"] } -serde_with = "1.14.0" nom = "6.1.2" blake2b_simd = "0.5" chrono = { version = "0.4.23", "features" = ["serde"] } -hex = "0.4.2" -reqwest = { version = "0.11.9", features = ["json"]} +log = { version = "0.4.19", "features" = ["std"] } +hex = { version = "0.4.2", "features" = ["serde"] } +reqwest = { version = "0.11.9", features = ["json", "rustls-tls"], default-features = false } base64 = "0.21.2" url = { version = "2.2.2", features = ["serde"] } derive_more = "0.99.11" rustc-hex = "2" -mm2_net = { path = "../../mm2_net" } http = "0.2.12" -common = { path = "../../common" } async-trait = "0.1.76" thiserror = "1.0.40" percent-encoding = "2.1.0" - [dev-dependencies] once_cell = "1.18.0" -tokio = "1.28.2" [target.'cfg(target_arch = "wasm32")'.dependencies] getrandom = { version = "0.2.9", features = ["js"] } @@ -42,3 +42,8 @@ web-sys = { version = "0.3.55", features = ["Request", "RequestInit", "RequestMo [target.'cfg(target_arch = "wasm32")'.dev-dependencies] wasm-bindgen-test = { version = "0.3.2" } + +[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies] +tokio = { version = "1.28.2", features = ["rt", "macros"] } + + diff --git a/src/blake2b_internal.rs b/src/blake2b_internal.rs index 39c756b..382296c 100644 --- a/src/blake2b_internal.rs +++ b/src/blake2b_internal.rs @@ -1,7 +1,4 @@ -use crate::specifier::Specifier; -use crate::spend_policy::UnlockKey; -use crate::types::H256; -use crate::PublicKey; +use crate::types::{Hash256, PublicKey, Specifier, UnlockKey}; use blake2b_simd::Params; use std::default::Default; @@ -24,16 +21,21 @@ const STANDARD_SIGS_REQUIRED_BLAKE2B_HASH: [u8; 32] = [ 0x5a, 0xb4, 0xe1, 0xa7, 0x6e, 0x62, 0x4a, 0x87, 0x98, 0xcb, 0x63, 0x49, 0x7b, ]; +// FIXME remove direct indexing of arrays or add sanity checks to prevent out of bound access + +/// Directly ported from Sia core +/// #[derive(Debug, PartialEq)] pub struct Accumulator { - trees: [H256; 64], + trees: [Hash256; 64], num_leaves: u64, } impl Default for Accumulator { fn default() -> Self { + // Initialize all bytes to zero Accumulator { - trees: [H256::default(); 64], // Initialize all bytes to zero + trees: std::array::from_fn(|_| Hash256::default()), num_leaves: 0, } } @@ -44,7 +46,7 @@ impl Accumulator { fn has_tree_at_height(&self, height: u64) -> bool { self.num_leaves & (1 << height) != 0 } // Add a leaf to the accumulator - pub fn add_leaf(&mut self, h: H256) { + pub fn add_leaf(&mut self, h: Hash256) { let mut i = 0; let mut new_hash = h; while self.has_tree_at_height(i) { @@ -56,13 +58,13 @@ impl Accumulator { } // Calulate the root hash of the Merkle tree - pub fn root(&self) -> H256 { + pub fn root(&self) -> Hash256 { // trailing_zeros determines the height Merkle tree accumulator where the current lowest single leaf is located let i = self.num_leaves.trailing_zeros() as u64; if i == 64 { - return H256::default(); // Return all zeros if no leaves + return Hash256::default(); // Return all zeros if no leaves } - let mut root = self.trees[i as usize]; + let mut root = self.trees[i as usize].clone(); for j in i + 1..64 { if self.has_tree_at_height(j) { root = hash_blake2b_pair(&NODE_HASH_PREFIX, &self.trees[j as usize].0, &root.0); @@ -72,7 +74,7 @@ impl Accumulator { } } -pub fn sigs_required_leaf(sigs_required: u64) -> H256 { +pub fn sigs_required_leaf(sigs_required: u64) -> Hash256 { let sigs_required_array: [u8; 8] = sigs_required.to_le_bytes(); let mut combined = Vec::new(); combined.extend_from_slice(&LEAF_HASH_PREFIX); @@ -83,7 +85,7 @@ pub fn sigs_required_leaf(sigs_required: u64) -> H256 { // public key leaf is // blake2b(leafHashPrefix + 16_byte_ascii_algorithm_identifier + public_key_length_u64 + public_key) -pub fn public_key_leaf(unlock_key: &UnlockKey) -> H256 { +pub fn public_key_leaf(unlock_key: &UnlockKey) -> Hash256 { let mut combined = Vec::new(); combined.extend_from_slice(&LEAF_HASH_PREFIX); match unlock_key { @@ -101,7 +103,7 @@ pub fn public_key_leaf(unlock_key: &UnlockKey) -> H256 { hash_blake2b_single(&combined) } -pub fn timelock_leaf(timelock: u64) -> H256 { +pub fn timelock_leaf(timelock: u64) -> Hash256 { let timelock: [u8; 8] = timelock.to_le_bytes(); let mut combined = Vec::new(); combined.extend_from_slice(&LEAF_HASH_PREFIX); @@ -117,8 +119,8 @@ pub fn timelock_leaf(timelock: u64) -> H256 { // ┌─────────┴──────────┐ // ┌─────┴─────┐ │ // timelock pubkey sigsrequired -pub fn standard_unlock_hash(pubkey: &PublicKey) -> H256 { - let pubkey_leaf = public_key_leaf(&UnlockKey::Ed25519(*pubkey)); +pub fn standard_unlock_hash(pubkey: &PublicKey) -> Hash256 { + let pubkey_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey.clone())); let timelock_pubkey_node = hash_blake2b_pair(&NODE_HASH_PREFIX, &STANDARD_TIMELOCK_BLAKE2B_HASH, &pubkey_leaf.0); hash_blake2b_pair( &NODE_HASH_PREFIX, @@ -127,13 +129,15 @@ pub fn standard_unlock_hash(pubkey: &PublicKey) -> H256 { ) } -pub fn hash_blake2b_single(preimage: &[u8]) -> H256 { +pub fn hash_blake2b_single(preimage: &[u8]) -> Hash256 { let hash = Params::new().hash_length(32).to_state().update(preimage).finalize(); - let ret_array = hash.as_bytes(); - ret_array[0..32].into() + let mut array = [0u8; 32]; + debug_assert!(hash.as_bytes().len() == 32); + array.copy_from_slice(hash.as_bytes()); + Hash256(array) } -fn hash_blake2b_pair(prefix: &[u8], leaf1: &[u8], leaf2: &[u8]) -> H256 { +fn hash_blake2b_pair(prefix: &[u8], leaf1: &[u8], leaf2: &[u8]) -> Hash256 { let hash = Params::new() .hash_length(32) .to_state() @@ -141,182 +145,180 @@ fn hash_blake2b_pair(prefix: &[u8], leaf1: &[u8], leaf2: &[u8]) -> H256 { .update(leaf1) .update(leaf2) .finalize(); - let ret_array = hash.as_bytes(); - ret_array[0..32].into() + let mut array = [0u8; 32]; + debug_assert!(hash.as_bytes().len() == 32); + array.copy_from_slice(hash.as_bytes()); + Hash256(array) } -#[test] -fn test_accumulator_new() { - let default_accumulator = Accumulator::default(); +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; - let expected = Accumulator { - trees: [H256::from("0000000000000000000000000000000000000000000000000000000000000000"); 64], - num_leaves: 0, - }; - assert_eq!(default_accumulator, expected) -} + cross_target_tests! { + fn test_accumulator_new() { + let default_accumulator = Accumulator::default(); -#[test] -fn test_accumulator_root_default() { assert_eq!(Accumulator::default().root(), H256::default()) } + let expected = Accumulator { + trees: std::array::from_fn(|_| Hash256::default()), + num_leaves: 0, + }; + assert_eq!(default_accumulator, expected) + } -#[test] -fn test_accumulator_root() { - let mut accumulator = Accumulator::default(); + fn test_accumulator_root_default() { assert_eq!(Accumulator::default().root(), Hash256::default()) } - let timelock_leaf = timelock_leaf(0u64); - accumulator.add_leaf(timelock_leaf); + fn test_accumulator_root() { + let mut accumulator = Accumulator::default(); - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey)); - accumulator.add_leaf(pubkey_leaf); + let timelock_leaf = timelock_leaf(0u64); + accumulator.add_leaf(timelock_leaf); - let sigs_required_leaf = sigs_required_leaf(1u64); - accumulator.add_leaf(sigs_required_leaf); + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey)); + accumulator.add_leaf(pubkey_leaf); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(accumulator.root(), expected); -} + let sigs_required_leaf = sigs_required_leaf(1u64); + accumulator.add_leaf(sigs_required_leaf); -#[test] -fn test_accumulator_add_leaf_standard_unlock_hash() { - let mut accumulator = Accumulator::default(); + let expected = Hash256::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d").unwrap(); + assert_eq!(accumulator.root(), expected); + } - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + fn test_accumulator_add_leaf_standard_unlock_hash() { + let mut accumulator = Accumulator::default(); - let pubkey_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey)); - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(1u64); + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey_leaf); - accumulator.add_leaf(sigs_required_leaf); + let pubkey_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey)); + let timelock_leaf = timelock_leaf(0u64); + let sigs_required_leaf = sigs_required_leaf(1u64); - let root = accumulator.root(); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(root, expected) -} + accumulator.add_leaf(timelock_leaf); + accumulator.add_leaf(pubkey_leaf); + accumulator.add_leaf(sigs_required_leaf); -#[test] -fn test_accumulator_add_leaf_2of2_multisig_unlock_hash() { - let mut accumulator = Accumulator::default(); + let root = accumulator.root(); + let expected = Hash256::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d").unwrap(); + assert_eq!(root, expected) + } - let pubkey1 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + fn test_accumulator_add_leaf_2of2_multisig_unlock_hash() { + let mut accumulator = Accumulator::default(); - let pubkey1_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey1)); - let pubkey2_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey2)); + let pubkey1 = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey2 = PublicKey::from_bytes( + &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(2u64); + let pubkey1_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey1)); + let pubkey2_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey2)); - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey1_leaf); - accumulator.add_leaf(pubkey2_leaf); - accumulator.add_leaf(sigs_required_leaf); + let timelock_leaf = timelock_leaf(0u64); + let sigs_required_leaf = sigs_required_leaf(2u64); - let root = accumulator.root(); - let expected = H256::from("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7"); - assert_eq!(root, expected) -} + accumulator.add_leaf(timelock_leaf); + accumulator.add_leaf(pubkey1_leaf); + accumulator.add_leaf(pubkey2_leaf); + accumulator.add_leaf(sigs_required_leaf); -#[test] -fn test_accumulator_add_leaf_1of2_multisig_unlock_hash() { - let mut accumulator = Accumulator::default(); + let root = accumulator.root(); + let expected = Hash256::from_str("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7").unwrap(); + assert_eq!(root, expected) + } - let pubkey1 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + fn test_accumulator_add_leaf_1of2_multisig_unlock_hash() { + let mut accumulator = Accumulator::default(); - let pubkey1_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey1)); - let pubkey2_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey2)); + let pubkey1 = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey2 = PublicKey::from_bytes( + &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); - let timelock_leaf = timelock_leaf(0u64); - let sigs_required_leaf = sigs_required_leaf(1u64); + let pubkey1_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey1)); + let pubkey2_leaf = public_key_leaf(&UnlockKey::Ed25519(pubkey2)); - accumulator.add_leaf(timelock_leaf); - accumulator.add_leaf(pubkey1_leaf); - accumulator.add_leaf(pubkey2_leaf); - accumulator.add_leaf(sigs_required_leaf); + let timelock_leaf = timelock_leaf(0u64); + let sigs_required_leaf = sigs_required_leaf(1u64); - let root = accumulator.root(); - let expected = H256::from("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585"); - assert_eq!(root, expected) -} + accumulator.add_leaf(timelock_leaf); + accumulator.add_leaf(pubkey1_leaf); + accumulator.add_leaf(pubkey2_leaf); + accumulator.add_leaf(sigs_required_leaf); -#[test] -fn test_standard_unlock_hash() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + let root = accumulator.root(); + let expected = Hash256::from_str("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585").unwrap(); + assert_eq!(root, expected) + } - let hash = standard_unlock_hash(&pubkey); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected) -} + fn test_standard_unlock_hash() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); -#[test] -fn test_hash_blake2b_pair() { - let left: [u8; 32] = hex::decode("cdcce3978a58ceb6c8480d218646db4eae85eb9ea9c2f5138fbacb4ce2c701e3") - .unwrap() - .try_into() - .unwrap(); - let right: [u8; 32] = hex::decode("b36010eb285c154a8cd63084acbe7eac0c4d625ab4e1a76e624a8798cb63497b") - .unwrap() - .try_into() - .unwrap(); - - let hash = hash_blake2b_pair(&NODE_HASH_PREFIX, &left, &right); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected) -} + let hash = standard_unlock_hash(&pubkey); + let expected = Hash256::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d").unwrap(); + assert_eq!(hash, expected) + } -#[test] -fn test_timelock_leaf() { - let hash = timelock_leaf(0); - let expected = H256::from(STANDARD_TIMELOCK_BLAKE2B_HASH); - assert_eq!(hash, expected) -} + fn test_hash_blake2b_pair() { + let left: [u8; 32] = hex::decode("cdcce3978a58ceb6c8480d218646db4eae85eb9ea9c2f5138fbacb4ce2c701e3") + .unwrap() + .try_into() + .unwrap(); + let right: [u8; 32] = hex::decode("b36010eb285c154a8cd63084acbe7eac0c4d625ab4e1a76e624a8798cb63497b") + .unwrap() + .try_into() + .unwrap(); + + let hash = hash_blake2b_pair(&NODE_HASH_PREFIX, &left, &right); + let expected = Hash256::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d").unwrap(); + assert_eq!(hash, expected) + } -#[test] -fn test_sigs_required_leaf() { - let hash = sigs_required_leaf(1u64); - let expected = H256::from(STANDARD_SIGS_REQUIRED_BLAKE2B_HASH); - assert_eq!(hash, expected) -} + fn test_timelock_leaf() { + let hash = timelock_leaf(0); + let expected = Hash256(STANDARD_TIMELOCK_BLAKE2B_HASH); + assert_eq!(hash, expected) + } -#[test] -fn test_hash_blake2b_single() { - let hash = hash_blake2b_single(&hex::decode("006564323535313900000000000000000020000000000000000102030000000000000000000000000000000000000000000000000000000000").unwrap()); - let expected = H256::from("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b"); - assert_eq!(hash, expected) -} + fn test_sigs_required_leaf() { + let hash = sigs_required_leaf(1u64); + let expected = Hash256(STANDARD_SIGS_REQUIRED_BLAKE2B_HASH); + assert_eq!(hash, expected) + } -#[test] -fn test_public_key_leaf() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + fn test_hash_blake2b_single() { + let hash = hash_blake2b_single(&hex::decode("006564323535313900000000000000000020000000000000000102030000000000000000000000000000000000000000000000000000000000").unwrap()); + let expected = Hash256::from_str("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b").unwrap(); + assert_eq!(hash, expected) + } + + fn test_public_key_leaf() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); - let hash = public_key_leaf(&UnlockKey::Ed25519(pubkey)); - let expected = H256::from("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b"); - assert_eq!(hash, expected) + let hash = public_key_leaf(&UnlockKey::Ed25519(pubkey)); + let expected = Hash256::from_str("21ce940603a2ee3a283685f6bfb4b122254894fd1ed3eb59434aadbf00c75d5b").unwrap(); + assert_eq!(hash, expected) + } + } } diff --git a/src/encoding.rs b/src/encoding.rs index 9d798f0..e27dd94 100644 --- a/src/encoding.rs +++ b/src/encoding.rs @@ -1,33 +1,5 @@ use crate::blake2b_internal::hash_blake2b_single; -use crate::types::H256; -use crate::{PublicKey, Signature}; -use serde::{Deserialize, Deserializer, Serialize, Serializer}; -use std::convert::From; -use std::convert::{TryFrom, TryInto}; -use std::fmt; -use std::str::FromStr; - -#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] -#[serde(try_from = "String", into = "String")] -pub struct HexArray64(#[serde(with = "hex")] pub [u8; 64]); - -impl AsRef<[u8]> for HexArray64 { - fn as_ref(&self) -> &[u8] { &self.0 } -} - -impl TryFrom for HexArray64 { - type Error = hex::FromHexError; - - fn try_from(value: String) -> Result { - let bytes = hex::decode(value)?; - let array = bytes.try_into().map_err(|_| hex::FromHexError::InvalidStringLength)?; - Ok(HexArray64(array)) - } -} - -impl From for String { - fn from(value: HexArray64) -> Self { hex::encode(value.0) } -} +use crate::types::Hash256; // https://github.com/SiaFoundation/core/blob/092850cc52d3d981b19c66cd327b5d945b3c18d3/types/encoding.go#L16 // TODO go implementation limits this to 1024 bytes, should we? @@ -41,7 +13,7 @@ impl Encoder { /// writes a length-prefixed []byte to the underlying stream. pub fn write_len_prefixed_bytes(&mut self, data: &[u8]) { - self.buffer.extend_from_slice(&data.len().to_le_bytes()); + self.buffer.extend_from_slice(&(data.len() as u64).to_le_bytes()); self.buffer.extend_from_slice(data); } @@ -67,10 +39,10 @@ impl Encoder { pub fn write_bool(&mut self, b: bool) { self.buffer.push(b as u8) } - pub fn hash(&self) -> H256 { hash_blake2b_single(&self.buffer) } + pub fn hash(&self) -> Hash256 { hash_blake2b_single(&self.buffer) } // Utility method to create, encode, and hash - pub fn encode_and_hash(item: &T) -> H256 { + pub fn encode_and_hash(item: &T) -> Hash256 { let mut encoder = Encoder::default(); item.encode(&mut encoder); encoder.hash() @@ -81,322 +53,94 @@ pub trait Encodable { fn encode(&self, encoder: &mut Encoder); } -/// This wrapper allows us to use Signature internally but still serde as "sig:" prefixed string -#[derive(Debug)] -pub struct PrefixedSignature(pub Signature); - -impl<'de> Deserialize<'de> for PrefixedSignature { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct PrefixedSignatureVisitor; - - impl<'de> serde::de::Visitor<'de> for PrefixedSignatureVisitor { - type Value = PrefixedSignature; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string prefixed with 'sig:' and followed by a 128-character hex string") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if let Some(hex_str) = value.strip_prefix("sig:") { - Signature::from_str(hex_str) - .map(PrefixedSignature) - .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } - } - } - - deserializer.deserialize_str(PrefixedSignatureVisitor) - } -} - -impl Serialize for PrefixedSignature { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{}", self)) - } -} - -impl fmt::Display for PrefixedSignature { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "sig:{:x}", self.0) } -} - -impl From for Signature { - fn from(sia_hash: PrefixedSignature) -> Self { sia_hash.0 } -} - -impl From for PrefixedSignature { - fn from(signature: Signature) -> Self { PrefixedSignature(signature) } +impl Encodable for Hash256 { + fn encode(&self, encoder: &mut Encoder) { encoder.write_slice(&self.0); } } -/// This wrapper allows us to use PublicKey internally but still serde as "ed25519:" prefixed string -#[derive(Clone, Debug, PartialEq)] -pub struct PrefixedPublicKey(pub PublicKey); - -impl<'de> Deserialize<'de> for PrefixedPublicKey { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct PrefixedPublicKeyVisitor; - - impl<'de> serde::de::Visitor<'de> for PrefixedPublicKeyVisitor { - type Value = PrefixedPublicKey; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string prefixed with 'ed25519:' and followed by a 64-character hex string") - } +#[cfg(test)] +mod test { + use super::*; + use std::str::FromStr; - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if let Some(hex_str) = value.strip_prefix("ed25519:") { - let bytes = - hex::decode(hex_str).map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))?; - PublicKey::from_bytes(&bytes) - .map(PrefixedPublicKey) - .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } - } + cross_target_tests! { + fn test_encoder_default_hash() { + assert_eq!( + Encoder::default().hash(), + Hash256::from_str("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8").unwrap() + ) } - deserializer.deserialize_str(PrefixedPublicKeyVisitor) - } -} - -impl Serialize for PrefixedPublicKey { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("ed25519:{}", hex::encode(self.0.as_bytes()))) - } -} - -impl From for PublicKey { - fn from(sia_public_key: PrefixedPublicKey) -> Self { sia_public_key.0 } -} - -impl From for PrefixedPublicKey { - fn from(public_key: PublicKey) -> Self { PrefixedPublicKey(public_key) } -} - -/// This wrapper allows us to use H256 internally but still serde as "h:" prefixed string -#[derive(Clone, Debug, PartialEq)] -pub struct PrefixedH256(pub H256); - -// FIXME this code pattern is reoccuring in many places and should be generalized with helpers or macros -impl<'de> Deserialize<'de> for PrefixedH256 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct PrefixedH256Visitor; - - impl<'de> serde::de::Visitor<'de> for PrefixedH256Visitor { - type Value = PrefixedH256; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string prefixed with 'h:' and followed by a 64-character hex string") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if let Some(hex_str) = value.strip_prefix("h:") { - H256::from_str(hex_str) - .map(PrefixedH256) - .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } - } + fn test_encoder_write_bytes() { + let mut encoder = Encoder::default(); + encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); + assert_eq!( + encoder.hash(), + Hash256::from_str("d4a72b52e2e1f40e20ee40ea6d5080a1b1f76164786defbb7691a4427f3388f5").unwrap() + ); } - deserializer.deserialize_str(PrefixedH256Visitor) - } -} - -impl Serialize for PrefixedH256 { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{}", self)) - } -} - -impl fmt::Display for PrefixedH256 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "h:{}", self.0) } -} - -impl From for H256 { - fn from(sia_hash: PrefixedH256) -> Self { sia_hash.0 } -} - -impl From for PrefixedH256 { - fn from(h256: H256) -> Self { PrefixedH256(h256) } -} - -/// This wrapper allows us to use H256 internally but still serde as "scoid:" prefixed string -#[derive(Clone, Debug, PartialEq)] -pub struct ScoidH256(pub H256); - -// FIXME this code pattern is reoccuring in many places and should be generalized with helpers or macros -impl<'de> Deserialize<'de> for ScoidH256 { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct ScoidH256Visitor; + fn test_encoder_write_u8() { + let mut encoder = Encoder::default(); + encoder.write_u8(1); + assert_eq!( + encoder.hash(), + Hash256::from_str("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25").unwrap() + ); + } - impl<'de> serde::de::Visitor<'de> for ScoidH256Visitor { - type Value = ScoidH256; + fn test_encoder_write_u64() { + let mut encoder = Encoder::default(); + encoder.write_u64(1); + assert_eq!( + encoder.hash(), + Hash256::from_str("1dbd7d0b561a41d23c2a469ad42fbd70d5438bae826f6fd607413190c37c363b").unwrap() + ); + } - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string prefixed with 'scoid:' and followed by a 64-character hex string") - } + fn test_encoder_write_distiguisher() { + let mut encoder = Encoder::default(); + encoder.write_distinguisher("test"); + assert_eq!( + encoder.hash(), + Hash256::from_str("25fb524721bf98a9a1233a53c40e7e198971b003bf23c24f59d547a1bb837f9c").unwrap() + ); + } - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if let Some(hex_str) = value.strip_prefix("scoid:") { - H256::from_str(hex_str) - .map(ScoidH256) - .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } - } + fn test_encoder_write_bool() { + let mut encoder = Encoder::default(); + encoder.write_bool(true); + assert_eq!( + encoder.hash(), + Hash256::from_str("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25").unwrap() + ); } - deserializer.deserialize_str(ScoidH256Visitor) - } -} + fn test_encoder_reset() { + let mut encoder = Encoder::default(); + encoder.write_bool(true); + assert_eq!( + encoder.hash(), + Hash256::from_str("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25").unwrap() + ); + + encoder.reset(); + encoder.write_bool(false); + assert_eq!( + encoder.hash(), + Hash256::from_str("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314").unwrap() + ); + } -impl Serialize for ScoidH256 { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&format!("{}", self)) + fn test_encoder_complex() { + let mut encoder = Encoder::default(); + encoder.write_distinguisher("test"); + encoder.write_bool(true); + encoder.write_u8(1); + encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); + assert_eq!( + encoder.hash(), + Hash256::from_str("b66d7a9bef9fb303fe0e41f6b5c5af410303e428c4ff9231f6eb381248693221").unwrap() + ); + } } } - -impl fmt::Display for ScoidH256 { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "h:{}", self.0) } -} - -impl From for H256 { - fn from(sia_hash: ScoidH256) -> Self { sia_hash.0 } -} - -impl From for ScoidH256 { - fn from(h256: H256) -> Self { ScoidH256(h256) } -} - -impl Encodable for H256 { - fn encode(&self, encoder: &mut Encoder) { encoder.write_slice(&self.0); } -} - -#[test] -fn test_encoder_default_hash() { - assert_eq!( - Encoder::default().hash(), - H256::from("0e5751c026e543b2e8ab2eb06099daa1d1e5df47778f7787faab45cdf12fe3a8") - ) -} - -#[test] -fn test_encoder_write_bytes() { - let mut encoder = Encoder::default(); - encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); - assert_eq!( - encoder.hash(), - H256::from("d4a72b52e2e1f40e20ee40ea6d5080a1b1f76164786defbb7691a4427f3388f5") - ); -} - -#[test] -fn test_encoder_write_u8() { - let mut encoder = Encoder::default(); - encoder.write_u8(1); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); -} - -#[test] -fn test_encoder_write_u64() { - let mut encoder = Encoder::default(); - encoder.write_u64(1); - assert_eq!( - encoder.hash(), - H256::from("1dbd7d0b561a41d23c2a469ad42fbd70d5438bae826f6fd607413190c37c363b") - ); -} - -#[test] -fn test_encoder_write_distiguisher() { - let mut encoder = Encoder::default(); - encoder.write_distinguisher("test"); - assert_eq!( - encoder.hash(), - H256::from("25fb524721bf98a9a1233a53c40e7e198971b003bf23c24f59d547a1bb837f9c") - ); -} - -#[test] -fn test_encoder_write_bool() { - let mut encoder = Encoder::default(); - encoder.write_bool(true); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); -} - -#[test] -fn test_encoder_reset() { - let mut encoder = Encoder::default(); - encoder.write_bool(true); - assert_eq!( - encoder.hash(), - H256::from("ee155ace9c40292074cb6aff8c9ccdd273c81648ff1149ef36bcea6ebb8a3e25") - ); - - encoder.reset(); - encoder.write_bool(false); - assert_eq!( - encoder.hash(), - H256::from("03170a2e7597b7b7e3d84c05391d139a62b157e78786d8c082f29dcf4c111314") - ); -} - -#[test] -fn test_encoder_complex() { - let mut encoder = Encoder::default(); - encoder.write_distinguisher("test"); - encoder.write_bool(true); - encoder.write_u8(1); - encoder.write_len_prefixed_bytes(&[1, 2, 3, 4]); - assert_eq!( - encoder.hash(), - H256::from("b66d7a9bef9fb303fe0e41f6b5c5af410303e428c4ff9231f6eb381248693221") - ); -} diff --git a/src/hash.rs b/src/hash.rs deleted file mode 100644 index c97d79c..0000000 --- a/src/hash.rs +++ /dev/null @@ -1,362 +0,0 @@ -//! Fixed-size hashes -use rustc_hex::{FromHex, FromHexError, ToHex}; -use serde; -use serde::de::Unexpected; -use std::cmp::Ordering; -use std::hash::{Hash, Hasher}; -use std::str::FromStr; -use std::{cmp, fmt, ops, str}; - -// FIXME; most of this can be removed -// This is mostly a direct copy of the H256 type from Komodo DeFi Framework - -// FIXME H256::from() has unhandled unwrap() calls -// eg, H256::from("0") - -macro_rules! impl_global_hash { - ($name: ident, $size: expr) => { - #[derive(Copy)] - #[repr(C)] - pub struct $name([u8; $size]); - - impl Default for $name { - fn default() -> Self { $name([0u8; $size]) } - } - - impl AsRef<$name> for $name { - fn as_ref(&self) -> &$name { self } - } - - impl AsRef<[u8]> for $name { - fn as_ref(&self) -> &[u8] { &self.0 } - } - - impl Clone for $name { - fn clone(&self) -> Self { - let mut result = Self::default(); - result.copy_from_slice(&self.0); - result - } - } - - impl From<[u8; $size]> for $name { - fn from(h: [u8; $size]) -> Self { $name(h) } - } - - impl From<$name> for [u8; $size] { - fn from(h: $name) -> Self { h.0 } - } - - impl<'a> From<&'a [u8]> for $name { - fn from(slc: &[u8]) -> Self { - let mut inner = [0u8; $size]; - inner[..].clone_from_slice(&slc[0..$size]); - $name(inner) - } - } - - impl From<&'static str> for $name { - fn from(s: &'static str) -> Self { s.parse().unwrap() } - } - - impl From for $name { - fn from(v: u8) -> Self { - let mut result = Self::default(); - result.0[0] = v; - result - } - } - - impl str::FromStr for $name { - type Err = FromHexError; - - fn from_str(s: &str) -> Result { - let vec: Vec = s.from_hex()?; - match vec.len() { - $size => { - let mut result = [0u8; $size]; - result.copy_from_slice(&vec); - Ok($name(result)) - }, - _ => Err(FromHexError::InvalidHexLength), - } - } - } - - impl fmt::Debug for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.0.to_hex::()) } - } - - impl fmt::Display for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str(&self.0.to_hex::()) } - } - - impl ops::Deref for $name { - type Target = [u8; $size]; - - fn deref(&self) -> &Self::Target { &self.0 } - } - - impl ops::DerefMut for $name { - fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } - } - - impl cmp::PartialEq for $name { - fn eq(&self, other: &Self) -> bool { - let self_ref: &[u8] = &self.0; - let other_ref: &[u8] = &other.0; - self_ref == other_ref - } - } - - impl cmp::PartialEq<&$name> for $name { - fn eq(&self, other: &&Self) -> bool { - let self_ref: &[u8] = &self.0; - let other_ref: &[u8] = &other.0; - self_ref == other_ref - } - } - - impl cmp::PartialOrd for $name { - fn partial_cmp(&self, other: &Self) -> Option { - let self_ref: &[u8] = &self.0; - let other_ref: &[u8] = &other.0; - self_ref.partial_cmp(other_ref) - } - } - - impl Hash for $name { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - state.write(&self.0); - state.finish(); - } - } - - impl Eq for $name {} - - impl $name { - pub fn take(self) -> [u8; $size] { self.0 } - - pub fn as_slice(&self) -> &[u8] { &self.0 } - - pub fn reversed(&self) -> Self { - let mut result = self.clone(); - result.reverse(); - result - } - - pub fn size() -> usize { $size } - - pub fn is_zero(&self) -> bool { self.0.iter().all(|b| *b == 0) } - } - }; -} - -macro_rules! impl_hash { - ($name: ident, $other: ident, $size: expr) => { - /// Hash serialization - #[derive(Clone, Copy)] - pub struct $name(pub [u8; $size]); - - impl $name { - pub const fn const_default() -> $name { $name([0; $size]) } - } - - impl Default for $name { - fn default() -> Self { $name::const_default() } - } - - impl fmt::Display for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{:02x}", self) } - } - - impl fmt::Debug for $name { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "{:02x}", self) } - } - - impl From for $name - where - $other: From, - { - fn from(o: T) -> Self { $name($other::from(o).take()) } - } - - impl FromStr for $name { - type Err = <$other as FromStr>::Err; - - fn from_str(s: &str) -> Result { - let other = $other::from_str(s)?; - Ok($name(other.take())) - } - } - - #[allow(clippy::from_over_into)] - impl Into<$other> for $name { - fn into(self) -> $other { $other::from(self.0) } - } - - #[allow(clippy::from_over_into)] - impl Into> for $name { - fn into(self) -> Vec { self.0.to_vec() } - } - - impl Eq for $name {} - - impl Ord for $name { - fn cmp(&self, other: &Self) -> Ordering { - let self_ref: &[u8] = &self.0; - let other_ref: &[u8] = &other.0; - self_ref.cmp(other_ref) - } - } - - impl PartialEq for $name { - fn eq(&self, other: &Self) -> bool { - let self_ref: &[u8] = &self.0; - let other_ref: &[u8] = &other.0; - self_ref == other_ref - } - } - - impl PartialOrd for $name { - fn partial_cmp(&self, other: &Self) -> Option { - let self_ref: &[u8] = &self.0; - let other_ref: &[u8] = &other.0; - self_ref.partial_cmp(other_ref) - } - } - - impl Hash for $name { - fn hash(&self, state: &mut H) - where - H: Hasher, - { - $other::from(self.0.clone()).hash(state) - } - } - - impl serde::Serialize for $name { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut hex = String::new(); - hex.push_str(&$other::from(self.0.clone()).to_hex::()); - serializer.serialize_str(&hex) - } - } - - impl<'a> serde::Deserialize<'a> for $name { - fn deserialize(deserializer: D) -> Result<$name, D::Error> - where - D: serde::Deserializer<'a>, - { - struct HashVisitor; - - impl<'b> serde::de::Visitor<'b> for HashVisitor { - type Value = $name; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a hash string") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if value.len() != $size * 2 { - return Err(E::invalid_value(Unexpected::Str(value), &self)); - } - - match value[..].from_hex::>() { - Ok(ref v) => { - let mut result = [0u8; $size]; - result.copy_from_slice(v); - Ok($name($other::from(result).take())) - }, - _ => Err(E::invalid_value(Unexpected::Str(value), &self)), - } - } - - fn visit_string(self, value: String) -> Result - where - E: serde::de::Error, - { - self.visit_str(value.as_ref()) - } - } - - deserializer.deserialize_identifier(HashVisitor) - } - } - - impl ::core::fmt::LowerHex for $name { - fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { - for i in &self.0[..] { - write!(f, "{:02x}", i)?; - } - Ok(()) - } - } - }; -} - -impl H256 { - #[inline] - pub fn reversed(&self) -> Self { - let mut result = *self; - result.0.reverse(); - result - } -} - -impl GlobalH256 { - #[inline] - pub fn from_reversed_str(s: &'static str) -> Self { GlobalH256::from(s).reversed() } - - #[inline] - pub fn to_reversed_str(self) -> String { self.reversed().to_string() } -} - -impl_global_hash!(GlobalH256, 32); -impl_hash!(H256, GlobalH256, 32); - -#[cfg(test)] -mod tests { - use super::{GlobalH256, H256}; - use std::str::FromStr; - - #[test] - fn hash_debug() { - let str_reversed = "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"; - let reversed_hash = H256::from(str_reversed); - let debug_result = format!("{:?}", reversed_hash); - assert_eq!(debug_result, str_reversed); - } - - #[test] - fn hash_from_str() { - let str_reversed = "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"; - match H256::from_str(str_reversed) { - Ok(reversed_hash) => assert_eq!(format!("{:?}", reversed_hash), str_reversed), - _ => panic!("unexpected"), - } - - let str_reversed = "XXXYYY"; - if H256::from_str(str_reversed).is_ok() { - panic!("unexpected"); - } - } - - #[test] - fn hash_to_global_hash() { - let str_reversed = "00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"; - let reversed_hash = H256::from(str_reversed); - let global_hash = GlobalH256::from(str_reversed); - let global_converted: GlobalH256 = reversed_hash.into(); - assert_eq!(global_converted, global_hash); - } -} diff --git a/src/lib.rs b/src/lib.rs index 14013a4..8f912ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,116 +1,13 @@ -use derive_more::Display; -use ed25519_dalek::{Keypair as Ed25519Keypair, PublicKey as Ed25519PublicKey, SecretKey, - Signature as Ed25519Signature, SignatureError as Ed25519SignatureError, Signer}; -use serde::{Deserialize, Serialize}; -use std::fmt; -use std::ops::Deref; -use std::str::FromStr; +#[macro_use] +pub(crate) mod utils; +// TODO Alright - if this is truly "internal" it should not be public pub mod blake2b_internal; pub mod encoding; -pub mod hash; -pub mod http; -pub mod specifier; -pub mod spend_policy; -pub mod transaction; +pub mod transport; pub mod types; -#[derive(Debug, Display)] -pub enum KeypairError { - InvalidSecretKey(Ed25519SignatureError), -} - #[cfg(test)] mod tests; #[cfg(test)] #[macro_use] extern crate serde_json; - -pub struct Keypair(pub Ed25519Keypair); - -impl Keypair { - pub fn from_private_bytes(bytes: &[u8]) -> Result { - let secret = SecretKey::from_bytes(bytes).map_err(KeypairError::InvalidSecretKey)?; - let public = Ed25519PublicKey::from(&secret); - Ok(Keypair(Ed25519Keypair { secret, public })) - } - - pub fn sign(&self, message: &[u8]) -> Signature { self.0.sign(message).into() } -} - -#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct Signature(pub Ed25519Signature); - -impl Signature { - pub fn from_bytes(bytes: &[u8]) -> Result { - let signature = Ed25519Signature::from_bytes(bytes).map_err(SignatureError::ParseError)?; - Ok(Signature(signature)) - } -} - -impl From for Signature { - fn from(signature: Ed25519Signature) -> Self { Signature(signature) } -} - -impl Deref for Signature { - type Target = Ed25519Signature; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -#[derive(Debug, Display)] -pub enum SignatureError { - ParseError(ed25519_dalek::ed25519::Error), - InvalidSignature(Ed25519SignatureError), -} - -impl From for SignatureError { - fn from(e: Ed25519SignatureError) -> Self { SignatureError::InvalidSignature(e) } -} - -impl fmt::LowerHex for Signature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", hex::encode(self.0.to_bytes())) } -} - -impl FromStr for Signature { - type Err = SignatureError; - - fn from_str(s: &str) -> Result { - Ed25519Signature::from_str(s) - .map(Signature) - .map_err(SignatureError::InvalidSignature) - } -} - -#[derive(Copy, Clone, Debug, PartialEq, Deserialize, Serialize)] -pub struct PublicKey(pub Ed25519PublicKey); - -impl PublicKey { - pub fn from_bytes(bytes: &[u8]) -> Result { - let public_key = Ed25519PublicKey::from_bytes(bytes)?; - Ok(PublicKey(public_key)) - } -} - -impl From for PublicKey { - fn from(public_key: Ed25519PublicKey) -> Self { PublicKey(public_key) } -} - -impl Deref for PublicKey { - type Target = Ed25519PublicKey; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl fmt::Display for PublicKey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", hex::encode(self.as_bytes())) } -} - -impl Deref for Keypair { - type Target = Ed25519Keypair; - - fn deref(&self) -> &Self::Target { &self.0 } -} - -impl Keypair { - pub fn public(&self) -> PublicKey { PublicKey(self.0.public) } -} diff --git a/src/tests/encoding.rs b/src/tests/encoding.rs index ae2524c..9860ca1 100644 --- a/src/tests/encoding.rs +++ b/src/tests/encoding.rs @@ -1,182 +1,173 @@ -use crate::blake2b_internal::standard_unlock_hash; -use crate::encoding::Encoder; -use crate::spend_policy::{SpendPolicy, UnlockCondition}; -use crate::types::{Address, H256}; -use crate::PublicKey; -use std::str::FromStr; - -#[test] -fn test_unlock_condition_unlock_hash_2of2_multisig() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 2); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7"); - assert_eq!(hash, expected); -} - -#[test] -fn test_unlock_condition_unlock_hash_1of2_multisig() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 1); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585"); - assert_eq!(hash, expected); -} - -#[test] -fn test_spend_policy_encode_above() { - let policy = SpendPolicy::above(1); - - let hash = Encoder::encode_and_hash(&policy); - let expected = H256::from("bebf6cbdfb440a92e3e5d832ac30fe5d226ff6b352ed3a9398b7d35f086a8ab6"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:188b997bb99dee13e95f92c3ea150bd76b3ec72e5ba57b0d57439a1a6e2865e9b25ea5d1825e").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_after() { - let policy = SpendPolicy::after(1); - let hash = Encoder::encode_and_hash(&policy); - let expected = H256::from("07b0f28eafd87a082ad11dc4724e1c491821260821a30bec68254444f97d9311"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:60c74e0ce5cede0f13f83b0132cb195c995bc7688c9fac34bbf2b14e14394b8bbe2991bc017f").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_pubkey() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let policy = SpendPolicy::PublicKey(pubkey); - - let hash = Encoder::encode_and_hash(&policy); - let expected = H256::from("4355c8f80f6e5a98b70c9c2f9a22f17747989b4744783c90439b2b034f698bfe"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:55a7793237722c6df8222fd512063cb74228085ef1805c5184713648c159b919ac792fbad0e1").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_hash() { - let hash = H256::from("0102030000000000000000000000000000000000000000000000000000000000"); - let policy = SpendPolicy::Hash(hash); - - let hash = Encoder::encode_and_hash(&policy); - let expected = H256::from("9938967aefa6cbecc1f1620d2df5170d6811d4b2f47a879b621c1099a3b0628a"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:a4d5a06d8d3c2e45aa26627858ce8e881505ae3c9d122a1d282c7824163751936cffb347e435").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_threshold() { - let policy = SpendPolicy::Threshold { - n: 1, - of: vec![SpendPolicy::above(1), SpendPolicy::after(1)], - }; - - let hash = Encoder::encode_and_hash(&policy); - let expected = H256::from("7d792df6cd0b5e0f795287b3bf4087bbcc4c1bd0c52880a552cdda3e5e33d802"); - assert_eq!(hash, expected); - - let address = policy.address(); - let expected = - Address::from_str("addr:4179b53aba165e46e4c85b3c8766bb758fb6f0bfa5721550b81981a3ec38efc460557dc1ded4").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_spend_policy_encode_unlock_condition() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let sub_policy = SpendPolicy::UnlockConditions(unlock_condition); - let base_address = sub_policy.address(); - let expected = - Address::from_str("addr:72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a").unwrap(); - assert_eq!(base_address, expected); - - let policy = SpendPolicy::Threshold { - n: 1, - of: vec![sub_policy], - }; - let address = policy.address(); - let expected = - Address::from_str("addr:1498a58c843ce66740e52421632d67a0f6991ea96db1fc97c29e46f89ae56e3534078876331d").unwrap(); - assert_eq!(address, expected); -} - -#[test] -fn test_unlock_condition_encode() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let hash = Encoder::encode_and_hash(&unlock_condition); - let expected = H256::from("5d49bae37b97c86573a1525246270c180464acf33d63cc2ac0269ef9a8cb9d98"); - assert_eq!(hash, expected); -} - -#[test] -fn test_public_key_encode() { - let public_key = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - - let hash = Encoder::encode_and_hash(&public_key); - let expected = H256::from("d487326614f066416308bf6aa4e5041d1949928e4b26ede98e3cebb36a3b1726"); - assert_eq!(hash, expected); -} - -#[test] -fn test_unlock_condition_unlock_hash_standard() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - - let hash = unlock_condition.unlock_hash(); - let expected = H256::from("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d"); - assert_eq!(hash, expected); - - let hash = standard_unlock_hash(&pubkey); - assert_eq!(hash, expected); +#[cfg(test)] +mod test { + use crate::blake2b_internal::standard_unlock_hash; + use crate::encoding::Encoder; + use crate::types::{Address, Hash256, PublicKey, SpendPolicy, UnlockCondition}; + use std::str::FromStr; + + cross_target_tests! { + fn test_unlock_condition_unlock_hash_2of2_multisig() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey2 = PublicKey::from_bytes( + &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 2); + + let hash = unlock_condition.unlock_hash(); + let expected = Hash256::from_str("1e94357817d236167e54970a8c08bbd41b37bfceeeb52f6c1ce6dd01d50ea1e7").unwrap(); + assert_eq!(hash, expected); + } + + fn test_unlock_condition_unlock_hash_1of2_multisig() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey2 = PublicKey::from_bytes( + &hex::decode("0101010000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let unlock_condition = UnlockCondition::new(vec![pubkey, pubkey2], 0, 1); + + let hash = unlock_condition.unlock_hash(); + let expected = Hash256::from_str("d7f84e3423da09d111a17f64290c8d05e1cbe4cab2b6bed49e3a4d2f659f0585").unwrap(); + assert_eq!(hash, expected); + } + + fn test_spend_policy_encode_above() { + let policy = SpendPolicy::above(1); + + let hash = Encoder::encode_and_hash(&policy); + let expected = Hash256::from_str("bebf6cbdfb440a92e3e5d832ac30fe5d226ff6b352ed3a9398b7d35f086a8ab6").unwrap(); + assert_eq!(hash, expected); + + let address = policy.address(); + let expected = + Address::from_str("188b997bb99dee13e95f92c3ea150bd76b3ec72e5ba57b0d57439a1a6e2865e9b25ea5d1825e").unwrap(); + assert_eq!(address, expected); + } + + fn test_spend_policy_encode_after() { + let policy = SpendPolicy::after(1); + let hash = Encoder::encode_and_hash(&policy); + let expected = Hash256::from_str("07b0f28eafd87a082ad11dc4724e1c491821260821a30bec68254444f97d9311").unwrap(); + assert_eq!(hash, expected); + + let address = policy.address(); + let expected = + Address::from_str("60c74e0ce5cede0f13f83b0132cb195c995bc7688c9fac34bbf2b14e14394b8bbe2991bc017f").unwrap(); + assert_eq!(address, expected); + } + + fn test_spend_policy_encode_pubkey() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let policy = SpendPolicy::PublicKey(pubkey); + + let hash = Encoder::encode_and_hash(&policy); + let expected = Hash256::from_str("4355c8f80f6e5a98b70c9c2f9a22f17747989b4744783c90439b2b034f698bfe").unwrap(); + assert_eq!(hash, expected); + + let address = policy.address(); + let expected = + Address::from_str("55a7793237722c6df8222fd512063cb74228085ef1805c5184713648c159b919ac792fbad0e1").unwrap(); + assert_eq!(address, expected); + } + + fn test_spend_policy_encode_hash() { + let hash = Hash256::from_str("0102030000000000000000000000000000000000000000000000000000000000").unwrap(); + let policy = SpendPolicy::Hash(hash); + + let hash = Encoder::encode_and_hash(&policy); + let expected = Hash256::from_str("9938967aefa6cbecc1f1620d2df5170d6811d4b2f47a879b621c1099a3b0628a").unwrap(); + assert_eq!(hash, expected); + + let address = policy.address(); + let expected = + Address::from_str("a4d5a06d8d3c2e45aa26627858ce8e881505ae3c9d122a1d282c7824163751936cffb347e435").unwrap(); + assert_eq!(address, expected); + } + + fn test_spend_policy_encode_threshold() { + let policy = SpendPolicy::Threshold { + n: 1, + of: vec![SpendPolicy::above(1), SpendPolicy::after(1)], + }; + + let hash = Encoder::encode_and_hash(&policy); + let expected = Hash256::from_str("7d792df6cd0b5e0f795287b3bf4087bbcc4c1bd0c52880a552cdda3e5e33d802").unwrap(); + assert_eq!(hash, expected); + + let address = policy.address(); + let expected = + Address::from_str("4179b53aba165e46e4c85b3c8766bb758fb6f0bfa5721550b81981a3ec38efc460557dc1ded4").unwrap(); + assert_eq!(address, expected); + } + + fn test_spend_policy_encode_unlock_condition() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); + + let sub_policy = SpendPolicy::UnlockConditions(unlock_condition); + let base_address = sub_policy.address(); + let expected = + Address::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a").unwrap(); + assert_eq!(base_address, expected); + + let policy = SpendPolicy::Threshold { + n: 1, + of: vec![sub_policy], + }; + let address = policy.address(); + let expected = + Address::from_str("1498a58c843ce66740e52421632d67a0f6991ea96db1fc97c29e46f89ae56e3534078876331d").unwrap(); + assert_eq!(address, expected); + } + + fn test_unlock_condition_encode() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); + + let hash = Encoder::encode_and_hash(&unlock_condition); + let expected = Hash256::from_str("5d49bae37b97c86573a1525246270c180464acf33d63cc2ac0269ef9a8cb9d98").unwrap(); + assert_eq!(hash, expected); + } + + fn test_public_key_encode() { + let public_key = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + + let hash = Encoder::encode_and_hash(&public_key); + let expected = Hash256::from_str("d487326614f066416308bf6aa4e5041d1949928e4b26ede98e3cebb36a3b1726").unwrap(); + assert_eq!(hash, expected); + } + + fn test_unlock_condition_unlock_hash_standard() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let unlock_condition = UnlockCondition::new(vec![pubkey.clone()], 0, 1); + + let hash = unlock_condition.unlock_hash(); + let expected = Hash256::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515d").unwrap(); + assert_eq!(hash, expected); + let hash = standard_unlock_hash(&pubkey); + assert_eq!(hash, expected); + } + } } diff --git a/src/tests/serde.rs b/src/tests/serde.rs index 4154e03..7abd2a0 100644 --- a/src/tests/serde.rs +++ b/src/tests/serde.rs @@ -1,441 +1,71 @@ -use crate::encoding::PrefixedH256; -use crate::spend_policy::UnlockKey; -use crate::transaction::{SiacoinElement, SiacoinOutput, StateElement, V2Transaction}; -use crate::types::{Address, BlockID, Event}; - -// Ensure the original value matches the value after round-trip (serialize -> deserialize -> serialize) -macro_rules! test_serde { - ($type:ty, $json_value:expr) => {{ - let json_str = $json_value.to_string(); - let value: $type = serde_json::from_str(&json_str).unwrap(); - let serialized = serde_json::to_string(&value).unwrap(); - let serialized_json_value: serde_json::Value = serde_json::from_str(&serialized).unwrap(); - assert_eq!($json_value, serialized_json_value); - }}; -} - -// FIXME reminder to populate the following tests -#[test] -#[ignore] -fn test_serde_block_id() { - test_serde!( - BlockID, - json!("bid:c67c3b2e57490617a25a9fcb9fd54ab6acbe72fc1e4f1f432cb9334177917667") - ); - test_serde!(BlockID, json!("bid:badc0de")); - test_serde!(BlockID, json!("bid:1badc0de")); - test_serde!(BlockID, json!("1badc0de")); - test_serde!(BlockID, json!(1)); -} - -#[test] -fn test_serde_address() { - test_serde!( - Address, - json!("addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f") - ); -} - -#[test] -fn test_serde_unlock_key() { - test_serde!( - UnlockKey, - json!("ed25519:0102030000000000000000000000000000000000000000000000000000000000") - ); -} - -#[test] -fn test_serde_sia_hash() { - test_serde!( - PrefixedH256, - json!("h:dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1") - ); -} - -#[test] -fn test_serde_siacoin_output() { - let j = json!({ - "value": "300000000000000000000000000000", - "address": "addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f" - }); - test_serde!(SiacoinOutput, j); -} - -#[test] -fn test_serde_state_element() { - let j = json!({ - "id": "h:dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1", - "leafIndex": 21, - "merkleProof": null - }); - serde_json::from_value::(j).unwrap(); -} - -#[test] -fn test_serde_siacoin_element() { - let j = json!( { - "id": "h:dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1", - "leafIndex": 21, - "merkleProof": ["h:8dfc4731c4ef4bf35f789893e72402a39c7ea63ba9e75565cb11000d0159959e"], - "siacoinOutput": { - "value": "300000000000000000000000000000", - "address": "addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f" - }, - "maturityHeight": 154 - } - ); - serde_json::from_value::(j).unwrap(); -} - -#[test] -fn test_serde_siacoin_element_null_merkle_proof() { - let j = json!( { - "id": "h:dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1", - "leafIndex": 21, - "merkleProof": null, - "siacoinOutput": { - "value": "300000000000000000000000000000", - "address": "addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f" - }, - "maturityHeight": 154 - } - ); - serde_json::from_value::(j).unwrap(); -} - #[test] -fn test_serde_event_v2_contract_resolution_storage_proof() { - let j = json!( - { - "id": "h:a863dbc4f02efdfbf9f8d03e1aada090ede0a5752b71503787617d5f395c1335", - "index": { - "height": 201, - "id": "bid:e6e5282f107f2957844a93612e71003ec67238f32504b151e9e21fbb9224e8cf" - }, - "timestamp": "2024-07-18T19:04:16Z", - "maturityHeight": 345, - "type": "v2ContractResolution", - "data": { - "resolution": { - "parent": { - "id": "h:b30e0d25d4e414763378236b00a98cfbf9cd6a5e81540d1dcd40338ab6a5c636", - "leafIndex": 397, - "merkleProof": [ - "h:4d2a433de745231ff1eb0736ba68ffc3f8b1a976dbc3eca9649b5cf2dd5c2c44", - "h:e23fdf53d7c3c2bc7dc58660cb16e5b66dbf2e71c0a46c778af1c4d59a83cf63", - "h:0e63636af15d58fd9a87e21719899c2d518a948305e325929cbc4652d0fc3b38", - "h:37e5cee3bb2607e537209807b07dafef9658253080751b11858a9ae844364c0b", - "h:077252892fc0b8e687f14baf2ad3d2812539d05a293bfcabe8f0b884d8c91b01" - ], - "v2FileContract": { - "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", - "proofHeight": 200, - "expirationHeight": 210, - "renterOutput": { - "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" - }, - "hostOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "missedHostValue": "0", - "totalCollateral": "0", - "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "revisionNumber": 0, - "renterSignature": "sig:9d001e60633801956d1ce8b281b18a4b7da1249e8cb1e13b808f19c23e31c52596c303bd5efca278461877050412f1bec489037f101b7f41d3069906c60be30d", - "hostSignature": "sig:9d001e60633801956d1ce8b281b18a4b7da1249e8cb1e13b808f19c23e31c52596c303bd5efca278461877050412f1bec489037f101b7f41d3069906c60be30d" - } - }, - "type": "storageProof", - "resolution": { - "proofIndex": { - "id": "h:ee154b9b26af5a130d189c2467bd0157f24f4357478bfe5184243ab918c20290", - "leafIndex": 416, - "merkleProof": [], - "chainIndex": { - "height": 200, - "id": "bid:ee154b9b26af5a130d189c2467bd0157f24f4357478bfe5184243ab918c20290" - } - }, - "leaf": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "proof": [] - } - }, - "siacoinElement": { - "id": "h:a863dbc4f02efdfbf9f8d03e1aada090ede0a5752b71503787617d5f395c1335", - "leafIndex": 418, - "merkleProof": null, - "siacoinOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "maturityHeight": 345 - }, - "missed": false - } - } - ); - - let _event = serde_json::from_value::(j).unwrap(); - - // FIXME this should deserialize from a JSON object generated from walletd and recalcuate the txid to check encoding/serde -} - -#[test] -fn test_serde_event_v2_contract_resolution_renewal() { - let j = json!( - { - "id": "h:debd3b8461d1aaa9011ba62d79c7ed7991eb0c60f9576880faadf2a8051aad54", - "index": { - "height": 203, - "id": "bid:bd04c08bb96203c7f24adf2d405cb1069c7da8573573011379a986be62fc2a29" - }, - "timestamp": "2024-07-18T19:04:16Z", - "maturityHeight": 347, - "type": "v2ContractResolution", - "data": { - "resolution": { - "parent": { - "id": "h:06b6349f4e76819aa36b7f1190d276b9ca97f0d5fc4564f153d6a36ed3c38033", - "leafIndex": 423, - "merkleProof": [ - "h:ba1427aad85e9985b61f262a2ea768a74f24af02d7e6c17f0cdb92559e7951ea", - "h:147817a1d32c3f068be5456d935bc6cddd6306fe5633b576d91260d43a82e6d8", - "h:f447a5360e1a7c4cab3062dd1699f56ea642b4f6cc6464fdfca0d1aa15fa436c", - "h:1cdf40c0a759931ff590496b953938fbe7315394ce3726b4e4c4b81fed3d5498" - ], - "v2FileContract": { - "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", - "proofHeight": 211, - "expirationHeight": 221, - "renterOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "hostOutput": { - "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" - }, - "missedHostValue": "0", - "totalCollateral": "0", - "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "revisionNumber": 0, - "renterSignature": "sig:7d6f0e5b799c689dca7b55b1ff8ad028c7285b777d6df0e68235bde5778802adfb87e80afaf5d6c9b9fa63cd0e433aaa7189e3fdf2c7bf374c0ca20858071f03", - "hostSignature": "sig:7d6f0e5b799c689dca7b55b1ff8ad028c7285b777d6df0e68235bde5778802adfb87e80afaf5d6c9b9fa63cd0e433aaa7189e3fdf2c7bf374c0ca20858071f03" - } - }, - "type": "renewal", - "resolution": { - "finalRevision": { - "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", - "proofHeight": 211, - "expirationHeight": 221, - "renterOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "hostOutput": { - "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" - }, - "missedHostValue": "0", - "totalCollateral": "0", - "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "revisionNumber": 18446744073709551615u64, - "renterSignature": "sig:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "hostSignature": "sig:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - }, - "newContract": { - "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", - "proofHeight": 221, - "expirationHeight": 231, - "renterOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "hostOutput": { - "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" - }, - "missedHostValue": "0", - "totalCollateral": "0", - "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "revisionNumber": 0, - "renterSignature": "sig:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", - "hostSignature": "sig:00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" - }, - "renterRollover": "0", - "hostRollover": "0", - "renterSignature": "sig:54a4bb0247518f62b20bf141686e2c05858e91acd23ae5e42436d173e331aca92af344e8cb9b5da98f0bdef01c7b7d840cbe7e781b8f7acc7c33b0fa44c7ef08", - "hostSignature": "sig:54a4bb0247518f62b20bf141686e2c05858e91acd23ae5e42436d173e331aca92af344e8cb9b5da98f0bdef01c7b7d840cbe7e781b8f7acc7c33b0fa44c7ef08" - } - }, - "siacoinElement": { - "id": "h:debd3b8461d1aaa9011ba62d79c7ed7991eb0c60f9576880faadf2a8051aad54", - "leafIndex": 427, - "merkleProof": null, - "siacoinOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "maturityHeight": 347 - }, - "missed": false - } - } - ); - - let _event = serde_json::from_value::(j).unwrap(); - - // FIXME this should deserialize from a JSON object generated from walletd and recalcuate the txid to check encoding/serde -} - -#[test] -#[ignore] // FIXME Error("expected an empty map for expiration", line: 0, column: 0) -fn test_serde_event_v2_contract_resolution_expiration() { - let j = json!( - { - "id": "h:4c0170b9e82eacc2d14a13b974ce0c03560358276f135403bd060b53ce53be1c", - "index": { - "height": 190, - "id": "bid:730f554f8cd5e6bd855b21b8c53f59808f3aa7351093f44da7761181283e3c6b" - }, - "timestamp": "2024-07-18T19:04:16Z", - "maturityHeight": 334, - "type": "v2ContractResolution", - "data": { - "resolution": { - "parent": { - "id": "h:34f6bb9b9ed58dedebce2f39d29a526ea3012e9ae005cfca6a5257761c5412f6", - "leafIndex": 351, - "merkleProof": [ - "h:e805430ecdd47bcaca574f78721c3b6a24f0a877110fc9fa7ab347fd231a9885", - "h:70782818a59e512d4995efd4ee94299e601496011b9c42b47eb0a3cd65aa89c9", - "h:42ab48d2ef2b54352d44ab2ef33c1a6d76589360c0dd556d703a452b7d3e4a2c", - "h:4af61bcae0a46d70f9b826b9bace336647389c38e6cb4c54356b9dd7fd6060aa", - "h:59d21dd10aa3def083106844e23ad7f6b93e309c80b24a03e2c9b6eba8acef33", - "h:f95c3f0fc4d632e5da8adcaa9249ea6b0c5fe66466a951871f5dc30a0c96b76d", - "h:3374baebf913a23e0b9811ae22e72f6cdf6999d332ccda4b4dbab87f58b2a574" - ], - "v2FileContract": { - "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", - "proofHeight": 179, - "expirationHeight": 189, - "renterOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "hostOutput": { - "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" - }, - "missedHostValue": "0", - "totalCollateral": "0", - "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", - "revisionNumber": 0, - "renterSignature": "sig:c293b22c9feee5a081699ddbf83486704df855129c2bbe27c2dc56afcb7e68cd355785fa36954471c1e48691864b240969168422b1fd6396e18f720ebec50e00", - "hostSignature": "sig:c293b22c9feee5a081699ddbf83486704df855129c2bbe27c2dc56afcb7e68cd355785fa36954471c1e48691864b240969168422b1fd6396e18f720ebec50e00" - } - }, - "type": "expiration", - "resolution": {} - }, - "siacoinElement": { - "id": "h:4c0170b9e82eacc2d14a13b974ce0c03560358276f135403bd060b53ce53be1c", - "leafIndex": 391, - "merkleProof": null, - "siacoinOutput": { - "value": "10000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "maturityHeight": 334 - }, - "missed": true - } - } - ); - - let _event = serde_json::from_value::(j).unwrap(); -} - -#[test] -#[ignore] // I don't have a good test case for this yet because wallet_test.go TestEventTypes doesn't output this type +#[ignore] // FIXME I don't have a good test case for this yet because wallet_test.go TestEventTypes doesn't output this type fn test_serde_event_v2_contract_resolution_finalization() { + use crate::types::Event; let j = json!( { - "id": "h:4057e021e1d6dec8d4e4ef9d6e9fa2e4491c559144848b9af5765e03b39bb69d", + "id": "4057e021e1d6dec8d4e4ef9d6e9fa2e4491c559144848b9af5765e03b39bb69d", "index": { "height": 0, - "id": "bid:0000000000000000000000000000000000000000000000000000000000000000" + "id": "0000000000000000000000000000000000000000000000000000000000000000" }, "timestamp": "2024-07-12T10:04:18.564506-07:00", "maturityHeight": 0, "type": "v2ContractResolution", "data": { "parent": { - "id": "h:ee87ab83f9d16c9377d6154c477ac40d2ee70619de2ba146fcfe36fd0de86bf5", + "id": "ee87ab83f9d16c9377d6154c477ac40d2ee70619de2ba146fcfe36fd0de86bf5", "leafIndex": 6680213938505633000u64, "merkleProof": [ - "h:0000000000000000000000000000000000000000000000000000000000000000", - "h:0000000000000000000000000000000000000000000000000000000000000000", - "h:0000000000000000000000000000000000000000000000000000000000000000", - "h:0000000000000000000000000000000000000000000000000000000000000000", - "h:0000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000", + "0000000000000000000000000000000000000000000000000000000000000000" ], "v2FileContract": { "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", "proofHeight": 10, "expirationHeight": 20, "renterOutput": { "value": "10000000000000000000000000000", - "address": "addr:c899f7795bb20c94e57c764f06699e09e6ad071ad95539eef4fb505e79ab22e8be4d64067ccc" + "address": "c899f7795bb20c94e57c764f06699e09e6ad071ad95539eef4fb505e79ab22e8be4d64067ccc" }, "hostOutput": { "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" }, "missedHostValue": "0", "totalCollateral": "0", "renterPublicKey": "ed25519:65ea9701c409d4457a830b6fe7a2513d6f466ab4e424b3941de9f34a4a2d6170", "hostPublicKey": "ed25519:65ea9701c409d4457a830b6fe7a2513d6f466ab4e424b3941de9f34a4a2d6170", "revisionNumber": 0, - "renterSignature": "sig:bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f", - "hostSignature": "sig:bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f" + "renterSignature": "bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f", + "hostSignature": "bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f" } }, "type": "finalization", "resolution": { "filesize": 0, - "fileMerkleRoot": "h:0000000000000000000000000000000000000000000000000000000000000000", + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", "proofHeight": 10, "expirationHeight": 20, "renterOutput": { "value": "10000000000000000000000000000", - "address": "addr:c899f7795bb20c94e57c764f06699e09e6ad071ad95539eef4fb505e79ab22e8be4d64067ccc" + "address": "c899f7795bb20c94e57c764f06699e09e6ad071ad95539eef4fb505e79ab22e8be4d64067ccc" }, "hostOutput": { "value": "0", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" }, "missedHostValue": "0", "totalCollateral": "0", "renterPublicKey": "ed25519:65ea9701c409d4457a830b6fe7a2513d6f466ab4e424b3941de9f34a4a2d6170", "hostPublicKey": "ed25519:65ea9701c409d4457a830b6fe7a2513d6f466ab4e424b3941de9f34a4a2d6170", "revisionNumber": 18446744073709551615u64, - "renterSignature": "sig:bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f", - "hostSignature": "sig:bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f" + "renterSignature": "bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f", + "hostSignature": "bd1794b9266fa0de94aea0f0ffb6550efd7e8874133963022413c8ccfe1a0e31c14690d3a5bbd343b160ed59219bd67f79103c45aee07f519d72b5ab4319440f" } } } @@ -446,91 +76,456 @@ fn test_serde_event_v2_contract_resolution_finalization() { // FIXME this should deserialize from a JSON object generated from walletd and recalcuate the txid to check encoding/serde } -#[test] -fn test_serde_event_v2_transaction() { - let j = json!( - { - "id": "h:5900e475aace932c94bcc94cf296596ccff1d77d9aba52a079e9f429605671cd", - "index": { - "height": 203, - "id": "bid:bd04c08bb96203c7f24adf2d405cb1069c7da8573573011379a986be62fc2a29" - }, - "timestamp": "2024-07-18T19:04:16Z", - "maturityHeight": 203, - "type": "v2Transaction", - "data": { - "siacoinInputs": [ +#[cfg(test)] +mod test { + macro_rules! test_serde { + ($type:ty, $json_value:expr) => {{ + let json_str = $json_value.to_string(); + let value: $type = serde_json::from_str(&json_str).unwrap(); + let serialized = serde_json::to_string(&value).unwrap(); + let serialized_json_value: serde_json::Value = serde_json::from_str(&serialized).unwrap(); + assert_eq!($json_value, serialized_json_value); + }}; + } + // Ensure the original value matches the value after round-trip (serialize -> deserialize -> serialize) + use crate::types::{Address, Event, Hash256, SiacoinElement, SiacoinOutput, StateElement, UnlockKey, V2Transaction}; + + cross_target_tests! { + fn test_serde_address() { + test_serde!( + Address, + json!("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f") + ); + } + + fn test_serde_unlock_key() { + test_serde!( + UnlockKey, + json!("ed25519:0102030000000000000000000000000000000000000000000000000000000000") + ); + } + + fn test_serde_sia_hash() { + test_serde!( + Hash256, + json!("dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1") + ); + } + + fn test_serde_siacoin_output() { + let j = json!({ + "value": "300000000000000000000000000000", + "address": "591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f" + }); + test_serde!(SiacoinOutput, j); + } + + // check that merkleProof field serde is the same when it is null, missing or empty + fn test_serde_state_element() { + let j = json!({ + "id": "dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1", + "leafIndex": 21, + "merkleProof": null + }); + let null_proof = serde_json::from_value::(j).unwrap(); + + let j = json!({ + "id": "dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1", + "leafIndex": 21, + "merkleProof": [] + }); + let empty_proof = serde_json::from_value::(j).unwrap(); + + let j = json!({ + "id": "dc07e5bf84fbda867a7ed7ca80c6d1d81db05cef16ff38f6ba80b6bf01e1ddb1", + "leafIndex": 21 + }); + let missing_proof = serde_json::from_value::(j).unwrap(); + + assert_eq!(null_proof, empty_proof); + assert_eq!(null_proof, missing_proof); + } + + fn test_serde_siacoin_element() { + let j = json!( { + "id": "0102030000000000000000000000000000000000000000000000000000000000", + "stateElement": { + "leafIndex": 1, + "merkleProof": [ + "0405060000000000000000000000000000000000000000000000000000000000", + "0708090000000000000000000000000000000000000000000000000000000000" + ] + }, + "siacoinOutput": { + "value": "1", + "address": "72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a" + }, + "maturityHeight": 0 + } + ); + serde_json::from_value::(j).unwrap(); + } + + fn test_serde_siacoin_element_missing_merkle_proof() { + let json_str = r#" { - "parent": { - "id": "h:78d58090bcdeaccf22abf99b6e0de25273e9eb82210359a16cefbd743a85fd50", - "leafIndex": 421, - "merkleProof": [ - "h:f26accb7c256e867a9ed62671ebe6c3eb34d085e5266f67073af2daa549f980d", - "h:d39e139147168c70da11c3f6db4fa54d35914ef67ba5654a75107da9c099ddda", - "h:f447a5360e1a7c4cab3062dd1699f56ea642b4f6cc6464fdfca0d1aa15fa436c" - ], + "id": "16406893374eb18eeea95e8c0d6b6c325275ecb99cf2fec7a6708b0b8def75bd", + "stateElement": { + "leafIndex": 391 + }, "siacoinOutput": { - "value": "256394172736732570239334030000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" }, - "maturityHeight": 0 - }, - "satisfiedPolicy": { - "policy": { - "type": "uc", - "policy": { - "timelock": 0, - "publicKeys": [ - "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc" - ], - "signaturesRequired": 1 - } + "maturityHeight": 334 + }"#; + serde_json::from_str::(json_str).unwrap(); + } + + fn test_serde_event_v2_contract_resolution_storage_proof() { + let j = r#" + { + "id": "16406893374eb18eeea95e8c0d6b6c325275ecb99cf2fec7a6708b0b8def75bd", + "index": { + "height": 190, + "id": "22693d8885ad7b5e2abf22fe838fd6ae9856142f898607ffd2ddb8dd3d7ca67b" + }, + "confirmations": 17, + "type": "v2ContractResolution", + "data": { + "resolution": { + "parent": { + "id": "e5adb3e8e49d9bd29e54966e809cc652f08dfca2183fad00f3da29df83f65091", + "stateElement": { + "leafIndex": 351 + }, + "v2FileContract": { + "capacity": 0, + "filesize": 0, + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "proofHeight": 179, + "expirationHeight": 189, + "renterOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "hostOutput": { + "value": "0", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + "missedHostValue": "0", + "totalCollateral": "0", + "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "revisionNumber": 0, + "renterSignature": "88b5f53a69759264f60cb227e7d4fdb25ee185f9c9b9bcf4f6e94c413ace76e1d1dcf72d509670e3d4e89d3dccb326d9c74411909e0a2e0e7e1e18bf3acb6c0c", + "hostSignature": "88b5f53a69759264f60cb227e7d4fdb25ee185f9c9b9bcf4f6e94c413ace76e1d1dcf72d509670e3d4e89d3dccb326d9c74411909e0a2e0e7e1e18bf3acb6c0c" + } + }, + "type": "expiration", + "resolution": {} + }, + "siacoinElement": { + "id": "16406893374eb18eeea95e8c0d6b6c325275ecb99cf2fec7a6708b0b8def75bd", + "stateElement": { + "leafIndex": 391 + }, + "siacoinOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "maturityHeight": 334 }, - "signatures": [ - "sig:c432fea5f147205e49235ddbd75c232fd8e9c7526b2b1575f70653ae2b3c0d0338c7fe710be338482060cf6ef2dea5e2319252fc28deaf70c77a2be60a533400" + "missed": true + }, + "maturityHeight": 334, + "timestamp": "2024-11-15T19:41:06Z", + "relevant": [ + "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" ] - } } - ], - "siacoinOutputs": [ - { - "value": "10400000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, + "#; + + let _event = serde_json::from_str::(j).unwrap(); + } + + fn test_serde_event_v2_contract_resolution_renewal() { + let json_str = r#" { - "value": "245994172736732570239334030000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + "id": "5d565129957e1493902123f6d58775593a53ccbff1e30342defaf563853c30b4", + "index": { + "height": 203, + "id": "f5674e39f155f1d5afe6cd2315a8b6c89843c1fbc19b13d8c6b3636b20cb537c" + }, + "confirmations": 4, + "type": "v2ContractResolution", + "data": { + "resolution": { + "parent": { + "id": "d219a1300698e798338df61f6f816f593672f71bce274d5130e1ba95e1d63814", + "stateElement": { + "leafIndex": 423 + }, + "v2FileContract": { + "capacity": 0, + "filesize": 0, + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "proofHeight": 211, + "expirationHeight": 221, + "renterOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "hostOutput": { + "value": "0", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + "missedHostValue": "0", + "totalCollateral": "0", + "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "revisionNumber": 0, + "renterSignature": "3aaf47eb60d992bced4818291eb1b7773e20a731df48857474715602db31a12fddf29170337803f6dd1ce95e1e2043714c2b3bcb99925ea37ad2cf4880922c02", + "hostSignature": "3aaf47eb60d992bced4818291eb1b7773e20a731df48857474715602db31a12fddf29170337803f6dd1ce95e1e2043714c2b3bcb99925ea37ad2cf4880922c02" + } + }, + "type": "renewal", + "resolution": { + "finalRevision": { + "capacity": 0, + "filesize": 0, + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "proofHeight": 211, + "expirationHeight": 221, + "renterOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "hostOutput": { + "value": "0", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + "missedHostValue": "0", + "totalCollateral": "0", + "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "revisionNumber": 18446744073709551615, + "renterSignature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "hostSignature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "newContract": { + "capacity": 0, + "filesize": 0, + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "proofHeight": 221, + "expirationHeight": 231, + "renterOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "hostOutput": { + "value": "0", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + "missedHostValue": "0", + "totalCollateral": "0", + "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "revisionNumber": 0, + "renterSignature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "hostSignature": "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" + }, + "renterRollover": "0", + "hostRollover": "0", + "renterSignature": "f43d4b5d859931669f7db479af2e3064ed40cfa333b625120521f20f9cf9867b2c38d95cc2ee6f2d75e438ad6c25ce9f7b436e6ccbe70237f0b66e2d1dae720f", + "hostSignature": "f43d4b5d859931669f7db479af2e3064ed40cfa333b625120521f20f9cf9867b2c38d95cc2ee6f2d75e438ad6c25ce9f7b436e6ccbe70237f0b66e2d1dae720f" + } + }, + "siacoinElement": { + "id": "5d565129957e1493902123f6d58775593a53ccbff1e30342defaf563853c30b4", + "stateElement": { + "leafIndex": 427 + }, + "siacoinOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "maturityHeight": 347 + }, + "missed": false + }, + "maturityHeight": 347, + "timestamp": "2024-11-15T19:41:06Z", + "relevant": [ + "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + ] } - ], - "minerFee": "0" - } - } - ); - test_serde!(Event, j); -} + "#; -#[test] -fn test_v2_transaction_serde_basic_send() { - let j = json!( - { - "siacoinInputs": [ + let _event = serde_json::from_str::(json_str).unwrap(); + } + + fn test_serde_event_v2_contract_resolution_expiration() { + let j = json!( + { + "id": "16406893374eb18eeea95e8c0d6b6c325275ecb99cf2fec7a6708b0b8def75bd", + "index": { + "height": 190, + "id": "22693d8885ad7b5e2abf22fe838fd6ae9856142f898607ffd2ddb8dd3d7ca67b" + }, + "confirmations": 17, + "type": "v2ContractResolution", + "data": { + "resolution": { + "parent": { + "id": "e5adb3e8e49d9bd29e54966e809cc652f08dfca2183fad00f3da29df83f65091", + "stateElement": { + "leafIndex": 351 + }, + "v2FileContract": { + "capacity": 0, + "filesize": 0, + "fileMerkleRoot": "0000000000000000000000000000000000000000000000000000000000000000", + "proofHeight": 179, + "expirationHeight": 189, + "renterOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "hostOutput": { + "value": "0", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + "missedHostValue": "0", + "totalCollateral": "0", + "renterPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "hostPublicKey": "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc", + "revisionNumber": 0, + "renterSignature": "88b5f53a69759264f60cb227e7d4fdb25ee185f9c9b9bcf4f6e94c413ace76e1d1dcf72d509670e3d4e89d3dccb326d9c74411909e0a2e0e7e1e18bf3acb6c0c", + "hostSignature": "88b5f53a69759264f60cb227e7d4fdb25ee185f9c9b9bcf4f6e94c413ace76e1d1dcf72d509670e3d4e89d3dccb326d9c74411909e0a2e0e7e1e18bf3acb6c0c" + } + }, + "type": "expiration", + "resolution": {} + }, + "siacoinElement": { + "id": "16406893374eb18eeea95e8c0d6b6c325275ecb99cf2fec7a6708b0b8def75bd", + "stateElement": { + "leafIndex": 391 + }, + "siacoinOutput": { + "value": "10000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "maturityHeight": 334 + }, + "missed": true + }, + "maturityHeight": 334, + "timestamp": "2024-11-15T19:41:06Z", + "relevant": [ + "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + ] + } + ); + + let _event = serde_json::from_value::(j).unwrap(); + } + + fn test_serde_event_v2_transaction() { + let j = json!( + { + "id": "3203cda6aa67faca699fc9fd1e75d46cfa0ee080ddaf5485fad9bc42282a04b9", + "index": { + "height": 169, + "id": "d4b10532623709b888fb6f2a2c6d865dc3d21f4d768f83c7f43814c29acf5b2b" + }, + "confirmations": 38, + "type": "v2Transaction", + "data": { + "siacoinInputs": [ + { + "parent": { + "id": "a97dab89d5ba12e2c3ea852021e3be6b4472e55fc5408497d38fbfd05fd98362", + "stateElement": { + "leafIndex": 302, + "merkleProof": [ + "98c9f7eee6105d146b9374c9d7e28d8cec7ffcf95e71a33630510b90ef3b4fbb", + "e00568ef169225bb1e049e8c6435809396bee2da99595f870d834d3deb436df9", + "cd725e13fac773e43b5492ea5ffae6003ff7e3cacc4505689080fd657558a983", + "5dc34e64ffe5fdc537bc1021fbb9469e970b5a362a93acd2025215a894d1ee7f", + "9e033c9bf3664f59c573336d0d6dbf8c8a20bdf73d0ed2ce63b8cf835836ee8a", + "98fd662dfa09c67642a468d5f2d7da6a8a13a3aac74ef24a42461ec61a0f498d" + ] + }, + "siacoinOutput": { + "value": "288594172736732570239334030000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "maturityHeight": 0 + }, + "satisfiedPolicy": { + "policy": { + "type": "uc", + "policy": { + "timelock": 0, + "publicKeys": [ + "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc" + ], + "signaturesRequired": 1 + } + }, + "signatures": [ + "53752750b684cab9c8c1d091f53f4dea9d1b3ab72d12d97ff73088594aa9b62198a1ed5b2fb33328075bb10f5f4a8ff14488787fc7238a174d2bc62bc96f9d07" + ] + } + } + ], + "siacoinOutputs": [ + { + "value": "1000000000000000000000000000", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + { + "value": "287594172736732570239334030000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + } + ], + "minerFee": "0" + }, + "maturityHeight": 169, + "timestamp": "2024-11-15T19:41:06Z", + "relevant": [ + "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + ] + } + ); + test_serde!(Event, j); + } + + fn test_v2_transaction_serde_basic_send() { + let j = json!( + { + "siacoinInputs": [ { "parent": { - "id": "h:f59e395dc5cbe3217ee80eff60585ffc9802e7ca580d55297782d4a9b4e08589", - "leafIndex": 3, - "merkleProof": [ - "h:ab0e1726444c50e2c0f7325eb65e5bd262a97aad2647d2816c39d97958d9588a", - "h:467e2be4d8482eca1f99440b6efd531ab556d10a8371a98a05b00cb284620cf0", - "h:64d5766fce1ff78a13a4a4744795ad49a8f8d187c01f9f46544810049643a74a", - "h:31d5151875152bc25d1df18ca6bbda1bef5b351e8d53c277791ecf416fcbb8a8", - "h:12a92a1ba87c7b38f3c4e264c399abfa28fb46274cfa429605a6409bd6d0a779", - "h:eda1d58a9282dbf6c3f1beb4d6c7bdc036d14a1cfee8ab1e94fabefa9bd63865", - "h:e03dee6e27220386c906f19fec711647353a5f6d76633a191cbc2f6dce239e89", - "h:e70fcf0129c500f7afb49f4f2bb82950462e952b7cdebb2ad0aa1561dc6ea8eb" - ], + "id": "f59e395dc5cbe3217ee80eff60585ffc9802e7ca580d55297782d4a9b4e08589", + "stateElement": { + "leafIndex": 3, + "merkleProof": [ + "ab0e1726444c50e2c0f7325eb65e5bd262a97aad2647d2816c39d97958d9588a", + "467e2be4d8482eca1f99440b6efd531ab556d10a8371a98a05b00cb284620cf0", + "64d5766fce1ff78a13a4a4744795ad49a8f8d187c01f9f46544810049643a74a", + "31d5151875152bc25d1df18ca6bbda1bef5b351e8d53c277791ecf416fcbb8a8", + "12a92a1ba87c7b38f3c4e264c399abfa28fb46274cfa429605a6409bd6d0a779", + "eda1d58a9282dbf6c3f1beb4d6c7bdc036d14a1cfee8ab1e94fabefa9bd63865", + "e03dee6e27220386c906f19fec711647353a5f6d76633a191cbc2f6dce239e89", + "e70fcf0129c500f7afb49f4f2bb82950462e952b7cdebb2ad0aa1561dc6ea8eb" + ], + }, "siacoinOutput": { "value": "300000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" }, "maturityHeight": 145 }, @@ -540,33 +535,94 @@ fn test_v2_transaction_serde_basic_send() { "policy": { "timelock": 0, "publicKeys": [ - "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc" + "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc" ], "signaturesRequired": 1 } }, "signatures": [ - "sig:f0a29ba576eb0dbc3438877ac1d3a6da4f3c4cbafd9030709c8a83c2fffa64f4dd080d37444261f023af3bd7a10a9597c33616267d5371bf2c0ade5e25e61903" + "f0a29ba576eb0dbc3438877ac1d3a6da4f3c4cbafd9030709c8a83c2fffa64f4dd080d37444261f023af3bd7a10a9597c33616267d5371bf2c0ade5e25e61903" ] } } - ], - "siacoinOutputs": [ + ], + "siacoinOutputs": [ { "value": "1000000000000000000000000000", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" }, { "value": "299000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + } + ], + "minerFee": "0" + } + ); + let tx = serde_json::from_value::(j).unwrap(); + + let j2 = serde_json::to_value(&tx).unwrap().to_string(); + let tx2 = serde_json::from_str::(&j2).unwrap(); + assert_eq!(tx, tx2); + } + + fn test_v2_transaction_serde_with_arbitrary_data() { + let j = r#" + { + "siacoinInputs": [ + { + "parent": { + "id": "3d91fd82c8b197f7c63a803a5b6b15feeeb55f03a861036832137a2cd43175c5", + "stateElement": { + "leafIndex": 445, + "merkleProof": [ + "550179f319d4702c067891cc412ac3adfdbaab268285ad9e94ec8d523bda769a", + "7b0a8ab5a6fe6466ef509fc645583d128a6a20b5cff710dc7092647c9833240e", + "02f359ce3b8c053e336afbc53c1b5e11bee75b73c40d809802ab0c454b8bf556", + "d3ec699dff3207f0dc458b715b01e60a51561820ad3260a0f5857fb24d238137", + "16a007608da1f582c2901eb9b49e3a00456ce4f1c3290ad0125e59be83de19bd", + "94cdee3fd437d058d14fa43faedb8a2c6d260c8da968cee5f4293e0296f9ddfc", + "549e313b749f317300a7bb7b92545c9fb929ba5b9466f56c3151f0c180a9d7f6", + "cd0b57da0c99b37bba96270250c0c2e883655b41443d88e0e6780b420e8c5616", + "d709b55266c068cac2ca645c39a1ecce14fc30677d5855074a290e9e435b39a5" + ] + }, + "siacoinOutput": { + "value": "300000000000000000000000000000", + "address": "8b7201e203c8b4a58e2f68d29e6cb8973706f1578a074f5b29b1ae1f4136da85ae7f7667a714" + }, + "maturityHeight": 229 + }, + "satisfiedPolicy": { + "policy": { + "type": "pk", + "policy": "ed25519:32f8eb30eab7b9c8ec8be44f37ac0a2b0f7fa1f7c9540abb6e21267b1995024f" + }, + "signatures": [ + "4639d5738969c4870307d6bae35e16a1e38417a66e041aea639cdfb97ae250c5c583967e6367c5eb976c3e036792a6e0a2bcb053bfef86789a35b468bfc01b0c" + ] + } } ], - "minerFee": "0" - } - ); - let tx = serde_json::from_value::(j).unwrap(); + "siacoinOutputs": [ + { + "value": "12870012870012870012", + "address": "0125788b383a1dd122cd511386dd2668c62e54610cc743307d7a8ba17161a175f4b03c40b656" + }, + { + "value": "299999999977129987129987129988", + "address": "8b7201e203c8b4a58e2f68d29e6cb8973706f1578a074f5b29b1ae1f4136da85ae7f7667a714" + } + ], + "arbitraryData": "hss62dS2RraC7PbMFY6ETQ==", + "minerFee": "10000000000000000000" + }"#; + + let tx = serde_json::from_str::(j).unwrap(); + let j2 = serde_json::to_value(&tx).unwrap().to_string(); + let tx2 = serde_json::from_str::(&j2).unwrap(); + assert_eq!(tx, tx2); + } - let j2 = serde_json::to_value(&tx).unwrap().to_string(); - let tx2 = serde_json::from_str::(&j2).unwrap(); - assert_eq!(tx, tx2); + } } diff --git a/src/tests/spend_policy.rs b/src/tests/spend_policy.rs index 43cefd2..452d05c 100644 --- a/src/tests/spend_policy.rs +++ b/src/tests/spend_policy.rs @@ -1,165 +1,161 @@ -use crate::spend_policy::{spend_policy_atomic_swap_success, SpendPolicy, SpendPolicyHelper, UnlockCondition, UnlockKey}; -use crate::types::{Address, H256}; -use crate::PublicKey; -use std::str::FromStr; - -#[test] -fn test_serde_spend_policy_above() { - let j = json!( - { - "type": "above", - "policy": 100 - } - ); - - let spend_policy_deser = serde_json::from_value::(j).unwrap().into(); - let spend_policy = SpendPolicy::Above(100); - - assert_eq!(spend_policy, spend_policy_deser); -} +#[cfg(test)] +mod test { + use crate::types::{Address, Hash256, PublicKey, SpendPolicy, UnlockCondition, UnlockKey}; + use std::str::FromStr; + + cross_target_tests! { + fn test_serde_spend_policy_above() { + let j = json!( + { + "type": "above", + "policy": 100 + } + ); -#[test] -fn test_serde_spend_policy_after() { - let j = json!( - { - "type": "after", - "policy": 200 - } - ); + let spend_policy_deser = serde_json::from_value::(j).unwrap().into(); + let spend_policy = SpendPolicy::Above(100); - let spend_policy_deser = serde_json::from_value::(j).unwrap().into(); - let spend_policy = SpendPolicy::After(200); + assert_eq!(spend_policy, spend_policy_deser); + } - assert_eq!(spend_policy, spend_policy_deser); -} + fn test_serde_spend_policy_after() { + let j = json!( + { + "type": "after", + "policy": 200 + } + ); -#[test] -fn test_serde_spend_policy_public_key() { - let j = json!( - { - "type": "pk", - "policy": "ed25519:0102030000000000000000000000000000000000000000000000000000000000" - } - ); - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); - let spend_policy = SpendPolicy::PublicKey(pubkey); - - assert_eq!(spend_policy, spend_policy_deser); -} + let spend_policy_deser = serde_json::from_value::(j).unwrap().into(); + let spend_policy = SpendPolicy::After(200); -#[test] -fn test_serde_spend_policy_hash() { - let j = json!( - { - "type": "h", - "policy": "h:0102030000000000000000000000000000000000000000000000000000000000" - } - ); - let hash = H256::from("0102030000000000000000000000000000000000000000000000000000000000"); - let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); - let spend_policy = SpendPolicy::Hash(hash); + assert_eq!(spend_policy, spend_policy_deser); + } - assert_eq!(spend_policy, spend_policy_deser); -} + fn test_serde_spend_policy_public_key() { + let j = json!( + { + "type": "pk", + "policy": "ed25519:0102030000000000000000000000000000000000000000000000000000000000" + } + ); + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); + let spend_policy = SpendPolicy::PublicKey(pubkey); + + assert_eq!(spend_policy, spend_policy_deser); + } -#[test] -fn test_serde_spend_policy_opaque() { - let j = json!( - { - "type": "opaque", - "policy": "addr:f72e84ee9e344e424a6764068ffd7fdce4b4e50609892c6801bc1ead79d3ae0d71791b277a3a" - } - ); - let address = - Address::from_str("addr:f72e84ee9e344e424a6764068ffd7fdce4b4e50609892c6801bc1ead79d3ae0d71791b277a3a").unwrap(); - let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); - let spend_policy = SpendPolicy::Opaque(address); + fn test_serde_spend_policy_hash() { + let j = json!( + { + "type": "h", + "policy": "0102030000000000000000000000000000000000000000000000000000000000" + } + ); + let hash = Hash256::from_str("0102030000000000000000000000000000000000000000000000000000000000").unwrap(); + let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); + let spend_policy = SpendPolicy::Hash(hash); - assert_eq!(spend_policy, spend_policy_deser); -} + assert_eq!(spend_policy, spend_policy_deser); + } -#[test] -fn test_serde_spend_policy_threshold() { - let alice_pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let bob_pubkey = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - - let secret_hash = H256::from("0100000000000000000000000000000000000000000000000000000000000000"); - let spend_policy = spend_policy_atomic_swap_success(alice_pubkey, bob_pubkey, 77777777, secret_hash); - - let j = json!( - { - "type": "thresh", - "policy": { - "n": 1, - "of": [ - { - "type": "thresh", - "policy": { - "n": 2, - "of": [ - { - "type": "pk", - "policy": "ed25519:0102030000000000000000000000000000000000000000000000000000000000" - }, - { - "type": "h", - "policy": "h:0100000000000000000000000000000000000000000000000000000000000000" - } - ] - } - }, - { - "type": "opaque", - "policy": "addr:f72e84ee9e344e424a6764068ffd7fdce4b4e50609892c6801bc1ead79d3ae0d71791b277a3a" - } - ] + fn test_serde_spend_policy_opaque() { + let j = json!( + { + "type": "opaque", + "policy": "f72e84ee9e344e424a6764068ffd7fdce4b4e50609892c6801bc1ead79d3ae0d71791b277a3a" } + ); + let address = + Address::from_str("f72e84ee9e344e424a6764068ffd7fdce4b4e50609892c6801bc1ead79d3ae0d71791b277a3a").unwrap(); + let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); + let spend_policy = SpendPolicy::Opaque(address); + + assert_eq!(spend_policy, spend_policy_deser); } - ); - let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); + fn test_serde_spend_policy_threshold() { + let alice_pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let bob_pubkey = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); + + let secret_hash = Hash256::from_str("0100000000000000000000000000000000000000000000000000000000000000").unwrap(); + let spend_policy = SpendPolicy::atomic_swap_success(&alice_pubkey, &bob_pubkey, 77777777, &secret_hash); + + let j = json!( + { + "type": "thresh", + "policy": { + "n": 1, + "of": [ + { + "type": "thresh", + "policy": { + "n": 2, + "of": [ + { + "type": "pk", + "policy": "ed25519:0102030000000000000000000000000000000000000000000000000000000000" + }, + { + "type": "h", + "policy": "0100000000000000000000000000000000000000000000000000000000000000" + } + ] + } + }, + { + "type": "opaque", + "policy": "f72e84ee9e344e424a6764068ffd7fdce4b4e50609892c6801bc1ead79d3ae0d71791b277a3a" + } + ] + } + } + ); - assert_eq!(spend_policy, spend_policy_deser); -} + let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); -#[test] -fn test_serde_spend_policy_unlock_conditions_standard() { - let j = json!( - { - "type": "uc", - "policy": { - "timelock": 0, - "publicKeys": [ - "ed25519:0102030000000000000000000000000000000000000000000000000000000000" - ], - "signaturesRequired": 1 - } + assert_eq!(spend_policy, spend_policy_deser); } - ); - let public_key = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + fn test_serde_spend_policy_unlock_conditions_standard() { + let j = json!( + { + "type": "uc", + "policy": { + "timelock": 0, + "publicKeys": [ + "ed25519:0102030000000000000000000000000000000000000000000000000000000000" + ], + "signaturesRequired": 1 + } + } + ); + + let public_key = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); - let uc = UnlockCondition { - unlock_keys: vec![UnlockKey::Ed25519(public_key)], - timelock: 0, - signatures_required: 1, - }; + let uc = UnlockCondition { + unlock_keys: vec![UnlockKey::Ed25519(public_key)], + timelock: 0, + signatures_required: 1, + }; - let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); - let spend_policy = SpendPolicy::UnlockConditions(uc); + let spend_policy_deser: SpendPolicy = serde_json::from_value::(j).unwrap().into(); + let spend_policy = SpendPolicy::UnlockConditions(uc); - assert_eq!(spend_policy, spend_policy_deser); + assert_eq!(spend_policy, spend_policy_deser); + } + } } diff --git a/src/tests/transaction.rs b/src/tests/transaction.rs index d10a062..3de3c25 100644 --- a/src/tests/transaction.rs +++ b/src/tests/transaction.rs @@ -1,738 +1,751 @@ -use crate::encoding::Encoder; -use crate::spend_policy::{spend_policy_atomic_swap_refund, spend_policy_atomic_swap_success, SpendPolicy, - UnlockCondition}; -use crate::transaction::{Attestation, Currency, CurrencyVersion, FileContractRevisionV2, SatisfiedPolicy, - SiacoinElement, SiacoinInputV1, SiacoinInputV2, SiacoinOutput, SiacoinOutputVersion, - StateElement, V2FileContract, V2FileContractElement, V2Transaction}; -use crate::types::{v1_standard_address_from_pubkey, Address, H256}; -use crate::{PublicKey, Signature}; -use std::str::FromStr; - -#[test] -fn test_siacoin_input_encode() { - let public_key = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let unlock_condition = UnlockCondition::new(vec![public_key], 0, 1); - - let vin = SiacoinInputV1 { - parent_id: H256::from("0405060000000000000000000000000000000000000000000000000000000000"), - unlock_condition, - }; - - let hash = Encoder::encode_and_hash(&vin); - let expected = H256::from("1d4b77aaa82c71ca68843210679b380f9638f8bec7addf0af16a6536dd54d6b4"); - assert_eq!(hash, expected); -} +#[cfg(test)] +mod test { + use crate::encoding::Encoder; + use crate::types::{Address, Attestation, Currency, CurrencyVersion, FileContractRevisionV2, Hash256, Keypair, + Preimage, PublicKey, SatisfiedPolicy, SiacoinElement, SiacoinInputV1, SiacoinInputV2, + SiacoinOutput, SiacoinOutputId, SiacoinOutputVersion, Signature, SpendPolicy, StateElement, + UnlockCondition, V2FileContract, V2FileContractElement, V2Transaction}; + use std::convert::TryFrom; + use std::str::FromStr; + + cross_target_tests! { + fn test_siacoin_input_encode() { + let public_key = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let unlock_condition = UnlockCondition::new(vec![public_key], 0, 1); + + let vin = SiacoinInputV1 { + parent_id: Hash256::from_str("0405060000000000000000000000000000000000000000000000000000000000") + .unwrap() + .into(), + unlock_condition, + }; + + let hash = Encoder::encode_and_hash(&vin); + let expected = Hash256::from_str("1d4b77aaa82c71ca68843210679b380f9638f8bec7addf0af16a6536dd54d6b4").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_currency_encode_v1() { - let currency: Currency = 1.into(); + fn test_siacoin_currency_encode_v1() { + let currency: Currency = 1u64.into(); - let hash = Encoder::encode_and_hash(&CurrencyVersion::V1(¤cy)); - let expected = H256::from("a1cc3a97fc1ebfa23b0b128b153a29ad9f918585d1d8a32354f547d8451b7826"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&CurrencyVersion::V1(¤cy)); + let expected = Hash256::from_str("a1cc3a97fc1ebfa23b0b128b153a29ad9f918585d1d8a32354f547d8451b7826").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_currency_encode_v2() { - let currency: Currency = 1.into(); + fn test_siacoin_currency_encode_v2() { + let currency: Currency = 1u64.into(); - let hash = Encoder::encode_and_hash(&CurrencyVersion::V2(¤cy)); - let expected = H256::from("a3865e5e284e12e0ea418e73127db5d1092bfb98ed372ca9a664504816375e1d"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&CurrencyVersion::V2(¤cy)); + let expected = Hash256::from_str("a3865e5e284e12e0ea418e73127db5d1092bfb98ed372ca9a664504816375e1d").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_currency_encode_v1_max() { - let currency = Currency(u128::MAX); + fn test_siacoin_currency_encode_v1_max() { + let currency = Currency(u128::MAX); - let hash = Encoder::encode_and_hash(&CurrencyVersion::V1(¤cy)); - let expected = H256::from("4b9ed7269cb15f71ddf7238172a593a8e7ffe68b12c1bf73d67ac8eec44355bb"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&CurrencyVersion::V1(¤cy)); + let expected = Hash256::from_str("4b9ed7269cb15f71ddf7238172a593a8e7ffe68b12c1bf73d67ac8eec44355bb").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_currency_encode_v2_max() { - let currency = Currency(u128::MAX); + fn test_siacoin_currency_encode_v2_max() { + let currency = Currency(u128::MAX); - let hash = Encoder::encode_and_hash(&CurrencyVersion::V2(¤cy)); - let expected = H256::from("681467b3337425fd38fa3983531ca1a6214de9264eebabdf9c9bc5d157d202b4"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&CurrencyVersion::V2(¤cy)); + let expected = Hash256::from_str("681467b3337425fd38fa3983531ca1a6214de9264eebabdf9c9bc5d157d202b4").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_output_encode_v1() { - let vout = SiacoinOutput { - value: 1.into(), - address: Address::from_str("addr:72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a") - .unwrap(), - }; - - let hash = Encoder::encode_and_hash(&SiacoinOutputVersion::V1(&vout)); - let expected = H256::from("3253c57e76600721f2bdf03497a71ed47c09981e22ef49aed92e40da1ea91b28"); - assert_eq!(hash, expected); -} + fn test_siacoin_output_encode_v1() { + let vout = SiacoinOutput { + value: 1u64.into(), + address: Address::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a") + .unwrap(), + }; -#[test] -fn test_siacoin_output_encode_v2() { - let vout = SiacoinOutput { - value: 1.into(), - address: Address::from_str("addr:72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a") - .unwrap(), - }; - - let hash = Encoder::encode_and_hash(&SiacoinOutputVersion::V2(&vout)); - let expected = H256::from("c278eceae42f594f5f4ca52c8a84b749146d08af214cc959ed2aaaa916eaafd3"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&SiacoinOutputVersion::V1(&vout)); + let expected = Hash256::from_str("3253c57e76600721f2bdf03497a71ed47c09981e22ef49aed92e40da1ea91b28").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_element_encode() { - let state_element = StateElement { - id: H256::from("0102030000000000000000000000000000000000000000000000000000000000"), - leaf_index: 1, - merkle_proof: Some(vec![ - H256::from("0405060000000000000000000000000000000000000000000000000000000000"), - H256::from("0708090000000000000000000000000000000000000000000000000000000000"), - ]), - }; - let siacoin_element = SiacoinElement { - state_element, - siacoin_output: SiacoinOutput { - value: 1.into(), - address: Address::from_str( - "addr:72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a", + fn test_siacoin_output_encode_v2() { + let vout = SiacoinOutput { + value: 1u64.into(), + address: Address::from_str("72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a") + .unwrap(), + }; + + let hash = Encoder::encode_and_hash(&SiacoinOutputVersion::V2(&vout)); + let expected = Hash256::from_str("c278eceae42f594f5f4ca52c8a84b749146d08af214cc959ed2aaaa916eaafd3").unwrap(); + assert_eq!(hash, expected); + } + + fn test_siacoin_element_encode() { + let state_element = StateElement { + leaf_index: 1, + merkle_proof: vec![ + Hash256::from_str("0405060000000000000000000000000000000000000000000000000000000000").unwrap(), + Hash256::from_str("0708090000000000000000000000000000000000000000000000000000000000").unwrap(), + ], + }; + let siacoin_element = SiacoinElement { + id: Hash256::from_str("0102030000000000000000000000000000000000000000000000000000000000").unwrap().into(), + state_element, + siacoin_output: SiacoinOutput { + value: 1u64.into(), + address: Address::from_str( + "72b0762b382d4c251af5ae25b6777d908726d75962e5224f98d7f619bb39515dd64b9a56043a", + ) + .unwrap(), + }, + maturity_height: 0, + }; + + let hash = Encoder::encode_and_hash(&siacoin_element); + let expected = Hash256::from_str("4c46cbe535099409d2ea4255debda3fb62993595e305c78688ec4306f8464d7d").unwrap(); + assert_eq!(hash, expected); + } + + fn test_state_element_encode() { + let state_element = StateElement { + leaf_index: 1, + merkle_proof: vec![ + Hash256::from_str("0405060000000000000000000000000000000000000000000000000000000000").unwrap(), + Hash256::from_str("0708090000000000000000000000000000000000000000000000000000000000").unwrap(), + ], + }; + + let hash = Encoder::encode_and_hash(&state_element); + let expected = Hash256::from_str("70f868873fcb6196cd54bbb1e9e480188043426d3f7c9dc8fc5a7a536981cef1").unwrap(); + assert_eq!(hash, expected); + } + + fn test_state_element_encode_null_merkle_proof() { + let j = r#"{"leafIndex":1}"#; + let state_element = serde_json::from_str::(j).unwrap(); + + let hash = Encoder::encode_and_hash(&state_element); + let expected = Hash256::from_str("a3865e5e284e12e0ea418e73127db5d1092bfb98ed372ca9a664504816375e1d").unwrap(); + assert_eq!(hash, expected); + } + + fn test_state_element_encode_empty_merkle_proof() { + let j = r#"{"leafIndex":1,"merkleProof":[]}"#; + let state_element = serde_json::from_str::(j).unwrap(); + + let hash = Encoder::encode_and_hash(&state_element); + let expected = Hash256::from_str("a3865e5e284e12e0ea418e73127db5d1092bfb98ed372ca9a664504816375e1d").unwrap(); + assert_eq!(hash, expected); + } + + fn test_siacoin_input_encode_v1() { + let vin = SiacoinInputV1 { + parent_id: Hash256::default().into(), + unlock_condition: UnlockCondition::new(vec![], 0, 0), + }; + + let hash = Encoder::encode_and_hash(&vin); + let expected = Hash256::from_str("2f806f905436dc7c5079ad8062467266e225d8110a3c58d17628d609cb1c99d0").unwrap(); + assert_eq!(hash, expected); + } + + fn test_signature_encode() { + let signature = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap().as_ref()).unwrap(); + + let hash = Encoder::encode_and_hash(&signature); + let expected = Hash256::from_str("1e6952fe04eb626ae759a0090af2e701ba35ee6ad15233a2e947cb0f7ae9f7c7").unwrap(); + assert_eq!(hash, expected); + } + + fn test_satisfied_policy_encode_public_key() { + let public_key = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), ) - .unwrap(), - }, - maturity_height: 0, - }; - - let hash = Encoder::encode_and_hash(&siacoin_element); - let expected = H256::from("3c867a54b7b3de349c56585f25a4365f31d632c3e42561b615055c77464d889e"); - assert_eq!(hash, expected); -} + .unwrap(); -#[test] -fn test_state_element_encode() { - let state_element = StateElement { - id: H256::from("0102030000000000000000000000000000000000000000000000000000000000"), - leaf_index: 1, - merkle_proof: Some(vec![ - H256::from("0405060000000000000000000000000000000000000000000000000000000000"), - H256::from("0708090000000000000000000000000000000000000000000000000000000000"), - ]), - }; - - let hash = Encoder::encode_and_hash(&state_element); - let expected = H256::from("bf6d7b74fb1e15ec4e86332b628a450e387c45b54ea98e57a6da8c9af317e468"); - assert_eq!(hash, expected); -} + let policy = SpendPolicy::PublicKey(public_key); -#[test] -fn test_state_element_encode_null_merkle_proof() { - let state_element = StateElement { - id: H256::from("0102030000000000000000000000000000000000000000000000000000000000"), - leaf_index: 1, - merkle_proof: None, - }; - - let hash = Encoder::encode_and_hash(&state_element); - let expected = H256::from("d69bc48bc797aff93050447aff0a3f7c4d489705378c122cd123841fe7778a3e"); - assert_eq!(hash, expected); -} + let signature = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); -#[test] -fn test_siacoin_input_encode_v1() { - let vin = SiacoinInputV1 { - parent_id: H256::default(), - unlock_condition: UnlockCondition::new(vec![], 0, 0), - }; + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![signature], + preimages: vec![], + }; - let hash = Encoder::encode_and_hash(&vin); - let expected = H256::from("2f806f905436dc7c5079ad8062467266e225d8110a3c58d17628d609cb1c99d0"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("51832be911c7382502a2011cbddf1a9f689c4ca08c6a83ae3d021fb0dc781822").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_signature_encode() { - let signature = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + fn test_satisfied_policy_encode_hash_empty() { + let policy = SpendPolicy::Hash(Hash256::default()); - let hash = Encoder::encode_and_hash(&signature); - let expected = H256::from("1e6952fe04eb626ae759a0090af2e701ba35ee6ad15233a2e947cb0f7ae9f7c7"); - assert_eq!(hash, expected); -} + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![], + preimages: vec![Preimage::default()], + }; -#[test] -fn test_satisfied_policy_encode_public_key() { - let public_key = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("1e612d1ee36338b93a36bac0c52007a2d678cde0bd9b95c36a1f61166cf02b87").unwrap(); + assert_eq!(hash, expected); + } - let policy = SpendPolicy::PublicKey(public_key); + // Adding a signature to SatisfiedPolicy of PolicyHash should have no effect + fn test_satisfied_policy_encode_hash_frivulous_signature() { + let policy = SpendPolicy::Hash(Hash256::default()); - let signature = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + let mut preimage = [0u8; 32]; + preimage[..4].copy_from_slice(&[1, 2, 3, 4]); - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![signature], - preimages: vec![], - }; + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec!(Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap()), + preimages: vec!(preimage.into()), + }; - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("51832be911c7382502a2011cbddf1a9f689c4ca08c6a83ae3d021fb0dc781822"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("80f3caa4507615945bc839c8505546decd91e9642120f26938b2fc370fa61992").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_satisfied_policy_encode_hash_empty() { - let policy = SpendPolicy::Hash(H256::default()); + fn test_satisfied_policy_encode_hash() { + let policy = SpendPolicy::Hash(Hash256::default()); - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![], - preimages: vec![vec![]], // vec!(1u8, 2u8, 3u8, 4u8) - }; + let mut preimage = [0u8; 32]; + preimage[..4].copy_from_slice(&[1, 2, 3, 4]); + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![], + preimages: vec![preimage.into()], + }; - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("86b4b84950016d711732617d2501bd22e41614535f2705a65bd5b0e95c992a44"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("80f3caa4507615945bc839c8505546decd91e9642120f26938b2fc370fa61992").unwrap(); + assert_eq!(hash, expected); + } -// Adding a signature to SatisfiedPolicy of PolicyHash should have no effect -#[test] -fn test_satisfied_policy_encode_hash_frivulous_signature() { - let policy = SpendPolicy::Hash(H256::default()); - - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec!(Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap()), - preimages: vec!(vec!(1u8, 2u8, 3u8, 4u8)), - }; - - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("7424653d0ca3ffded9a029bebe75f9ae9c99b5f284e23e9d07c0b03456f724f9"); - assert_eq!(hash, expected); -} + fn test_satisfied_policy_encode_unlock_condition_standard() { + let pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); -#[test] -fn test_satisfied_policy_encode_hash() { - let policy = SpendPolicy::Hash(H256::default()); + let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![], - preimages: vec![vec![1u8, 2u8, 3u8, 4u8]], - }; + let policy = SpendPolicy::UnlockConditions(unlock_condition); - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("7424653d0ca3ffded9a029bebe75f9ae9c99b5f284e23e9d07c0b03456f724f9"); - assert_eq!(hash, expected); -} + let signature = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); -#[test] -fn test_satisfied_policy_encode_unlock_condition_standard() { - let pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![signature], + preimages: vec![], + }; - let unlock_condition = UnlockCondition::new(vec![pubkey], 0, 1); + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("c749f9ac53395ec557aed7e21d202f76a58e0de79222e5756b27077e9295931f").unwrap(); + assert_eq!(hash, expected); + } - let policy = SpendPolicy::UnlockConditions(unlock_condition); + fn test_satisfied_policy_encode_unlock_condition_complex() { + let pubkey0 = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey1 = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); + let pubkey2 = PublicKey::from_bytes( + &hex::decode("BE043906FD42297BC0A03CAA6E773EF27FC644261C692D090181E704BE4A88C3").unwrap(), + ) + .unwrap(); - let signature = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + let unlock_condition = UnlockCondition::new(vec![pubkey0, pubkey1, pubkey2], 77777777, 3); - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![signature], - preimages: vec![], - }; + let policy = SpendPolicy::UnlockConditions(unlock_condition); - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("c749f9ac53395ec557aed7e21d202f76a58e0de79222e5756b27077e9295931f"); - assert_eq!(hash, expected); -} + let sig0 = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + let sig1 = Signature::try_from( + hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); + let sig2 = Signature::try_from( + hex::decode("482A2A905D7A6FC730387E06B45EA0CF259FCB219C9A057E539E705F60AC36D7079E26DAFB66ED4DBA9B9694B50BCA64F1D4CC4EBE937CE08A34BF642FAC1F0C").unwrap()).unwrap(); -#[test] -fn test_satisfied_policy_encode_unlock_condition_complex() { - let pubkey0 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey1 = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - let pubkey2 = PublicKey::from_bytes( - &hex::decode("BE043906FD42297BC0A03CAA6E773EF27FC644261C692D090181E704BE4A88C3").unwrap(), - ) - .unwrap(); - - let unlock_condition = UnlockCondition::new(vec![pubkey0, pubkey1, pubkey2], 77777777, 3); - - let policy = SpendPolicy::UnlockConditions(unlock_condition); - - let sig0 = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - let sig1 = Signature::from_bytes( - &hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); - let sig2 = Signature::from_bytes( - &hex::decode("482A2A905D7A6FC730387E06B45EA0CF259FCB219C9A057E539E705F60AC36D7079E26DAFB66ED4DBA9B9694B50BCA64F1D4CC4EBE937CE08A34BF642FAC1F0C").unwrap()).unwrap(); - - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![sig0, sig1, sig2], - preimages: vec![], - }; - - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("13806b6c13a97478e476e0e5a0469c9d0ad8bf286bec0ada992e363e9fc60901"); - assert_eq!(hash, expected); -} + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![sig0, sig1, sig2], + preimages: vec![], + }; -#[test] -fn test_satisfied_policy_encode_threshold_simple() { - let sub_policy = SpendPolicy::Hash(H256::default()); - let policy = SpendPolicy::Threshold { - n: 1, - of: vec![sub_policy], - }; - - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![], - preimages: vec![vec![1u8, 2u8, 3u8, 4u8]], - }; - - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("50f4808b0661f56842472aed259136a43ed2bd7d59a88a3be28de9883af4a92d"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("13806b6c13a97478e476e0e5a0469c9d0ad8bf286bec0ada992e363e9fc60901").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_satisfied_policy_encode_threshold_atomic_swap_success() { - let alice_pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let bob_pubkey = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - - let secret_hash = H256::from("0100000000000000000000000000000000000000000000000000000000000000"); - - let policy = spend_policy_atomic_swap_success(alice_pubkey, bob_pubkey, 77777777, secret_hash); - let signature = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![signature], - preimages: vec![vec![1u8, 2u8, 3u8, 4u8]], - }; - - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("c835e516bbf76602c897a9160c17bfe0e4a8bc9044f62b3e5e45a381232a2f86"); - assert_eq!(hash, expected); -} + fn test_satisfied_policy_encode_threshold_simple() { + let sub_policy = SpendPolicy::Hash(Hash256::default()); + let policy = SpendPolicy::Threshold { + n: 1, + of: vec![sub_policy], + }; + let mut preimage = [0u8; 32]; + preimage[..4].copy_from_slice(&[1, 2, 3, 4]); + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![], + preimages: vec![preimage.into()], + }; + + let hash = Encoder::encode_and_hash(&satisfied_policy); + // FIXME update this in go equivalent. Preimage was changed from Vec to [u8; 32] + let expected = Hash256::from_str("2200a1464864cfaea8d312c1f16b5e00b816110896bea32ef7e1ccd43042d312").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_satisfied_policy_encode_threshold_atomic_swap_refund() { - let alice_pubkey = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let bob_pubkey = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - - let secret_hash = H256::from("0100000000000000000000000000000000000000000000000000000000000000"); - - let policy = spend_policy_atomic_swap_refund(alice_pubkey, bob_pubkey, 77777777, secret_hash); - let signature = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - - let satisfied_policy = SatisfiedPolicy { - policy, - signatures: vec![signature], - preimages: vec![vec![1u8, 2u8, 3u8, 4u8]], - }; - - let hash = Encoder::encode_and_hash(&satisfied_policy); - let expected = H256::from("8975e8cf990d5a20d9ec3dae18ed3b3a0c92edf967a8d93fcdef6a1eb73bb348"); - assert_eq!(hash, expected); -} + fn test_satisfied_policy_encode_threshold_atomic_swap_success() { + let alice_pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let bob_pubkey = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); + + let secret_hash = Hash256::from_str("0100000000000000000000000000000000000000000000000000000000000000").unwrap(); + + let policy = SpendPolicy::atomic_swap_success(&alice_pubkey, &bob_pubkey, 77777777, &secret_hash); + let signature = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + + let mut preimage = [0u8; 32]; + preimage[..4].copy_from_slice(&[1, 2, 3, 4]); + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![signature], + preimages: vec![preimage.into()], + }; + + let hash = Encoder::encode_and_hash(&satisfied_policy); + // FIXME update this in go equivalent. Preimage was changed from Vec to [u8; 32] + let expected = Hash256::from_str("08852e4ad99f726120028ecd82925b5f55fa441952cfc034a5cf4f09159b9372").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_siacoin_input_encode_v2() { - let sub_policy = SpendPolicy::Hash(H256::default()); - let policy = SpendPolicy::Threshold { - n: 1, - of: vec![sub_policy], - }; - - let satisfied_policy = SatisfiedPolicy { - policy: policy.clone(), - signatures: vec![], - preimages: vec![vec![1u8, 2u8, 3u8, 4u8]], - }; - - let vin = SiacoinInputV2 { - parent: SiacoinElement { - state_element: StateElement { - id: H256::default(), - leaf_index: 0, - merkle_proof: Some(vec![H256::default()]), - }, - siacoin_output: SiacoinOutput { - value: 1.into(), - address: policy.address(), - }, - maturity_height: 0, - }, - satisfied_policy, - }; - - let hash = Encoder::encode_and_hash(&vin); - let expected = H256::from("a8ab11b91ee19ce68f2d608bd4d19212841842f0c50151ae4ccb8e9db68cd6c4"); - assert_eq!(hash, expected); -} + fn test_satisfied_policy_encode_threshold_atomic_swap_refund() { + let alice_pubkey = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let bob_pubkey = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); -#[test] -fn test_attestation_encode() { - let public_key = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let signature = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - - let attestation = Attestation { - public_key, - key: "HostAnnouncement".to_string(), - value: vec![1u8, 2u8, 3u8, 4u8], - signature, - }; - - let hash = Encoder::encode_and_hash(&attestation); - let expected = H256::from("b28b32c6f91d1b57ab4a9ea9feecca16b35bb8febdee6a0162b22979415f519d"); - assert_eq!(hash, expected); -} + let secret_hash = Hash256::from_str("0100000000000000000000000000000000000000000000000000000000000000").unwrap(); -#[test] -fn test_file_contract_v2_encode() { - let pubkey0 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey1 = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - - let sig0 = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - let sig1 = Signature::from_bytes( - &hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); - - let address0 = v1_standard_address_from_pubkey(&pubkey0); - let address1 = v1_standard_address_from_pubkey(&pubkey1); - - let vout0 = SiacoinOutput { - value: 1.into(), - address: address0, - }; - let vout1 = SiacoinOutput { - value: 1.into(), - address: address1, - }; - - let file_contract_v2 = V2FileContract { - filesize: 1, - file_merkle_root: H256::default(), - proof_height: 1, - expiration_height: 1, - renter_output: vout0, - host_output: vout1, - missed_host_value: 1.into(), - total_collateral: 1.into(), - renter_public_key: pubkey0, - host_public_key: pubkey1, - revision_number: 1, - renter_signature: sig0, - host_signature: sig1, - }; - - let hash = Encoder::encode_and_hash(&file_contract_v2); - let expected = H256::from("6171a8d8ec31e06f80d46efbd1aecf2c5a7c344b5f2a2d4f660654b0cb84113c"); - assert_eq!(hash, expected); -} + let policy = SpendPolicy::atomic_swap_refund(&alice_pubkey, &bob_pubkey, 77777777, &secret_hash); + let signature = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); -#[test] -fn test_file_contract_element_v2_encode() { - let pubkey0 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey1 = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - - let sig0 = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - let sig1 = Signature::from_bytes( - &hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); - - let address0 = v1_standard_address_from_pubkey(&pubkey0); - let address1 = v1_standard_address_from_pubkey(&pubkey1); - - let vout0 = SiacoinOutput { - value: 1.into(), - address: address0, - }; - let vout1 = SiacoinOutput { - value: 1.into(), - address: address1, - }; - - let file_contract_v2 = V2FileContract { - filesize: 1, - file_merkle_root: H256::default(), - proof_height: 1, - expiration_height: 1, - renter_output: vout0, - host_output: vout1, - missed_host_value: 1.into(), - total_collateral: 1.into(), - renter_public_key: pubkey0, - host_public_key: pubkey1, - revision_number: 1, - renter_signature: sig0, - host_signature: sig1, - }; - - let state_element = StateElement { - id: H256::from("0102030000000000000000000000000000000000000000000000000000000000"), - leaf_index: 1, - merkle_proof: Some(vec![ - H256::from("0405060000000000000000000000000000000000000000000000000000000000"), - H256::from("0708090000000000000000000000000000000000000000000000000000000000"), - ]), - }; - - let file_contract_element_v2 = V2FileContractElement { - state_element, - v2_file_contract: file_contract_v2, - }; - - let hash = Encoder::encode_and_hash(&file_contract_element_v2); - let expected = H256::from("4cde411635118b2b7e1b019c659a2327ada53b303da0e46524e604d228fcd039"); - assert_eq!(hash, expected); -} + let mut preimage = [0u8; 32]; + preimage[..4].copy_from_slice(&[1, 2, 3, 4]); + let satisfied_policy = SatisfiedPolicy { + policy, + signatures: vec![signature], + preimages: vec![preimage.into()], + }; -#[test] -fn test_file_contract_revision_v2_encode() { - let pubkey0 = PublicKey::from_bytes( - &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let pubkey1 = PublicKey::from_bytes( - &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), - ) - .unwrap(); - - let sig0 = Signature::from_bytes( - &hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); - let sig1 = Signature::from_bytes( - &hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); - - let address0 = v1_standard_address_from_pubkey(&pubkey0); - let address1 = v1_standard_address_from_pubkey(&pubkey1); - - let vout0 = SiacoinOutput { - value: 1.into(), - address: address0, - }; - let vout1 = SiacoinOutput { - value: 1.into(), - address: address1, - }; - - let file_contract_v2 = V2FileContract { - filesize: 1, - file_merkle_root: H256::default(), - proof_height: 1, - expiration_height: 1, - renter_output: vout0, - host_output: vout1, - missed_host_value: 1.into(), - total_collateral: 1.into(), - renter_public_key: pubkey0, - host_public_key: pubkey1, - revision_number: 1, - renter_signature: sig0, - host_signature: sig1, - }; - - let state_element = StateElement { - id: H256::from("0102030000000000000000000000000000000000000000000000000000000000"), - leaf_index: 1, - merkle_proof: Some(vec![ - H256::from("0405060000000000000000000000000000000000000000000000000000000000"), - H256::from("0708090000000000000000000000000000000000000000000000000000000000"), - ]), - }; - - let file_contract_element_v2 = V2FileContractElement { - state_element, - v2_file_contract: file_contract_v2.clone(), - }; - - let file_contract_revision_v2 = FileContractRevisionV2 { - parent: file_contract_element_v2, - revision: file_contract_v2, - }; - - let hash = Encoder::encode_and_hash(&file_contract_revision_v2); - let expected = H256::from("22d5d1fd8c2762758f6b6ecf7058d73524ef209ac5a64f160b71ce91677db9a6"); - assert_eq!(hash, expected); -} + let hash = Encoder::encode_and_hash(&satisfied_policy); + let expected = Hash256::from_str("8975e8cf990d5a20d9ec3dae18ed3b3a0c92edf967a8d93fcdef6a1eb73bb348").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_v2_transaction_sig_hash() { - let j = json!( - { - "siacoinInputs": [ - { - "parent": { - "id": "h:b49cba94064a92a75bf8c6f9d32ab18f38bfb14a2252e3e117d04da89d536f29", - "leafIndex": 302, - "merkleProof": [ - "h:6f41d366712e9dfa423160b5388f3faf673addf43566d7b3562106d15b833f46", - "h:eb7df5e13eccd812a47f29a233bbf3212b7379ca6dd20ba9981524bfd5eadce6", - "h:04104cbada51333f8f37a6eb71f1e8cb287da2d62469568a8a36dc8c76602c80", - "h:16aac5c671d49d8cfc5493cb4c6f34889e30a0d283745c6473406bd60ab5e754", - "h:1b9ccf2b6f555687b1384091faa9ed1c154f41aaff81dcf393295383ca99f518", - "h:31337c9db5cdd181f5ff142bd490f779eedb1485e5dd905743280aeac3cd7ac9" - ], - "siacoinOutput": { - "value": "288594172736732570239334030000", - "address": "addr:2757c80b7ec2e493a138fed45b906f9f5735a992b68dcbd2069fbdf418c8b25158f3ac7a816b" - }, - "maturityHeight": 0 + fn test_siacoin_input_encode_v2() { + let sub_policy = SpendPolicy::Hash(Hash256::default()); + let policy = SpendPolicy::Threshold { + n: 1, + of: vec![sub_policy], + }; + let mut preimage = [0u8; 32]; + preimage[..4].copy_from_slice(&[1, 2, 3, 4]); + + let satisfied_policy = SatisfiedPolicy { + policy: policy.clone(), + signatures: vec![], + preimages: vec![preimage.into()], + }; + + let vin = SiacoinInputV2 { + parent: SiacoinElement { + id: SiacoinOutputId::default(), + state_element: StateElement { + leaf_index: 0, + merkle_proof: vec![Hash256::default()], }, - "satisfiedPolicy": { - "policy": { - "type": "uc", - "policy": { - "timelock": 0, - "publicKeys": [ - "ed25519:7931b69fe8888e354d601a778e31bfa97fa89dc6f625cd01cc8aa28046e557e7" - ], - "signaturesRequired": 1 - } - }, - "signatures": [ - "sig:f43380794a6384e3d24d9908143c05dd37aaac8959efb65d986feb70fe289a5e26b84e0ac712af01a2f85f8727da18aae13a599a51fb066d098591e40cb26902" - ] - } - } - ], - "siacoinOutputs": [ - { - "value": "1000000000000000000000000000", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + siacoin_output: SiacoinOutput { + value: 1u64.into(), + address: policy.address(), + }, + maturity_height: 0, }, - { - "value": "287594172736732570239334030000", - "address": "addr:2757c80b7ec2e493a138fed45b906f9f5735a992b68dcbd2069fbdf418c8b25158f3ac7a816b" - } - ], - "minerFee": "0" + satisfied_policy, + }; + + let hash = Encoder::encode_and_hash(&vin); + // FIXME update this in go equivalent. Preimage was changed from Vec to [u8; 32] + let expected = Hash256::from_str("d31a05b155113a5244f14ae833887fd8b30f555129be126ca4b90592290db24a").unwrap(); + assert_eq!(hash, expected); } - ); - let tx = serde_json::from_value::(j).unwrap(); - let hash = tx.input_sig_hash(); - let expected = H256::from("ef2f59bb25300bed9accbdcd95e1a2bd9f146ab6b474002670dc908ad68aacac"); - assert_eq!(hash, expected); -} + fn test_attestation_encode() { + let public_key = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let signature = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + + let attestation = Attestation { + public_key, + key: "HostAnnouncement".to_string(), + value: vec![1u8, 2u8, 3u8, 4u8], + signature, + }; + + let hash = Encoder::encode_and_hash(&attestation); + let expected = Hash256::from_str("b28b32c6f91d1b57ab4a9ea9feecca16b35bb8febdee6a0162b22979415f519d").unwrap(); + assert_eq!(hash, expected); + } + + fn test_file_contract_v2_encode() { + let pubkey0 = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey1 = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); + + let sig0 = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + let sig1 = Signature::try_from( + hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); + + let address0 = Address::standard_address_v1(&pubkey0); + let address1 = Address::standard_address_v1(&pubkey1); + + let vout0 = SiacoinOutput { + value: 1u64.into(), + address: address0, + }; + let vout1 = SiacoinOutput { + value: 1u64.into(), + address: address1, + }; + + let file_contract_v2 = V2FileContract { + capacity: 0, + filesize: 1, + file_merkle_root: Hash256::default(), + proof_height: 1, + expiration_height: 1, + renter_output: vout0, + host_output: vout1, + missed_host_value: 1u64.into(), + total_collateral: 1u64.into(), + renter_public_key: pubkey0, + host_public_key: pubkey1, + revision_number: 1, + renter_signature: sig0, + host_signature: sig1, + }; + + let hash = Encoder::encode_and_hash(&file_contract_v2); + let expected = Hash256::from_str("e851362bab643dc066b9d3c22c0fa0d67bc7b0cb520c689765e2292f4e7f435e").unwrap(); + assert_eq!(hash, expected); + } + + fn test_file_contract_element_v2_encode() { + let pubkey0 = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey1 = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); + + let sig0 = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + let sig1 = Signature::try_from( + hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); + + let address0 = Address::standard_address_v1(&pubkey0); + let address1 = Address::standard_address_v1(&pubkey1); + + let vout0 = SiacoinOutput { + value: 1u64.into(), + address: address0, + }; + let vout1 = SiacoinOutput { + value: 1u64.into(), + address: address1, + }; + + let file_contract_v2 = V2FileContract { + capacity: 0, + filesize: 1, + file_merkle_root: Hash256::default(), + proof_height: 1, + expiration_height: 1, + renter_output: vout0, + host_output: vout1, + missed_host_value: 1u64.into(), + total_collateral: 1u64.into(), + renter_public_key: pubkey0, + host_public_key: pubkey1, + revision_number: 1, + renter_signature: sig0, + host_signature: sig1, + }; + + let state_element = StateElement { + leaf_index: 1, + merkle_proof: vec![ + Hash256::from_str("0405060000000000000000000000000000000000000000000000000000000000").unwrap(), + Hash256::from_str("0708090000000000000000000000000000000000000000000000000000000000").unwrap(), + ], + }; + + let file_contract_element_v2 = V2FileContractElement { + id: Hash256::from_str("0707070000000000000000000000000000000000000000000000000000000000").unwrap().into(), + state_element, + v2_file_contract: file_contract_v2, + }; + + let hash = Encoder::encode_and_hash(&file_contract_element_v2); + let expected = Hash256::from_str("3005594b14c1615aadaef2d8558713ebeabfa7d54f1dec671ba67ea8264816e6").unwrap(); + assert_eq!(hash, expected); + } + + fn test_file_contract_revision_v2_encode() { + let pubkey0 = PublicKey::from_bytes( + &hex::decode("0102030000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let pubkey1 = PublicKey::from_bytes( + &hex::decode("06C87838297B7BB16AB23946C99DFDF77FF834E35DB07D71E9B1D2B01A11E96D").unwrap(), + ) + .unwrap(); + + let sig0 = Signature::try_from( + hex::decode("105641BF4AE119CB15617FC9658BEE5D448E2CC27C9BC3369F4BA5D0E1C3D01EBCB21B669A7B7A17CF8457189EAA657C41D4A2E6F9E0F25D0996D3A17170F309").unwrap()).unwrap(); + let sig1 = Signature::try_from( + hex::decode("0734761D562958F6A82819474171F05A40163901513E5858BFF9E4BD9CAFB04DEF0D6D345BACE7D14E50C5C523433B411C7D7E1618BE010A63C55C34A2DEE70A").unwrap()).unwrap(); + + let address0 = Address::standard_address_v1(&pubkey0); + let address1 = Address::standard_address_v1(&pubkey1); + + let vout0 = SiacoinOutput { + value: 1u64.into(), + address: address0, + }; + let vout1 = SiacoinOutput { + value: 1u64.into(), + address: address1, + }; + + let file_contract_v2 = V2FileContract { + capacity: 0, + filesize: 1, + file_merkle_root: Hash256::default(), + proof_height: 1, + expiration_height: 1, + renter_output: vout0, + host_output: vout1, + missed_host_value: 1u64.into(), + total_collateral: 1u64.into(), + renter_public_key: pubkey0, + host_public_key: pubkey1, + revision_number: 1, + renter_signature: sig0, + host_signature: sig1, + }; + + let state_element = StateElement { + leaf_index: 1, + merkle_proof: vec![ + Hash256::from_str("0405060000000000000000000000000000000000000000000000000000000000").unwrap(), + Hash256::from_str("0708090000000000000000000000000000000000000000000000000000000000").unwrap(), + ], + }; + + let file_contract_element_v2 = V2FileContractElement { + id: Hash256::from_str("0102030000000000000000000000000000000000000000000000000000000000").unwrap().into(), + state_element, + v2_file_contract: file_contract_v2.clone(), + }; + + let file_contract_revision_v2 = FileContractRevisionV2 { + parent: file_contract_element_v2, + revision: file_contract_v2, + }; + + let hash = Encoder::encode_and_hash(&file_contract_revision_v2); + let expected = Hash256::from_str("4f23582ec40570345f72adab8cd6249c0167669b78aec9ac7209befefc281f4f").unwrap(); + assert_eq!(hash, expected); + } -#[test] -fn test_v2_transaction_signing() { - use crate::{Keypair, Signature}; - let j = json!( - { - "siacoinInputs": [ + fn test_v2_transaction_sig_hash() { + let j = json!( { - "parent": { - "id": "h:f59e395dc5cbe3217ee80eff60585ffc9802e7ca580d55297782d4a9b4e08589", - "leafIndex": 3, - "merkleProof": [ - "h:ab0e1726444c50e2c0f7325eb65e5bd262a97aad2647d2816c39d97958d9588a", - "h:467e2be4d8482eca1f99440b6efd531ab556d10a8371a98a05b00cb284620cf0", - "h:64d5766fce1ff78a13a4a4744795ad49a8f8d187c01f9f46544810049643a74a", - "h:31d5151875152bc25d1df18ca6bbda1bef5b351e8d53c277791ecf416fcbb8a8", - "h:12a92a1ba87c7b38f3c4e264c399abfa28fb46274cfa429605a6409bd6d0a779", - "h:eda1d58a9282dbf6c3f1beb4d6c7bdc036d14a1cfee8ab1e94fabefa9bd63865", - "h:e03dee6e27220386c906f19fec711647353a5f6d76633a191cbc2f6dce239e89", - "h:e70fcf0129c500f7afb49f4f2bb82950462e952b7cdebb2ad0aa1561dc6ea8eb" - ], - "siacoinOutput": { - "value": "300000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" - }, - "maturityHeight": 145 - }, - "satisfiedPolicy": { - "policy": { - "type": "uc", - "policy": { - "timelock": 0, - "publicKeys": [ - "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc" - ], - "signaturesRequired": 1 + "siacoinInputs": [ + { + "parent": { + "id": "b49cba94064a92a75bf8c6f9d32ab18f38bfb14a2252e3e117d04da89d536f29", + "stateElement": { + "leafIndex": 302, + "merkleProof": [ + "6f41d366712e9dfa423160b5388f3faf673addf43566d7b3562106d15b833f46", + "eb7df5e13eccd812a47f29a233bbf3212b7379ca6dd20ba9981524bfd5eadce6", + "04104cbada51333f8f37a6eb71f1e8cb287da2d62469568a8a36dc8c76602c80", + "16aac5c671d49d8cfc5493cb4c6f34889e30a0d283745c6473406bd60ab5e754", + "1b9ccf2b6f555687b1384091faa9ed1c154f41aaff81dcf393295383ca99f518", + "31337c9db5cdd181f5ff142bd490f779eedb1485e5dd905743280aeac3cd7ac9" + ], + }, + "siacoinOutput": { + "value": "288594172736732570239334030000", + "address": "2757c80b7ec2e493a138fed45b906f9f5735a992b68dcbd2069fbdf418c8b25158f3ac7a816b" + }, + "maturityHeight": 0 + }, + "satisfiedPolicy": { + "policy": { + "type": "uc", + "policy": { + "timelock": 0, + "publicKeys": [ + "ed25519:7931b69fe8888e354d601a778e31bfa97fa89dc6f625cd01cc8aa28046e557e7" + ], + "signaturesRequired": 1 + } + }, + "signatures": [ + "f43380794a6384e3d24d9908143c05dd37aaac8959efb65d986feb70fe289a5e26b84e0ac712af01a2f85f8727da18aae13a599a51fb066d098591e40cb26902" + ] } + } + ], + "siacoinOutputs": [ + { + "value": "1000000000000000000000000000", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" }, - "signatures": [ - "sig:f0a29ba576eb0dbc3438877ac1d3a6da4f3c4cbafd9030709c8a83c2fffa64f4dd080d37444261f023af3bd7a10a9597c33616267d5371bf2c0ade5e25e61903" - ] - } + { + "value": "287594172736732570239334030000", + "address": "2757c80b7ec2e493a138fed45b906f9f5735a992b68dcbd2069fbdf418c8b25158f3ac7a816b" + } + ], + "minerFee": "0" } - ], - "siacoinOutputs": [ - { - "value": "1000000000000000000000000000", - "address": "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" - }, + ); + + let tx = serde_json::from_value::(j).unwrap(); + let hash = tx.input_sig_hash(); + let expected = Hash256::from_str("ef2f59bb25300bed9accbdcd95e1a2bd9f146ab6b474002670dc908ad68aacac").unwrap(); + assert_eq!(hash, expected); + } + + fn test_v2_transaction_signing() { + let j = json!( { - "value": "299000000000000000000000000000", - "address": "addr:f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + "siacoinInputs": [ + { + "parent": { + "id": "f59e395dc5cbe3217ee80eff60585ffc9802e7ca580d55297782d4a9b4e08589", + "stateElement": { + "leafIndex": 3, + "merkleProof": [ + "ab0e1726444c50e2c0f7325eb65e5bd262a97aad2647d2816c39d97958d9588a", + "467e2be4d8482eca1f99440b6efd531ab556d10a8371a98a05b00cb284620cf0", + "64d5766fce1ff78a13a4a4744795ad49a8f8d187c01f9f46544810049643a74a", + "31d5151875152bc25d1df18ca6bbda1bef5b351e8d53c277791ecf416fcbb8a8", + "12a92a1ba87c7b38f3c4e264c399abfa28fb46274cfa429605a6409bd6d0a779", + "eda1d58a9282dbf6c3f1beb4d6c7bdc036d14a1cfee8ab1e94fabefa9bd63865", + "e03dee6e27220386c906f19fec711647353a5f6d76633a191cbc2f6dce239e89", + "e70fcf0129c500f7afb49f4f2bb82950462e952b7cdebb2ad0aa1561dc6ea8eb" + ] + }, + "siacoinOutput": { + "value": "300000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + }, + "maturityHeight": 145 + }, + "satisfiedPolicy": { + "policy": { + "type": "uc", + "policy": { + "timelock": 0, + "publicKeys": [ + "ed25519:cecc1507dc1ddd7295951c290888f095adb9044d1b73d696e6df065d683bd4fc" + ], + "signaturesRequired": 1 + } + }, + "signatures": [ + "f0a29ba576eb0dbc3438877ac1d3a6da4f3c4cbafd9030709c8a83c2fffa64f4dd080d37444261f023af3bd7a10a9597c33616267d5371bf2c0ade5e25e61903" + ] + } + } + ], + "siacoinOutputs": [ + { + "value": "1000000000000000000000000000", + "address": "000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69" + }, + { + "value": "299000000000000000000000000000", + "address": "f7843ac265b037658b304468013da4fd0f304a1b73df0dc68c4273c867bfa38d01a7661a187f" + } + ], + "minerFee": "0" } - ], - "minerFee": "0" + ); + let tx = serde_json::from_value::(j).unwrap(); + let keypair = Keypair::from_private_bytes( + &hex::decode("0100000000000000000000000000000000000000000000000000000000000000").unwrap(), + ) + .unwrap(); + let sig_hash = tx.input_sig_hash(); + + // test that we can correctly regenerate the signature + let sig: Signature = keypair.sign(&sig_hash.0); + assert_eq!(tx.siacoin_inputs[0].satisfied_policy.signatures[0], sig); + } + + fn test_siacoin_output_id_new() { + let txid = Hash256::from_str("31be0badc64d40fbcb91b63835c07d75ab49addd1fc1d839b8415e1e5ff38cb5").unwrap(); + let output_index = 0u32; + let output_id = SiacoinOutputId::new(txid, output_index); + let expected = SiacoinOutputId( + Hash256::from_str("47b2ceee0a9e246d5f997129a250ecb3d0917f5e844989d520e246145349d292").unwrap(), + ); + assert_eq!(output_id, expected); } - ); - let tx = serde_json::from_value::(j).unwrap(); - let keypair = Keypair::from_private_bytes( - &hex::decode("0100000000000000000000000000000000000000000000000000000000000000").unwrap(), - ) - .unwrap(); - let sig_hash = tx.input_sig_hash(); - - // test that we can correctly regenerate the signature - let sig: Signature = keypair.sign(&sig_hash.0); - assert_eq!(tx.siacoin_inputs[0].satisfied_policy.signatures[0], sig); + } } diff --git a/src/http/client.rs b/src/transport/client.rs similarity index 57% rename from src/http/client.rs rename to src/transport/client.rs index 04c910e..85d5640 100644 --- a/src/http/client.rs +++ b/src/transport/client.rs @@ -1,6 +1,4 @@ -use crate::http::endpoints::{AddressBalanceRequest, AddressBalanceResponse, ConsensusTipRequest, SiaApiRequest}; - -use crate::types::Address; +use crate::transport::endpoints::SiaApiRequest; use async_trait::async_trait; use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC}; use serde_json::Value as JsonValue; @@ -11,6 +9,9 @@ use url::Url; #[cfg(not(target_arch = "wasm32"))] pub mod native; #[cfg(target_arch = "wasm32")] pub mod wasm; +mod helpers; +pub use helpers::{ApiClientHelpers, HelperError}; + // FIXME remove these client specific error types #[cfg(not(target_arch = "wasm32"))] use reqwest::Error as ReqwestError; @@ -44,17 +45,6 @@ pub trait ApiClient: Clone { async fn dispatcher(&self, request: R) -> Result; } -#[async_trait] -pub trait ApiClientHelpers: ApiClient { - async fn current_height(&self) -> Result { - Ok(self.dispatcher(ConsensusTipRequest).await?.height) - } - - async fn address_balance(&self, address: Address) -> Result { - self.dispatcher(AddressBalanceRequest { address }).await - } -} - #[derive(Debug, Error)] pub enum ApiClientError { #[error("BuildError error: {0}")] @@ -64,13 +54,11 @@ pub enum ApiClientError { #[error("UrlParse error: {0}")] UrlParse(#[from] url::ParseError), #[error("UnexpectedHttpStatus error: status:{status} body:{body}")] - UnexpectedHttpStatus{ status: http::StatusCode, body: String }, + UnexpectedHttpStatus { status: http::StatusCode, body: String }, #[error("Serde error: {0}")] Serde(#[from] serde_json::Error), #[error("UnexpectedEmptyResponse error: {expected_type}")] - UnexpectedEmptyResponse { - expected_type: String, - }, + UnexpectedEmptyResponse { expected_type: String }, #[error("WasmFetchError error: {0}")] #[cfg(target_arch = "wasm32")] WasmFetchError(#[from] FetchError), @@ -81,6 +69,7 @@ pub enum ApiClientError { // Not all client implementations will have an exact equivalent of HTTP methods // However, the client implementation should be able to map the HTTP methods to its own methods +#[derive(Clone, Debug)] pub enum SchemaMethod { Get, Post, @@ -99,6 +88,7 @@ impl From for http::Method { } } +#[derive(Clone, Debug)] pub struct EndpointSchema { pub path_schema: String, // The endpoint path template (e.g., /api/transactions/{id}) pub path_params: Option>, // Optional parameters to replace in the path (e.g., /{key} becomes /value) @@ -131,8 +121,8 @@ impl EndpointSchemaBuilder { self } - pub fn query_params(mut self, query_params: HashMap) -> Self { - self.query_params = Some(query_params); + pub fn query_params(mut self, query_params: Option>) -> Self { + self.query_params = query_params; self } @@ -152,6 +142,7 @@ impl EndpointSchemaBuilder { } } +#[derive(Clone, Debug)] pub enum Body { Utf8(String), Json(JsonValue), @@ -187,93 +178,3 @@ impl EndpointSchema { Ok(url) } } - -#[cfg(all(target_arch = "wasm32", test))] -mod wasm_tests { - use super::*; - use common::log::info; - use common::log::wasm_log::register_wasm_log; - use once_cell::sync::Lazy; - use wasm_bindgen::prelude::*; - use wasm_bindgen_test::*; - - wasm_bindgen_test_configure!(run_in_browser); - - static CONF: Lazy = Lazy::new(|| ClientConf { - url: Url::parse("https://sia-walletd.komodo.earth/").unwrap(), - password: "password".to_string(), - }); - - fn init_test_env() { register_wasm_log(); } - - #[wasm_bindgen_test] - async fn test_dispatcher_invalid_base_url() { - let bad_conf = ClientConf { - url: Url::parse("http://user:password@example.com").unwrap(), - password: "password".to_string(), - }; - - let client = SiaApiClient::new(bad_conf).await.unwrap(); - } - - #[wasm_bindgen_test] - async fn test_sia_wasm_client_wip() { - use crate::http::endpoints::TxpoolBroadcastRequest; - use crate::transaction::V2Transaction; - init_test_env(); - let client = SiaApiClient::new(CONF.clone()).await.unwrap(); - - let tx_str = r#" - { - "siacoinInputs": [ - { - "parent": { - "id": "h:27248ab562cbbee260e07ccae87c74aae71c9358d7f91eee25837e2011ce36d3", - "leafIndex": 21867, - "merkleProof": [ - "h:ac2fdcbed40f103e54b0b1a37c20a865f6f1f765950bc6ac358ff3a0e769da50", - "h:b25570eb5c106619d4eef5ad62482023df7a1c7461e9559248cb82659ebab069", - "h:baa78ec23a169d4e9d7f801e5cf25926bf8c29e939e0e94ba065b43941eb0af8", - "h:239857343f2997462bed6c253806cf578d252dbbfd5b662c203e5f75d897886d", - "h:ad727ef2112dc738a72644703177f730c634a0a00e0b405bd240b0da6cdfbc1c", - "h:4cfe0579eabafa25e98d83c3b5d07ae3835ce3ea176072064ea2b3be689e99aa", - "h:736af73aa1338f3bc28d1d8d3cf4f4d0393f15c3b005670f762709b6231951fc" - ], - "siacoinOutput": { - "value": "772999980000000000000000000", - "address": "addr:1599ea80d9af168ce823e58448fad305eac2faf260f7f0b56481c5ef18f0961057bf17030fb3" - }, - "maturityHeight": 0 - }, - "satisfiedPolicy": { - "policy": { - "type": "pk", - "policy": "ed25519:968e286ef5df3954b7189c53a0b4b3d827664357ebc85d590299b199af46abad" - }, - "signatures": [ - "sig:7a2c332fef3958a0486ef5e55b70d2a68514ff46d9307a85c3c0e40b76a19eebf4371ab3dd38a668cefe94dbedff2c50cc67856fbf42dce2194b380e536c1500" - ] - } - } - ], - "siacoinOutputs": [ - { - "value": "2000000000000000000000000", - "address": "addr:1d9a926b1e14b54242375c7899a60de883c8cad0a45a49a7ca2fdb6eb52f0f01dfe678918204" - }, - { - "value": "770999970000000000000000000", - "address": "addr:1599ea80d9af168ce823e58448fad305eac2faf260f7f0b56481c5ef18f0961057bf17030fb3" - } - ], - "minerFee": "10000000000000000000" - } - "#; - let tx: V2Transaction = serde_json::from_str(tx_str).unwrap(); - let req = TxpoolBroadcastRequest { - transactions: vec![], - v2transactions: vec![tx], - }; - let resp = client.dispatcher(req).await.unwrap(); - } -} diff --git a/src/http/client/example.rs b/src/transport/client/example.rs similarity index 95% rename from src/http/client/example.rs rename to src/transport/client/example.rs index 6b7e9da..19dcec4 100644 --- a/src/http/client/example.rs +++ b/src/transport/client/example.rs @@ -1,10 +1,10 @@ // THIS FILE MUST NOT INCLUDED IN WORKSPACE **Some Broken Code to Ensure the Build Fails if this File is Included in the Workspace** -use crate::http::endpoints::{SiaApiRequest}; +use crate::transport::endpoints::{SiaApiRequest}; use async_trait::async_trait; use serde::Deserialize; -use crate::http::client::{ApiClient, ApiClientError, ApiClientHelpers, EndpointSchema}; +use crate::transport::client::{ApiClient, ApiClientError, ApiClientHelpers, EndpointSchema}; #[derive(Clone)] diff --git a/src/transport/client/helpers.rs b/src/transport/client/helpers.rs new file mode 100644 index 0000000..dd3db78 --- /dev/null +++ b/src/transport/client/helpers.rs @@ -0,0 +1,407 @@ +use super::{ApiClient, ApiClientError}; +use crate::transport::endpoints::{AddressBalanceRequest, AddressBalanceResponse, AddressesEventsRequest, + ConsensusIndexRequest, ConsensusTipRequest, ConsensusTipstateRequest, + ConsensusTipstateResponse, ConsensusUpdatesRequest, ConsensusUpdatesResponse, + GetAddressUtxosRequest, GetEventRequest, TxpoolBroadcastRequest, + TxpoolTransactionsRequest}; +use crate::types::{Address, Currency, Event, EventDataWrapper, Hash256, PublicKey, SiacoinElement, SiacoinOutputId, + SpendPolicy, TransactionId, V2Transaction, V2TransactionBuilder}; +use async_trait::async_trait; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum HelperError { + #[error("ApiClientHelpers::utxo_from_txid failed: {0}")] + UtxoFromTxid(#[from] UtxoFromTxidError), + #[error("ApiClientHelpers::get_transaction failed: {0}")] + GetTx(#[from] GetTransactionError), + #[error("ApiClientHelpers::get_unconfirmed_transaction: failed to fetch mempool {0}")] + GetUnconfirmedTx(#[from] ApiClientError), + #[error("ApiClientHelpers::select_unspent_outputs failed: {0}")] + SelectUtxos(#[from] SelectUtxosError), + #[error("ApiClientHelpers::get_event failed to fetch event: {0}")] + GetEvent(ApiClientError), + #[error("ApiClientHelpers::get_address_events failed: {0}")] + GetAddressEvents(ApiClientError), + #[error("ApiClientHelpers::broadcast_transaction failed to broadcast transaction: {0}")] + BroadcastTx(ApiClientError), + #[error("ApiClientHelpers::get_median_timestamp failed: {0}")] + GetMedianTimestamp(#[from] GetMedianTimestampError), + #[error("ApiClientHelpers::get_consensus_updates_since_height failed: {0}")] + UpdatesSinceHeight(#[from] UpdatesSinceHeightError), + #[error("ApiClientHelpers::find_where_utxo_spent failed: {0}")] + FindWhereUtxoSpent(#[from] FindWhereUtxoSpentError), +} + +#[derive(Debug, Error)] +pub enum UtxoFromTxidError { + #[error("ApiClientHelpers::utxo_from_txid: failed to fetch event {0}")] + FetchEvent(ApiClientError), + #[error("ApiClientHelpers::utxo_from_txid: invalid event variant {0:?}")] + EventVariant(Event), + #[error("ApiClientHelpers::utxo_from_txid: output index out of bounds txid: {txid} index: {index}")] + OutputIndexOutOfBounds { txid: TransactionId, index: u32 }, + #[error("ApiClientHelpers::utxo_from_txid: get_unspent_outputs helper failed {0}")] + FetchUtxos(ApiClientError), + #[error("ApiClientHelpers::utxo_from_txid: output not found txid: {txid} index: {index}")] + NotFound { txid: TransactionId, index: u32 }, + #[error("ApiClientHelpers::utxo_from_txid: found duplicate utxo txid: {txid} index: {index}")] + DuplicateUtxoFound { txid: TransactionId, index: u32 }, +} + +#[derive(Debug, Error)] +pub enum GetTransactionError { + #[error("ApiClientHelpers::get_transaction: failed to fetch event {0}")] + FetchEvent(#[from] ApiClientError), + #[error("ApiClientHelpers::get_transaction: unexpected variant error {0:?}")] + EventVariant(EventDataWrapper), +} + +#[derive(Debug, Error)] +pub enum SelectUtxosError { + #[error( + "ApiClientHelpers::select_unspent_outputs: insufficent funds, available: {available:?} required: {required:?}" + )] + Funding { available: Currency, required: Currency }, + #[error("ApiClientHelpers::select_unspent_outputs: failed to fetch UTXOs {0}")] + FetchUtxos(#[from] ApiClientError), +} + +#[derive(Debug, Error)] +pub enum GetMedianTimestampError { + #[error("ApiClientHelpers::get_median_timestamp: failed to fetch consensus tipstate: {0}")] + FetchTipstate(#[from] ApiClientError), + #[error( + r#"ApiClientHelpers::get_median_timestamp: expected 11 timestamps in response: {0:?}. + The walletd state is likely corrupt as it is evidently reporting a chain height of less + than 11 blocks."# + )] + TimestampVecLen(ConsensusTipstateResponse), +} + +/// Helper methods for the ApiClient trait +/// These generally provide higher level functionality than the base ApiClient trait +/// This crate is focused on catering to the Komodo Defi Framework integration +#[async_trait] +pub trait ApiClientHelpers: ApiClient { + async fn current_height(&self) -> Result { + Ok(self.dispatcher(ConsensusTipRequest).await?.height) + } + + async fn address_balance(&self, address: Address) -> Result { + self.dispatcher(AddressBalanceRequest { address }).await + } + + async fn get_unspent_outputs( + &self, + address: &Address, + limit: Option, + offset: Option, + ) -> Result, ApiClientError> { + self.dispatcher(GetAddressUtxosRequest { + address: address.clone(), + limit, + offset, + }) + .await + } + + /// Fetches unspent outputs for the given address and attempts to select a subset of outputs + /// whose total value is at least `total_amount`. The outputs are sorted from largest to smallest to minimize + /// the number of outputs selected. The function returns a vector of the selected outputs and the difference between + /// the total value of the selected outputs and the required amount, aka the change. + /// # Arguments + /// + /// * `unspent_outputs` - A vector of `SiacoinElement`s representing unspent Siacoin outputs. + /// * `total_amount` - The total amount required for the selection. Should generally be the sum of the outputs and miner fee. + /// + /// # Returns + /// + /// This function returns `Result<(Vec, Currency), ApiClientHelpersError>`: + /// * `Ok((Vec, Currency))` - A tuple containing, a vector of the selected unspent outputs and the change amount. + /// * `Err(ApiClientHelpersError)` - An error is returned if the available outputs cannot meet the required amount or a transport error is encountered. + async fn select_unspent_outputs( + &self, + address: &Address, + total_amount: Currency, + ) -> Result<(Vec, Currency), HelperError> { + let mut unspent_outputs = self + .get_unspent_outputs(address, None, None) + .await + .map_err(SelectUtxosError::FetchUtxos)?; + + // Sort outputs from largest to smallest + unspent_outputs.sort_by(|a, b| b.siacoin_output.value.0.cmp(&a.siacoin_output.value.0)); + + let mut selected = Vec::new(); + let mut selected_amount = Currency::ZERO; + + // Select outputs until the total amount is reached + for output in unspent_outputs { + selected_amount += output.siacoin_output.value; + selected.push(output); + + if selected_amount >= total_amount { + break; + } + } + + if selected_amount < total_amount { + return Err(SelectUtxosError::Funding { + available: selected_amount, + required: total_amount, + })?; + } + let change = selected_amount - total_amount; + + Ok((selected, change)) + } + + /// Fund a transaction with utxos from the given address. + /// This should generally be used only after all outputs and miner_fee have been added to the builder. + /// Assumes no file contracts or resolutions. This is a helper designed for Komodo DeFi Framework. + /// Adds inputs from the given address until the total amount from outputs and miner_fee is reached. + /// Adds the change amount to the transaction outputs + /// See `select_unspent_outputs` for more details on UTXO selection. + /// # Arguments + /// * `tx_builder` - A mutable reference to a `V2TransactionBuilder. + /// * `public_key` - The public key of the address to spend utxos from. + /// # Returns + /// * `Ok(())` - The transaction builder has been successfully funded + /// * `Err(ApiClientHelpersError)` - An error is returned if the available outputs cannot meet + /// the required amount or a transport error is encountered. + // Alright TODO - move V2TransactionBuilder to a separate module then move this logic to a + // method of V2TransactionBuilder to allow chaining. It was included here because V2TransactionBuilder + // is currently inside the transaction module which is generally meant for consensnus related types. + // It would not be appropriate to include ApiClient-related code in transaction.rs + async fn fund_tx_single_source( + &self, + tx_builder: &mut V2TransactionBuilder, + public_key: &PublicKey, + ) -> Result<(), HelperError> { + let address = public_key.address(); + let outputs_total: Currency = tx_builder.siacoin_outputs.iter().map(|output| output.value).sum(); + + // select utxos from public key's address that total at least the sum of outputs and miner fee + let (selected_utxos, change) = self + .select_unspent_outputs(&address, outputs_total + tx_builder.miner_fee) + .await?; + + // add selected utxos as inputs to the transaction + for utxo in &selected_utxos { + tx_builder.add_siacoin_input(utxo.clone(), SpendPolicy::PublicKey(public_key.clone())); + } + + if change > Currency::DUST { + // add change as an output + tx_builder.add_siacoin_output((address, change).into()); + } + + Ok(()) + } + + /// Fetches a SiacoinElement(a UTXO) from a TransactionId and Index + /// Walletd doesn't currently offer an easy way to fetch the SiacoinElement type needed to build + /// SiacoinInputs. + async fn utxo_from_txid(&self, txid: &TransactionId, vout_index: u32) -> Result { + let output_id = SiacoinOutputId::new(txid.clone(), vout_index); + + // fetch the Event via /api/events/{txid} + let event = self + .dispatcher(GetEventRequest { txid: txid.clone() }) + .await + .map_err(UtxoFromTxidError::FetchEvent)?; + + // check that the fetched event is V2Transaction + let tx = match event.data { + EventDataWrapper::V2Transaction(tx) => tx, + _ => return Err(UtxoFromTxidError::EventVariant(event))?, + }; + + // check that the output index is within bounds + if tx.siacoin_outputs.len() <= (vout_index as usize) { + return Err(UtxoFromTxidError::OutputIndexOutOfBounds { + txid: txid.clone(), + index: vout_index, + })?; + } + + let output_address = tx.siacoin_outputs[vout_index as usize].address.clone(); + + // fetch unspent outputs of the address + let address_utxos = self + .get_unspent_outputs(&output_address, None, None) + .await + .map_err(UtxoFromTxidError::FetchUtxos)?; + + // filter the utxos to find any matching the expected SiacoinOutputId + let filtered_utxos: Vec = address_utxos + .into_iter() + .filter(|element| element.id == output_id) + .collect(); + + // ensure only one utxo was found + match filtered_utxos.len() { + 1 => Ok(filtered_utxos[0].clone()), + 0 => Err(UtxoFromTxidError::NotFound { + txid: txid.clone(), + index: vout_index, + })?, + _ => Err(UtxoFromTxidError::DuplicateUtxoFound { + txid: txid.clone(), + index: vout_index, + })?, + } + } + + async fn get_event(&self, event_id: &Hash256) -> Result { + self.dispatcher(GetEventRequest { txid: event_id.clone() }) + .await + .map_err(HelperError::GetEvent) + } + + async fn get_address_events(&self, address: Address) -> Result, HelperError> { + let request = AddressesEventsRequest { + address, + limit: None, + offset: None, + }; + self.dispatcher(request).await.map_err(HelperError::GetAddressEvents) + } + + /// Fetch a v2 transaction from the blockchain + // FIXME Alright - this should return a Result, HelperError> to allow for + // logic to handle the case where the transaction is not found in the blockchain + // ApiClientError must be refactored to allow this + async fn get_transaction(&self, txid: &TransactionId) -> Result { + let event = self + .dispatcher(GetEventRequest { txid: txid.clone() }) + .await + .map_err(GetTransactionError::FetchEvent)?; + match event.data { + EventDataWrapper::V2Transaction(tx) => Ok(tx), + wrong_variant => Err(GetTransactionError::EventVariant(wrong_variant))?, + } + } + + /// Fetch a v2 transaction from the transaction pool / mempool + /// Returns Ok(None) if the transaction is not found in the mempool + async fn get_unconfirmed_transaction(&self, txid: &TransactionId) -> Result, HelperError> { + let found_in_mempool = self + .dispatcher(TxpoolTransactionsRequest) + .await + .map_err(HelperError::GetUnconfirmedTx)? + .v2transactions + .into_iter() + .find(|tx| tx.txid() == *txid); + Ok(found_in_mempool) + } + + /// Get the median timestamp of the chain's last 11 blocks + /// This is used in the evaluation of SpendPolicy::After + async fn get_median_timestamp(&self) -> Result { + let tipstate = self + .dispatcher(ConsensusTipstateRequest) + .await + .map_err(GetMedianTimestampError::FetchTipstate)?; + + // This can happen if the chain has less than 11 blocks + // We assume the chain is at least 11 blocks long for this helper. + if tipstate.prev_timestamps.len() != 11 { + return Err(GetMedianTimestampError::TimestampVecLen(tipstate))?; + } + + let median_timestamp = tipstate.prev_timestamps[5]; + Ok(median_timestamp.timestamp() as u64) + } + + async fn broadcast_transaction(&self, tx: &V2Transaction) -> Result<(), HelperError> { + let request = TxpoolBroadcastRequest { + transactions: vec![], + v2transactions: vec![tx.clone()], + }; + + self.dispatcher(request).await.map_err(HelperError::BroadcastTx)?; + Ok(()) + } + + async fn get_consensus_updates_since_height( + &self, + begin_height: u64, + ) -> Result { + let index_request = ConsensusIndexRequest { height: begin_height }; + let chain_index = self + .dispatcher(index_request) + .await + .map_err(UpdatesSinceHeightError::FetchIndex)?; + + let updates_request = ConsensusUpdatesRequest { + height: chain_index.height, + block_hash: chain_index.id, + limit: None, + }; + + self.dispatcher(updates_request) + .await + .map_err(UpdatesSinceHeightError::FetchUpdates) + .map_err(HelperError::UpdatesSinceHeight) + } + + /// Find the transaction that spent the given utxo + /// Scans the blockchain starting from `begin_height` + /// Returns Ok(None) if the utxo has not been spent + async fn find_where_utxo_spent( + &self, + siacoin_output_id: &SiacoinOutputId, + begin_height: u64, + ) -> Result, HelperError> { + // the SiacoinOutputId is displayed with h: prefix in the endpoint response so use we Hash256 + let output_id = siacoin_output_id; + + let updates = self + .get_consensus_updates_since_height(begin_height) + .await + .map_err(|e| FindWhereUtxoSpentError::FetchUpdates(Box::new(e)))?; + + // find the update that has the provided `siacoin_output_id`` in its "spent" field + let update_option = updates + .applied + .into_iter() + .find(|applied_update| applied_update.update.spent.contains(&output_id.0)); + + // If no update with the output_id was found, return Ok(None) indicating no error occured, + // but the spend transaction has not been found + let update = match update_option { + Some(update) => update, + None => return Ok(None), + }; + + // scan the block indicated by the update to find the transaction that spent the utxo + let tx = update + .block + .v2 + .transactions + .into_iter() + .find(|tx| tx.siacoin_inputs.iter().any(|input| input.parent.id == *output_id)) + .ok_or(FindWhereUtxoSpentError::SpendNotInBlock { id: output_id.clone() })?; + + Ok(Some(tx)) + } +} + +#[derive(Debug, Error)] +pub enum FindWhereUtxoSpentError { + // Boxed to allow HelperError to be held within itself + #[error("ApiClientHelpers::find_where_utxo_spent: failed to fetch consensus updates {0}")] + FetchUpdates(#[from] Box), + #[error("ApiClientHelpers::find_where_utxo_spent: scoid:{id} was not spent in the expected block")] + SpendNotInBlock { id: SiacoinOutputId }, +} + +#[derive(Debug, Error)] +pub enum UpdatesSinceHeightError { + #[error("ApiClientHelpers::get_consensus_updates_since_height: failed to fetch ChainIndex {0}")] + FetchIndex(ApiClientError), + #[error("ApiClientHelpers::get_consensus_updates_since_height: failed to fetch updates {0}")] + FetchUpdates(ApiClientError), +} diff --git a/src/http/client/native.rs b/src/transport/client/native.rs similarity index 83% rename from src/http/client/native.rs rename to src/transport/client/native.rs index 47400de..78da295 100644 --- a/src/http/client/native.rs +++ b/src/transport/client/native.rs @@ -1,4 +1,4 @@ -use crate::http::endpoints::{ConsensusTipRequest, SiaApiRequest}; +use crate::transport::endpoints::{ConsensusTipRequest, SiaApiRequest}; use async_trait::async_trait; use base64::engine::general_purpose::STANDARD as BASE64; use base64::Engine; @@ -7,7 +7,7 @@ use reqwest::Client as ReqwestClient; use serde::Deserialize; use url::Url; -use crate::http::client::{ApiClient, ApiClientError, ApiClientHelpers, Body as ClientBody, EndpointSchema}; +use crate::transport::client::{ApiClient, ApiClientError, ApiClientHelpers, Body as ClientBody, EndpointSchema}; use core::time::Duration; #[derive(Clone)] @@ -40,7 +40,7 @@ impl ApiClient for NativeClient { HeaderValue::from_str(&auth_value).map_err(|e| ApiClientError::BuildError(e.to_string()))?, ); } - let timeout = conf.timeout.unwrap_or(10); + let timeout = conf.timeout.unwrap_or(30); let client = ReqwestClient::builder() .default_headers(headers) .timeout(Duration::from_secs(timeout)) @@ -63,7 +63,8 @@ impl ApiClient for NativeClient { ClientBody::Utf8(body) => self.client.request(schema.method.into(), url).body(body).build(), ClientBody::Json(body) => self.client.request(schema.method.into(), url).json(&body).build(), ClientBody::Bytes(body) => self.client.request(schema.method.into(), url).body(body).build(), - }.map_err(ApiClientError::ReqwestError)?; + } + .map_err(ApiClientError::ReqwestError)?; Ok(req) } @@ -105,11 +106,8 @@ impl ApiClient for NativeClient { .map_err(|e| format!("Failed to retrieve body: {}", e)) .unwrap_or_else(|e| e); - Err(ApiClientError::UnexpectedHttpStatus { - status, - body, - }) - } + Err(ApiClientError::UnexpectedHttpStatus { status, body }) + }, } } } @@ -121,7 +119,7 @@ impl ApiClientHelpers for NativeClient {} #[cfg(test)] mod tests { use super::*; - use crate::http::endpoints::{AddressBalanceRequest, GetEventRequest}; + use crate::transport::endpoints::{AddressBalanceRequest, GetEventRequest}; use crate::types::Address; use std::str::FromStr; @@ -142,31 +140,33 @@ mod tests { api_client.dispatcher(request).await.unwrap() } + #[ignore = "FIXME Alright must utilize docker container or mock server"] #[tokio::test] async fn test_new_client() { let _api_client = init_client().await; } + #[ignore = "FIXME Alright must utilize docker container or mock server"] #[tokio::test] async fn test_api_consensus_tip() { // paranoid unit test - NativeClient::new already pings the server with ConsensusTipRequest let _response = test_dispatch(ConsensusTipRequest).await; } + #[ignore = "FIXME Alright must utilize docker container or mock server"] #[tokio::test] async fn test_api_address_balance() { let request = AddressBalanceRequest { - address: Address::from_str( - "addr:591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f", - ) - .unwrap(), + address: Address::from_str("591fcf237f8854b5653d1ac84ae4c107b37f148c3c7b413f292d48db0c25a8840be0653e411f") + .unwrap(), }; let _response = test_dispatch(request).await; } + #[ignore = "FIXME Alright must utilize docker container or mock server"] #[tokio::test] async fn test_api_events() { - use crate::types::H256; + use crate::types::Hash256; let request = GetEventRequest { - txid: H256::from_str("77c5ae2220eac76dd841e365bb14fcba5499977e6483472b96f4a83bcdd6c892").unwrap(), + txid: Hash256::from_str("77c5ae2220eac76dd841e365bb14fcba5499977e6483472b96f4a83bcdd6c892").unwrap(), }; let _response = test_dispatch(request).await; } diff --git a/src/http/client/wasm.rs b/src/transport/client/wasm.rs similarity index 52% rename from src/http/client/wasm.rs rename to src/transport/client/wasm.rs index efa88d9..66e495d 100644 --- a/src/http/client/wasm.rs +++ b/src/transport/client/wasm.rs @@ -1,5 +1,5 @@ -use crate::http::client::{ApiClient, ApiClientError, ApiClientHelpers, Body, EndpointSchema, SchemaMethod}; -use crate::http::endpoints::{ConsensusTipRequest, SiaApiRequest}; +use crate::transport::client::{ApiClient, ApiClientError, ApiClientHelpers, Body, EndpointSchema, SchemaMethod}; +use crate::transport::endpoints::{ConsensusTipRequest, SiaApiRequest}; use async_trait::async_trait; use http::StatusCode; @@ -102,12 +102,9 @@ impl ApiClient for Client { .body .map(|b| format!("{}", b)) // Use Display trait to format Body .unwrap_or_else(|| "".to_string()); // If body is None, use an empty string - - Err(ApiClientError::UnexpectedHttpStatus { - status, - body, - }) - } + + Err(ApiClientError::UnexpectedHttpStatus { status, body }) + }, } } } @@ -117,3 +114,84 @@ impl ApiClient for Client { // unless custom implementations for the traits methods are needed #[async_trait] impl ApiClientHelpers for Client {} + +#[cfg(all(target_arch = "wasm32", test))] +mod wasm_tests { + use super::*; + use once_cell::sync::Lazy; + use wasm_bindgen_test::*; + + wasm_bindgen_test_configure!(run_in_browser); + + static CONF: Lazy = Lazy::new(|| Conf { + server_url: Url::parse("https://sia-walletd.komodo.earth/").unwrap(), + headers: HashMap::new(), + }); + + // #[ignore] -- FIXME Alright must use docker container or mock server + // #[wasm_bindgen_test] + async fn test_sia_wasm_client_client_error() { + use crate::transport::endpoints::TxpoolBroadcastRequest; + use crate::types::V2Transaction; + let client = Client::new(CONF.clone()).await.unwrap(); + + let tx_str = r#" + { + "siacoinInputs": [ + { + "parent": { + "id": "27248ab562cbbee260e07ccae87c74aae71c9358d7f91eee25837e2011ce36d3", + "leafIndex": 21867, + "merkleProof": [ + "ac2fdcbed40f103e54b0b1a37c20a865f6f1f765950bc6ac358ff3a0e769da50", + "b25570eb5c106619d4eef5ad62482023df7a1c7461e9559248cb82659ebab069", + "baa78ec23a169d4e9d7f801e5cf25926bf8c29e939e0e94ba065b43941eb0af8", + "239857343f2997462bed6c253806cf578d252dbbfd5b662c203e5f75d897886d", + "ad727ef2112dc738a72644703177f730c634a0a00e0b405bd240b0da6cdfbc1c", + "4cfe0579eabafa25e98d83c3b5d07ae3835ce3ea176072064ea2b3be689e99aa", + "736af73aa1338f3bc28d1d8d3cf4f4d0393f15c3b005670f762709b6231951fc" + ], + "siacoinOutput": { + "value": "772999980000000000000000000", + "address": "1599ea80d9af168ce823e58448fad305eac2faf260f7f0b56481c5ef18f0961057bf17030fb3" + }, + "maturityHeight": 0 + }, + "satisfiedPolicy": { + "policy": { + "type": "pk", + "policy": "ed25519:968e286ef5df3954b7189c53a0b4b3d827664357ebc85d590299b199af46abad" + }, + "signatures": [ + "7a2c332fef3958a0486ef5e55b70d2a68514ff46d9307a85c3c0e40b76a19eebf4371ab3dd38a668cefe94dbedff2c50cc67856fbf42dce2194b380e536c1500" + ] + } + } + ], + "siacoinOutputs": [ + { + "value": "2000000000000000000000000", + "address": "1d9a926b1e14b54242375c7899a60de883c8cad0a45a49a7ca2fdb6eb52f0f01dfe678918204" + }, + { + "value": "770999970000000000000000000", + "address": "1599ea80d9af168ce823e58448fad305eac2faf260f7f0b56481c5ef18f0961057bf17030fb3" + } + ], + "minerFee": "10000000000000000000" + } + "#; + let tx: V2Transaction = serde_json::from_str(tx_str).unwrap(); + let req = TxpoolBroadcastRequest { + transactions: vec![], + v2transactions: vec![tx], + }; + match client.dispatcher(req).await.expect_err("Expected HTTP 400 error") { + ApiClientError::UnexpectedHttpStatus { + status: StatusCode::BAD_REQUEST, + body: _, + } => (), + e => panic!("Unexpected error: {:?}", e), + } + } +} diff --git a/src/http/client/wasm/wasm_fetch.rs b/src/transport/client/wasm/wasm_fetch.rs similarity index 93% rename from src/http/client/wasm/wasm_fetch.rs rename to src/transport/client/wasm/wasm_fetch.rs index 39139f2..54a5460 100644 --- a/src/http/client/wasm/wasm_fetch.rs +++ b/src/transport/client/wasm/wasm_fetch.rs @@ -28,26 +28,20 @@ pub fn stringify_js_error(error: &JsValue) -> String { #[derive(Debug, Error)] pub enum FetchError { #[error("Error deserializing '{uri}' response: {error}")] - ErrorDeserializing { - uri: String, - error: String, - }, - + ErrorDeserializing { uri: String, error: String }, + #[error("Transport '{uri}' error: {error}")] - Transport { - uri: String, - error: String, - }, - + Transport { uri: String, error: String }, + #[error("Invalid status code in response")] InvalidStatusCode(#[from] http::status::InvalidStatusCode), - + #[error("Invalid headers in response: {0}")] InvalidHeadersInResponse(String), - + #[error("Invalid body: {0}")] InvalidBody(String), - + #[error("Internal error: {0}")] Internal(String), } @@ -223,8 +217,7 @@ impl FetchRequest { let future = JsFuture::from(request_promise); let resp_value = future.await.map_err(|e| FetchError::Transport { uri: uri.clone(), - //error: stringify_js_error(&e), - error: format!("Triggers a CORS(I think) error!! {:?}", e).to_string(), + error: stringify_js_error(&e), })?; let js_response: JsResponse = match resp_value.dyn_into() { Ok(res) => res, @@ -233,8 +226,6 @@ impl FetchRequest { return Err(FetchError::Internal(error)); }, }; - let _status = StatusCode::from_u16(js_response.status()).map_err(FetchError::InvalidStatusCode)?; - let _headers = js_response.headers(); let fetch_response = FetchResponse::from_js_response(js_response).await?; Ok(fetch_response) @@ -285,7 +276,7 @@ mod tests { let uri = "http://example.com"; let mut req_init = RequestInit::new(); req_init.method("GET"); - let js_request = JsRequest::new_with_str_and_init(&uri, &req_init).unwrap(); + let _js_request = JsRequest::new_with_str_and_init(&uri, &req_init).unwrap(); } // further unit tests could be implemented for the Err case based on the spec @@ -298,8 +289,6 @@ mod tests { let err = JsRequest::new_with_str_and_init(&uri, &req_init) .map_err(|e| FetchError::Internal(stringify_js_error(&e))) .unwrap_err(); - assert!(err - .to_string() - .contains("Request cannot be constructed from a URL that includes credentials")); + assert!(err.to_string().contains("is an url with embedded credentials")); } } diff --git a/src/http/endpoints.rs b/src/transport/endpoints.rs similarity index 59% rename from src/http/endpoints.rs rename to src/transport/endpoints.rs index 96e2549..2695495 100644 --- a/src/http/endpoints.rs +++ b/src/transport/endpoints.rs @@ -1,6 +1,8 @@ -use crate::http::client::{ApiClientError, Body, EndpointSchema, EndpointSchemaBuilder, SchemaMethod}; -use crate::transaction::{SiacoinElement, V1Transaction, V2Transaction}; -use crate::types::{Address, BlockID, Currency, Event, H256}; +use crate::transport::client::{ApiClientError, Body, EndpointSchema, EndpointSchemaBuilder, SchemaMethod}; +use crate::types::{Address, ApiApplyUpdate, BlockId, ChainIndex, Currency, Event, Hash256, SiacoinElement, + V1Transaction, V2Transaction}; +use crate::utils::deserialize_null_as_empty_vec; +use chrono::{DateTime, Utc}; use serde::de::DeserializeOwned; use serde::{Deserialize, Serialize}; use std::collections::HashMap; @@ -9,10 +11,14 @@ const ENDPOINT_ADDRESSES_BALANCE: &str = "api/addresses/{address}/balance"; const ENDPOINT_ADDRESSES_EVENTS: &str = "api/addresses/{address}/events"; const ENDPOINT_ADDRESSES_UTXOS_SIACOIN: &str = "api/addresses/{address}/outputs/siacoin"; const ENDPOINT_CONSENSUS_TIP: &str = "api/consensus/tip"; +const ENDPOINT_CONSENSUS_INDEX: &str = "api/consensus/index/{height}"; +const ENDPOINT_CONSENSUS_TIPSTATE: &str = "api/consensus/tipstate"; +const ENDPOINT_CONSENSUS_UPDATES: &str = "api/consensus/updates/{height}::{hash}"; const ENDPOINT_EVENTS: &str = "api/events/{txid}"; const ENDPOINT_TXPOOL_BROADCAST: &str = "api/txpool/broadcast"; const ENDPOINT_TXPOOL_FEE: &str = "api/txpool/fee"; const ENDPOINT_TXPOOL_TRANSACTIONS: &str = "api/txpool/transactions"; +const ENDPOINT_DEBUG_MINE: &str = "api/debug/mine"; pub trait SiaApiRequest: Send { type Response: DeserializeOwned; @@ -33,7 +39,7 @@ pub trait SiaApiRequest: Send { /// and its block ID, representing the latest state of the blockchain. /// /// # Response -/// - The response is a `ConsensusTipResponse`, which contains the block's height and ID. +/// - The response is a `ChainIndex`, which contains the block's height and ID. /// This corresponds to the `types.ChainIndex` type in Go. /// /// # References @@ -41,21 +47,148 @@ pub trait SiaApiRequest: Send { /// - [Go Source for the ChainIndex Type](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/types.go#L194) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct ConsensusTipRequest; impl SiaApiRequest for ConsensusTipRequest { - type Response = ConsensusTipResponse; + type Response = ChainIndex; fn to_endpoint_schema(&self) -> Result { Ok(EndpointSchemaBuilder::new(ENDPOINT_CONSENSUS_TIP.to_owned(), SchemaMethod::Get).build()) } } -#[derive(Deserialize, Serialize, Debug)] -pub struct ConsensusTipResponse { +/// Represents the request-response pair for fetching a BlockId from a provided height. +/// +/// # Walletd Endpoint +/// `GET /consensus/index/{height}` +/// +/// # Description +/// Returns the ChainIndex of the Block at the provided height. The consensus tip includes the block's height +/// and its BlockId. +/// +/// # Response +/// - The response is a `ChainIndex`, which contains the block's height and ID. +/// This corresponds to the `types.ChainIndex` type in Go. +/// +/// # References +/// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/6ff23fe34f6fa45a19bfb6e4bacc8a16d2c48144/api/server.go#L158) +/// - [Go Source for the ChainIndex Type](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/types.go#L194) +/// +/// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct ConsensusIndexRequest { + pub height: u64, +} + +impl SiaApiRequest for ConsensusIndexRequest { + type Response = ChainIndex; + + fn to_endpoint_schema(&self) -> Result { + // Create the path_params HashMap to substitute {height} in the path schema + let mut path_params = HashMap::new(); + path_params.insert("height".to_owned(), self.height.to_string()); + + Ok( + EndpointSchemaBuilder::new(ENDPOINT_CONSENSUS_INDEX.to_owned(), SchemaMethod::Get) + .path_params(path_params) + .build(), + ) + } +} + +/// Represents the request-response pair for fetching the current consensus tipstate of the Sia network. +/// +/// # Walletd Endpoint +/// `GET /consensus/tipstate` +/// +/// # Description +/// Returns the current consensus state of the Sia network. +/// +/// # Response +/// - The response is a `ConsensusTipstateResponse`, which is a partial implementation of the `consensus.State` type in Go. +/// This response includes the current block's height and ID, as well as timestamps of the previous 11 blocks. +/// The median of the provided timestamps is the medianTimestamp used to evaluate SpendPolicy::After. +/// SpendPolicy::After(time) evaluates to true if `time > medianTimestamp`. +/// +/// # References +/// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/d71cf08d4579ba952c51e535f988000e43ed8722/api/server.go#L162) +/// - [Go Source for the consensus.State Type](https://github.com/SiaFoundation/core/blob/00682daf422864b250b6bc750d4229dd76a8632d/consensus/state.go#L111) +/// +/// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct ConsensusTipstateRequest; + +impl SiaApiRequest for ConsensusTipstateRequest { + type Response = ConsensusTipstateResponse; + + fn to_endpoint_schema(&self) -> Result { + Ok(EndpointSchemaBuilder::new(ENDPOINT_CONSENSUS_TIPSTATE.to_owned(), SchemaMethod::Get).build()) + } +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ConsensusTipstateResponse { + pub index: ChainIndex, + pub prev_timestamps: Vec>, +} + +/// Represents the request-response pair for fetching consensus updates of the Sia network. +/// +/// # Walletd Endpoint +/// `GET /consensus/updates/{height}::{hash}` +/// +/// # Description +/// Returns consensus updates since the specific block height until the current consensus tip. +/// +/// # Response +/// - The response is a `ConsensusUpdatesResponse`, which is a partial implementation of the `ConsensusUpdatesResponse` type in Go. +/// This endpoint only partially implements the Go type provided as response. +/// This endpoints implements the minimum required fields to facilitate the logic of ApiClientHelpers::find_where_utxo_spent method. +/// +/// # References +/// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/d71cf08d4579ba952c51e535f988000e43ed8722/api/server.go#L162) +/// - [Go Source for the consensus.State Type](https://github.com/SiaFoundation/core/blob/00682daf422864b250b6bc750d4229dd76a8632d/consensus/state.go#L111) +/// +/// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct ConsensusUpdatesRequest { pub height: u64, - pub id: BlockID, + pub block_hash: BlockId, + pub limit: Option, +} + +impl SiaApiRequest for ConsensusUpdatesRequest { + type Response = ConsensusUpdatesResponse; + + fn to_endpoint_schema(&self) -> Result { + // Create the path_params HashMap to substitute {height} and {hash} in the path schema + let mut path_params = HashMap::new(); + path_params.insert("height".to_owned(), self.height.to_string()); + path_params.insert("hash".to_owned(), format!("{}", self.block_hash.0)); + + let mut query_params = HashMap::new(); + if let Some(limit) = self.limit { + query_params.insert("limit".to_owned(), limit.to_string()); + } + + let query_params_option = (!query_params.is_empty()).then_some(query_params); + + Ok( + EndpointSchemaBuilder::new(ENDPOINT_CONSENSUS_UPDATES.to_owned(), SchemaMethod::Get) + .path_params(path_params) // Set the path params containing the height and hash + .query_params(query_params_option) // Set the query params for ?limit= + .build(), + ) + } +} + +#[derive(Clone, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ConsensusUpdatesResponse { + #[serde(deserialize_with = "deserialize_null_as_empty_vec")] + pub applied: Vec, } /// Represents the request-response pair for fetching the balance of an individual address. @@ -81,7 +214,7 @@ pub struct ConsensusTipResponse { /// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/6ff23fe34f6fa45a19bfb6e4bacc8a16d2c48144/api/server.go#L752) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct AddressBalanceRequest { pub address: Address, } @@ -101,7 +234,7 @@ impl SiaApiRequest for AddressBalanceRequest { } } -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct AddressBalanceResponse { pub siacoins: Currency, #[serde(rename = "immatureSiacoins")] @@ -121,20 +254,20 @@ pub struct AddressBalanceResponse { /// - [Go Source for Hash256](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/types.go#L63) /// /// # Response -/// - The response is a `GetEventResponse` in Rust, corresponding to `types.Event` in Go. +/// - The response is an `Event` in Rust, corresponding to `types.Event` in Go. /// - [Go Source for Event](https://github.com/SiaFoundation/walletd/blob/6ff23fe34f6fa45a19bfb6e4bacc8a16d2c48144/wallet/wallet.go#L14) /// /// # References /// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/134a28b063df60a687899ac33aa373bf461480bc/api/server.go#L828) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct GetEventRequest { - pub txid: H256, + pub txid: Hash256, } impl SiaApiRequest for GetEventRequest { - type Response = GetEventResponse; + type Response = Event; fn to_endpoint_schema(&self) -> Result { // Create the path_params HashMap to substitute {txid} in the path schema @@ -149,9 +282,6 @@ impl SiaApiRequest for GetEventRequest { } } -#[derive(Debug, Deserialize, Serialize)] -pub struct GetEventResponse(pub Event); - /// Represents the request-response pair for fetching events for a specific address. /// /// # Walletd Endpoint @@ -159,8 +289,8 @@ pub struct GetEventResponse(pub Event); /// /// # Fields /// - `addr`: (`types.Address` in Go) the address for which events are fetched. -/// - `limit`: (`i64` in Go) optional limit for the number of results. -/// - `offset`: (`i64` in Go) optional offset for paginated results. +/// - `limit`: (`int` type in Go) optional limit for the number of results. +/// - `offset`: (`int` type in Go) optional offset for paginated results. /// /// # Response /// - `[]types.Event` in Go corresponds to `Vec` in Rust. @@ -173,7 +303,7 @@ pub struct GetEventResponse(pub Event); /// - [Go Source for the Event Object](https://github.com/SiaFoundation/walletd/blob/6ff23fe34f6fa45a19bfb6e4bacc8a16d2c48144/wallet/wallet.go#L14) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct AddressesEventsRequest { pub address: Address, pub limit: Option, @@ -195,10 +325,12 @@ impl SiaApiRequest for AddressesEventsRequest { query_params.insert("offset".to_owned(), offset.to_string()); } + let query_params_option = (!query_params.is_empty()).then_some(query_params); + Ok( EndpointSchemaBuilder::new(ENDPOINT_ADDRESSES_EVENTS.to_owned(), SchemaMethod::Get) .path_params(path_params) // Set the path params containing the address - .query_params(query_params) // Set the query params for limit and offset + .query_params(query_params_option) // Set the query params for limit and offset .build(), ) } @@ -217,8 +349,8 @@ pub type AddressesEventsResponse = Vec; /// # Fields /// - `address`: The address for which to fetch UTXOs. In Go, this corresponds to `types.Address`. /// - [Go Source for Address Type](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/types.go#L165) -/// - `limit`: An optional limit on the number of results. Corresponds to `i64` in Go. -/// - `offset`: An optional offset for paginated results. Corresponds to `i64` in Go. +/// - `limit`: An optional limit on the number of results. Corresponds to `int64` in Go. +/// - `offset`: An optional offset for paginated results. Corresponds to `int64` in Go. /// /// # Response /// - The response is a `Vec` in Rust, corresponding to `[]types.SiacoinElement` in Go. @@ -228,23 +360,22 @@ pub type AddressesEventsResponse = Vec; /// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/6ff23fe34f6fa45a19bfb6e4bacc8a16d2c48144/api/server.go#L795) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct GetAddressUtxosRequest { pub address: Address, pub limit: Option, pub offset: Option, } -pub type GetAddressUtxosResponse = Vec; - impl SiaApiRequest for GetAddressUtxosRequest { - type Response = GetAddressUtxosResponse; + type Response = Vec; fn to_endpoint_schema(&self) -> Result { let mut path_params = HashMap::new(); path_params.insert("address".to_owned(), self.address.to_string()); let mut query_params = HashMap::new(); + if let Some(limit) = self.limit { query_params.insert("limit".to_owned(), limit.to_string()); } @@ -252,10 +383,12 @@ impl SiaApiRequest for GetAddressUtxosRequest { query_params.insert("offset".to_owned(), offset.to_string()); } + let query_params_option = (!query_params.is_empty()).then_some(query_params); + Ok( EndpointSchemaBuilder::new(ENDPOINT_ADDRESSES_UTXOS_SIACOIN.to_owned(), SchemaMethod::Get) .path_params(path_params) // Set the path params containing the address - .query_params(query_params) // Set the query params for limit and offset + .query_params(query_params_option) // Set the query params for limit and offset .build(), ) } @@ -290,13 +423,15 @@ impl SiaApiRequest for GetAddressUtxosRequest { /// - [Go Source for the V2Transaction Type](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/types.go#L649) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct TxpoolBroadcastRequest { pub transactions: Vec, pub v2transactions: Vec, } -#[derive(Deserialize, Serialize, Debug)] +// TODO Alright - this was initially thought neccesary to implement methods on it, but it seems () +// will work in its place +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct EmptyResponse; impl SiaApiRequest for TxpoolBroadcastRequest { @@ -339,10 +474,10 @@ impl SiaApiRequest for TxpoolBroadcastRequest { /// - [Go Source for the Currency Type](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/currency.go#L26) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct TxpoolFeeRequest; -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct TxpoolFeeResponse(pub Currency); impl SiaApiRequest for TxpoolFeeRequest { @@ -371,17 +506,68 @@ impl SiaApiRequest for TxpoolFeeRequest { /// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/6ff23fe34f6fa45a19bfb6e4bacc8a16d2c48144/api/server.go#L282C18-L282C43) /// /// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. -#[derive(Deserialize, Serialize, Debug)] +#[derive(Clone, Deserialize, Serialize, Debug)] pub struct TxpoolTransactionsRequest; +#[derive(Clone, Deserialize, Serialize, Debug, Default)] +#[serde(rename_all = "camelCase")] +pub struct TxpoolTransactionsResponse { + #[serde(deserialize_with = "deserialize_null_as_empty_vec")] + pub transactions: Vec, + #[serde(deserialize_with = "deserialize_null_as_empty_vec")] + pub v2transactions: Vec, +} + impl SiaApiRequest for TxpoolTransactionsRequest { + type Response = TxpoolTransactionsResponse; + + fn to_endpoint_schema(&self) -> Result { + Ok( + EndpointSchemaBuilder::new(ENDPOINT_TXPOOL_TRANSACTIONS.to_owned(), SchemaMethod::Get).build(), // No path_params, query_params, or body needed for this request + ) + } +} + +/// Represents the request-response pair for mining blocks on a Sia node. +/// +/// # Walletd Endpoint +/// `POST api/debug/mine` +/// +/// # Description +/// Mine n blocks to a specified address. This is a debug endpoint intended to be used on CI/CD test networks only. +/// This method is only supported on walletd nodes started with `-debug` flag. +/// +/// # Fields +/// - `address`: The address where blocks will be mined to. (`types.Address` type in Go) +/// - [Go Source for Address Type](https://github.com/SiaFoundation/core/blob/300042fd2129381468356dcd87c5e9a6ad94c0ef/types/types.go#L165) +/// - `blocks`: The amount of blocks to mine. (`int` type in Go) +/// +/// # Response +/// - The response is `HTTP 204 NO CONTENT`, which is represented by `EmptyResponse` in Rust. +/// This indicates that the request was successful but there is no response body. +/// +/// # References +/// - [Go Source for the HTTP Endpoint](https://github.com/SiaFoundation/walletd/blob/1e56661fa23bb39438ec869c91d661d51bc889a4/api/server.go#L872) +/// +/// This type is ported from the Go codebase, representing the equivalent request-response pair in Rust. +#[derive(Clone, Deserialize, Serialize, Debug)] +pub struct DebugMineRequest { + pub address: Address, + pub blocks: i64, +} + +impl SiaApiRequest for DebugMineRequest { type Response = EmptyResponse; fn is_empty_response() -> Option { Some(EmptyResponse) } fn to_endpoint_schema(&self) -> Result { + // Serialize the request into a JSON string + let body = serde_json::to_string(self).map_err(ApiClientError::Serde)?; Ok( - EndpointSchemaBuilder::new(ENDPOINT_TXPOOL_TRANSACTIONS.to_owned(), SchemaMethod::Get).build(), // No path_params, query_params, or body needed for this request + EndpointSchemaBuilder::new(ENDPOINT_DEBUG_MINE.to_owned(), SchemaMethod::Post) + .body(Body::Utf8(body)) // Set the JSON body for the POST request + .build(), ) } } diff --git a/src/http/mod.rs b/src/transport/mod.rs similarity index 100% rename from src/http/mod.rs rename to src/transport/mod.rs diff --git a/src/types.rs b/src/types.rs index 26cd348..ef68785 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,20 +1,36 @@ use crate::blake2b_internal::standard_unlock_hash; -use crate::encoding::{Encodable, Encoder, PrefixedH256}; -pub use crate::hash::H256; -pub use crate::transaction::Currency; -use crate::transaction::{FileContractElementV1, SiacoinElement, SiafundElement, StateElement, V1Transaction, - V2FileContractResolution, V2Transaction}; -use crate::PublicKey; +use crate::encoding::{Encodable, Encoder}; use blake2b_simd::Params; use chrono::{DateTime, Utc}; -use hex::FromHexError; +use derive_more::{Display, From, Into}; +use hex; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; -use serde_with::{serde_as, FromInto}; -use std::convert::From; -use std::convert::TryInto; +use std::convert::{TryFrom, TryInto}; use std::fmt; use std::str::FromStr; +use thiserror::Error; + +mod hash; +pub use hash::{Hash256, Hash256Error}; + +mod signature; +pub use signature::{Signature, SignatureError}; + +mod keypair; +pub use keypair::{Keypair, KeypairError, PublicKey, PublicKeyError}; + +mod spend_policy; +pub use spend_policy::*; + +mod transaction; +pub use transaction::*; + +mod specifier; +pub use specifier::*; + +mod consensus_updates; +pub use consensus_updates::*; const ADDRESS_HASH_LENGTH: usize = 32; const ADDRESS_CHECKSUM_LENGTH: usize = 6; @@ -22,7 +38,7 @@ const ADDRESS_CHECKSUM_LENGTH: usize = 6; // TODO this could probably include the checksum within the data type // generating the checksum on the fly is how Sia Go does this however #[derive(Debug, Clone, PartialEq)] -pub struct Address(pub H256); +pub struct Address(pub Hash256); impl Serialize for Address { fn serialize(&self, serializer: S) -> Result @@ -45,7 +61,7 @@ impl<'de> Deserialize<'de> for Address { type Value = Address; fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string prefixed with 'addr:' and followed by a 76-character hex string") + formatter.write_str("a 76-character hex string representing a Sia address") } fn visit_str(self, value: &str) -> Result @@ -61,11 +77,12 @@ impl<'de> Deserialize<'de> for Address { } impl Address { - pub fn str_without_prefix(&self) -> String { - let bytes = self.0 .0.as_ref(); - let checksum = blake2b_checksum(bytes); - format!("{}{}", hex::encode(bytes), hex::encode(checksum)) + pub fn standard_address_v1(pubkey: &PublicKey) -> Self { + let hash = standard_unlock_hash(pubkey); + Address(hash) } + + pub fn from_public_key(pubkey: &PublicKey) -> Self { SpendPolicy::PublicKey(pubkey.clone()).address() } } impl Encodable for Address { @@ -73,137 +90,70 @@ impl Encodable for Address { } impl fmt::Display for Address { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "addr:{}", self.str_without_prefix()) } -} - -impl fmt::Display for ParseAddressError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "Failed to parse Address: {:?}", self) } -} - -#[derive(Debug, Deserialize, Serialize)] -pub enum ParseAddressError { - #[serde(rename = "Address must begin with addr: prefix")] - MissingPrefix, - InvalidHexEncoding(String), - InvalidChecksum, - InvalidLength, + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let bytes = self.0 .0.as_ref(); + let checksum = blake2b_checksum(bytes); + write!(f, "{}{}", hex::encode(bytes), hex::encode(checksum)) + } } -impl From for ParseAddressError { - fn from(e: FromHexError) -> Self { ParseAddressError::InvalidHexEncoding(e.to_string()) } +#[derive(Debug, Error)] +pub enum AddressError { + #[error("Address::from_str Failed to decode hex: {0}")] + InvalidHex(#[from] hex::FromHexError), + #[error("Address::from_str: Invalid length, expected 38 byte hex string, found: {0}")] + InvalidLength(String), + #[error("Address::from_str: invalid checksum, expected:{expected}, found:{found}")] + InvalidChecksum { expected: String, found: String }, } impl FromStr for Address { - type Err = ParseAddressError; + type Err = AddressError; fn from_str(s: &str) -> Result { - if !s.starts_with("addr:") { - return Err(ParseAddressError::MissingPrefix); + // An address consists of a 32 byte blake2h hash followed by a 6 byte checksum + let address_bytes = hex::decode(s)?; + if address_bytes.len() != ADDRESS_HASH_LENGTH + ADDRESS_CHECKSUM_LENGTH { + return Err(AddressError::InvalidLength(s.to_owned())); } - let without_prefix = &s[ADDRESS_CHECKSUM_LENGTH - 1..]; - if without_prefix.len() != (ADDRESS_HASH_LENGTH + ADDRESS_CHECKSUM_LENGTH) * 2 { - return Err(ParseAddressError::InvalidLength); - } - - let (address_hex, checksum_hex) = without_prefix.split_at(ADDRESS_HASH_LENGTH * 2); - - let address_bytes: [u8; ADDRESS_HASH_LENGTH] = hex::decode(address_hex) - .map_err(ParseAddressError::from)? - .try_into() - .map_err(|_| ParseAddressError::InvalidLength)?; - - let checksum = hex::decode(checksum_hex).map_err(ParseAddressError::from)?; - let checksum_bytes: [u8; ADDRESS_CHECKSUM_LENGTH] = - checksum.try_into().map_err(|_| ParseAddressError::InvalidLength)?; + let hash_bytes = &address_bytes[0..ADDRESS_HASH_LENGTH]; + let checksum_bytes = &address_bytes[ADDRESS_HASH_LENGTH..]; - if checksum_bytes != blake2b_checksum(&address_bytes) { - return Err(ParseAddressError::InvalidChecksum); + let checksum = blake2b_checksum(hash_bytes); + if checksum_bytes != checksum { + return Err(AddressError::InvalidChecksum { + expected: hex::encode(checksum), + found: hex::encode(checksum_bytes), + }); } - - Ok(Address(H256::from(address_bytes))) + let inner_hash = Hash256::try_from(hash_bytes).expect("hash_bytes is 32 bytes long"); + Ok(Address(inner_hash)) } } -// Sia uses the first 6 bytes of blake2b(preimage) appended -// to address as checksum +/// Return the first 6 bytes of the blake2b(preimage) hash +/// Used in generating the checksum for a Sia address fn blake2b_checksum(preimage: &[u8]) -> [u8; 6] { let hash = Params::new().hash_length(32).to_state().update(preimage).finalize(); hash.as_bytes()[0..6].try_into().expect("array is 64 bytes long") } -pub fn v1_standard_address_from_pubkey(pubkey: &PublicKey) -> Address { - let hash = standard_unlock_hash(pubkey); - Address(hash) -} - -#[derive(Clone, Debug, PartialEq)] -pub struct BlockID(pub H256); - -impl From for H256 { - fn from(sia_hash: BlockID) -> Self { sia_hash.0 } -} - -impl From for BlockID { - fn from(h256: H256) -> Self { BlockID(h256) } -} - -impl<'de> Deserialize<'de> for BlockID { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct BlockIDVisitor; - - impl<'de> serde::de::Visitor<'de> for BlockIDVisitor { - type Value = BlockID; - - fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { - formatter.write_str("a string prefixed with 'bid:' and followed by a 64-character hex string") - } - - fn visit_str(self, value: &str) -> Result - where - E: serde::de::Error, - { - if let Some(hex_str) = value.strip_prefix("bid:") { - H256::from_str(hex_str) - .map(BlockID) - .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } else { - Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) - } - } - } - - deserializer.deserialize_str(BlockIDVisitor) - } -} - -impl Serialize for BlockID { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.serialize_str(&self.to_string()) - } -} - -impl fmt::Display for BlockID { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "bid:{}", self.0) } -} +#[derive(Clone, Debug, Display, PartialEq, From, Into, Serialize, Deserialize)] +#[serde(transparent)] +pub struct BlockId(pub Hash256); #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct ChainIndex { pub height: u64, - pub id: BlockID, + pub id: BlockId, } // TODO unit test impl Encodable for ChainIndex { fn encode(&self, encoder: &mut Encoder) { encoder.write_u64(self.height); - let block_id: H256 = self.id.clone().into(); + let block_id: Hash256 = self.id.clone().into(); block_id.encode(encoder); } } @@ -242,12 +192,11 @@ pub enum EventType { V2ContractResolution, } -#[serde_as] #[derive(Clone, Debug, Serialize)] pub struct Event { - #[serde_as(as = "FromInto")] - pub id: H256, + pub id: Hash256, pub index: ChainIndex, + pub confirmations: u64, pub timestamp: DateTime, #[serde(rename = "maturityHeight")] pub maturity_height: u64, @@ -265,8 +214,9 @@ impl<'de> Deserialize<'de> for Event { { #[derive(Deserialize, Debug)] struct EventHelper { - id: PrefixedH256, + id: Hash256, index: ChainIndex, + confirmations: u64, timestamp: DateTime, #[serde(rename = "maturityHeight")] maturity_height: u64, @@ -294,7 +244,8 @@ impl<'de> Deserialize<'de> for Event { .map(EventDataWrapper::V2Transaction) .map_err(serde::de::Error::custom), EventType::V1ContractResolution => { - return Err(serde::de::Error::custom("V1ContractResolution not supported")) + // FIXME we require this to safely deser V2Transactions sent over the wire + return Err(serde::de::Error::custom("V1ContractResolution not supported")); }, EventType::V2ContractResolution => serde_json::from_value::(helper.data) .map(|data| EventDataWrapper::V2FileContractResolution(Box::new(data))) @@ -302,8 +253,9 @@ impl<'de> Deserialize<'de> for Event { }?; Ok(Event { - id: helper.id.into(), + id: helper.id, index: helper.index, + confirmations: helper.confirmations, timestamp: helper.timestamp, maturity_height: helper.maturity_height, event_type: helper.event_type, diff --git a/src/types/consensus_updates.rs b/src/types/consensus_updates.rs new file mode 100644 index 0000000..1ab5395 --- /dev/null +++ b/src/types/consensus_updates.rs @@ -0,0 +1,46 @@ +use crate::types::{Hash256, V2Transaction}; +use crate::utils::deserialize_null_as_empty_vec; + +use serde::{Deserialize, Serialize}; + +/// This module consists of types related to walletd's `api/consensus/updates/:index` endpoint. +/// Only a partial implementation is done here to facilitate `ApiClientHelpers::find_where_utxo_spent` +/// It's possible these may be extended in the future, so a dedicated +/// module is created for this. + +/// Minimal implementation of Go type `api.ApplyUpdate` +/// As per walletd: "An ApplyUpdate is a consensus update that was applied to the best chain." +#[derive(Clone, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct ApiApplyUpdate { + pub update: Update, + pub block: Block, +} + +/// Minimal implementation of Go type `consensus.ApplyUpdate` +/// As per sia-core: "An ApplyUpdate represents the effects of applying a block to a state." +#[derive(Clone, Serialize, Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Update { + #[serde(deserialize_with = "deserialize_null_as_empty_vec")] + pub spent: Vec, +} + +/// Minimal implementation of Go type `types.Block` +/// As per sia-core: "A Block is a set of transactions grouped under a header." +#[derive(Clone, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct Block { + pub v2: V2BlockData, +} + +/// Equivalent of Go type `types.V2BlockData` +/// As per sia-core: "V2BlockData contains additional fields not present in v1 blocks."" +#[derive(Clone, Deserialize, Serialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct V2BlockData { + pub height: u64, + pub commitment: Hash256, + #[serde(deserialize_with = "deserialize_null_as_empty_vec")] + pub transactions: Vec, +} diff --git a/src/types/hash.rs b/src/types/hash.rs new file mode 100644 index 0000000..9989a99 --- /dev/null +++ b/src/types/hash.rs @@ -0,0 +1,148 @@ +use hex; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::convert::TryFrom; +use std::fmt::{self, Display}; +use std::str::FromStr; +use thiserror::Error; + +#[derive(Debug, Error)] +pub enum Hash256Error { + #[error("Hash256::from_str invalid hex: expected 32 byte hex string, found {0}")] + InvalidHex(String), + #[error("Hash256::from_str invalid length: expected 32 byte hex string, found {0}")] + InvalidLength(String), + #[error("Hash256::TryFrom<&[u8]> invalid slice length: expected 32 byte slice, found {0:?}")] + InvalidSliceLength(Vec), +} +#[derive(Clone, Debug, Default, Eq, PartialEq)] +pub struct Hash256(pub [u8; 32]); + +impl Serialize for Hash256 { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Hash256 { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Hash256::from_str(&s).map_err(serde::de::Error::custom) + } +} + +impl FromStr for Hash256 { + type Err = Hash256Error; + + fn from_str(hex_str: &str) -> Result { + if hex_str.len() != 64 { + return Err(Hash256Error::InvalidLength(hex_str.to_string())); + } + + let mut bytes = [0u8; 32]; + match hex::decode_to_slice(hex_str, &mut bytes) { + Ok(_) => Ok(Hash256(bytes)), + Err(_) => Err(Hash256Error::InvalidHex(hex_str.to_string())), + } + } +} + +impl Display for Hash256 { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", hex::encode(self.0)) } +} + +impl TryFrom<&[u8]> for Hash256 { + type Error = Hash256Error; + + fn try_from(slice: &[u8]) -> Result { + let slice_len = slice.len(); + if slice_len == 32 { + let mut array = [0u8; 32]; + array.copy_from_slice(slice); + Ok(Hash256(array)) + } else { + Err(Hash256Error::InvalidSliceLength(slice.to_owned())) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + + cross_target_tests! { + fn test_default() { + let hash = Hash256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + assert_eq!(hash, Hash256::default()); + } + + fn test_valid() { + let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap(); + assert_eq!(hash.to_string(), "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); + } + + fn test_display() { + let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap(); + assert_eq!(hash.to_string(), "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee"); + } + + fn test_serialize() { + let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap(); + let serialized = serde_json::to_string(&hash).unwrap(); + assert_eq!(&serialized, r#""c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#); + } + + fn test_deserialize() { + let hash = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee").unwrap(); + let deserialized: Hash256 = serde_json::from_str(r#""c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee""#).unwrap(); + assert_eq!(deserialized, hash); + } + + fn test_invalid_hex() { + let err = Hash256::from_str("c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeg").expect_err("no prefix"); + let expected = "c0ffeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeg"; + match err { + Hash256Error::InvalidHex(ref e) if expected == e => (), + _ => panic!("unexpected error: {:?}", err), + } + } + + fn test_invalid_length() { + let err = Hash256::from_str("badc0de").expect_err("invalid length"); + let expected = "badc0de"; + match err { + Hash256Error::InvalidLength(ref e) if expected == e => (), + _ => panic!("unexpected error: {:?}", err), + } + } + + fn test_from_str_valid() { + let hash = Hash256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(); + assert_eq!(hash, Hash256::default()) + } + + fn test_from_str_invalid_length() { + let err = Hash256::from_str("badc0de").expect_err("invalid length"); + let expected = "badc0de"; + match err { + Hash256Error::InvalidLength(ref e) if expected == e => (), + _ => panic!("unexpected error: {:?}", err), + } + } + + fn test_from_str_invalid_hex() { + let err = Hash256::from_str("g00000000000000000000000000000000000000000000000000000000000000e").expect_err("invalid hex"); + let expected = "g00000000000000000000000000000000000000000000000000000000000000e"; + match err { + Hash256Error::InvalidHex(ref e) if expected == e => (), + _ => panic!("unexpected error: {:?}", err), + } + } + } +} diff --git a/src/types/keypair.rs b/src/types/keypair.rs new file mode 100644 index 0000000..fd884a7 --- /dev/null +++ b/src/types/keypair.rs @@ -0,0 +1,190 @@ +use curve25519_dalek::edwards::CompressedEdwardsY; +use ed25519_dalek::{ExpandedSecretKey, PublicKey as Ed25519PublicKey, SecretKey, + SignatureError as Ed25519SignatureError, Signer, Verifier, SECRET_KEY_LENGTH}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::fmt; +use std::str::FromStr; +use thiserror::Error; + +use crate::types::{Address, Signature, SpendPolicy}; + +/// A Sia Public-Private Keypair +/// The purpose of this wrapper type is to limit the functionality of underlying ed25519 types. +/// The inner fields are not public by design. +/// We must not allow the consumer to create an invalid ed25519 Keypair or edit the PublicKey after creation. +/// see +pub struct Keypair { + public: PublicKey, + private: PrivateKey, +} + +#[derive(Debug, Error)] +pub enum KeypairError { + #[error("Keypair::verify: invalid signature {0}")] + VerifySignature(#[from] PublicKeyError), + #[error("Keypair::from_private_bytes: invalid private key {0}")] + InvalidPrivateKey(#[from] Ed25519SignatureError), +} + +impl Signer for Keypair { + /// Sign a message with this keypair's secret key. + fn try_sign(&self, message: &[u8]) -> Result { + let expanded: ExpandedSecretKey = (&self.private.0).into(); + Ok(Signature::from(expanded.sign(message, &self.public.0))) + } +} + +impl Keypair { + pub fn from_private_bytes(bytes: &[u8]) -> Result { + let secret = SecretKey::from_bytes(bytes)?; + let public = PublicKey(Ed25519PublicKey::from(&secret)); + let private = PrivateKey(secret); + Ok(Keypair { public, private }) + } + + pub fn sign(&self, message: &[u8]) -> Signature { Signer::sign(self, message) } + + /// Verify a signature of a message with this keypair's public key. + pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), KeypairError> { + Ok(self.public.verify(message, signature)?) + } + + pub fn public(&self) -> PublicKey { self.public.clone() } + + pub fn private(&self) -> [u8; SECRET_KEY_LENGTH] { self.private.0.to_bytes() } +} + +struct PrivateKey(SecretKey); + +#[derive(Clone, Debug, PartialEq)] +pub struct PublicKey(pub Ed25519PublicKey); + +#[derive(Debug, Error)] +pub enum PublicKeyError { + #[error("invalid public key length: expected 32 byte hex string prefixed with 'ed25519:', found {0}")] + InvalidLength(String), + #[error("public key invalid hex: expected 32 byte hex string, found {0}")] + InvalidHex(String), + #[error("public key invalid: corrupt curve point {0}")] + CorruptPoint(String), + #[error("public key invalid: from_bytes failed {0}")] + ParseBytes(Ed25519SignatureError), + #[error("PublicKey::verify: invalid signature {0}")] + VerifySignature(#[from] Ed25519SignatureError), +} + +impl Verifier for PublicKey { + /// Verify a signature of a message with this keypair's public key. + fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), Ed25519SignatureError> { + self.0.verify(message, &signature.0) + } +} + +impl PublicKey { + pub fn from_bytes(bytes: &[u8]) -> Result { + let public_key = Ed25519PublicKey::from_bytes(bytes) + .map(PublicKey) + .map_err(PublicKeyError::ParseBytes)?; + + match public_key.validate_point() { + true => Ok(public_key), + false => Err(PublicKeyError::CorruptPoint(hex::encode(bytes))), + } + } + + /// Check if public key is a valid point on the Ed25519 curve + pub fn validate_point(&self) -> bool { + // Create a CompressedEdwardsY point from the first 32 bytes + CompressedEdwardsY::from_slice(&self.0.to_bytes()) + .decompress() + .is_some() + } + + pub fn as_bytes(&self) -> &[u8] { self.0.as_bytes() } + + pub fn to_bytes(&self) -> [u8; 32] { self.0.to_bytes() } + + // Method for parsing a hex string without the "ed25519:" prefix + pub fn from_str_no_prefix(hex_str: &str) -> Result { + let mut bytes = [0u8; 32]; + hex::decode_to_slice(hex_str, &mut bytes).map_err(|_| PublicKeyError::InvalidHex(hex_str.to_string()))?; + + let public_key = Self::from_bytes(&bytes)?; + + match public_key.validate_point() { + true => Ok(public_key), + false => Err(PublicKeyError::CorruptPoint(hex::encode(bytes))), + } + } + + /// Generate the default v1 address from the public key + pub fn v1_address(&self) -> Address { SpendPolicy::unlock_condition(vec![self.clone()], 0, 1).address() } + + /// Generate the default v2 address from the public key + pub fn address(&self) -> Address { SpendPolicy::PublicKey(self.clone()).address() } + + /// Verify a signature of a message with this keypair's public key. + pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), PublicKeyError> { + Ok(Verifier::verify(self, message, signature)?) + } +} + +impl FromStr for PublicKey { + type Err = PublicKeyError; + + fn from_str(s: &str) -> Result { + if let Some(hex_str) = s.strip_prefix("ed25519:") { + PublicKey::from_str_no_prefix(hex_str) + } else { + Err(PublicKeyError::InvalidHex(s.to_string())) + } + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PublicKeyVisitor; + + impl<'de> serde::de::Visitor<'de> for PublicKeyVisitor { + type Value = PublicKey; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a string prefixed with 'ed25519:' and followed by a 64-character hex string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + if let Some(hex_str) = value.strip_prefix("ed25519:") { + PublicKey::from_str_no_prefix(hex_str) + .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self)) + } else { + Err(E::invalid_value(serde::de::Unexpected::Str(value), &self)) + } + } + } + + deserializer.deserialize_str(PublicKeyVisitor) + } +} + +impl Serialize for PublicKey { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&format!("{}", self)) + } +} + +impl fmt::Display for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "ed25519:{:02x}", self) } +} + +impl fmt::LowerHex for PublicKey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", hex::encode(self.as_bytes())) } +} diff --git a/src/types/signature.rs b/src/types/signature.rs new file mode 100644 index 0000000..cedc483 --- /dev/null +++ b/src/types/signature.rs @@ -0,0 +1,179 @@ +use crate::types::keypair::{PublicKey, PublicKeyError}; + +use curve25519_dalek::edwards::CompressedEdwardsY; +use derive_more::{From, Into}; +use ed25519_dalek::ed25519::signature::{Error as SignatureCrateError, Signature as SignatureTrait}; +use ed25519_dalek::{Signature as Ed25519Signature, SIGNATURE_LENGTH}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use std::convert::TryFrom; +use std::fmt; +use std::str::FromStr; +use thiserror::Error; + +#[derive(Clone, Debug, PartialEq, From, Into)] +pub struct Signature(pub Ed25519Signature); + +#[derive(Debug, Error)] +pub enum SignatureError { + #[error("Signature::TryFrom<&[u8]>: failed to parse signature from slice {0}")] + ParseSlice(#[from] ed25519_dalek::ed25519::Error), + #[error("Signature::TryFrom<&[u8]>: invalid signature:{0:?}, corrupt R point")] + CorruptRPointSlice(Vec), + #[error("Signature::from_str: invalid signature:{0}, corrupt R point")] + CorruptRPointStr(String), + #[error("Signature::verify: invalid signature: {0}")] + VerifyFailed(#[from] PublicKeyError), +} + +impl Default for Signature { + fn default() -> Self { Signature(Ed25519Signature::try_from([0u8; 64]).expect("00'd signature is valid")) } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{:02x}", self.0) } +} + +impl Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let s = String::deserialize(deserializer)?; + Signature::from_str(&s).map_err(serde::de::Error::custom) + } +} + +// trait bound of Signer for Keypair +impl SignatureTrait for Signature { + fn from_bytes(bytes: &[u8]) -> Result { + // Delegate to the inner type's implementation + Ed25519Signature::from_bytes(bytes).map(Signature) + } +} + +// trait bound of signature_crate::Signature +impl AsRef<[u8]> for Signature { + fn as_ref(&self) -> &[u8] { self.0.as_ref() } +} + +impl TryFrom<&[u8]> for Signature { + type Error = SignatureError; + + fn try_from(bytes: &[u8]) -> Result { + let signature = Ed25519Signature::from_bytes(bytes) + .map(Signature) + .map_err(SignatureError::ParseSlice)?; + + match signature.validate_r_point() { + true => Ok(signature), + false => Err(SignatureError::CorruptRPointSlice(bytes.to_vec())), + } + } +} + +impl TryFrom> for Signature { + type Error = SignatureError; + + fn try_from(bytes: Vec) -> Result { Signature::try_from(bytes.as_slice()) } +} + +impl Signature { + pub fn to_bytes(&self) -> [u8; SIGNATURE_LENGTH] { self.0.to_bytes() } + + /// Check if R value is a valid point on the Ed25519 curve + pub fn validate_r_point(&self) -> bool { + let r_bytes = &self.0.to_bytes()[0..SIGNATURE_LENGTH / 2]; + + // Create a CompressedEdwardsY point from the first 32 bytes + CompressedEdwardsY::from_slice(r_bytes).decompress().is_some() + } + + pub fn verify(&self, message: &[u8], public_key: &PublicKey) -> Result<(), SignatureError> { + Ok(public_key.verify(message, self)?) + } +} + +// impl fmt::LowerHex for Signature { +// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { +// // Delegate to the fmt::LowerHex implementation of the inner Ed25519Signature +// fmt::LowerHex::fmt(&self.0, f) +// } +// } + +impl FromStr for Signature { + type Err = SignatureError; + + fn from_str(value: &str) -> Result { + let signature = Ed25519Signature::from_str(value).map(Signature)?; + + match signature.validate_r_point() { + true => Ok(signature), + false => Err(SignatureError::CorruptRPointStr(value.to_string())), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use serde_json; + + const VALID_STR: &str = "f43380794a6384e3d24d9908143c05dd37aaac8959efb65d986feb70fe289a5e26b84e0ac712af01a2f85f8727da18aae13a599a51fb066d098591e40cb26902"; + const VALID_JSON_STR: &str = r#""f43380794a6384e3d24d9908143c05dd37aaac8959efb65d986feb70fe289a5e26b84e0ac712af01a2f85f8727da18aae13a599a51fb066d098591e40cb26902""#; + + fn valid_signature() -> Signature { Signature::from_str(VALID_STR).unwrap() } + + cross_target_tests! { + fn test_display() { + assert_eq!(valid_signature().to_string(), VALID_STR); + } + + fn test_debug() { + assert_eq!(format!("{:?}", valid_signature()), "Signature(ed25519::Signature(F43380794A6384E3D24D9908143C05DD37AAAC8959EFB65D986FEB70FE289A5E26B84E0AC712AF01A2F85F8727DA18AAE13A599A51FB066D098591E40CB26902))"); + } + + fn test_serialize() { + assert_eq!(&serde_json::to_string(&valid_signature()).unwrap(), VALID_JSON_STR); + } + + fn test_deserialize() { + assert_eq!(serde_json::from_str::(VALID_JSON_STR).unwrap(), valid_signature()); + } + + fn test_invalid_hex() { + let test_case = "g43380794a6384e3d24d9908143c05dd37aaac8959efb65d986feb70fe289a5e26b84e0ac712af01a2f85f8727da18aae13a599a51fb066d098591e40cb26902"; + let err = Signature::from_str(test_case).expect_err("no prefix"); + match err { + SignatureError::ParseSlice(_) => (), + _ => panic!("unexpected error: {:?}", err), + } + } + + fn test_invalid_r_signature() { + let test_case = "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000000"; + let err = Signature::from_str(test_case).expect_err("no prefix"); + match err { + SignatureError::CorruptRPointStr(_) => (), + _ => panic!("unexpected error: {:?}", err), + } + } + + fn test_invalid_length() { + let test_case = "badc0de"; + let err = Signature::from_str(test_case).expect_err("invalid length"); + match err { + SignatureError::ParseSlice(_) => (), + _ => panic!("unexpected error: {:?}", err), + } + } + } +} diff --git a/src/specifier.rs b/src/types/specifier.rs similarity index 100% rename from src/specifier.rs rename to src/types/specifier.rs diff --git a/src/spend_policy.rs b/src/types/spend_policy.rs similarity index 65% rename from src/spend_policy.rs rename to src/types/spend_policy.rs index 4f043af..3e0a8a8 100644 --- a/src/spend_policy.rs +++ b/src/types/spend_policy.rs @@ -1,9 +1,6 @@ use crate::blake2b_internal::{public_key_leaf, sigs_required_leaf, standard_unlock_hash, timelock_leaf, Accumulator}; -use crate::encoding::{Encodable, Encoder, PrefixedH256, PrefixedPublicKey}; -use crate::specifier::Specifier; -use crate::transaction::{Preimage, SatisfiedPolicy}; -use crate::types::{Address, H256}; -use crate::{PublicKey, Signature}; +use crate::encoding::{Encodable, Encoder}; +use crate::types::{Address, Hash256, PublicKey, Specifier}; use nom::bytes::complete::{take_until, take_while, take_while_m_n}; use nom::character::complete::char; use nom::combinator::all_consuming; @@ -15,64 +12,105 @@ use std::str::FromStr; const POLICY_VERSION: u8 = 1u8; +/* +The full representation of the atomic swap contract is as follows: + SpendPolicy::Threshold { + n: 1, + of: vec![ + SpendPolicy::Threshold { + n: 2, + of: vec![ + SpendPolicy::Hash(), + SpendPolicy::PublicKey() + ] + }, + SpendPolicy::Threshold { + n: 2, + of: vec![ + SpendPolicy::After(), + SpendPolicy::PublicKey() + ] + }, + ] + } + +In English, the above specifies that: + - Alice can spend the UTXO if: + - Alice provides the preimage of the SHA256 hash specified in the UTXO (the secret) + - Alice provides a valid signature + - Bob can spend the UTXO if: + - the current time is greater than the specified timestamp + - Bob provides a valid signature + +To lock funds in such a contract, we generate the address(see SpendPolicy::address) of the above SpendPolicy and use this Address in a transaction output. + +The resulting UTXO can then be spent by either Alice or Bob by meeting the conditions specified above. + +It is only neccesary to reveal the path that will be satisfied. The other path will be opacified(see SpendPolicy::opacify) and replaced with SpendPolicy::Opaque(). + +Alice can spend the UTXO by providing a signature, the secret and revealing the relevant path within the full SpendPolicy. + +Alice can construct the following SatisfiedPolicy to spend the UTXO: + +SatisfiedPolicy { + policy: SpendPolicy::Threshold { + n: 1, + of: vec![ + SpendPolicy::Threshold { + n: 2, + of: vec![ + SpendPolicy::Hash(), + SpendPolicy::PublicKey() + ] + }, + SpendPolicy::Opaque(), + ] + }, + signatures: vec![], + preimages: vec![] +} + +Similarly, Bob can spend the UTXO with the following SatisfiedPolicy assuming he waits until the timestamp has passed: + +SatisfiedPolicy { + policy: SpendPolicy::Threshold { + n: 1, + of: vec![ + SpendPolicy::Opaque(), + SpendPolicy::Threshold { + n: 2, + of: vec![ + SpendPolicy::After(), + SpendPolicy::PublicKey() + ] + } + ] + }, + signatures: vec![], + preimages: vec![] +} + +*/ + #[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] +#[serde(tag = "type", content = "policy")] pub enum SpendPolicy { + #[serde(rename = "above")] Above(u64), + #[serde(rename = "after")] After(u64), + #[serde(rename = "pk")] PublicKey(PublicKey), - Hash(H256), + #[serde(rename = "h")] + Hash(Hash256), + #[serde(rename = "thresh")] Threshold { n: u8, of: Vec }, + #[serde(rename = "opaque")] Opaque(Address), + #[serde(rename = "uc")] UnlockConditions(UnlockCondition), // For v1 compatibility } -/// Helper to serialize/deserialize SpendPolicy with prefixed PublicKey and H256 -#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)] -#[serde(tag = "type", content = "policy", rename_all = "camelCase")] -pub enum SpendPolicyHelper { - Above(u64), - After(u64), - Pk(PrefixedPublicKey), - H(PrefixedH256), - Thresh { n: u8, of: Vec }, - Opaque(Address), - Uc(UnlockCondition), // For v1 compatibility -} - -impl From for SpendPolicy { - fn from(helper: SpendPolicyHelper) -> Self { - match helper { - SpendPolicyHelper::Above(height) => SpendPolicy::Above(height), - SpendPolicyHelper::After(time) => SpendPolicy::After(time), - SpendPolicyHelper::Pk(pk) => SpendPolicy::PublicKey(pk.0), - SpendPolicyHelper::H(hash) => SpendPolicy::Hash(hash.0), - SpendPolicyHelper::Thresh { n, of } => SpendPolicy::Threshold { - n, - of: of.into_iter().map(SpendPolicy::from).collect(), - }, - SpendPolicyHelper::Opaque(address) => SpendPolicy::Opaque(address), - SpendPolicyHelper::Uc(uc) => SpendPolicy::UnlockConditions(uc), - } - } -} - -impl From for SpendPolicyHelper { - fn from(policy: SpendPolicy) -> Self { - match policy { - SpendPolicy::Above(height) => SpendPolicyHelper::Above(height), - SpendPolicy::After(time) => SpendPolicyHelper::After(time), - SpendPolicy::PublicKey(pk) => SpendPolicyHelper::Pk(PrefixedPublicKey(pk)), - SpendPolicy::Hash(hash) => SpendPolicyHelper::H(PrefixedH256(hash)), - SpendPolicy::Threshold { n, of } => SpendPolicyHelper::Thresh { - n, - of: of.into_iter().map(SpendPolicyHelper::from).collect(), - }, - SpendPolicy::Opaque(address) => SpendPolicyHelper::Opaque(address), - SpendPolicy::UnlockConditions(uc) => SpendPolicyHelper::Uc(uc), - } - } -} - impl Encodable for SpendPolicy { fn encode(&self, encoder: &mut Encoder) { encoder.write_u8(POLICY_VERSION); @@ -162,99 +200,82 @@ impl SpendPolicy { pub fn public_key(pk: PublicKey) -> Self { SpendPolicy::PublicKey(pk) } - pub fn hash(h: H256) -> Self { SpendPolicy::Hash(h) } + pub fn hash(h: Hash256) -> Self { SpendPolicy::Hash(h) } pub fn threshold(n: u8, of: Vec) -> Self { SpendPolicy::Threshold { n, of } } pub fn opaque(p: &SpendPolicy) -> Self { SpendPolicy::Opaque(p.address()) } + pub fn unlock_condition(pubkeys: Vec, timelock: u64, signatures_required: u64) -> Self { + SpendPolicy::UnlockConditions(UnlockCondition::new(pubkeys, timelock, signatures_required)) + } + pub fn anyone_can_spend() -> Self { SpendPolicy::threshold(0, vec![]) } pub fn opacify(&self) -> Self { SpendPolicy::Opaque(self.address()) } - - pub fn satisfy(&self, data: T) -> Result { data.satisfy(self) } } -pub trait SatisfyPolicy { - fn satisfy(self, policy: &SpendPolicy) -> Result; -} +impl SpendPolicy { + /// Create a HTLC SpendPolicy. + /// Arguments: + /// - success_public_key: the public key that is able to claim the funds immediately by providing + /// the preimage of `secret_hash` + /// - refund_public_key: the public key that is able to claim the funds after `lock_time` + /// - lock_time: the timestamp after which `refund_public_key` can claim the funds + /// - secret_hash: the sha256 hash of the secret + pub fn atomic_swap( + success_public_key: &PublicKey, + refund_public_key: &PublicKey, + lock_time: u64, + secret_hash: &Hash256, + ) -> Self { + let policy_after = SpendPolicy::After(lock_time); + let policy_hash = SpendPolicy::Hash(secret_hash.clone()); + + let policy_success = SpendPolicy::Threshold { + n: 2, + of: vec![SpendPolicy::PublicKey(success_public_key.clone()), policy_hash], + }; -impl SatisfyPolicy for Signature { - fn satisfy(self, policy: &SpendPolicy) -> Result { - match policy { - SpendPolicy::PublicKey(_) | SpendPolicy::UnlockConditions(_) => Ok(SatisfiedPolicy { - policy: policy.clone(), - signatures: vec![self], - preimages: vec![], - }), - _ => Err("Failed to satisfy. Policy is not PublicKey or UnlockConditions".to_string()), - } - } -} + let policy_refund = SpendPolicy::Threshold { + n: 2, + of: vec![SpendPolicy::PublicKey(refund_public_key.clone()), policy_after], + }; -impl SatisfyPolicy for Preimage { - fn satisfy(self, policy: &SpendPolicy) -> Result { - match policy { - SpendPolicy::Hash(_) => Ok(SatisfiedPolicy { - policy: policy.clone(), - signatures: vec![], - preimages: vec![self], - }), - _ => Err("Failed to satisfy. Policy is not Hash".to_string()), + SpendPolicy::Threshold { + n: 1, + of: vec![policy_success, policy_refund], } } -} -impl SatisfyPolicy for () { - fn satisfy(self, policy: &SpendPolicy) -> Result { - match policy { - SpendPolicy::Above(_) | SpendPolicy::After(_) | SpendPolicy::Opaque(_) => Ok(SatisfiedPolicy { - policy: policy.clone(), - signatures: vec![], - preimages: vec![], - }), - _ => Err("Failed to satisfy. Policy is not Above, After or Opaque".to_string()), + pub fn atomic_swap_success( + success_public_key: &PublicKey, + refund_public_key: &PublicKey, + lock_time: u64, + secret_hash: &Hash256, + ) -> Self { + match Self::atomic_swap(success_public_key, refund_public_key, lock_time, secret_hash) { + SpendPolicy::Threshold { n, mut of } => { + of[1] = of[1].opacify(); + SpendPolicy::Threshold { n, of } + }, + _ => unreachable!(), } } -} - -pub fn spend_policy_atomic_swap(alice: PublicKey, bob: PublicKey, lock_time: u64, hash: H256) -> SpendPolicy { - let policy_after = SpendPolicy::After(lock_time); - let policy_hash = SpendPolicy::Hash(hash); - - let policy_success = SpendPolicy::Threshold { - n: 2, - of: vec![SpendPolicy::PublicKey(alice), policy_hash], - }; - - let policy_refund = SpendPolicy::Threshold { - n: 2, - of: vec![SpendPolicy::PublicKey(bob), policy_after], - }; - - SpendPolicy::Threshold { - n: 1, - of: vec![policy_success, policy_refund], - } -} - -pub fn spend_policy_atomic_swap_success(alice: PublicKey, bob: PublicKey, lock_time: u64, hash: H256) -> SpendPolicy { - match spend_policy_atomic_swap(alice, bob, lock_time, hash) { - SpendPolicy::Threshold { n, mut of } => { - of[1] = of[1].opacify(); - SpendPolicy::Threshold { n, of } - }, - _ => unreachable!(), - } -} -pub fn spend_policy_atomic_swap_refund(alice: PublicKey, bob: PublicKey, lock_time: u64, hash: H256) -> SpendPolicy { - match spend_policy_atomic_swap(alice, bob, lock_time, hash) { - SpendPolicy::Threshold { n, mut of } => { - of[0] = of[0].opacify(); - SpendPolicy::Threshold { n, of } - }, - _ => unreachable!(), + pub fn atomic_swap_refund( + success_public_key: &PublicKey, + refund_public_key: &PublicKey, + lock_time: u64, + secret_hash: &Hash256, + ) -> Self { + match Self::atomic_swap(success_public_key, refund_public_key, lock_time, secret_hash) { + SpendPolicy::Threshold { n, mut of } => { + of[0] = of[0].opacify(); + SpendPolicy::Threshold { n, of } + }, + _ => unreachable!(), + } } } @@ -422,7 +443,7 @@ impl UnlockCondition { } } - pub fn unlock_hash(&self) -> H256 { + pub fn unlock_hash(&self) -> Hash256 { // almost all UnlockConditions are standard, so optimize for that case if let UnlockKey::Ed25519(public_key) = &self.unlock_keys[0] { if self.timelock == 0 && self.unlock_keys.len() == 1 && self.signatures_required == 1 { diff --git a/src/transaction.rs b/src/types/transaction.rs similarity index 68% rename from src/transaction.rs rename to src/types/transaction.rs index b480291..f620546 100644 --- a/src/transaction.rs +++ b/src/types/transaction.rs @@ -1,28 +1,59 @@ -use crate::encoding::{Encodable, Encoder, HexArray64, PrefixedH256, PrefixedPublicKey, PrefixedSignature, ScoidH256}; -use crate::spend_policy::{SpendPolicy, SpendPolicyHelper, UnlockCondition, UnlockKey}; -use crate::types::{Address, ChainIndex, H256}; -use crate::{Keypair, PublicKey, Signature}; +use crate::encoding::{Encodable, Encoder}; +use crate::types::{Address, ChainIndex, Hash256, Keypair, PublicKey, Signature, SpendPolicy, UnlockCondition, + UnlockKey}; +use crate::utils::deserialize_null_as_empty_vec; use base64::{engine::general_purpose::STANDARD as base64, Engine as _}; +use derive_more::{Add, AddAssign, Deref, Display, Div, DivAssign, From, Into, Mul, MulAssign, Sub, SubAssign, Sum}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde_json::Value; -use serde_with::{serde_as, FromInto}; +use std::convert::{TryFrom, TryInto}; use std::fmt; -use std::ops::Deref; use std::str::FromStr; +use thiserror::Error; const V2_REPLAY_PREFIX: u8 = 2; -#[derive(Copy, Clone, Debug, Default, PartialEq)] +/// A currency amount in the Sia network represented in Hastings, the smallest unit of currency. +/// 1 SC = 10^24 Hastings +/// use to_string_hastings() or to_string_siacoin() to display the value.\ +// TODO Alright impl Add, Sub, PartialOrd, etc +#[derive( + Copy, + Clone, + Debug, + Deref, + Add, + Sub, + Mul, + Div, + AddAssign, + SubAssign, + MulAssign, + DivAssign, + PartialEq, + Eq, + PartialOrd, + Ord, + Display, + Default, + From, + Into, + Sum, +)] pub struct Currency(pub u128); -impl Deref for Currency { - type Target = u128; +impl Currency { + pub const ZERO: Currency = Currency(0); - fn deref(&self) -> &Self::Target { &self.0 } -} + pub const COIN: Currency = Currency(1000000000000000000000000); -impl Currency { - const ZERO: Currency = Currency(0); + /// The minimum amount of currency for a transaction output + // FIXME this is a placeholder value until testing is complete + pub const DUST: Currency = Currency(1); + + /// A default fee amount for transactions + /// FIXME This is a placeholder value until testing is complete + pub const DEFAULT_FEE: Currency = Currency(10000000000000000000); } // TODO does this also need to be able to deserialize from an integer? @@ -66,14 +97,6 @@ impl From for Currency { fn from(value: u64) -> Self { Currency(value.into()) } } -impl From for Currency { - fn from(value: i32) -> Self { Currency(value as u128) } -} - -impl From for Currency { - fn from(value: u128) -> Self { Currency(value) } -} - // Currency remains the same data structure between V1 and V2 however the encoding changes #[derive(Clone, Debug)] pub enum CurrencyVersion<'a> { @@ -105,15 +128,87 @@ impl<'a> Encodable for CurrencyVersion<'a> { } } -pub type Preimage = Vec; +/// Preimage is a 32-byte array representing the preimage of a hash used in Sia's SpendPolicy::Hash +/// Used to allow HLTC-style hashlock contracts in Sia +#[derive(Clone, Debug, Default, PartialEq, From, Into)] +pub struct Preimage(pub [u8; 32]); + +impl Serialize for Preimage { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + // Use hex::encode to convert the byte array to a lowercase hex string + let hex_string = hex::encode(self.0); + serializer.serialize_str(&hex_string) + } +} + +impl<'de> Deserialize<'de> for Preimage { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct PreimageVisitor; + + impl<'de> serde::de::Visitor<'de> for PreimageVisitor { + type Value = Preimage; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a 32 byte hex string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + // Ensure the length is correct for a 32 byte hex string (64 hex characters) + if value.len() != 64 { + return Err(E::invalid_length(value.len(), &self)); + } + + // Decode the hex string into a byte array + let mut bytes = [0u8; 32]; + hex::decode_to_slice(value, &mut bytes) + .map_err(|_| E::invalid_value(serde::de::Unexpected::Str(value), &self))?; + + Ok(Preimage(bytes)) + } + } + + deserializer.deserialize_str(PreimageVisitor) + } +} + +#[derive(Debug, Error)] +pub enum PreimageError { + #[error("PreimageError: failed to convert from slice invalid length: {0}")] + InvalidSliceLength(usize), +} + +impl From for Vec { + fn from(preimage: Preimage) -> Self { preimage.0.to_vec() } +} + +impl TryFrom<&[u8]> for Preimage { + type Error = PreimageError; + + fn try_from(slice: &[u8]) -> Result { + let slice_len = slice.len(); + if slice_len == 32 { + let mut array = [0u8; 32]; + array.copy_from_slice(slice); + Ok(Preimage(array)) + } else { + Err(PreimageError::InvalidSliceLength(slice_len)) + } + } +} -#[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(deny_unknown_fields)] pub struct SatisfiedPolicy { - #[serde_as(as = "FromInto")] pub policy: SpendPolicy, - #[serde_as(as = "Vec>")] #[serde(default, skip_serializing_if = "Vec::is_empty")] pub signatures: Vec, #[serde(default, skip_serializing_if = "Vec::is_empty")] @@ -144,7 +239,7 @@ impl Encodable for SatisfiedPolicy { }, SpendPolicy::Hash(_) => { if *prei < sp.preimages.len() { - encoder.write_len_prefixed_bytes(&sp.preimages[*prei]); + encoder.write_slice(&sp.preimages[*prei].0); *prei += 1; } else { // Sia Go code panics here but our code assumes encoding will always be successful @@ -160,7 +255,7 @@ impl Encodable for SatisfiedPolicy { SpendPolicy::UnlockConditions(uc) => { for unlock_key in &uc.unlock_keys { if let UnlockKey::Ed25519(public_key) = unlock_key { - rec(&SpendPolicy::PublicKey(*public_key), encoder, sigi, prei, sp); + rec(&SpendPolicy::PublicKey(public_key.clone()), encoder, sigi, prei, sp); } // else FIXME consider when this is possible, is it always developer error or could it be forced maliciously? } @@ -173,33 +268,25 @@ impl Encodable for SatisfiedPolicy { } } -#[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct StateElement { - #[serde_as(as = "FromInto")] - pub id: H256, pub leaf_index: u64, - #[serde(default)] - #[serde_as(as = "Option>>")] - pub merkle_proof: Option>, + #[serde(deserialize_with = "deserialize_null_as_empty_vec", default)] + pub merkle_proof: Vec, } +// FIXME Alright requires new unit tests and corresponding rust_port_test.go tests +// merkle_proof was previously Option> because Walletd can return null for this field +// Test unintialized slice (ie, null) vs empty slice - do they encode the same? +// the following encoding assumes that they do encode the same impl Encodable for StateElement { fn encode(&self, encoder: &mut Encoder) { - self.id.encode(encoder); encoder.write_u64(self.leaf_index); + encoder.write_u64(self.merkle_proof.len() as u64); - match &self.merkle_proof { - Some(proof) => { - encoder.write_u64(proof.len() as u64); - for p in proof { - p.encode(encoder); - } - }, - None => { - encoder.write_u64(0u64); - }, + for proof in &self.merkle_proof { + proof.encode(encoder); } } } @@ -207,7 +294,8 @@ impl Encodable for StateElement { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiafundElement { - #[serde(flatten)] + #[serde(rename = "ID")] + pub id: SiafundOutputId, pub state_element: StateElement, pub siafund_output: SiafundOutput, pub claim_start: Currency, @@ -221,10 +309,15 @@ impl Encodable for SiafundElement { } } +/// As per, Sia Core a "SiacoinElement is a record of a SiacoinOutput within the state accumulator." +/// This type is effectively a "UTXO" in Bitcoin terms. +/// A SiacoinElement can be combined with a SatisfiedPolicy to create a SiacoinInputV2. +/// Ported from Sia Core: +/// #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct SiacoinElement { - #[serde(flatten)] + pub id: SiacoinOutputId, pub state_element: StateElement, pub siacoin_output: SiacoinOutput, pub maturity_height: u64, @@ -233,6 +326,7 @@ pub struct SiacoinElement { impl Encodable for SiacoinElement { fn encode(&self, encoder: &mut Encoder) { self.state_element.encode(encoder); + self.id.encode(encoder); SiacoinOutputVersion::V2(&self.siacoin_output).encode(encoder); encoder.write_u64(self.maturity_height); } @@ -255,19 +349,17 @@ impl Encodable for SiafundInputV2 { } // https://github.com/SiaFoundation/core/blob/6c19657baf738c6b730625288e9b5413f77aa659/types/types.go#L197-L198 -#[serde_as] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct SiacoinInputV1 { #[serde(rename = "parentID")] - #[serde_as(as = "FromInto")] - pub parent_id: H256, + pub parent_id: SiacoinOutputId, #[serde(rename = "unlockConditions")] pub unlock_condition: UnlockCondition, } impl Encodable for SiacoinInputV1 { fn encode(&self, encoder: &mut Encoder) { - self.parent_id.encode(encoder); + self.parent_id.0.encode(encoder); self.unlock_condition.encode(encoder); } } @@ -336,12 +428,69 @@ impl<'a> Encodable for SiacoinOutputVersion<'a> { } } +/// A Sia transaction id aka "txid" +// This could be a newtype like SiacoinOutputId with custom serde, but we have no use for this beyond +// making SiacoinOutputId::new more explicit. +pub type TransactionId = Hash256; + +#[derive(Clone, Debug, PartialEq, From, Into, Deserialize, Serialize, Display, Default)] +#[serde(transparent)] +pub struct SiacoinOutputId(pub Hash256); + +impl Encodable for SiacoinOutputId { + fn encode(&self, encoder: &mut Encoder) { self.0.encode(encoder) } +} + +impl SiacoinOutputId { + pub fn new(txid: TransactionId, index: u32) -> Self { + let mut encoder = Encoder::default(); + encoder.write_distinguisher("id/siacoinoutput"); + txid.encode(&mut encoder); + encoder.write_u64(index as u64); + SiacoinOutputId(encoder.hash()) + } +} + +#[derive(Clone, Debug, PartialEq, From, Into, Deserialize, Serialize, Display)] +#[serde(transparent)] +pub struct SiafundOutputId(pub Hash256); + +impl Encodable for SiafundOutputId { + fn encode(&self, encoder: &mut Encoder) { self.0.encode(encoder) } +} + +#[derive(Clone, Debug, Default, PartialEq, From, Into, Deserialize, Serialize, Display)] +#[serde(transparent)] +pub struct FileContractID(pub Hash256); + +impl Encodable for FileContractID { + fn encode(&self, encoder: &mut Encoder) { self.0.encode(encoder) } +} + #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] pub struct SiacoinOutput { pub value: Currency, pub address: Address, } +impl From<(Currency, Address)> for SiacoinOutput { + fn from(tuple: (Currency, Address)) -> Self { + SiacoinOutput { + value: tuple.0, + address: tuple.1, + } + } +} + +impl From<(Address, Currency)> for SiacoinOutput { + fn from(tuple: (Address, Currency)) -> Self { + SiacoinOutput { + address: tuple.0, + value: tuple.1, + } + } +} + #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] #[serde(default)] @@ -359,13 +508,11 @@ pub struct CoveredFields { pub signatures: Vec, } -#[serde_as] #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct TransactionSignature { - #[serde_as(as = "FromInto")] #[serde(rename = "parentID")] - pub parent_id: H256, + pub parent_id: Hash256, #[serde(default)] pub public_key_index: u64, #[serde(default)] @@ -408,13 +555,13 @@ impl<'de> Deserialize<'de> for V1Signature { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct FileContract { pub filesize: u64, - pub file_merkle_root: H256, + pub file_merkle_root: Hash256, pub window_start: u64, pub window_end: u64, pub payout: Currency, pub valid_proof_outputs: Vec, pub missed_proof_outputs: Vec, - pub unlock_hash: H256, + pub unlock_hash: Hash256, pub revision_number: u64, } @@ -438,35 +585,30 @@ impl Encodable for FileContract { } } -#[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct V2FileContract { + pub capacity: u64, pub filesize: u64, - #[serde_as(as = "FromInto")] - pub file_merkle_root: H256, + pub file_merkle_root: Hash256, pub proof_height: u64, pub expiration_height: u64, pub renter_output: SiacoinOutput, pub host_output: SiacoinOutput, pub missed_host_value: Currency, pub total_collateral: Currency, - #[serde_as(as = "FromInto")] pub renter_public_key: PublicKey, - #[serde_as(as = "FromInto")] pub host_public_key: PublicKey, pub revision_number: u64, - #[serde_as(as = "FromInto")] pub renter_signature: Signature, - #[serde_as(as = "FromInto")] pub host_signature: Signature, } impl V2FileContract { pub fn with_nil_sigs(&self) -> V2FileContract { V2FileContract { - renter_signature: Signature::from_bytes(&[0u8; 64]).expect("Err unreachable"), - host_signature: Signature::from_bytes(&[0u8; 64]).expect("Err unreachable"), + renter_signature: Signature::default(), + host_signature: Signature::default(), ..self.clone() } } @@ -474,6 +616,7 @@ impl V2FileContract { impl Encodable for V2FileContract { fn encode(&self, encoder: &mut Encoder) { + encoder.write_u64(self.capacity); encoder.write_u64(self.filesize); self.file_merkle_root.encode(encoder); encoder.write_u64(self.proof_height); @@ -492,7 +635,7 @@ impl Encodable for V2FileContract { #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct V2FileContractElement { - #[serde(flatten)] + pub id: FileContractID, pub state_element: StateElement, pub v2_file_contract: V2FileContract, } @@ -500,6 +643,7 @@ pub struct V2FileContractElement { impl Encodable for V2FileContractElement { fn encode(&self, encoder: &mut Encoder) { self.state_element.encode(encoder); + self.id.encode(encoder); self.v2_file_contract.encode(encoder); } } @@ -543,12 +687,30 @@ impl Encodable for Attestation { self.signature.encode(encoder); } } -#[serde_as] + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +#[serde(try_from = "String", into = "String")] +pub struct Leaf(#[serde(with = "hex")] pub [u8; 64]); + +impl TryFrom for Leaf { + type Error = hex::FromHexError; + + fn try_from(value: String) -> Result { + let bytes = hex::decode(value)?; + let array = bytes.try_into().map_err(|_| hex::FromHexError::InvalidStringLength)?; + Ok(Leaf(array)) + } +} + +impl From for String { + fn from(value: Leaf) -> Self { hex::encode(value.0) } +} + #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct StorageProof { pub parent_id: FileContractID, - pub leaf: HexArray64, - pub proof: Vec, + pub leaf: Leaf, + pub proof: Vec, } impl Encodable for StorageProof { @@ -562,9 +724,6 @@ impl Encodable for StorageProof { } } -type SiafundOutputID = H256; -type FileContractID = H256; - #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct FileContractRevision { pub parent_id: FileContractID, @@ -596,7 +755,7 @@ impl Encodable for FileContractRevision { #[derive(Clone, Debug, Deserialize, PartialEq, Serialize)] pub struct SiafundInputV1 { - pub parent_id: SiafundOutputID, + pub parent_id: SiafundOutputId, pub unlock_condition: UnlockCondition, pub claim_address: Address, } @@ -722,7 +881,6 @@ impl Encodable for V2FileContractFinalization { fn encode(&self, encoder: &mut Encoder) { self.0.encode(encoder); } } -#[serde_as] #[derive(Clone, Debug, Deserialize, Serialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct V2FileContractRenewal { @@ -730,23 +888,17 @@ pub struct V2FileContractRenewal { new_contract: V2FileContract, renter_rollover: Currency, host_rollover: Currency, - #[serde_as(as = "FromInto")] renter_signature: Signature, - #[serde_as(as = "FromInto")] host_signature: Signature, } impl V2FileContractRenewal { pub fn with_nil_sigs(&self) -> V2FileContractRenewal { - debug_assert!( - Signature::from_bytes(&[0u8; 64]).is_ok(), - "nil signature is valid and cannot return Err" - ); V2FileContractRenewal { final_revision: self.final_revision.with_nil_sigs(), new_contract: self.new_contract.with_nil_sigs(), - renter_signature: Signature::from_bytes(&[0u8; 64]).expect("Err unreachable"), - host_signature: Signature::from_bytes(&[0u8; 64]).expect("Err unreachable"), + renter_signature: Signature::default(), + host_signature: Signature::default(), ..self.clone() } } @@ -767,8 +919,8 @@ impl Encodable for V2FileContractRenewal { #[serde(rename_all = "camelCase")] pub struct V2StorageProof { proof_index: ChainIndexElement, - leaf: HexArray64, - proof: Vec, + leaf: Leaf, + proof: Vec, } impl V2StorageProof { @@ -776,7 +928,7 @@ impl V2StorageProof { V2StorageProof { proof_index: ChainIndexElement { state_element: StateElement { - merkle_proof: None, + merkle_proof: vec![], ..self.proof_index.state_element.clone() }, ..self.proof_index.clone() @@ -824,13 +976,13 @@ pub struct FileContractElementV1 { #[derive(Clone, Debug, Deserialize, Serialize)] pub struct FileContractV1 { pub filesize: u64, - pub file_merkle_root: H256, + pub file_merkle_root: Hash256, pub window_start: u64, pub window_end: u64, pub payout: Currency, pub valid_proof_outputs: Vec, pub missed_proof_outputs: Vec, - pub unlock_hash: H256, + pub unlock_hash: Hash256, pub revision_number: u64, } @@ -847,7 +999,8 @@ impl Encodable for V1ArbitraryData { } } /* -While implementing this, we faced two options. +While implementing +, we faced two options. 1.) Treat every field as an Option<> 2.) Always initialize every empty field as a Vec<> @@ -870,7 +1023,7 @@ pub struct V1Transaction { } impl V1Transaction { - pub fn txid(&self) -> H256 { Encoder::encode_and_hash(&V1TransactionSansSigs(self.clone())) } + pub fn txid(&self) -> Hash256 { Encoder::encode_and_hash(&V1TransactionSansSigs(self.clone())) } } impl Encodable for SiafundInputV1 { @@ -881,15 +1034,9 @@ impl Encodable for SiafundInputV1 { } } // TODO possible this can just hold a ref to V1Transaction like CurrencyVersion -#[derive(Clone, Debug, Default, Deserialize, Serialize)] +#[derive(Clone, Debug, Default, Deref, Deserialize, Serialize)] pub struct V1TransactionSansSigs(V1Transaction); -impl Deref for V1TransactionSansSigs { - type Target = V1Transaction; - - fn deref(&self) -> &Self::Target { &self.0 } -} - impl Encodable for V1TransactionSansSigs { fn encode(&self, encoder: &mut Encoder) { encoder.write_len_prefixed_vec(&self.siacoin_inputs); @@ -921,7 +1068,7 @@ impl Encodable for V1TransactionSansSigs { } #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)] -#[serde(default, deny_unknown_fields, rename_all = "camelCase")] +#[serde(default, rename_all = "camelCase")] pub struct V2Transaction { #[serde(skip_serializing_if = "Vec::is_empty")] pub siacoin_inputs: Vec, @@ -939,8 +1086,8 @@ pub struct V2Transaction { pub file_contract_resolutions: Vec, // TODO needs Encodable trait #[serde(skip_serializing_if = "Vec::is_empty")] pub attestations: Vec, - #[serde(skip_serializing_if = "Vec::is_empty")] - pub arbitrary_data: Vec, + #[serde(skip_serializing_if = "ArbitraryData::is_empty")] + pub arbitrary_data: ArbitraryData, #[serde(skip_serializing_if = "Option::is_none")] pub new_foundation_address: Option
, pub miner_fee: Currency, @@ -956,7 +1103,7 @@ impl V2Transaction { } } - pub fn input_sig_hash(&self) -> H256 { + pub fn input_sig_hash(&self) -> Hash256 { let mut encoder = Encoder::default(); encoder.write_distinguisher("sig/input"); encoder.write_u8(V2_REPLAY_PREFIX); @@ -964,7 +1111,7 @@ impl V2Transaction { encoder.hash() } - pub fn txid(&self) -> H256 { + pub fn txid(&self) -> TransactionId { let mut encoder = Encoder::default(); encoder.write_distinguisher("id/transaction"); self.encode(&mut encoder); @@ -977,7 +1124,7 @@ impl Encodable for V2Transaction { fn encode(&self, encoder: &mut Encoder) { encoder.write_u64(self.siacoin_inputs.len() as u64); for si in &self.siacoin_inputs { - si.parent.state_element.id.encode(encoder); + si.parent.id.encode(encoder); } encoder.write_u64(self.siacoin_outputs.len() as u64); @@ -987,7 +1134,7 @@ impl Encodable for V2Transaction { encoder.write_u64(self.siafund_inputs.len() as u64); for si in &self.siafund_inputs { - si.parent.state_element.id.encode(encoder); + si.parent.id.encode(encoder); } encoder.write_u64(self.siafund_outputs.len() as u64); @@ -1002,13 +1149,13 @@ impl Encodable for V2Transaction { encoder.write_u64(self.file_contract_revisions.len() as u64); for fcr in &self.file_contract_revisions { - fcr.parent.state_element.id.encode(encoder); + fcr.parent.id.encode(encoder); fcr.revision.with_nil_sigs().encode(encoder); } encoder.write_u64(self.file_contract_resolutions.len() as u64); for fcr in &self.file_contract_resolutions { - fcr.parent.state_element.id.encode(encoder); + fcr.parent.id.encode(encoder); fcr.with_nil_sigs().encode(encoder); // FIXME .encode() leads to unimplemented!() } @@ -1018,7 +1165,7 @@ impl Encodable for V2Transaction { att.encode(encoder); } - encoder.write_len_prefixed_bytes(&self.arbitrary_data); + self.arbitrary_data.encode(encoder); encoder.write_bool(self.new_foundation_address.is_some()); match &self.new_foundation_address { @@ -1029,38 +1176,87 @@ impl Encodable for V2Transaction { } } -pub struct V2TransactionBuilder { - siacoin_inputs: Vec, - siacoin_outputs: Vec, - siafund_inputs: Vec, - siafund_outputs: Vec, - file_contracts: Vec, - file_contract_revisions: Vec, - file_contract_resolutions: Vec, - attestations: Vec, - arbitrary_data: Vec, - new_foundation_address: Option
, - miner_fee: Currency, +/// FeePolicy is data optionally included in V2TransactionBuilder to allow easier fee calculation. +/// Sia fee calculation can be complex in comparison to a typical UTXO protocol because the fee paid +/// to the miner is not simply the sum of the inputs minus the sum of the outputs. Instead, the +/// miner fee is a distinct field within the transaction, `miner_fee`. This `miner_fee` field is part +/// of signature calculation. As a result, you can build a transaction, produce signatures and preimages +/// for the inputs only to find out that the miner_fee hastings/byte rate is lower than expected. +/// Therefore a precise hastings/byte calculation requires correctly estimating the size of all +/// satisfied inputs prior to producing signatures. +#[derive(Clone, Debug)] +pub enum FeePolicy { + HastingsPerByte(Currency), + HastingsFixed(Currency), } -impl V2TransactionBuilder { - /** - * "weight" is the size of the transaction in bytes. This can be used to estimate miner fees. - * The recommended method for calculating a suitable fee is to multiply the response of `/txpool/fee` API endpoint - * and the weight to get the fee in hastings. - */ - pub fn weight(&self) -> u64 { - let mut encoder = Encoder::default(); - self.encode(&mut encoder); - encoder.buffer.len() as u64 +#[derive(Clone, Debug, Default, PartialEq, From, Into)] +pub struct ArbitraryData(pub Vec); + +impl ArbitraryData { + pub fn is_empty(&self) -> bool { self.0.is_empty() } +} + +impl Encodable for ArbitraryData { + fn encode(&self, encoder: &mut Encoder) { encoder.write_len_prefixed_bytes(&self.0); } +} + +impl Serialize for ArbitraryData { + fn serialize(&self, serializer: S) -> Result { + serializer.collect_str(&base64.encode(&self.0)) } } +impl<'de> Deserialize<'de> for ArbitraryData { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + struct ArbitraryDataVisitor; + + impl<'de> serde::de::Visitor<'de> for ArbitraryDataVisitor { + type Value = ArbitraryData; + + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { + formatter.write_str("a base64 encoded string") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + let decoded = base64.decode(value).map_err(serde::de::Error::custom)?; + Ok(ArbitraryData(decoded)) + } + } + + deserializer.deserialize_str(ArbitraryDataVisitor) + } +} + +#[derive(Clone, Debug)] +pub struct V2TransactionBuilder { + pub siacoin_inputs: Vec, + pub siacoin_outputs: Vec, + pub siafund_inputs: Vec, + pub siafund_outputs: Vec, + pub file_contracts: Vec, + pub file_contract_revisions: Vec, + pub file_contract_resolutions: Vec, + pub attestations: Vec, + pub arbitrary_data: ArbitraryData, + pub new_foundation_address: Option
, + pub miner_fee: Currency, + // fee_policy is not part Sia consensus and it not encoded into any resulting transaction. + // fee_policy has no effect unless a helper like `ApiClientHelpers::fund_tx_single_source` utilizes it. + pub fee_policy: Option, +} + impl Encodable for V2TransactionBuilder { fn encode(&self, encoder: &mut Encoder) { encoder.write_u64(self.siacoin_inputs.len() as u64); for si in &self.siacoin_inputs { - si.parent.state_element.id.encode(encoder); + si.parent.id.encode(encoder); } encoder.write_u64(self.siacoin_outputs.len() as u64); @@ -1070,7 +1266,7 @@ impl Encodable for V2TransactionBuilder { encoder.write_u64(self.siafund_inputs.len() as u64); for si in &self.siafund_inputs { - si.parent.state_element.id.encode(encoder); + si.parent.id.encode(encoder); } encoder.write_u64(self.siafund_outputs.len() as u64); @@ -1085,13 +1281,13 @@ impl Encodable for V2TransactionBuilder { encoder.write_u64(self.file_contract_revisions.len() as u64); for fcr in &self.file_contract_revisions { - fcr.parent.state_element.id.encode(encoder); + fcr.parent.id.encode(encoder); fcr.revision.with_nil_sigs().encode(encoder); } encoder.write_u64(self.file_contract_resolutions.len() as u64); for fcr in &self.file_contract_resolutions { - fcr.parent.state_element.id.encode(encoder); + fcr.parent.id.encode(encoder); fcr.with_nil_sigs().encode(encoder); // FIXME .encode() leads to unimplemented!() } @@ -1101,7 +1297,7 @@ impl Encodable for V2TransactionBuilder { att.encode(encoder); } - encoder.write_len_prefixed_bytes(&self.arbitrary_data); + self.arbitrary_data.encode(encoder); encoder.write_bool(self.new_foundation_address.is_some()); match &self.new_foundation_address { @@ -1112,6 +1308,14 @@ impl Encodable for V2TransactionBuilder { } } +#[derive(Debug, Error)] +pub enum V2TransactionBuilderError { + #[error("V2TransactionBuilder::satisfy_atomic_swap_success: provided index: {index} is out of bounds for inputs of length: {len}")] + SatisfySuccessIndexOutOfBounds { len: usize, index: u32 }, + #[error("V2TransactionBuilder::satisfy_atomic_swap_refund: provided index: {index} is out of bounds for inputs of length: {len}")] + SatisfyRefundIndexOutOfBounds { len: usize, index: u32 }, +} + impl V2TransactionBuilder { pub fn new() -> Self { Self { @@ -1123,70 +1327,86 @@ impl V2TransactionBuilder { file_contract_revisions: Vec::new(), file_contract_resolutions: Vec::new(), attestations: Vec::new(), - arbitrary_data: Vec::new(), + arbitrary_data: ArbitraryData::default(), new_foundation_address: None, miner_fee: Currency::ZERO, + fee_policy: None, } } - pub fn siacoin_inputs(mut self, inputs: Vec) -> Self { + pub fn siacoin_inputs(&mut self, inputs: Vec) -> &mut Self { self.siacoin_inputs = inputs; self } - pub fn siacoin_outputs(mut self, outputs: Vec) -> Self { + pub fn siacoin_outputs(&mut self, outputs: Vec) -> &mut Self { self.siacoin_outputs = outputs; self } - pub fn siafund_inputs(mut self, inputs: Vec) -> Self { + pub fn siafund_inputs(&mut self, inputs: Vec) -> &mut Self { self.siafund_inputs = inputs; self } - pub fn siafund_outputs(mut self, outputs: Vec) -> Self { + pub fn siafund_outputs(&mut self, outputs: Vec) -> &mut Self { self.siafund_outputs = outputs; self } - pub fn file_contracts(mut self, contracts: Vec) -> Self { + pub fn file_contracts(&mut self, contracts: Vec) -> &mut Self { self.file_contracts = contracts; self } - pub fn file_contract_revisions(mut self, revisions: Vec) -> Self { + pub fn file_contract_revisions(&mut self, revisions: Vec) -> &mut Self { self.file_contract_revisions = revisions; self } - pub fn file_contract_resolutions(mut self, resolutions: Vec) -> Self { + pub fn file_contract_resolutions(&mut self, resolutions: Vec) -> &mut Self { self.file_contract_resolutions = resolutions; self } - pub fn attestations(mut self, attestations: Vec) -> Self { + pub fn attestations(&mut self, attestations: Vec) -> &mut Self { self.attestations = attestations; self } - pub fn arbitrary_data(mut self, data: Vec) -> Self { + pub fn arbitrary_data(&mut self, data: ArbitraryData) -> &mut Self { self.arbitrary_data = data; self } - pub fn new_foundation_address(mut self, address: Address) -> Self { + pub fn new_foundation_address(&mut self, address: Address) -> &mut Self { self.new_foundation_address = Some(address); self } - pub fn miner_fee(mut self, fee: Currency) -> Self { + pub fn miner_fee(&mut self, fee: Currency) -> &mut Self { self.miner_fee = fee; self } - // input is a special case becuase we cannot generate signatures until after fully constructing the transaction - // only the parent field is utilized while encoding the transaction to calculate the signature hash - pub fn add_siacoin_input(mut self, parent: SiacoinElement, policy: SpendPolicy) -> Self { + /** + * "weight" is the size of the transaction in bytes. This can be used to estimate miner fees. + * The recommended method for calculating a suitable fee is to multiply the response of + * `/txpool/fee` API endpoint and the weight to get the fee in hastings. + */ + pub fn weight(&self) -> u64 { + let mut encoder = Encoder::default(); + self.encode(&mut encoder); + encoder.buffer.len() as u64 + } + + /* Input is a special case becuase we cannot generate signatures until after fully constructing + the transaction. Only the parent field is utilized while encoding the transaction to + calculate the signature hash. + Policy is included here to give any signing function or method a schema for producing a + signature for the input. Do not use this method if you are manually creating SatisfiedPolicys. + Use siacoin_inputs() to add fully formed inputs instead. */ + pub fn add_siacoin_input(&mut self, parent: SiacoinElement, policy: SpendPolicy) -> &mut Self { self.siacoin_inputs.push(SiacoinInputV2 { parent, satisfied_policy: SatisfiedPolicy { @@ -1198,12 +1418,12 @@ impl V2TransactionBuilder { self } - pub fn add_siacoin_output(mut self, output: SiacoinOutput) -> Self { + pub fn add_siacoin_output(&mut self, output: SiacoinOutput) -> &mut Self { self.siacoin_outputs.push(output); self } - pub fn input_sig_hash(&self) -> H256 { + pub fn input_sig_hash(&self) -> Hash256 { let mut encoder = Encoder::default(); encoder.write_distinguisher("sig/input"); encoder.write_u8(V2_REPLAY_PREFIX); @@ -1213,18 +1433,20 @@ impl V2TransactionBuilder { // Sign all PublicKey or UnlockConditions policies with the provided keypairs // Incapable of handling threshold policies - pub fn sign_simple(mut self, keypairs: Vec<&Keypair>) -> Result { + pub fn sign_simple(&mut self, keypairs: Vec<&Keypair>) -> &mut Self { let sig_hash = self.input_sig_hash(); for keypair in keypairs { let sig = keypair.sign(&sig_hash.0); for si in &mut self.siacoin_inputs { match &si.satisfied_policy.policy { - SpendPolicy::PublicKey(pk) if pk == &keypair.public() => si.satisfied_policy.signatures.push(sig), + SpendPolicy::PublicKey(pk) if pk == &keypair.public() => { + si.satisfied_policy.signatures.push(sig.clone()) + }, SpendPolicy::UnlockConditions(uc) => { for p in &uc.unlock_keys { match p { UnlockKey::Ed25519(pk) if pk == &keypair.public() => { - si.satisfied_policy.signatures.push(sig) + si.satisfied_policy.signatures.push(sig.clone()) }, _ => (), } @@ -1234,22 +1456,67 @@ impl V2TransactionBuilder { } } } + self + } + + pub fn satisfy_atomic_swap_success( + &mut self, + keypair: &Keypair, + secret: Preimage, + input_index: u32, + ) -> Result<&mut Self, V2TransactionBuilderError> { + let sig_hash = self.input_sig_hash(); + let sig = keypair.sign(&sig_hash.0); + + // check input_index exists prior to indexing into the vector + if self.siacoin_inputs.len() <= (input_index as usize) { + return Err(V2TransactionBuilderError::SatisfySuccessIndexOutOfBounds { + len: self.siacoin_inputs.len(), + index: input_index, + }); + } + + let htlc_input = &mut self.siacoin_inputs[input_index as usize]; + htlc_input.satisfied_policy.signatures.push(sig); + htlc_input.satisfied_policy.preimages.push(secret); + Ok(self) + } + + pub fn satisfy_atomic_swap_refund( + &mut self, + keypair: &Keypair, + input_index: u32, + ) -> Result<&mut Self, V2TransactionBuilderError> { + let sig_hash = self.input_sig_hash(); + let sig = keypair.sign(&sig_hash.0); + + // check input_index exists prior to indexing into the vector + if self.siacoin_inputs.len() <= (input_index as usize) { + return Err(V2TransactionBuilderError::SatisfyRefundIndexOutOfBounds { + len: self.siacoin_inputs.len(), + index: input_index, + }); + } + + let htlc_input = &mut self.siacoin_inputs[input_index as usize]; + htlc_input.satisfied_policy.signatures.push(sig); Ok(self) } - pub fn build(self) -> V2Transaction { + pub fn build(&mut self) -> V2Transaction { + let cloned = self.clone(); V2Transaction { - siacoin_inputs: self.siacoin_inputs, - siacoin_outputs: self.siacoin_outputs, - siafund_inputs: self.siafund_inputs, - siafund_outputs: self.siafund_outputs, - file_contracts: self.file_contracts, - file_contract_revisions: self.file_contract_revisions, - file_contract_resolutions: self.file_contract_resolutions, - attestations: self.attestations, - arbitrary_data: self.arbitrary_data, - new_foundation_address: self.new_foundation_address, - miner_fee: self.miner_fee, + siacoin_inputs: cloned.siacoin_inputs, + siacoin_outputs: cloned.siacoin_outputs, + siafund_inputs: cloned.siafund_inputs, + siafund_outputs: cloned.siafund_outputs, + file_contracts: cloned.file_contracts, + file_contract_revisions: cloned.file_contract_revisions, + file_contract_resolutions: cloned.file_contract_resolutions, + attestations: cloned.attestations, + arbitrary_data: cloned.arbitrary_data, + new_foundation_address: cloned.new_foundation_address, + miner_fee: cloned.miner_fee, } } } diff --git a/src/utils.rs b/src/utils.rs new file mode 100644 index 0000000..2e91f96 --- /dev/null +++ b/src/utils.rs @@ -0,0 +1,34 @@ +use serde::{Deserialize, Deserializer}; + +/// Run a block of unit tests on both wasm32 and non-wasm32 targets +/// Can only be used once per scope due to wasm_bindgen initialization +#[cfg(test)] +macro_rules! cross_target_tests { + ($($test_fn:item)*) => { + #[cfg(all(test, target_arch = "wasm32"))] + use wasm_bindgen_test::*; + + #[cfg(all(test, target_arch = "wasm32"))] + wasm_bindgen_test_configure!(run_in_browser); + + $( + #[cfg(all(test, target_arch = "wasm32"))] + #[wasm_bindgen_test] + $test_fn + + #[cfg(not(target_arch = "wasm32"))] + #[test] + $test_fn + )* + }; +} + +/// Deserialize a null value as an empty vector. +/// Allows using Vec<> instead of Option> for convenience. +pub(crate) fn deserialize_null_as_empty_vec<'de, D, T>(deserializer: D) -> Result, D::Error> +where + D: Deserializer<'de>, + T: Deserialize<'de>, +{ + Option::deserialize(deserializer).map(|opt| opt.unwrap_or_default()) +}