From 300121aad0be99914a3e70e2111484dd0f1ab767 Mon Sep 17 00:00:00 2001 From: satoshiotomakan <127754187+satoshiotomakan@users.noreply.github.com> Date: Mon, 17 Apr 2023 10:49:20 +0200 Subject: [PATCH] [Protobuf]: Support Protobuf from Rust FFI (#3069) --- rust/Cargo.lock | 225 +++++++++++++++++- rust/Cargo.toml | 1 + rust/coverage.stats | 2 +- rust/tw_memory/src/ffi/c_byte_array.rs | 12 +- rust/tw_memory/src/ffi/c_byte_array_ref.rs | 43 ++++ rust/tw_memory/src/ffi/mod.rs | 1 + rust/tw_proto/Cargo.toml | 12 + rust/tw_proto/build.rs | 53 +++++ rust/tw_proto/src/ffi.rs | 100 ++++++++ rust/tw_proto/src/lib.rs | 39 +++ rust/tw_proto/tests/proto_ffi_tests.rs | 26 ++ rust/tw_proto/tests/proto_tests.rs | 91 +++++++ rust/wallet_core_rs/Cargo.toml | 1 + rust/wallet_core_rs/cbindgen.toml | 4 +- rust/wallet_core_rs/src/lib.rs | 1 + .../common/rust/bindgen/WalletCoreRsTests.cpp | 70 ++++++ 16 files changed, 674 insertions(+), 7 deletions(-) create mode 100644 rust/tw_memory/src/ffi/c_byte_array_ref.rs create mode 100644 rust/tw_proto/Cargo.toml create mode 100644 rust/tw_proto/build.rs create mode 100644 rust/tw_proto/src/ffi.rs create mode 100644 rust/tw_proto/src/lib.rs create mode 100644 rust/tw_proto/tests/proto_ffi_tests.rs create mode 100644 rust/tw_proto/tests/proto_tests.rs diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 6fa2b705a7b..8b3906bebf1 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -2,6 +2,24 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + [[package]] name = "anyhow" version = "1.0.69" @@ -85,6 +103,17 @@ dependencies = [ "syn", ] +[[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.1.0" @@ -107,6 +136,12 @@ dependencies = [ "thiserror", ] +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bitvec" version = "0.20.4" @@ -199,6 +234,21 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap", + "unicode-width", + "vec_map", +] + [[package]] name = "cpufeatures" version = "0.2.5" @@ -266,7 +316,7 @@ dependencies = [ "ident_case", "proc-macro2", "quote", - "strsim", + "strsim 0.10.0", "syn", ] @@ -318,6 +368,19 @@ dependencies = [ "subtle", ] +[[package]] +name = "env_logger" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + [[package]] name = "ethbloom" version = "0.11.1" @@ -407,6 +470,15 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[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" @@ -432,6 +504,15 @@ dependencies = [ "digest 0.10.6", ] +[[package]] +name = "humantime" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f" +dependencies = [ + "quick-error", +] + [[package]] name = "ident_case" version = "1.0.1" @@ -512,9 +593,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.139" +version = "0.2.141" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79" +checksum = "3304a64d199bb964be99741b7a14d26972741915b3649639149b2479bb46f4b5" [[package]] name = "log" @@ -531,6 +612,12 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "move-core-types" version = "0.0.4" @@ -546,6 +633,16 @@ dependencies = [ "serde_bytes", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "nom8" version = "0.2.0" @@ -629,6 +726,18 @@ version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" +[[package]] +name = "pb-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "354a34df9c65b596152598001c0fe3393379ec2db03ae30b9985659422e2607e" +dependencies = [ + "clap", + "env_logger", + "log", + "nom", +] + [[package]] name = "pest" version = "2.5.5" @@ -677,6 +786,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] +name = "quick-protobuf" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d6da84cc204722a989e01ba2f6e1e276e190f22263d0cb6ce8526fcdb0d2e1f" +dependencies = [ + "byteorder", +] + [[package]] name = "quote" version = "1.0.23" @@ -742,6 +866,23 @@ dependencies = [ "syn", ] +[[package]] +name = "regex" +version = "1.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b1f693b24f6ac912f4893ef08244d70b6067480d2f1a46e950c9691e6749d1d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "rfc6979" version = "0.1.0" @@ -987,6 +1128,12 @@ 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.10.0" @@ -1028,6 +1175,24 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" +[[package]] +name = "termcolor" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + [[package]] name = "thiserror" version = "1.0.38" @@ -1116,6 +1281,16 @@ dependencies = [ "tw_memory", ] +[[package]] +name = "tw_proto" +version = "0.1.0" +dependencies = [ + "pb-rs", + "quick-protobuf", + "tw_encoding", + "tw_memory", +] + [[package]] name = "tw_starknet" version = "0.1.0" @@ -1158,12 +1333,24 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc" +[[package]] +name = "unicode-width" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0edd1e5b14653f783770bce4a4dabb4a5108a5370a5f5d8cfe8710c361f6c8b" + [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.4" @@ -1178,6 +1365,7 @@ dependencies = [ "tw_hash", "tw_memory", "tw_move_parser", + "tw_proto", "tw_starknet", ] @@ -1241,6 +1429,37 @@ version = "0.2.84" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0046fef7e28c3804e5e38bfa31ea2a0f73905319b677e57ebe37e49358989b5d" +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "wyz" version = "0.2.0" diff --git a/rust/Cargo.toml b/rust/Cargo.toml index fb05a3ef48e..e767d54ae85 100644 --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -4,6 +4,7 @@ members = [ "tw_hash", "tw_memory", "tw_move_parser", + "tw_proto", "tw_starknet", "wallet_core_rs", ] diff --git a/rust/coverage.stats b/rust/coverage.stats index 6b5505813f6..929c02d867b 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -93.3 \ No newline at end of file +93.7 \ No newline at end of file diff --git a/rust/tw_memory/src/ffi/c_byte_array.rs b/rust/tw_memory/src/ffi/c_byte_array.rs index 6ddd47ac6cf..65f04e2f13d 100644 --- a/rust/tw_memory/src/ffi/c_byte_array.rs +++ b/rust/tw_memory/src/ffi/c_byte_array.rs @@ -12,7 +12,7 @@ pub struct CByteArrayResult { crate::impl_c_result!(CByteArrayResult, CByteArray, CByteArray::null()); -/// C-compatible byte array. +/// A C-compatible wrapper over a byte array allocated in Rust. #[repr(C)] #[derive(Debug)] pub struct CByteArray { @@ -85,6 +85,16 @@ impl CByteArray { self.size = 0; self.capacity = 0; } + + /// Returns the const pointer to the data. + pub fn data(&self) -> *const u8 { + self.data.cast_const() + } + + /// Returns the data length. + pub fn size(&self) -> usize { + self.size + } } /// Releases the memory previously allocated for the pointer to `CByteArray`. diff --git a/rust/tw_memory/src/ffi/c_byte_array_ref.rs b/rust/tw_memory/src/ffi/c_byte_array_ref.rs new file mode 100644 index 00000000000..160b14d3f0d --- /dev/null +++ b/rust/tw_memory/src/ffi/c_byte_array_ref.rs @@ -0,0 +1,43 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +/// A C-compatible wrapper over a byte array given as the FFI argument. +#[repr(C)] +#[derive(Debug)] +pub struct CByteArrayRef { + data: *const u8, + size: usize, +} + +impl CByteArrayRef { + /// Creates a new `CByteArrayRef` from the allocated byte array. + pub fn new(data: *const u8, size: usize) -> CByteArrayRef { + CByteArrayRef { data, size } + } + + /// Clones the byte array as a `Vec`. + /// Returns `None` if `CByteArrayRef::data` is null. + /// + /// # Safety + /// + /// The inner data must be valid. + pub unsafe fn to_vec(&self) -> Option> { + self.as_slice().map(|data| data.to_vec()) + } + + /// Returns a slice. + /// Returns `None` if `CByteArrayRef::data` is null. + /// + /// # Safety + /// + /// The inner data must be valid. + pub unsafe fn as_slice(&self) -> Option<&'static [u8]> { + if self.data.is_null() { + return None; + } + Some(std::slice::from_raw_parts(self.data, self.size)) + } +} diff --git a/rust/tw_memory/src/ffi/mod.rs b/rust/tw_memory/src/ffi/mod.rs index c475657637d..1193bfece9c 100644 --- a/rust/tw_memory/src/ffi/mod.rs +++ b/rust/tw_memory/src/ffi/mod.rs @@ -9,6 +9,7 @@ use std::ffi::{c_char, CString}; pub mod c_byte_array; +pub mod c_byte_array_ref; pub mod c_result; /// Releases the memory previously allocated for the `ptr` string. diff --git a/rust/tw_proto/Cargo.toml b/rust/tw_proto/Cargo.toml new file mode 100644 index 00000000000..22179b761bc --- /dev/null +++ b/rust/tw_proto/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "tw_proto" +version = "0.1.0" +edition = "2021" + +[dependencies] +quick-protobuf = "0.8.1" +tw_encoding = { path = "../tw_encoding" } +tw_memory = { path = "../tw_memory" } + +[build-dependencies] +pb-rs = "0.10.0" diff --git a/rust/tw_proto/build.rs b/rust/tw_proto/build.rs new file mode 100644 index 00000000000..1ba71bd5a11 --- /dev/null +++ b/rust/tw_proto/build.rs @@ -0,0 +1,53 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use pb_rs::types::FileDescriptor; +use pb_rs::ConfigBuilder; +use std::path::{Path, PathBuf}; +use std::{env, fs}; + +fn main() { + let proto_ext = Some(Path::new("proto").as_os_str()); + + let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()).join("proto"); + + let proto_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap()) + .join("..") + .join("..") + .join("src") + .join("proto"); + let proto_dir_str = proto_dir.to_str().expect("Invalid proto directory path"); + // Re-run this build.rs if the `proto` directory has been changed (i.e. a new file is added). + println!("cargo:rerun-if-changed={}", proto_dir_str); + + let protos: Vec<_> = fs::read_dir(&proto_dir) + .expect("Expected a valid directory with proto files") + .filter_map(|file| { + let file = file.ok()?; + if file.path().extension() != proto_ext { + return None; + } + + let path = file.path(); + let path_str = path.to_str().expect("Invalid Proto file name"); + println!("cargo:rerun-if-changed={}", path_str); + Some(path) + }) + .collect(); + + // Delete all old generated files before re-generating new ones + if out_dir.exists() { + fs::remove_dir_all(&out_dir).expect("Error removing out directory"); + } + fs::DirBuilder::new() + .create(&out_dir) + .expect("Error creating out directory"); + + let out_protos = ConfigBuilder::new(&protos, None, Some(&out_dir), &[proto_dir]) + .expect("Error configuring pb-rs builder") + .build(); + FileDescriptor::run(&out_protos).expect("Error generating proto files"); +} diff --git a/rust/tw_proto/src/ffi.rs b/rust/tw_proto/src/ffi.rs new file mode 100644 index 00000000000..227925729ba --- /dev/null +++ b/rust/tw_proto/src/ffi.rs @@ -0,0 +1,100 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +#![allow(clippy::missing_safety_doc)] + +use std::borrow::Cow; +use tw_memory::ffi::c_byte_array::{CByteArray, CByteArrayResult}; +use tw_memory::ffi::c_byte_array_ref::CByteArrayRef; +use tw_memory::ffi::c_result::ErrorCode; + +#[repr(C)] +pub enum CProtoError { + Ok = 0, + Internal = 1, + ErrorDeserializingMsg = 2, + ErrorSerializingMsg = 3, +} + +impl From for ErrorCode { + fn from(e: CProtoError) -> Self { + e as ErrorCode + } +} + +/// Takes a serialized `Ethereum::Proto::SigningInput` message, deserializes it and returns a serialized message back. +/// This FFI is used within integration tests. +/// \param input *non-null* byte array. +/// \param input_len length of the input byte array. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn pass_eth_signing_msg_through( + input: *const u8, + input_len: usize, +) -> CByteArrayResult { + let data = CByteArrayRef::new(input, input_len) + .to_vec() + .unwrap_or_default(); + + let msg: crate::Ethereum::Proto::SigningInput = match crate::deserialize(&data) { + Ok(msg) => msg, + Err(_) => return CByteArrayResult::error(CProtoError::ErrorDeserializingMsg), + }; + crate::serialize(&msg) + .map(CByteArray::from) + .map_err(|_| CProtoError::ErrorSerializingMsg) + .into() +} + +/// Returns a serialized `Polkadot::Proto::SigningInput` message. +/// This FFI is used within integration tests. +/// \return C-compatible result with a C-compatible byte array. +#[no_mangle] +pub unsafe extern "C" fn polkadot_test_signing_input() -> CByteArrayResult { + use crate::Polkadot::Proto::{ + self as proto, mod_Balance::OneOfmessage_oneof as BalanceEnum, + mod_SigningInput::OneOfmessage_oneof as SigningMsgEnum, + }; + + let block_hash = "0x343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"; + let genesis_hash = "91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"; + let to_address = "14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3"; + let privkey = [ + 171, 248, 229, 189, 190, 48, 198, 86, 86, 192, 163, 203, 209, 129, 255, 138, 86, 41, 74, + 105, 223, 237, 210, 121, 130, 170, 206, 74, 118, 144, 145, 21, + ]; + let value = [48, 57]; // 12345 + + let block_hash = tw_encoding::hex::decode(block_hash).expect("Expect valid hash"); + let genesis_hash = tw_encoding::hex::decode(genesis_hash).expect("Expect valid hash"); + + let balance = BalanceEnum::transfer(proto::mod_Balance::Transfer { + to_address: Cow::from(to_address), + value: Cow::Borrowed(&value), + }); + let signing_input = proto::SigningInput { + block_hash: Cow::Owned(block_hash), + genesis_hash: Cow::Owned(genesis_hash), + nonce: 0, + spec_version: 17, + transaction_version: 3, + era: Some(proto::Era { + block_number: 927699, + period: 8, + }), + private_key: Cow::Borrowed(&privkey), + network: 0, + message_oneof: SigningMsgEnum::balance_call(proto::Balance { + message_oneof: balance, + }), + ..proto::SigningInput::default() + }; + + crate::serialize(&signing_input) + .map(CByteArray::from) + .map_err(|_| CProtoError::ErrorSerializingMsg) + .into() +} diff --git a/rust/tw_proto/src/lib.rs b/rust/tw_proto/src/lib.rs new file mode 100644 index 00000000000..1b52d670b61 --- /dev/null +++ b/rust/tw_proto/src/lib.rs @@ -0,0 +1,39 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use quick_protobuf::{BytesReader, MessageRead, MessageWrite, Writer}; + +#[allow(non_snake_case)] +#[rustfmt::skip] +mod generated { + include!(concat!(env!("OUT_DIR"), "/proto/mod.rs")); +} + +pub use generated::TW::*; +pub use quick_protobuf::{ + deserialize_from_slice as deserialize_prefixed, serialize_into_vec as serialize_prefixed, + Error as ProtoError, Result as ProtoResult, +}; + +pub mod ffi; + +/// Serializes a Protobuf message without the length prefix. +/// Please note that [`quick_protobuf::serialize_into_vec`] appends a `varint32` length prefix. +pub fn serialize(message: &T) -> ProtoResult> { + let len = message.get_size(); + let mut v = Vec::with_capacity(quick_protobuf::sizeofs::sizeof_len(len)); + let mut writer = Writer::new(&mut v); + message.write_message(&mut writer)?; + Ok(v) +} + +/// Deserializes a Protobuf message from the given `data` bytes without the length prefix. +/// Please note that [`quick_protobuf::deserialize_from_slice`] requires the data +/// starts from a `varint32` length prefix. +pub fn deserialize<'a, T: MessageRead<'a>>(data: &'a [u8]) -> ProtoResult { + let mut reader = BytesReader::from_bytes(data); + T::from_reader(&mut reader, data) +} diff --git a/rust/tw_proto/tests/proto_ffi_tests.rs b/rust/tw_proto/tests/proto_ffi_tests.rs new file mode 100644 index 00000000000..654141268da --- /dev/null +++ b/rust/tw_proto/tests/proto_ffi_tests.rs @@ -0,0 +1,26 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use tw_memory::ffi::c_byte_array::CByteArray; + +/// Test `pass_eth_signing_msg_through` to avoid dropping code coverage. +#[test] +fn test_pass_eth_signing_msg_through() { + let serialized = tw_encoding::hex::decode("0a0101120100220509c76524002a030130b9422a3078366231373534373465383930393463343464613938623935346565646561633439353237316430664a20608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151523812360a2a30783533323262333463383865643036393139373162663532613730343734343866306634656663383412081bc16d674ec80000").unwrap(); + let actual = unsafe { + let array = CByteArray::new(serialized.clone()); + tw_proto::ffi::pass_eth_signing_msg_through(array.data(), array.size()) + .unwrap() + .into_vec() + }; + assert_eq!(actual, serialized); +} + +/// Test `polkadot_test_signing_input` to avoid dropping code coverage. +#[test] +fn test_polkadot_test_signing_input() { + unsafe { tw_proto::ffi::polkadot_test_signing_input().unwrap() }; +} diff --git a/rust/tw_proto/tests/proto_tests.rs b/rust/tw_proto/tests/proto_tests.rs new file mode 100644 index 00000000000..1bc62808559 --- /dev/null +++ b/rust/tw_proto/tests/proto_tests.rs @@ -0,0 +1,91 @@ +// Copyright © 2017-2023 Trust Wallet. +// +// This file is part of Trust. The full Trust copyright notice, including +// terms governing use, modification, and redistribution, is contained in the +// file LICENSE at the root of the source code distribution tree. + +use std::borrow::Cow; +use tw_proto::{deserialize, deserialize_prefixed, serialize, serialize_prefixed, Ethereum}; + +const SERIALIZED: [u8; 6] = [10, 1, 1, 18, 1, 11]; +const SERIALIZED_PREFIXED: [u8; 7] = [6, 10, 1, 1, 18, 1, 11]; + +fn test_msg() -> Ethereum::Proto::SigningInput<'static> { + Ethereum::Proto::SigningInput { + chain_id: Cow::Owned(vec![1]), + nonce: Cow::Owned(vec![11]), + ..Ethereum::Proto::SigningInput::default() + } +} + +#[test] +fn test_serialize() { + let msg = test_msg(); + let serialized = serialize(&msg).unwrap(); + // Must not contain a prefix. + assert_eq!(serialized, SERIALIZED); +} + +#[test] +fn test_deserialize() { + let actual: Ethereum::Proto::SigningInput = deserialize(&SERIALIZED).unwrap(); + assert_eq!(actual, test_msg()); +} + +#[test] +fn test_serialize_prefixed() { + let msg = test_msg(); + let serialized = serialize_prefixed(&msg).unwrap(); + // Must not contain a prefix. + assert_eq!(serialized, SERIALIZED_PREFIXED); +} + +#[test] +fn test_deserialize_prefixed() { + let actual: Ethereum::Proto::SigningInput = deserialize_prefixed(&SERIALIZED_PREFIXED).unwrap(); + assert_eq!(actual, test_msg()); +} + +#[test] +fn test_serialize_deserialize() { + let serialized = [ + 10, 1, 1, 18, 1, 0, 34, 5, 9, 199, 101, 36, 0, 42, 3, 1, 48, 185, 66, 42, 48, 120, 54, 98, + 49, 55, 53, 52, 55, 52, 101, 56, 57, 48, 57, 52, 99, 52, 52, 100, 97, 57, 56, 98, 57, 53, + 52, 101, 101, 100, 101, 97, 99, 52, 57, 53, 50, 55, 49, 100, 48, 102, 74, 32, 96, 141, 203, + 23, 66, 187, 63, 183, 174, 192, 2, 7, 78, 52, 32, 228, 250, 183, 208, 12, 206, 215, 156, + 205, 172, 83, 237, 91, 39, 19, 129, 81, 82, 56, 18, 54, 10, 42, 48, 120, 53, 51, 50, 50, + 98, 51, 52, 99, 56, 56, 101, 100, 48, 54, 57, 49, 57, 55, 49, 98, 102, 53, 50, 97, 55, 48, + 52, 55, 52, 52, 56, 102, 48, 102, 52, 101, 102, 99, 56, 52, 18, 8, 27, 193, 109, 103, 78, + 200, 0, 0, + ]; + let privkey = [ + 96u8, 141, 203, 23, 66, 187, 63, 183, 174, 192, 2, 7, 78, 52, 32, 228, 250, 183, 208, 12, + 206, 215, 156, 205, 172, 83, 237, 91, 39, 19, 129, 81, + ]; + + let expected_erc20 = Ethereum::Proto::mod_Transaction::ERC20Transfer { + to: Cow::from("0x5322b34c88ed0691971bf52a7047448f0f4efc84"), + amount: Cow::Borrowed(&[27u8, 193, 109, 103, 78, 200, 0, 0]), + }; + let expected_tx = Ethereum::Proto::Transaction { + transaction_oneof: Ethereum::Proto::mod_Transaction::OneOftransaction_oneof::erc20_transfer( + expected_erc20, + ), + }; + let expected = Ethereum::Proto::SigningInput { + chain_id: Cow::Borrowed(&[1]), + nonce: Cow::Borrowed(&[0]), + gas_price: Cow::Borrowed(&[9, 199, 101, 36, 0]), + gas_limit: Cow::Borrowed(&[1, 48, 185]), + to_address: Cow::from("0x6b175474e89094c44da98b954eedeac495271d0f"), + private_key: Cow::Borrowed(&privkey), + transaction: Some(expected_tx), + ..Ethereum::Proto::SigningInput::default() + }; + + let actual: Ethereum::Proto::SigningInput = deserialize(&serialized).unwrap(); + assert_eq!(actual, expected); + + let actual_serialized = serialize(&actual).unwrap(); + assert_eq!(actual_serialized, serialized); +} diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index aad7e2f9829..242fa59c6bd 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -12,4 +12,5 @@ tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_memory = { path = "../tw_memory" } tw_move_parser = { path = "../tw_move_parser" } +tw_proto = { path = "../tw_proto" } tw_starknet = { path = "../tw_starknet" } diff --git a/rust/wallet_core_rs/cbindgen.toml b/rust/wallet_core_rs/cbindgen.toml index 935d1283e86..ddd788b7744 100644 --- a/rust/wallet_core_rs/cbindgen.toml +++ b/rust/wallet_core_rs/cbindgen.toml @@ -6,5 +6,5 @@ namespaces = ["TW", "Rust"] [parse] parse_deps = true -extra_bindings = ["tw_memory", "tw_encoding", "tw_hash", "tw_move_parser", "tw_starknet"] -include = ["tw_memory", "tw_encoding", "tw_hash", "tw_move_parser", "tw_starknet"] +extra_bindings = ["tw_memory", "tw_encoding", "tw_hash", "tw_move_parser", "tw_proto", "tw_starknet"] +include = ["tw_memory", "tw_encoding", "tw_hash", "tw_move_parser", "tw_proto", "tw_starknet"] diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index 685513b111f..49365f627a4 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -8,4 +8,5 @@ pub extern crate tw_encoding; pub extern crate tw_hash; pub extern crate tw_memory; pub extern crate tw_move_parser; +pub extern crate tw_proto; pub extern crate tw_starknet; diff --git a/tests/common/rust/bindgen/WalletCoreRsTests.cpp b/tests/common/rust/bindgen/WalletCoreRsTests.cpp index ea34d818935..34941ca98eb 100644 --- a/tests/common/rust/bindgen/WalletCoreRsTests.cpp +++ b/tests/common/rust/bindgen/WalletCoreRsTests.cpp @@ -4,8 +4,14 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +#include "HexCoding.h" +#include "Polkadot/Signer.h" +#include "TestUtilities.h" #include "rust/bindgen/WalletCoreRSBindgen.h" #include "gtest/gtest.h" +#include "proto/Ethereum.pb.h" +#include "proto/Polkadot.pb.h" +#include "uint256.h" TEST(RustBindgen, MoveParseFunctionArgument) { using namespace TW; @@ -15,3 +21,67 @@ TEST(RustBindgen, MoveParseFunctionArgument) { ASSERT_EQ(std::string(str_result.result), "8096980000000000"); Rust::free_string(str_result.result); } + +TEST(RustBindgen, EthSigningMessageProto) { + using namespace TW; + + auto chainId = store(uint256_t(1)); + auto nonce = store(uint256_t(0)); + auto gasPrice = store(uint256_t(42000000000)); // 0x09c7652400 + auto gasLimit = store(uint256_t(78009)); // 130B9 + auto toAddress = "0x5322b34c88ed0691971bf52a7047448f0f4efc84"; + auto token = "0x6b175474e89094c44da98b954eedeac495271d0f"; // DAI + auto amount = uint256_t(2000000000000000000); + auto amountData = store(amount); + auto key = parse_hex("0x608dcb1742bb3fb7aec002074e3420e4fab7d00cced79ccdac53ed5b27138151"); + + Ethereum::Proto::SigningInput input; + input.set_chain_id(chainId.data(), chainId.size()); + input.set_nonce(nonce.data(), nonce.size()); + // tx_mode not set, Legacy is the default + input.set_gas_price(gasPrice.data(), gasPrice.size()); + input.set_gas_limit(gasLimit.data(), gasLimit.size()); + input.set_to_address(token); + input.set_private_key(key.data(), key.size()); + auto& erc20 = *input.mutable_transaction()->mutable_erc20_transfer(); + erc20.set_to(toAddress); + erc20.set_amount(amountData.data(), amountData.size()); + + Data serialized = data(input.SerializeAsString()); + Rust::CByteArrayResultWrapper res = Rust::pass_eth_signing_msg_through(serialized.data(), serialized.size()); + ASSERT_TRUE(res.isOk()); + ASSERT_EQ(res.unwrap().data, serialized); +} + +TEST(RustBindgen, PolkadotSignTxProto) { + using namespace TW; + + auto blockHash = parse_hex("0x343a3f4258fd92f5ca6ca5abdf473d86a78b0bcd0dc09c568ca594245cc8c642"); + auto genesisHash = parse_hex("91b171bb158e2d3848fa23a9f1c25182fb8e20313b2c1eb49219da7a70ce90c3"); + auto toAddress = "14E5nqKAp3oAJcmzgZhUD2RcptBeUBScxKHgJKU4HPNcKVf3"; + auto privateKey = parse_hex("0xabf8e5bdbe30c65656c0a3cbd181ff8a56294a69dfedd27982aace4a76909115"); + auto expectedEncoded = "29028488dc3417d5058ec4b4503e0c12ea1a0a89be200fe98922423d4334014fa6b0ee003d91a06263956d8ce3ce5c55455baefff299d9cb2bb3f76866b6828ee4083770b6c03b05d7b6eb510ac78d047002c1fe5c6ee4b37c9c5a8b09ea07677f12e50d3200000005008eaf04151687736326c9fea17e25fc5287613693c912909cb226aa4794f26a48e5c0"; + + Rust::CByteArrayResultWrapper res = Rust::polkadot_test_signing_input(); + ASSERT_TRUE(res.isOk()); + auto serialized = std::move(res.unwrap().data); + + Polkadot::Proto::SigningInput input; + input.ParseFromArray(serialized.data(), static_cast(serialized.size())); + + ASSERT_EQ(input.nonce(), 0); + ASSERT_EQ(input.spec_version(), 17); + ASSERT_EQ(data(input.private_key()), privateKey); + ASSERT_EQ(input.network(), 0); + ASSERT_EQ(input.transaction_version(), 3); + + ASSERT_EQ(input.era().block_number(), 927699); + ASSERT_EQ(input.era().period(), 8); + + auto transfer = input.balance_call().transfer(); + ASSERT_EQ(data(transfer.value()), store(uint256_t(12345))); + ASSERT_EQ(transfer.to_address(), toAddress); + + auto output = Polkadot::Signer::sign(input); + ASSERT_EQ(hex(output.encoded()), expectedEncoded); +}