diff --git a/.gitignore b/.gitignore index ea8c4bf..3d942c7 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /target +.idea/ +token diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/CleytoCoin.iml b/.idea/CleytoCoin.iml new file mode 100644 index 0000000..bbe0a70 --- /dev/null +++ b/.idea/CleytoCoin.iml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..f0100af --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 0560b30..40b30cc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,21 +1,28 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "CleytoCoin" -version = "0.1.0" +name = "addr2line" +version = "0.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" dependencies = [ - "chrono", - "hex", - "rand", - "rsa", - "serde", - "serde_json", - "sha2", - "tiny-bip39", + "gimli", ] +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -31,6 +38,32 @@ dependencies = [ "libc", ] +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" @@ -38,10 +71,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "base64ct" -version = "1.7.3" +name = "backtrace" +version = "0.3.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "89e25b6adfb930f02d1981565a6e5d9c547ac15a96606256d3b59040e5cd4ca3" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "block-buffer" @@ -59,10 +119,25 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] -name = "byteorder" -version = "1.5.0" +name = "bytes" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" + +[[package]] +name = "cassowary" +version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53" + +[[package]] +name = "castaway" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0abae9be0aaf9ea96a3b1b8b1b55c602ca751eba1b1500220cea4ecbafe7c0d5" +dependencies = [ + "rustversion", +] [[package]] name = "cc" @@ -89,15 +164,109 @@ dependencies = [ "iana-time-zone", "js-sys", "num-traits", + "serde", "wasm-bindgen", "windows-link", ] [[package]] -name = "const-oid" -version = "0.9.6" +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags 1.3.2", + "strsim 0.8.0", + "textwrap", + "unicode-width 0.1.14", + "vec_map", +] + +[[package]] +name = "cleyto_coin" +version = "0.1.0" +dependencies = [ + "chrono", + "color-eyre", + "crossterm 0.29.0", + "directories", + "futures", + "hex", + "num_cpus", + "once_cell", + "openssl", + "rand", + "ratatui", + "reqwest", + "serde", + "serde_json", + "sha2", + "structopt", + "tokio", + "toml", +] + +[[package]] +name = "color-eyre" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" +dependencies = [ + "backtrace", + "color-spantrace", + "eyre", + "indenter", + "once_cell", + "owo-colors", + "tracing-error", +] + +[[package]] +name = "color-spantrace" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd6be1b2a7e382e2b98b43b2adcca6bb0e465af0bdd38123873ae61eb17a72c2" +dependencies = [ + "once_cell", + "owo-colors", + "tracing-core", + "tracing-error", +] + +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + +[[package]] +name = "convert_case" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb402b8d4c85569410425650ce3eddc7d698ed96d39a73f941b08fb63082f1e7" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "core-foundation" +version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] [[package]] name = "core-foundation-sys" @@ -115,505 +284,1956 @@ dependencies = [ ] [[package]] -name = "crypto-common" -version = "0.1.6" +name = "crossterm" +version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +checksum = "829d955a0bb380ef178a640b91779e3987da38c9aea133b20614cfed8cdea9c6" dependencies = [ - "generic-array", - "typenum", + "bitflags 2.9.0", + "crossterm_winapi", + "mio", + "parking_lot", + "rustix 0.38.44", + "signal-hook", + "signal-hook-mio", + "winapi", ] [[package]] -name = "der" -version = "0.7.9" +name = "crossterm" +version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +checksum = "d8b9f2e4c67f833b660cdb0a3523065869fb35570177239812ed4c905aeff87b" dependencies = [ - "const-oid", - "pem-rfc7468", - "zeroize", + "bitflags 2.9.0", + "crossterm_winapi", + "derive_more", + "document-features", + "mio", + "parking_lot", + "rustix 1.0.5", + "signal-hook", + "signal-hook-mio", + "winapi", ] [[package]] -name = "digest" -version = "0.10.7" +name = "crossterm_winapi" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b" dependencies = [ - "block-buffer", - "const-oid", - "crypto-common", - "subtle", + "winapi", ] [[package]] -name = "generic-array" -version = "0.14.7" +name = "crypto-common" +version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ + "generic-array", "typenum", - "version_check", ] [[package]] -name = "getrandom" -version = "0.2.15" +name = "darling" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ - "cfg-if", - "libc", - "wasi", + "darling_core", + "darling_macro", ] [[package]] -name = "hex" -version = "0.4.3" +name = "darling_core" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim 0.11.1", + "syn 2.0.100", +] [[package]] -name = "hmac" -version = "0.12.1" +name = "darling_macro" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c49c37c09c17a53d937dfbb742eb3a961d65a994e6bcdcf37e7399d0cc8ab5e" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ - "digest", + "darling_core", + "quote", + "syn 2.0.100", ] [[package]] -name = "iana-time-zone" -version = "0.1.61" +name = "derive_more" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +checksum = "093242cf7570c207c83073cf82f79706fe7b8317e98620a47d5be7c3d8497678" dependencies = [ - "android_system_properties", - "core-foundation-sys", - "iana-time-zone-haiku", - "js-sys", - "wasm-bindgen", - "windows-core", + "derive_more-impl", ] [[package]] -name = "iana-time-zone-haiku" -version = "0.1.2" +name = "derive_more-impl" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +checksum = "bda628edc44c4bb645fbe0f758797143e4e07926f7ebf4e9bdfbd3d2ce621df3" dependencies = [ - "cc", + "convert_case", + "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] -name = "itoa" -version = "1.0.15" +name = "digest" +version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] [[package]] -name = "js-sys" -version = "0.3.77" +name = "directories" +version = "6.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +checksum = "16f5094c54661b38d03bd7e50df373292118db60b585c08a411c6d840017fe7d" dependencies = [ - "once_cell", - "wasm-bindgen", + "dirs-sys", ] [[package]] -name = "lazy_static" -version = "1.5.0" +name = "dirs-sys" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" dependencies = [ - "spin", + "libc", + "option-ext", + "redox_users", + "windows-sys 0.59.0", ] [[package]] -name = "libc" -version = "0.2.171" +name = "displaydoc" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] [[package]] -name = "libm" +name = "document-features" version = "0.2.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8355be11b20d696c8f18f6cc018c4e372165b1fa8126cef092399c9951984ffa" +checksum = "95249b50c6c185bee49034bcb378a49dc2b5dff0be90ff6616d31d64febab05d" +dependencies = [ + "litrs", +] [[package]] -name = "log" -version = "0.4.26" +name = "either" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] -name = "memchr" -version = "2.7.4" +name = "encoding_rs" +version = "0.8.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] [[package]] -name = "num-bigint-dig" -version = "0.8.4" +name = "equivalent" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc84195820f291c7697304f3cbdadd1cb7199c0efc917ff5eafd71225c136151" -dependencies = [ - "byteorder", - "lazy_static", - "libm", - "num-integer", - "num-iter", - "num-traits", - "rand", - "smallvec", - "zeroize", -] +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] -name = "num-integer" -version = "0.1.46" +name = "errno" +version = "0.3.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +checksum = "33d852cb9b869c2a9b3df2f71a3074817f01e1844f839a144f5fcef059a4eb5d" dependencies = [ - "num-traits", + "libc", + "windows-sys 0.59.0", ] [[package]] -name = "num-iter" -version = "0.1.45" +name = "eyre" +version = "0.6.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "indenter", + "once_cell", ] [[package]] -name = "num-traits" -version = "0.2.19" +name = "fastrand" +version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", - "libm", -] +checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] -name = "once_cell" -version = "1.21.1" +name = "fnv" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] -name = "pbkdf2" -version = "0.12.2" +name = "foldhash" +version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8ed6a7761f76e3b9f92dfb0a60a6a6477c61024b775147ff0973a02653abaf2" -dependencies = [ - "digest", - "hmac", -] +checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] -name = "pem-rfc7468" -version = "0.7.0" +name = "foreign-types" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88b39c9bfcfc231068454382784bb460aae594343fb030d46e9f50a645418412" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ - "base64ct", + "foreign-types-shared", ] [[package]] -name = "pkcs1" -version = "0.7.5" +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + +[[package]] +name = "form_urlencoded" +version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8ffb9f10fa047879315e6625af03c164b16962a5368d724ed16323b68ace47f" +checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ - "der", - "pkcs8", - "spki", + "percent-encoding", ] [[package]] -name = "pkcs8" -version = "0.10.2" +name = "futures" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" dependencies = [ - "der", - "spki", + "futures-channel", + "futures-core", + "futures-executor", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", ] [[package]] -name = "ppv-lite86" -version = "0.2.21" +name = "futures-channel" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ - "zerocopy", + "futures-core", + "futures-sink", ] [[package]] -name = "proc-macro2" -version = "1.0.94" +name = "futures-core" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-executor" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" dependencies = [ - "unicode-ident", + "futures-core", + "futures-task", + "futures-util", ] [[package]] -name = "quote" -version = "1.0.40" +name = "futures-io" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" dependencies = [ "proc-macro2", + "quote", + "syn 2.0.100", ] [[package]] -name = "rand" -version = "0.8.5" +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ - "libc", - "rand_chacha", - "rand_core", + "futures-channel", + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "pin-utils", + "slab", ] [[package]] -name = "rand_chacha" -version = "0.3.1" +name = "generic-array" +version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ - "ppv-lite86", - "rand_core", + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.11.0+wasi-snapshot-preview1", +] + +[[package]] +name = "getrandom" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" +dependencies = [ + "cfg-if", + "libc", + "r-efi", + "wasi 0.14.2+wasi-0.2.4", +] + +[[package]] +name = "gimli" +version = "0.28.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" + +[[package]] +name = "h2" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5017294ff4bb30944501348f6f8e42e6ad28f42c8bbef7a74029aff064a4e3c2" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap", + "slab", + "tokio", + "tokio-util", + "tracing", +] + +[[package]] +name = "hashbrown" +version = "0.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b021d93e26becf5dc7e1b75b1bed1fd93124b374ceb73f43d4d4eafec896a64a" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc2b571658e38e0c01b1fdca3bbbe93c00d3d71693ff2770043f8c29bc7d6f80" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "h2", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d191583f3da1305256f22463b9bb0471acad48a4e534a5218b9963e9c1f59b2" +dependencies = [ + "futures-util", + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + +[[package]] +name = "hyper-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +dependencies = [ + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "libc", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "686f825264d630750a544639377bae737628043f20d38bbc029e8f29ea968a7e" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + +[[package]] +name = "indexmap" +version = "2.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3954d50fe15b02142bf25d3b8bdadb634ec3948f103d04ffe3031bc8fe9d7058" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "indoc" +version = "2.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c7245a08504955605670dbf141fceab975f15ca21570696aebe9d2e71576bd" + +[[package]] +name = "instability" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf9fed6d91cfb734e7476a06bde8300a1b94e217e1b523b6f0cd1a01998c71d" +dependencies = [ + "darling", + "indoc", + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "ipnet" +version = "2.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130" + +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" + +[[package]] +name = "js-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" +dependencies = [ + "once_cell", + "wasm-bindgen", +] + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "libc" +version = "0.2.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" + +[[package]] +name = "libredox" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3" +dependencies = [ + "bitflags 2.9.0", + "libc", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe7db12097d22ec582439daf8618b8fdd1a7bef6270e9af3b1ebcd30893cf413" + +[[package]] +name = "litemap" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" + +[[package]] +name = "litrs" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4ce301924b7887e9d637144fdade93f9dfff9b60981d4ac161db09720d39aa5" + +[[package]] +name = "lock_api" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" +dependencies = [ + "autocfg", + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30bde2b3dc3671ae49d8e2e9f044c7c005836e7a023ee57cffa25ab82764bb9e" + +[[package]] +name = "lru" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234cf4f4a04dc1f57e24b96cc0cd600cf2af460d4161ac5ecdd0af8e1f3b2a38" +dependencies = [ + "hashbrown", +] + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +dependencies = [ + "adler", +] + +[[package]] +name = "mio" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2886843bf800fba2e3377cff24abf6379b4c4d5c6681eaf9ea5b0d15090450bd" +dependencies = [ + "libc", + "log", + "wasi 0.11.0+wasi-snapshot-preview1", + "windows-sys 0.52.0", +] + +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43" +dependencies = [ + "hermit-abi 0.3.9", + "libc", +] + +[[package]] +name = "object" +version = "0.32.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" +dependencies = [ + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.21.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" + +[[package]] +name = "openssl" +version = "0.10.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e14130c6a98cd258fdcb0fb6d744152343ff729cbfcb28c656a9d12b999fbcd" +dependencies = [ + "bitflags 2.9.0", + "cfg-if", + "foreign-types", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8bb61ea9811cc39e3c2069f40b8b8e2e70d8569b361f879786cc7ed48b777cdd" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "owo-colors" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" + +[[package]] +name = "parking_lot" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-targets 0.52.6", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "pkg-config" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c" + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.94" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "rand" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3779b94aeb87e8bd4e834cee3650289ee9e0d5677f976ecdb6d219e5f4f6cd94" +dependencies = [ + "rand_chacha", + "rand_core", + "zerocopy", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" +dependencies = [ + "getrandom 0.3.2", +] + +[[package]] +name = "ratatui" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eabd94c2f37801c20583fc49dd5cd6b0ba68c716787c2dd6ed18571e1e63117b" +dependencies = [ + "bitflags 2.9.0", + "cassowary", + "compact_str", + "crossterm 0.28.1", + "indoc", + "instability", + "itertools", + "lru", + "paste", + "strum", + "unicode-segmentation", + "unicode-truncate", + "unicode-width 0.2.0", +] + +[[package]] +name = "redox_syscall" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2f103c6d277498fbceb16e84d317e2a400f160f46904d5f5410848c829511a3" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "redox_users" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" +dependencies = [ + "getrandom 0.2.15", + "libredox", + "thiserror", +] + +[[package]] +name = "reqwest" +version = "0.12.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d19c46a6fdd48bc4dab94b6103fccc55d34c67cc0ad04653aad4ea2a07cd7bbb" +dependencies = [ + "base64", + "bytes", + "encoding_rs", + "futures-channel", + "futures-core", + "futures-util", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "ipnet", + "js-sys", + "log", + "mime", + "native-tls", + "once_cell", + "percent-encoding", + "pin-project-lite", + "rustls-pemfile", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "system-configuration", + "tokio", + "tokio-native-tls", + "tower", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "windows-registry", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.15", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d97817398dd4bb2e6da002002db259209759911da105da92bec29ccb12cf58bf" +dependencies = [ + "bitflags 2.9.0", + "errno", + "libc", + "linux-raw-sys 0.9.3", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustls" +version = "0.23.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "822ee9188ac4ec04a2f0531e55d035fb2de73f18b41a63c70c2712503b6fb13c" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pemfile" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "rustls-pki-types" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "917ce264624a4b4db1c364dcc35bfca9ded014d0a958cd47ad3e960e988ea51c" + +[[package]] +name = "rustls-webpki" +version = "0.103.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fef8b8769aaccf73098557a87cd1816b4f9c7c16811c9c77142aa695c16f2c03" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + +[[package]] +name = "ryu" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" + +[[package]] +name = "schannel" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f29ebaa345f945cec9fbbc532eb307f0fdad8161f281b6369539c8d84876b3d" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49db231d56a190491cb4aeda9527f1ad45345af50b0851622a7adb8c03b01c32" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "serde" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.219" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "serde_json" +version = "1.0.140" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40734c41988f7306bb04f0ecf60ec0f3f1caa34290e4e8ea471dcd3346483b83" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.10.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "signal-hook" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8621587d4798caf8eb44879d42e56b9a93ea5dcd315a6487c357130095b62801" +dependencies = [ + "libc", + "signal-hook-registry", +] + +[[package]] +name = "signal-hook-mio" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd" +dependencies = [ + "libc", + "mio", + "signal-hook", +] + +[[package]] +name = "signal-hook-registry" +version = "1.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9203b8055f63a2a00e2f593bb0510367fe707d7ff1e5c872de2f537b339e5410" +dependencies = [ + "libc", +] + +[[package]] +name = "slab" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" +dependencies = [ + "autocfg", +] + +[[package]] +name = "smallvec" +version = "1.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" + +[[package]] +name = "socket2" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" +dependencies = [ + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "static_assertions" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "strum" +version = "0.26.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fec0f0aef304996cf250b31b5a10dee7980c85da9d759361292b8bca5a18f06" +dependencies = [ + "strum_macros", +] + +[[package]] +name = "strum_macros" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6bee85a5a24955dc440386795aa378cd9cf82acd5f764469152d2270e581be" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.100", +] + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.100" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8af7666ab7b6390ab78131fb5b0fce11d6b7a6951602017c35fa82800708971" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.9.0", + "core-foundation", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "tempfile" +version = "3.19.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7437ac7763b9b123ccf33c338a5cc1bac6f69b45a136c19bdd8a65e3916435bf" +dependencies = [ + "fastrand", + "getrandom 0.3.2", + "once_cell", + "rustix 1.0.5", + "windows-sys 0.59.0", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width 0.1.14", +] + +[[package]] +name = "thiserror" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3467d614147380f2e4e374161426ff399c91084acd2363eaf549172b3d5e60c0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c5e1be1c48b9172ee610da68fd9cd2770e7a4056cb3fc98710ee6906f0c7960" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "thread_local" +version = "1.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tokio" +version = "1.44.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +dependencies = [ + "backtrace", + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "tokio-macros", + "windows-sys 0.52.0", +] + +[[package]] +name = "tokio-macros" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e727b36a1a0e8b74c376ac2211e40c2c8af09fb4013c60d910495810f008e9b" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b9590b93e6fcc1739458317cccd391ad3955e2bde8913edf6f95f9e65a8f034" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75129e1dc5000bfbaa9fee9d1b21f974f9fbad9daec557a521ee6e080825f6e8" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + +[[package]] +name = "toml_datetime" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bade1c3e902f58d73d3f294cd7f20391c1cb2fbcb643b73566bc773971df91e3" +dependencies = [ + "serde", ] [[package]] -name = "rand_core" -version = "0.6.4" +name = "toml_parser" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "b551886f449aa90d4fe2bdaa9f4a2577ad2dde302c61ecf262d80b116db95c10" dependencies = [ - "getrandom", + "winnow", ] [[package]] -name = "rsa" -version = "0.9.8" +name = "toml_writer" +version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78928ac1ed176a5ca1d17e578a1825f3d81ca54cf41053a592584b020cfd691b" -dependencies = [ - "const-oid", - "digest", - "num-bigint-dig", - "num-integer", - "num-traits", - "pkcs1", - "pkcs8", - "rand_core", - "sha2", - "signature", - "spki", - "subtle", - "zeroize", -] +checksum = "fcc842091f2def52017664b53082ecbbeb5c7731092bad69d2c63050401dfd64" [[package]] -name = "rustc-hash" -version = "1.1.0" +name = "tower" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" +checksum = "d039ad9159c98b70ecfd540b2573b97f7f52c3e8d9f8ad57a24b916a536975f9" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] [[package]] -name = "rustversion" -version = "1.0.20" +name = "tower-layer" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" [[package]] -name = "ryu" -version = "1.0.20" +name = "tower-service" +version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" [[package]] -name = "serde" -version = "1.0.219" +name = "tracing" +version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" +checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0" dependencies = [ - "serde_derive", + "pin-project-lite", + "tracing-core", ] [[package]] -name = "serde_derive" -version = "1.0.219" +name = "tracing-core" +version = "0.1.33" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" +checksum = "e672c95779cf947c5311f83787af4fa8fffd12fb27e4993211a84bdfd9610f9c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "once_cell", + "valuable", ] [[package]] -name = "serde_json" -version = "1.0.140" +name = "tracing-error" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" +checksum = "8b1581020d7a273442f5b45074a6a57d5757ad0a47dac0e9f0bd57b81936f3db" dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", + "tracing", + "tracing-subscriber", ] [[package]] -name = "sha2" -version = "0.10.8" +name = "tracing-subscriber" +version = "0.3.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "793db75ad2bcafc3ffa7c68b215fee268f537982cd901d132f89c6343f3a3dc8" +checksum = "e8189decb5ac0fa7bc8b96b7cb9b2701d60d48805aca84a238004d665fcc4008" dependencies = [ - "cfg-if", - "cpufeatures", - "digest", + "sharded-slab", + "thread_local", + "tracing-core", ] [[package]] -name = "shlex" -version = "1.3.0" +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] -name = "signature" -version = "2.2.0" +name = "typenum" +version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" -dependencies = [ - "digest", - "rand_core", -] +checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] -name = "smallvec" -version = "1.14.0" +name = "unicode-ident" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7fcf8323ef1faaee30a44a340193b1ac6814fd9b7b4e88e9d4519a3e4abe1cfd" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] -name = "spin" -version = "0.9.8" +name = "unicode-segmentation" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" +checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" [[package]] -name = "spki" -version = "0.7.3" +name = "unicode-truncate" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +checksum = "b3644627a5af5fa321c95b9b235a72fd24cd29c648c2c379431e6628655627bf" dependencies = [ - "base64ct", - "der", + "itertools", + "unicode-segmentation", + "unicode-width 0.1.14", ] [[package]] -name = "subtle" -version = "2.6.1" +name = "unicode-width" +version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" [[package]] -name = "syn" -version = "2.0.100" +name = "unicode-width" +version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] +checksum = "1fc81956842c57dac11422a97c3b8195a1ff727f06e85c84ed2e8aa277c9a0fd" [[package]] -name = "thiserror" -version = "1.0.69" +name = "untrusted" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl", -] +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] -name = "thiserror-impl" -version = "1.0.69" +name = "url" +version = "2.5.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +checksum = "32f8b686cadd1473f4bd0117a5d28d36b1ade384ea9b5069a1c40aefed7fda60" dependencies = [ - "proc-macro2", - "quote", - "syn", + "form_urlencoded", + "idna", + "percent-encoding", ] [[package]] -name = "tiny-bip39" -version = "2.0.0" +name = "utf16_iter" +version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a30fd743a02bf35236f6faf99adb03089bb77e91c998dac2c2ad76bb424f668c" -dependencies = [ - "once_cell", - "pbkdf2", - "rand", - "rustc-hash", - "sha2", - "thiserror", - "unicode-normalization", - "wasm-bindgen", - "zeroize", -] +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" [[package]] -name = "tinyvec" -version = "1.9.0" +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" -dependencies = [ - "tinyvec_macros", -] +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] -name = "tinyvec_macros" +name = "valuable" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" - -[[package]] -name = "typenum" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] -name = "unicode-ident" -version = "1.0.18" +name = "vcpkg" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] -name = "unicode-normalization" -version = "0.1.24" +name = "vec_map" +version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" -dependencies = [ - "tinyvec", -] +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" [[package]] name = "version_check" @@ -621,12 +2241,30 @@ version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wasi" +version = "0.14.2+wasi-0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" +dependencies = [ + "wit-bindgen-rt", +] + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -649,10 +2287,23 @@ dependencies = [ "log", "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-shared", ] +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "555d470ec0bc3bb57890405e5d4322cc9ea83cebb085523ced7be4144dac1e61" +dependencies = [ + "cfg-if", + "js-sys", + "once_cell", + "wasm-bindgen", + "web-sys", +] + [[package]] name = "wasm-bindgen-macro" version = "0.2.100" @@ -671,7 +2322,7 @@ checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -685,13 +2336,45 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "web-sys" +version = "0.3.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[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-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets", + "windows-targets 0.52.6", ] [[package]] @@ -700,20 +2383,83 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6dccfd733ce2b1753b03b6d3c65edf020262ea35e20ccdf3e288043e6dd620e3" +[[package]] +name = "windows-registry" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4286ad90ddb45071efd1a66dfa43eb02dd0dfbae1545ad6cc3c51cf34d7e8ba3" +dependencies = [ + "windows-result", + "windows-strings", + "windows-targets 0.53.0", +] + +[[package]] +name = "windows-result" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06374efe858fab7e4f881500e6e86ec8bc28f9462c47e5a9941a0142ad86b189" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-strings" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm 0.52.6", + "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", +] + +[[package]] +name = "windows-targets" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e4c7e8ceaaf9cb7d7507c974735728ab453b67ef8f18febdd7c11fe59dca8b" +dependencies = [ + "windows_aarch64_gnullvm 0.53.0", + "windows_aarch64_msvc 0.53.0", + "windows_i686_gnu 0.53.0", + "windows_i686_gnullvm 0.53.0", + "windows_i686_msvc 0.53.0", + "windows_x86_64_gnu 0.53.0", + "windows_x86_64_gnullvm 0.53.0", + "windows_x86_64_msvc 0.53.0", ] [[package]] @@ -722,48 +2468,147 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764" + [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" +[[package]] +name = "windows_aarch64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c" + [[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_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1dc67659d35f387f5f6c479dc4e28f1d4bb90ddd1a5d3da2e5d97b42d6272c3" + [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" +[[package]] +name = "windows_i686_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11" + [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" +[[package]] +name = "windows_i686_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d" + [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" +[[package]] +name = "windows_x86_64_gnu" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba" + [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57" + [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "windows_x86_64_msvc" +version = "0.53.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "271414315aff87387382ec3d271b52d7ae78726f5d44ac98b4f4030c91880486" + +[[package]] +name = "winnow" +version = "0.7.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf" + +[[package]] +name = "wit-bindgen-rt" +version = "0.39.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" +dependencies = [ + "bitflags 2.9.0", +] + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", +] + [[package]] name = "zerocopy" version = "0.8.23" @@ -781,7 +2626,28 @@ checksum = "6352c01d0edd5db859a63e2605f4ea3183ddbd15e2c4a9e7d32184df75e4f154" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", + "synstructure", ] [[package]] @@ -789,17 +2655,25 @@ name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" dependencies = [ - "zeroize_derive", + "yoke", + "zerofrom", + "zerovec-derive", ] [[package]] -name = "zeroize_derive" -version = "1.4.2" +name = "zerovec-derive" +version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce36e65b0d2999d2aafac989fb249189a141aee1f53c612c1f37d72631959f69" +checksum = "6eafa6dfb17584ea3e2bd6e76e0cc15ad7af12b09abdd1ca55961bed9b1063c6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.100", ] diff --git a/Cargo.toml b/Cargo.toml index cd3e288..784dbef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,14 +1,32 @@ [package] -name = "CleytoCoin" +name = "cleyto_coin" version = "0.1.0" -edition = "2024" +edition = "2021" + +[[bin]] +name = "node" +test = false +bench = false + + [dependencies] sha2 = "0.10" -rsa = {version = "0.9.8", features = ["sha2"]} -rand = "0.8.0" +rand = "0.9.0" hex = "0.4.3" -tiny-bip39 = "2.0.0" -chrono = "0.4.40" -serde = "1.0.219" +chrono = { version="0.4.40", features = ["serde"] } +serde = { version="1.0.219", features = ["derive", "rc"] } serde_json = "1.0.140" +reqwest = { version = "0.12.15", features = ["blocking", "json"] } +num_cpus = "1.16.0" +once_cell = "1.21.3" +openssl = "0.10.71" +crossterm = "0.29" +ratatui = "0.29.0" +color-eyre = "0.6.3" +tokio = { version = "1.44.1", features = ["rt", "rt-multi-thread", "macros"] } +futures = "0.3.31" +directories = "6.0.0" +toml = "0.9.5" +structopt = "0.3.26" + diff --git a/README.md b/README.md index 97e1eab..79a56ce 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# CleytoCoin +# CleytoCoin ## Table of Contents @@ -11,25 +11,18 @@ - [Testing](#testing) - [Contributing](#contributing) -# CleytoCoin is still under development, so many of the features listed underneath aren't yet functional. +# CleytoCoin is still under development, so some of the features listed underneath aren't yet functional. Those are marked with [Under development] + We appreciate the interest and are working towards making CleytoCoin a functional and reliable cryptocurrency, but currently it's still in it's early development stages. -Feel free to give sugestions in the meantime of what you'd like to see implemented in our project! +Feel free to give suggestions in the meantime of what you'd like to see implemented in our project! ## About -**CleytoCoin** is a cryptocurrency built using the Rust programming language. This project aims to create a secure, fast, and decentralized cryptocurrency to facilitate peer-to-peer transactions. - -## Features - -- **Proof of Work (PoW)** consensus mechanism -- Secure peer-to-peer transactions -- Fast block generation time -- High scalability and low latency -- Rust-based with a focus on performance and safety +**CleytoCoin** is a cryptocurrency built using the Rust programming language. This project aims to create a decentralized cryptocurrency to facilitate peer-to-peer transactions. ## Getting Started -Follow these instructions to get your local instance of **CleytoCoin** up and running. +Follow these instructions to get your local instance of a **CleytoCoin** node up and running. ### Prerequisites @@ -43,62 +36,136 @@ Ensure you have the following dependencies installed on your machine: Clone this repository to your local machine: ```bash -$ git clone https://github.com/dantee-e/CleytoCoin.git -$ cd CleytoCoin +git clone https://github.com/dantee-e/CleytoCoin.git +cd CleytoCoin ``` + To build and install the project: -``` bash -$ cargo build --release + +```bash +cargo build --release ``` + This will compile the project and generate an optimized binary in the `target/release` directory. -## Usage +# How to use the binaries + +Thanks to the [StructOpt](https://crates.io/crates/structopt) crate, whenever you feel in doubt about one of the features of **CleytoCoin**, you can use the flag --help of the CLI tool to see flags and arguments for said feature. + +## Node Usage ### Starting the node + To start the cryptocurrency node, use the following command: -``` bash -cargo run --bin CleytoCoin + +```bash +cargo run --bin node start ``` -The node will start and connect to the network. You can start mining or send/receive transactions. -### Creating a wallet -To generate a new wallet, run the following: -``` bash -cargo run --bin CleytoCoin-wallet generate +With the option of running the GUI: + +```bash +cargo run --bin node start --gui ``` -This will generate a private key and address for your wallet. + +The server with the GUI will block the terminal, while just running the start creates a new process, which has to be killed afterwards using the [kill command](#killing-the-node) + + +The node will start and connect to the network. For now, only full nodes are available and they don't have yet the capacity for mining + +### Killing the node + +To kill the node, we follow the same pattern as before: + +```bash +cargo run --bin node kill +``` + +## Wallet Usage + +The `cleyto-coin-wallet` CLI has two main commands: `generate` (to create a wallet) and `send` (to send transactions). + +### Generating a wallet +Generates a new keypair (private and public keys) for your wallet. + +```bash +cargo run --bin cleyto-coin-wallet generate \ + --private-key-file \ + --public-key-file \ + [-p ] +vbnet +``` +--- + +### Options + +| Option | Description | Default | +|--------|-------------|---------| +| `--private-key-file` | Path where the generated private key will be stored | `./private.pem` | +| `--public-key-file` | Path where the generated public key will be stored | `./public.pem` | +| `-p, --password` | Optional password to encrypt your private key | none | + + + ### Sending a transaction -To send a transaction, use the following command: -``` bash -cargo run --bin CleytoCoin-wallet send --to --amount --private-key + +To send a transaction, you can use the same binary, but with the `send` subcommand + +```bash +cargo run --bin cleyto-coin-wallet send \ + --recipient-key \ + --sender-key \ + --amount \ + [-p ] +``` +Or, using key files: +```bash +cargo run --bin cleyto-coin-wallet send \ + --recipient-key-file \ + --sender-key-file \ + --amount \ + [-p ] ``` -### Mining +### Mining [Under develpment] + Start mining by running: -``` bash -cargo run --bin CleytoCoin-miner start --mining-key + +```bash +cargo run --bin cleyto-coin-miner start --mining-key ``` ### Stopping the node -To stop the node, press `CTRL+C` or run the following: -``` bash -cargo run --bin CleytoCoin stop + +To stop the node, on the terminal window running your server, press `CTRL+C`, `q` or `Esc` + +**Running the server without the GUI is not yet implemented** +If you ran it without the GUI, use the command + +```bash +cargo run --bin cleyto-coin stop # Not yet implemented ``` ## Testing + To run the tests for the project, use the following command: -``` bash + +```bash cargo test ``` + This will run all unit tests, integration tests, and any other tests defined in this project. If you wish to run with output run: -``` bash + +```bash cargo test -- --nocapture ``` ## Contributing + We welcome contributions to the CleytoCoin project. If you have an idea or find a bug, please feel free to submit an issue or a pull request. + 1. Fork the repository 2. Create a new branch (`git checkout -b feature/feature-name`) 3. Make your changes diff --git a/private.pem b/private.pem new file mode 100644 index 0000000..ff1d226 --- /dev/null +++ b/private.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQqeAwhIZ59VH8O4gD +IPuAbQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEDkljmulTgAI0Ndf +Q66m1vIEggTQiM0d2VcX8terXyVCMTZjlXn6IxPDpQxJ+f02WfAinQWhGL05AQyC +TjEtnsSU2Xq6cKvPa/BfOHNhPO616DenTS4bAClH/AKi4dSX6xgKrxw3e2lgxAtE +v6Flrfk32JLdJamUjZjNiw/1HDlZ28YhZNC4us80AUxkkhL4NGt0NKLkOCCDgLZO +95aYqXtSO63CVV/16ul0YBTAjvVybY6aLbeXBwuIggQP+CDKBmruVQKe/ncssdnT +HOsmsk0od9f6QAVhuhHVMHBgmko2IeC+8LX95NFwDU02LsV9GOBbvockaRAidUhm +dZ0IpIbGZM9hDSLy6dXKYBjaoLJ7+iQLgtpKuIyLcI9ViJBrVKoEh5pVBsdpBrDb +m1GMrCrxyn2pbdCS3pV/MREm3KiI/4mVYgnn07uLvfma1EWMLVSWN7iWTNtDTcob +RjF5+U3kTJsSqL7vLkxQCC/+ma/VmywRjJodHor5PPk8pEHRBZ/X6EV88niSb2uC +iooTKQh76zwTmR3ArvHmraA6Vbopck4BKO1uScMzthkhO2mcQRXroLEVsTDD2GdO +Z4rjXVlXVCoHNoZ/BXHl6qK8lEvNKq5IgajRwcvZSyDzE0AWr6Jnr2MZ55y8FYFk +kEJ3uQgrGLXHmicxaBDCXM9P3J5LAEG16ZI6IaLl96t1MqbkVfv5HE6nbFzgs3dl +3CR1RwMXkojLgrRLdxehKKhRLB+1/tNqgFAoz2P0z0/l2vrhKFow/h5NN0pj1DcX +3txgtspSBKOu2Eo5TK0n9Bf0Kzd4n2QklokOabMEJ1eUsOvq3FvsoQznfZWvqXKN +lJrP6JiQfJnXZ4tSbU17yldwL8IIzUOwAkMC0DZtnbk+7TCHTjm4lbcf/vPQ872X +gQWG8hMutO12jgk798HF40pH00axXRanNEhiheI4Ad4spmsZ1YIixqKcCh83tnLf +LvQ4NU/hhnjrXprqkOEipVvsiNdOP8PH8ftPXDprmwXgt5yJoglj/m3dPyUYNWIb +ac38gKEvX/bSxU/c6+7SXUeJ2BKKCpSLYTzfO0QLKg1zKyybvAnNYW3IjrjOn8eN +5g7+XukYpH93d6zVv57dzxTUNu42Ms6eTecXIuV0Ox3BOQQ0jQJItzb/5S/zZzgi +eBI0Q/IoOrXyrO2ThDn/N26SFFbqmJMKVnXhgFEUKEOOdmiusAxYf/Fh21SVjzwT +SZ9X8zua0F2fw9QD8l3L06m5CmQI2xkJHPLGL9JUqavxmzXNRLfzesfoCeLOl3nY +qNMrV4SaXnqbFE2jtMCQqf8vl9zdIr50YiOg6LqHSAfAZbuGOZWTuu7bq+NP4aHm +VxLn5ykxEMxG60NTzTfj2vwjovR4hR29SqfOfHNnqWQk7JnuS5md2IofoaEdl6jY +IlU5yhD6uMxsfNKorJtdW0VU/BoAeMvlcGiN+yjOduuAdCe59Ji9gEEPoLXDCzOP +HatD1Ei5nrkx7Q9wpIDk4gwZSrVFsudEidjagFDn8J59ifEipDZ+P8M04X5d55yq +Hbi4VS2qCfcg0hXWwPS59ZPFLo8Ene5NYbE9z4TDYWfRwudRbRZKUMWGskWG2QYz +/L8euwJsuhs6UgmBdyZv0J4E7vvZCHHMZqmF7/1jaVKP6e0Fq3gVHB0= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/public.pem b/public.pem new file mode 100644 index 0000000..241ef93 --- /dev/null +++ b/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA99BKIwf+qgyOLuLzSaTh +Ncv/k7KtDi9+W6pBLNwF6ZWRyseeve0RFYX1UT8eIDIhkeBbYcd7JzoJTamkK5wP +knNnfs8ggCt8gjTmMesLkGi6BBDtCZ/Ls8pxxsDo6Hta5a7pQAk8t6Vvh1jQCfS/ +V3OxqOhv5X+zGsYCCEPnx/lHFyENJQ5fcmErbk5nzF1llv/KJxn8BkqPdgFg/YQH +mL3Lqlp4MqyrbeFMPJE7TPoqtRZhf7ys85M0+Dm21yqBq7avzIYhRqR95aZOo01c +6seQRL7YDYm4MRfWonNKiohQILx2YaSLYwnix1YW27dA7QJiXqpTDYaPwcqoHZpy +3wIDAQAB +-----END PUBLIC KEY----- diff --git a/src/TODO.txt b/src/TODO.txt index f77fc3f..55cbb2e 100644 --- a/src/TODO.txt +++ b/src/TODO.txt @@ -1,7 +1,5 @@ -- Criar um node com uma port no localhost para receber transacoes, e de x em x tempos fechar o bloco - - Encontrar nodes - Quando fechar o bloco, emitir essa info pra outros nodes -- Minerar o bloco \ No newline at end of file +- Minerar o bloco diff --git a/src/bin/cleyto_coin_wallet.rs b/src/bin/cleyto_coin_wallet.rs new file mode 100644 index 0000000..7c130d8 --- /dev/null +++ b/src/bin/cleyto_coin_wallet.rs @@ -0,0 +1,91 @@ +use cleyto_coin::{generate, send}; +use std::path::PathBuf; +use structopt::StructOpt; + +/// CLI for key management and transactions +#[derive(Debug, StructOpt)] +#[structopt(name = "cleyto-coin-wallet")] +enum Args { + /// Generate a new keypair + Generate { + /// Where to store the generated private key + #[structopt(long, parse(from_os_str), default_value = "./private.pem")] + private_key_file: PathBuf, + + /// Where to store the generated public key + #[structopt(long, parse(from_os_str), default_value = "./public.pem")] + public_key_file: PathBuf, + + #[structopt(long, short)] + password: Option, + }, + + /// Send a transaction + Send { + /// Recipient’s public key as a string + #[structopt(long = "recipient-key", required_unless = "recipient-key-file")] + recipient_key: Option, + + /// Recipient’s public key from a file + #[structopt( + long = "recipient-key-file", + parse(from_os_str), + required_unless = "recipient-key" + )] + recipient_key_file: Option, + + /// Sender’s private key as a string + #[structopt(long = "sender-key", short = "sk", required_unless = "sender-key-file")] + sender_key: Option, + + /// Sender’s private key from a file + #[structopt( + long = "sender-key-file", + parse(from_os_str), + required_unless = "sender-key" + )] + sender_key_file: Option, + + /// Password used to encode the private key + #[structopt(long, short)] + password: Option, + + /// Transaction amount + #[structopt(long, short)] + amount: u64, + }, +} + +#[tokio::main] +async fn main() { + let args = Args::from_args(); + match args { + Args::Generate { + private_key_file, + public_key_file, + password, + } => generate(&private_key_file, &public_key_file, &password), + Args::Send { + recipient_key, + recipient_key_file, + sender_key, + sender_key_file, + password, + amount, + } => { + match send( + recipient_key, + recipient_key_file, + sender_key, + sender_key_file, + password, + amount, + ) + .await + { + Ok(_) => {} + Err(e) => println!("Error {e} when sending transaction to server"), + } + } + } +} diff --git a/src/bin/create_transaction.rs b/src/bin/create_transaction.rs new file mode 100644 index 0000000..069e97f --- /dev/null +++ b/src/bin/create_transaction.rs @@ -0,0 +1,77 @@ +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::utxo::UTXO; +use cleyto_coin::chain::wallet::Wallet; +use reqwest::Client; +use std::error::Error; + +#[tokio::main] +async fn main() { + todo!("The post_json function is not operational right now, because it only creates a random transacion without any arguments"); + #[allow(unreachable_code)] + post_json().await.expect("TODO: panic message"); +} + +// fn create_transaction_json() -> String { +// let (wallet1, mut wallet1_pk) = Wallet::new(); +// let (wallet2, _) = Wallet::new(); +// let transaction_info = TransactionInfo::new(0.3, Utc::now()); +// let signature = wallet1_pk.sign_transaction(&transaction_info).unwrap(); +// let transaction = match Transaction::new(wallet1, wallet2, transaction_info, signature) { +// Ok(tx) => tx, +// Err(e) => match e { +// TransactionValidationError::OpenSSLError(_) => panic!("{e}"), +// TransactionValidationError::ValidationError => panic!("Validation error"), +// }, +// }; +// +// transaction.serialize() +// } + +async fn post_json() -> Result<(), Box> { + // Initialize the HTTP client + let client = Client::new(); + + let (wallet_sender, walletpk_sender) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_sender.clone()), + ]; + let transactioninfo: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match walletpk_sender.sign_transaction(&transactioninfo) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + let transaction: Transaction = + Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature).unwrap(); + let transaction_json = transaction.serialize(); + + // Send the POST request + let response = client + .post("http://localhost:9473/submit-transaction") + .header("Content-Type", "application/json") + .body(transaction_json) + .send() + .await?; + + // Check the response status + let status = response.status(); + let response_body = response.text().await?; + + // Print the response + println!("Response Status: {}", status); + println!("Response Body: {}", response_body); + + Ok(()) +} diff --git a/src/bin/node.rs b/src/bin/node.rs new file mode 100644 index 0000000..cf8cf25 --- /dev/null +++ b/src/bin/node.rs @@ -0,0 +1,36 @@ +use cleyto_coin::{kill_server, run_server, run_server_new_process, run_server_with_gui}; +use structopt::StructOpt; + +#[derive(Debug, StructOpt)] +#[structopt(name = "cleyto-coin-wallet")] +enum Args { + /// Kills the running server + Kill {}, + + /// Start the server. The flag --gui defines if headless or not + Start { + #[structopt(long)] + gui: bool, + + #[structopt(long = "blocking")] + blocking: bool, + }, +} + +fn main() { + let args = Args::from_args(); + + match args { + Args::Kill {} => kill_server(), + Args::Start { gui, blocking } => match gui { + true => run_server_with_gui().unwrap(), + false => { + if blocking { + run_server(); + } else { + run_server_new_process(); + } + } + }, + } +} diff --git a/src/bin/transaction.json b/src/bin/transaction.json new file mode 100644 index 0000000..cdfb8d0 --- /dev/null +++ b/src/bin/transaction.json @@ -0,0 +1 @@ +{"sender":{"public_key":[45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,116,106,113,49,53,100,85,105,108,82,78,52,51,86,86,85,116,74,113,104,10,75,110,80,50,105,118,47,43,84,78,86,110,43,113,50,51,109,110,65,106,85,85,77,65,48,119,90,90,76,87,121,53,53,111,56,101,54,71,107,86,72,84,103,97,57,122,70,78,48,107,77,84,51,106,68,90,102,97,89,120,67,85,67,98,10,102,47,83,108,70,119,66,102,107,103,103,50,66,108,98,47,75,121,82,55,80,108,121,67,100,66,51,67,117,76,43,55,70,74,83,88,111,107,114,108,118,81,87,87,65,107,51,74,70,102,52,69,112,68,77,113,119,80,116,116,65,88,86,72,10,98,79,82,69,71,84,75,47,84,54,86,78,111,74,103,67,65,51,112,116,50,65,77,70,114,116,80,107,67,72,101,89,84,69,101,57,75,107,50,90,112,106,113,115,97,56,114,102,105,49,97,67,66,110,89,69,55,68,51,103,121,57,102,99,10,85,85,121,115,108,56,49,43,110,84,51,85,67,120,114,119,90,86,85,88,105,122,67,122,99,99,112,109,47,100,108,47,48,84,117,111,70,79,87,103,119,57,108,97,105,97,43,73,88,88,50,78,90,76,49,120,111,43,98,48,78,83,52,122,10,113,53,49,90,89,111,49,112,117,80,78,107,76,103,47,122,80,103,117,107,78,73,110,82,80,80,86,90,75,115,119,108,116,115,84,67,122,78,88,88,67,57,84,87,73,111,70,71,53,109,47,117,72,121,97,43,65,77,98,105,85,104,119,75,10,76,81,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10]},"receiver":{"public_key":[45,45,45,45,45,66,69,71,73,78,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10,77,73,73,66,73,106,65,78,66,103,107,113,104,107,105,71,57,119,48,66,65,81,69,70,65,65,79,67,65,81,56,65,77,73,73,66,67,103,75,67,65,81,69,65,56,69,66,114,50,105,88,49,87,66,56,55,52,117,108,55,65,100,100,50,10,76,119,103,119,89,54,90,80,72,81,51,80,121,120,82,50,101,88,99,76,51,51,86,81,85,100,108,43,97,115,48,76,80,116,98,69,116,67,121,79,108,51,117,105,103,43,99,119,77,47,103,85,100,76,122,68,67,98,55,49,117,110,119,118,10,85,114,51,77,57,50,101,100,47,122,78,53,69,68,118,107,114,85,101,78,75,105,108,54,104,81,52,109,70,81,109,87,89,114,82,108,48,104,112,106,98,66,68,115,109,86,84,47,77,110,77,56,57,77,115,48,100,89,74,87,47,78,113,107,10,52,108,76,51,68,50,69,97,118,122,101,82,122,43,47,65,102,112,77,79,98,68,85,120,103,51,69,71,70,107,73,77,110,84,51,43,51,72,109,51,98,50,117,90,78,112,68,74,102,109,72,81,68,85,84,88,75,109,74,69,85,66,48,49,10,113,50,53,90,106,76,118,43,88,119,103,106,72,70,69,80,51,97,75,122,54,114,99,112,87,56,65,47,80,70,70,65,54,47,106,65,103,101,67,47,110,104,79,48,72,70,115,81,43,87,122,106,102,107,122,53,47,102,78,102,86,102,85,88,10,86,72,48,49,113,80,82,67,112,66,90,76,74,70,106,70,43,99,57,112,117,49,69,48,104,81,70,74,88,69,69,114,54,98,113,104,87,122,72,102,71,87,81,65,113,114,68,76,52,70,99,51,103,84,101,115,50,66,81,111,114,65,102,90,10,101,119,73,68,65,81,65,66,10,45,45,45,45,45,69,78,68,32,80,85,66,76,73,67,32,75,69,89,45,45,45,45,45,10]},"signature":[8,188,47,157,206,57,163,165,35,220,116,20,68,189,94,111,49,187,252,228,94,52,73,128,9,34,173,208,81,199,18,3,179,137,110,250,61,20,170,130,190,206,155,152,230,188,32,35,223,192,111,100,249,162,35,18,63,105,3,91,132,61,114,55,169,232,209,127,96,244,7,61,72,147,146,212,45,169,55,136,62,27,182,84,85,82,38,14,200,233,34,74,246,55,48,16,12,119,43,2,209,109,108,234,26,150,109,44,146,24,255,167,14,61,171,154,160,202,73,16,166,196,233,148,109,23,40,25,205,103,131,179,69,87,25,165,166,27,76,54,236,54,210,228,107,73,227,143,43,90,66,49,203,156,177,227,55,191,37,157,57,7,245,119,109,137,142,244,150,47,4,252,112,126,217,66,156,82,10,185,124,139,177,14,84,249,43,153,195,31,147,107,37,204,241,217,128,92,150,74,69,227,41,245,63,159,63,116,37,114,80,172,99,230,157,50,217,70,210,75,173,239,175,142,37,0,38,178,16,47,254,112,48,221,182,164,100,210,181,215,235,66,231,144,175,203,251,220,185,44,126,190,73,170,206,236],"transaction_info":{"value":0.3,"date":"2025-04-30T14:44:49.530070491Z"}} \ No newline at end of file diff --git a/src/chain.rs b/src/chain.rs deleted file mode 100644 index ecaf706..0000000 --- a/src/chain.rs +++ /dev/null @@ -1,51 +0,0 @@ -pub mod utils; -pub mod transaction; -pub mod block; -pub mod wallet; -use block::Block; -use chrono::{DateTime, Utc}; -use sha2::Sha256; -use transaction::Transaction as Transaction; - -pub struct Chain { - blocks: Vec -} - -impl Chain { - - pub fn new() -> Self { - Self{ - blocks: Vec::new() - } - } - - pub fn add_block(&mut self, block: Block){ - self.blocks.push(block); - } - - pub fn create_genesis_block(&mut self) -> Block { - let genesis = Block::genesis_block(); - self.add_block(genesis.clone()); - genesis - } - - fn get_last_hash(&mut self) -> String { - match self.blocks.last() { - Some(block) => block.get_hash(), - None => { - let genesis_block = self.create_genesis_block(); - genesis_block.get_hash() - }, - } - } - - fn get_last_index(&mut self) -> i64 { - match self.blocks.last() { - Some(block) => block.get_index(), - None =>{ - let genesis_block = self.create_genesis_block(); - genesis_block.get_index() - }, - } - } -} diff --git a/src/chain/block.rs b/src/chain/block.rs index 6e1264a..1de08b9 100644 --- a/src/chain/block.rs +++ b/src/chain/block.rs @@ -1,17 +1,19 @@ use chrono::{DateTime, Utc}; -use sha2::{Digest, Sha256}; +use openssl::hash::{Hasher, MessageDigest}; +use serde::{Deserialize, Serialize}; use super::transaction::Transaction; +use super::utils::PROOF_OF_WORK_DIFFICULTY; use super::Chain; - -#[derive(Clone)] +#[derive(Clone, Serialize, Deserialize)] pub struct Block { previous_hash: String, transactions: Vec, - index: i64, + index: u64, timestamp: DateTime, hash: String, + nonce: u64, } impl Block { @@ -19,37 +21,102 @@ impl Block { self.hash.clone() } - pub fn get_index(&self) -> i64 { - self.index.clone() + pub fn get_index(&self) -> u64 { + self.index + } + + /// This merkle tree does not work the same way as the bitcoin core one. If the number of + /// leaves is not a power of two, it copies the last transaction's hash until we have enough + /// leaves for a binary tree, and then it collapses the tree into the root, which is then + /// returned. + fn calculate_merkle_root(transactions: &[Transaction]) -> [u8; 32] { + // This gets the closest bigger power of 2 + let log_2 = f32::log2(transactions.len() as f32); + let mut closest_log_2: u32 = log_2 as u32; + if log_2 > closest_log_2 as f32 { + closest_log_2 += 1; + } + let mut closest_pow_2 = 2usize.pow(closest_log_2); + + // Runs first time to populate the hash_vec with the hash of all transactions + let mut hash_vec = (0..closest_pow_2) + .map(|i| { + let transaction_hash = { + let transaction = match transactions.get(i) { + Some(transaction) => transaction, + None => transactions.last().unwrap(), + }; + transaction.txid + }; + transaction_hash + }) + .collect::>(); + + // Fuck recursion + let result: [u8; 32]; + loop { + if closest_pow_2 == 1 { + result = hash_vec[0]; + break; + } + + for i in (0..closest_pow_2).step_by(2) { + // gets either the hashes in the right index or the last hash on the hash_vec + let (hash_1, hash_2) = { + ( + match hash_vec.get(i) { + Some(hash) => hash, + None => hash_vec.last().unwrap(), + }, + match hash_vec.get(i + 1) { + Some(hash) => hash, + None => hash_vec.last().unwrap(), + }, + ) + }; + let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap(); + hasher.update(hash_1).unwrap(); + hasher.update(hash_2).unwrap(); + hash_vec[i / 2] = hasher.finish().unwrap().as_ref().try_into().unwrap(); + } + closest_pow_2 /= 2; + } + result } pub fn calculate_hash(&self) -> String { - let transactions_string = self.transactions - .iter() - .map(|t| t.to_string()) // Calls the `to_string()` method of `Transaction` - .collect::>() // Collects into a Vec - .join("::END_OF_TRANSACTION::BEGIN_OF_TRANSACTION::"); // Joins all elements with "; " as separator - - let serialized = serde_json::to_string - (&( + //This here should be replaced by the calculation of the merkle tree + // let transactions_string = self + // .transactions + // .iter() + // .map(|t| t.to_string()) // Calls the `to_string()` method of `Transaction` + // .collect::>() // Collects into a Vec + // .join("::END_OF_TRANSACTION::BEGIN_OF_TRANSACTION::"); // Joins all elements with "; " as separator + + let merkle_root = Self::calculate_merkle_root(&self.transactions); + + let serialized = serde_json::to_string(&( "BEGIN::BEGIN_PREVIOUS_HASH::", &self.previous_hash, "::END_PREVIOUS_HASH::BEGIN_TRANSACTIONS::", "BEGIN_OF_TRANSACTION::", - transactions_string, + merkle_root, "::END_OF_TRANSACTION", "::END_TRANSACTIONS::BEGIN_INDEX::", &self.index, "::END_INDEX::BEGIN_TIMESTAMP::", &self.timestamp.to_string(), - "::END_TIMESTAMP::END::", - )).expect("Coudn't serialize the block to create the hash"); + "::END_TIMESTAMP::BEGIN_NONCE::", + &self.nonce, + "::END_NONCE::END", + )) + .expect("Coudn't serialize the block to create the hash"); - println!("behold the serialized block:\n{serialized}"); + // println!("behold the serialized block:\n{serialized}"); - let mut hasher = Sha256::new(); - hasher.update(serialized.as_bytes()); - let result = hasher.finalize(); + let mut hasher = Hasher::new(MessageDigest::sha256()).unwrap(); + hasher.update(serialized.as_bytes()).unwrap(); + let result = hasher.finish().unwrap(); hex::encode(result) // Converts bytes to a hex string } @@ -59,28 +126,66 @@ impl Block { let index = chain.get_last_index() + 1; let timestamp = Utc::now(); - let mut block = Self { previous_hash, transactions, index, timestamp, - hash: String::new() // temporary so that we can calculate hash + hash: String::new(), + nonce: 0, // temporary so that we can calculate hash }; block.hash = block.calculate_hash(); block - } - pub fn genesis_block() -> Self{ + pub fn genesis_block() -> Self { Self { previous_hash: String::from("Foguete nao da re"), transactions: Vec::new(), - index: 0, + index: 1, timestamp: Utc::now(), - hash: String::from("The Times 03/Jan/2009 Chancellor on brink of second bailout for banks") + hash: String::from( + "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks", + ), + nonce: 0, + } + } + + pub fn mine_block(mut self) -> Self { + let prefix = "0".repeat(PROOF_OF_WORK_DIFFICULTY.into()); + + while !self.hash.starts_with(&prefix) { + self.nonce += 1; + self.hash = self.calculate_hash(); } + + self + } + + pub fn test_block(chain: &Chain) -> Self { + let previous_hash = chain.get_last_hash(); + let index = chain.get_last_index() + 1; + let timestamp = Utc::now(); + + let transactions = vec![ + Transaction::default(), + Transaction::default(), + Transaction::default(), + ]; + + let mut block = Self { + previous_hash, + transactions, + index, + timestamp, + hash: String::new(), + nonce: 0, // temporary so that we can calculate hash + }; + + block.hash = block.calculate_hash(); + + block } } diff --git a/src/chain/mod.rs b/src/chain/mod.rs new file mode 100644 index 0000000..02ecd1f --- /dev/null +++ b/src/chain/mod.rs @@ -0,0 +1,46 @@ +pub mod block; +pub mod ordered_vector; +pub mod transaction; +pub mod utils; +pub mod utxo; +pub mod wallet; +mod wallet_pk; +use block::Block; +use serde::{Deserialize, Serialize}; + +#[derive(Default, Serialize, Deserialize)] +pub struct Chain { + blocks: Vec, +} + +impl Chain { + pub fn new() -> Self { + let mut chain = Self { blocks: Vec::new() }; + chain.create_genesis_block(); + chain + } + + pub fn add_block(&mut self, block: Block) { + self.blocks.push(block); + } + + pub fn create_genesis_block(&mut self) -> Block { + let genesis = Block::genesis_block(); + self.add_block(genesis.clone()); + genesis + } + + pub fn get_last_hash(&self) -> String { + self.blocks + .last() + .expect("Chain was created without genesis_block") + .get_hash() + } + + pub fn get_last_index(&self) -> u64 { + self.blocks + .last() + .expect("Chain was created without genesis_block") + .get_index() + } +} diff --git a/src/chain/ordered_vector.rs b/src/chain/ordered_vector.rs new file mode 100644 index 0000000..4f7034f --- /dev/null +++ b/src/chain/ordered_vector.rs @@ -0,0 +1,95 @@ +use std::cmp::Reverse; + +use serde::{Deserialize, Serialize}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct OrderedVec +where + T: std::cmp::Ord, +{ + vec: Vec, +} + +impl OrderedVec +where + T: std::cmp::Ord, +{ + pub fn new() -> Self { + OrderedVec { vec: Vec::new() } + } + pub fn insert(&mut self, value: T) + where + T: Ord, + { + match self.vec.binary_search_by_key(&Reverse(&value), Reverse) { + Ok(_) => {} + Err(pos) => self.vec.insert(pos, value), + } + } + #[allow(dead_code)] + pub fn last(&self) -> Option<&T> { + self.vec.last() + } + #[allow(dead_code)] + pub fn get(&self, index: Idx) -> Option<&T> + where + Idx: std::slice::SliceIndex<[T], Output = T>, + { + self.vec.get(index) + } + pub fn len(&self) -> usize { + self.vec.len() + } + pub fn is_empty(&self) -> bool { + self.vec.len() == 0 + } + pub fn get_slice(&self, range: std::ops::Range) -> &[T] { + &self.vec[range] + } +} + +impl Default for OrderedVec +where + T: std::cmp::Ord, +{ + fn default() -> Self { + Self::new() + } +} + +impl std::ops::Index for OrderedVec +where + Idx: std::slice::SliceIndex<[T], Output = T>, + T: std::cmp::Ord, +{ + type Output = Idx::Output; + + fn index(&self, index: Idx) -> &Self::Output { + &self.vec[index] + } +} + +impl IntoIterator for OrderedVec +where + T: Ord, +{ + type Item = T; + type IntoIter = std::vec::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.vec.into_iter() + } +} + +impl From> for OrderedVec +where + T: Ord, +{ + fn from(vec: Vec) -> Self { + let mut ord_vec = OrderedVec::new(); + for value in vec { + ord_vec.insert(value); + } + ord_vec + } +} diff --git a/src/chain/transaction.rs b/src/chain/transaction.rs index 28de67e..8d86bea 100644 --- a/src/chain/transaction.rs +++ b/src/chain/transaction.rs @@ -1,83 +1,235 @@ -//use std::fmt; - +use super::utxo::UTXO; use super::wallet::Wallet; -use rsa::pkcs1v15::Signature; use chrono::{DateTime, Utc}; +use openssl::error::ErrorStack; +use openssl::sha::Sha256; +use serde::{Deserialize, Serialize}; +use std::fmt; +use std::fmt::Debug; +use std::fmt::Display; -#[derive(Clone)] -#[derive(Debug)] -// ---------------------------------------------- TransactionInfo definition ---------------------------------------- +#[derive(Clone, Debug, Serialize, Deserialize)] +// ---------------------------------------------- TransactionInfo definition ----------------------- pub struct TransactionInfo { - value: f32, - date: DateTime + pub inputs: Vec, + pub outputs: Vec, + pub date: DateTime, } impl TransactionInfo { - pub fn new(value: f32, date: DateTime) -> TransactionInfo { + pub fn new(inputs: Vec, outputs: Vec) -> TransactionInfo { + let date = Utc::now(); Self { - value, - date + inputs, + outputs, + date, } } +} - pub fn to_string(&self) -> String { - format!( - "VALUE::{}::TIME::{}", - self.value.to_string(), - self.date.to_string() - ) +impl Display for TransactionInfo { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let inputs: String = self + .inputs + .clone() + .into_iter() + .map(|input| input.to_string()) + .collect::>() + .join("::"); + + let outputs: String = self + .outputs + .clone() + .into_iter() + .map(|output| output.to_string()) + .collect::>() + .join("::"); + + write!(f, "INPUTS::{}:OUTPUTS::{}", inputs, outputs) + } +} +// ------------------------------------------------------------------------------------------------- + +// --------------------------------------- Transaction Serialization Utils ------------------------- + +// TODO move this to a errors file +#[derive(Debug)] +pub enum TransactionDeserializeError { + InsufficientFunds, + MalformedTransaction, + SerdeError(serde_json::Error), +} +impl fmt::Display for TransactionDeserializeError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransactionDeserializeError::InsufficientFunds => write!(f, "Insufficient funds"), + TransactionDeserializeError::MalformedTransaction => write!(f, "Malformed transaction"), + TransactionDeserializeError::SerdeError(value) => write!(f, "{}", value), + } + } +} +impl std::error::Error for TransactionDeserializeError {} + +#[derive(Debug)] +pub enum TransactionError { + OpenSSLError(ErrorStack), + InsufficientInputs, + ValidationError, + InsufficientFunds, + ConnectionError(String), +} +impl fmt::Display for TransactionError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + TransactionError::OpenSSLError(e) => { + let mut error = String::new(); + error += "The validation of the transaction was not successful due to some \ + internal OpenSSL error:"; + for i in e.errors() { + error += &format!("\n{}", i); + } + write!(f, "{}", error) + } + TransactionError::ValidationError => { + write!( + f, + "The validation of the transaction was not successful, as the signature \ + did not match the provided transaction info." + ) + } + TransactionError::InsufficientInputs => { + write!( + f, + "The transaction was not validated because the inputed UTXOs where not \ + sufficient to cover the outuputed UTXOs." + ) + } + TransactionError::InsufficientFunds => { + write!( + f, + "It wasn't possible to execute the transaction because there weren't enough funds." + ) + } + TransactionError::ConnectionError(_) => { + write!( + f, + "The transaction was not sent to the server due to a connection error." + ) + } + } } } -// ----------------------------------------------------------------------------------------------------------------- +impl std::error::Error for TransactionError {} -// ---------------------------------------------- Transaction definition ------------------------------------------- -#[derive(Clone)] +// ------------------------------------------------------------------------------------------------- + +// ------------------------------------- Transaction definition ------------------------------------ + +#[derive(Clone, Serialize, Deserialize)] pub struct Transaction { pub sender: Wallet, pub receiver: Wallet, - pub signature: Signature, + pub signature: Vec, pub transaction_info: TransactionInfo, + pub txid: [u8; 32], } +// TODO eventually, I want to make the transactions not need to have the sender adress impl Transaction { - pub fn new(sender: Wallet, receiver: Wallet, transaction_info: TransactionInfo, signature: Signature) -> Self { - - let verify_signature = sender.verify_transaction_info(&transaction_info, &signature); - - if verify_signature { - Self { - sender, - receiver, - signature, - transaction_info - } - } else { - panic!("Signature couldn't be verified"); + pub fn new( + sender: Wallet, + receiver: Wallet, + transaction_info: TransactionInfo, + signature: Vec, + ) -> Result { + let mut transaction = Self { + sender, + receiver, + signature, + transaction_info, + txid: [0; 32], // This could be optimized by avoiding the creation of this Vec, which + // serves no function on its own, but I don't really see that being a problem + }; + + let input_sum = UTXO::sum(&transaction.transaction_info.inputs); + let output_sum = UTXO::sum(&transaction.transaction_info.outputs); + let change: i64 = input_sum as i64 - output_sum as i64; + + if change < 0 { + return Err(TransactionError::InsufficientInputs); + } + + let to_hash = transaction.to_string(); + let mut hasher: Sha256 = Sha256::new(); + hasher.update(to_hash.as_bytes()); + transaction.txid = hasher.finish().to_owned(); + + match transaction.verify() { + Ok(()) => Ok(transaction), + Err(error) => Err(error), } } - pub fn to_string(&self) -> String { - format!( - "SENDER::{}::RECEIVER::{}::{}::SIGNATURE::{}", - self.sender.to_string(), - self.receiver.to_string(), - self.transaction_info.to_string(), - self.signature.to_string() - ) + pub(crate) fn verify(&self) -> Result<(), TransactionError> { + match self + .sender + .verify_transaction_info(&self.transaction_info, &self.signature) + { + Ok(value) => match value { + true => Ok(()), + false => Err(TransactionError::ValidationError), + }, + Err(stack) => Err(TransactionError::OpenSSLError(stack)), + } + } + + pub fn serialize(&self) -> String { + serde_json::to_string(self).unwrap() + } + pub fn deserialize(json: String) -> Result { + let tx: Transaction = match serde_json::from_str(&json) { + Ok(tx) => tx, + Err(e) => return Err(TransactionDeserializeError::SerdeError(e)), + }; + + let input_sum = UTXO::sum(&tx.transaction_info.inputs); + let output_sum = UTXO::sum(&tx.transaction_info.outputs); + let change = input_sum - output_sum; + + if change < 1 { + return Err(TransactionDeserializeError::InsufficientFunds); + } + + Ok(tx) + } +} + +impl Default for Transaction { + fn default() -> Self { + let (sender, sender_pk) = Wallet::new(); + let (receiver, _) = Wallet::new(); + + let value: u32 = rand::random(); + + let transaction_info = TransactionInfo::new( + vec![UTXO::new(value as u64, sender.clone())], + vec![UTXO::new(value as u64, receiver.clone())], + ); + + let signature = sender_pk.sign_transaction(&transaction_info).unwrap(); + Transaction::new(sender, receiver, transaction_info, signature).unwrap() } } -// ---------------------------------------------- UNIT TESTS ------------------------------------------------------- -#[cfg(test)] -mod tests { - use crate::chain::{transaction::TransactionInfo, wallet::Wallet}; - use chrono::Utc; - - #[test] //mark a function as a test. - fn test_transactioninfo_creation() { - let transaction: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - println!("transaction info:\n{}", transaction.to_string()); - println!("{:?}", transaction); +impl Display for Transaction { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "SENDER::{:?}::RECEIVER::{:?}::{}::SIGNATURE::{:?}", + self.sender, + self.receiver.to_pem(), + self.transaction_info, + self.signature + ) } } -// ----------------------------------------------------------------------------------------------------------------- diff --git a/src/chain/utils.rs b/src/chain/utils.rs index 5ebe58e..77945fb 100644 --- a/src/chain/utils.rs +++ b/src/chain/utils.rs @@ -1,61 +1,38 @@ -use sha2::{Sha256, Digest}; +use sha2::{Digest, Sha256}; +pub const PROOF_OF_WORK_DIFFICULTY: u8 = 4; -pub struct HashedData{ - hash: [u8;32], +pub struct HashedData { + hash: [u8; 32], } impl HashedData { - pub fn new(data: &[u8]) -> Self{ - let new_data: Result<[u8; 32], _> = data.try_into().map_err(|_| "Slice has a different length than 32"); + pub fn new(data: &[u8]) -> Self { + let new_data: Result<[u8; 32], _> = data + .try_into() + .map_err(|_| "Slice has a different length than 32"); match new_data { - Ok(arr) => Self{ hash: arr }, - Err(e) => panic!("Couldnt convert data to fit into HashedData object\nError: {e}") + Ok(arr) => Self { hash: arr }, + Err(e) => panic!("Couldnt convert data to fit into HashedData object\nError: {e}"), } } - pub fn from_string(str: &String) -> Self{ + pub fn from_string(str: &String) -> Self { let mut hasher = Sha256::new(); hasher.update(str.as_bytes()); let hash_result = hasher.finalize(); // GenericArray Self { - hash: hash_result.into() // GenericArray to [u8; 32] + hash: hash_result.into(), // GenericArray to [u8; 32] } } - + pub fn get_hash(&self) -> [u8; 32] { self.hash } - pub fn hash_as_string(&self) -> String{ + pub fn hash_as_string(&self) -> String { let mut return_str = String::new(); return_str.push(self.hash[0] as char); return_str } } - -/* pub struct Date{ - year: u32, - month: u32, - day: u32, - hour: u32, - minute: u32, - second: u32, -} - -impl Date { - pub fn now() -> Self { - let now = Local::now(); - Self { - year: now.year_ce().1, - month: now.month(), - day: now.day(), - hour: now.hour(), - minute: now.minute(), - second: now.second(), - } - } - fn to_string(&self) -> String { - format!("{}.{}.{}.{}.{}.{}", self.year, self.month, self.day, self.hour, self.minute, self.second) - } -} */ \ No newline at end of file diff --git a/src/chain/utxo.rs b/src/chain/utxo.rs new file mode 100644 index 0000000..27a944d --- /dev/null +++ b/src/chain/utxo.rs @@ -0,0 +1,83 @@ +use core::panic; +use std::{cmp::Ordering, fmt::Display}; + +use serde::{Deserialize, Serialize}; + +use super::wallet::Wallet; + +#[derive(Debug, Deserialize, Serialize, Clone)] +pub struct UTXO { + value: u64, + owner: Wallet, +} +impl UTXO { + pub fn new(value: u64, owner: Wallet) -> Self { + Self { value, owner } + } + pub fn value(&self) -> u64 { + self.value + } + pub fn owner(&self) -> Wallet { + self.owner.clone() + } + pub fn sum(vec: &T) -> u64 + where + T: IntoIterator, + T: Clone, + { + vec.clone().into_iter().map(|utxo| utxo.value).sum() + } +} + +impl Display for UTXO { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let owner_pem = self.owner.to_pem(); + let owner = if let Ok(val) = String::from_utf8(owner_pem) { + val + } else { + panic!("Invalid UTF-8 when getting UTXO owner") + }; + write!(f, "VALUE::{}::OWNER::{}", self.value, owner) + } +} + +impl PartialEq for UTXO { + fn eq(&self, other: &Self) -> bool { + self.value == other.value && self.owner == other.owner + } +} +impl PartialOrd for UTXO { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} +impl Eq for UTXO {} +impl Ord for UTXO { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.value.cmp(&other.value) + } + + fn max(self, other: Self) -> Self + where + Self: Sized, + { + if other.value < self.value { + self + } else { + other + } + } + + fn min(self, other: Self) -> Self + where + Self: Sized, + { + if other.value < self.value { + other + } else { + self + } + } +} + +// If you want to use sum for a collection of UTXOs, use the func UTXO::sum() diff --git a/src/chain/wallet.rs b/src/chain/wallet.rs index 022a9c5..45f0958 100644 --- a/src/chain/wallet.rs +++ b/src/chain/wallet.rs @@ -1,85 +1,528 @@ -use rsa::{RsaPrivateKey, RsaPublicKey}; -use rsa::pkcs1v15::{SigningKey, VerifyingKey}; -use rsa::signature::{Keypair, RandomizedSigner, Verifier}; -use rsa::pkcs1::EncodeRsaPublicKey; -use rsa::sha2::Sha256; +use crate::chain::ordered_vector::OrderedVec; +use crate::chain::utxo::UTXO; + use super::transaction::TransactionInfo; +use openssl::error::ErrorStack; +pub use super::wallet_pk::WalletPK; +use openssl::hash::MessageDigest; +use openssl::pkey::{PKey, Public}; +use openssl::rsa::Rsa; +use openssl::sign::Verifier; +use serde::de::{self, Error}; +use serde::{Deserialize, Serialize}; -// ---------------------------------------------- WalletPK definition ---------------------------------------------- +// ------------------------------------------- Wallet errors definition -------------------------------------------- #[derive(Debug)] -pub struct WalletPK{ - #[allow(unused)] - private_key: RsaPrivateKey, - signing_key: SigningKey +pub enum WalletError { + InsufficientFunds, +} +impl std::fmt::Display for WalletError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Insufficient funds on the wallet to conduct operation") + } +} +impl std::error::Error for WalletError {} +// ----------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Serde stuff --------------------------------------------------- +fn serialize_public_key(key: &PKey, serializer: S) -> Result +where + S: serde::Serializer, +{ + let processed: Vec = key.public_key_to_pem().map_err(serde::ser::Error::custom)?; + processed.serialize(serializer) } -impl WalletPK { - pub fn sign_transaction(&mut self, transaction_info: &TransactionInfo) -> Result{ - let signed_hashed_message = self.signing_key.sign_with_rng(&mut rand::thread_rng(), transaction_info.to_string().as_bytes()); +fn deserialize_public_key<'de, D>(deserializer: D) -> Result, D::Error> +where + D: de::Deserializer<'de>, +{ + struct StringVisitor; + impl<'de> de::Visitor<'de> for StringVisitor { + type Value = PKey; - Ok(signed_hashed_message) + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("The PEM string as a vector of u8") + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: Error, + { + PKey::public_key_from_pem(v).map_err(E::custom) + } + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + PKey::public_key_from_pem(v.as_bytes()).map_err(E::custom) + } + } + + // use our visitor to deserialize an `ActualValue` + deserializer.deserialize_any(StringVisitor) +} +// ----------------------------------------------------------------------------------------------------------------- + +// ------------------------------------------------- Traits stuff -------------------------------------------------- +impl PartialEq for Wallet { + fn eq(&self, other: &Self) -> bool { + self.to_pem() == other.to_pem() + } +} + +impl From for Wallet { + fn from(value: String) -> Self { + let public_rsa = openssl::rsa::Rsa::public_key_from_pem(value.as_bytes()) + .expect("Could not read the public key"); + let public_key = + PKey::from_rsa(public_rsa).expect("Error converting from RSA to PKey"); + Self { + public_key, + available_utxos: None, + } + } +} +impl From> for Wallet { + fn from(public_key: PKey) -> Self { + Self { + public_key, + available_utxos: None, + } } } // ----------------------------------------------------------------------------------------------------------------- // ---------------------------------------------- Wallet definition ------------------------------------------------ -#[derive(Clone)] -#[derive(Debug)] -pub struct Wallet{ - public_key: RsaPublicKey, - verifying_key: VerifyingKey +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Wallet { + #[serde( + serialize_with = "serialize_public_key", + deserialize_with = "deserialize_public_key" + )] + pub(crate) public_key: PKey, + pub(crate) available_utxos: Option>, } +const MAX_UTXO_SEARCH_DEPTH: usize = 100; +const UTXO_WEIGHT: u64 = 64; // typical weight of a P2PKH output - replace with real weight +const LONG_TERM_FEE_RATE: u64 = 1; // placeholder - replace with real rate +const MAX_ITERATIONS_NON_EXACT_COIN_SELECTION: u64 = 10_000; + +// Helper for the coin selection algorithms +#[derive(Clone)] +struct UtxoEstimate { + utxo: UTXO, + effective_value: u64, + weight: u64, +} impl Wallet { + /* --------------------------------------------------------------------- * + * Construction & Utilities * + * --------------------------------------------------------------------- */ + + /// Creates a fresh key pair and returns `(wallet, private_key_wrapper)`. pub fn new() -> (Self, WalletPK) { - let bits: usize = 2048; - let private_key: RsaPrivateKey = RsaPrivateKey::new(&mut rand::thread_rng(), bits).expect("failed to generate a key"); - let public_key: RsaPublicKey = RsaPublicKey::from(&private_key); - let signing_key: SigningKey = SigningKey::::new(private_key.clone()); - let verifying_key = signing_key.verifying_key(); - + // 2048‑bit RSA key – adjust size if you need stronger security. + let rsa = Rsa::generate(2048).expect("RSA generation failed"); + let private_key = PKey::from_rsa(rsa).expect("Invalid RSA key"); + + // Export the public part as PEM and immediately parse it back. + let public_key = PKey::public_key_from_pem( + &private_key + .public_key_to_pem() + .expect("Could not extract public key from private key"), + ) + .expect("Failed to parse PEM public key"); + ( - Wallet{ - public_key, - verifying_key + Wallet { + public_key, + available_utxos: None, }, - WalletPK{ - private_key, - signing_key, } + WalletPK { private_key }, ) } - pub fn verify_transaction_info(&self, data: &TransactionInfo, signature: &rsa::pkcs1v15::Signature) -> bool { - let verified = self.verifying_key.verify(data.to_string().as_bytes(), &signature); - match verified { - Ok(()) => true, - Err(_) => false, + /// Verify a signed `TransactionInfo` using the stored public key. + pub fn verify_transaction_info( + &self, + transaction_info: &TransactionInfo, + signature: &[u8], + ) -> Result { + let mut verifier = Verifier::new(MessageDigest::sha256(), &self.public_key)?; + verifier.update(transaction_info.to_string().as_bytes())?; + verifier.verify(signature) + } + + /// Export the public key as PEM bytes. + pub fn to_pem(&self) -> Vec { + self.public_key + .public_key_to_pem() + .expect("PEM conversion failed") + } + + /// Rough fee estimate per UTXO – replace with a real estimator later. + fn estimate_fee_per_utxo(_utxo: &UTXO) -> u64 { + 100 + } + + /// Insert a batch of new UTXOs, keeping the internal ordering intact. + pub fn add_utxos(&mut self, new_vec: Vec) { + match &mut self.available_utxos { + Some(ord_vec) => { + for utxo in new_vec { + ord_vec.insert(utxo); + } + } + None => self.available_utxos = Some(OrderedVec::from(new_vec)), + } + } + + /* --------------------------------------------------------------------- * + * Coin‑Selection Logic * + * --------------------------------------------------------------------- */ + /// Public entry point – selects a set of UTXOs whose summed value covers + /// `amount`. Returns an error if the wallet does not contain enough funds. + pub fn get_utxos(&self, amount: u64) -> Result, WalletError> { + let utxos = self + .available_utxos + .as_ref() + .ok_or(WalletError::InsufficientFunds)? + .clone(); + + // Quick check – if the total balance is insufficient we can bail early. + if UTXO::sum(&utxos) < amount { + return Err(WalletError::InsufficientFunds); + } + + // 1️⃣ Simple exact‑match shortcut. + if let Some(single) = utxos.clone().into_iter().find(|u| u.value() == amount) { + return Ok(vec![single.clone()]); + } + + // 2️⃣ Find the smallest UTXO that already exceeds the target. + let first_over_idx = utxos + .clone() + .into_iter() + .position(|u| u.value() < amount) + .unwrap_or(0); + + // 3️⃣ Branch‑and‑bound selection on the remaining (smaller) UTXOs. + + let dust_threshold = Self::estimate_fee_per_utxo(&utxos[0]) * 3; + let smaller_utxos = &utxos.get_slice(first_over_idx..utxos.len()); + + let mut total_sum = 0; + let estimates: Vec = smaller_utxos + .iter() + .map(|u| { + total_sum += u.value(); + UtxoEstimate { + utxo: u.clone(), + effective_value: u.value() - Self::estimate_fee_per_utxo(u), + weight: UTXO_WEIGHT, + } + }) + .collect(); + let (bnb_solution, _) = self.branch_and_bound( + &estimates, + amount, + dust_threshold, + 100_000, // max repetitions + total_sum, + ); + + if !bnb_solution.is_empty() { + return Ok(bnb_solution); + } else { + println!("bnb solution is empty"); + } + + // 4️⃣ Pick the “best” solution (the one with the lowest waste that still + // satisfies the target). If none exists we fall back to the exact‑match + // error (already handled at the top). + + // If we found a single “big enough” UTXO, keep it as a candidate solution. + let mut candidate_solutions = Vec::new(); + if first_over_idx > 0 { + if let Some(utxo) = utxos.get(first_over_idx - 1) { + candidate_solutions.push(vec![utxo.clone()]); + } + } + + let target = amount + dust_threshold; + let solution = Self::dantes_crazy_algorithm_entrypoint(smaller_utxos, target); + + if solution.is_empty() { + return Err(WalletError::InsufficientFunds); + } + Ok(solution) + } + + // Helper: compute “waste” (extra fee paid beyond the long‑term rate). + fn waste(estimates: &[UtxoEstimate], fee_rate: u64) -> u64 { + let total_weight: u64 = estimates.iter().map(|e| e.weight).sum(); + // println!( + // "waste is {}, total_weight is {}, fee rate is {}", + // total_weight * (fee_rate - LONG_TERM_FEE_RATE), + // total_weight, + // fee_rate + // ); + total_weight * (fee_rate - LONG_TERM_FEE_RATE) + } + /// Core branch‑and‑bound algorithm – returns a tuple `(selected_utxos, total_value)`. + fn branch_and_bound( + &self, + estimates: &[UtxoEstimate], + target: u64, + dust_threshold: u64, + max_reps: usize, + total_sum: u64, + ) -> (Vec, u64) { + // Transform each UTXO into an enriched struct that carries an estimated + // “effective value” (value minus fee) and its weight. + + // for est in estimates.clone() { + // println!( + // "UTXOEstimate (effective_value: {}, weight: {} )", + // est.utxo.value() - Self::estimate_fee_per_utxo(&est.utxo), + // UTXO_WEIGHT + // ); + // } + + // Recursive branch‑and‑bound search. + #[allow(clippy::too_many_arguments)] + fn recurse( + remaining: &[UtxoEstimate], + cur_sum: u64, + cur_set: Vec, + sum_left: u64, + best_set: &mut Vec, + best_sum: &mut u64, + best_waste: &mut u64, + target_interval: (u64, u64), + fee_rate: u64, + reps_left: usize, + ) { + if reps_left == 0 || remaining.is_empty() { + return; + } + + // Update the amount left after discarding the current head’s fee. + let sum_left = sum_left - remaining[0].effective_value; + + // ---------- Include the head ---------- + let mut incl_set = cur_set.clone(); + incl_set.push(remaining[0].clone()); + let incl_sum = cur_sum + remaining[0].effective_value; + let incl_waste = Wallet::waste(&incl_set, fee_rate); + + if incl_sum >= target_interval.0 + && incl_sum <= target_interval.1 + && incl_waste < *best_waste + { + *best_sum = incl_sum; + *best_set = incl_set.clone(); + *best_waste = incl_waste; + // Found a feasible solution - we can stop exploring this branch. + return; + } + + if incl_sum < target_interval.1 + && sum_left as i64 > target_interval.0 as i64 - incl_sum as i64 + { + recurse( + &remaining[1..], + incl_sum, + incl_set, + sum_left, + best_set, + best_sum, + best_waste, + target_interval, + fee_rate, + reps_left - 1, + ); + } + + // ---------- Omit the head ---------- + if sum_left as i64 > target_interval.0 as i64 - cur_sum as i64 { + recurse( + &remaining[1..], + cur_sum, + cur_set, + sum_left, + best_set, + best_sum, + best_waste, + target_interval, + fee_rate, + reps_left - 1, + ); + } } + + // Initialise the search. + let mut best_set = Vec::new(); + let mut best_sum = u64::MAX; + let mut best_waste = u64::MAX; + recurse( + estimates, + 0, + Vec::new(), + total_sum, + &mut best_set, + &mut best_sum, + &mut best_waste, + (target, target + dust_threshold), + 2, // fee_rate placeholder – replace with real rate + max_reps, + ); + + // Strip the auxiliary data and return plain UTXOs. + let selected = best_set.into_iter().map(|e| e.utxo).collect::>(); + let total_selected = UTXO::sum(&selected); + (selected, total_selected) } - pub fn to_string(&self) -> String { - self.public_key.to_pkcs1_pem(rsa::pkcs1::LineEnding::LF).unwrap().to_string() + fn calculate_recursion_depth( + max_depth: usize, + elements_tested: usize, + total_elements: usize, + ) -> i32 { + if total_elements == 0 { + return 0; + } + let fraction_used = elements_tested as f64 / total_elements as f64; + let new_depth = (max_depth as f64 * (1.0 - fraction_used)).ceil() as i32; + println!("The result of the calculate_recursion_depth func is {new_depth}"); + std::cmp::max(new_depth, 1) } - #[allow(unused)] - pub fn get_public_key(&self) -> RsaPublicKey { - self.public_key.clone() + fn dantes_crazy_algorithm_entrypoint(slice: &[UTXO], target: u64) -> Vec { + let mut solution: Vec = Vec::new(); + let mut solution_waste = u64::MAX; + + let slice_estimates: Vec = slice + .iter() + .map(|utxo| UtxoEstimate { + utxo: utxo.clone(), + effective_value: utxo.value() - Wallet::estimate_fee_per_utxo(utxo), + weight: UTXO_WEIGHT, + }) + .collect(); + + Self::dantes_crazy_coin_selection_algorithm( + &slice_estimates, + slice_estimates.len() / 2, + &mut solution, + &mut solution_waste, + target, + -1, + 2, // TODO get the real fee_rate + 1, + ); + + solution.into_iter().map(|estimate| estimate.utxo).collect() } -} -// ----------------------------------------------------------------------------------------------------------------- -// ---------------------------------------------- UNIT TESTS ------------------------------------------------------- -#[cfg(test)] //ensures that the tests module is only included when running tests. -mod tests { - use crate::chain::wallet::Wallet; + fn dantes_crazy_coin_selection_algorithm( + slice: &[UtxoEstimate], + middle_index: usize, + solution: &mut Vec, + solution_waste: &mut u64, + target: u64, + mut number_of_iterations: i32, // initialize with -1 + fee_rate: u64, + position: u64, + ) { + if number_of_iterations == -1 { + println!("number_of_iterations is {number_of_iterations}"); + } + if position == MAX_ITERATIONS_NON_EXACT_COIN_SELECTION + || number_of_iterations == 0 + || slice.is_empty() + { + return; + } + let mut sum = 0; + let mut elements: Vec = match slice.get(middle_index) { + Some(_) => vec![slice[middle_index].clone()], + None => return, + }; + sum += slice[middle_index].utxo.value(); + + if number_of_iterations == -1 { + println!("number_of_iterations is still {number_of_iterations}"); + } + for k in 2..slice.len() + 1 { + // Gets the middle_index + or - k/2 depending on whether k is even or odd. Also checks + // if the resulting index is smaller than 0, and if so skips the iteration + let index = match k % 2 { + 1 => { + let x = middle_index as i32 - (k as i32 / 2); + if x < 0 { + continue; + } else { + x as usize + } + } + 0 => middle_index + k / 2, + _ => unreachable!(), + }; + + if let Some(x) = slice.get(index) { + sum += x.utxo.value(); + elements.push(x.clone()); + // println!( + // "Pushing {} to sum {} on position {position} with target {target}", + // slice[index].utxo.value(), + // sum + // ); + } + if sum > target { + // if there's no x yet, we calculate it here. The x is a function of the number + // of elements necessary in the first iteration. If many elements are needed in the + // first iteration, that means that if I continue for too many times there will be a + // lot of overlap. Therefore, we reduce the size of x + if number_of_iterations == -1 { + number_of_iterations = + Self::calculate_recursion_depth(MAX_UTXO_SEARCH_DEPTH, k, slice.len()); + println!("Caculated the recursion depth to be {number_of_iterations}"); + } + let new_waste = Self::waste(&elements, fee_rate); + if new_waste < *solution_waste { + *solution = elements.clone(); + *solution_waste = new_waste; + } - #[test] //mark a function as a test. - fn test_wallet_creation() { - let (wallet, wallet_pk) = Wallet::new(); - println!("wallet.to_string: {}", wallet.to_string()); - println!("{:#?}", wallet); - println!("{:#?}", wallet_pk); + break; + } + } + + Self::dantes_crazy_coin_selection_algorithm( + slice, + middle_index / 2, + solution, + solution_waste, + target, + number_of_iterations - 1, + fee_rate, + position * 2, + ); + Self::dantes_crazy_coin_selection_algorithm( + slice, + middle_index * 3 / 2, + solution, + solution_waste, + target, + number_of_iterations - 1, + fee_rate, + position * 2 + 1, + ); } } + // ----------------------------------------------------------------------------------------------------------------- diff --git a/src/chain/wallet_pk.rs b/src/chain/wallet_pk.rs new file mode 100644 index 0000000..7bdfb9a --- /dev/null +++ b/src/chain/wallet_pk.rs @@ -0,0 +1,51 @@ +use super::transaction::TransactionInfo; +use super::wallet::Wallet; +use openssl::error::ErrorStack; +use openssl::hash::MessageDigest; +use openssl::pkey::{PKey, Private}; +use openssl::sign::Signer; +use openssl::symm::Cipher; +// ---------------------------------------------- WalletPK definition ---------------------------------------------- +#[derive(Debug)] +pub struct WalletPK { + pub(crate) private_key: PKey, +} + +impl WalletPK { + pub fn sign_transaction( + &self, + transaction_info: &TransactionInfo, + ) -> Result, ErrorStack> { + let mut signer = Signer::new(MessageDigest::sha256(), &self.private_key)?; + signer.sign_oneshot_to_vec(transaction_info.to_string().as_bytes()) + } + pub fn to_pem_with_password(&self, password: &String) -> Vec { + self.private_key + .private_key_to_pem_pkcs8_passphrase(Cipher::aes_256_cbc(), password.as_bytes()) + .unwrap() + } + pub fn to_pem(&self) -> Vec { + self.private_key.private_key_to_pem_pkcs8().unwrap() + } + pub fn public_wallet(&self) -> Wallet { + let public_key = PKey::public_key_from_pem( + &self + .private_key + .public_key_to_pem() + .expect("Could not extract Publick Key from Private Key"), + ) + .unwrap(); + + Wallet { + public_key, + available_utxos: None, + } + } +} +impl From> for WalletPK { + fn from(private_key: PKey) -> Self { + Self { private_key } + } +} + +// ----------------------------------------------------------------------------------------------------------------- diff --git a/src/configs.rs b/src/configs.rs new file mode 100644 index 0000000..e701d80 --- /dev/null +++ b/src/configs.rs @@ -0,0 +1 @@ +pub const KILL_SERVER_SOCKET_PATH: &str = "/tmp/cleyto_coin/kill_server.sock"; diff --git a/src/lib.rs b/src/lib.rs index 28ac57d..806c1af 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1 +1,211 @@ +use crate::{ + chain::{ + transaction::{self, Transaction, TransactionError, TransactionInfo}, + utxo::UTXO, + wallet::{Wallet, WalletPK}, + Chain, + }, + node::ui::App, +}; +use openssl::pkey::{PKey, Private, Public}; +use reqwest::{Client, StatusCode}; +use std::{ + io::Write, + os::unix::net::UnixListener, + path::PathBuf, + process::{Command, Stdio}, +}; +use std::{sync::Arc, thread}; +mod configs; +use configs::KILL_SERVER_SOCKET_PATH; + pub mod chain; +pub mod node; + +async fn send_transaction(transaction: transaction::Transaction) -> Result<(), TransactionError> { + let client = Client::new(); + + let transaction_json = transaction.serialize(); + + // Send the POST request + let response = client + .post("http://localhost:9473/submit-transaction") + .header("Content-Type", "application/json") + .body(transaction_json) + .send() + .await + .unwrap(); + + // Check the response status + let status = response.status(); + let response_body = response.text().await.unwrap(); + match status { + StatusCode::OK => Ok(()), + + _ => Err(TransactionError::ConnectionError(format!( + "Error: {status}\n{response_body}" + ))), + } +} + +fn read_key_string_or_file(string: &Option, file: &Option) -> String { + if let Some(s) = string { + s.clone() + } else if let Some(path) = file { + std::fs::read_to_string(path).expect("Failed to read key file") + } else { + panic!("Key not provided"); + } +} + +pub fn generate(private_key_file: &PathBuf, public_key_file: &PathBuf, password: &Option) { + let (wallet, walletpk) = Wallet::new(); + + let parents = [ + private_key_file + .parent() + .expect("The path provided has no parent"), + public_key_file + .parent() + .expect("The path provided has no parent"), + ]; + + // Checks to see if parents exist. if not, creates them + for parent in parents { + if !parent.exists() { + std::fs::create_dir_all(parent).expect("Could not create parent directory"); + } + } + + if let Some(password) = password { + std::fs::write(private_key_file, walletpk.to_pem_with_password(password)) + .expect("Could not write new wallet's private key to file"); + } else { + std::fs::write(private_key_file, walletpk.to_pem()) + .expect("Could not write new wallet's private key to file"); + } + + std::fs::write(public_key_file, wallet.to_pem()) + .expect("Could not write new wallet's public key to file"); +} + +pub async fn send( + recipient_key: Option, + recipient_key_file: Option, + sender_key: Option, + sender_key_file: Option, + password: Option, + amount: u64, +) -> Result<(), TransactionError> { + let recipient_key_str = read_key_string_or_file(&recipient_key, &recipient_key_file); + let sender_key_str = read_key_string_or_file(&sender_key, &sender_key_file); + + // convert to PKey objects + let sender_pkey: PKey = if let Some(password) = password { + PKey::private_key_from_pem_passphrase(sender_key_str.as_bytes(), password.as_bytes()) + .expect("Failed to parse sender private key") + } else { + PKey::private_key_from_pem(sender_key_str.as_bytes()) + .expect("Failed to parse sender private key") + }; + + let recipient_pkey: PKey = PKey::public_key_from_pem(recipient_key_str.as_bytes()) + .expect("Failed to parse recipient public key"); + + // create wallets + let sender_wallet = WalletPK::from(sender_pkey); + let recipient_wallet = Wallet::from(recipient_pkey); + + // find input utxos + let input_utxos = match sender_wallet.public_wallet().get_utxos(amount) { + Ok(vec) => vec, + Err(_) => return Err(TransactionError::InsufficientFunds), + }; + + // Create output UTXOs + let input_sum = UTXO::sum(&input_utxos); + let rec_utxo = UTXO::new(amount, recipient_wallet.clone()); + let change_utxo = UTXO::new(input_sum - amount, sender_wallet.public_wallet()); + let output_utxos = vec![change_utxo, rec_utxo]; + + // create transaction info + let transaction_info = TransactionInfo::new(input_utxos, output_utxos); + + // sign the transaction + let signature = sender_wallet + .sign_transaction(&transaction_info) + .expect("Failed on signing of transaction"); + + let transaction = Transaction::new( + sender_wallet.public_wallet(), + recipient_wallet, + transaction_info, + signature, + ) + .inspect_err(|e| eprintln!("Failed creating the transaction: {e}")) + .unwrap(); + + send_transaction(transaction).await +} + +pub fn run_server_with_gui() -> color_eyre::Result<()> { + // Channel to kill thread + // let rx = Arc::new(Mutex::new(rx)); + + let (mut node, logger) = node::Node::new(Chain::new()); + + // Run server thread + let server = thread::spawn(move || { + // let rx = Arc::clone(&rx); + + node.run(true, 0); + }); + + color_eyre::install()?; + let terminal = ratatui::init(); + let result = App::new(Arc::clone(&logger), node::Node::DEFAULT_PORT).run(terminal); + ratatui::restore(); + + // Quits server + kill_server(); + + server.join().unwrap(); + result +} + +/// Spawns thread with server and return the channel that sends the kill signal +/// Mostly useful for testing +pub fn run_server_thread() { + let (mut node, _) = node::Node::new(Chain::new()); + thread::spawn(move || { + node.run(true, 0); + }); +} + +pub fn run_server() { + let (mut node, _) = node::Node::new(Chain::new()); + node.run(true, 0); +} +pub fn run_server_new_process() { + #[allow(clippy::zombie_processes)] + let child = Command::new(std::env::current_exe().unwrap()) + .arg("start") + .arg("--blocking") + .stdout(Stdio::null()) + .stdin(Stdio::null()) + .spawn() + .expect("Failed to start server process"); + println!("Spawned process with pid {}", child.id()); +} + +/// Sends the kill signal to the server +pub fn kill_server() { + if std::path::Path::new(KILL_SERVER_SOCKET_PATH).exists() { + std::fs::remove_file(KILL_SERVER_SOCKET_PATH).unwrap(); + } + let listener = UnixListener::bind(KILL_SERVER_SOCKET_PATH).expect("Could not bind socket"); + let (mut stream, _) = listener.accept().expect("No one connected to the listener"); + stream + .write_all("kill".as_bytes()) + .expect("Error writing kill signal"); +} diff --git a/src/node/config b/src/node/config new file mode 100644 index 0000000..f602c2c --- /dev/null +++ b/src/node/config @@ -0,0 +1,2 @@ +RESOURCES_FOLDER="./resources" +CHAIN_FILE="chain.bin" \ No newline at end of file diff --git a/src/node/example_http_get.yml b/src/node/example_http_get.yml new file mode 100644 index 0000000..14a7a3e --- /dev/null +++ b/src/node/example_http_get.yml @@ -0,0 +1,25 @@ +POST / HTTP/1.1 +Host: localhost:9473 +Connection: keep-alive +sec-ch-ua: "Not(A:Brand";v="99", "Brave";v="133", "Chromium";v="133" +sec-ch-ua-mobile: ?0 +sec-ch-ua-platform: "Linux" +DNT: 1 +Upgrade-Insecure-Requests: 1 +User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36 +Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8 +Sec-GPC: 1 +Accept-Language: en-US,en;q=0.6 +Sec-Fetch-Site: none +Sec-Fetch-Mode: navigate +Sec-Fetch-User: ?1 +Sec-Fetch-Dest: document +Accept-Encoding: gzip, deflate, br, zstd +Cookie: session=eyJpZCI6ICIxZjZjNDBiNS02YTBkLTRiOWYtOGY4Yy1kYzQ4N2NjYmQ5OT + + +POST / HTTP/1.1 +content-type: application/json +content-length: 36 +accept: */* +host: localhost:9473 \ No newline at end of file diff --git a/src/node/logger.rs b/src/node/logger.rs new file mode 100644 index 0000000..76556af --- /dev/null +++ b/src/node/logger.rs @@ -0,0 +1,381 @@ +use chrono::prelude::Utc; +use serde::{self, Deserialize, Serialize}; +use std::io::{self}; +use std::sync::{Mutex, PoisonError}; + +#[derive(Default, Serialize, Deserialize)] +pub struct Logger { + logs: Mutex>, + temp_logs: Mutex>, +} + +#[derive(Debug)] +pub enum LoggerError { + PoisonError(String), + FileWriteError(std::io::Error), +} +impl From> for LoggerError { + fn from(value: PoisonError) -> LoggerError { + LoggerError::PoisonError(value.to_string()) + } +} +impl From for LoggerError { + fn from(value: std::io::Error) -> Self { + LoggerError::FileWriteError(value) + } +} + +impl Logger { + pub fn new() -> Self { + Self { + logs: Mutex::new(Vec::new()), + temp_logs: Mutex::new(Vec::new()), + } + } + + fn temp_log(&self, log: String) { + let mut temp_logs = self.temp_logs.lock().unwrap(); // Lock the Mutex to modify the temp_logs + temp_logs.push(log); + if temp_logs.len() > 50 { + temp_logs.remove(0); // Remove the oldest log if there are more than 50 + } + } + + fn log_internal(&self, log: String) { + let mut logs = self.logs.lock().unwrap(); // Lock the Mutex to modify the logs + logs.push(log); + + if let Err(e) = std::fs::write("/tmp/foo", logs.join("\n").as_bytes()) { + eprintln!("Unable to write to log file: {e}"); + } + } + pub fn log_error(&self, log: String) { + let dt = Utc::now(); + self.log_internal(format!( + "[ERROR] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + self.temp_log(format!( + "[ERROR] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + } + pub fn log(&self, log: String) { + let dt = Utc::now(); + self.log_internal(format!( + "[LOG] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + self.temp_log(format!( + "[LOG] {} | {}", + dt.format("%Y-%m-%d %H:%M:%S"), + log + )); + } + + pub fn read_logs(&self) -> io::Result> { + let logs = self.logs.lock().unwrap(); + Ok(logs.clone()) + } + pub fn read_temp_logs(&self) -> io::Result> { + let logs = self.temp_logs.lock().unwrap(); + Ok(logs.clone()) + } + + // std::sync::PoisonError>>> + pub fn write_logs_file( + &self, + path: &std::path::PathBuf, + ) -> std::result::Result<(), LoggerError> { + let v = match self.logs.lock() { + Ok(v) => v, + Err(e) => { + println!("Poisoned mutex (this is not good)"); + return Err(LoggerError::from(e)); + } + }; + let contents = v.join("\n"); + std::fs::write(path, contents)?; + Ok(()) + } + pub fn read_logs_file(path: &std::path::PathBuf) -> std::result::Result { + println!("config log path is {}", path.to_str().unwrap()); + let contents = std::fs::read_to_string(path)?; + let logs = contents.split("\n").map(|str| str.to_string()).collect(); + + Ok(Logger { + logs: Mutex::new(logs), + temp_logs: Mutex::new(Vec::new()), + }) + } +} +// impl Serialize for Logger { +// fn serialize(&self, serializer: S) -> Result +// where +// S: serde::Serializer, +// { +// let v = match self.logs.lock() { +// Ok(v) => v.join("\n"), +// Err(e) => return Err(serde::ser::Error::custom(e.to_string())), +// }; +// serializer.serialize_str(v.as_str()) +// } +// } +// struct LoggerVisitor; +// impl serde::de::Visitor for LoggerError { +// fn visit_bool(self, v: bool) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Bool(v), +// &self, +// )) +// } +// +// fn visit_i8(self, v: i8) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_i64(v as i64) +// } +// +// fn visit_i16(self, v: i16) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_i64(v as i64) +// } +// +// fn visit_i32(self, v: i32) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_i64(v as i64) +// } +// +// fn visit_i64(self, v: i64) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Signed(v), +// &self, +// )) +// } +// +// fn visit_i128(self, v: i128) -> Result +// where +// E: serde::de::Error, +// { +// let mut buf = [0u8; 58]; +// let mut writer = format::Buf::new(&mut buf); +// std::fmt::Write::write_fmt(&mut writer, format_args!("integer `{}` as i128", v)).unwrap(); +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Other(writer.as_str()), +// &self, +// )) +// } +// +// fn visit_u8(self, v: u8) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_u64(v as u64) +// } +// +// fn visit_u16(self, v: u16) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_u64(v as u64) +// } +// +// fn visit_u32(self, v: u32) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_u64(v as u64) +// } +// +// fn visit_u64(self, v: u64) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Unsigned(v), +// &self, +// )) +// } +// +// fn visit_u128(self, v: u128) -> Result +// where +// E: serde::de::Error, +// { +// let mut buf = [0u8; 57]; +// let mut writer = format::Buf::new(&mut buf); +// std::fmt::Write::write_fmt(&mut writer, format_args!("integer `{}` as u128", v)).unwrap(); +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Other(writer.as_str()), +// &self, +// )) +// } +// +// fn visit_f32(self, v: f32) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_f64(v as f64) +// } +// +// fn visit_f64(self, v: f64) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Float(v), +// &self, +// )) +// } +// +// fn visit_char(self, v: char) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_str(v.encode_utf8(&mut [0u8; 4])) +// } +// +// fn visit_str(self, v: &str) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Str(v), +// &self, +// )) +// } +// +// fn visit_borrowed_str(self, v: &'de str) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_str(v) +// } +// +// fn visit_string(self, v: String) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_str(&v) +// } +// +// fn visit_bytes(self, v: &[u8]) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Bytes(v), +// &self, +// )) +// } +// +// fn visit_borrowed_bytes(self, v: &'de [u8]) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_bytes(v) +// } +// +// fn visit_byte_buf(self, v: Vec) -> Result +// where +// E: serde::de::Error, +// { +// self.visit_bytes(&v) +// } +// +// fn visit_none(self) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Option, +// &self, +// )) +// } +// +// fn visit_some(self, deserializer: D) -> Result +// where +// D: serde::Deserializer<'de>, +// { +// let _ = deserializer; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Option, +// &self, +// )) +// } +// +// fn visit_unit(self) -> Result +// where +// E: serde::de::Error, +// { +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Unit, +// &self, +// )) +// } +// +// fn visit_newtype_struct(self, deserializer: D) -> Result +// where +// D: serde::Deserializer<'de>, +// { +// let _ = deserializer; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::NewtypeStruct, +// &self, +// )) +// } +// +// fn visit_seq(self, seq: A) -> Result +// where +// A: serde::de::SeqAccess<'de>, +// { +// let _ = seq; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Seq, +// &self, +// )) +// } +// +// fn visit_map(self, map: A) -> Result +// where +// A: serde::de::MapAccess<'de>, +// { +// let _ = map; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Map, +// &self, +// )) +// } +// +// fn visit_enum(self, data: A) -> Result +// where +// A: serde::de::EnumAccess<'de>, +// { +// let _ = data; +// Err(serde::de::Error::invalid_type( +// serde::de::Unexpected::Enum, +// &self, +// )) +// } +// } +// impl<'de> Deserialize<'de> for Logger { +// fn deserialize(deserializer: D) -> Result +// where +// D: serde::Deserializer<'de>, +// { +// deserializer.deserialize_str(LoggerVisitor) +// } +// } diff --git a/src/node/mod.rs b/src/node/mod.rs new file mode 100644 index 0000000..27e8a04 --- /dev/null +++ b/src/node/mod.rs @@ -0,0 +1,311 @@ +pub mod logger; +mod resolve_requests; +mod thread_pool; +pub mod ui; +mod utils; +use crate::chain::{transaction::Transaction, Chain}; +use crate::configs::KILL_SERVER_SOCKET_PATH; +use crate::node::logger::Logger; +use core::panic; +use directories::ProjectDirs; +use once_cell::sync::Lazy; +use resolve_requests::endpoints::resolve_endpoint; +use resolve_requests::methods::{HTTPParseError, HTTPRequest}; +use serde::{Deserialize, Serialize}; +use std::fs::{self}; +use std::os::unix::net::UnixStream; +use std::path::PathBuf; +use std::time::Duration; +use std::{ + collections::HashMap, + io::{prelude::*, BufReader}, + net::{TcpListener, TcpStream}, + sync::{Arc, Mutex}, + thread, +}; +use thread_pool::custom_thread_pool::ThreadPool; + +#[derive(Serialize, Deserialize)] +pub struct NodeState { + status: bool, + chain: Chain, + transactions_pool: Vec, +} +#[derive(Debug, Serialize, Deserialize)] +struct NodeConfig { + log_path: PathBuf, + socket_path: PathBuf, +} +impl Default for NodeConfig { + fn default() -> Self { + let proj_dirs = ProjectDirs::from("", "CleytoCoin Big Mean Corp", "cleyto_coin") + .expect("Could not find the config directory"); + Self { + log_path: proj_dirs.data_dir().join("logs.log"), + socket_path: PathBuf::from(KILL_SERVER_SOCKET_PATH), + } + } +} + +#[derive(Serialize, Deserialize)] +pub struct Node { + state: Arc>, + + // The logs are manually saved on shutdown and reloaded on initialization + #[serde(skip)] + logger: Arc, + + // The configs are best reloaded with every initialization + #[serde(skip)] + config: NodeConfig, +} + +static NUMBER_OF_THREADS_IN_THREAD_POOL: Lazy = Lazy::new(num_cpus::get); + +// 0 = None +// 1 = Prod +// 2 = Debug +pub const LOG_LEVEL: u8 = 2; + +fn load_config() -> NodeConfig { + let proj_dirs = ProjectDirs::from("", "CleytoCoin Big Mean Corp", "cleyto_coin") + .expect("Could not find the config directory"); + let config_path = proj_dirs.config_dir().join("config.toml"); + + if let Ok(contents) = fs::read_to_string(&config_path) { + toml::from_str(&contents).expect("Invalid config format") + } else { + fs::create_dir_all(proj_dirs.config_dir()).expect("Could not create config directories"); + + let default_cfg = NodeConfig::default(); + let toml_str = toml::to_string_pretty(&default_cfg).unwrap(); + fs::write(&config_path, &toml_str).expect("Couldn't write default config"); + default_cfg + } +} + +impl Node { + // these configurations should be moved to a file + pub const DEFAULT_PORT: u16 = 9473; + pub const REFRESH_RATE_SERVER_IN_MS: u64 = 50; + + pub fn new(chain: Chain) -> (Node, Arc) { + let config = load_config(); + let logger = + Arc::new(Logger::read_logs_file(&config.log_path).unwrap_or_else(|_| Logger::new())); + let logger_clone = Arc::clone(&logger); + ( + Node { + state: Arc::new(Mutex::new(NodeState { + status: true, + chain, + transactions_pool: Vec::new(), + })), + logger, + config, + }, + logger_clone, + ) + } + + fn parse_http_request( + mut buf_reader: BufReader, + ) -> Result { + let mut http_headers: HashMap = HashMap::new(); + + let mut line = String::new(); + + // reading status_line + + match buf_reader.read_line(&mut line) { + Ok(n) if (n > 0) => n, + Ok(_) => return Err(HTTPParseError::InvalidStatusLine), + Err(_) => return Err(HTTPParseError::InvalidStatusLine), + }; + + let status_line: String = line.trim().to_string(); + + let mut tokens = status_line.split(' '); + let (method, path, http_version) = ( + tokens + .next() + .ok_or(HTTPParseError::InvalidRequestLine)? + .to_string(), + tokens + .next() + .ok_or(HTTPParseError::InvalidRequestLine)? + .to_string(), + tokens + .next() + .ok_or(HTTPParseError::InvalidRequestLine)? + .to_string(), + ); + + // reading headers + loop { + line.clear(); + + if buf_reader.read_line(&mut line).is_err() { + return Err(HTTPParseError::InvalidRequestLine); + } + + let line = line.trim_end().to_string(); + + if line.is_empty() { + break; + } + + if let Some((key, value)) = line.split_once(":") { + http_headers.insert(key.trim().to_string(), value.trim().to_string()); + } else { + return Err(HTTPParseError::InvalidRequestLine); + }; + } + + // If method is GET, return before trying to read the body + if method == "GET" { + return Ok(HTTPRequest::new( + None, + method, + PathBuf::from(path), + http_version, + http_headers, + None, + )); + } + + // getting content_length from headers + let content_length = match http_headers.get("content-length") { + Some(value) => match value.parse::() { + Ok(length) => length, + Err(_) => { + return Err(HTTPParseError::MissingContentLength); + } + }, + None => { + return Err(HTTPParseError::MissingContentLength); + } + }; + + // reading body + let mut body = vec![0; content_length]; + if let Err(e) = buf_reader.read_exact(&mut body) { + eprintln!("Error reading body: {}", e); + return Err(HTTPParseError::InvalidRequestLine); + } + + let http_body: Option = Some(String::from_utf8_lossy(&body).to_string()); + + if method == "POST" { + return Ok(HTTPRequest::new( + None, + method, + PathBuf::from(path), + http_version, + http_headers, + http_body, + )); + } + + Err(HTTPParseError::InvalidStatusLine) + } + + fn handle_connection( + state: Arc>, + stream: TcpStream, + ) -> Result, Option> { + let buf_reader = BufReader::new(&stream); + + let mut request_object: HTTPRequest = match Self::parse_http_request(buf_reader) { + Ok(value) => value, + Err(e) => { + return Err(Some(format!("Error processing HTTP request: {e}"))); + } + }; + + request_object.set_stream(stream); + + resolve_endpoint(state, request_object) + } + + pub fn run(&mut self, default: bool, selected_port: u16) { + let port: u16 = if default { + Self::DEFAULT_PORT + } else { + match selected_port { + port if (1..=65535).contains(&port) => port, + _ => { + println!("Invalid port! Using default: {}", Self::DEFAULT_PORT); + Self::DEFAULT_PORT + } + } + }; + + let tcp_listener = TcpListener::bind(format!("127.0.0.1:{port}")).unwrap(); + + tcp_listener + .set_nonblocking(true) + .expect("Cannot set non-blocking"); + + let thread_pool = match ThreadPool::new(*NUMBER_OF_THREADS_IN_THREAD_POOL) { + Ok(value) => value, + Err(e) => panic!("{e}"), + }; + + // The termination signal will be a socket now + let parent = self.config.socket_path.parent().unwrap(); + std::fs::create_dir_all(parent).expect("Could not create temp dirs for parent socket"); + + let mut read_buffer: [u8; 100] = [0u8; 100]; + loop { + if let Ok(mut listener) = UnixStream::connect(KILL_SERVER_SOCKET_PATH) { + let command: Option<&str> = match listener.read(&mut read_buffer) { + Ok(n) => str::from_utf8(&read_buffer[..n]).ok(), + Err(_) => None, + }; + + match command { + Some("kill") => break, + Some(&_) => {} + None => {} + } + } + // Check for local signal + + // Try accepting a connection + match tcp_listener.accept() { + Ok((stream, _)) => { + let logger = Arc::clone(&self.logger); + let state = Arc::clone(&self.state); + thread_pool.execute(move || { + match Self::handle_connection(state, stream) { + Ok(Some(value)) => { + logger.log(value); + } + Err(Some(value)) => logger.log_error(value), + _ => {} + }; + }) + } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + thread::sleep(Duration::from_millis(Self::REFRESH_RATE_SERVER_IN_MS)); + } + Err(e) => { + eprintln!("Error accepting connection: {}", e); + break; + } + } + } + + println!("Dropping thread pool"); + } +} + +impl Drop for Node { + fn drop(&mut self) { + match self.logger.write_logs_file(&self.config.log_path) { + Ok(_) => {} + Err(e) => eprintln!("Error saving log file: {e:?}"), + } + } +} diff --git a/src/node/resolve_requests/endpoints.rs b/src/node/resolve_requests/endpoints.rs new file mode 100644 index 0000000..1599f94 --- /dev/null +++ b/src/node/resolve_requests/endpoints.rs @@ -0,0 +1,186 @@ +use super::super::LOG_LEVEL; +use super::errors::HTTPResponseError; +use super::helpers::{ + method_not_allowed, path_not_found, return_html, return_image, return_json, GETFunc, + HTTPResult, Handler, POSTFunc, +}; +use super::methods::{Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData}; +use crate::chain::transaction::{Transaction, TransactionDeserializeError, TransactionError}; +use crate::node::NodeState; +use chrono::Utc; +use core::panic; +use serde_json::json; +use std::collections::HashMap; +use std::sync::{Arc, Mutex}; + +// pub type POSTFunc = fn(&POSTData, Arc>) -> HTTPResult; +// pub type GETFunc = fn(&GETData, Arc>) -> HTTPResult; + +pub fn index(_: &GETData, _: Arc>) -> HTTPResult { + return_html("index.html") +} + +pub fn submit_transaction(data: &POSTData, state: Arc>) -> HTTPResult { + let body = data.body.clone().unwrap(); + let transaction: Transaction = match Transaction::deserialize(body) { + Ok(tx) => tx, + Err(e) => { + return match e { + TransactionDeserializeError::InsufficientFunds => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::MalformedTransaction => { + Err(HTTPResponseError::InvalidBody(None)) + } + TransactionDeserializeError::SerdeError(_) => { + Err(HTTPResponseError::InvalidBody(None)) + } + } + } + }; + + match transaction.verify() { + Ok(()) => {} + Err(e) => { + return match e { + TransactionError::OpenSSLError(_) => Err(HTTPResponseError::InternalServerError( + Some("Error in the OpenSSL library when verifying a transaction".to_string()), + )), + TransactionError::ValidationError => Err(HTTPResponseError::BadRequest(Some( + "Transaction submitted with \ + invalid signature" + .to_string(), + ))), + TransactionError::InsufficientInputs => Err(HTTPResponseError::BadRequest(Some( + "Transaction's outputs are bigger that its inputs".to_string(), + ))), + // TODO Should move both of those to another error enum, maybe client and server errors + TransactionError::InsufficientFunds => panic!("Not the server's problem"), + TransactionError::ConnectionError(_) => panic!("Not the server's problem"), + }; + } + }; + + state.lock().unwrap().transactions_pool.push(transaction); + + Ok(HTTPResponse::OK(Some(Content::JSON(json!({ + "msg": "The transaction was added to the pool.", + "status_code": "200" + }))))) +} + +pub fn get_transaction_pool(_: &GETData, state: Arc>) -> HTTPResult { + let transaction_pool: Vec = state.lock().unwrap().transactions_pool.clone(); + let response = serde_json::to_value(transaction_pool).unwrap(); + Ok(HTTPResponse::OK(Some(Content::JSON(response)))) +} + +pub fn favicon(_: &GETData, _: Arc>) -> HTTPResult { + return_image("fav.ico", ImageType::ICO) +} + +pub fn status(_: &GETData, state: Arc>) -> HTTPResult { + let state = match state.lock() { + Ok(guard) => guard, + Err(_) => panic!("Mutex lock was poisoned in function status on endpoints"), + }; + + return_json(json!({ + "status": state.status, + "blockHeight": state.chain.get_last_index(), + "peers": 100000000, + "timestamp": Utc::now() + })) +} + +pub fn resolve_endpoint( + state: Arc>, + mut request: HTTPRequest, +) -> Result, Option> { + /* + TODO: This creates the endpoints var every time the resolve_endpoints function runs, + which is inefficient. We should move the creation of the endpoints var to the + initialization of the program, and pass it around as a parameter to the functions that + need it + */ + + fn curry_add_endpoint<'a, 'b>( + endpoints: &'b mut HashMap<&'a str, HashMap<&'a str, Box>>, + ) -> impl FnMut(&'a str, Option, Option) + 'b { + |path: &'a str, get: Option, post: Option| { + let mut methods: HashMap<&'a str, Box> = HashMap::new(); + if let Some(get) = get { + methods.insert("GET", Box::new(get) as Box); + } + if let Some(post) = post { + methods.insert("POST", Box::new(post) as Box); + } + + endpoints.insert(path, methods); + } + } + fn initialize_endpoints<'a>() -> HashMap<&'a str, HashMap<&'a str, Box>> { + let mut endpoints: HashMap<&str, HashMap<&str, Box>> = HashMap::new(); + { + let mut add_endpoints = curry_add_endpoint(&mut endpoints); + add_endpoints("/", Some(index), None); + add_endpoints("/favicon.ico", Some(favicon), None); + add_endpoints("/status", Some(status), None); + add_endpoints("/submit-transaction", None, Some(submit_transaction)); + add_endpoints("/get-transaction-pool", Some(get_transaction_pool), None); + } + endpoints + } + + let endpoints = initialize_endpoints(); + + let (path, method) = match request.get_method() { + Method::GET(data) => (data.path.clone(), "GET"), + Method::POST(data) => (data.path.clone(), "POST"), + }; + + let r = match endpoints.get(path.to_str().unwrap()) { + Some(methods) => match methods.get(method) { + Some(handler) => handler.call(&request, state), + None => method_not_allowed(Some(path.to_str().unwrap())), + }, + None => path_not_found(Some(path.to_str().unwrap())), + }; + + match r { + Ok(value) => { + request.response(value); + let path = path.to_str().unwrap(); + // I don't give a single fuck about favicon + if path == "/favicon.ico" || LOG_LEVEL < 2 { + return Ok(None); + } + Ok(Some(format!( + "Request {} to path {} was successful", + method, path + ))) + } + Err(e) => match e { + HTTPResponseError::InvalidMethod(log) => { + request.response(HTTPResponse::InvalidMethod); + Err(log) + } + HTTPResponseError::InvalidPath(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InvalidBody(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + HTTPResponseError::InternalServerError(log) => { + request.response(HTTPResponse::InternalServerError); + Err(log) + } + HTTPResponseError::BadRequest(log) => { + request.response(HTTPResponse::BadRequest); + Err(log) + } + }, + } +} diff --git a/src/node/resolve_requests/errors.rs b/src/node/resolve_requests/errors.rs new file mode 100644 index 0000000..2483667 --- /dev/null +++ b/src/node/resolve_requests/errors.rs @@ -0,0 +1,58 @@ +use std::fmt; + +#[derive(Debug)] +pub enum HTTPResponseError { + InvalidMethod(Option), + InvalidPath(Option), + InvalidBody(Option), + InternalServerError(Option), + BadRequest(Option), +} +impl fmt::Display for HTTPResponseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + // If log adds log, else just prints the type of the enum + HTTPResponseError::InvalidMethod(log) => match log { + None => { + write!(f, "Invalid Method") + } + Some(log) => { + write!(f, "Invalid Method: {}", log) + } + }, + HTTPResponseError::InvalidPath(log) => match log { + None => { + write!(f, "Invalid Path") + } + Some(log) => { + write!(f, "Invalid Path: {}", log) + } + }, + HTTPResponseError::InvalidBody(log) => match log { + None => { + write!(f, "Invalid Body") + } + Some(log) => { + write!(f, "Invalid Body: {}", log) + } + }, + HTTPResponseError::InternalServerError(log) => match log { + None => { + write!(f, "Internal Server Error") + } + Some(log) => { + write!(f, "Internal Server Error: {}", log) + } + }, + HTTPResponseError::BadRequest(log) => match log { + None => { + write!(f, "Bad Request") + } + Some(log) => { + write!(f, "Bad Request: {}", log) + } + }, + } + } +} +impl std::error::Error for HTTPResponseError {} diff --git a/src/node/resolve_requests/helpers.rs b/src/node/resolve_requests/helpers.rs new file mode 100644 index 0000000..98ae7b5 --- /dev/null +++ b/src/node/resolve_requests/helpers.rs @@ -0,0 +1,96 @@ +use super::methods::{Content, GETData, HTTPRequest, HTTPResponse, ImageType, Method, POSTData}; +use crate::node::resolve_requests::errors::HTTPResponseError; +use crate::node::NodeState; +use std::path::PathBuf; +use std::sync::{Arc, Mutex}; + +const STATIC_FOLDER: &str = "src/node/static/"; + +pub type HTTPResult = Result; + +pub type POSTFunc = fn(&POSTData, Arc>) -> HTTPResult; +pub type GETFunc = fn(&GETData, Arc>) -> HTTPResult; +pub fn path_not_found(s: Option<&str>) -> HTTPResult { + if s.is_some() { + return Err(HTTPResponseError::InvalidPath(Some(format!( + "Path {} was not found", + s.unwrap() + )))); + } + Err(HTTPResponseError::InvalidPath(None)) +} +pub fn method_not_allowed(s: Option<&str>) -> HTTPResult { + if s.is_some() { + return Err(HTTPResponseError::InvalidMethod(Some(format!( + "Attempt of accessing the path {} with wrong method", + s.unwrap() + )))); + } + Err(HTTPResponseError::InvalidMethod(None)) +} + +pub trait Handler { + fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult; +} + +// Implement the trait for GETFunc +impl Handler for GETFunc { + fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { + match request.get_method() { + Method::GET(data) => self(data, state), + _ => method_not_allowed(None), + } + } +} + +// Implement the trait for POSTFunc +impl Handler for POSTFunc { + fn call(&self, request: &HTTPRequest, state: Arc>) -> HTTPResult { + match request.get_method() { + Method::POST(data) => self(data, state), + _ => method_not_allowed(None), + } + } +} + +pub fn return_image(path: &str, image_type: ImageType) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::Image( + PathBuf::from(STATIC_FOLDER.to_owned() + path), + image_type, + )))) +} +pub fn return_html(path: &str) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::HTML(PathBuf::from( + STATIC_FOLDER.to_owned() + path, + ))))) +} +pub fn return_json(json: serde_json::Value) -> HTTPResult { + Ok(HTTPResponse::OK(Some(Content::JSON(json)))) +} + +// pub fn post(request: HTTPRequest, f: POSTFunc, state: Arc>) -> HTTPResult { +// let method = request.get_method(); +// if let Method::POST(data) = method { +// f(data, state) +// } else { +// Err(HTTPResponseError::InvalidMethod(None)) +// } +// } +// pub fn get(request: HTTPRequest, f: GETFunc, state: Arc>) -> HTTPResult { +// if let Method::GET(data) = request.get_method() { +// f(data, state) +// } else { +// Err(HTTPResponseError::InvalidMethod(None)) +// } +// } +// pub fn get_post( +// request: HTTPRequest, +// get: GETFunc, +// post: POSTFunc, +// state: Arc>, +// ) -> HTTPResult { +// match request.get_method() { +// Method::POST(data) => post(data, state), +// Method::GET(data) => get(data, state), +// } +// } diff --git a/src/node/resolve_requests/methods.rs b/src/node/resolve_requests/methods.rs new file mode 100644 index 0000000..d0b9b47 --- /dev/null +++ b/src/node/resolve_requests/methods.rs @@ -0,0 +1,292 @@ +use serde_json::json; +use std::collections::HashMap; +use std::io::prelude::*; +use std::net::TcpStream; +use std::path::PathBuf; +use std::{fmt, fs}; + +#[allow(clippy::upper_case_acronyms)] +pub enum ImageType { + // PNG, + ICO, + // JPEG, +} + +#[allow(clippy::upper_case_acronyms)] +pub enum Content { + HTML(PathBuf), // path to HTML file + JSON(serde_json::Value), + Image(PathBuf, ImageType), // path to image file + // PlainText(String), +} + +pub enum HTTPResponse { + OK(Option), + InvalidMethod, + BadRequest, + InternalServerError, +} + +#[derive(Debug, Clone)] +pub struct GETData { + pub path: PathBuf, +} +#[derive(Debug, Clone)] +pub struct POSTData { + pub path: PathBuf, + pub body: Option, +} + +#[derive(Debug, Clone)] +#[allow(clippy::upper_case_acronyms)] +pub enum Method { + GET(GETData), + POST(POSTData), +} +impl fmt::Display for Method { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Method::GET(data) => write!(f, "GET {:?}", data.path), + Method::POST(data) => write!(f, "POST {:?}", data.path), + } + } +} + +struct Response { + status: u16, + content_type: &'static str, + body: Vec, +} + +impl Response { + fn to_bytes(&self) -> Vec { + let mut v = Vec::new(); + let header = format!( + "HTTP/1.1 {} {}\r\n\ + Content-Type: {}\r\n\ + Content-Length: {}\r\n\r\n", + self.status, + match self.status { + 200 => "OK", + 400 => "Bad Request", + 405 => "Method Not Allowed", + _ => "Unknown", + }, + self.content_type, + self.body.len(), + ); + v.extend_from_slice(header.as_bytes()); + v.extend_from_slice(&self.body); + v + } + fn new(status: u16, content_type: &'static str, body: Vec) -> Self { + Self { + status, + content_type, + body, + } + } +} + +#[derive(Debug)] +pub struct HTTPRequest { + stream: Option, + pub headers: HashMap, + method: Method, + http_version: String, +} + +impl HTTPRequest { + pub fn new( + stream: Option, + method: String, + path: PathBuf, + http_version: String, + headers: HashMap, + body: Option, + ) -> HTTPRequest { + HTTPRequest { + stream, + method: match method.as_str() { + "GET" => Method::GET(GETData { path }), + "POST" => Method::POST(POSTData { path, body }), + _ => panic!("Unavailable method"), + }, + http_version, + headers, + } + } + + pub fn set_stream(&mut self, stream: TcpStream) { + self.stream = Some(stream) + } + + pub fn get_method(&self) -> &Method { + &self.method + } + + fn make_response(status: HTTPResponse, accept: Option<&str>) -> std::io::Result { + fn response_ok_content(content: Content) -> std::io::Result { + match content { + Content::HTML(path) => Ok(Response { + status: 200, + content_type: "text/html", + body: fs::read(path)?, + }), + Content::JSON(value) => Ok(Response { + status: 200, + content_type: "application/json", + body: serde_json::to_vec(&value)?, + }), + Content::Image(path, img_type) => Ok(Response { + status: 200, + content_type: match img_type { + // ImageType::PNG => "image/png", + ImageType::ICO => "image/vnd.microsoft.icon", + // ImageType::JPEG => "image/jpeg", + }, + body: fs::read(path)?, + }), + // Content::PlainText(text) => Ok(Response { + // status: 200, + // content_type: "text/plain", + // body: text.into_bytes(), + // }), + } + } + fn response_ok_no_content(a: &str) -> std::io::Result { + if a.contains("text/html") { + return Ok(Response::new( + 200, + "text/html", + fs::read("static/200.html").unwrap_or("NOT FOUND".into()), + )); + } else if a.contains("application/json") { + let j = json!({ "msg": "OK", "status": 200 }); + return Ok(Response::new( + 200, + "application/json", + serde_json::to_vec(&j)?, + )); + } + + Ok(Response::new(200, "text", "Success".into())) + } + + match status { + HTTPResponse::OK(content_opt) => { + if let Some(content) = content_opt { + response_ok_content(content) + } else { + response_ok_no_content(accept.unwrap_or("text")) + } + } + HTTPResponse::InvalidMethod => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new( + 405, + "text/html", + fs::read("static/405.html") + .unwrap_or_else(|_| b"405 Invalid Method".to_vec()), + )) + } else { + Ok(Response::new( + 405, + "text/plain", + b"405 Invalid Method".to_vec(), + )) + } + } + HTTPResponse::BadRequest => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new( + 400, + "text/html", + fs::read("static/400.html").unwrap_or_else(|_| b"400 Bad Request".to_vec()), + )) + } else { + Ok(Response::new( + 400, + "text/plain", + b"400 Bad Request".to_vec(), + )) + } + } + HTTPResponse::InternalServerError => { + if accept.unwrap_or("").contains("text/html") { + Ok(Response::new( + 500, + "text/html", + fs::read("static/500.html") + .unwrap_or_else(|_| b"500 Internal Server Error".to_vec()), + )) + } else { + Ok(Response::new( + 500, + "text/plain", + b"500 Internal Server Error".to_vec(), + )) + } + } + } + } + + pub fn response(&mut self, status: HTTPResponse) { + let accept = self.headers.get("Accept").map(|s| s.as_str()); + let resp = Self::make_response(status, accept).unwrap(); + if let Err(e) = self.stream.as_mut().unwrap().write_all(&resp.to_bytes()) { + eprintln!("Error writing response to stream: {}", e); + } + } +} + +impl fmt::Display for HTTPRequest { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // Write the HTTP method and version + writeln!(f, "Method: {}", self.method)?; + writeln!(f, "HTTP Version: {}", self.http_version)?; + + // Write the stream status + writeln!( + f, + "Stream: {}", + if self.stream.is_some() { + "Connected" + } else { + "Disconnected" + } + )?; + + // Write the headers + writeln!(f, "Headers:")?; + if self.headers.is_empty() { + writeln!(f, " (none)")?; + } else { + for (key, value) in &self.headers { + writeln!(f, " {}: {}", key, value)?; + } + } + + Ok(()) + } +} +#[derive(Debug)] +pub enum HTTPParseError { + InvalidStatusLine, + InvalidRequestLine, + // MissingFields, + MissingContentLength, +} +impl fmt::Display for HTTPParseError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + HTTPParseError::InvalidRequestLine => write!(f, "Invalid request line"), + // HTTPParseError::MissingFields => write!(f, "Missing required fields"), + HTTPParseError::InvalidStatusLine => write!(f, "Invalid status line"), + HTTPParseError::MissingContentLength => { + write!(f, "Missing content-length field in headers") + } + } + } +} +impl std::error::Error for HTTPParseError {} diff --git a/src/node/resolve_requests/mod.rs b/src/node/resolve_requests/mod.rs new file mode 100644 index 0000000..b4cdc8f --- /dev/null +++ b/src/node/resolve_requests/mod.rs @@ -0,0 +1,4 @@ +pub mod endpoints; +pub mod errors; +pub mod helpers; +pub mod methods; diff --git a/src/node/static/200.html b/src/node/static/200.html new file mode 100644 index 0000000..3382bd5 --- /dev/null +++ b/src/node/static/200.html @@ -0,0 +1,26 @@ + + + + + Success + + + +

Success!

+

Your request was completed successfully.

+ + diff --git a/src/node/static/400.html b/src/node/static/400.html new file mode 100644 index 0000000..cc7e866 --- /dev/null +++ b/src/node/static/400.html @@ -0,0 +1,26 @@ + + + + + 400 Bad Request + + + +

400 - Bad Request

+

The server couldn't understand your request. Please check the syntax and try again.

+ + diff --git a/src/node/static/404.html b/src/node/static/404.html new file mode 100644 index 0000000..956761d --- /dev/null +++ b/src/node/static/404.html @@ -0,0 +1,26 @@ + + + + + 404 Not Found + + + +

404 - Page Not Found

+

The page you're looking for doesn't exist or has been moved.

+ + diff --git a/src/node/static/405.html b/src/node/static/405.html new file mode 100644 index 0000000..e4348b5 --- /dev/null +++ b/src/node/static/405.html @@ -0,0 +1,26 @@ + + + + + 405 Method Not Allowed + + + +

405 - Method Not Allowed

+

The method used in the request is not supported for this resource.

+ + diff --git a/src/node/static/500.html b/src/node/static/500.html new file mode 100644 index 0000000..a043f66 --- /dev/null +++ b/src/node/static/500.html @@ -0,0 +1,26 @@ + + + + + 500 Internal Server Error + + + +

500 - Internal Server Error

+

Oops! Something went wrong on the server.

+ + diff --git a/src/node/static/fav.ico b/src/node/static/fav.ico new file mode 100644 index 0000000..29543c5 Binary files /dev/null and b/src/node/static/fav.ico differ diff --git a/src/node/static/fav.png b/src/node/static/fav.png new file mode 100644 index 0000000..b36ffd2 Binary files /dev/null and b/src/node/static/fav.png differ diff --git a/src/node/static/index.html b/src/node/static/index.html new file mode 100644 index 0000000..9550a9b --- /dev/null +++ b/src/node/static/index.html @@ -0,0 +1,87 @@ + + + + + + Cryptocoin Node Dashboard + + + +
+

Cryptocoin Node Dashboard

+
+

Node Status: Checking...

+

Block Height: 0

+

Connected Peers: 0

+

Last Updated: -

+
+ +
+ + + + \ No newline at end of file diff --git a/src/node/thread_pool.rs b/src/node/thread_pool.rs new file mode 100644 index 0000000..2ae0b1e --- /dev/null +++ b/src/node/thread_pool.rs @@ -0,0 +1,114 @@ +pub mod custom_thread_pool { + use std::{ + fmt, + sync::{mpsc, Arc, Mutex}, + thread, + }; + + #[derive(Debug)] + pub enum PoolCreationError { + TooFewThreads, + TooManyThreads, + } + + impl fmt::Display for PoolCreationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + PoolCreationError::TooFewThreads => { + write!(f, "You must use at least one thread on the pool") + } + PoolCreationError::TooManyThreads => { + write!(f, "You must use less than {} threads on the pool", u32::MAX) + } + } + } + } + impl std::error::Error for PoolCreationError {} + + struct Worker { + id: usize, + handle: thread::JoinHandle<()>, + } + + impl Worker { + pub fn new(id: usize, receiver: Arc>>) -> Worker { + let thread = thread::spawn(move || loop { + let message = receiver.lock().unwrap().recv(); + + match message { + Ok(job) => { + job(); + } + Err(_) => { + println!("Worker {id} disconnected; shutting down."); + break; + } + } + }); + Worker { id, handle: thread } + } + } + + type Job = Box; + + pub struct ThreadPool { + workers: Vec, + sender: Option>, + } + + impl ThreadPool { + /// Create a new ThreadPool. + /// + /// The size is the number of threads in the pool. + /// + /// # Panics + /// + /// The `new` function will panic if the size is zero. + pub fn new(size: usize) -> Result { + // println!("number of threads in thread pool is {}", size); + if size < 1 { + return Err(PoolCreationError::TooFewThreads); + } else if size > 100000 { + // I think this is unreachable + return Err(PoolCreationError::TooManyThreads); + } + + let (sender, receiver) = mpsc::channel(); + + let receiver = Arc::new(Mutex::new(receiver)); + + let mut workers = Vec::with_capacity(size); + + for id in 0..size { + workers.push(Worker::new(id, Arc::clone(&receiver))); + } + + Ok(ThreadPool { + workers, + sender: Some(sender), + }) + } + + /// Receives a closure compatible with the thread::spawn() function. + pub fn execute(&self, f: F) + where + F: FnOnce() + Send + 'static, // to receive a closure compatible with thread.spawn() + { + let job = Box::new(f); + self.sender.as_ref().unwrap().send(job).unwrap(); + } + } + + impl Drop for ThreadPool { + fn drop(&mut self) { + println!("Dropping threadPool"); + + drop(self.sender.take()); + + for worker in self.workers.drain(..) { + println!("Shutting down worker {}", worker.id); + worker.handle.join().unwrap(); + } + } + } +} diff --git a/src/node/ui.rs b/src/node/ui.rs new file mode 100644 index 0000000..7dd96c6 --- /dev/null +++ b/src/node/ui.rs @@ -0,0 +1,107 @@ +use crate::node::logger::Logger; +use color_eyre::Result; +use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers}; +use ratatui::{ + prelude::*, + style::Stylize, + text::Line, + widgets::{Block, Paragraph}, + DefaultTerminal, Frame, +}; +use std::sync::Arc; +use std::time::Duration; + +/// The main application which holds the state and logic of the application. +pub struct App { + /// Is the application running? + running: bool, + port: u16, + logger: Arc, +} + +impl App { + /// Construct a new instance of [`App`]. + pub fn new(logger: Arc, port: u16) -> Self { + Self { + running: true, + port, + logger, + } + } + + /// Run the application's main loop. + pub fn run(mut self, mut terminal: DefaultTerminal) -> Result<()> { + self.running = true; + while self.running { + terminal.draw(|frame| self.render(frame))?; + self.handle_crossterm_events()?; + } + Ok(()) + } + + fn render(&mut self, frame: &mut Frame) { + let layout = Layout::default() + .direction(Direction::Horizontal) + .constraints(vec![Constraint::Percentage(50), Constraint::Percentage(50)]) + .split(frame.area()); + + let main_title = Line::from(" CleytoCoin node is running! ") + .bold() + .blue() + .centered(); + let logs_title = Line::from(" LOGS ").bold().blue().centered(); + let port = self.port; + + let mut lines = self.logger.read_temp_logs().unwrap(); + + let offset = lines.len() as i32 - frame.area().height as i32; + if offset > 0 { + for _ in 0..offset { + lines.remove(0); + } + } + + let text = format!( + "Node running in port {port} + \n\nPress `Esc`, `Ctrl-C` or `q` to stop running.\n" + ); + + frame.render_widget( + Paragraph::new(text) + .block(Block::bordered().title(main_title)) + .centered(), + layout[0], + ); + frame.render_widget( + Paragraph::new(lines.join("\n")) + .block(Block::bordered().title(logs_title)) + .left_aligned(), + layout[1], + ) + } + + fn handle_crossterm_events(&mut self) -> Result<()> { + if event::poll(Duration::from_millis(100))? { + match event::read()? { + Event::Key(key) if key.kind == KeyEventKind::Press => self.on_key_event(key), + Event::Mouse(_) => {} + Event::Resize(_, _) => {} + _ => {} + } + } + Ok(()) + } + + fn on_key_event(&mut self, key: KeyEvent) { + match (key.modifiers, key.code) { + (_, KeyCode::Esc | KeyCode::Char('q')) + | (KeyModifiers::CONTROL, KeyCode::Char('c') | KeyCode::Char('C')) => self.quit(), + // Add other key handlers here. + _ => {} + } + } + + fn quit(&mut self) { + self.running = false; + } +} diff --git a/src/node/utils.rs b/src/node/utils.rs new file mode 100644 index 0000000..2c15df4 --- /dev/null +++ b/src/node/utils.rs @@ -0,0 +1,19 @@ +// use serde::{Deserialize, Serialize}; + +/* +pub const RESPONSES_FOLDER: &str = "src/node/responses/"; +pub const SUCCESS_FILE_JSON: &str = "sucess_response.json"; +pub const ERROR_FILE_JSON: &str = "error_response.json"; + +#[derive(Serialize, Deserialize)] +pub struct SuccessJson { + msg: String, + status_code: u8 +} + + +#[derive(Serialize, Deserialize)] +pub struct ErrorJson { + msg: String, + status_code: u8 +} */ diff --git a/tests/create_block_and_add_chain.rs b/tests/create_block_and_add_chain.rs index b790bdd..b3b4972 100644 --- a/tests/create_block_and_add_chain.rs +++ b/tests/create_block_and_add_chain.rs @@ -1,19 +1,32 @@ -use CleytoCoin::chain::{Chain, wallet::Wallet, block::Block, transaction::{Transaction, TransactionInfo}}; -use chrono::Utc; +use cleyto_coin::chain::{ + block::Block, + transaction::{Transaction, TransactionInfo}, + utxo::UTXO, + wallet::Wallet, + Chain, +}; #[test] fn create_block_and_add_chain() { - let (wallet1, mut wallet1_pk) = Wallet::new(); + let (wallet1, wallet1_pk) = Wallet::new(); let (wallet2, _) = Wallet::new(); - let transaction_info = TransactionInfo::new(10.5, Utc::now()); + let input_utxos = vec![ + UTXO::new(1000, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet2.clone()), + UTXO::new(500, wallet2.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); let signature = match wallet1_pk.sign_transaction(&transaction_info) { Ok(value) => value, Err(e) => panic!("Error creating signed message: {e}"), }; - let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature); + let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature).unwrap(); let mut chain = Chain::new(); diff --git a/tests/create_headless_node.rs b/tests/create_headless_node.rs new file mode 100644 index 0000000..1589610 --- /dev/null +++ b/tests/create_headless_node.rs @@ -0,0 +1,10 @@ +use std::{thread, time::Duration}; + +use cleyto_coin::{kill_server, run_server_thread}; + +#[test] +fn run_and_kill_node() { + run_server_thread(); + thread::sleep(Duration::from_millis(100)); + kill_server(); +} diff --git a/tests/create_transaction.rs b/tests/create_transaction.rs deleted file mode 100644 index 172a4a1..0000000 --- a/tests/create_transaction.rs +++ /dev/null @@ -1,27 +0,0 @@ -use CleytoCoin::chain::transaction::{TransactionInfo, Transaction}; -use CleytoCoin::chain::wallet::Wallet; -use chrono::Utc; - -#[test] -fn create_transaction() { - let (wallet_sender, mut walletpk_sender) = Wallet::new(); - let (wallet_receiver, walletpk_receiver) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - - let signature = match walletpk_sender.sign_transaction(&transactioninfo) { - Ok(signed_hashed_message) => signed_hashed_message, - _ => panic!("error while signing transaction"), - }; - println!("Transaction signature (signed using the wallet_pk):\n{:?}", signature); - - // this will also be verified by the Transaction::new(); - if wallet_sender.verify_transaction_info(&transactioninfo, &signature) == true { - println!("transaction verified (by the wallet)"); - } else { - println!("transaction not verified"); - } - - let transaction: Transaction = Transaction::new(wallet_sender, wallet_receiver, transactioninfo, signature); - - println!("transaction.to_string(): {}", transaction.to_string()); -} diff --git a/tests/create_wallets_and_send_transaction.rs b/tests/create_wallets_and_send_transaction.rs new file mode 100644 index 0000000..4d424ba --- /dev/null +++ b/tests/create_wallets_and_send_transaction.rs @@ -0,0 +1,50 @@ +use std::path::PathBuf; + +use cleyto_coin::{generate, kill_server, run_server_thread, send}; + +const SENDER_PUBLIC_KEY_PATH: &str = "./wallets/sender/public.pem"; +const SENDER_PRIVATE_KEY_PATH: &str = "./wallets/sender/private.pem"; +const SENDER_PASSWORD: &str = "palmeiras"; + +const RECEIVER_PUBLIC_KEY_PATH: &str = "./wallets/receiver/public.pem"; +const RECEIVER_PRIVATE_KEY_PATH: &str = "./wallets/receiver/private.pem"; + +#[test] +fn test_wallet_creation() { + let sender_private_key_file = PathBuf::from(SENDER_PRIVATE_KEY_PATH); + let sender_public_key_file = PathBuf::from(SENDER_PUBLIC_KEY_PATH); + let sender_password = Some(String::from(SENDER_PASSWORD)); + generate( + &sender_private_key_file, + &sender_public_key_file, + &sender_password, + ); + + let receiver_private_key_file = PathBuf::from(RECEIVER_PRIVATE_KEY_PATH); + let receiver_public_key_file = PathBuf::from(RECEIVER_PUBLIC_KEY_PATH); + generate(&receiver_private_key_file, &receiver_public_key_file, &None); +} + +#[tokio::test] //mark a function as a test. +async fn test_send_transaction() { + test_wallet_creation(); + let sender_private_key_file = PathBuf::from(SENDER_PRIVATE_KEY_PATH); + let sender_password = Some(String::from(SENDER_PASSWORD)); + + let receiver_public_key_file = PathBuf::from(RECEIVER_PUBLIC_KEY_PATH); + + run_server_thread(); + + send( + None, + Some(receiver_public_key_file), + None, + Some(sender_private_key_file), + sender_password, + 100, + ) + .await + .unwrap(); + + kill_server(); +} diff --git a/tests/node.rs b/tests/node.rs new file mode 100644 index 0000000..6dbc5ef --- /dev/null +++ b/tests/node.rs @@ -0,0 +1,18 @@ +use cleyto_coin::{ + chain::{block::Block, Chain}, + node::Node, +}; + +#[test] +fn serialize_and_deserialize_node() { + let mut chain = Chain::new(); + + chain.add_block(Block::test_block(&chain)); + chain.add_block(Block::test_block(&chain)); + chain.add_block(Block::test_block(&chain)); + chain.add_block(Block::test_block(&chain)); + + let node1 = Node::new(chain); + let node_json = serde_json::to_string(&node1).expect("Could not serialize node"); + let _: Node = serde_json::from_str(&node_json).expect("Could not deserialize node"); +} diff --git a/tests/requests.rs b/tests/requests.rs new file mode 100644 index 0000000..1a90fab --- /dev/null +++ b/tests/requests.rs @@ -0,0 +1,114 @@ +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::utxo::UTXO; +use cleyto_coin::chain::wallet::Wallet; +use cleyto_coin::{kill_server, run_server_thread}; +use std::thread; + +use reqwest::blocking::Client; + +fn thread_post(n: u16) { + let url = "http://localhost:9473/submit-transaction"; // Replace with your server URL + let client = Client::new(); + + let mut handles = vec![]; + + let (wallet1, wallet1_pk) = Wallet::new(); + let (wallet2, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet2.clone()), + UTXO::new(500, wallet2.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match wallet1_pk.sign_transaction(&transaction_info) { + Ok(value) => value, + Err(e) => panic!("Error creating signed message: {e}"), + }; + + let new_transaction = Transaction::new(wallet1, wallet2, transaction_info, signature).unwrap(); + let json_transaction = new_transaction.serialize(); + println!("json_transaction is:\n{}", json_transaction); + + for i in 0..n { + let client = client.clone(); + let url = url.to_string(); + let transaction_copy = json_transaction.clone(); + + let handle = thread::spawn(move || { + match client + .post(&url) + .header("Content-Type", "application/json") + .body(transaction_copy) + .send() + { + Ok(resp) => { + assert_eq!(resp.status(), 200); + println!("Thread #{i}: {}", resp.status()) + } + Err(err) => eprintln!("Thread #{i} failed: {err}"), + } + }); + + handles.push(handle); + } + + for handle in handles { + let _ = handle.join(); + } +} + +fn thread_get(n: u16) { + let url = "http://localhost:9473/get-transaction-pool"; // Replace with your server URL + let client = Client::new(); + + let mut handles = vec![]; + + for i in 0..n { + let client = client.clone(); + let url = url.to_string(); + + let handle = thread::spawn(move || { + match client + .get(&url) + .header("Content-Type", "application/json") + .send() + { + Ok(resp) => { + assert_eq!(resp.status(), 200); + println!("Thread #{i}: {:#?}", resp.status()) + } + Err(err) => eprintln!( + "Thread #{i} failed (timeout) (connect): {} {}", + err.is_timeout(), + err.is_connect() + ), + } + }); + + handles.push(handle); + } + + for handle in handles { + let _ = handle.join(); + } +} + +#[test] +fn main() { + // Channel to kill thread + + // Run server thread + run_server_thread(); + + // 10.000 breaks the os (client), but the server seems fine + // Error accepting connection: Too many open files (os error 24) + thread_get(10); + thread_post(10); + + kill_server(); +} diff --git a/tests/sign_and_verify_transactioninfo.rs b/tests/sign_and_verify_transactioninfo.rs deleted file mode 100644 index 73184f6..0000000 --- a/tests/sign_and_verify_transactioninfo.rs +++ /dev/null @@ -1,20 +0,0 @@ -use CleytoCoin::chain::{transaction::TransactionInfo, wallet::Wallet}; -use chrono::Utc; - -#[test] -fn sign_and_verify_transactioninfo() { - let (wallet, mut wallet_pk) = Wallet::new(); - let transactioninfo: TransactionInfo = TransactionInfo::new(12345 as f32, Utc::now()); - - let signature = match wallet_pk.sign_transaction(&transactioninfo) { - Ok(signed_hashed_message) => signed_hashed_message, - _ => panic!("error while signing transaction"), - }; - println!("Transaction signature (signed using the wallet_pk):\n{:?}", signature); - - if wallet.verify_transaction_info(&transactioninfo, &signature) == true { - println!("transaction verified (by the wallet)"); - } else { - println!("transaction not verified"); - } -} diff --git a/tests/test_coin_selection.rs b/tests/test_coin_selection.rs new file mode 100644 index 0000000..63255b0 --- /dev/null +++ b/tests/test_coin_selection.rs @@ -0,0 +1,48 @@ +use cleyto_coin::chain::{utxo::UTXO, wallet::Wallet}; + +#[test] +fn test_get_utxo_wallet() { + let (mut wallet1, _) = Wallet::new(); + + let input_utxos = vec![ + // Large UTXOs - good for covering big amounts efficiently + UTXO::new(50000, wallet1.clone()), + UTXO::new(25000, wallet1.clone()), + // Medium UTXOs - typical transaction amounts + UTXO::new(10000, wallet1.clone()), + UTXO::new(5000, wallet1.clone()), + // Small UTXOs - test efficiency vs dust management + UTXO::new(3000, wallet1.clone()), + UTXO::new(1200, wallet1.clone()), + UTXO::new(1000, wallet1.clone()), + // Very small UTXOs - potential dust scenarios + UTXO::new(300, wallet1.clone()), + ]; + + fn print_utxo_vec(input_utxos: Vec) { + for utxo in input_utxos { + println!("utxo: ({})", utxo.value()); + } + } + + wallet1.add_utxos(input_utxos); + println!("Checkpoint 1"); + + assert_eq!( + wallet1.get_utxos(50000).unwrap(), + vec![UTXO::new(50000, wallet1.clone())] + ); + println!("Checkpoint 2"); + + assert!(wallet1.get_utxos(100000000).is_err()); + println!("Checkpoint 3"); + + print_utxo_vec(wallet1.get_utxos(30000).unwrap()); + println!("Checkpoint 4"); + + print_utxo_vec(wallet1.get_utxos(40000).unwrap()); + println!("Checkpoint 5"); + + print_utxo_vec(wallet1.get_utxos(60000).unwrap()); + println!("Checkpoint 6"); +} diff --git a/tests/test_ordered_vec.rs b/tests/test_ordered_vec.rs new file mode 100644 index 0000000..02401aa --- /dev/null +++ b/tests/test_ordered_vec.rs @@ -0,0 +1,37 @@ +use cleyto_coin::chain::ordered_vector::OrderedVec; +use cleyto_coin::chain::utxo::UTXO; +use cleyto_coin::chain::wallet::Wallet; + +#[test] +fn test_ordered_vec() { + let (wallet1, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(50000, wallet1.clone()), + UTXO::new(32000, wallet1.clone()), + UTXO::new(25000, wallet1.clone()), + UTXO::new(15000, wallet1.clone()), + UTXO::new(12000, wallet1.clone()), + UTXO::new(10000, wallet1.clone()), + UTXO::new(8500, wallet1.clone()), + UTXO::new(7200, wallet1.clone()), + UTXO::new(6000, wallet1.clone()), + UTXO::new(5500, wallet1.clone()), + UTXO::new(3000, wallet1.clone()), + UTXO::new(2500, wallet1.clone()), + UTXO::new(2000, wallet1.clone()), + UTXO::new(1500, wallet1.clone()), + UTXO::new(1200, wallet1.clone()), + UTXO::new(1000, wallet1.clone()), + UTXO::new(800, wallet1.clone()), + UTXO::new(600, wallet1.clone()), + UTXO::new(400, wallet1.clone()), + UTXO::new(300, wallet1.clone()), + ]; + + let vec = OrderedVec::from(input_utxos); + + for i in vec { + println!("utxo of value {}", i.value()); + } +} diff --git a/tests/transactions.rs b/tests/transactions.rs new file mode 100644 index 0000000..43095ee --- /dev/null +++ b/tests/transactions.rs @@ -0,0 +1,129 @@ +use cleyto_coin::chain::transaction::{Transaction, TransactionInfo}; +use cleyto_coin::chain::utxo::UTXO; +use cleyto_coin::chain::wallet::Wallet; + +#[test] +fn create_transaction() { + let (wallet_sender, walletpk_sender) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_receiver.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match walletpk_sender.sign_transaction(&transaction_info) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + // this will also be verified by the Transaction::new(); + if wallet_sender + .verify_transaction_info(&transaction_info, &signature) + .unwrap() + { + println!("transaction verified (by the wallet)"); + } else { + println!("transaction not verified"); + } + + let transaction: Transaction = + Transaction::new(wallet_sender, wallet_receiver, transaction_info, signature).unwrap(); + + println!("transaction.to_string(): {}", transaction); +} + +#[test] +fn test_transaction_info_creation() { + let (wallet_sender, _) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_receiver.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + println!("transaction info:\n{}", transaction_info); + println!("{:?}", transaction_info); +} + +#[test] +fn sign_and_verify_transaction_info() { + let (wallet_sender, wallet_pk) = Wallet::new(); + let (wallet_receiver, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet_sender.clone()), + UTXO::new(2000, wallet_sender.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, wallet_receiver.clone()), + UTXO::new(500, wallet_receiver.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match wallet_pk.sign_transaction(&transaction_info) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + println!( + "Transaction signature (signed using the wallet_pk):\n{:?}", + signature + ); + + if wallet_sender + .verify_transaction_info(&transaction_info, &signature) + .unwrap() + { + println!("transaction verified (by the wallet)"); + } else { + println!("transaction not verified"); + } +} + +#[test] +fn serialize_and_deserialize_transaction() { + let (wallet, wallet_pk) = Wallet::new(); + let (mallet, _) = Wallet::new(); + + let input_utxos = vec![ + UTXO::new(1000, wallet.clone()), + UTXO::new(2000, wallet.clone()), + ]; + let output_utxos = vec![ + UTXO::new(2500, mallet.clone()), + UTXO::new(500, mallet.clone()), + ]; + let transaction_info: TransactionInfo = TransactionInfo::new(input_utxos, output_utxos); + + let signature = match wallet_pk.sign_transaction(&transaction_info) { + Ok(signed_hashed_message) => signed_hashed_message, + _ => panic!("error while signing transaction"), + }; + + let transaction = Transaction::new(wallet, mallet, transaction_info, signature).unwrap(); + + let serialized_transaction = transaction.serialize(); + println!("serialized_transaction: \n{serialized_transaction}"); + + match Transaction::deserialize(serialized_transaction) { + Ok(value) => { + println!("Success deserializing transaction"); + value + } + Err(_) => panic!("Error deserializing transaction"), + }; +} diff --git a/todo.txt b/todo.txt new file mode 100644 index 0000000..4d0a36f --- /dev/null +++ b/todo.txt @@ -0,0 +1 @@ +write tests for the new get_utxos function, check if it panics or if something bad happens diff --git a/wallets/receiver/private.pem b/wallets/receiver/private.pem new file mode 100644 index 0000000..8489cc6 --- /dev/null +++ b/wallets/receiver/private.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDJGljHoOnQgY2y +Za1tabb7ZXGxp8aRq2OPFpcQ+IbNrDtgvh+ihj/n7lA4WVejj50n7xIUIP/TkEo/ ++ofZVYIwO9i6L5cDfY+zrAFpb6980nTCeLYk1CjGsNBEIW5UE53+sxXqeOH1QuyL +0ARrYDxvESyRifDArwksqKkgmmU5cJkD6uzv2JKPAmuRU6qlB6chwFQFkaPdIVc+ +G3ayPzGuzh9PmtnRlM0zeDrn3aEmEXC8Fr22MkAnk5xQb+ePPRyhqjd6U6g5IHvw +i8czKRfKdTzS9k875B15+L4fWi8QcP4vVJHbQc8lRgD6a1NDJczd2w88eeHKylNl +LPxOClJ5AgMBAAECggEACEASda4o/tWHJf3R4vPmqThSNRhNpzBBggn1N0xa4MPj +c7vuZFE0UfmO76DgCDpzbMNRbzijW4nx4d9kr1/jWsM5yE7817cmVsKvFCTxgszH +iYBA82XY073xQrpJt8f46rgXer/BgA8M0UzpbCujOb3vTCaSlUpuX+yoyPOh73yX +yVsDNlN71cIKqM0sLdzFNuYcO37IXEMWfgAZ71bfRge9YmPKZJtA3IPktgmlFe49 +b0rGoKIWqiIgyv96CtvFyTmFOWuJ6ik1l/eYJ38dSPkfRk/ol0QjghmvcObiR747 +q0AOiVUD4oVc3w/Mj1Gdzxciol6v0vZQk35cbZavGQKBgQDrNmpqqTB9onBcBmEu +mPOU5ZNoNpe5O+XK157VaS8VKxebudypZaqPo2JdsF/hkx9tPkrJfMs8kAAPRMFE +6JhWu8pt+sQ294kMU0wHUySH4IA85d+PcB5ZCUi8IghDbzB8NuWp5GWkYwzoy0YZ +30Ja7NKW3/+aHXU8AZ5wFTSgYwKBgQDa4DcDQmafT6s4mFtRSuSdiJoOYI7IQM0X +X5Mrlw/c00d8dpdUoPOPBmWZv3ywcY67zsW9sNwx2F+qhLSxiTVPi+5HykNFS7RH +EVxt4wHinX4mAJcjqMqEj2xivCmQWs7QabN7GNzQGF5ZdfwQZ64lASEwTJwYKq9V +zerZinyCcwKBgC/9/mCc/OPljP953cJgOvMalKUi3npRGmX08NeiipTLIhoIJln6 +AH8mWx+6qRWhqzvjBeduqxlEWH7FDJo+yzaHQpqGHBsLDs+Q/2ZPNJj02bWTQbZG +riqElm6skvsPaNkvalTr4UFVZIDrWPZWc3eR8rYOJl0PvafsvKMp8H75AoGBAI7Z +qW36J2owoApW/bqHy7+5SPq7MFUoXfK0USQw+oxgZJap+8ijJ3MgdK0s2d96rfKL +WGmehYgOtRlgdWItr9qT9Fdsfg07BJUhkpaxgyh5K7z3w6zlXA+6X73tGp95dON3 +KUndBzjVvrZal8HJOVIzc4rHZVUsfrTcqTuD9BFbAoGAe3qcjPZs6mUt02I56TWD +vIhFeVFF7KDDhVikjoNqM6f4tW2TzQoE0mjvxSx5vNgqOSk9bUgk0MeTKMYiIbdU +N5+zaSyMT98tr88FNBzJ4MyV/IU4RYDAZ3tZH+xtdM/85Flhs7YMaPGgGlTluMUT +z7sLsP3M1pZEZGQoSRhhma0= +-----END PRIVATE KEY----- diff --git a/wallets/receiver/public.pem b/wallets/receiver/public.pem new file mode 100644 index 0000000..ac08f7a --- /dev/null +++ b/wallets/receiver/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAyRpYx6Dp0IGNsmWtbWm2 ++2VxsafGkatjjxaXEPiGzaw7YL4fooY/5+5QOFlXo4+dJ+8SFCD/05BKP/qH2VWC +MDvYui+XA32Ps6wBaW+vfNJ0wni2JNQoxrDQRCFuVBOd/rMV6njh9ULsi9AEa2A8 +bxEskYnwwK8JLKipIJplOXCZA+rs79iSjwJrkVOqpQenIcBUBZGj3SFXPht2sj8x +rs4fT5rZ0ZTNM3g6592hJhFwvBa9tjJAJ5OcUG/njz0coao3elOoOSB78IvHMykX +ynU80vZPO+Qdefi+H1ovEHD+L1SR20HPJUYA+mtTQyXM3dsPPHnhyspTZSz8TgpS +eQIDAQAB +-----END PUBLIC KEY----- diff --git a/wallets/sender/private.pem b/wallets/sender/private.pem new file mode 100644 index 0000000..6f983a3 --- /dev/null +++ b/wallets/sender/private.pem @@ -0,0 +1,30 @@ +-----BEGIN ENCRYPTED PRIVATE KEY----- +MIIFNTBfBgkqhkiG9w0BBQ0wUjAxBgkqhkiG9w0BBQwwJAQQjWPn1hghIOF4nfRA +m43WcQICCAAwDAYIKoZIhvcNAgkFADAdBglghkgBZQMEASoEEELMuQ1uX/Sm/LOD +4vWpb+gEggTQf+VfrPAkqZQf9ioamfQunk3s826GEj6UmET6AMTzq/fwCuk7FqTc +y4AYtZgcl0ZCI0sUZckARe21MbDLK3Khnkl1DisgSpJ9wQ7GH2+4i7dkOntSVnx4 +213pycsECx4F4KR0vuQZYCt28YY7DF2nH2qZHPFlfNZsUEkFYC975Uc/a1DHBesA +15tsasnjxREVE0VNITmfq02dyRFb9dodasTVSjorlX/UqjNc3ELmyoxuSd+32Mwp +5S4cwX6WaVvhm00ywEWYhDB5Q7yLLTp1X95M1EGF2tpph4iYLjrvNVYpXA/BRJVq +M/U1vur80UB2XHTiKdVwg7SOw3K+qP4eznM0+XokklRCWd/rvzJnFAEEqOsojvM2 +YyNhuPGaIcoLzOZQiGDpSnBy7saT+goX3GjScBceBJtxn/7ZxUe0BVwK3OgG7Chk +XXEg7VH2OoB+WBjP75vAyRhm0z06OvZu1C/zwigsVv/gg4FgcN0i26Mi3xprpH4+ +qvgyICAi1VNWUIUcBUx+g5rjxiMOCsMPJi3ipAHsd78VTlhgAXvpJMGhzAY3QXpj +y8NzJ1HAuQgGim+UfnUd5T6OgzFLc2/nbWjbkh0i3pdTz9Tm9nwqZ6f2pU6dATzZ +mGHAPvU0MNZa+nMOmSPCsbNpFGSLVjt0uet0KS4zAe4j+mG9IOmv2CvBoWuCQeok +k3akNEhqVWhz3HNRSS2cqkXSxtfEf6/SEDpuzLq7y53ZL5933dgTiHnvEePkMKhO +w9/kTdLb1VO63RpI7Zn0Ms7/fbC72dPt9U6ucazMD8/035AlO1y6Ql4ce3WyBQUz +0JKRLH/vKMVUS2vlXzsGN+DQ2+gzO8/Vu1n5rAnNi5LM4JIdzhOmtzdUT3zW53uu +H6iwwbpEmF/gi5X6udr8jAl9xTN3KiHNkfYj/9BitZho5Ogs1GtRnMDkkQVU/NPq +oEWAGo0zLN6WX8aQVGBVqFnkScXcul2RCQaH8H/scsLz04w+Vs9Ium+YDgklbYOF +WRFhu+sNiakU7CYXU5wI9f21IdfxpLsy/y1JThgusllihI2hUXi5DaBFobm/NiHX +7BiJq0Vb8IbF9YRVFRid/KliU50WHz9hsWrLrB2m6buSw4lMJbkoaXIkXM/xUtDQ +go9UWMZqW5YmIIu3O9tmFUMd7fj7XsTk6atUcY/9r8mIoPQAn/YMNniKCY3sKgJS +3o30RpuT4pj2MsqMBSc0AjLgl+cdcROChhyr+CmeF9Cocej/gyXZ/qTj//+tru0G +h2fOTUsViaa8LQuP8rxDkP3r6Lgd3agHxh2q6HfNKFfxFtoZ6LMFi5i9yspuXqSW +CycsTRrSJg4RTh60PbiWwSvQeeMuX1NSL+ZvVONmaXxMexquJxiLYEKDqdBnFLiY +BfMDB942nBkTeZCr9QQ1hqCFR4bCvzODYwvaXXeiAbQZu20GCVYu9PLX/MTI2Rwe +MO/Kux/TxTV3lB20raaZVxaeBlxHXqio70JzHOyzMcrBeAjnlKFRhev3ZGKWusXi +Fh/yIUDd1PmGNqga92+6AZMPMfN+zv+ml66h4763PKqwFuOtR7r1AJCxCG2yLPON +sOg0pNZ8qdjUca3GTT/rS6xlAUzf1ZThOMEDH0tPGiQJt/8yGSCPawk= +-----END ENCRYPTED PRIVATE KEY----- diff --git a/wallets/sender/public.pem b/wallets/sender/public.pem new file mode 100644 index 0000000..d2fc9f0 --- /dev/null +++ b/wallets/sender/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAr9CsChZCtB4EfkUQYNMp +6NiqLb/0rfxtFxeDifWbK2kQuTAhJxO0KfvhE8uSR49bmhe+cQzlpVgjAEfi74bD +THSk5f2yIXh6tNMH/zuiX3vOFyawmgqP5Vl8KFvTYgQm5oRaMXjTOZWPlTLFMkPN +mV0Sx4q2mHEPAHu6TTUN5IUfvA07ECGRtNMpqyRcYSuQo/fkGehUDakjbISEhNJw +sffJllUWtZsfaIVpm3hgBw7CwhvW1liAOxfC3+xMzEOyfehWOy8ssAK77euFsLAX +QA/sVSfJnt05cC5naCwzls3K91AsNVUP7W31cbVXj0Nz24OD31CzjZHiWIUqM3c3 +lwIDAQAB +-----END PUBLIC KEY-----