diff --git a/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt new file mode 100644 index 00000000000..80abb39ff7f --- /dev/null +++ b/android/app/src/androidTest/java/com/trustwallet/core/app/blockchains/ethereum/TestEthereumRlp.kt @@ -0,0 +1,57 @@ +package com.trustwallet.core.app.blockchains.ethereum + +import com.google.protobuf.ByteString +import com.trustwallet.core.app.utils.toHexByteArray +import org.junit.Assert.assertEquals +import org.junit.Test +import com.trustwallet.core.app.utils.Numeric +import wallet.core.jni.CoinType +import wallet.core.jni.proto.Common +import wallet.core.jni.proto.EthereumRlp + +class TestEthereumRlp { + + init { + System.loadLibrary("TrustWalletCore") + } + + @Test + fun testEthereumRlpEncodeEip1559() { + val chainId = ByteString.copyFrom("0x0a".toHexByteArray()) + val nonce = ByteString.copyFrom("0x06".toHexByteArray()) + val maxInclusionFeePerGas = ByteString.copyFrom("0x77359400".toHexByteArray()) // 2000000000 + val maxFeePerGas = ByteString.copyFrom("0xb2d05e00".toHexByteArray()) // 3000000000 + val gasLimit = ByteString.copyFrom("0x526c".toHexByteArray()) // 21100 + val to = "0x6b175474e89094c44da98b954eedeac495271d0f" + val amount = 0.toLong() + val payload = ByteString.copyFrom("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1".toHexByteArray()) + // An empty `accessList`. + val accessList = EthereumRlp.RlpList.newBuilder().build() + + val rlpList = EthereumRlp.RlpList.newBuilder() + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(chainId)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(nonce)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(maxInclusionFeePerGas)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(maxFeePerGas)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU256(gasLimit)) + .addItems(EthereumRlp.RlpItem.newBuilder().setAddress(to)) + .addItems(EthereumRlp.RlpItem.newBuilder().setNumberU64(amount)) + .addItems(EthereumRlp.RlpItem.newBuilder().setData(payload)) + .addItems(EthereumRlp.RlpItem.newBuilder().setList(accessList)) + .build() + + val encodingInput = EthereumRlp.EncodingInput.newBuilder().apply { + item = EthereumRlp.RlpItem.newBuilder().setList(rlpList).build() + }.build() + + val outputData = wallet.core.jni.EthereumRlp.encode(CoinType.ETHEREUM, encodingInput.toByteArray()) + val output = EthereumRlp.EncodingOutput.parseFrom(outputData) + + assertEquals(output.error, Common.SigningError.OK) + assert(output.errorMessage.isEmpty()) + assertEquals( + Numeric.toHexString(output.encoded.toByteArray()), + "0xf86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0" + ) + } +} \ No newline at end of file diff --git a/codegen-v2/src/codegen/swift/templates/WalletCore.h b/codegen-v2/src/codegen/swift/templates/WalletCore.h index 8b5657a9eb4..98e432e9066 100644 --- a/codegen-v2/src/codegen/swift/templates/WalletCore.h +++ b/codegen-v2/src/codegen/swift/templates/WalletCore.h @@ -45,6 +45,7 @@ FOUNDATION_EXPORT const unsigned char WalletCoreVersionString[]; #include "TWEthereumAbiFunction.h" #include "TWEthereumAbiValue.h" #include "TWEthereumChainID.h" +#include "TWEthereumRlp.h" #include "TWEthereumMessageSigner.h" #include "TWFIOAccount.h" #include "TWFilecoinAddressConverter.h" diff --git a/include/TrustWalletCore/TWEthereumRlp.h b/include/TrustWalletCore/TWEthereumRlp.h new file mode 100644 index 00000000000..1644c86e1e2 --- /dev/null +++ b/include/TrustWalletCore/TWEthereumRlp.h @@ -0,0 +1,27 @@ +// 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. + +#pragma once + +#include "TWBase.h" +#include "TWCoinType.h" +#include "TWData.h" +#include "TWString.h" + +TW_EXTERN_C_BEGIN + +TW_EXPORT_STRUCT +struct TWEthereumRlp; + +/// Encode an item or a list of items as Eth RLP binary format. +/// +/// \param coin EVM-compatible coin type. +/// \param input Non-null serialized `EthereumRlp::Proto::EncodingInput`. +/// \return serialized `EthereumRlp::Proto::EncodingOutput`. +TW_EXPORT_STATIC_METHOD +TWData* _Nonnull TWEthereumRlpEncode(enum TWCoinType coin, TWData* _Nonnull input); + +TW_EXTERN_C_END diff --git a/rust/Cargo.lock b/rust/Cargo.lock index 9d60d056815..5d434b4697b 100644 --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1530,6 +1530,7 @@ dependencies = [ "serde_json", "tw_coin_entry", "tw_ethereum", + "tw_evm", "tw_keypair", "tw_memory", "tw_misc", @@ -1573,6 +1574,7 @@ dependencies = [ "tw_hash", "tw_keypair", "tw_memory", + "tw_misc", "tw_number", "tw_proto", ] @@ -1738,13 +1740,16 @@ version = "0.1.0" dependencies = [ "tw_any_coin", "tw_bitcoin", + "tw_coin_entry", "tw_coin_registry", "tw_encoding", "tw_ethereum", "tw_hash", "tw_keypair", "tw_memory", + "tw_misc", "tw_move_parser", + "tw_number", "tw_proto", ] diff --git a/rust/coverage.stats b/rust/coverage.stats index 6dd328881c1..2ac469b6906 100644 --- a/rust/coverage.stats +++ b/rust/coverage.stats @@ -1 +1 @@ -92.0 +89.5 \ No newline at end of file diff --git a/rust/tw_any_coin/src/any_address.rs b/rust/tw_any_coin/src/any_address.rs index 26475855ce4..23fe3fe42e4 100644 --- a/rust/tw_any_coin/src/any_address.rs +++ b/rust/tw_any_coin/src/any_address.rs @@ -7,8 +7,8 @@ use tw_coin_entry::derivation::Derivation; use tw_coin_entry::error::{AddressError, AddressResult}; use tw_coin_entry::prefix::AddressPrefix; -use tw_coin_registry::coin_dispatcher; use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; use tw_keypair::tw::PublicKey; use tw_memory::Data; use tw_misc::try_or_false; diff --git a/rust/tw_any_coin/src/any_signer.rs b/rust/tw_any_coin/src/any_signer.rs index bc7cc6bff60..eea6859517b 100644 --- a/rust/tw_any_coin/src/any_signer.rs +++ b/rust/tw_any_coin/src/any_signer.rs @@ -5,8 +5,8 @@ // file LICENSE at the root of the source code distribution tree. use tw_coin_entry::error::{SigningError, SigningResult}; -use tw_coin_registry::coin_dispatcher; use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; use tw_memory::Data; /// Represents a signer to sign transactions for any blockchain. diff --git a/rust/tw_any_coin/src/transaction_compiler.rs b/rust/tw_any_coin/src/transaction_compiler.rs index bda83b071b4..85e5013bdb9 100644 --- a/rust/tw_any_coin/src/transaction_compiler.rs +++ b/rust/tw_any_coin/src/transaction_compiler.rs @@ -6,8 +6,8 @@ use tw_coin_entry::coin_entry::{PublicKeyBytes, SignatureBytes}; use tw_coin_entry::error::{SigningError, SigningResult}; -use tw_coin_registry::coin_dispatcher; use tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::coin_dispatcher; use tw_memory::Data; /// Non-core transaction utility methods, like building a transaction using an external signature. diff --git a/rust/tw_coin_registry/Cargo.toml b/rust/tw_coin_registry/Cargo.toml index e48c3411521..dc91228fb3b 100644 --- a/rust/tw_coin_registry/Cargo.toml +++ b/rust/tw_coin_registry/Cargo.toml @@ -9,6 +9,7 @@ serde = { version = "1.0.163", features = ["derive"] } serde_json = "1.0.96" tw_coin_entry = { path = "../tw_coin_entry" } tw_ethereum = { path = "../tw_ethereum" } +tw_evm = { path = "../tw_evm" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_misc = { path = "../tw_misc" } diff --git a/rust/tw_coin_registry/src/blockchain_type.rs b/rust/tw_coin_registry/src/blockchain_type.rs index 653d872e013..d807fc79037 100644 --- a/rust/tw_coin_registry/src/blockchain_type.rs +++ b/rust/tw_coin_registry/src/blockchain_type.rs @@ -4,7 +4,7 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -use crate::RegistryError; +use crate::error::RegistryError; use serde::de::Error; use serde::{Deserialize, Deserializer}; use std::str::FromStr; diff --git a/rust/tw_coin_registry/src/dispatcher.rs b/rust/tw_coin_registry/src/dispatcher.rs new file mode 100644 index 00000000000..d2ace9c0a52 --- /dev/null +++ b/rust/tw_coin_registry/src/dispatcher.rs @@ -0,0 +1,47 @@ +// 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 crate::blockchain_type::BlockchainType; +use crate::coin_context::CoinRegistryContext; +use crate::coin_type::CoinType; +use crate::error::{RegistryError, RegistryResult}; +use crate::registry::get_coin_item; +use tw_coin_entry::coin_entry_ext::CoinEntryExt; +use tw_ethereum::entry::EthereumEntry; +use tw_evm::evm_entry::EvmEntryExt; +use tw_ronin::entry::RoninEntry; + +pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; +pub type EvmEntryExtStaticRef = &'static dyn EvmEntryExt; + +const ETHEREUM: EthereumEntry = EthereumEntry; +const RONIN: RoninEntry = RoninEntry; + +pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { + match blockchain { + BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::Ronin => Ok(&RONIN), + BlockchainType::Unsupported => Err(RegistryError::Unsupported), + } +} + +pub fn coin_dispatcher( + coin: CoinType, +) -> RegistryResult<(CoinRegistryContext, CoinEntryExtStaticRef)> { + let item = get_coin_item(coin)?; + let coin_entry = blockchain_dispatcher(item.blockchain)?; + let coin_context = CoinRegistryContext::with_coin_item(item); + Ok((coin_context, coin_entry)) +} + +pub fn evm_dispatcher(coin: CoinType) -> RegistryResult { + let item = get_coin_item(coin)?; + match item.blockchain { + BlockchainType::Ethereum => Ok(ÐEREUM), + BlockchainType::Ronin => Ok(&RONIN), + BlockchainType::Unsupported => Err(RegistryError::Unsupported), + } +} diff --git a/rust/tw_coin_registry/src/lib.rs b/rust/tw_coin_registry/src/lib.rs index 10731da3448..cbad9faaa44 100644 --- a/rust/tw_coin_registry/src/lib.rs +++ b/rust/tw_coin_registry/src/lib.rs @@ -7,36 +7,6 @@ pub mod blockchain_type; pub mod coin_context; pub mod coin_type; +pub mod dispatcher; pub mod error; pub mod registry; - -use crate::blockchain_type::BlockchainType; -use crate::coin_context::CoinRegistryContext; -use crate::coin_type::CoinType; -use crate::error::{RegistryError, RegistryResult}; -use crate::registry::get_coin_item; -use tw_coin_entry::coin_entry_ext::CoinEntryExt; -use tw_ethereum::entry::EthereumEntry; -use tw_ronin::entry::RoninEntry; - -pub type CoinEntryExtStaticRef = &'static dyn CoinEntryExt; - -const ETHEREUM: EthereumEntry = EthereumEntry; -const RONIN: RoninEntry = RoninEntry; - -pub fn blockchain_dispatcher(blockchain: BlockchainType) -> RegistryResult { - match blockchain { - BlockchainType::Ethereum => Ok(ÐEREUM), - BlockchainType::Ronin => Ok(&RONIN), - BlockchainType::Unsupported => Err(RegistryError::Unsupported), - } -} - -pub fn coin_dispatcher( - coin: CoinType, -) -> RegistryResult<(CoinRegistryContext, CoinEntryExtStaticRef)> { - let item = get_coin_item(coin)?; - let coin_entry = blockchain_dispatcher(item.blockchain)?; - let coin_context = CoinRegistryContext::with_coin_item(item); - Ok((coin_context, coin_entry)) -} diff --git a/rust/tw_coin_registry/src/registry.rs b/rust/tw_coin_registry/src/registry.rs index c1667186861..422dd38ae6f 100644 --- a/rust/tw_coin_registry/src/registry.rs +++ b/rust/tw_coin_registry/src/registry.rs @@ -4,9 +4,9 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. +use crate::blockchain_type::BlockchainType; use crate::coin_type::CoinType; use crate::error::{RegistryError, RegistryResult}; -use crate::BlockchainType; use lazy_static::lazy_static; use serde::Deserialize; use std::collections::HashMap; diff --git a/rust/tw_ethereum/src/entry.rs b/rust/tw_ethereum/src/entry.rs index 79f9ba673d9..64cf49d2900 100644 --- a/rust/tw_ethereum/src/entry.rs +++ b/rust/tw_ethereum/src/entry.rs @@ -13,11 +13,14 @@ use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::prefix::NoPrefix; use tw_evm::address::Address; use tw_evm::evm_context::StandardEvmContext; +use tw_evm::evm_entry::EvmEntry; use tw_evm::modules::compiler::Compiler; use tw_evm::modules::json_signer::EthJsonSigner; +use tw_evm::modules::rlp_encoder::RlpEncoder; use tw_evm::modules::signer::Signer; use tw_keypair::tw::PublicKey; use tw_proto::Ethereum::Proto; +use tw_proto::EthereumRlp::Proto as RlpProto; use tw_proto::TxCompiler::Proto as CompilerProto; pub struct EthereumEntry; @@ -86,3 +89,12 @@ impl CoinEntry for EthereumEntry { Some(EthJsonSigner::default()) } } + +impl EvmEntry for EthereumEntry { + type RlpEncodingInput<'a> = RlpProto::EncodingInput<'a>; + type RlpEncodingOutput = RlpProto::EncodingOutput<'static>; + + fn encode_rlp(input: Self::RlpEncodingInput<'_>) -> Self::RlpEncodingOutput { + RlpEncoder::::encode_with_proto(input) + } +} diff --git a/rust/tw_evm/Cargo.toml b/rust/tw_evm/Cargo.toml index 4cd68972e5b..4e20144defd 100644 --- a/rust/tw_evm/Cargo.toml +++ b/rust/tw_evm/Cargo.toml @@ -14,5 +14,6 @@ tw_encoding = { path = "../tw_encoding" } tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } +tw_misc = { path = "../tw_misc" } tw_number = { path = "../tw_number" } tw_proto = { path = "../tw_proto" } diff --git a/rust/tw_evm/src/evm_entry.rs b/rust/tw_evm/src/evm_entry.rs new file mode 100644 index 00000000000..35053108aa4 --- /dev/null +++ b/rust/tw_evm/src/evm_entry.rs @@ -0,0 +1,34 @@ +// 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::Data; +use tw_proto::{deserialize, serialize, MessageRead, MessageWrite, ProtoResult}; + +/// An EVM-compatible chain entry. +pub trait EvmEntry { + type RlpEncodingInput<'a>: MessageRead<'a> + MessageWrite; + type RlpEncodingOutput: MessageWrite; + + /// Encodes an item or a list of items as Eth RLP binary format. + fn encode_rlp(input: Self::RlpEncodingInput<'_>) -> Self::RlpEncodingOutput; +} + +/// The [`EvmEntry`] trait extension. +pub trait EvmEntryExt { + /// Encodes an item or a list of items as Eth RLP binary format. + fn encode_rlp(&self, input: &[u8]) -> ProtoResult; +} + +impl EvmEntryExt for T +where + T: EvmEntry, +{ + fn encode_rlp(&self, input: &[u8]) -> ProtoResult { + let input = deserialize(input)?; + let output = ::encode_rlp(input); + serialize(&output) + } +} diff --git a/rust/tw_evm/src/lib.rs b/rust/tw_evm/src/lib.rs index a8e5851e50a..86d40f172ad 100644 --- a/rust/tw_evm/src/lib.rs +++ b/rust/tw_evm/src/lib.rs @@ -7,6 +7,7 @@ pub mod abi; pub mod address; pub mod evm_context; +pub mod evm_entry; pub mod modules; pub mod rlp; pub mod transaction; diff --git a/rust/tw_evm/src/modules/mod.rs b/rust/tw_evm/src/modules/mod.rs index 2364366317a..9fc8f30c96d 100644 --- a/rust/tw_evm/src/modules/mod.rs +++ b/rust/tw_evm/src/modules/mod.rs @@ -6,5 +6,6 @@ pub mod compiler; pub mod json_signer; +pub mod rlp_encoder; pub mod signer; pub mod tx_builder; diff --git a/rust/tw_evm/src/modules/rlp_encoder.rs b/rust/tw_evm/src/modules/rlp_encoder.rs new file mode 100644 index 00000000000..e45e2ef0d2d --- /dev/null +++ b/rust/tw_evm/src/modules/rlp_encoder.rs @@ -0,0 +1,92 @@ +// 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 crate::evm_context::EvmContext; +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::list::RlpList; +use crate::rlp::RlpEncode; +use std::borrow::Cow; +use std::marker::PhantomData; +use std::str::FromStr; +use tw_coin_entry::error::{SigningError, SigningErrorType, SigningResult}; +use tw_coin_entry::signing_output_error; +use tw_memory::Data; +use tw_number::U256; +use tw_proto::EthereumRlp::Proto; + +/// cbindgen:ignore +pub const RECURSION_LIMIT: usize = 10; + +pub struct RlpEncoder { + _phantom: PhantomData, +} + +impl RlpEncoder { + pub fn encode(val: T) -> Data + where + T: RlpEncode, + { + let mut buf = RlpBuffer::new(); + val.rlp_append(&mut buf); + buf.finish() + } + + pub fn encode_with_proto(input: Proto::EncodingInput<'_>) -> Proto::EncodingOutput<'static> { + Self::encode_with_proto_impl(input) + .unwrap_or_else(|err| signing_output_error!(Proto::EncodingOutput, err)) + } + + fn encode_with_proto_impl( + input: Proto::EncodingInput<'_>, + ) -> SigningResult> { + let Some(rlp_item) = input.item else { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + }; + + let initial_depth = 0; + let encoded = Self::encode_proto_item(initial_depth, rlp_item)?; + Ok(Proto::EncodingOutput { + encoded: Cow::from(encoded.as_slice().to_vec()), + ..Proto::EncodingOutput::default() + }) + } + + fn encode_proto_item(depth: usize, rlp_item: Proto::RlpItem) -> SigningResult { + use Proto::mod_RlpItem::OneOfitem as Item; + + if depth >= RECURSION_LIMIT { + return Err(SigningError(SigningErrorType::Error_invalid_params)); + } + + let encoded_item = match rlp_item.item { + Item::string_item(str) => RlpEncoder::::encode(str.as_ref()), + Item::number_u64(num) => RlpEncoder::::encode(U256::from(num)), + Item::number_u256(num_be) => { + let num = U256::from_big_endian_slice(num_be.as_ref())?; + RlpEncoder::::encode(num) + }, + Item::address(addr_s) => { + let addr = Context::Address::from_str(addr_s.as_ref())?; + RlpEncoder::::encode(addr.into()) + }, + Item::data(data) => RlpEncoder::::encode(data.as_ref()), + Item::list(proto_nested_list) => { + let mut rlp_nested_list = RlpList::new(); + let new_depth = depth + 1; + + for proto_nested_list in proto_nested_list.items { + let encoded_item = Self::encode_proto_item(new_depth, proto_nested_list)?; + rlp_nested_list.append_raw_encoded(encoded_item.as_slice()); + } + rlp_nested_list.finish() + }, + // Pass the `raw_encoded` item as it is. + Item::raw_encoded(encoded) => encoded.to_vec(), + Item::None => return Err(SigningError(SigningErrorType::Error_invalid_params)), + }; + Ok(encoded_item) + } +} diff --git a/rust/tw_evm/src/rlp/address.rs b/rust/tw_evm/src/rlp/address.rs deleted file mode 100644 index 1a941e8b144..00000000000 --- a/rust/tw_evm/src/rlp/address.rs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 crate::address::Address; -use rlp::{Encodable, RlpStream}; - -pub struct RlpAddressOption(Option
); - -impl From> for RlpAddressOption { - fn from(addr: Option
) -> Self { - RlpAddressOption(addr) - } -} - -impl Encodable for RlpAddressOption { - fn rlp_append(&self, s: &mut RlpStream) { - let addr_data = match self.0 { - Some(ref addr) => addr.as_slice(), - None => &[], - }; - s.encoder().encode_value(addr_data) - } -} diff --git a/rust/tw_evm/src/rlp/buffer.rs b/rust/tw_evm/src/rlp/buffer.rs new file mode 100644 index 00000000000..eea9d9e750e --- /dev/null +++ b/rust/tw_evm/src/rlp/buffer.rs @@ -0,0 +1,64 @@ +// 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::Data; + +#[derive(Default)] +pub struct RlpBuffer { + stream: rlp::RlpStream, +} + +impl RlpBuffer { + /// cbindgen:ignore + const NO_ITEMS: usize = 0; + /// cbindgen:ignore + const ONE_ITEM: usize = 1; + + /// Creates `RlpBuffer` with a default capacity. + pub fn new() -> RlpBuffer { + RlpBuffer::default() + } + + /// Begins an RLP list. + /// Please note that it should be finalized using [`RlpBuffer::finalize_list`]. + pub(crate) fn begin_list(&mut self) { + self.stream.begin_unbounded_list(); + } + + /// Appends an item by its `bytes` representation. + /// This method is usually called from [`RlpEncode::rlp_append`]. + pub fn append_data(&mut self, bytes: &[u8]) { + self.stream.encoder().encode_value(bytes) + } + + /// Appends an empty list. + pub(crate) fn append_empty_list(&mut self) { + self.stream.begin_list(Self::NO_ITEMS); + } + + /// Appends an already encoded with all required headers value. + pub(crate) fn append_raw_encoded(&mut self, bytes: &[u8]) { + self.stream.append_raw(bytes, Self::ONE_ITEM); + } + + /// Finalize the list. + /// + /// # Panic + /// + /// The method may panic if [`RlpBuffer::begin_list`] hasn't been called before. + pub(crate) fn finalize_list(&mut self) { + self.stream.finalize_unbounded_list(); + } + + /// Streams out encoded bytes. + /// + /// # Panic + /// + /// Tte method panic if [`RlpBuffer::begin_list`] has been called without [`RlpBuffer::finalize_list`]. + pub fn finish(self) -> Data { + self.stream.out().into() + } +} diff --git a/rust/tw_evm/src/rlp/impls.rs b/rust/tw_evm/src/rlp/impls.rs new file mode 100644 index 00000000000..1927c01cff8 --- /dev/null +++ b/rust/tw_evm/src/rlp/impls.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. + +use crate::address::Address; +use crate::rlp::buffer::RlpBuffer; +use crate::rlp::RlpEncode; +use tw_number::U256; + +impl RlpEncode for U256 { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(&self.to_big_endian_compact()) + } +} + +impl RlpEncode for Address { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(self.as_slice()) + } +} + +impl RlpEncode for Option
{ + fn rlp_append(&self, buf: &mut RlpBuffer) { + match self { + Some(ref addr) => addr.rlp_append(buf), + None => buf.append_data(&[]), + } + } +} + +impl<'a> RlpEncode for &'a [u8] { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(self) + } +} + +impl<'a> RlpEncode for &'a str { + fn rlp_append(&self, buf: &mut RlpBuffer) { + buf.append_data(self.as_bytes()) + } +} diff --git a/rust/tw_evm/src/rlp/list.rs b/rust/tw_evm/src/rlp/list.rs new file mode 100644 index 00000000000..4bf58634d1f --- /dev/null +++ b/rust/tw_evm/src/rlp/list.rs @@ -0,0 +1,64 @@ +// 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 crate::rlp::buffer::RlpBuffer; +use crate::rlp::RlpEncode; +use tw_memory::Data; + +/// An RLP list of items. +pub struct RlpList { + buf: RlpBuffer, +} + +impl Default for RlpList { + fn default() -> Self { + RlpList::new() + } +} + +impl RlpList { + /// Creates a default `RlpList`. + pub fn new() -> RlpList { + let mut buf = RlpBuffer::new(); + buf.begin_list(); + RlpList { buf } + } + + /// Appends an item. + pub fn append(&mut self, item: T) -> &mut Self + where + T: RlpEncode, + { + item.rlp_append(&mut self.buf); + self + } + + /// Appends a sublist. + pub fn append_list(&mut self, list: RlpList) -> &mut Self { + let encoded_list = list.finish(); + self.buf.append_data(encoded_list.as_ref()); + self + } + + /// Appends an empty sublist. + pub fn append_empty_list(&mut self) -> &mut Self { + self.buf.append_empty_list(); + self + } + + /// Appends an already encoded with all required headers value. + pub fn append_raw_encoded(&mut self, encoded: &[u8]) -> &mut Self { + self.buf.append_raw_encoded(encoded); + self + } + + /// Finalizes the RLP list buffer. Returns encoded RLP list with a header. + #[must_use] + pub fn finish(mut self) -> Data { + self.buf.finalize_list(); + self.buf.finish() + } +} diff --git a/rust/tw_evm/src/rlp/mod.rs b/rust/tw_evm/src/rlp/mod.rs index ce25b87018a..88bbe1ff550 100644 --- a/rust/tw_evm/src/rlp/mod.rs +++ b/rust/tw_evm/src/rlp/mod.rs @@ -4,5 +4,13 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -pub mod address; -pub mod u256; +use crate::rlp::buffer::RlpBuffer; + +pub mod buffer; +pub mod impls; +pub mod list; + +/// The trait should be implemented for all types that need to be encoded in RLP. +pub trait RlpEncode { + fn rlp_append(&self, buf: &mut RlpBuffer); +} diff --git a/rust/tw_evm/src/rlp/u256.rs b/rust/tw_evm/src/rlp/u256.rs deleted file mode 100644 index b2993970385..00000000000 --- a/rust/tw_evm/src/rlp/u256.rs +++ /dev/null @@ -1,24 +0,0 @@ -// 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 rlp::{Encodable, RlpStream}; -use tw_number::U256; - -/// TODO refactor this by implementing `RlpEncode` for `Address` at the next iteration. -pub struct RlpU256(U256); - -impl From for RlpU256 { - fn from(num: U256) -> Self { - RlpU256(num) - } -} - -impl Encodable for RlpU256 { - #[inline] - fn rlp_append(&self, s: &mut RlpStream) { - s.encoder().encode_value(&self.0.to_big_endian_compact()); - } -} diff --git a/rust/tw_evm/src/transaction/transaction_eip1559.rs b/rust/tw_evm/src/transaction/transaction_eip1559.rs index a3034c7706e..496e5a25a81 100644 --- a/rust/tw_evm/src/transaction/transaction_eip1559.rs +++ b/rust/tw_evm/src/transaction/transaction_eip1559.rs @@ -5,18 +5,14 @@ // file LICENSE at the root of the source code distribution tree. use crate::address::Address; -use crate::rlp::address::RlpAddressOption; -use crate::rlp::u256::RlpU256; +use crate::rlp::list::RlpList; use crate::transaction::signature::{EthSignature, Signature}; use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; -use rlp::{RlpStream, EMPTY_LIST_RLP}; use tw_keypair::ecdsa::secp256k1; use tw_memory::Data; use tw_number::U256; const EIP1559_TX_TYPE: u8 = 0x02; -const TX_FIELDS_LEN: usize = 9; -const TX_FIELDS_WITH_SIGNATURE_LEN: usize = TX_FIELDS_LEN + 3; /// EIP1559 transaction. pub struct TransactionEip1559 { @@ -90,36 +86,29 @@ fn encode_transaction( chain_id: U256, signature: Option<&Signature>, ) -> Data { - let len = match signature { - Some(_) => TX_FIELDS_WITH_SIGNATURE_LEN, - None => TX_FIELDS_LEN, - }; - - let mut stream = RlpStream::new_list(len); - stream - .append(&RlpU256::from(chain_id)) - .append(&RlpU256::from(tx.nonce)) - .append(&RlpU256::from(tx.max_inclusion_fee_per_gas)) - .append(&RlpU256::from(tx.max_fee_per_gas)) - .append(&RlpU256::from(tx.gas_limit)) - .append(&RlpAddressOption::from(tx.to)) - .append(&RlpU256::from(tx.amount)) - .append(&tx.payload.as_slice()) + let mut list = RlpList::new(); + list.append(chain_id) + .append(tx.nonce) + .append(tx.max_inclusion_fee_per_gas) + .append(tx.max_fee_per_gas) + .append(tx.gas_limit) + .append(tx.to) + .append(tx.amount) + .append(tx.payload.as_slice()) // empty `access_list`. - .append_raw(&EMPTY_LIST_RLP[..], 1); + .append_empty_list(); if let Some(signature) = signature { - stream - .append(&RlpU256::from(signature.v())) - .append(&RlpU256::from(signature.r())) - .append(&RlpU256::from(signature.s())); + list.append(signature.v()); + list.append(signature.r()); + list.append(signature.s()); } - let tx_encoded = stream.out(); + let tx_encoded = list.finish(); let mut envelope = Vec::with_capacity(tx_encoded.len() + 1); envelope.push(EIP1559_TX_TYPE); - envelope.extend_from_slice(&tx_encoded); + envelope.extend_from_slice(tx_encoded.as_slice()); envelope } diff --git a/rust/tw_evm/src/transaction/transaction_non_typed.rs b/rust/tw_evm/src/transaction/transaction_non_typed.rs index 78c51063dd1..f5392539d16 100644 --- a/rust/tw_evm/src/transaction/transaction_non_typed.rs +++ b/rust/tw_evm/src/transaction/transaction_non_typed.rs @@ -5,17 +5,13 @@ // file LICENSE at the root of the source code distribution tree. use crate::address::Address; -use crate::rlp::address::RlpAddressOption; -use crate::rlp::u256::RlpU256; +use crate::rlp::list::RlpList; use crate::transaction::signature::{EthSignature, SignatureEip155}; use crate::transaction::{SignedTransaction, TransactionCommon, UnsignedTransaction}; -use rlp::RlpStream; use tw_keypair::ecdsa::secp256k1; use tw_memory::Data; use tw_number::U256; -const TX_FIELDS_LEN: usize = 9; - /// Original transaction format, with no explicit type, legacy as pre-EIP2718. pub struct TransactionNonTyped { pub nonce: U256, @@ -87,25 +83,20 @@ fn encode_transaction( chain_id: U256, signature: Option<&SignatureEip155>, ) -> Data { - let mut stream = RlpStream::new_list(TX_FIELDS_LEN); - stream - .append(&RlpU256::from(tx.nonce)) - .append(&RlpU256::from(tx.gas_price)) - .append(&RlpU256::from(tx.gas_limit)) - .append(&RlpAddressOption::from(tx.to)) - .append(&RlpU256::from(tx.amount)) - .append(&tx.payload.as_slice()); + let mut list = RlpList::new(); + list.append(tx.nonce) + .append(tx.gas_price) + .append(tx.gas_limit) + .append(tx.to) + .append(tx.amount) + .append(tx.payload.as_slice()); let (v, r, s) = match signature { Some(sign) => (sign.v(), sign.r(), sign.s()), None => (chain_id, U256::zero(), U256::zero()), }; - - stream - .append(&RlpU256::from(v)) - .append(&RlpU256::from(r)) - .append(&RlpU256::from(s)); - stream.out().to_vec() + list.append(v).append(r).append(s); + list.finish() } #[cfg(test)] diff --git a/rust/tw_evm/tests/rlp.rs b/rust/tw_evm/tests/rlp.rs new file mode 100644 index 00000000000..df7bb0c9915 --- /dev/null +++ b/rust/tw_evm/tests/rlp.rs @@ -0,0 +1,247 @@ +// 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 std::str::FromStr; +use tw_coin_entry::error::SigningErrorType; +use tw_encoding::hex::{DecodeHex, ToHex}; +use tw_evm::evm_context::StandardEvmContext; +use tw_evm::modules::rlp_encoder::{RlpEncoder, RECURSION_LIMIT}; +use tw_number::U256; +use tw_proto::EthereumRlp::Proto as RlpProto; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +fn make_item(item: Item) -> RlpProto::RlpItem { + RlpProto::RlpItem { item } +} + +#[track_caller] +fn test_encode(item: Item, expected: &str) { + let input = RlpProto::EncodingInput { + item: Some(make_item(item)), + }; + let output = RlpEncoder::::encode_with_proto(input); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!(output.encoded.to_hex(), expected); +} + +#[test] +fn test_encode_string() { + test_encode(Item::string_item(Cow::from("")), "80"); + test_encode(Item::string_item(Cow::from("d")), "64"); + test_encode(Item::string_item(Cow::from("dog")), "83646f67"); +} + +#[test] +fn test_encode_number_u64() { + test_encode(Item::number_u64(0), "80"); + test_encode(Item::number_u64(127), "7f"); + test_encode(Item::number_u64(128), "8180"); + test_encode(Item::number_u64(255), "81ff"); + test_encode(Item::number_u64(256), "820100"); + test_encode(Item::number_u64(1024), "820400"); + test_encode(Item::number_u64(0xffff), "82ffff"); + test_encode(Item::number_u64(0x010000), "83010000"); + test_encode(Item::number_u64(0xffffff), "83ffffff"); + test_encode(Item::number_u64(0xffffffff), "84ffffffff"); + test_encode(Item::number_u64(0xffffffffffffff), "87ffffffffffffff"); +} + +#[test] +fn test_ethereum_rlp_number_u256() { + macro_rules! test_encode_u256 { + ($num_str:literal => $expected:literal) => { + let num = U256::from_str($num_str).unwrap().to_big_endian_compact(); + test_encode(Item::number_u256(Cow::from(num)), $expected); + }; + } + + test_encode_u256!("0" => "80"); + test_encode_u256!("1" => "01"); + test_encode_u256!("127" => "7f"); + test_encode_u256!("128" => "8180"); + test_encode_u256!("256" => "820100"); + test_encode_u256!("1024" => "820400"); + + // 0xffffff + test_encode_u256!("16777215" => "83ffffff"); + // 0xffffffff + test_encode_u256!("4294967295" => "84ffffffff"); + // 0xffffffffffffff + test_encode_u256!("72057594037927935" => "87ffffffffffffff"); + // 0x102030405060708090a0b0c0d0e0f2 + test_encode_u256!("83729609699884896815286331701780722" => "8f102030405060708090a0b0c0d0e0f2"); + // 0x0100020003000400050006000700080009000a000b000c000d000e01 + test_encode_u256!("105315505618206987246253880190783558935785933862974822347068935681" => "9c0100020003000400050006000700080009000a000b000c000d000e01"); + // 0x0100000000000000000000000000000000000000000000000000000000000000 + test_encode_u256!("452312848583266388373324160190187140051835877600158453279131187530910662656" => "a00100000000000000000000000000000000000000000000000000000000000000"); +} + +#[test] +fn test_ethereum_rlp_raw_encoded() { + let raw_encoded = "946b175474e89094c44da98b954eedeac495271d0f" + .decode_hex() + .unwrap(); + test_encode( + Item::raw_encoded(Cow::from(raw_encoded)), + "946b175474e89094c44da98b954eedeac495271d0f", + ); + + let empty: &[u8] = &[]; + test_encode(Item::raw_encoded(Cow::from(empty)), ""); +} + +#[test] +fn test_ethereum_rlp_list() { + // Empty list. + let list = RlpProto::RlpList { + items: Vec::default(), + }; + test_encode(Item::list(list), "c0"); + + // [1, 2, 3] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::number_u64(1)), + make_item(Item::number_u64(2)), + make_item(Item::number_u64(3)), + ], + }; + test_encode(Item::list(list), "c3010203"); + + // ["a", "b"] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("a"))), + make_item(Item::string_item(Cow::from("b"))), + ], + }; + test_encode(Item::list(list), "c26162"); + + // ["cat", "dog"] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("cat"))), + make_item(Item::string_item(Cow::from("dog"))), + ], + }; + test_encode(Item::list(list), "c88363617483646f67"); + + // ["cat", "dog"] list. + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("cat"))), + make_item(Item::string_item(Cow::from("dog"))), + ], + }; + test_encode(Item::list(list), "c88363617483646f67"); +} + +#[test] +fn test_ethereum_rlp_nested_list() { + let l11 = RlpProto::RlpList { + items: vec![ + make_item(Item::number_u64(1)), + make_item(Item::number_u64(2)), + make_item(Item::number_u64(3)), + ], + }; + let l12 = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("apple"))), + make_item(Item::string_item(Cow::from("banana"))), + make_item(Item::string_item(Cow::from("cherry"))), + ], + }; + let l21 = RlpProto::RlpList { + items: vec![ + make_item(Item::data(Cow::from("abcdef".decode_hex().unwrap()))), + make_item(Item::data(Cow::from( + "00010203040506070809".decode_hex().unwrap(), + ))), + ], + }; + let l22 = RlpProto::RlpList { + items: vec![ + make_item(Item::string_item(Cow::from("bitcoin"))), + make_item(Item::string_item(Cow::from("beeenbee"))), + make_item(Item::string_item(Cow::from("eth"))), + ], + }; + let l1 = RlpProto::RlpList { + items: vec![make_item(Item::list(l11)), make_item(Item::list(l12))], + }; + let l2 = RlpProto::RlpList { + items: vec![make_item(Item::list(l21)), make_item(Item::list(l22))], + }; + let list = RlpProto::RlpList { + items: vec![make_item(Item::list(l1)), make_item(Item::list(l2))], + }; + + // The value is checked by using https://codechain-io.github.io/rlp-debugger/ + test_encode( + Item::list(list), + "f841d9c3010203d4856170706c658662616e616e6186636865727279e6cf83abcdef8a00010203040506070809d587626974636f696e88626565656e62656583657468" + ); +} + +#[test] +fn test_ethereum_rlp_nested_list_recursion_limit() { + fn make_nested_list(nested_lists_to_create: usize) -> RlpProto::RlpList<'static> { + if nested_lists_to_create == 1 { + // This is the last call in the recursion. + return RlpProto::RlpList { items: Vec::new() }; + } + + let nested_list = make_nested_list(nested_lists_to_create - 1); + RlpProto::RlpList { + items: vec![make_item(Item::list(nested_list))], + } + } + + let nested_list = make_nested_list(RECURSION_LIMIT + 10); + let input = RlpProto::EncodingInput { + item: Some(make_item(Item::list(nested_list))), + }; + + let output = RlpEncoder::::encode_with_proto(input); + assert_eq!(output.error, SigningErrorType::Error_invalid_params); +} + +#[test] +fn test_ethereum_rlp_list_eip1559() { + let chain_id = U256::encode_be_compact(10); + let nonce = U256::encode_be_compact(6); + let max_inclusion_fee_per_gas = 2_000_000_000; + let max_fee_per_gas = U256::encode_be_compact(3_000_000_000); + let gas_limit = U256::encode_be_compact(21_100); + let to = "0x6b175474e89094c44da98b954eedeac495271d0f"; + let amount = 0; + let payload = "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1".decode_hex().unwrap(); + + // Empty nested list. + let access_list = RlpProto::RlpList { + items: Vec::default(), + }; + let list = RlpProto::RlpList { + items: vec![ + make_item(Item::number_u256(chain_id)), + make_item(Item::number_u256(nonce)), + make_item(Item::number_u64(max_inclusion_fee_per_gas)), + make_item(Item::number_u256(max_fee_per_gas)), + make_item(Item::number_u256(gas_limit)), + make_item(Item::address(Cow::from(to))), + make_item(Item::number_u64(amount)), + make_item(Item::data(Cow::from(payload))), + make_item(Item::list(access_list)), + ], + }; + test_encode( + Item::list(list), + "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0" + ); +} diff --git a/rust/tw_number/tests/u256.rs b/rust/tw_number/tests/u256.rs index 7ecaf39075f..dd8c2dbaafc 100644 --- a/rust/tw_number/tests/u256.rs +++ b/rust/tw_number/tests/u256.rs @@ -4,8 +4,6 @@ // terms governing use, modification, and redistribution, is contained in the // file LICENSE at the root of the source code distribution tree. -extern crate core; - use std::str::FromStr; use tw_encoding::hex; use tw_encoding::hex::ToHex; diff --git a/rust/tw_ronin/src/entry.rs b/rust/tw_ronin/src/entry.rs index 4ca451978fb..92d741f3a8f 100644 --- a/rust/tw_ronin/src/entry.rs +++ b/rust/tw_ronin/src/entry.rs @@ -13,11 +13,14 @@ use tw_coin_entry::derivation::Derivation; use tw_coin_entry::error::{AddressError, AddressResult}; use tw_coin_entry::modules::plan_builder::NoPlanBuilder; use tw_coin_entry::prefix::NoPrefix; +use tw_evm::evm_entry::EvmEntry; use tw_evm::modules::compiler::Compiler; use tw_evm::modules::json_signer::EthJsonSigner; +use tw_evm::modules::rlp_encoder::RlpEncoder; use tw_evm::modules::signer::Signer; use tw_keypair::tw::PublicKey; use tw_proto::Ethereum::Proto; +use tw_proto::EthereumRlp::Proto as RlpProto; use tw_proto::TxCompiler::Proto as CompilerProto; pub struct RoninEntry; @@ -86,3 +89,13 @@ impl CoinEntry for RoninEntry { Some(EthJsonSigner::default()) } } + +impl EvmEntry for RoninEntry { + type RlpEncodingInput<'a> = RlpProto::EncodingInput<'a>; + type RlpEncodingOutput = RlpProto::EncodingOutput<'static>; + + #[inline] + fn encode_rlp(input: Self::RlpEncodingInput<'_>) -> Self::RlpEncodingOutput { + RlpEncoder::::encode_with_proto(input) + } +} diff --git a/rust/tw_ronin/tests/rlp.rs b/rust/tw_ronin/tests/rlp.rs new file mode 100644 index 00000000000..d4d88b6dbb0 --- /dev/null +++ b/rust/tw_ronin/tests/rlp.rs @@ -0,0 +1,34 @@ +// 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_coin_entry::error::SigningErrorType; +use tw_encoding::hex::ToHex; +use tw_evm::evm_entry::EvmEntryExt; +use tw_proto::EthereumRlp::Proto as RlpProto; +use tw_proto::{deserialize, serialize}; +use tw_ronin::entry::RoninEntry; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +#[test] +fn test_rlp_encode_ronin_address() { + let ronin_addr = "ronin:6b175474e89094c44da98b954eedeac495271d0f"; + let input = RlpProto::EncodingInput { + item: Some(RlpProto::RlpItem { + item: Item::address(Cow::from(ronin_addr)), + }), + }; + let input_data = serialize(&input).unwrap(); + let output_data = RoninEntry.encode_rlp(&input_data).unwrap(); + let output: RlpProto::EncodingOutput = + deserialize(&output_data).expect("Coin entry returned an invalid output"); + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + assert_eq!( + output.encoded.to_hex(), + "946b175474e89094c44da98b954eedeac495271d0f" + ); +} diff --git a/rust/wallet_core_rs/Cargo.toml b/rust/wallet_core_rs/Cargo.toml index 7be4d0a2158..11f02172bd5 100644 --- a/rust/wallet_core_rs/Cargo.toml +++ b/rust/wallet_core_rs/Cargo.toml @@ -5,7 +5,11 @@ edition = "2021" [lib] name = "wallet_core_rs" -crate-type = ["staticlib"] # Creates static lib +crate-type = ["staticlib", "rlib"] # Creates static lib + +[features] +default = ["ethereum-rlp"] +ethereum-rlp = [] [dependencies] tw_any_coin = { path = "../tw_any_coin" } @@ -17,7 +21,11 @@ tw_hash = { path = "../tw_hash" } tw_keypair = { path = "../tw_keypair" } tw_memory = { path = "../tw_memory" } tw_move_parser = { path = "../tw_move_parser" } +tw_misc = { path = "../tw_misc" } tw_proto = { path = "../tw_proto" } [dev-dependencies] tw_any_coin = { path = "../tw_any_coin", features = ["test-utils"] } +tw_coin_entry = { path = "../tw_coin_entry" } +tw_memory = { path = "../tw_memory", features = ["test-utils"] } +tw_number = { path = "../tw_number", features = ["helpers"] } diff --git a/rust/wallet_core_rs/src/ffi/ethereum/mod.rs b/rust/wallet_core_rs/src/ffi/ethereum/mod.rs new file mode 100644 index 00000000000..2b92b14f894 --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/ethereum/mod.rs @@ -0,0 +1,8 @@ +// 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. + +#[cfg(feature = "ethereum-rlp")] +pub mod rlp; diff --git a/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs b/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs new file mode 100644 index 00000000000..308228bd09b --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/ethereum/rlp.rs @@ -0,0 +1,31 @@ +// 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 tw_coin_registry::coin_type::CoinType; +use tw_coin_registry::dispatcher::evm_dispatcher; +use tw_memory::ffi::tw_data::TWData; +use tw_memory::ffi::RawPtrTrait; +use tw_misc::try_or_else; + +/// Encodes an item or a list of items as Eth RLP binary format. +/// +/// \param coin EVM-compatible coin type. +/// \param input Non-null serialized `EthereumRlp::Proto::EncodingInput`. +/// \return serialized `EthereumRlp::Proto::EncodingOutput`. +#[no_mangle] +pub unsafe extern "C" fn tw_ethereum_rlp_encode( + coin: CoinType, + input: *const TWData, +) -> *mut TWData { + let input_data = try_or_else!(TWData::from_ptr_as_ref(input), std::ptr::null_mut); + let evm_dispatcher = try_or_else!(evm_dispatcher(coin), std::ptr::null_mut); + evm_dispatcher + .encode_rlp(input_data.as_slice()) + .map(|data| TWData::from(data).into_ptr()) + .unwrap_or_else(|_| std::ptr::null_mut()) +} diff --git a/rust/wallet_core_rs/src/ffi/mod.rs b/rust/wallet_core_rs/src/ffi/mod.rs new file mode 100644 index 00000000000..1ee22be0aeb --- /dev/null +++ b/rust/wallet_core_rs/src/ffi/mod.rs @@ -0,0 +1,7 @@ +// 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. + +pub mod ethereum; diff --git a/rust/wallet_core_rs/src/lib.rs b/rust/wallet_core_rs/src/lib.rs index b92945faeff..6335fa4a0f4 100644 --- a/rust/wallet_core_rs/src/lib.rs +++ b/rust/wallet_core_rs/src/lib.rs @@ -14,3 +14,5 @@ pub extern crate tw_keypair; pub extern crate tw_memory; pub extern crate tw_move_parser; pub extern crate tw_proto; + +pub mod ffi; diff --git a/rust/wallet_core_rs/tests/ethereum_rlp.rs b/rust/wallet_core_rs/tests/ethereum_rlp.rs new file mode 100644 index 00000000000..29811ac0def --- /dev/null +++ b/rust/wallet_core_rs/tests/ethereum_rlp.rs @@ -0,0 +1,37 @@ +// 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_coin_entry::error::SigningErrorType; +use tw_encoding::hex::ToHex; +use tw_memory::test_utils::tw_data_helper::TWDataHelper; +use tw_proto::EthereumRlp::Proto as RlpProto; +use tw_proto::{deserialize, serialize}; +use wallet_core_rs::ffi::ethereum::rlp::tw_ethereum_rlp_encode; +use RlpProto::mod_RlpItem::OneOfitem as Item; + +const ETHEREUM_COIN_TYPE: u32 = 60; + +#[test] +fn test_ethereum_rlp() { + let item = RlpProto::RlpItem { + item: Item::number_u64(128), + }; + let input = RlpProto::EncodingInput { item: Some(item) }; + let input_data = TWDataHelper::create(serialize(&input).unwrap()); + + let output_data = + TWDataHelper::wrap(unsafe { tw_ethereum_rlp_encode(ETHEREUM_COIN_TYPE, input_data.ptr()) }) + .to_vec() + .expect("!tw_ethereum_rlp_encode returned nullptr"); + let output: RlpProto::EncodingOutput = + deserialize(&output_data).expect("!tw_ethereum_rlp_encode returned an invalid output"); + + assert_eq!(output.error, SigningErrorType::OK); + assert!(output.error_message.is_empty()); + + let expected_encoded = "8180"; + assert_eq!(output.encoded.to_hex(), expected_encoded); +} diff --git a/samples/kmp/shared/build.gradle.kts b/samples/kmp/shared/build.gradle.kts index aea1d8cffce..7bf329d4c27 100644 --- a/samples/kmp/shared/build.gradle.kts +++ b/samples/kmp/shared/build.gradle.kts @@ -35,7 +35,7 @@ kotlin { sourceSets { val commonMain by getting { dependencies { - implementation("com.trustwallet:wallet-core-kotlin:3.2.6") + implementation("com.trustwallet:wallet-core-kotlin:3.2.9") } } val commonTest by getting { diff --git a/src/Aeternity/Signer.cpp b/src/Aeternity/Signer.cpp index ea05ed59875..20d31a6dd61 100644 --- a/src/Aeternity/Signer.cpp +++ b/src/Aeternity/Signer.cpp @@ -48,20 +48,23 @@ Proto::SigningOutput Signer::sign(const TW::PrivateKey& privateKey, Transaction& return createProtoOutput(signature, signedEncodedTx); } -Data Signer::buildRlpTxRaw(Data& txRaw, Data& sigRaw) { - auto rlpTxRaw = Data(); - auto signaturesList = Data(); - append(signaturesList, Ethereum::RLP::encode(sigRaw)); +Data Signer::buildRlpTxRaw(const Data& txRaw, const Data& sigRaw) { + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); - append(rlpTxRaw, Ethereum::RLP::encode(Identifiers::objectTagSignedTransaction)); - append(rlpTxRaw, Ethereum::RLP::encode(Identifiers::rlpMessageVersion)); - append(rlpTxRaw, Ethereum::RLP::encodeList(signaturesList)); - append(rlpTxRaw, Ethereum::RLP::encode(txRaw)); + rlpList->add_items()->set_number_u64(Identifiers::objectTagSignedTransaction); + rlpList->add_items()->set_number_u64(Identifiers::rlpMessageVersion); - return Ethereum::RLP::encodeList(rlpTxRaw); + // Append a list of signatures. + auto* signaturesList = rlpList->add_items()->mutable_list(); + signaturesList->add_items()->set_data(sigRaw.data(), sigRaw.size()); + + rlpList->add_items()->set_data(txRaw.data(), txRaw.size()); + + return Ethereum::RLP::encode(input); } -Data Signer::buildMessageToSign(Data& txRaw) { +Data Signer::buildMessageToSign(const Data& txRaw) { auto data = Data(); Data bytes(Identifiers::networkId.begin(), Identifiers::networkId.end()); append(data, bytes); diff --git a/src/Aeternity/Signer.h b/src/Aeternity/Signer.h index fd085b2fdb3..db49586d535 100644 --- a/src/Aeternity/Signer.h +++ b/src/Aeternity/Signer.h @@ -23,9 +23,9 @@ class Signer { private: static const uint8_t checkSumSize = 4; - static Data buildRlpTxRaw(Data& txRaw, Data& sigRaw); + static Data buildRlpTxRaw(const Data& txRaw, const Data& sigRaw); - static Data buildMessageToSign(Data& txRaw); + static Data buildMessageToSign(const Data& txRaw); static Proto::SigningOutput createProtoOutput(std::string& signature, const std::string& signedTx); diff --git a/src/Aeternity/Transaction.cpp b/src/Aeternity/Transaction.cpp index cb104daede2..90dd197b74b 100644 --- a/src/Aeternity/Transaction.cpp +++ b/src/Aeternity/Transaction.cpp @@ -12,21 +12,44 @@ namespace TW::Aeternity { +/// Aeternity network does not accept zero int values as rlp param, +/// instead empty byte array should be encoded +/// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera +EthereumRlp::Proto::RlpItem prepareSafeZero(const uint256_t& value) { + EthereumRlp::Proto::RlpItem item; + + if (value == 0) { + Data zeroValue{0}; + item.set_data(zeroValue.data(), zeroValue.size()); + } else { + auto valueData = store(value); + item.set_number_u256(valueData.data(), valueData.size()); + } + + return item; +} + /// RLP returns a byte serialized representation Data Transaction::encode() { - auto encoded = Data(); - append(encoded, Ethereum::RLP::encode(Identifiers::objectTagSpendTransaction)); - append(encoded, Ethereum::RLP::encode(Identifiers::rlpMessageVersion)); - append(encoded, Ethereum::RLP::encode(buildTag(sender_id))); - append(encoded, Ethereum::RLP::encode(buildTag(recipient_id))); - append(encoded, encodeSafeZero(amount)); - append(encoded, encodeSafeZero(fee)); - append(encoded, encodeSafeZero(ttl)); - append(encoded, encodeSafeZero(nonce)); - append(encoded, Ethereum::RLP::encode(payload)); - - const Data& raw = Ethereum::RLP::encodeList(encoded); - return raw; + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + auto senderIdTag = buildTag(sender_id); + auto recipientIdTag = buildTag(recipient_id); + + rlpList->add_items()->set_number_u64(Identifiers::objectTagSpendTransaction); + rlpList->add_items()->set_number_u64(Identifiers::rlpMessageVersion); + rlpList->add_items()->set_data(senderIdTag.data(), senderIdTag.size()); + rlpList->add_items()->set_data(recipientIdTag.data(), recipientIdTag.size()); + + *rlpList->add_items() = prepareSafeZero(amount); + *rlpList->add_items() = prepareSafeZero(fee); + *rlpList->add_items() = prepareSafeZero(ttl); + *rlpList->add_items() = prepareSafeZero(nonce); + + rlpList->add_items()->set_data(payload.data(), payload.size()); + + return Ethereum::RLP::encode(input); } TW::Data Transaction::buildTag(const std::string& address) { @@ -39,11 +62,4 @@ TW::Data Transaction::buildTag(const std::string& address) { return data; } -TW::Data Transaction::encodeSafeZero(uint256_t value) { - if (value == 0) { - return Ethereum::RLP::encode(Data{0}); - } - return Ethereum::RLP::encode(value); -} - } // namespace TW::Aeternity diff --git a/src/Aeternity/Transaction.h b/src/Aeternity/Transaction.h index 20efc9d5d79..f019395ecd6 100644 --- a/src/Aeternity/Transaction.h +++ b/src/Aeternity/Transaction.h @@ -52,13 +52,6 @@ class Transaction { //// buildIDTag assemble an id() object //// see https://github.com/aeternity/protocol/blob/epoch-v0.22.0/serializations.md#the-id-type static Data buildTag(const std::string& address); - - /// Aeternity network does not accept zero int values as rlp param, - /// instead empty byte array should be encoded - /// see https://forum.aeternity.com/t/invalid-tx-error-on-mainnet-goggle-says-it-looks-good/4118/5?u=defuera - static Data encodeSafeZero(uint256_t value); - - }; } // namespace TW::Aeternity diff --git a/src/Aion/RLP.h b/src/Aion/RLP.h index 5f0c8a1991c..a1995e11a60 100644 --- a/src/Aion/RLP.h +++ b/src/Aion/RLP.h @@ -17,20 +17,28 @@ namespace TW::Aion { +using boost::multiprecision::uint128_t; + /// Aion's RLP encoding for long numbers /// https://github.com/aionnetwork/aion/issues/680 struct RLP { - static Data encodeLong(boost::multiprecision::uint128_t l) noexcept { + static EthereumRlp::Proto::RlpItem prepareLong(uint128_t l) { + EthereumRlp::Proto::RlpItem item; + if ((l & 0x00000000FFFFFFFFL) == l) { - return Ethereum::RLP::encode(static_cast(l)); + auto u256 = store(l); + item.set_number_u256(u256.data(), u256.size()); + } else { + Data result(9); + result[0] = 0x80 + 8; + for (int i = 8; i > 0; i--) { + result[i] = (byte)(l & 0xFF); + l >>= 8; + } + item.set_raw_encoded(result.data(), result.size()); } - Data result(9); - result[0] = 0x80 + 8; - for (int i = 8; i > 0; i--) { - result[i] = (byte)(l & 0xFF); - l >>= 8; - } - return result; + + return item; } }; diff --git a/src/Aion/Transaction.cpp b/src/Aion/Transaction.cpp index 3cfad0b3d91..62f81e6b163 100644 --- a/src/Aion/Transaction.cpp +++ b/src/Aion/Transaction.cpp @@ -5,27 +5,42 @@ // file LICENSE at the root of the source code distribution tree. #include "Transaction.h" + +#include "Ethereum/RLP.h" #include "RLP.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" using namespace TW; using boost::multiprecision::uint128_t; namespace TW::Aion { +static const uint128_t gTransactionType = 1; + Data Transaction::encode() const noexcept { - auto encoded = Data(); - append(encoded, Ethereum::RLP::encode(nonce)); - append(encoded, Ethereum::RLP::encode(to.bytes)); - append(encoded, Ethereum::RLP::encode(amount)); - append(encoded, Ethereum::RLP::encode(payload)); - append(encoded, Ethereum::RLP::encode(timestamp)); - append(encoded, RLP::encodeLong(gasLimit)); - append(encoded, RLP::encodeLong(gasPrice)); - append(encoded, RLP::encodeLong(uint128_t(1))); // Aion transaction type + auto nonceData = store(nonce); + auto amountData = store(amount); + auto timestampData = store(timestamp); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u256(nonceData.data(), nonceData.size()); + rlpList->add_items()->set_data(to.bytes.data(), to.bytes.size()); + rlpList->add_items()->set_number_u256(amountData.data(), amountData.size()); + rlpList->add_items()->set_data(payload.data(), payload.size()); + rlpList->add_items()->set_number_u256(timestampData.data(), timestampData.size()); + + *rlpList->add_items() = RLP::prepareLong(gasLimit); + *rlpList->add_items() = RLP::prepareLong(gasPrice); + *rlpList->add_items() = RLP::prepareLong(gTransactionType); + if (!signature.empty()) { - append(encoded, Ethereum::RLP::encode(signature)); + rlpList->add_items()->set_data(signature.data(), signature.size()); } - return Ethereum::RLP::encodeList(encoded); + + return Ethereum::RLP::encode(input); } } // namespace TW::Aion diff --git a/src/Ethereum/RLP.cpp b/src/Ethereum/RLP.cpp index f8179cbaadf..486162a58de 100644 --- a/src/Ethereum/RLP.cpp +++ b/src/Ethereum/RLP.cpp @@ -6,196 +6,41 @@ #include "RLP.h" -#include "../BinaryCoding.h" -#include "../Numeric.h" +#include "BinaryCoding.h" +#include "TrustWalletCore/TWCoinType.h" +#include "rust/Wrapper.h" #include namespace TW::Ethereum { -Data RLP::encode(const uint256_t& value) noexcept { - using boost::multiprecision::cpp_int; +Data RLP::encode(const EthereumRlp::Proto::EncodingInput& input) { + Rust::TWDataWrapper inputData(data(input.SerializeAsString())); + Rust::TWDataWrapper outputPtr = Rust::tw_ethereum_rlp_encode(TWCoinTypeEthereum, inputData.get()); - Data bytes; - export_bits(value, std::back_inserter(bytes), 8); - - if (bytes.empty() || (bytes.size() == 1 && bytes[0] == 0)) { - return {0x80}; + auto outputData = outputPtr.toDataOrDefault(); + if (outputData.empty()) { + return {}; } - return encode(bytes); -} + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(outputData.data(), static_cast(outputData.size())); -Data RLP::encodeList(const Data& encoded) noexcept { - auto result = encodeHeader(encoded.size(), 0xc0, 0xf7); - result.reserve(result.size() + encoded.size()); - result.insert(result.end(), encoded.begin(), encoded.end()); - return result; + return data(output.encoded()); } -Data RLP::encode(const Data& data) noexcept { - if (data.size() == 1 && data[0] <= 0x7f) { - // Fits in single byte, no header - return data; - } - - auto encoded = encodeHeader(data.size(), 0x80, 0xb7); - encoded.insert(encoded.end(), data.begin(), data.end()); - return encoded; +Data RLP::encodeString(const std::string& s) { + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_string_item(s); + return encode(input); } -Data RLP::encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept { - if (size < 56) { - return {static_cast(smallTag + size)}; - } +Data RLP::encodeU256(const uint256_t & num) { + auto numData = store(num); - const auto sizeData = putVarInt(size); - - auto header = Data(); - header.reserve(1 + sizeData.size()); - header.push_back(largeTag + static_cast(sizeData.size())); - header.insert(header.end(), sizeData.begin(), sizeData.end()); - return header; -} - -Data RLP::putVarInt(uint64_t i) noexcept { - Data bytes; // accumulate bytes here, in reverse order - do { - // take LSB byte, append - bytes.push_back(i & 0xff); - i = i >> 8; - } while (i); - assert(bytes.size() >= 1 && bytes.size() <= 8); - std::reverse(bytes.begin(), bytes.end()); - return bytes; -} - -uint64_t RLP::parseVarInt(size_t size, const Data& data, size_t index) { - if (size < 1 || size > 8) { - throw std::invalid_argument("invalid length length"); - } - if (data.size() - index < size) { - throw std::invalid_argument("Not enough data for varInt"); - } - if (size >= 2 && data[index] == 0) { - throw std::invalid_argument("multi-byte length must have no leading zero"); - } - uint64_t val = 0; - for (auto i = 0U; i < size; ++i) { - val = val << 8; - val += data[index + i]; - } - return static_cast(val); -} - -RLP::DecodedItem RLP::decodeList(const Data& input) { - RLP::DecodedItem item; - auto remainder = input; - while (true) { - auto listItem = RLP::decode(remainder); - item.decoded.push_back(listItem.decoded[0]); - if (listItem.remainder.size() == 0) { - break; - } else { - remainder = listItem.remainder; - } - } - return item; -} - -RLP::DecodedItem RLP::decode(const Data& input) { - if (input.size() == 0) { - throw std::invalid_argument("can't decode empty rlp data"); - } - RLP::DecodedItem item; - auto inputLen = input.size(); - auto prefix = input[0]; - if (prefix <= 0x7f) { - // 00--7f: a single byte whose value is in the [0x00, 0x7f] range, that byte is its own RLP encoding. - item.decoded.emplace_back(Data{input[0]}); - item.remainder = subData(input, 1); - return item; - } - if (prefix <= 0xb7) { - // 80--b7: short string - // string is 0-55 bytes long. A single byte with value 0x80 plus the length of the string followed by the string - // The range of the first byte is [0x80, 0xb7] - - // empty string - if (prefix == 0x80) { - item.decoded.emplace_back(); - item.remainder = subData(input, 1); - return item; - } - - auto strLen = prefix - 0x80; - if (strLen == 1 && input[1] <= 0x7f) { - throw std::invalid_argument("single byte below 128 must be encoded as itself"); - } - - if (inputLen < (1U + strLen)) { - throw std::invalid_argument(std::string("invalid short string, length ") + std::to_string(strLen)); - } - item.decoded.push_back(subData(input, 1, strLen)); - item.remainder = subData(input, 1 + strLen); - - return item; - } - if (prefix <= 0xbf) { - // b8--bf: long string - auto lenOfStrLen = size_t(prefix - 0xb7); - auto strLen = static_cast(parseVarInt(lenOfStrLen, input, 1)); - bool isStrLenInvalid = inputLen < lenOfStrLen - || checkAddUnsignedOverflow(1U + lenOfStrLen, strLen) - || inputLen < (1U + lenOfStrLen + strLen); - if (isStrLenInvalid) { - throw std::invalid_argument(std::string("Invalid rlp encoding length, length ") + std::to_string(strLen)); - } - auto data = subData(input, 1 + lenOfStrLen, strLen); - item.decoded.push_back(data); - item.remainder = subData(input, 1 + lenOfStrLen + strLen); - return item; - } - if (prefix <= 0xf7) { - // c0--f7: a list between 0-55 bytes long - auto listLen = size_t(prefix - 0xc0); - if (inputLen < (1 + listLen)) { - throw std::invalid_argument(std::string("Invalid rlp string length, length ") + std::to_string(listLen)); - } - // empty list - if (listLen == 0) { - item.remainder = subData(input, 1); - return item; - } - - // decode list - auto listItem = decodeList(subData(input, 1, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = subData(input, 1 + listLen); - return item; - } - // f8--ff - auto lenOfListLen = size_t(prefix - 0xf7); - auto listLen = static_cast(parseVarInt(lenOfListLen, input, 1)); - if (listLen < 56) { - throw std::invalid_argument("length below 56 must be encoded in one byte"); - } - auto isListLenInvalid = inputLen < lenOfListLen - || checkAddUnsignedOverflow(1U + lenOfListLen, listLen) - || inputLen < (1U + lenOfListLen + listLen); - if (isListLenInvalid) { - throw std::invalid_argument(std::string("Invalid rlp list length, length ") + std::to_string(listLen)); - } - - // decode list - auto listItem = decodeList(subData(input, 1 + lenOfListLen, listLen)); - for (auto& data : listItem.decoded) { - item.decoded.push_back(data); - } - item.remainder = subData(input, 1 + lenOfListLen + listLen); - return item; + EthereumRlp::Proto::EncodingInput input; + input.mutable_item()->set_number_u256(numData.data(), numData.size()); + return encode(input); } } // namespace TW::Ethereum diff --git a/src/Ethereum/RLP.h b/src/Ethereum/RLP.h index beb0f5cb0f5..57c0ef18113 100644 --- a/src/Ethereum/RLP.h +++ b/src/Ethereum/RLP.h @@ -7,7 +7,8 @@ #pragma once #include "Data.h" -#include "../uint256.h" +#include "proto/EthereumRlp.pb.h" +#include "uint256.h" #include #include @@ -19,87 +20,11 @@ namespace TW::Ethereum { /// /// - SeeAlso: https://github.com/ethereum/wiki/wiki/RLP struct RLP { - /// Encodes a string; - static Data encode(const std::string& string) noexcept { - return encode(Data(string.begin(), string.end())); - } + static Data encode(const EthereumRlp::Proto::EncodingInput& input); - static Data encode(uint8_t number) noexcept { return encode(uint256_t(number)); } + static Data encodeString(const std::string& s); - static Data encode(uint16_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(int32_t number) noexcept { - if (number < 0) { - return {}; // RLP cannot encode negative numbers - } - return encode(static_cast(number)); - } - - static Data encode(uint32_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(int64_t number) noexcept { - if (number < 0) { - return {}; // RLP cannot encode negative numbers - } - return encode(static_cast(number)); - } - - static Data encode(uint64_t number) noexcept { return encode(uint256_t(number)); } - - static Data encode(const uint256_t& number) noexcept; - - /// Wraps encoded data as a list. - static Data encodeList(const Data& encoded) noexcept; - - /// Encodes a block of data. - static Data encode(const Data& data) noexcept; - - /// Encodes a static array. - template - static Data encode(const std::array& data) noexcept { - if (N == 1 && data[0] <= 0x7f) { - // Fits in single byte, no header - return Data(data.begin(), data.end()); - } - - auto encoded = encodeHeader(data.size(), 0x80, 0xb7); - encoded.insert(encoded.end(), data.begin(), data.end()); - return encoded; - } - - /// Encodes a list of elements. - template - static Data encodeList(T elements) noexcept { - auto encodedData = Data(); - for (const auto& el : elements) { - auto encoded = encode(el); - if (encoded.empty()) { - return {}; - } - encodedData.insert(encodedData.end(), encoded.begin(), encoded.end()); - } - - auto encoded = encodeHeader(encodedData.size(), 0xc0, 0xf7); - encoded.insert(encoded.end(), encodedData.begin(), encodedData.end()); - return encoded; - } - - /// Encodes a list header. - static Data encodeHeader(uint64_t size, uint8_t smallTag, uint8_t largeTag) noexcept; - - struct DecodedItem { - std::vector decoded; - Data remainder; - }; - - static DecodedItem decodeList(const Data& input); - /// Decodes data, remainder from RLP encoded data - static DecodedItem decode(const Data& data); - - /// Returns the representation of an integer using the least number of bytes needed, between 1 and 8 bytes, big endian - static Data putVarInt(uint64_t i) noexcept; - /// Parses an integer of given size, between 1 and 8 bytes, big endian - static uint64_t parseVarInt(size_t size, const Data& data, size_t index); + static Data encodeU256(const uint256_t& num); }; } // namespace TW::Ethereum diff --git a/src/Harmony/Signer.cpp b/src/Harmony/Signer.cpp index 6fc570d235e..9b262426187 100644 --- a/src/Harmony/Signer.cpp +++ b/src/Harmony/Signer.cpp @@ -12,6 +12,7 @@ namespace TW::Harmony { using INVALID_ENUM = std::integral_constant; +using RLP = TW::Ethereum::RLP; std::tuple Signer::values(const uint256_t& chainID, const Data& signature) noexcept { @@ -307,159 +308,224 @@ void Signer::sign(const PrivateKey& privateKey, const Data& hash, T& transaction } Data Signer::rlpNoHash(const Transaction& transaction, const bool include_vrs) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); - append(encoded, RLP::encode(transaction.fromShardID)); - append(encoded, RLP::encode(transaction.toShardID)); - append(encoded, RLP::encode(transaction.to.getKeyHash())); - append(encoded, RLP::encode(transaction.amount)); - append(encoded, RLP::encode(transaction.payload)); + auto nonce = store(transaction.nonce); + auto gasPrice = store(transaction.gasPrice); + auto gasLimit = store(transaction.gasLimit); + auto fromShardID = store(transaction.fromShardID); + auto toShardID = store(transaction.toShardID); + auto toKeyHash = transaction.to.getKeyHash(); + auto amount = store(transaction.amount); + + Data v; + Data r; + Data s; if (include_vrs) { - append(encoded, RLP::encode(transaction.v)); - append(encoded, RLP::encode(transaction.r)); - append(encoded, RLP::encode(transaction.s)); + v = store(transaction.v); + r = store(transaction.r); + s = store(transaction.s); } else { - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); + v = store(chainID); + r = store(0); + s = store(0); } - return RLP::encodeList(encoded); -} -template -Data Signer::rlpNoHash(const Staking& transaction, const bool include_vrs) const - noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u256(nonce.data(), nonce.size()); + rlpList->add_items()->set_number_u256(gasPrice.data(), gasPrice.size()); + rlpList->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + rlpList->add_items()->set_number_u256(fromShardID.data(), fromShardID.size()); + rlpList->add_items()->set_number_u256(toShardID.data(), toShardID.size()); + rlpList->add_items()->set_data(toKeyHash.data(), toKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + rlpList->add_items()->set_data(transaction.payload.data(), transaction.payload.size()); + + rlpList->add_items()->set_number_u256(v.data(), v.size()); + rlpList->add_items()->set_number_u256(r.data(), r.size()); + rlpList->add_items()->set_number_u256(s.data(), s.size()); - append(encoded, RLP::encode(transaction.directive)); - append(encoded, rlpNoHashDirective(transaction)); + return RLP::encode(input); +} - append(encoded, RLP::encode(transaction.nonce)); - append(encoded, RLP::encode(transaction.gasPrice)); - append(encoded, RLP::encode(transaction.gasLimit)); +template +Data Signer::rlpNoHash(const Staking& transaction, const bool include_vrs) const noexcept { + Data v; + Data r; + Data s; if (include_vrs) { - append(encoded, RLP::encode(transaction.v)); - append(encoded, RLP::encode(transaction.r)); - append(encoded, RLP::encode(transaction.s)); + v = store(transaction.v); + r = store(transaction.r); + s = store(transaction.s); } else { - append(encoded, RLP::encode(chainID)); - append(encoded, RLP::encode(0)); - append(encoded, RLP::encode(0)); + v = store(chainID); + r = store(0); + s = store(0); } - return RLP::encodeList(encoded); + + auto nonce = store(transaction.nonce); + auto gasPrice = store(transaction.gasPrice); + auto gasLimit = store(transaction.gasLimit); + + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(transaction.directive); + *rlpList->add_items() = rlpNoHashDirective(transaction); + + rlpList->add_items()->set_number_u256(nonce.data(), nonce.size()); + rlpList->add_items()->set_number_u256(gasPrice.data(), gasPrice.size()); + rlpList->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + + rlpList->add_items()->set_number_u256(v.data(), v.size()); + rlpList->add_items()->set_number_u256(r.data(), r.size()); + rlpList->add_items()->set_number_u256(s.data(), s.size()); + + return RLP::encode(input); } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; +template +EthereumRlp::Proto::RlpItem Signer::rlpPrepareDescription(const Staking& transaction) const noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.name); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.identity); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.website); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.securityContact); + rlpList->add_items()->set_string_item(transaction.stakeMsg.description.details); - auto descriptionEncoded = Data(); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.name)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.identity)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.website)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.securityContact)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.details)); - append(encoded, RLP::encodeList(descriptionEncoded)); + return item; +} - auto commissionEncoded = Data(); +EthereumRlp::Proto::RlpItem Signer::rlpPrepareCommissionRates(const Staking &transaction) noexcept { + auto rateValue = store(transaction.stakeMsg.commissionRates.rate.value); + auto maxRateValue = store(transaction.stakeMsg.commissionRates.maxRate.value); + auto maxChangeRateValue = store(transaction.stakeMsg.commissionRates.maxChangeRate.value); - auto rateEncoded = Data(); - append(rateEncoded, RLP::encode(transaction.stakeMsg.commissionRates.rate.value)); - append(commissionEncoded, RLP::encodeList(rateEncoded)); + EthereumRlp::Proto::RlpItem item; + auto* commissionList = item.mutable_list(); - auto maxRateEncoded = Data(); - append(maxRateEncoded, RLP::encode(transaction.stakeMsg.commissionRates.maxRate.value)); - append(commissionEncoded, RLP::encodeList(maxRateEncoded)); + // Append `commission::rate` properties list with a single item. + auto* rateList = commissionList->add_items()->mutable_list(); + rateList->add_items()->set_number_u256(rateValue.data(), rateValue.size()); - auto maxChangeRateEncoded = Data(); - append(maxChangeRateEncoded, - RLP::encode(transaction.stakeMsg.commissionRates.maxChangeRate.value)); - append(commissionEncoded, RLP::encodeList(maxChangeRateEncoded)); + // Append `commission::maxRate` properties list. + auto* maxRateList = commissionList->add_items()->mutable_list(); + maxRateList->add_items()->set_number_u256(maxRateValue.data(), maxRateValue.size()); - append(encoded, RLP::encodeList(commissionEncoded)); + // Append `commission::maxChangeRate` properties list. + auto* maxChangeRateList = commissionList->add_items()->mutable_list(); + maxChangeRateList->add_items()->set_number_u256(maxChangeRateValue.data(), maxChangeRateValue.size()); - append(encoded, RLP::encode(transaction.stakeMsg.minSelfDelegation)); - append(encoded, RLP::encode(transaction.stakeMsg.maxTotalDelegation)); + return item; +} - auto slotPubKeysEncoded = Data(); - for (auto pk : transaction.stakeMsg.slotPubKeys) { - append(slotPubKeysEncoded, RLP::encode(pk)); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto minSelfDelegation = store(transaction.stakeMsg.minSelfDelegation); + auto maxTotalDelegation = store(transaction.stakeMsg.maxTotalDelegation); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + *rlpList->add_items() = rlpPrepareDescription(transaction); + + // Append `commission` properties list of `rate`, `maxRate`, `maxChangeRate` sublists. + *rlpList->add_items() = rlpPrepareCommissionRates(transaction); + + rlpList->add_items()->set_number_u256(minSelfDelegation.data(), minSelfDelegation.size()); + rlpList->add_items()->set_number_u256(maxTotalDelegation.data(), maxTotalDelegation.size()); + + // Append a list of slot public keys. + auto* slotPubkeysList = rlpList->add_items()->mutable_list(); + for (const auto& pk : transaction.stakeMsg.slotPubKeys) { + slotPubkeysList->add_items()->set_data(pk.data(), pk.size()); } - append(encoded, RLP::encodeList(slotPubKeysEncoded)); - auto slotBlsSigsEncoded = Data(); - for (auto sig : transaction.stakeMsg.slotKeySigs) { - append(slotBlsSigsEncoded, RLP::encode(sig)); + // Append a list of slot key signatures. + auto* slotKeySigsList = rlpList->add_items()->mutable_list(); + for (const auto& sign : transaction.stakeMsg.slotKeySigs) { + slotKeySigsList->add_items()->set_data(sign.data(), sign.size()); } - append(encoded, RLP::encodeList(slotBlsSigsEncoded)); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); - return RLP::encodeList(encoded); + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto minSelfDelegation = store(transaction.stakeMsg.minSelfDelegation); + auto maxTotalDelegation = store(transaction.stakeMsg.maxTotalDelegation); + auto active = store(transaction.stakeMsg.active); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); - auto descriptionEncoded = Data(); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.name)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.identity)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.website)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.securityContact)); - append(descriptionEncoded, RLP::encode(transaction.stakeMsg.description.details)); - append(encoded, RLP::encodeList(descriptionEncoded)); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + *rlpList->add_items() = rlpPrepareDescription(transaction); - auto decEncoded = Data(); + auto* commissionRateList = rlpList->add_items()->mutable_list(); if (transaction.stakeMsg.commissionRate.has_value()) { // Note: std::optional.value() is not available in XCode with target < iOS 12; using '*' - append(decEncoded, RLP::encode((*transaction.stakeMsg.commissionRate).value)); + auto commissionRateValue = store((*transaction.stakeMsg.commissionRate).value); + commissionRateList->add_items()->set_number_u256(commissionRateValue.data(), commissionRateValue.size()); } - append(encoded, RLP::encodeList(decEncoded)); - append(encoded, RLP::encode(transaction.stakeMsg.minSelfDelegation)); - append(encoded, RLP::encode(transaction.stakeMsg.maxTotalDelegation)); + rlpList->add_items()->set_number_u256(minSelfDelegation.data(), minSelfDelegation.size()); + rlpList->add_items()->set_number_u256(maxTotalDelegation.data(), maxTotalDelegation.size()); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToRemove)); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToAdd)); - append(encoded, RLP::encode(transaction.stakeMsg.slotKeyToAddSig)); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToRemove.data(), transaction.stakeMsg.slotKeyToRemove.size()); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToAdd.data(), transaction.stakeMsg.slotKeyToAdd.size()); + rlpList->add_items()->set_data(transaction.stakeMsg.slotKeyToAddSig.data(), transaction.stakeMsg.slotKeyToAddSig.size()); - append(encoded, RLP::encode(transaction.stakeMsg.active)); + rlpList->add_items()->set_number_u256(active.data(), active.size()); - return RLP::encodeList(encoded); + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.validatorAddress.getKeyHash())); - append(encoded, RLP::encode(transaction.stakeMsg.amount)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + auto validatorKeyHash = transaction.stakeMsg.validatorAddress.getKeyHash(); + auto amount = store(transaction.stakeMsg.amount); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + rlpList->add_items()->set_data(validatorKeyHash.data(), validatorKeyHash.size()); + rlpList->add_items()->set_number_u256(amount.data(), amount.size()); + + return item; } -Data Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { - auto encoded = Data(); - using RLP = TW::Ethereum::RLP; - append(encoded, RLP::encode(transaction.stakeMsg.delegatorAddress.getKeyHash())); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem Signer::rlpNoHashDirective(const Staking& transaction) const noexcept { + auto delegatorKeyHash = transaction.stakeMsg.delegatorAddress.getKeyHash(); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(delegatorKeyHash.data(), delegatorKeyHash.size()); + + return item; } std::string Signer::txnAsRLPHex(Transaction& transaction) const noexcept { diff --git a/src/Harmony/Signer.h b/src/Harmony/Signer.h index 71a45b162b1..9a24142279e 100644 --- a/src/Harmony/Signer.h +++ b/src/Harmony/Signer.h @@ -12,6 +12,7 @@ #include "../Hash.h" #include "../PrivateKey.h" #include "../proto/Harmony.pb.h" +#include "../proto/EthereumRlp.pb.h" #include #include @@ -96,11 +97,16 @@ class Signer { template Data rlpNoHash(const Staking &transaction, const bool) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; - Data rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + EthereumRlp::Proto::RlpItem rlpNoHashDirective(const Staking &transaction) const noexcept; + + template + EthereumRlp::Proto::RlpItem rlpPrepareDescription(const Staking& transaction) const noexcept; + + static EthereumRlp::Proto::RlpItem rlpPrepareCommissionRates(const Staking &transaction) noexcept; }; } // namespace TW::Harmony diff --git a/src/Theta/Signer.cpp b/src/Theta/Signer.cpp index 01d46f037f6..050deff969b 100755 --- a/src/Theta/Signer.cpp +++ b/src/Theta/Signer.cpp @@ -31,7 +31,7 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { auto output = Proto::SigningOutput(); transaction.setSignature(from, signature); - auto encoded = transaction.encode(); + auto encoded = transaction.encodePayload(); output.set_encoded(encoded.data(), encoded.size()); output.set_signature(signature.data(), signature.size()); return output; @@ -39,25 +39,25 @@ Proto::SigningOutput Signer::sign(const Proto::SigningInput& input) noexcept { Data Signer::encode(const Transaction& transaction) const { const uint64_t nonce = 0; - const uint256_t gasPrice = 0; + const uint64_t gasPrice = 0; const uint64_t gasLimit = 0; - const Ethereum::Address to = Ethereum::Address("0x0000000000000000000000000000000000000000"); - const uint256_t amount = 0; + const auto* to = "0x0000000000000000000000000000000000000000"; + const uint64_t amount = 0; + auto txData = transaction.encode(chainID); + + EthereumRlp::Proto::EncodingInput encodingInput; + auto* rlpList = encodingInput.mutable_item()->mutable_list(); - auto encoded = Data(); /// Need to add the following prefix to the tx signbytes to be compatible with /// the Ethereum tx format - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encode(gasPrice)); - append(encoded, RLP::encode(gasLimit)); - append(encoded, RLP::encode(to.bytes)); - append(encoded, RLP::encode(amount)); - /// Chain ID - auto payload = Data(); - append(payload, RLP::encode(chainID)); - append(payload, transaction.encode()); - append(encoded, RLP::encode(payload)); - return RLP::encodeList(encoded); + rlpList->add_items()->set_number_u64(nonce); + rlpList->add_items()->set_number_u64(gasPrice); + rlpList->add_items()->set_number_u64(gasLimit); + rlpList->add_items()->set_address(to); + rlpList->add_items()->set_number_u64(amount); + rlpList->add_items()->set_data(txData.data(), txData.size()); + + return RLP::encode(encodingInput); } Data Signer::sign(const PrivateKey& privateKey, const Transaction& transaction) noexcept { @@ -102,7 +102,7 @@ Proto::SigningOutput Signer::compile(const Data& signature, const PublicKey& pub auto protoOutput = Proto::SigningOutput(); auto transaction = buildTransaction(); transaction.setSignature(from, signature); - auto encoded = transaction.encode(); + auto encoded = transaction.encodePayload(); protoOutput.set_encoded(encoded.data(), encoded.size()); protoOutput.set_signature(signature.data(), signature.size()); diff --git a/src/Theta/Transaction.cpp b/src/Theta/Transaction.cpp index b9a70c0611e..939c54aa7b3 100644 --- a/src/Theta/Transaction.cpp +++ b/src/Theta/Transaction.cpp @@ -12,43 +12,61 @@ namespace TW::Theta { using RLP = Ethereum::RLP; -Data encode(const Coins& coins) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(coins.thetaWei)); - append(encoded, RLP::encode(coins.tfuelWei)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const Coins& coins) noexcept { + auto thetaWei = store(coins.thetaWei); + auto tfuelWei = store(coins.tfuelWei); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_number_u256(thetaWei.data(), thetaWei.size()); + rlpList->add_items()->set_number_u256(tfuelWei.data(), tfuelWei.size()); + + return item; } -Data encode(const TxInput& input) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(input.address.bytes)); - append(encoded, encode(input.coins)); - append(encoded, RLP::encode(input.sequence)); - append(encoded, RLP::encode(input.signature)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const TxInput& input) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(input.address.bytes.data(), input.address.bytes.size()); + *rlpList->add_items() = prepare(input.coins); + rlpList->add_items()->set_number_u64(input.sequence); + rlpList->add_items()->set_data(input.signature.data(), input.signature.size()); + + return item; } -Data encode(const std::vector& inputs) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepare(const std::vector& inputs) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + for (const auto& input : inputs) { - append(encoded, encode(input)); + *rlpList->add_items() = prepare(input); } - return RLP::encodeList(encoded); + + return item; } -Data encode(const TxOutput& output) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(output.address.bytes)); - append(encoded, encode(output.coins)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepare(const TxOutput& output) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(output.address.bytes.data(), output.address.bytes.size()); + *rlpList->add_items() = prepare(output.coins); + + return item; } -Data encode(const std::vector& outputs) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepare(const std::vector& outputs) noexcept { + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + for (const auto& output : outputs) { - append(encoded, encode(output)); + *rlpList->add_items() = prepare(output); } - return RLP::encodeList(encoded); + + return item; } Transaction::Transaction(Ethereum::Address from, Ethereum::Address to, @@ -65,15 +83,29 @@ Transaction::Transaction(Ethereum::Address from, Ethereum::Address to, this->outputs.push_back(output); } -Data Transaction::encode() const noexcept { - auto encoded = Data(); - uint16_t txType = 2; // TxSend - append(encoded, RLP::encode(txType)); - auto encodedData = Data(); - append(encodedData, Theta::encode(_fee)); - append(encodedData, Theta::encode(inputs)); - append(encodedData, Theta::encode(outputs)); - append(encoded, RLP::encodeList(encodedData)); +Data Transaction::encodePayload() const noexcept { + const uint64_t txType = 2; // TxSend + + EthereumRlp::Proto::EncodingInput txInput; + auto* txPropertiesList = txInput.mutable_item()->mutable_list(); + + *txPropertiesList->add_items() = prepare(_fee); + *txPropertiesList->add_items() = prepare(inputs); + *txPropertiesList->add_items() = prepare(outputs); + + auto txPropertiesEncoded = RLP::encode(txInput); + + Data payload; + append(payload, RLP::encodeU256(static_cast(txType))); + append(payload, txPropertiesEncoded); + + return payload; +} + +Data Transaction::encode(const std::string& chainId) const noexcept { + Data encoded; + append(encoded, RLP::encodeString(chainId)); + append(encoded, encodePayload()); return encoded; } diff --git a/src/Theta/Transaction.h b/src/Theta/Transaction.h index 770bcc1cf75..5e1a1973d12 100644 --- a/src/Theta/Transaction.h +++ b/src/Theta/Transaction.h @@ -11,7 +11,7 @@ #include "Coins.h" #include "Data.h" -#include "../Ethereum/Address.h" +#include "Ethereum/Address.h" namespace TW::Theta { @@ -51,8 +51,11 @@ class Transaction { const uint256_t& thetaAmount, const uint256_t& tfuelAmount, uint64_t sequence, const uint256_t& feeAmount = 1000000000000); - /// Encodes the transaction - Data encode() const noexcept; + /// Encodes the essential part of the transaction without a Chain ID. + Data encodePayload() const noexcept; + + /// Encodes the transaction with the given `chainId`. + Data encode(const std::string& chainId) const noexcept; /// Sets signature bool setSignature(const Ethereum::Address& address, const Data& signature) noexcept; diff --git a/src/VeChain/Transaction.cpp b/src/VeChain/Transaction.cpp index 8c28ea8becf..48030e74f77 100644 --- a/src/VeChain/Transaction.cpp +++ b/src/VeChain/Transaction.cpp @@ -12,38 +12,50 @@ namespace TW::VeChain { using RLP = Ethereum::RLP; -Data encode(const Clause& clause) noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(clause.to.bytes)); - append(encoded, RLP::encode(clause.value)); - append(encoded, RLP::encode(clause.data)); - return RLP::encodeList(encoded); +EthereumRlp::Proto::RlpItem prepareClause(const Clause& clause) noexcept { + auto value = store(clause.value); + + EthereumRlp::Proto::RlpItem item; + auto* rlpList = item.mutable_list(); + + rlpList->add_items()->set_data(clause.to.bytes.data(), clause.to.bytes.size()); + rlpList->add_items()->set_number_u256(value.data(), value.size()); + rlpList->add_items()->set_data(clause.data.data(), clause.data.size()); + + return item; } -Data encodeClauses(std::vector clauses) noexcept { - auto encoded = Data(); +EthereumRlp::Proto::RlpItem prepareClauses(const std::vector& clauses) noexcept { + EthereumRlp::Proto::RlpItem item; + + auto* rlpList = item.mutable_list(); for (const auto& clause : clauses) { - auto encodedClause = encode(clause); - append(encoded, encodedClause); + *rlpList->add_items() = prepareClause(clause); } - return RLP::encodeList(encoded); + + return item; } Data Transaction::encode() const noexcept { - auto encoded = Data(); - append(encoded, RLP::encode(chainTag)); - append(encoded, RLP::encode(blockRef)); - append(encoded, RLP::encode(expiration)); - append(encoded, encodeClauses(clauses)); - append(encoded, RLP::encode(gasPriceCoef)); - append(encoded, RLP::encode(gas)); - append(encoded, RLP::encode(dependsOn)); - append(encoded, RLP::encode(nonce)); - append(encoded, RLP::encodeList(reserved)); + EthereumRlp::Proto::EncodingInput input; + auto* rlpList = input.mutable_item()->mutable_list(); + + rlpList->add_items()->set_number_u64(chainTag); + rlpList->add_items()->set_number_u64(blockRef); + rlpList->add_items()->set_number_u64(expiration); + *rlpList->add_items() = prepareClauses(clauses); + rlpList->add_items()->set_number_u64(gasPriceCoef); + rlpList->add_items()->set_number_u64(gas); + rlpList->add_items()->set_data(dependsOn.data(), dependsOn.size()); + rlpList->add_items()->set_number_u64(nonce); + // Put an empty list - reserved field for backward compatibility. + rlpList->add_items()->mutable_list(); + if (!signature.empty()) { - append(encoded, RLP::encode(signature)); + rlpList->add_items()->set_data(signature.data(), signature.size()); } - return RLP::encodeList(encoded); + + return RLP::encode(input); } } // namespace TW::VeChain diff --git a/src/interface/TWEthereumRlp.cpp b/src/interface/TWEthereumRlp.cpp new file mode 100644 index 00000000000..4c405d54882 --- /dev/null +++ b/src/interface/TWEthereumRlp.cpp @@ -0,0 +1,22 @@ +// 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. + +#include + +#include "rust/Wrapper.h" +#include "Data.h" + +using namespace TW; + +TWData* _Nonnull TWEthereumRlpEncode(enum TWCoinType coin, TWData* _Nonnull input) { + const Data& dataIn = *(reinterpret_cast(input)); + + const Rust::TWDataWrapper dataInPtr(dataIn); + Rust::TWDataWrapper dataOutPtr = Rust::tw_ethereum_rlp_encode(static_cast(coin), dataInPtr.get()); + + auto dataOut = dataOutPtr.toDataOrDefault(); + return TWDataCreateWithBytes(dataOut.data(), dataOut.size()); +} diff --git a/src/proto/EthereumRlp.proto b/src/proto/EthereumRlp.proto new file mode 100644 index 00000000000..0655ff37064 --- /dev/null +++ b/src/proto/EthereumRlp.proto @@ -0,0 +1,49 @@ +syntax = "proto3"; + +package TW.EthereumRlp.Proto; +option java_package = "wallet.core.jni.proto"; + +import "Common.proto"; + +// List of elements. +message RlpList { + repeated RlpItem items = 1; +} + +// RLP item. +message RlpItem { + oneof item { + // A string to be encoded. + string string_item = 1; + // A U64 number to be encoded. + uint64 number_u64 = 2; + // A U256 number to be encoded. + bytes number_u256 = 3; + // An address to be encoded. + string address = 4; + // A data to be encoded. + bytes data = 5; + // A list of items to be encoded. + RlpList list = 6; + // An RLP encoded item to be appended as it is. + bytes raw_encoded = 7; + } +} + +// RLP encoding input. +message EncodingInput { + // An item or a list to encode. + RlpItem item = 1; +} + +/// RLP encoding output. +message EncodingOutput { + // An item RLP encoded. + bytes encoded = 1; + + // Error code, 0 is ok, other codes will be treated as errors. + Common.Proto.SigningError error = 2; + + // Error code description. + string error_message = 3; +} diff --git a/swift/Tests/Blockchains/EthereumRlpTests.swift b/swift/Tests/Blockchains/EthereumRlpTests.swift new file mode 100644 index 00000000000..3690657bab9 --- /dev/null +++ b/swift/Tests/Blockchains/EthereumRlpTests.swift @@ -0,0 +1,47 @@ +// Copyright © 2017-2020 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. + +import XCTest +import WalletCore + +class EthereumRlpTests: XCTestCase { + func testRlpEncodeEip1559() throws { + let chainId = Data(hexString: "0x0a")! + let nonce = Data(hexString: "0x06")! + let maxInclusionFeePerGas = Data(hexString: "0x77359400")! // 2000000000 + let maxFeePerGas = Data(hexString: "0xb2d05e00")! // 3000000000 + let gasLimit = Data(hexString: "0x526c")! // 21100 + let to = "0x6b175474e89094c44da98b954eedeac495271d0f" + let amount = Data(hexString: "0x00")! + let payload = Data(hexString: "a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1")! + // Empty `access_list`. + let accessList = EthereumRlpRlpList() + + let rlpList = EthereumRlpRlpList.with { + $0.items = [ + EthereumRlpRlpItem.with { $0.numberU256 = chainId }, + EthereumRlpRlpItem.with { $0.numberU256 = nonce }, + EthereumRlpRlpItem.with { $0.numberU256 = maxInclusionFeePerGas }, + EthereumRlpRlpItem.with { $0.numberU256 = maxFeePerGas }, + EthereumRlpRlpItem.with { $0.numberU256 = gasLimit }, + EthereumRlpRlpItem.with { $0.address = to }, + EthereumRlpRlpItem.with { $0.numberU256 = amount }, + EthereumRlpRlpItem.with { $0.data = payload }, + EthereumRlpRlpItem.with { $0.list = accessList }, + ] + } + + let encodingInput = EthereumRlpEncodingInput.with { + $0.item.list = rlpList + } + let inputData = try encodingInput.serializedData() + let outputData = EthereumRlp.encode(coin: .ethereum, input: inputData) + + let encodingOutput = try EthereumRlpEncodingOutput(serializedData: outputData) + XCTAssertEqual(encodingOutput.error, CommonSigningError.ok) + XCTAssertEqual(encodingOutput.encoded.hexString, "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0") + } +} diff --git a/tests/chains/Aion/RLPTests.cpp b/tests/chains/Aion/RLPTests.cpp index cda4a23fbbd..7ad104a9bdf 100644 --- a/tests/chains/Aion/RLPTests.cpp +++ b/tests/chains/Aion/RLPTests.cpp @@ -12,16 +12,23 @@ namespace TW::Aion::tests { using boost::multiprecision::uint128_t; +// Function helper over `RLP::prepareLong`. +Data encodeLong(const uint128_t& l) { + EthereumRlp::Proto::EncodingInput input; + *input.mutable_item() = RLP::prepareLong(l); + return Ethereum::RLP::encode(input); +} + TEST(AionRLP, EncodeLong) { - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(1))), "01"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(21000))), "825208"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(1000000))), "830f4240"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(20000000000))), "8800000004a817c800"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(9007199254740991))), "88001fffffffffffff"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(9007199254740990))), "88001ffffffffffffe"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4294967296L))), "880000000100000000"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); - EXPECT_EQ(hex(RLP::encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); + EXPECT_EQ(hex(encodeLong(uint128_t(1))), "01"); + EXPECT_EQ(hex(encodeLong(uint128_t(21000))), "825208"); + EXPECT_EQ(hex(encodeLong(uint128_t(1000000))), "830f4240"); + EXPECT_EQ(hex(encodeLong(uint128_t(20000000000))), "8800000004a817c800"); + EXPECT_EQ(hex(encodeLong(uint128_t(9007199254740991))), "88001fffffffffffff"); + EXPECT_EQ(hex(encodeLong(uint128_t(9007199254740990))), "88001ffffffffffffe"); + EXPECT_EQ(hex(encodeLong(uint128_t(4294967296L))), "880000000100000000"); + EXPECT_EQ(hex(encodeLong(uint128_t(4295000060L))), "880000000100007ffc"); + EXPECT_EQ(hex(encodeLong(uint128_t(72057594037927935L))), "8800ffffffffffffff"); } } // namespace TW::Aion::tests diff --git a/tests/chains/Ethereum/RLPTests.cpp b/tests/chains/Ethereum/RLPTests.cpp deleted file mode 100644 index e5502d58a2e..00000000000 --- a/tests/chains/Ethereum/RLPTests.cpp +++ /dev/null @@ -1,312 +0,0 @@ -// 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. - -#include "Ethereum/RLP.h" -#include "HexCoding.h" - -#include - -namespace TW::Ethereum::tests { - -using boost::multiprecision::uint256_t; - -std::string stringifyItems(const RLP::DecodedItem& di); - -std::string stringifyData(const Data& data) { - if (data.size() == 0) - return "0"; - // try if only letters - bool isLettersOnly = true; - for (auto i : data) { - if (!((i >= 'A' && i <= 'Z') || (i >= 'a' && i <= 'z') || i == ' ' || i == ',')) { - isLettersOnly = false; - break; - } - } - if (isLettersOnly) - return std::string("'") + std::string(data.begin(), data.end()) + "'"; - // try if it can be parsed (recursive) - if (data.size() >= 2) { - try { - const auto di = RLP::decode(data); - if (di.decoded.size() > 0 && di.remainder.size() == 0) { - return stringifyItems(di); - } - } catch (...) { - } - } - // any other: as hex string - return hex(data); -} - -std::string stringifyItems(const RLP::DecodedItem& di) { - const auto n = di.decoded.size(); - if (n == 0) { - return "-"; - } - if (n == 1) { - return stringifyData(di.decoded[0]); - } - std::string res = "(" + std::to_string(n) + ": "; - int count = 0; - for (auto i : di.decoded) { - if (count++) - res += " "; - res += stringifyData(i); - } - res += ")"; - return res; -} - -std::string decodeHelper(const std::string& hexData) { - const auto data = parse_hex(hexData); - const auto di = RLP::decode(data); - return stringifyItems(di); -} - -TEST(RLP, EncodeString) { - EXPECT_EQ(hex(RLP::encode("")), "80"); - EXPECT_EQ(hex(RLP::encode("d")), "64"); - EXPECT_EQ(hex(RLP::encode("dog")), "83646f67"); -} - -TEST(RLP, EncodeInteger) { - EXPECT_EQ(hex(RLP::encode(0)), "80"); - EXPECT_EQ(hex(RLP::encode(127)), "7f"); - EXPECT_EQ(hex(RLP::encode(128)), "8180"); - EXPECT_EQ(hex(RLP::encode(255)), "81ff"); - EXPECT_EQ(hex(RLP::encode(256)), "820100"); - EXPECT_EQ(hex(RLP::encode(1024)), "820400"); - EXPECT_EQ(hex(RLP::encode(0xffff)), "82ffff"); - EXPECT_EQ(hex(RLP::encode(0x010000)), "83010000"); - EXPECT_EQ(hex(RLP::encode(0xffffff)), "83ffffff"); - EXPECT_EQ(hex(RLP::encode(static_cast(0xffffffffULL))), "84ffffffff"); - EXPECT_EQ(hex(RLP::encode(static_cast(0xffffffffffffffULL))), "87ffffffffffffff"); -} - -TEST(RLP, EncodeUInt256) { - EXPECT_EQ(hex(RLP::encode(uint256_t(0))), "80"); - EXPECT_EQ(hex(RLP::encode(uint256_t(1))), "01"); - EXPECT_EQ(hex(RLP::encode(uint256_t(127))), "7f"); - EXPECT_EQ(hex(RLP::encode(uint256_t(128))), "8180"); - EXPECT_EQ(hex(RLP::encode(uint256_t(256))), "820100"); - EXPECT_EQ(hex(RLP::encode(uint256_t(1024))), "820400"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffff))), "83ffffff"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffULL))), "84ffffffff"); - EXPECT_EQ(hex(RLP::encode(uint256_t(0xffffffffffffffULL))), "87ffffffffffffff"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x102030405060708090a0b0c0d0e0f2"))), - "8f102030405060708090a0b0c0d0e0f2"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x0100020003000400050006000700080009000a000b000c000d000e01"))), - "9c0100020003000400050006000700080009000a000b000c000d000e01"); - EXPECT_EQ( - hex(RLP::encode(uint256_t("0x0100000000000000000000000000000000000000000000000000000000000000"))), - "a00100000000000000000000000000000000000000000000000000000000000000"); -} - -TEST(RLP, EncodeList) { - EXPECT_EQ(hex(RLP::encodeList(std::vector())), "c0"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{1, 2, 3})), "c3010203"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{"a", "b"})), "c26162"); - EXPECT_EQ(hex(RLP::encodeList(std::vector{"cat", "dog"})), "c88363617483646f67"); - { - const auto encoded = RLP::encodeList(std::vector(1024)); - EXPECT_EQ(hex(subData(encoded, 0, 20)), "f904008080808080808080808080808080808080"); - } -} - -TEST(RLP, EncodeListNested) { - const auto l11 = RLP::encodeList(std::vector{1, 2, 3}); - const auto l12 = RLP::encodeList(std::vector{"apple", "banana", "cherry"}); - const auto l21 = RLP::encodeList(std::vector{parse_hex("abcdef"), parse_hex("00010203040506070809")}); - const auto l22 = RLP::encodeList(std::vector{"bitcoin", "beeenbee", "eth"}); - const auto l1 = RLP::encodeList(std::vector{l11, l12}); - const auto l2 = RLP::encodeList(std::vector{l21, l22}); - const auto encoded = RLP::encodeList(std::vector{l1, l2}); - EXPECT_EQ(hex(encoded), "f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"); -} - -TEST(RLP, EncodeInvalid) { - ASSERT_TRUE(RLP::encode(-1).empty()); - ASSERT_TRUE(RLP::encodeList(std::vector{0, -1}).empty()); -} - -TEST(RLP, DecodeInteger) { - EXPECT_EQ(decodeHelper("00"), "00"); // not the primary encoding for 0 - EXPECT_EQ(decodeHelper("01"), "01"); - EXPECT_EQ(decodeHelper("09"), "09"); - EXPECT_EQ(decodeHelper("7f"), "7f"); - EXPECT_EQ(decodeHelper("80"), "0"); - EXPECT_EQ(decodeHelper("8180"), "80"); - EXPECT_EQ(decodeHelper("81ff"), "ff"); - EXPECT_EQ(decodeHelper("820100"), "0100"); - EXPECT_EQ(decodeHelper("820400"), "0400"); - EXPECT_EQ(decodeHelper("82ffff"), "ffff"); - EXPECT_EQ(decodeHelper("83010000"), "010000"); - EXPECT_EQ(decodeHelper("83ffffff"), "ffffff"); - EXPECT_EQ(decodeHelper("84ffffffff"), "ffffffff"); - EXPECT_EQ(decodeHelper("87ffffffffffffff"), "ffffffffffffff"); -} - -TEST(RLP, DecodeString) { - EXPECT_EQ(decodeHelper("80"), "0"); - EXPECT_EQ(decodeHelper("64"), "'d'"); - EXPECT_EQ(decodeHelper("83646f67"), "'dog'"); - EXPECT_EQ(decodeHelper("83636174"), "'cat'"); - EXPECT_EQ(decodeHelper("8f102030405060708090a0b0c0d0e0f2"), "102030405060708090a0b0c0d0e0f2"); - EXPECT_EQ(decodeHelper("9c0100020003000400050006000700080009000a000b000c000d000e01"), "0100020003000400050006000700080009000a000b000c000d000e01"); - EXPECT_EQ(decodeHelper("a00100000000000000000000000000000000000000000000000000000000000000"), "0100000000000000000000000000000000000000000000000000000000000000"); - // long string - EXPECT_EQ(decodeHelper("b87674686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e672c2074686973206973206120612076657279206c6f6e6720737472696e67"), - "'this is a a very long string, this is a a very long string, this is a a very long string, this is a a very long string'"); -} - -TEST(RLP, DecodeList) { - // empty list - EXPECT_EQ(decodeHelper("c0"), "-"); - // short list - EXPECT_EQ(decodeHelper("c3010203"), "(3: 01 02 03)"); - EXPECT_EQ(decodeHelper("c26162"), "(2: 'a' 'b')"); - EXPECT_EQ(decodeHelper("c88363617483646f67"), "(2: 'cat' 'dog')"); - - // long list, raw ether transfer tx - EXPECT_EQ(decodeHelper("f86b81a985051f4d5ce982520894515778891c99e3d2e7ae489980cb7c77b37b5e76861b48eb57e0008025a0ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475a00dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65"), - "(9: " - "a9 " // nonce - "051f4d5ce9 " // gas price - "5208 " // gas limit - "515778891c99e3d2e7ae489980cb7c77b37b5e76 " // to - "1b48eb57e000 " // amount - "0 " // data - "25 " // v - "ad01c32a7c974df9d0bd48c8d7e0ecab62e90811917aa7dc0c966751a0c3f475 " // r - "0dc77d9ec68484481bdf87faac14378f4f18d477f84c0810d29480372c1bbc65" // s - ")"); - - // long list, raw token transfer tx - EXPECT_EQ(decodeHelper("f8aa81d485077359400082db9194dac17f958d2ee523a2206206994597c13d831ec780b844a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf0801ca02843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6aca05d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a"), - "(9: " - "d4 " - "0773594000 " - "db91 " - "dac17f958d2ee523a2206206994597c13d831ec7 " - "0 " - "a9059cbb000000000000000000000000c6b6b55c8c4971145a842cc4e5db92d879d0b3e00000000000000000000000000000000000000000000000000000000002faf080 " - "1c " - "2843d8ed66b9623392dc336dd36d5dd5a630b2019962869b6e50fdb4ecb5b6ac " - "5d9ea377bc65e2921f7fc257de8135530cc74e3188b6ba57a4b9cb284393050a" - ")"); - - { - // long list, with 2-byte size - const std::string elem = "0123"; - const std::size_t n = 500; - std::vector longarr; - for (auto i = 0ul; i < n; ++i) - longarr.push_back(elem); - - const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(hex(subData(encoded, 0, 20)), "f909c48430313233843031323384303132338430"); - - auto decoded = RLP::decode(encoded); - ASSERT_EQ(decoded.decoded.size(), n); - for (int i = 0; i < 20; i++) { - EXPECT_EQ(hex(decoded.decoded[i]), "30313233"); - } - } - { - // long list, with 3-byte size - const std::string elem = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; - const std::size_t n = 650; - std::vector longarr; - for (auto i = 0ul; i < n; ++i) - longarr.push_back(elem); - - const Data encoded = RLP::encodeList(longarr); - ASSERT_EQ(encoded.size(), 66304ul); - ASSERT_EQ(hex(subData(encoded, 0, 30)), "fa0102fcb864303132333435363738393031323334353637383930313233"); - - auto decoded = RLP::decode(encoded); - ASSERT_EQ(decoded.decoded.size(), n); - } - - // nested list - EXPECT_EQ(decodeHelper("f8479cdb84c301020395d4856170706c658662616e616e6186636865727279a9e890cf83abcdef8a0001020304050607080996d587626974636f696e88626565656e62656583657468"), - "(2: (2: (3: 01 02 03) (3: 'apple' 'banana' 'cherry')) (2: (2: abcdef 00010203040506070809) (3: 'bitcoin' 'beeenbee' 'eth')))"); -} - -TEST(RLP, DecodeInvalid) { - // decode empty data - EXPECT_THROW(RLP::decode(Data()), std::invalid_argument); - - // incorrect length - EXPECT_THROW(RLP::decode(parse_hex("0x81636174")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("0xb9ffff")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("0xc883636174")), std::invalid_argument); - - // some tests are from https://github.com/ethereum/tests/blob/develop/RLPTests/invalidRLPTest.json - // int32 overflow - EXPECT_THROW(RLP::decode(parse_hex("0xbf0f000000000000021111")), std::invalid_argument); - - // wrong size list - EXPECT_THROW(RLP::decode(parse_hex("0xf80180")), std::invalid_argument); - - // bytes should be single byte - EXPECT_THROW(RLP::decode(parse_hex("0x8100")), std::invalid_argument); - - // leading zeros in long length list - EXPECT_THROW(RLP::decode(parse_hex("fb00000040000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f")), std::invalid_argument); - EXPECT_THROW(RLP::decode(parse_hex("f800")), std::invalid_argument); -} - -TEST(RLP, putVarInt) { - EXPECT_EQ(hex(RLP::putVarInt(0)), "00"); - EXPECT_EQ(hex(RLP::putVarInt(1)), "01"); - EXPECT_EQ(hex(RLP::putVarInt(0x21)), "21"); - EXPECT_EQ(hex(RLP::putVarInt(0xff)), "ff"); - EXPECT_EQ(hex(RLP::putVarInt(0x100)), "0100"); - EXPECT_EQ(hex(RLP::putVarInt(0x4321)), "4321"); - EXPECT_EQ(hex(RLP::putVarInt(0x654321)), "654321"); - EXPECT_EQ(hex(RLP::putVarInt(0x87654321)), "87654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xa987654321)), "a987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xcba987654321)), "cba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xedcba987654321)), "edcba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0x21edcba987654321)), "21edcba987654321"); - EXPECT_EQ(hex(RLP::putVarInt(0xffffffffffffffff)), "ffffffffffffffff"); -} - -TEST(RLP, parseVarInt) { - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("00"), 0))), "00"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("01"), 0))), "01"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("fc"), 0))), "fc"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("ff"), 0))), "ff"); - EXPECT_EQ(hex(store(RLP::parseVarInt(1, parse_hex("abcd"), 1))), "cd"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("0102"), 0))), "0102"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("0100"), 0))), "0100"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("fedc"), 0))), "fedc"); - EXPECT_EQ(hex(store(RLP::parseVarInt(2, parse_hex("ffff"), 0))), "ffff"); - EXPECT_EQ(hex(store(RLP::parseVarInt(3, parse_hex("010203"), 0))), "010203"); - EXPECT_EQ(hex(store(RLP::parseVarInt(4, parse_hex("01020304"), 0))), "01020304"); - EXPECT_EQ(hex(store(RLP::parseVarInt(5, parse_hex("0102030405"), 0))), "0102030405"); - EXPECT_EQ(hex(store(RLP::parseVarInt(6, parse_hex("010203040506"), 0))), "010203040506"); - EXPECT_EQ(hex(store(RLP::parseVarInt(7, parse_hex("01020304050607"), 0))), "01020304050607"); - EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("0102030405060708"), 0))), "0102030405060708"); - EXPECT_EQ(hex(store(RLP::parseVarInt(8, parse_hex("abcd0102030405060708"), 2))), "0102030405060708"); - EXPECT_THROW(RLP::parseVarInt(0, parse_hex("01"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(9, parse_hex("010203040506070809"), 0), std::invalid_argument); // wrong size - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("0102"), 0), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(4, parse_hex("01020304"), 2), std::invalid_argument); // too short - EXPECT_THROW(RLP::parseVarInt(2, parse_hex("0002"), 0), std::invalid_argument); // starts with 0 -} - -TEST(RLP, decodeLenOverflow) { - EXPECT_THROW(RLP::decode(parse_hex("c9bffffffffffffffff7")), std::invalid_argument); // String length overflow (64 bit) - EXPECT_THROW(RLP::decode(parse_hex("c7fbfffffffbc17f")), std::invalid_argument); // List length overflow (32 bit) - EXPECT_THROW(RLP::decode(parse_hex("cbfffffffffffffffff7c17f")), std::invalid_argument); // List length overflow (64 bit) -} - -} // namespace TW::Ethereum::tests diff --git a/tests/chains/Ethereum/TWRlpTests.cpp b/tests/chains/Ethereum/TWRlpTests.cpp new file mode 100644 index 00000000000..745f3467a59 --- /dev/null +++ b/tests/chains/Ethereum/TWRlpTests.cpp @@ -0,0 +1,51 @@ +// 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. + +#include "TrustWalletCore/TWEthereumRlp.h" +#include "proto/EthereumRlp.pb.h" +#include "HexCoding.h" +#include "TestUtilities.h" +#include "uint256.h" + +#include + +using namespace TW; + +TEST(TWEthereumRlp, Eip1559) { + auto chainId = store(10); + auto nonce = store(6); + auto maxInclusionFeePerGas = 2'000'000'000; + auto maxFeePerGas = store(3'000'000'000); + auto gasLimit = store(21'100); + const auto* to = "0x6b175474e89094c44da98b954eedeac495271d0f"; + auto amount = 0; + auto payload = parse_hex("a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1"); + + EthereumRlp::Proto::EncodingInput input; + auto* list = input.mutable_item()->mutable_list(); + + list->add_items()->set_number_u256(chainId.data(), chainId.size()); + list->add_items()->set_number_u256(nonce.data(), nonce.size()); + list->add_items()->set_number_u64(maxInclusionFeePerGas); + list->add_items()->set_number_u256(maxFeePerGas.data(), maxFeePerGas.size()); + list->add_items()->set_number_u256(gasLimit.data(), gasLimit.size()); + list->add_items()->set_address(to); + list->add_items()->set_number_u64(amount); + list->add_items()->set_data(payload.data(), payload.size()); + // Append an empty `access_list`. + list->add_items()->mutable_list(); + + auto inputData = input.SerializeAsString(); + auto inputTWData = WRAPD(TWDataCreateWithBytes((const uint8_t *)inputData.data(), inputData.size())); + auto outputTWData = WRAPD(TWEthereumRlpEncode(TWCoinTypeEthereum, inputTWData.get())); + + EthereumRlp::Proto::EncodingOutput output; + output.ParseFromArray(TWDataBytes(outputTWData.get()), static_cast(TWDataSize(outputTWData.get()))); + + EXPECT_EQ(output.error(), Common::Proto::SigningError::OK); + EXPECT_TRUE(output.error_message().empty()); + EXPECT_EQ(hex(output.encoded()), "f86c0a06847735940084b2d05e0082526c946b175474e89094c44da98b954eedeac495271d0f80b844a9059cbb0000000000000000000000005322b34c88ed0691971bf52a7047448f0f4efc840000000000000000000000000000000000000000000000000001ee0c29f50cb1c0"); +} diff --git a/tests/chains/Theta/SignerTests.cpp b/tests/chains/Theta/SignerTests.cpp index ebb037b5111..6fd92992a84 100644 --- a/tests/chains/Theta/SignerTests.cpp +++ b/tests/chains/Theta/SignerTests.cpp @@ -26,7 +26,7 @@ TEST(Signer, Sign) { ASSERT_EQ(hex(signature), "5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8" "fe9134ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501"); - ASSERT_EQ(hex(transaction.encode()), + ASSERT_EQ(hex(transaction.encodePayload()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007" diff --git a/tests/chains/Theta/TransactionTests.cpp b/tests/chains/Theta/TransactionTests.cpp index aae8c5be97b..a5dc2cd456c 100644 --- a/tests/chains/Theta/TransactionTests.cpp +++ b/tests/chains/Theta/TransactionTests.cpp @@ -12,23 +12,23 @@ namespace TW::Theta::tests { -TEST(ThetaTransaction, Encode) { +TEST(ThetaTransaction, EncodePayload) { const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); auto transaction = Transaction(from, to, 10, 20, 1); - ASSERT_EQ(hex(transaction.encode()), + ASSERT_EQ(hex(transaction.encodePayload()), "02f843c78085e8d4a51000e0df942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a51014" "0180d9d8949f1233798e905e173560071255140b4a8abd3ec6c20a14"); } -TEST(ThetaTransaction, EncodeWithSignature) { +TEST(ThetaTransaction, EncodePayloadWithSignature) { const auto from = Ethereum::Address("0x2E833968E5bB786Ae419c4d13189fB081Cc43bab"); const auto to = Ethereum::Address("0x9F1233798E905E173560071255140b4A8aBd3Ec6"); auto transaction = Transaction(from, to, 10, 20, 1); transaction.setSignature( from, parse_hex("5190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501")); - ASSERT_EQ(hex(transaction.encode()), + ASSERT_EQ(hex(transaction.encodePayload()), "02f887c78085e8d4a51000f863f861942e833968e5bb786ae419c4d13189fb081cc43babc70a85e8d4a5" "101401b8415190868498d587d074d57298f41853d0109d997f15ddf617f471eb8cbb7fff267cb8fe9134" "ccdef053ec7cabd18070325c9c436efe1abbacd14eb7561d3fc10501d9d8949f1233798e905e17356007"