From f14d95c1cb8182ae7e818422e6dbce29fc42703e Mon Sep 17 00:00:00 2001 From: Fiono Date: Fri, 27 Sep 2024 18:07:07 +0100 Subject: [PATCH 1/2] Add ed25519-dalek-rust --- nano/lib/numbers.cpp | 5 +- nano/lib/rsnano.hpp | 29 + rust/.cargo/config.toml | 6 + rust/Cargo.lock | 974 ++++++++++++++++++++++++++ rust/Cargo.toml | 3 + rust/README.md | 23 + rust/core/Cargo.toml | 17 + rust/core/src/key_pair.rs | 69 ++ rust/core/src/lib.rs | 25 + rust/core/src/raw_key.rs | 95 +++ rust/core/src/signature.rs | 117 ++++ rust/core/src/u256_struct.rs | 231 ++++++ rust/core/src/utils/container_info.rs | 12 + rust/core/src/utils/json.rs | 273 ++++++++ rust/core/src/utils/mod.rs | 194 +++++ rust/core/src/utils/stream.rs | 333 +++++++++ rust/ffi/Cargo.toml | 16 + rust/ffi/build.rs | 21 + rust/ffi/src/core/mod.rs | 40 ++ rust/ffi/src/lib.rs | 3 + 20 files changed, 2484 insertions(+), 2 deletions(-) create mode 100644 nano/lib/rsnano.hpp create mode 100644 rust/.cargo/config.toml create mode 100644 rust/Cargo.lock create mode 100644 rust/Cargo.toml create mode 100644 rust/README.md create mode 100644 rust/core/Cargo.toml create mode 100644 rust/core/src/key_pair.rs create mode 100644 rust/core/src/lib.rs create mode 100644 rust/core/src/raw_key.rs create mode 100644 rust/core/src/signature.rs create mode 100644 rust/core/src/u256_struct.rs create mode 100644 rust/core/src/utils/container_info.rs create mode 100644 rust/core/src/utils/json.rs create mode 100644 rust/core/src/utils/mod.rs create mode 100644 rust/core/src/utils/stream.rs create mode 100644 rust/ffi/Cargo.toml create mode 100644 rust/ffi/build.rs create mode 100644 rust/ffi/src/core/mod.rs create mode 100644 rust/ffi/src/lib.rs diff --git a/nano/lib/numbers.cpp b/nano/lib/numbers.cpp index f33d6f3cb3..0ddfaed6e1 100644 --- a/nano/lib/numbers.cpp +++ b/nano/lib/numbers.cpp @@ -409,7 +409,8 @@ nano::public_key nano::pub_key (nano::raw_key const & raw_key_a) nano::signature nano::sign_message (nano::raw_key const & private_key, nano::public_key const & public_key, uint8_t const * data, size_t size) { nano::signature result; - ed25519_sign (data, size, private_key.bytes.data (), public_key.bytes.data (), result.bytes.data ()); + if (rsnano::rsn_sign_message (private_key.bytes.data (), public_key.bytes.data (), data, size, result.bytes.data ()) != 0) + throw std::runtime_error ("could not sign message"); return result; } @@ -420,7 +421,7 @@ nano::signature nano::sign_message (nano::raw_key const & private_key, nano::pub bool nano::validate_message (nano::public_key const & public_key, uint8_t const * data, size_t size, nano::signature const & signature) { - return 0 != ed25519_sign_open (data, size, public_key.bytes.data (), signature.bytes.data ()); + return rsnano::rsn_validate_message ((uint8_t (*)[32])public_key.bytes.data (), data, size, (uint8_t (*)[64])signature.bytes.data ()); } bool nano::validate_message (nano::public_key const & public_key, nano::uint256_union const & message, nano::signature const & signature) diff --git a/nano/lib/rsnano.hpp b/nano/lib/rsnano.hpp new file mode 100644 index 0000000000..119b0b0e63 --- /dev/null +++ b/nano/lib/rsnano.hpp @@ -0,0 +1,29 @@ +#ifndef rs_nano_bindings_hpp +#define rs_nano_bindings_hpp + +#include +#include +#include +#include +#include + +namespace rsnano { + +extern "C" { + +int32_t rsn_sign_message(const uint8_t *priv_key, + const uint8_t *pub_key, + const uint8_t *message, + uintptr_t len, + uint8_t *signature); + +bool rsn_validate_message(const uint8_t (*pub_key)[32], + const uint8_t *message, + uintptr_t len, + const uint8_t (*signature)[64]); + +} // extern "C" + +} // namespace rsnano + +#endif // rs_nano_bindings_hpp diff --git a/rust/.cargo/config.toml b/rust/.cargo/config.toml new file mode 100644 index 0000000000..3b9697f8fc --- /dev/null +++ b/rust/.cargo/config.toml @@ -0,0 +1,6 @@ +[alias] +xtask = "run --package xtask --" + +[build] +target-dir = "../build/cargo" +#jobs = 4 diff --git a/rust/Cargo.lock b/rust/Cargo.lock new file mode 100644 index 0000000000..a311a597a4 --- /dev/null +++ b/rust/Cargo.lock @@ -0,0 +1,974 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "aes" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0" +dependencies = [ + "cfg-if", + "cipher", + "cpufeatures", +] + +[[package]] +name = "anyhow" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" + +[[package]] +name = "arrayvec" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c02d123df017efcdfbd739ef81735b36c5ba83ec3c59c80a9d7ecc718f92e50" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + +[[package]] +name = "base64ct" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c3c1a368f70d6cf7302d78f8f7093da241fb8e8807c05cc9e51a125895a6d5b" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + +[[package]] +name = "blake2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "46502ad458c9a52b69d4d4d32775c788b7a1b85e8bc9d482d92250fc0e3f8efe" +dependencies = [ + "digest", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "byte-slice-cast" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3ac9f8b63eca6fd385229b3675f6cc0dc5c8a5c8a54a59d4f52ffd670d87b0c" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "cbindgen" +version = "0.24.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b922faaf31122819ec80c4047cc684c6979a087366c069611e33649bf98e18d" +dependencies = [ + "clap", + "heck", + "indexmap 1.9.3", + "log", + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 1.0.109", + "tempfile", + "toml", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "cipher" +version = "0.4.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad" +dependencies = [ + "crypto-common", + "inout", +] + +[[package]] +name = "clap" +version = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "atty", + "bitflags 1.3.2", + "clap_lex", + "indexmap 1.9.3", + "strsim", + "termcolor", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "const-oid" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2459377285ad874054d797f3ccebf984978aa39129f6eafde5cdc8315b612f8" + +[[package]] +name = "cpufeatures" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" +dependencies = [ + "libc", +] + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "ctr" +version = "0.9.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0369ee1ad671834580515889b80f2ea915f23b8be8d0daa4bbaf2ac5c7590835" +dependencies = [ + "cipher", +] + +[[package]] +name = "curve25519-dalek" +version = "4.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97fb8b7c4503de7d6ae7b42ab72a5a59857b4c937ec27a3d4539dba95b5ab2be" +dependencies = [ + "cfg-if", + "cpufeatures", + "curve25519-dalek-derive", + "digest", + "fiat-crypto", + "rustc_version", + "subtle", + "zeroize", +] + +[[package]] +name = "curve25519-dalek-derive" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f46882e17999c6cc590af592290432be3bce0428cb0d5f8b6715e4dc7b383eb3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "der" +version = "0.7.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f55bf8e7b65898637379c1b74eb1551107c8294ed26d855ceb9fd1a09cfc9bc0" +dependencies = [ + "const-oid", + "zeroize", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", + "subtle", +] + +[[package]] +name = "ed25519" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "115531babc129696a58c64a4fef0a8bf9e9698629fb97e9e40767d235cfbcd53" +dependencies = [ + "pkcs8", + "signature", +] + +[[package]] +name = "ed25519-dalek" +version = "2.0.0-rc.3" +source = "git+https://github.com/Fiono11/ed25519-dalek.git?rev=e967e3792ed5aa4d67b89e98c2be1d719ef57aab#e967e3792ed5aa4d67b89e98c2be1d719ef57aab" +dependencies = [ + "blake2", + "curve25519-dalek", + "ed25519", + "rand_core", + "serde", + "zeroize", +] + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "errno" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" + +[[package]] +name = "fiat-crypto" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d" + +[[package]] +name = "fixed-hash" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "835c052cb0c08c1acf6ffd71c022172e18723949c8282f2b9f27efbc51e64534" +dependencies = [ + "byteorder", + "rand", + "rustc-hex", + "static_assertions", +] + +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "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", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "impl-codec" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67aa010c1e3da95bf151bd8b4c059b2ed7e75387cdb969b4f8f2723a43f9941" +dependencies = [ + "parity-scale-codec", +] + +[[package]] +name = "impl-trait-for-tuples" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d7a9f6330b71fea57921c9b61c47ee6e84f72d394754eff6163ae67e7395eb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", +] + +[[package]] +name = "inout" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5" +dependencies = [ + "generic-array", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "libc" +version = "0.2.155" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" + +[[package]] +name = "linux-raw-sys" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[package]] +name = "parity-scale-codec" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "306800abfa29c7f16596b5970a588435e3d5b3149683d00c12b699cc19f895ee" +dependencies = [ + "arrayvec", + "bitvec", + "byte-slice-cast", + "impl-trait-for-tuples", + "parity-scale-codec-derive", + "serde", +] + +[[package]] +name = "parity-scale-codec-derive" +version = "3.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d830939c76d294956402033aee57a6da7b438f2294eb94864c37b0569053a42c" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "pkcs8" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f950b2377845cebe5cf8b5165cb3cc1a5e0fa5cfa3e1f7f55707d8fd82e0a7b7" +dependencies = [ + "der", + "spki", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "primitive-types" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d15600a7d856470b7d278b3fe0e311fe28c2526348549f8ef2ff7db3299c87f5" +dependencies = [ + "fixed-hash", + "impl-codec", + "uint", +] + +[[package]] +name = "proc-macro-crate" +version = "3.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecf48c7ca261d60b74ab1a7b20da18bede46776b2e55535cb958eb595c5fa7b" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rsnano_core" +version = "0.1.0" +dependencies = [ + "aes", + "anyhow", + "ctr", + "ed25519-dalek", + "hex", + "primitive-types", + "rand", + "serde", + "serde_json", +] + +[[package]] +name = "rsnano_ffi" +version = "0.1.0" +dependencies = [ + "cbindgen", + "rsnano_core", +] + +[[package]] +name = "rustc-hex" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e75f6a532d0fd9f7f13144f392b6ad56a32696bfcd9c78f797f16bbb6f072d6" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" +dependencies = [ + "bitflags 2.6.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[package]] +name = "semver" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" + +[[package]] +name = "serde" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.204" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "serde_json" +version = "1.0.120" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "signature" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77549399552de45a898a580c1b41d445bf730df867cc44e6c0233bbc4b8329de" +dependencies = [ + "rand_core", +] + +[[package]] +name = "spki" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d91ed6c858b01f942cd56b37a94b3e0a1798290327d1236e4d9cf4eaca44d29d" +dependencies = [ + "base64ct", + "der", +] + +[[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.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[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.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b146dcf730474b4bcd16c311627b31ede9ab149045db4d6088b3becaea046462" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if", + "fastrand", + "rustix", + "windows-sys", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" + +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap 2.5.0", + "toml_datetime", + "winnow", +] + +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] +name = "uint" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "909988d098b2f738727b161a106cfc7cab00c539c2687a8836f8e565976fb53e" +dependencies = [ + "byteorder", + "crunchy", + "hex", + "static_assertions", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[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", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[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_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + +[[package]] +name = "zerocopy" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" +dependencies = [ + "byteorder", + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.7.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.71", +] + +[[package]] +name = "zeroize" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" diff --git a/rust/Cargo.toml b/rust/Cargo.toml new file mode 100644 index 0000000000..847fec711f --- /dev/null +++ b/rust/Cargo.toml @@ -0,0 +1,3 @@ +[workspace] +members = ["core", "ffi"] +resolver = "2" diff --git a/rust/README.md b/rust/README.md new file mode 100644 index 0000000000..4e668afee0 --- /dev/null +++ b/rust/README.md @@ -0,0 +1,23 @@ +# Rust codebase + +This folder contains all the Rust code of RsNano. + +The Rust code is structured according to A-frame architecture and is built with nullable infrastructure. This design and testing approach is extensively documented here: + +[http://www.jamesshore.com/v2/projects/nullables/testing-without-mocks] + +The following diagram shows how the crates are organized. The crates will be split up more when the codebase grows. + +![crate diagram](http://www.plantuml.com/plantuml/proxy?cache=no&fmt=svg&src=https://raw.github.com/simpago/rsnano-node/develop/rust/doc/crates.puml) + +* `main`: Contains the pure Rust node executable +* `ffi`: Contains all the glue code to connect the C++ and the Rust part (ffi = Foreign Function Interface) +* `node`: Contains the node implementation +* `rpc`: Contains the implemenation of the RPC server +* `ledger`: Contains the ledger implementation with. It is responsible for the consinstency of the data stores. +* `store_lmdb`: LMDB implementation of the data stores +* `messages`: Message types that nodes use for communication +* `network`: Manage outbound/inbound TCP channels to/from other nodes +* `core`: Contains the basic types like `BlockHash`, `Account`, `KeyPair`,... +* `nullables`: Nullable wrappers for infrastructure libraries + diff --git a/rust/core/Cargo.toml b/rust/core/Cargo.toml new file mode 100644 index 0000000000..b0c49819cb --- /dev/null +++ b/rust/core/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "rsnano_core" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +anyhow = "1" +ed25519-dalek = { git = "https://github.com/Fiono11/ed25519-dalek.git", rev = "e967e3792ed5aa4d67b89e98c2be1d719ef57aab", features = ["legacy_compatibility", "rand_core"] } +hex = "0" +serde = { version = "1", features = ["derive"] } +serde_json = "1.0.64" +rand = { version = "0" } +ctr = "0" +primitive-types = "0" +aes = "0" \ No newline at end of file diff --git a/rust/core/src/key_pair.rs b/rust/core/src/key_pair.rs new file mode 100644 index 0000000000..8561bc21c8 --- /dev/null +++ b/rust/core/src/key_pair.rs @@ -0,0 +1,69 @@ +use super::{PublicKey, RawKey, Signature}; +use anyhow::Context; +use ed25519_dalek::ed25519::signature::SignerMut; +use ed25519_dalek::Verifier; + +pub fn sign_message(private_key: &RawKey, data: &[u8]) -> Signature { + let secret = ed25519_dalek::SecretKey::from(*private_key.as_bytes()); + let mut signing_key = ed25519_dalek::SigningKey::from(&secret); + let signature = signing_key.sign(data); + Signature::from_bytes(signature.to_bytes()) +} + +pub fn validate_message( + public_key: &PublicKey, + message: &[u8], + signature: &Signature, +) -> anyhow::Result<()> { + let public = ed25519_dalek::VerifyingKey::from_bytes(public_key.as_bytes()) + .map_err(|_| anyhow!("could not extract public key"))?; + let sig = ed25519_dalek::Signature::from_bytes(signature.as_bytes()); + public + .verify(message, &sig) + .map_err(|_| anyhow!("could not verify message"))?; + Ok(()) +} + +#[cfg(test)] +mod tests { + use ed25519_dalek::ed25519::signature::SignerMut; + use super::*; + + #[test] + fn ed25519_signing() -> anyhow::Result<()> { + let secret_key = ed25519_dalek::SecretKey::from([0u8; 32]); + let message = [0u8; 32]; + let mut signing_key = ed25519_dalek::SigningKey::from(&secret_key); + let public_key = ed25519_dalek::VerifyingKey::from(&signing_key); + let signature = signing_key.sign(&message); + public_key.verify_strict(&message, &signature).unwrap(); + + let mut sig_bytes = signature.to_bytes(); + sig_bytes[32] ^= 0x1; + let signature = ed25519_dalek::Signature::from_bytes(&sig_bytes); + assert!(public_key.verify_strict(&message, &signature).is_err()); + + Ok(()) + } + + #[test] + fn sign_message_test() -> anyhow::Result<()> { + let keypair = KeyPair::new(); + let data = [0u8; 32]; + let signature = sign_message(&keypair.private_key(), &data); + validate_message(&keypair.public_key(), &data, &signature)?; + Ok(()) + } + + #[test] + fn signing_same_message_twice_produces_equal_signatures() { + // the C++ implementation adds random bytes and a padding when signing for extra security and for making side channel attacks more difficult. + // Currently the Rust impl does not do that. + // In C++ signing the same message twice will produce different signatures. In Rust we get the same signature. + let keypair = KeyPair::new(); + let data = [1, 2, 3]; + let signature_a = sign_message(&keypair.private_key(), &data); + let signature_b = sign_message(&keypair.private_key(), &data); + assert_eq!(signature_a, signature_b); + } +} diff --git a/rust/core/src/lib.rs b/rust/core/src/lib.rs new file mode 100644 index 0000000000..4130fe0071 --- /dev/null +++ b/rust/core/src/lib.rs @@ -0,0 +1,25 @@ +#[macro_use] +extern crate anyhow; + +pub mod key_pair; +pub use key_pair::*; + +mod utils; + +pub mod signature; +pub use signature::*; + +mod raw_key; +pub use raw_key::RawKey; + +mod u256_struct; + +u256_struct!(PublicKey); +serialize_32_byte_string!(PublicKey); + +pub fn write_hex_bytes(bytes: &[u8], f: &mut std::fmt::Formatter) -> Result<(), std::fmt::Error> { + for &byte in bytes { + write!(f, "{:02X}", byte)?; + } + Ok(()) +} \ No newline at end of file diff --git a/rust/core/src/raw_key.rs b/rust/core/src/raw_key.rs new file mode 100644 index 0000000000..2154365675 --- /dev/null +++ b/rust/core/src/raw_key.rs @@ -0,0 +1,95 @@ +use crate::{serialize_32_byte_string, u256_struct}; +use ctr::cipher::{KeyIvInit, StreamCipher}; +use rand::{thread_rng, Rng}; +use std::ops::BitXorAssign; + +type Aes256Ctr = ctr::Ctr64BE; + +u256_struct!(RawKey); +serialize_32_byte_string!(RawKey); + +impl RawKey { + pub fn random() -> Self { + Self::from_bytes(thread_rng().gen()) + } + + pub fn encrypt(&self, key: &RawKey, iv: &[u8; 16]) -> Self { + let mut cipher = Aes256Ctr::new(&(*key.as_bytes()).into(), &(*iv).into()); + let mut buf = self.0; + cipher.apply_keystream(&mut buf); + RawKey(buf) + } + + pub fn decrypt(&self, key: &RawKey, iv: &[u8; 16]) -> Self { + self.encrypt(key, iv) + } + + /// IV for Key encryption + pub fn initialization_vector_low(&self) -> [u8; 16] { + self.0[..16].try_into().unwrap() + } + + /// IV for Key encryption + pub fn initialization_vector_high(&self) -> [u8; 16] { + self.0[16..].try_into().unwrap() + } +} + +impl BitXorAssign for RawKey { + fn bitxor_assign(&mut self, rhs: Self) { + for (a, b) in self.0.iter_mut().zip(rhs.0) { + *a ^= b; + } + } +} + +#[cfg(test)] +mod tests { + use crate::{KeyPair, PublicKey}; + + use super::*; + + #[test] + fn encrypt() { + let clear_text = RawKey::from(1); + let key = RawKey::from(2); + let iv: u128 = 123; + let encrypted = RawKey::encrypt(&clear_text, &key, &iv.to_be_bytes()); + let expected = + RawKey::decode_hex("3ED412A6F9840EA148EAEE236AFD10983D8E11326B07DFB33C5E1C47000AF3FD") + .unwrap(); + assert_eq!(encrypted, expected) + } + + #[test] + fn encrypt_and_decrypt() { + let clear_text = RawKey::from(1); + let key = RawKey::from(2); + let iv: u128 = 123; + let encrypted = clear_text.encrypt(&key, &iv.to_be_bytes()); + let decrypted = encrypted.decrypt(&key, &iv.to_be_bytes()); + assert_eq!(decrypted, clear_text) + } + + #[test] + fn key_encryption() { + let keypair = KeyPair::new(); + let secret_key = RawKey::zero(); + let iv = keypair.public_key().initialization_vector(); + let encrypted = keypair.private_key().encrypt(&secret_key, &iv); + let decrypted = encrypted.decrypt(&secret_key, &iv); + assert_eq!(keypair.private_key(), decrypted); + let decrypted_pub = PublicKey::try_from(&decrypted).unwrap(); + assert_eq!(keypair.public_key(), decrypted_pub); + } + + #[test] + fn encrypt_produces_same_result_every_time() { + let secret = RawKey::zero(); + let number = RawKey::from(1); + let iv = [1; 16]; + let encrypted1 = number.encrypt(&secret, &iv); + let encrypted2 = number.encrypt(&secret, &iv); + assert_eq!(encrypted1, encrypted2); + } +} diff --git a/rust/core/src/signature.rs b/rust/core/src/signature.rs new file mode 100644 index 0000000000..e80dabb1c9 --- /dev/null +++ b/rust/core/src/signature.rs @@ -0,0 +1,117 @@ +use serde::de::{Unexpected, Visitor}; +use crate::utils::{BufferWriter, Serialize, Stream}; +use std::fmt::Write; + +#[derive(Clone, PartialEq, Eq, Debug, PartialOrd, Ord)] +pub struct Signature { + bytes: [u8; 64], +} + +impl Default for Signature { + fn default() -> Self { + Self { bytes: [0; 64] } + } +} + +impl Signature { + pub fn new() -> Self { + Self { bytes: [0u8; 64] } + } + + pub fn from_bytes(bytes: [u8; 64]) -> Self { + Self { bytes } + } + + pub fn try_from_bytes(bytes: &[u8]) -> anyhow::Result { + Ok(Self::from_bytes(bytes.try_into()?)) + } + + pub const fn serialized_size() -> usize { + 64 + } + + pub fn deserialize(stream: &mut dyn Stream) -> anyhow::Result { + let mut result = Signature { bytes: [0; 64] }; + + stream.read_bytes(&mut result.bytes, 64)?; + Ok(result) + } + + pub fn as_bytes(&'_ self) -> &'_ [u8; 64] { + &self.bytes + } + + pub unsafe fn copy_bytes(&self, target: *mut u8) { + let bytes = std::slice::from_raw_parts_mut(target, 64); + bytes.copy_from_slice(self.as_bytes()); + } + + pub fn make_invalid(&mut self) { + self.bytes[31] ^= 1; + } + + pub fn encode_hex(&self) -> String { + let mut result = String::with_capacity(128); + for byte in self.bytes { + write!(&mut result, "{:02X}", byte).unwrap(); + } + result + } + + pub fn decode_hex(s: impl AsRef) -> anyhow::Result { + let mut bytes = [0u8; 64]; + hex::decode_to_slice(s.as_ref(), &mut bytes)?; + Ok(Signature::from_bytes(bytes)) + } + + pub unsafe fn from_ptr(ptr: *const u8) -> Self { + let mut bytes = [0; 64]; + bytes.copy_from_slice(std::slice::from_raw_parts(ptr, 64)); + Signature::from_bytes(bytes) + } +} + +impl Serialize for Signature { + fn serialize(&self, writer: &mut dyn BufferWriter) { + writer.write_bytes_safe(&self.bytes) + } +} + +impl serde::Serialize for Signature { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.encode_hex()) + } +} + +impl<'de> serde::Deserialize<'de> for Signature { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = deserializer.deserialize_str(SignatureVisitor {})?; + Ok(value) + } +} + +pub(crate) struct SignatureVisitor {} + +impl<'de> Visitor<'de> for SignatureVisitor { + type Value = Signature; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex string containing 64 bytes") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let signature = Signature::decode_hex(v).map_err(|_| { + serde::de::Error::invalid_value(Unexpected::Str(v), &"a hex string containing 64 bytes") + })?; + Ok(signature) + } +} diff --git a/rust/core/src/u256_struct.rs b/rust/core/src/u256_struct.rs new file mode 100644 index 0000000000..c201c24e9c --- /dev/null +++ b/rust/core/src/u256_struct.rs @@ -0,0 +1,231 @@ +use serde::de::{Unexpected, Visitor}; + +#[macro_export] +macro_rules! u256_struct { + ($name:ident) => { + #[derive(PartialEq, Eq, Clone, Copy, Hash, Default, PartialOrd, Ord)] + pub struct $name([u8; 32]); + + #[allow(dead_code)] + impl $name { + pub const fn zero() -> Self { + Self([0; 32]) + } + + pub fn is_zero(&self) -> bool { + self.0 == [0; 32] + } + + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + Self(bytes) + } + + pub fn from_slice(bytes: &[u8]) -> Option { + match bytes.try_into() { + Ok(value) => Some(Self(value)), + Err(_) => None, + } + } + + pub fn as_bytes(&'_ self) -> &'_ [u8; 32] { + &self.0 + } + + pub unsafe fn copy_bytes(&self, target: *mut u8) { + let target_slice = std::slice::from_raw_parts_mut(target, 32); + target_slice.copy_from_slice(self.as_bytes()); + } + + pub fn number(&self) -> primitive_types::U256 { + primitive_types::U256::from_big_endian(&self.0) + } + + pub fn inc(&self) -> Option { + self.number() + .checked_add(primitive_types::U256::from(1)) + .map(|i| Self::from(i)) + } + + pub fn encode_hex(&self) -> String { + use std::fmt::Write; + let mut result = String::with_capacity(64); + for &byte in self.as_bytes() { + write!(&mut result, "{:02X}", byte).unwrap(); + } + result + } + + pub fn decode_hex(s: impl AsRef) -> anyhow::Result { + Ok(Self::from_bytes(crate::u256_struct::decode_32_bytes_hex( + s, + )?)) + } + + pub unsafe fn from_ptr(data: *const u8) -> Self { + Self(std::slice::from_raw_parts(data, 32).try_into().unwrap()) + } + } + + impl $crate::utils::Serialize for $name { + fn serialize(&self, writer: &mut dyn $crate::utils::BufferWriter) { + writer.write_bytes_safe(&self.0) + } + } + + impl $crate::utils::FixedSizeSerialize for $name { + fn serialized_size() -> usize { + 32 + } + } + + impl $crate::utils::Deserialize for $name { + type Target = Self; + fn deserialize(stream: &mut dyn $crate::utils::Stream) -> anyhow::Result { + let mut result = Self::zero(); + stream.read_bytes(&mut result.0, 32)?; + Ok(result) + } + } + + impl From for $name { + fn from(value: i32) -> Self { + let mut bytes = [0; 32]; + bytes[28..].copy_from_slice(&value.to_be_bytes()); + Self::from_bytes(bytes) + } + } + + impl From for $name { + fn from(value: u64) -> Self { + let mut bytes = [0; 32]; + bytes[24..].copy_from_slice(&value.to_be_bytes()); + Self::from_bytes(bytes) + } + } + + impl From for $name { + fn from(value: u128) -> Self { + let mut bytes = [0; 32]; + bytes[16..].copy_from_slice(&value.to_be_bytes()); + Self::from_bytes(bytes) + } + } + + impl From for $name { + fn from(value: primitive_types::U256) -> Self { + let mut key = Self::zero(); + value.to_big_endian(); + key + } + } + + impl std::fmt::Debug for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + $crate::write_hex_bytes(&self.0, f) + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + $crate::write_hex_bytes(&self.0, f) + } + } + }; +} + +pub fn decode_32_bytes_hex(s: impl AsRef) -> anyhow::Result<[u8; 32]> { + let s = s.as_ref(); + if s.is_empty() || s.len() > 64 { + bail!( + "Invalid U256 string length. Expected <= 64 but was {}", + s.len() + ); + } + + let mut padded_string = String::new(); + let sanitized = if s.len() < 64 { + for _ in 0..(64 - s.len()) { + padded_string.push('0'); + } + padded_string.push_str(s); + &padded_string + } else { + s + }; + + let mut bytes = [0u8; 32]; + hex::decode_to_slice(sanitized, &mut bytes)?; + Ok(bytes) +} + +#[macro_export] +macro_rules! serialize_32_byte_string { + ($name:ident) => { + impl serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(&self.encode_hex()) + } + } + + impl<'de> serde::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = deserializer.deserialize_str(crate::u256_struct::U256Visitor {})?; + Ok(Self::from_bytes(value)) + } + } + }; +} + +pub(crate) struct U256Visitor {} + +impl<'de> Visitor<'de> for U256Visitor { + type Value = [u8; 32]; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex string containing 32 bytes") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + let bytes = decode_32_bytes_hex(v).map_err(|_| { + serde::de::Error::invalid_value(Unexpected::Str(v), &"a hex string containing 32 bytes") + })?; + Ok(bytes) + } +} + +#[cfg(test)] +mod tests { + u256_struct!(U256Test); + + #[test] + fn constructor() { + let x = U256Test::zero(); + assert_eq!(x.0, [0; 32]); + assert!(x.is_zero()); + } + + #[test] + fn encode_hex() { + assert_eq!( + U256Test::zero().encode_hex(), + "0000000000000000000000000000000000000000000000000000000000000000" + ); + assert_eq!( + U256Test::from(0x12ab).encode_hex(), + "00000000000000000000000000000000000000000000000000000000000012AB" + ); + assert_eq!( + U256Test::from_bytes([0xff; 32]).encode_hex(), + "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF" + ); + } +} diff --git a/rust/core/src/utils/container_info.rs b/rust/core/src/utils/container_info.rs new file mode 100644 index 0000000000..e45142ff25 --- /dev/null +++ b/rust/core/src/utils/container_info.rs @@ -0,0 +1,12 @@ +#[derive(Clone)] +pub struct ContainerInfo { + pub name: String, + pub count: usize, + pub sizeof_element: usize, +} + +#[derive(Clone)] +pub enum ContainerInfoComponent { + Leaf(ContainerInfo), + Composite(String, Vec), +} diff --git a/rust/core/src/utils/json.rs b/rust/core/src/utils/json.rs new file mode 100644 index 0000000000..578e169d19 --- /dev/null +++ b/rust/core/src/utils/json.rs @@ -0,0 +1,273 @@ +use serde_json::{Map, Value}; +use std::any::Any; +use std::collections::HashMap; + +pub trait PropertyTree { + fn get_string(&self, path: &str) -> anyhow::Result; + fn get_bool(&self, path: &str, default_value: bool) -> bool { + self.get_string(path) + .map(|s| s == "true") + .unwrap_or(default_value) + } + fn get_child(&self, path: &str) -> Option>; + fn get_children(&self) -> Vec<(String, Box)>; + fn data(&self) -> String; + fn clear(&mut self) -> anyhow::Result<()>; + fn put_string(&mut self, path: &str, value: &str) -> anyhow::Result<()>; + fn put_u64(&mut self, path: &str, value: u64) -> anyhow::Result<()>; + fn new_writer(&self) -> Box; + fn push_back(&mut self, path: &str, value: &dyn PropertyTree); + fn add_child(&mut self, path: &str, value: &dyn PropertyTree); + fn put_child(&mut self, path: &str, value: &dyn PropertyTree); + fn add(&mut self, path: &str, value: &str) -> anyhow::Result<()>; + fn as_any(&self) -> &dyn Any; + fn as_any_mut(&mut self) -> &mut dyn Any; + fn to_json(&self) -> String; +} + +pub trait PropertyTreeWriter {} + +pub struct TestPropertyTree { + properties: HashMap, +} + +impl TestPropertyTree { + pub fn new() -> Self { + Self { + properties: HashMap::new(), + } + } +} + +impl PropertyTree for TestPropertyTree { + fn get_string(&self, path: &str) -> anyhow::Result { + self.properties + .get(path) + .cloned() + .ok_or_else(|| anyhow!("path not found")) + } + + fn get_child(&self, _path: &str) -> Option> { + unimplemented!() + } + + fn get_children(&self) -> Vec<(String, Box)> { + unimplemented!() + } + + fn data(&self) -> String { + unimplemented!() + } + + fn put_string(&mut self, path: &str, value: &str) -> anyhow::Result<()> { + self.properties.insert(path.to_owned(), value.to_owned()); + Ok(()) + } + + fn put_u64(&mut self, _path: &str, _value: u64) -> anyhow::Result<()> { + todo!() + } + + fn new_writer(&self) -> Box { + todo!() + } + + fn push_back(&mut self, _path: &str, _value: &dyn PropertyTree) { + todo!() + } + + fn add_child(&mut self, _path: &str, _value: &dyn PropertyTree) { + todo!() + } + + fn add(&mut self, _path: &str, _value: &str) -> anyhow::Result<()> { + todo!() + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn clear(&mut self) -> anyhow::Result<()> { + todo!() + } + + fn put_child(&mut self, _path: &str, _value: &dyn PropertyTree) { + todo!() + } + + fn to_json(&self) -> String { + todo!() + } +} + +pub fn as_nano_json(value: bool) -> &'static str { + if value { + "true" + } else { + "false" + } +} + +#[derive(Clone)] +pub struct SerdePropertyTree { + pub value: Value, +} + +impl SerdePropertyTree { + pub fn new() -> Self { + Self { + value: Value::Object(Map::new()), + } + } + + pub fn from_value(value: Value) -> Self { + Self { value } + } + + pub fn parse(s: &str) -> anyhow::Result { + Ok(Self { + value: serde_json::from_str(s)?, + }) + } + + pub fn add_child_value(&mut self, path: String, value: Value) { + let Value::Object(map) = &mut self.value else { + panic!("not an object"); + }; + map.insert(path, value); + } +} + +impl PropertyTree for SerdePropertyTree { + fn get_string(&self, path: &str) -> anyhow::Result { + match self.value.get(path) { + Some(v) => match v { + serde_json::Value::String(s) => Ok(s.to_owned()), + _ => Err(anyhow!("not a string value")), + }, + None => Err(anyhow!("could not find path")), + } + } + + fn get_child(&self, path: &str) -> Option> { + self.value.get(path).map(|value| { + let child: Box = Box::new(Self { + value: value.clone(), + }); + child + }) + } + + fn get_children(&self) -> Vec<(String, Box)> { + match &self.value { + Value::Array(array) => array + .iter() + .map(|i| { + let reader: Box = + Box::new(SerdePropertyTree { value: i.clone() }); + (String::default(), reader) + }) + .collect(), + Value::Object(object) => object + .iter() + .map(|(k, v)| { + let reader: Box = + Box::new(SerdePropertyTree { value: v.clone() }); + (k.clone(), reader) + }) + .collect(), + _ => Vec::new(), + } + } + + fn data(&self) -> String { + match &self.value { + Value::String(s) => s.clone(), + _ => unimplemented!(), + } + } + + fn clear(&mut self) -> anyhow::Result<()> { + self.value = Value::Object(Map::new()); + Ok(()) + } + + fn put_string(&mut self, path: &str, value: &str) -> anyhow::Result<()> { + let Value::Object(map) = &mut self.value else { + bail!("not an object") + }; + map.insert(path.to_string(), Value::String(value.to_string())); + Ok(()) + } + + fn put_u64(&mut self, path: &str, value: u64) -> anyhow::Result<()> { + let Value::Object(map) = &mut self.value else { + bail!("not an object") + }; + map.insert(path.to_string(), Value::Number(value.into())); + Ok(()) + } + + fn new_writer(&self) -> Box { + Box::new(Self::new()) + } + + fn push_back(&mut self, _path: &str, _value: &dyn PropertyTree) { + todo!() + } + + fn add_child(&mut self, path: &str, value: &dyn PropertyTree) { + let child = value + .as_any() + .downcast_ref::() + .expect("not a serde ptree"); + + let Value::Object(map) = &mut self.value else { + panic!("not an object"); + }; + map.insert(path.to_string(), child.value.clone()); + } + + fn put_child(&mut self, _path: &str, _value: &dyn PropertyTree) { + todo!() + } + + fn add(&mut self, path: &str, value: &str) -> anyhow::Result<()> { + self.put_string(path, value) + } + + fn as_any(&self) -> &dyn Any { + self + } + + fn as_any_mut(&mut self) -> &mut dyn Any { + self + } + + fn to_json(&self) -> String { + self.value.to_string() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn property_not_found() { + let tree = TestPropertyTree::new(); + assert!(tree.get_string("DoesNotExist").is_err()); + } + + #[test] + fn set_string_property() { + let mut tree = TestPropertyTree::new(); + tree.put_string("foo", "bar").unwrap(); + assert_eq!(tree.get_string("foo").unwrap(), "bar"); + } +} diff --git a/rust/core/src/utils/mod.rs b/rust/core/src/utils/mod.rs new file mode 100644 index 0000000000..836ee86a38 --- /dev/null +++ b/rust/core/src/utils/mod.rs @@ -0,0 +1,194 @@ +mod container_info; +mod json; +mod stream; + +pub use container_info::{ContainerInfo, ContainerInfoComponent}; +pub use json::*; +use std::{ + net::{Ipv6Addr, SocketAddrV6}, + thread::available_parallelism, + time::{Duration, SystemTime, UNIX_EPOCH}, +}; +pub use stream::*; + +pub trait Serialize { + fn serialize(&self, stream: &mut dyn BufferWriter); +} + +pub trait FixedSizeSerialize: Serialize { + fn serialized_size() -> usize; +} + +pub trait Deserialize { + type Target; + fn deserialize(stream: &mut dyn Stream) -> anyhow::Result; +} + +impl Serialize for u64 { + fn serialize(&self, stream: &mut dyn BufferWriter) { + stream.write_u64_be_safe(*self) + } +} + +impl FixedSizeSerialize for u64 { + fn serialized_size() -> usize { + std::mem::size_of::() + } +} + +impl Deserialize for u64 { + type Target = Self; + fn deserialize(stream: &mut dyn Stream) -> anyhow::Result { + stream.read_u64_be() + } +} + +impl Serialize for [u8; 64] { + fn serialize(&self, stream: &mut dyn BufferWriter) { + stream.write_bytes_safe(self) + } +} + +impl FixedSizeSerialize for [u8; 64] { + fn serialized_size() -> usize { + 64 + } +} + +impl Deserialize for [u8; 64] { + type Target = Self; + + fn deserialize(stream: &mut dyn Stream) -> anyhow::Result { + let mut buffer = [0; 64]; + stream.read_bytes(&mut buffer, 64)?; + Ok(buffer) + } +} + +pub fn get_cpu_count() -> usize { + // Try to read overridden value from environment variable + let value = std::env::var("NANO_HARDWARE_CONCURRENCY") + .unwrap_or_else(|_| "0".into()) + .parse::() + .unwrap_or_default(); + + if value > 0 { + return value; + } + + available_parallelism().unwrap().get() +} + +pub type MemoryIntensiveInstrumentationCallback = extern "C" fn() -> bool; + +pub static mut MEMORY_INTENSIVE_INSTRUMENTATION: Option = + None; + +extern "C" fn default_is_sanitizer_build_callback() -> bool { + false +} +pub static mut IS_SANITIZER_BUILD: MemoryIntensiveInstrumentationCallback = + default_is_sanitizer_build_callback; + +pub fn memory_intensive_instrumentation() -> bool { + match std::env::var("NANO_MEMORY_INTENSIVE") { + Ok(val) => matches!(val.to_lowercase().as_str(), "1" | "true" | "on"), + Err(_) => unsafe { + match MEMORY_INTENSIVE_INSTRUMENTATION { + Some(f) => f(), + None => false, + } + }, + } +} + +pub fn is_sanitizer_build() -> bool { + unsafe { IS_SANITIZER_BUILD() } +} + +pub fn nano_seconds_since_epoch() -> u64 { + system_time_as_nanoseconds(SystemTime::now()) +} + +pub fn milliseconds_since_epoch() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_millis() as u64 +} + +pub fn seconds_since_epoch() -> u64 { + SystemTime::now() + .duration_since(SystemTime::UNIX_EPOCH) + .unwrap() + .as_secs() +} + +pub fn system_time_from_nanoseconds(nanos: u64) -> SystemTime { + SystemTime::UNIX_EPOCH + Duration::from_nanos(nanos) +} + +pub fn system_time_as_nanoseconds(time: SystemTime) -> u64 { + time.duration_since(SystemTime::UNIX_EPOCH) + .expect("Time went backwards") + .as_nanos() as u64 +} + +pub fn get_env_or_default(variable_name: &str, default: T) -> T +where + T: core::str::FromStr + Copy, +{ + std::env::var(variable_name) + .map(|v| v.parse::().unwrap_or(default)) + .unwrap_or(default) +} + +pub fn get_env_or_default_string(variable_name: &str, default: impl Into) -> String { + std::env::var(variable_name).unwrap_or_else(|_| default.into()) +} + +pub fn get_env_bool(variable_name: impl AsRef) -> Option { + let variable_name = variable_name.as_ref(); + std::env::var(variable_name) + .ok() + .map(|val| match val.to_lowercase().as_ref() { + "1" | "true" | "on" => true, + "0" | "false" | "off" => false, + _ => panic!("Invalid environment boolean value: {variable_name} = {val}"), + }) +} + +pub trait Latch: Send + Sync { + fn wait(&self); +} + +pub struct NullLatch {} + +impl NullLatch { + pub fn new() -> Self { + Self {} + } +} + +impl Latch for NullLatch { + fn wait(&self) {} +} + +pub fn parse_endpoint(s: &str) -> SocketAddrV6 { + s.parse().unwrap() +} + +pub const NULL_ENDPOINT: SocketAddrV6 = SocketAddrV6::new(Ipv6Addr::UNSPECIFIED, 0, 0, 0); + +pub const TEST_ENDPOINT_1: SocketAddrV6 = + SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0xffff, 0x10, 0, 0, 1), 1111, 0, 0); + +pub const TEST_ENDPOINT_2: SocketAddrV6 = + SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0xffff, 0x10, 0, 0, 2), 2222, 0, 0); + +pub const TEST_ENDPOINT_3: SocketAddrV6 = + SocketAddrV6::new(Ipv6Addr::new(0, 0, 0, 0xffff, 0x10, 0, 0, 3), 3333, 0, 0); + +pub fn new_test_timestamp() -> SystemTime { + UNIX_EPOCH + Duration::from_secs(1_000_000) +} diff --git a/rust/core/src/utils/stream.rs b/rust/core/src/utils/stream.rs new file mode 100644 index 0000000000..8618065175 --- /dev/null +++ b/rust/core/src/utils/stream.rs @@ -0,0 +1,333 @@ +pub trait Stream { + fn write_u8(&mut self, value: u8) -> anyhow::Result<()>; + fn write_bytes(&mut self, bytes: &[u8]) -> anyhow::Result<()>; + fn read_u8(&mut self) -> anyhow::Result; + fn read_bytes(&mut self, buffer: &mut [u8], len: usize) -> anyhow::Result<()>; + + /// Looking ahead into the stream. + /// returns: The number of characters available. + /// If a read position is available, returns the number of characters + /// available for reading before the buffer must be refilled. + /// Otherwise returns the derived showmanyc(). + fn in_avail(&mut self) -> anyhow::Result; +} + +pub trait StreamExt: Stream { + fn read_u32_be(&mut self) -> anyhow::Result { + let mut buffer = [0u8; 4]; + self.read_bytes(&mut buffer, 4)?; + Ok(u32::from_be_bytes(buffer)) + } + + fn read_u64_be(&mut self) -> anyhow::Result { + let mut buffer = [0u8; 8]; + self.read_bytes(&mut buffer, 8)?; + Ok(u64::from_be_bytes(buffer)) + } + + fn read_u64_le(&mut self) -> anyhow::Result { + let mut buffer = [0u8; 8]; + self.read_bytes(&mut buffer, 8)?; + Ok(u64::from_le_bytes(buffer)) + } + + fn read_u128_le(&mut self) -> anyhow::Result { + let mut buffer = [0u8; 16]; + self.read_bytes(&mut buffer, 16)?; + Ok(u128::from_le_bytes(buffer)) + } + + fn read_u128_be(&mut self) -> anyhow::Result { + let mut buffer = [0u8; 16]; + self.read_bytes(&mut buffer, 16)?; + Ok(u128::from_be_bytes(buffer)) + } + + fn read_u64_ne(&mut self) -> anyhow::Result { + let mut buffer = [0u8; 8]; + self.read_bytes(&mut buffer, 8)?; + Ok(u64::from_ne_bytes(buffer)) + } + + fn write_u32_be(&mut self, value: u32) -> anyhow::Result<()> { + self.write_bytes(&value.to_be_bytes()) + } + + fn write_u64_be(&mut self, value: u64) -> anyhow::Result<()> { + self.write_bytes(&value.to_be_bytes()) + } + + fn write_u64_ne(&mut self, value: u64) -> anyhow::Result<()> { + self.write_bytes(&value.to_ne_bytes()) + } +} + +impl StreamExt for T {} + +#[derive(Default)] +pub struct MemoryStream { + bytes: Vec, + read_index: usize, +} + +impl MemoryStream { + pub fn new() -> Self { + Default::default() + } + + pub fn bytes_written(&self) -> usize { + self.bytes.len() + } + + pub fn byte_at(&self, i: usize) -> u8 { + self.bytes[i] + } + + pub fn as_bytes(&self) -> &[u8] { + &self.bytes + } + + pub fn to_vec(self) -> Vec { + self.bytes + } + + pub fn at_end(&self) -> bool { + self.bytes.len() - self.read_index == 0 + } +} + +impl BufferWriter for MemoryStream { + fn write_bytes_safe(&mut self, bytes: &[u8]) { + self.bytes.extend_from_slice(bytes); + } + + fn write_u8_safe(&mut self, value: u8) { + self.bytes.push(value); + } + + fn write_u32_be_safe(&mut self, value: u32) { + self.write_bytes_safe(&value.to_be_bytes()); + } + + fn write_u64_be_safe(&mut self, value: u64) { + self.write_bytes_safe(&value.to_be_bytes()); + } + + fn write_u64_ne_safe(&mut self, value: u64) { + self.write_bytes_safe(&value.to_ne_bytes()); + } +} + +impl Stream for MemoryStream { + fn write_u8(&mut self, value: u8) -> anyhow::Result<()> { + self.bytes.push(value); + Ok(()) + } + + fn write_bytes(&mut self, bytes: &[u8]) -> anyhow::Result<()> { + self.bytes.extend_from_slice(bytes); + Ok(()) + } + + fn read_u8(&mut self) -> anyhow::Result { + if self.read_index >= self.bytes.len() { + bail!("no more bytes to read") + } + + let result = self.bytes[self.read_index]; + self.read_index += 1; + Ok(result) + } + + fn read_bytes(&mut self, buffer: &mut [u8], len: usize) -> anyhow::Result<()> { + if self.read_index + len > self.bytes.len() { + bail!("not enough bytes to read") + } + + buffer.copy_from_slice(&self.bytes[self.read_index..self.read_index + len]); + self.read_index += len; + Ok(()) + } + + fn in_avail(&mut self) -> anyhow::Result { + Ok(self.bytes.len() - self.read_index) + } +} + +pub struct MutStreamAdapter<'a> { + bytes: &'a mut [u8], + read_index: usize, + write_index: usize, +} + +pub trait BufferWriter { + fn write_bytes_safe(&mut self, bytes: &[u8]); + fn write_u8_safe(&mut self, value: u8); + fn write_u32_be_safe(&mut self, value: u32); + fn write_u64_be_safe(&mut self, value: u64); + fn write_u64_ne_safe(&mut self, value: u64); +} + +impl<'a> MutStreamAdapter<'a> { + pub fn new(bytes: &'a mut [u8]) -> Self { + Self { + bytes, + read_index: 0, + write_index: 0, + } + } + + pub fn bytes_written(&self) -> usize { + self.write_index + } + + pub fn written(&self) -> &[u8] { + &self.bytes[..self.write_index] + } +} + +impl<'a> BufferWriter for MutStreamAdapter<'a> { + fn write_bytes_safe(&mut self, bytes: &[u8]) { + if self.write_index + bytes.len() > self.bytes.len() { + panic!("buffer full"); + } + self.bytes[self.write_index..self.write_index + bytes.len()].copy_from_slice(bytes); + self.write_index += bytes.len(); + } + + fn write_u8_safe(&mut self, value: u8) { + if self.write_index >= self.bytes.len() { + panic!("buffer full"); + } + self.bytes[self.write_index] = value; + self.write_index += 1; + } + + fn write_u32_be_safe(&mut self, value: u32) { + self.write_bytes_safe(&value.to_be_bytes()) + } + + fn write_u64_be_safe(&mut self, value: u64) { + self.write_bytes_safe(&value.to_be_bytes()) + } + + fn write_u64_ne_safe(&mut self, value: u64) { + self.write_bytes_safe(&value.to_ne_bytes()) + } +} + +impl<'a> Stream for MutStreamAdapter<'a> { + fn write_u8(&mut self, value: u8) -> anyhow::Result<()> { + if self.write_index >= self.bytes.len() { + bail!("buffer full"); + } + self.bytes[self.write_index] = value; + self.write_index += 1; + Ok(()) + } + + fn write_bytes(&mut self, bytes: &[u8]) -> anyhow::Result<()> { + if self.write_index + bytes.len() > self.bytes.len() { + bail!("buffer full"); + } + self.bytes[self.write_index..self.write_index + bytes.len()].copy_from_slice(bytes); + self.write_index += bytes.len(); + Ok(()) + } + + fn read_u8(&mut self) -> anyhow::Result { + if self.read_index >= self.bytes.len() { + bail!("no more bytes to read") + } + + let result = self.bytes[self.read_index]; + self.read_index += 1; + Ok(result) + } + + fn read_bytes(&mut self, buffer: &mut [u8], len: usize) -> anyhow::Result<()> { + if self.read_index + len > self.bytes.len() { + bail!("not enough bytes to read") + } + + buffer.copy_from_slice(&self.bytes[self.read_index..self.read_index + len]); + self.read_index += len; + Ok(()) + } + + fn in_avail(&mut self) -> anyhow::Result { + Ok(self.bytes.len() - self.read_index) + } +} + +pub struct BufferReader<'a> { + bytes: &'a [u8], + read_index: usize, +} + +impl<'a> BufferReader<'a> { + pub fn new(bytes: &'a [u8]) -> Self { + Self { + bytes, + read_index: 0, + } + } + + pub fn remaining(&self) -> &[u8] { + &self.bytes[self.read_index..] + } +} + +impl<'a> Stream for BufferReader<'a> { + fn write_u8(&mut self, _value: u8) -> anyhow::Result<()> { + bail!("not supported"); + } + + fn write_bytes(&mut self, _bytes: &[u8]) -> anyhow::Result<()> { + bail!("not supported"); + } + + fn read_u8(&mut self) -> anyhow::Result { + if self.read_index >= self.bytes.len() { + bail!("no more bytes to read") + } + + let result = self.bytes[self.read_index]; + self.read_index += 1; + Ok(result) + } + + fn read_bytes(&mut self, buffer: &mut [u8], len: usize) -> anyhow::Result<()> { + if self.read_index + len > self.bytes.len() { + bail!("not enough bytes to read") + } + + buffer.copy_from_slice(&self.bytes[self.read_index..self.read_index + len]); + self.read_index += len; + Ok(()) + } + + fn in_avail(&mut self) -> anyhow::Result { + Ok(self.bytes.len() - self.read_index) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use anyhow::Result; + + #[test] + fn test_stream() -> Result<()> { + let mut stream = MemoryStream::new(); + stream.write_bytes(&[1, 2, 3])?; + assert_eq!(stream.bytes_written(), 3); + + let mut read_buffer = [0u8; 3]; + stream.read_bytes(&mut read_buffer, 3)?; + assert_eq!([1, 2, 3], read_buffer); + + assert!(stream.read_bytes(&mut read_buffer, 1).is_err()); + Ok(()) + } +} diff --git a/rust/ffi/Cargo.toml b/rust/ffi/Cargo.toml new file mode 100644 index 0000000000..ec6f00b41c --- /dev/null +++ b/rust/ffi/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "rsnano_ffi" +version = "0.1.0" +edition = "2021" +build = "build.rs" + +[lib] +crate-type = ["staticlib", "lib"] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rsnano_core = { path = "../core" } + +[build-dependencies] +cbindgen = "0.24.3" diff --git a/rust/ffi/build.rs b/rust/ffi/build.rs new file mode 100644 index 0000000000..913552d7d1 --- /dev/null +++ b/rust/ffi/build.rs @@ -0,0 +1,21 @@ +use cbindgen::{Config, Language, SortKey}; +use std::path::Path; + +fn main() { + let crate_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + let output_file = Path::new("../../nano/lib/rsnano.hpp"); + + let config = Config { + language: Language::Cxx, + include_guard: Some("rs_nano_bindings_hpp".to_string()), + namespace: Some("rsnano".to_string()), + sort_by: SortKey::Name, + ..Default::default() + }; + + cbindgen::generate_with_config(crate_dir, config) + .unwrap() + .write_to_file(output_file); + + println!("cargo:rerun-if-changed=src"); +} diff --git a/rust/ffi/src/core/mod.rs b/rust/ffi/src/core/mod.rs new file mode 100644 index 0000000000..7fd96df05b --- /dev/null +++ b/rust/ffi/src/core/mod.rs @@ -0,0 +1,40 @@ +use rsnano_core::{sign_message, validate_message, PublicKey, RawKey, Signature}; +use std::{ffi::CStr, net::Ipv6Addr, os::raw::c_char, slice}; + +#[no_mangle] +pub unsafe extern "C" fn rsn_sign_message( + priv_key: *const u8, + pub_key: *const u8, + message: *const u8, + len: usize, + signature: *mut u8, +) -> i32 { + let private_key = RawKey::from_ptr(priv_key); + let data = if message.is_null() { + &[] + } else { + std::slice::from_raw_parts(message, len) + }; + let sig = sign_message(&private_key, data); + let signature = slice::from_raw_parts_mut(signature, 64); + signature.copy_from_slice(sig.as_bytes()); + 0 +} + +#[no_mangle] +pub unsafe extern "C" fn rsn_validate_message( + pub_key: &[u8; 32], + message: *const u8, + len: usize, + signature: &[u8; 64], +) -> bool { + let public_key = PublicKey::from_bytes(*pub_key); + let message = if message.is_null() { + &[] + } else { + std::slice::from_raw_parts(message, len) + }; + let signature = Signature::from_bytes(*signature); + validate_message(&public_key, message, &signature).is_err() +} + diff --git a/rust/ffi/src/lib.rs b/rust/ffi/src/lib.rs new file mode 100644 index 0000000000..4c427ef3b2 --- /dev/null +++ b/rust/ffi/src/lib.rs @@ -0,0 +1,3 @@ +#![allow(clippy::missing_safety_doc)] + +pub mod core; From 48c0ba4f55416463c050ff048be13fdef3b8906f Mon Sep 17 00:00:00 2001 From: Fiono Date: Wed, 9 Oct 2024 17:29:13 +0100 Subject: [PATCH 2/2] Sign and validate messages with rust ed25519-dalek --- .gitignore | 7 ++ CMakeLists.txt | 6 ++ nano/crypto_lib/CMakeLists.txt | 4 +- rust/nullables/random/Cargo.toml | 7 ++ rust/nullables/random/src/lib.rs | 166 +++++++++++++++++++++++++++++++ 5 files changed, 189 insertions(+), 1 deletion(-) create mode 100644 rust/nullables/random/Cargo.toml create mode 100644 rust/nullables/random/src/lib.rs diff --git a/.gitignore b/.gitignore index eef7f3c2df..fcd0e3a25e 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,10 @@ Testing # Autogenerated Build Prep artifacts util/build_prep/*/prep.sh + +cargo +cmake-build-debug/ +rust/target +nano/lib/rsnano.hpp +build/ +.cache/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 0614d03be0..77b2f09eeb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,6 +16,12 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET project(nano-node) +# Build the Rust part +# --------------------- +find_package(Corrosion REQUIRED) +corrosion_import_crate(MANIFEST_PATH rust/ffi/Cargo.toml) +# --------------------- + # Get the latest abbreviated commit hash of the working branch execute_process( COMMAND git log -1 --format=%h diff --git a/nano/crypto_lib/CMakeLists.txt b/nano/crypto_lib/CMakeLists.txt index 533412cdff..654ce0ee38 100644 --- a/nano/crypto_lib/CMakeLists.txt +++ b/nano/crypto_lib/CMakeLists.txt @@ -5,4 +5,6 @@ target_link_libraries(ed25519 nano_ed25519) add_library(crypto_lib random_pool.hpp random_pool.cpp random_pool_shuffle.hpp secure_memory.hpp secure_memory.cpp) -target_link_libraries(crypto_lib blake2 ed25519 ${CRYPTOPP_LIBRARY}) +target_link_libraries(crypto_lib blake2 ed25519 ${CRYPTOPP_LIBRARY} rsnano_ffi) + +add_dependencies(crypto_lib cargo-build_rsnano_ffi) diff --git a/rust/nullables/random/Cargo.toml b/rust/nullables/random/Cargo.toml new file mode 100644 index 0000000000..9a12147397 --- /dev/null +++ b/rust/nullables/random/Cargo.toml @@ -0,0 +1,7 @@ +[package] +name = "rsnano_nullable_random" +version = "0.1.0" +edition = "2021" + +[dependencies] +rand = { version = "0" } diff --git a/rust/nullables/random/src/lib.rs b/rust/nullables/random/src/lib.rs new file mode 100644 index 0000000000..4d3a07d903 --- /dev/null +++ b/rust/nullables/random/src/lib.rs @@ -0,0 +1,166 @@ +use rand::{rngs::ThreadRng, CryptoRng, RngCore}; + +pub struct NullableRng { + strategy: RngStrategy, +} + +impl NullableRng { + pub fn new_null() -> Self { + Self::new_null_u64(42) + } + + pub fn new_null_u64(val: u64) -> Self { + Self { + strategy: RngStrategy::Nulled(RngStub::new(val.to_be_bytes().to_vec())), + } + } + + pub fn new_null_bytes(bytes: &[u8]) -> Self { + Self { + strategy: RngStrategy::Nulled(RngStub::new(bytes.to_vec())), + } + } + + pub fn thread_rng() -> Self { + Self { + strategy: RngStrategy::Thread(rand::thread_rng()), + } + } + + fn as_rng_core(&mut self) -> &mut dyn RngCore { + match &mut self.strategy { + RngStrategy::Thread(i) => i, + RngStrategy::Nulled(i) => i, + } + } +} + +impl RngCore for NullableRng { + fn next_u32(&mut self) -> u32 { + self.as_rng_core().next_u32() + } + + fn next_u64(&mut self) -> u64 { + self.as_rng_core().next_u64() + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + self.as_rng_core().fill_bytes(dest) + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.as_rng_core().try_fill_bytes(dest) + } +} + +enum RngStrategy { + Thread(ThreadRng), + Nulled(RngStub), +} + +impl CryptoRng for NullableRng {} + +struct RngStub { + data: Vec, + index: usize, +} + +impl RngStub { + pub fn new(data: Vec) -> Self { + Self { data, index: 0 } + } +} + +impl RngCore for RngStub { + fn next_u32(&mut self) -> u32 { + let mut buf = [0u8; 4]; + self.fill_bytes(&mut buf); + u32::from_be_bytes(buf) + } + + fn next_u64(&mut self) -> u64 { + let mut buf = [0u8; 8]; + self.fill_bytes(&mut buf); + u64::from_be_bytes(buf) + } + + fn fill_bytes(&mut self, dest: &mut [u8]) { + for i in 0..dest.len() { + dest[i] = self.data[self.index]; + self.index += 1; + if self.index >= self.data.len() { + self.index = 0; + } + } + } + + fn try_fill_bytes(&mut self, dest: &mut [u8]) -> Result<(), rand::Error> { + self.fill_bytes(dest); + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn real_rng() { + let mut rng = NullableRng::thread_rng(); + let a1 = rng.next_u64(); + let a2 = rng.next_u64(); + let a3 = rng.next_u64(); + assert!(a1 > 0 || a2 > 0 || a3 > 0); + + let b1 = rng.next_u32(); + let b2 = rng.next_u32(); + let b3 = rng.next_u32(); + assert!(b1 > 0 || b2 > 0 || b3 > 0); + + let mut buffer = [0; 32]; + + rng.fill_bytes(&mut buffer); + assert_eq!(buffer.iter().all(|&b| b == 0), false); + + buffer = [0; 32]; + rng.try_fill_bytes(&mut buffer).unwrap(); + assert_eq!(buffer.iter().all(|&b| b == 0), false); + } + + #[test] + fn nullable_with_u64() { + let mut rng = NullableRng::new_null(); + assert_eq!(rng.next_u64(), 42); + + assert_eq!(rng.next_u32(), 0); + assert_eq!(rng.next_u32(), 42); + + let mut buffer = [0; 32]; + rng.fill_bytes(&mut buffer); + assert_eq!( + buffer, + [ + 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, + 0, 0, 0, 0, 42, + ] + ); + + buffer = [0; 32]; + rng.try_fill_bytes(&mut buffer).unwrap(); + assert_eq!( + buffer, + [ + 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, 0, 0, 0, 0, 42, 0, 0, 0, + 0, 0, 0, 0, 42, + ] + ); + } + + #[test] + fn nullable_with_byte_slice() { + let mut rng = NullableRng::new_null_bytes(&[1, 2, 3, 4, 5, 6]); + let mut buffer = [0; 10]; + rng.fill_bytes(&mut buffer); + assert_eq!(buffer, [1, 2, 3, 4, 5, 6, 1, 2, 3, 4]); + } +}