From 2f3531814bb6acb7481792c0203508399ccbd865 Mon Sep 17 00:00:00 2001 From: Mostafa Date: Tue, 8 Oct 2024 00:53:16 +0800 Subject: [PATCH] define Decodable trait and implement decode --- rust/chains/tw_pactus/Cargo.toml | 1 + rust/chains/tw_pactus/src/address.rs | 44 ++-- rust/chains/tw_pactus/src/amount.rs | 10 +- .../tw_pactus/src/encode/compact_integer.rs | 130 ------------ rust/chains/tw_pactus/src/encode/decode.rs | 121 +++++++++++ rust/chains/tw_pactus/src/encode/encode.rs | 168 +++++++++++++++ rust/chains/tw_pactus/src/encode/error.rs | 14 ++ rust/chains/tw_pactus/src/encode/impls.rs | 152 -------------- rust/chains/tw_pactus/src/encode/mod.rs | 42 ++-- rust/chains/tw_pactus/src/encode/var_int.rs | 196 ++++++++++++++++++ rust/chains/tw_pactus/src/entry.rs | 7 + rust/chains/tw_pactus/src/lib.rs | 1 + rust/chains/tw_pactus/src/modules/mod.rs | 5 + .../tw_pactus/src/modules/transaction_util.rs | 35 ++++ rust/chains/tw_pactus/src/transaction.rs | 56 ++--- rust/chains/tw_solana/src/address.rs | 4 +- .../chains/tw_solana/src/modules/tx_signer.rs | 2 +- rust/chains/tw_sui/src/signature.rs | 2 +- rust/tw_keypair/src/ed25519/mod.rs | 2 +- .../modifications/cardano/extended_public.rs | 2 +- .../ed25519/modifications/waves/signature.rs | 2 +- rust/tw_keypair/src/ed25519/private.rs | 2 +- rust/tw_keypair/src/ed25519/public.rs | 13 +- rust/tw_keypair/tests/ed25519_tests.rs | 2 +- 24 files changed, 657 insertions(+), 356 deletions(-) delete mode 100644 rust/chains/tw_pactus/src/encode/compact_integer.rs create mode 100644 rust/chains/tw_pactus/src/encode/decode.rs create mode 100644 rust/chains/tw_pactus/src/encode/encode.rs create mode 100644 rust/chains/tw_pactus/src/encode/error.rs delete mode 100644 rust/chains/tw_pactus/src/encode/impls.rs create mode 100644 rust/chains/tw_pactus/src/encode/var_int.rs create mode 100644 rust/chains/tw_pactus/src/modules/mod.rs create mode 100644 rust/chains/tw_pactus/src/modules/transaction_util.rs diff --git a/rust/chains/tw_pactus/Cargo.toml b/rust/chains/tw_pactus/Cargo.toml index 1d7e651d4a3..3c7499cbd8d 100644 --- a/rust/chains/tw_pactus/Cargo.toml +++ b/rust/chains/tw_pactus/Cargo.toml @@ -11,6 +11,7 @@ tw_keypair = { path = "../../tw_keypair" } tw_memory = { path = "../../tw_memory" } tw_proto = { path = "../../tw_proto" } tw_hash = { path = "../../tw_hash" } +tw_encoding = { path = "../../tw_encoding" } [dev-dependencies] tw_encoding = { path = "../../tw_encoding" } diff --git a/rust/chains/tw_pactus/src/address.rs b/rust/chains/tw_pactus/src/address.rs index b9b5b51b4c2..3f900778416 100644 --- a/rust/chains/tw_pactus/src/address.rs +++ b/rust/chains/tw_pactus/src/address.rs @@ -2,8 +2,8 @@ // // Copyright © 2017 Trust Wallet. -use std::fmt; use std::str::FromStr; +use std::{fmt, io}; use bech32::{FromBase32, ToBase32}; use tw_coin_entry::coin_entry::CoinAddress; @@ -51,7 +51,7 @@ pub struct Address { impl Address { pub fn from_public_key(public_key: &PublicKey) -> Result { - let pud_data = public_key.to_bytes(); + let pud_data = public_key.to_h256(); let pub_hash = ripemd_160(&blake2_b(pud_data.as_ref(), 32).map_err(|_| AddressError::Internal)?); @@ -93,14 +93,14 @@ impl fmt::Display for Address { } impl encode::Encodable for Address { - fn encode(&self, stream: &mut encode::stream::Stream) { + fn encode(&self, w: &mut W) -> Result { if self.is_treasury() { - stream.append(&0u8); + &0u8.encode(w); - return; + return Ok(1); } - stream.append_raw_slice(&self.data()); + self.data().encode(w) } fn encoded_size(&self) -> usize { @@ -130,7 +130,7 @@ impl FromStr for Address { } if b32.len() != 33 { - return Err(AddressError::InvalidInput) + return Err(AddressError::InvalidInput); } let addr_type = AddressType::from(b32[0].to_u8()); @@ -150,7 +150,7 @@ impl FromStr for Address { #[cfg(test)] mod test { - use encode::{stream::{self, Stream}, Encodable}; + use encode::Encodable; use tw_encoding::hex::DecodeHex; use tw_keypair::ed25519::sha512::PrivateKey; @@ -161,22 +161,26 @@ mod test { let addr = Address::from_str(TREASURY_ADDRESS_STRING).unwrap(); assert!(addr.is_treasury()); - let mut stream = Stream::new(); - addr.encode(&mut stream); - assert_eq!( stream.out(), [0x00]); - assert_eq!( addr.encoded_size(), 1); + let mut w = Vec::new(); + addr.encode(&mut w); + assert_eq!(w.to_vec(), [0x00]); + assert_eq!(addr.encoded_size(), 1); } - #[test] fn test_address_encoding() { let addr = Address::from_str("pc1rqqqsyqcyq5rqwzqfpg9scrgwpuqqzqsr36kkra").unwrap(); assert!(!addr.is_treasury()); - let mut stream = Stream::new(); - addr.encode(&mut stream); - assert_eq!( stream.out(), "03000102030405060708090a0b0c0d0e0f00010203".decode_hex().unwrap()); - assert_eq!( addr.encoded_size(), 21); + let mut w = Vec::new(); + addr.encode(&mut w); + assert_eq!( + w.to_vec(), + "03000102030405060708090a0b0c0d0e0f00010203" + .decode_hex() + .unwrap() + ); + assert_eq!(addr.encoded_size(), 21); } #[test] @@ -238,11 +242,11 @@ mod test { ) .unwrap(); let address = Address::from_public_key(&private_key.public()).unwrap(); - let mut stream = Stream::new(); + let mut w = Vec::new(); - address.encode(&mut stream); + address.encode(&mut w); - assert_eq!(expected_data, stream.out(),); + assert_eq!(expected_data, w.to_vec(),); assert_eq!(expected_data.len(), address.encoded_size()); } } diff --git a/rust/chains/tw_pactus/src/amount.rs b/rust/chains/tw_pactus/src/amount.rs index 86b1b05c8f2..f6bd7e3aaf3 100644 --- a/rust/chains/tw_pactus/src/amount.rs +++ b/rust/chains/tw_pactus/src/amount.rs @@ -1,14 +1,16 @@ -use crate::encode::{compact_integer::CompactInteger, stream::Stream, Encodable}; +use std::io; + +use crate::encode::{var_int::VarInt, Encodable}; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Amount(pub i64); impl Encodable for Amount { - fn encode(&self, stream: &mut Stream) { - CompactInteger::from(self.0 as usize).encode(stream) + fn encode(&self, w: &mut W) -> Result { + VarInt::from(self.0 as usize).encode(w) } fn encoded_size(&self) -> usize { - CompactInteger::from(self.0 as usize).encoded_size() + VarInt::from(self.0 as usize).encoded_size() } } diff --git a/rust/chains/tw_pactus/src/encode/compact_integer.rs b/rust/chains/tw_pactus/src/encode/compact_integer.rs deleted file mode 100644 index 10c10f72495..00000000000 --- a/rust/chains/tw_pactus/src/encode/compact_integer.rs +++ /dev/null @@ -1,130 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use crate::encode::stream::Stream; -use crate::encode::Encodable; - -/// A type of variable-length integer commonly used in the Bitcoin P2P protocol and Bitcoin serialized data structures. -#[derive(Default, Debug, Clone, Copy, PartialEq)] -pub struct CompactInteger(u64); - -impl From for CompactInteger { - fn from(value: usize) -> Self { - CompactInteger(value as u64) - } -} - -impl Encodable for CompactInteger { - fn encode(&self, stream: &mut Stream) { - let mut val = self.0; - // Make sure that there is one after this - while val >= 0x80 { - let n = (val as u8 & 0x7f) | 0x80; - stream.append(&{ n }); - val >>= 7; // It should be in multiples of 7, this should just get the next part - } - - stream.append(&(val as u8)); - } - - fn encoded_size(&self) -> usize { - match self.0 { - val if val >= 0x8000000000000000 => 10, - val if val >= 0x100000000000000 => 9, - val if val >= 0x2000000000000 => 8, - val if val >= 0x40000000000 => 7, - val if val >= 0x800000000 => 6, - val if val >= 0x10000000 => 5, - val if val >= 0x200000 => 4, - val if val >= 0x4000 => 3, - val if val >= 0x80 => 2, - _ => 1, - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_compact_integer_stream() { - let mut stream = Stream::default(); - - stream - .append(&CompactInteger::from(0_usize)) - .append(&CompactInteger::from(0xfc_usize)) - .append(&CompactInteger::from(0xfd_usize)) - .append(&CompactInteger::from(0xffff_usize)) - .append(&CompactInteger::from(0x10000_usize)) - .append(&CompactInteger::from(0xffff_ffff_usize)) - .append(&CompactInteger(0x1_0000_0000_u64)); - - let expected = vec![ - 0_u8, // 0 - 0xfc, 0x1, // 0xfc - 0xfd, 0x1, // 0xfd - 0xff, 0xff, 0x3, // 0xffff - 0x80, 0x80, 0x04, // 0x10000 - 0xff, 0xff, 0xff, 0xff, 0xf, // 0xffff_ffff - 0x80, 0x80, 0x80, 0x80, 0x10, // 0x1_0000_0000 - ]; - - assert_eq!(stream.out(), expected); - } - - #[test] - fn test_write_var_int() { - let tests = vec![ - (0x0u64, vec![0x00u8]), - (0xffu64, vec![0xff, 0x01]), - (0x7fffu64, vec![0xff, 0xff, 0x01]), - (0x3fffffu64, vec![0xff, 0xff, 0xff, 0x01]), - (0x1fffffffu64, vec![0xff, 0xff, 0xff, 0xff, 0x01]), - (0xfffffffffu64, vec![0xff, 0xff, 0xff, 0xff, 0xff, 0x01]), - ( - 0x7ffffffffffu64, - vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], - ), - ( - 0x3ffffffffffffu64, - vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], - ), - ( - 0x1ffffffffffffffu64, - vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], - ), - ( - 0xffffffffffffffffu64, - vec![0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], - ), - (0x200u64, vec![0x80, 0x04]), - (0x027fu64, vec![0xff, 0x04]), - (0xff00000000u64, vec![0x80, 0x80, 0x80, 0x80, 0xf0, 0x1f]), - (0xffffffffu64, vec![0xff, 0xff, 0xff, 0xff, 0x0f]), - (0x100000000u64, vec![0x80, 0x80, 0x80, 0x80, 0x10]), - (0x7ffffffffu64, vec![0xff, 0xff, 0xff, 0xff, 0x7f]), - (0x800000000u64, vec![0x80, 0x80, 0x80, 0x80, 0x80, 0x01]), - ]; - - for (i, (input, expected_buf)) in tests.iter().enumerate() { - let mut stream = Stream::default(); - let com_int = CompactInteger(*input); - stream.append(&com_int); - - let out = stream.out(); - assert_eq!( - &out, expected_buf, - "Test case {} failed: buffer mismatch", - i - ); - assert_eq!( - com_int.encoded_size(), - out.len(), - "Test case {} failed: size mismatch", - i - ); - } - } -} diff --git a/rust/chains/tw_pactus/src/encode/decode.rs b/rust/chains/tw_pactus/src/encode/decode.rs new file mode 100644 index 00000000000..e5ca1207190 --- /dev/null +++ b/rust/chains/tw_pactus/src/encode/decode.rs @@ -0,0 +1,121 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::io; + +use tw_hash::Hash; +use tw_keypair::ed25519::{sha512::PublicKey, Signature}; + +use super::error::Error; +use crate::encode::var_int::VarInt; +use crate::encode::Decodable; + +pub(crate) fn decode_var_slice(r: &mut dyn std::io::Read) -> Result, Error> { + let len = *VarInt::decode(r)?; + let mut buf = vec![0; len as usize]; + r.read(&mut buf)?; + + Ok(buf) +} + +impl Decodable for Vec { + fn decode(r: &mut dyn std::io::Read) -> Result { + decode_var_slice(r) + } +} + +impl Decodable for String { + fn decode(r: &mut dyn std::io::Read) -> Result { + let data = decode_var_slice(r)?; + String::from_utf8(data).map_err(|_| self::Error::ParseFailed("Invalid String")) + } +} + +impl Decodable for PublicKey { + fn decode(r: &mut dyn std::io::Read) -> Result { + let data = decode_var_slice(r)?; + PublicKey::try_from(data.as_slice()) + .map_err(|_| self::Error::ParseFailed("Invalid Public Key")) + } +} + +impl Decodable for Signature { + fn decode(r: &mut dyn std::io::Read) -> Result { + let data = decode_var_slice(r)?; + Signature::try_from(data.as_slice()) + .map_err(|_| self::Error::ParseFailed("Invalid Signature")) + } +} + +impl Decodable for Hash { + fn decode(r: &mut dyn std::io::Read) -> Result { + let data = decode_var_slice(r)?; + Hash::try_from(data.as_slice()).map_err(|_| self::Error::ParseFailed("Invalid Hash")) + } +} + +macro_rules! impl_decodable_for_int { + ($int:ty, $size:literal, $write_fn:tt) => { + impl Decodable for $int { + #[inline] + fn decode(r: &mut dyn std::io::Read) -> Result { + let mut buf = [0; $size]; + r.read(&mut buf[..])?; + Ok(<$int>::from_le_bytes(buf)) + } + } + }; +} + +impl_decodable_for_int!(u8, 1, write_u8); +impl_decodable_for_int!(i32, 4, write_i32); +impl_decodable_for_int!(i64, 8, write_i64); +impl_decodable_for_int!(u16, 2, write_u16); +impl_decodable_for_int!(u32, 4, write_u32); +impl_decodable_for_int!(u64, 8, write_u64); + +#[cfg(test)] +mod tests { + use io::Cursor; + use tw_encoding::hex::DecodeHex; + + use super::*; + use crate::encode::{decode, deserialize, serialize}; + + #[test] + fn test_decode_var_slice() { + let expected = vec![1, 2, 3, 4]; + let mut cursor = Cursor::new("0401020304".decode_hex().unwrap()); + let slice = decode_var_slice(&mut cursor).unwrap(); + + assert_eq!(expected, slice); + } + + #[test] + fn test_encode_numbers() { + let data = vec![1_u8, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]; + let mut cursor = Cursor::new(data); + + assert_eq!(1u8, u8::decode(&mut cursor).unwrap()); + assert_eq!(2u16, u16::decode(&mut cursor).unwrap()); + assert_eq!(3u32, u32::decode(&mut cursor).unwrap()); + assert_eq!(4u64, u64::decode(&mut cursor).unwrap()); + } + + #[test] + fn test_decode_bytes() { + let expected = "0145".decode_hex().unwrap(); + let bytes = "020145".decode_hex().unwrap(); + + assert_eq!(expected, deserialize::>(&bytes).unwrap()); + } + + #[test] + fn test_encode_string() { + let expected = "hello".to_string(); + let bytes = "0568656c6c6f".decode_hex().unwrap(); + + assert_eq!(expected, deserialize::(&bytes).unwrap()); + } +} diff --git a/rust/chains/tw_pactus/src/encode/encode.rs b/rust/chains/tw_pactus/src/encode/encode.rs new file mode 100644 index 00000000000..ebdafe22083 --- /dev/null +++ b/rust/chains/tw_pactus/src/encode/encode.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::io; + +use byteorder::{LittleEndian, WriteBytesExt}; +use tw_hash::Hash; +use tw_keypair::ed25519::{sha512::PublicKey, Signature}; + +use crate::encode::var_int::VarInt; +use crate::encode::Encodable; + +pub(crate) fn encode_var_slice( + data: &[u8], + w: &mut W, +) -> Result { + let mut len = VarInt::from(data.len()).encode(w)?; + len += w.write(data)?; + + Ok(len) +} + +pub(crate) fn encode_size_var_slice(data: &[u8]) -> usize { + let mut len = VarInt::from(data.len()).encoded_size(); + len += data.len(); + + len +} + +impl Encodable for Vec { + fn encode(&self, w: &mut W) -> Result { + encode_var_slice(self, w) + } + + fn encoded_size(&self) -> usize { + encode_size_var_slice(self) + } +} + +impl Encodable for String { + fn encode(&self, w: &mut W) -> Result { + encode_var_slice(self.as_bytes(), w) + } + + fn encoded_size(&self) -> usize { + encode_size_var_slice(self.as_bytes()) + } +} + +impl Encodable for PublicKey { + fn encode(&self, w: &mut W) -> Result { + encode_var_slice(self.as_slice(), w) + } + + fn encoded_size(&self) -> usize { + encode_size_var_slice(self.as_slice()) + } +} + +impl Encodable for Signature { + fn encode(&self, w: &mut W) -> Result { + encode_var_slice(self.to_bytes().as_slice(), w) + } + + fn encoded_size(&self) -> usize { + encode_size_var_slice(self.to_bytes().as_slice()) + } +} + +impl Encodable for Hash { + fn encode(&self, w: &mut W) -> Result { + encode_var_slice(self.as_slice(), w) + } + + fn encoded_size(&self) -> usize { + encode_size_var_slice(self.as_slice()) + } +} + +impl Encodable for u8 { + #[inline] + fn encode(&self, w: &mut W) -> Result { + w.write_u8(*self)?; + + Ok(1) + } + + #[inline] + fn encoded_size(&self) -> usize { + 1 + } +} + +macro_rules! impl_encodable_for_int { + ($int:ty, $size:literal, $write_fn:tt) => { + impl Encodable for $int { + #[inline] + fn encode(&self, w: &mut W) -> Result { + w.$write_fn::(*self)?; + + Ok($size) + } + + #[inline] + fn encoded_size(&self) -> usize { + $size + } + } + }; +} + +impl_encodable_for_int!(i32, 4, write_i32); +impl_encodable_for_int!(i64, 8, write_i64); +impl_encodable_for_int!(u16, 2, write_u16); +impl_encodable_for_int!(u32, 4, write_u32); +impl_encodable_for_int!(u64, 8, write_u64); + +#[cfg(test)] +mod tests { + use tw_encoding::hex::DecodeHex; + + use super::*; + use crate::encode::serialize; + + + #[test] + fn test_encode_var_slice() { + let expected = "0401020304".decode_hex().unwrap(); + let slice = vec![1,2,3,4]; + let mut w = Vec::new(); + encode_var_slice(&slice, &mut w).unwrap(); + + assert_eq!(expected, w.to_vec()); + } + + + #[test] + fn test_encode_numbers() { + let mut w = Vec::new(); + + 1u8.encode(&mut w).unwrap(); + 2u16.encode(&mut w).unwrap(); + 3u32.encode(&mut w).unwrap(); + 4u64.encode(&mut w).unwrap(); + + let expected = vec![1_u8, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]; + assert_eq!(w.to_vec(), expected); + } + + #[test] + fn test_encode_bytes() { + let expected = "020145".decode_hex().unwrap(); + let bytes = "0145".decode_hex().unwrap(); + + assert_eq!(expected, serialize(&bytes).unwrap()); + assert_eq!(expected.len(), bytes.encoded_size()); + } + + #[test] + fn test_encode_string() { + let expected = "0568656c6c6f".decode_hex().unwrap(); + let msg = "hello".to_string(); + + assert_eq!(expected, serialize(&msg).unwrap()); + assert_eq!(expected.len(), msg.encoded_size()); + } +} diff --git a/rust/chains/tw_pactus/src/encode/error.rs b/rust/chains/tw_pactus/src/encode/error.rs new file mode 100644 index 00000000000..3de70cdf8fd --- /dev/null +++ b/rust/chains/tw_pactus/src/encode/error.rs @@ -0,0 +1,14 @@ +use std::io; + +/// Errors encountered when encoding or decoding data. +#[derive(Debug)] +pub enum Error { + IoError(std::io::Error), + ParseFailed(&'static str), +} + +impl From for Error { + fn from(err: std::io::Error) -> Self { + Error::IoError(err) + } +} diff --git a/rust/chains/tw_pactus/src/encode/impls.rs b/rust/chains/tw_pactus/src/encode/impls.rs deleted file mode 100644 index 6ecc2f9b0fb..00000000000 --- a/rust/chains/tw_pactus/src/encode/impls.rs +++ /dev/null @@ -1,152 +0,0 @@ -// SPDX-License-Identifier: Apache-2.0 -// -// Copyright © 2017 Trust Wallet. - -use byteorder::{LittleEndian, WriteBytesExt}; -use tw_hash::Hash; -use tw_keypair::ed25519::{sha512::PublicKey, Signature}; - -use crate::encode::compact_integer::CompactInteger; -use crate::encode::stream::Stream; -use crate::encode::Encodable; - -impl Encodable for Vec { - fn encode(&self, stream: &mut Stream) { - stream - .append(&CompactInteger::from(self.len())) - .append_raw_slice(self.as_ref()); - } - - fn encoded_size(&self) -> usize { - CompactInteger::from(self.len()).encoded_size() + self.len() - } -} - -impl Encodable for String { - fn encode(&self, stream: &mut Stream) { - stream - .append(&CompactInteger::from(self.len())) - .append_raw_slice(self.as_bytes()); - } - - fn encoded_size(&self) -> usize { - CompactInteger::from(self.len()).encoded_size() + self.len() - } -} - -impl Encodable for PublicKey { - fn encode(&self, stream: &mut Stream) { - self.to_bytes().encode(stream); - } - - fn encoded_size(&self) -> usize { - self.to_bytes().encoded_size() - } -} - -impl Encodable for Signature { - fn encode(&self, stream: &mut Stream) { - self.to_bytes().encode(stream); - } - - fn encoded_size(&self) -> usize { - self.to_bytes().encoded_size() - } -} - -impl Encodable for Hash { - #[inline] - fn encode(&self, stream: &mut Stream) { - stream.append_raw_slice(self.as_slice()); - } - - #[inline] - fn encoded_size(&self) -> usize { - N - } -} - -impl Encodable for u8 { - #[inline] - fn encode(&self, s: &mut Stream) { - s.write_u8(*self).unwrap(); - } - - #[inline] - fn encoded_size(&self) -> usize { - 1 - } -} - -macro_rules! impl_encodable_for_int { - ($int:ty, $size:literal, $write_fn:tt) => { - impl Encodable for $int { - #[inline] - fn encode(&self, s: &mut Stream) { - s.$write_fn::(*self).unwrap(); - } - - #[inline] - fn encoded_size(&self) -> usize { - $size - } - } - }; -} - -impl_encodable_for_int!(i32, 4, write_i32); -impl_encodable_for_int!(i64, 8, write_i64); -impl_encodable_for_int!(u16, 2, write_u16); -impl_encodable_for_int!(u32, 4, write_u32); -impl_encodable_for_int!(u64, 8, write_u64); - -#[cfg(test)] -mod tests { - use tw_encoding::hex::DecodeHex; - - use super::*; - use crate::encode::encode; - - #[test] - fn test_stream_append() { - let mut stream = Stream::default(); - - stream - .append(&1u8) - .append(&2u16) - .append(&3u32) - .append(&4u64); - - let expected = vec![1_u8, 2, 0, 3, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0]; - assert_eq!(stream.out(), expected); - } - - #[test] - fn test_bytes_serialize() { - let expected = "020145".decode_hex().unwrap(); - let bytes = "0145".decode_hex().unwrap(); - - assert_eq!(expected, encode(&bytes)); - assert_eq!(expected.len(), bytes.encoded_size()); - } - - #[test] - fn test_steam_append_slice() { - let expected = "64000000".decode_hex().unwrap(); - let mut slice = [0u8; 4]; - slice[0] = 0x64; - let mut stream = Stream::default(); - stream.append_raw_slice(&slice); - - assert_eq!(expected, stream.out()); - } - - #[test] - fn test_encode_string() { - let expected = "0568656c6c6f".decode_hex().unwrap(); - let msg = "hello".to_string(); - - assert_eq!(expected, encode(&msg)); - assert_eq!(expected.len(), msg.encoded_size()); - } -} diff --git a/rust/chains/tw_pactus/src/encode/mod.rs b/rust/chains/tw_pactus/src/encode/mod.rs index 405f808d9e0..3f4491fea50 100644 --- a/rust/chains/tw_pactus/src/encode/mod.rs +++ b/rust/chains/tw_pactus/src/encode/mod.rs @@ -2,27 +2,43 @@ // // Copyright © 2017 Trust Wallet. +use std::io::{self, Cursor}; + +use error::Error; use tw_memory::Data; -use crate::encode::stream::Stream; +// use crate::encode::stream::Stream; -pub mod compact_integer; -pub mod impls; -pub mod stream; +pub mod var_int; +pub mod encode; +pub mod decode; +pub mod error; -pub fn encode(t: &T) -> Data -where - T: Encodable, +pub fn serialize(t: &T) -> Result { - let mut stream = Stream::default(); - stream.append(t); - stream.out() + let mut writer = Vec::with_capacity(t.encoded_size()); + t.encode(&mut writer)?; + + Ok(writer.to_vec()) } +pub fn deserialize(data: &[u8]) -> Result +{ + let mut cursor = Cursor::new(data); + T::decode(&mut cursor) +} + +/// Trait for encoding an object into a consistent byte sequence. pub trait Encodable { - /// Serialize the struct and appends it to the end of stream. - fn encode(&self, stream: &mut Stream); + /// Encode the object in consistent and deterministic way. + fn encode(&self, w: &mut W) -> Result; - /// Hint about the size of serialized struct. + /// Determine the size of serialized object. fn encoded_size(&self) -> usize; } + +/// Trait for decoding an object from a byte sequence. +pub trait Decodable: Sized { + /// Decode the object in consistent and deterministic way. + fn decode(r: &mut dyn std::io::Read) -> Result; +} diff --git a/rust/chains/tw_pactus/src/encode/var_int.rs b/rust/chains/tw_pactus/src/encode/var_int.rs new file mode 100644 index 00000000000..d02fb140523 --- /dev/null +++ b/rust/chains/tw_pactus/src/encode/var_int.rs @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use std::{io, ops::Deref}; + +use byteorder::ReadBytesExt; + +use super::{error::Error, Decodable}; +use crate::encode::Encodable; + +/// A type of variable-length integer commonly used in the Bitcoin P2P protocol and Bitcoin serialized data structures. +#[derive(Default, Debug, Clone, Copy, PartialEq)] +pub struct VarInt(u64); + +impl From for VarInt { + fn from(value: usize) -> Self { + VarInt(value as u64) + } +} + +impl Deref for VarInt { + type Target = u64; + + fn deref(&self) -> &u64 { + &self.0 + } +} + +impl Encodable for VarInt { + fn encode(&self, w: &mut W) -> Result { + let mut val = self.0; + let mut len = 0; + // Make sure that there is one after this + while val >= 0x80 { + let n = (val as u8 & 0x7f) | 0x80; + len += w.write(&[n])?; + val >>= 7; // It should be in multiples of 7, this should just get the next part + } + + len += w.write(&[val as u8])?; + + Ok(len) + } + + fn encoded_size(&self) -> usize { + match self.0 { + val if val >= 0x8000000000000000 => 10, + val if val >= 0x100000000000000 => 9, + val if val >= 0x2000000000000 => 8, + val if val >= 0x40000000000 => 7, + val if val >= 0x800000000 => 6, + val if val >= 0x10000000 => 5, + val if val >= 0x200000 => 4, + val if val >= 0x4000 => 3, + val if val >= 0x80 => 2, + _ => 1, + } + } +} + +impl Decodable for VarInt { + fn decode(r: &mut dyn std::io::Read) -> Result { + let mut res: Vec = vec![]; + loop { + let n = r.read_u8()?; + // Zero in any position other than the first is invalid + // since it is not the shortest encoding. + if n == 0 && !res.is_empty() { + return Err(Error::ParseFailed("VarInt has a zero in a position other than the first. This is not the shortest encoding.")); + } + res.push(n & 0b0111_1111); + if n & 0b1000_0000 == 0 { + break; + } + } + let mut int = 0u64; + res.reverse(); + let (last, arr) = res.split_last().unwrap(); + for bits in arr { + int |= *bits as u64; + int = if int.leading_zeros() >= 7 { + int << 7 + } else { + return Err(Error::ParseFailed("VarInt overflows u64")); + }; + } + int |= *last as u64; + Ok(VarInt(int)) + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::encode::{self, deserialize}; + + #[test] + fn test_var_int_encode_data() { + let mut w = Vec::new(); + + VarInt::from(0_usize).encode(&mut w).unwrap(); + VarInt::from(0xfc_usize).encode(&mut w).unwrap(); + VarInt::from(0xfd_usize).encode(&mut w).unwrap(); + VarInt::from(0xffff_usize).encode(&mut w).unwrap(); + VarInt::from(0x10000_usize).encode(&mut w).unwrap(); + VarInt::from(0xffff_ffff_usize).encode(&mut w).unwrap(); + VarInt(0x1_0000_0000_u64).encode(&mut w).unwrap(); + + let expected = vec![ + 0_u8, // 0 + 0xfc, 0x1, // 0xfc + 0xfd, 0x1, // 0xfd + 0xff, 0xff, 0x3, // 0xffff + 0x80, 0x80, 0x04, // 0x10000 + 0xff, 0xff, 0xff, 0xff, 0xf, // 0xffff_ffff + 0x80, 0x80, 0x80, 0x80, 0x10, // 0x1_0000_0000 + ]; + + assert_eq!(w.to_vec(), expected); + } + + // Define the common test cases as a constant + const VARINT_TEST_CASES: &[(u64, &[u8])] = &[ + (0x0u64, &[0x00u8]), + (0xffu64, &[0xff, 0x01]), + (0x7fffu64, &[0xff, 0xff, 0x01]), + (0x3fffffu64, &[0xff, 0xff, 0xff, 0x01]), + (0x1fffffffu64, &[0xff, 0xff, 0xff, 0xff, 0x01]), + (0xfffffffffu64, &[0xff, 0xff, 0xff, 0xff, 0xff, 0x01]), + ( + 0x7ffffffffffu64, + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], + ), + ( + 0x3ffffffffffffu64, + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], + ), + ( + 0x1ffffffffffffffu64, + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], + ), + ( + 0xffffffffffffffffu64, + &[0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01], + ), + (0x200u64, &[0x80, 0x04]), + (0x027fu64, &[0xff, 0x04]), + (0xff00000000u64, &[0x80, 0x80, 0x80, 0x80, 0xf0, 0x1f]), + (0xffffffffu64, &[0xff, 0xff, 0xff, 0xff, 0x0f]), + (0x100000000u64, &[0x80, 0x80, 0x80, 0x80, 0x10]), + (0x7ffffffffu64, &[0xff, 0xff, 0xff, 0xff, 0x7f]), + (0x800000000u64, &[0x80, 0x80, 0x80, 0x80, 0x80, 0x01]), + ]; + + #[test] + fn test_var_int_encode() { + for (i, (value, encoded)) in VARINT_TEST_CASES.iter().enumerate() { + let mut w = Vec::new(); + let var_int = VarInt(*value); + let encoded_size = var_int.encoded_size(); + let len = var_int.encode(&mut w).unwrap(); + let out = w.as_slice(); + + assert_eq!(out, *encoded, "Test {i} failed: data mismatch"); + assert_eq!(len, out.len(), "Test {i} failed: encoded size mismatch"); + assert_eq!(len, encoded_size, "Test {i} failed: data size mismatch",); + } + } + + #[test] + fn test_var_int_decode() { + for (i, (value, data)) in VARINT_TEST_CASES.iter().enumerate() { + let var_int = deserialize::(*data).unwrap(); + + assert_eq!(*value, *var_int, "Test {i} failed: value mismatch"); + } + } + + #[test] + fn test_var_int_parse_error() { + // varint must be shortest encoding + let res = deserialize::(&[0x98, 0]); + assert!(matches!(res.unwrap_err(), Error::ParseFailed(_))); + + // If the last number is not a 0, it will error with an IO error (UnexpectedEof) + let res = deserialize::(&[0xff; 1]); + assert!(matches!(res.unwrap_err(), Error::IoError(_))); + + let res = deserialize::(&[ + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1u8, + ]); + assert!(matches!(res.unwrap_err(), Error::ParseFailed(_))); + } +} diff --git a/rust/chains/tw_pactus/src/entry.rs b/rust/chains/tw_pactus/src/entry.rs index 477da03311e..54eaab3f141 100644 --- a/rust/chains/tw_pactus/src/entry.rs +++ b/rust/chains/tw_pactus/src/entry.rs @@ -20,6 +20,7 @@ use tw_proto::TxCompiler::Proto as CompilerProto; use crate::address::Address; use crate::compiler::PactusCompiler; +use crate::modules::transaction_util::PactusTransactionUtil; use crate::signer::PactusSigner; pub struct PactusEntry; @@ -30,6 +31,7 @@ impl CoinEntry for PactusEntry { type SigningInput<'a> = Proto::SigningInput<'a>; type SigningOutput = Proto::SigningOutput<'static>; type PreSigningOutput = CompilerProto::PreSigningOutput<'static>; + type TransactionUtil = PactusTransactionUtil; // Optional modules: type JsonSigner = NoJsonSigner; @@ -95,4 +97,9 @@ impl CoinEntry for PactusEntry { ) -> Self::SigningOutput { PactusCompiler::compile(coin, input, signatures, public_keys) } + + #[inline] + fn transaction_util(&self) -> Option { + Some(PactusTransactionUtil) + } } diff --git a/rust/chains/tw_pactus/src/lib.rs b/rust/chains/tw_pactus/src/lib.rs index 5bc5103a054..4a89098b148 100644 --- a/rust/chains/tw_pactus/src/lib.rs +++ b/rust/chains/tw_pactus/src/lib.rs @@ -7,5 +7,6 @@ pub mod amount; pub mod compiler; pub mod encode; pub mod entry; +pub mod modules; pub mod signer; pub mod transaction; diff --git a/rust/chains/tw_pactus/src/modules/mod.rs b/rust/chains/tw_pactus/src/modules/mod.rs new file mode 100644 index 00000000000..9fb41d7845f --- /dev/null +++ b/rust/chains/tw_pactus/src/modules/mod.rs @@ -0,0 +1,5 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +pub mod transaction_util; \ No newline at end of file diff --git a/rust/chains/tw_pactus/src/modules/transaction_util.rs b/rust/chains/tw_pactus/src/modules/transaction_util.rs new file mode 100644 index 00000000000..c6c65a48cc6 --- /dev/null +++ b/rust/chains/tw_pactus/src/modules/transaction_util.rs @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: Apache-2.0 +// +// Copyright © 2017 Trust Wallet. + +use tw_coin_entry::coin_context::CoinContext; +use tw_coin_entry::error::prelude::*; +use tw_coin_entry::modules::transaction_util::TransactionUtil; +use tw_encoding::hex; +use tw_hash::sha3::sha3_256; + +use crate::transaction::Transaction; + +pub struct PactusTransactionUtil; + +impl TransactionUtil for PactusTransactionUtil { + fn calc_tx_hash(&self, coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + Self::calc_tx_hash_impl(coin, encoded_tx) + } +} + +impl PactusTransactionUtil { + fn calc_tx_hash_impl(_coin: &dyn CoinContext, encoded_tx: &str) -> SigningResult { + let txn_bytes = hex::decode(encoded_tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + todo!() + // // Deserialize the transaction + // let tx: Transaction = deserialize(&tx).map_err(|_| SigningErrorType::Error_input_parse)?; + + // // Calculate the transaction ID + // let txid = tx.txid(); + + // // Note: to_string() returns the reversed byte order, which is the RPC format + // Ok(txid.to_string()) + } +} diff --git a/rust/chains/tw_pactus/src/transaction.rs b/rust/chains/tw_pactus/src/transaction.rs index 8f0057109f4..13028220994 100644 --- a/rust/chains/tw_pactus/src/transaction.rs +++ b/rust/chains/tw_pactus/src/transaction.rs @@ -1,4 +1,5 @@ use std::fmt::Debug; +use std::io; use std::str::FromStr; use tw_coin_entry::error::prelude::{SigningError, SigningErrorType, SigningResult}; @@ -10,7 +11,6 @@ use tw_proto::Pactus; use crate::address::Address; use crate::amount::Amount; -use crate::encode::stream::Stream; use crate::encode::Encodable; const VERSION_LATEST: u8 = 1; @@ -25,8 +25,8 @@ pub enum PayloadType { } impl Encodable for PayloadType { - fn encode(&self, stream: &mut Stream) { - (self.clone() as u8).encode(stream) + fn encode(&self, w: &mut W) -> Result { + (self.clone() as u8).encode(w) } fn encoded_size(&self) -> usize { @@ -35,7 +35,7 @@ impl Encodable for PayloadType { } pub trait Payload: Debug { - fn encode(&self, stream: &mut Stream); + fn encode(&self, w: &mut dyn std::io::Write) -> Result; fn encoded_size(&self) -> usize; fn signer(&self) -> &Address; fn value(&self) -> Amount; @@ -49,11 +49,15 @@ pub struct TransferPayload { amount: Amount, } + + impl Payload for TransferPayload { - fn encode(&self, stream: &mut Stream) { - self.sender.encode(stream); - self.receiver.encode(stream); - self.amount.encode(stream); + fn encode(&self, w: &mut dyn std::io::Write) -> Result { + let mut len = self.sender.encode(w)?; + len += self.receiver.encode(w)?; + len += self.amount.encode(w)?; + + Ok(len) } fn encoded_size(&self) -> usize { @@ -135,6 +139,10 @@ impl Transaction { } } + pub fn from_bytes(input: &[u8]) -> SigningResult { + todo!() + } + pub fn sign(&mut self, private_key: &PrivateKey) -> SigningResult<()> { let sign_bytes = self.sign_bytes(); let signature = private_key.sign(sign_bytes)?; @@ -154,33 +162,33 @@ impl Transaction { } pub fn to_bytes(&self) -> Vec { - let mut stream = Stream::new(); + let mut w = Vec::new(); - self.flags.encode(&mut stream); - stream.append_raw_slice(&self.sign_bytes()); + self.flags.encode(&mut w); + self.sign_bytes().encode(&mut w); if let Some(signature) = &self.signature { - signature.encode(&mut stream) + signature.encode(&mut w); } if let Some(public_key) = &self.public_key { - public_key.encode(&mut stream) + public_key.encode(&mut w); } - stream.out() + w.to_vec() } fn sign_bytes(&self) -> Vec { - let mut stream = Stream::new(); + let mut w = Vec::new(); - self.version.encode(&mut stream); - self.lock_time.encode(&mut stream); - self.fee.encode(&mut stream); - self.memo.encode(&mut stream); - self.payload.payload_type().encode(&mut stream); - self.payload.encode(&mut stream); + self.version.encode(&mut w); + self.lock_time.encode(&mut w); + self.fee.encode(&mut w); + self.memo.encode(&mut w); + self.payload.payload_type().encode(&mut w); + self.payload.encode(&mut w); - stream.out() + w.to_vec() } } @@ -194,11 +202,11 @@ mod tests { #[test] fn test_payload_type_encoding() { - let mut stream = Stream::new(); + let mut stream = Vec::new(); let payload = PayloadType::Unbond; payload.encode(&mut stream); - assert_eq!(stream.out(), &[4]); + assert_eq!(stream.to_vec(), &[4]); } #[test] diff --git a/rust/chains/tw_solana/src/address.rs b/rust/chains/tw_solana/src/address.rs index cb2051be84c..f14e10cf140 100644 --- a/rust/chains/tw_solana/src/address.rs +++ b/rust/chains/tw_solana/src/address.rs @@ -28,13 +28,13 @@ impl SolanaAddress { let bytes = public_key .to_ed25519() .ok_or(AddressError::PublicKeyTypeMismatch)? - .to_bytes(); + .to_h256(); Ok(SolanaAddress { bytes }) } pub fn with_public_key_ed25519(public_key: &ed25519::sha512::PublicKey) -> SolanaAddress { SolanaAddress { - bytes: public_key.to_bytes(), + bytes: public_key.to_h256(), } } diff --git a/rust/chains/tw_solana/src/modules/tx_signer.rs b/rust/chains/tw_solana/src/modules/tx_signer.rs index 5fb421e6876..bf252df629a 100644 --- a/rust/chains/tw_solana/src/modules/tx_signer.rs +++ b/rust/chains/tw_solana/src/modules/tx_signer.rs @@ -29,7 +29,7 @@ impl TxSigner { // Sign the message with all given private keys. for private_key in keys { let signing_pubkey = - SolanaAddress::with_public_key_bytes(private_key.public().to_bytes()); + SolanaAddress::with_public_key_bytes(private_key.public().to_h256()); let ed25519_signature = private_key.sign(message_encoded.clone())?; key_signs.insert(signing_pubkey, ed25519_signature); diff --git a/rust/chains/tw_sui/src/signature.rs b/rust/chains/tw_sui/src/signature.rs index e03aacad05f..692278eb20d 100644 --- a/rust/chains/tw_sui/src/signature.rs +++ b/rust/chains/tw_sui/src/signature.rs @@ -27,7 +27,7 @@ impl SuiSignatureInfo { SuiSignatureInfo { scheme: SignatureScheme::ED25519, signature: signature.to_bytes(), - public_key: public_key.to_bytes(), + public_key: public_key.to_h256(), } } diff --git a/rust/tw_keypair/src/ed25519/mod.rs b/rust/tw_keypair/src/ed25519/mod.rs index 3c34d4ff6ec..914c27a3acf 100644 --- a/rust/tw_keypair/src/ed25519/mod.rs +++ b/rust/tw_keypair/src/ed25519/mod.rs @@ -86,7 +86,7 @@ mod tests { let expected = H256::from("b689ab808542e13f3d2ec56fe1efe43a1660dcadc73ce489fde7df98dd8ce5d9"); - assert_eq!(public.to_bytes(), expected); + assert_eq!(public.to_h256(), expected); } #[test] diff --git a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs index d3d82dba764..dcb3d6e813d 100644 --- a/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs +++ b/rust/tw_keypair/src/ed25519/modifications/cardano/extended_public.rs @@ -35,7 +35,7 @@ impl ExtendedPublicKey { /// Returns a public key bytes (32 length) that is used in signing. pub(crate) fn key_for_signing(&self) -> H256 { - self.key.public.to_bytes() + self.key.public.to_h256() } } diff --git a/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs b/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs index 39613316940..7ddc046f077 100644 --- a/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs +++ b/rust/tw_keypair/src/ed25519/modifications/waves/signature.rs @@ -28,7 +28,7 @@ impl Signature { standard_pubkey: &StandardPublicKey, standard_sign: &StandardSignature, ) -> Signature { - let pubkey_bytes: H256 = standard_pubkey.to_bytes(); + let pubkey_bytes: H256 = standard_pubkey.to_h256(); let sign_bit = pubkey_bytes[31] & PUBKEY_SIGN_MASK; let mut bytes = standard_sign.to_bytes(); diff --git a/rust/tw_keypair/src/ed25519/private.rs b/rust/tw_keypair/src/ed25519/private.rs index 984392393a6..4a7745266ad 100644 --- a/rust/tw_keypair/src/ed25519/private.rs +++ b/rust/tw_keypair/src/ed25519/private.rs @@ -44,7 +44,7 @@ impl PrivateKey { message: &[u8], ) -> KeyPairResult { self.expanded_key - .sign_with_pubkey(public.to_bytes(), message) + .sign_with_pubkey(public.to_h256(), message) } } diff --git a/rust/tw_keypair/src/ed25519/public.rs b/rust/tw_keypair/src/ed25519/public.rs index edee119dd0e..b3fcd80503b 100644 --- a/rust/tw_keypair/src/ed25519/public.rs +++ b/rust/tw_keypair/src/ed25519/public.rs @@ -66,12 +66,17 @@ impl PublicKey { } } - /// Returns the raw data of the public key (32 bytes). - pub fn to_bytes(&self) -> H256 { - H256::from(self.compressed.to_bytes()) + /// Converts the public key to its raw H256 representation. + pub fn to_h256(&self) -> H256 { + H256::from(self.to_bytes()) } - /// Returns the raw data of the data of the public key. + /// Converts the public key to a 32-byte array. + pub fn to_bytes(&self) -> [u8; 32] { + self.compressed.to_bytes() + } + + /// Returns the public key as a byte slice. pub fn as_slice(&self) -> &[u8] { self.compressed.as_bytes() } diff --git a/rust/tw_keypair/tests/ed25519_tests.rs b/rust/tw_keypair/tests/ed25519_tests.rs index 57dbe81c2e4..5ed90b8faf9 100644 --- a/rust/tw_keypair/tests/ed25519_tests.rs +++ b/rust/tw_keypair/tests/ed25519_tests.rs @@ -55,6 +55,6 @@ fn test_ed25519_priv_to_pub() { ); let public = keypair.public(); - assert_eq!(public.to_bytes(), test.public); + assert_eq!(public.to_h256(), test.public); } }