From ef05c3a1df3d4c365e2d9211d05b14d40a1faa10 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Fri, 24 May 2024 14:48:16 +0200 Subject: [PATCH 01/30] Add a macro for defining custom error types This will be useful when I go through and add descriptive errors to everything everywhere --- src/lib.rs | 3 +++ src/macros.rs | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/macros.rs diff --git a/src/lib.rs b/src/lib.rs index 99760c1..3404532 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,6 @@ +#[macro_use] +mod macros; + pub mod constants; pub mod crypto; pub mod primitives; diff --git a/src/macros.rs b/src/macros.rs new file mode 100644 index 0000000..f964310 --- /dev/null +++ b/src/macros.rs @@ -0,0 +1,54 @@ +/// Generate an enum to be used as an error type. +/// +/// This will automatically generate the enum, but also implement `Display` with given format +/// strings and optionally indicate the source error for errors which wrap another error. +/// +/// Example usage: +/// ```ignore +/// make_error_type!(pub MyError { +/// Unknown; "Unknown error", +/// IncorrectLength(length: usize); "Incorrect length {length}, expected 123", +/// InvalidHexData(source: hex::FromHexError); "Cannot convert hex string: {source}"; source, +/// }); +/// ``` +macro_rules! make_error_type { + (@fmt_args $tname:ident) => { Self::$tname }; + (@fmt_args $tname:ident ( $($targn:ident),+ )) => { Self::$tname($($targn),+) }; + + (@fmt_source) => { None }; + (@fmt_source $sourcen:expr) => { Some($sourcen) }; + + ($(#[derive( $($derive:ident),+ )])? $vis:vis $name:ident { + $( $tname:ident $(( $($targn:ident : $targ:ty),+ ))? ; $tmsg:literal $(; $sourcen:expr )?),+ $(,)? + }) => { + $( #[derive( $($derive),+ )] )? + #[derive(Clone, std::fmt::Debug)] + $vis enum $name { + $( $tname $(( $($targ),+ ))? ),+ + } + + impl std::error::Error for $name { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + $( + make_error_type!(@fmt_args $tname $(( $($targn),+ ))?) + => + make_error_type!(@fmt_source $($sourcen)?) + ),+ + } + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + $( + make_error_type!(@fmt_args $tname $(( $($targn),+ ))?) + => + write!(_f, $tmsg) + ),+ + } + } + } + }; +} From 1831f38c3e4bb8012efa6480acf98f811f6ab879 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 6 Jun 2024 12:02:26 +0200 Subject: [PATCH 02/30] support errors with per-variant attributes --- src/macros.rs | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index f964310..799d8ba 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -18,13 +18,24 @@ macro_rules! make_error_type { (@fmt_source) => { None }; (@fmt_source $sourcen:expr) => { Some($sourcen) }; - ($(#[derive( $($derive:ident),+ )])? $vis:vis $name:ident { - $( $tname:ident $(( $($targn:ident : $targ:ty),+ ))? ; $tmsg:literal $(; $sourcen:expr )?),+ $(,)? - }) => { - $( #[derive( $($derive),+ )] )? - #[derive(Clone, std::fmt::Debug)] + ( + $( #[$attr:meta] )* + $vis:vis enum $name:ident { + $( $( + #[$tattr:meta] )* + $tname:ident $(( $($targn:ident : $targ:ty),+ ))? + ; $tmsg:literal + $( ; $sourcen:expr )? + ),+ $(,)? + } + ) => { + $( #[$attr] )* + #[derive(::std::fmt::Debug)] $vis enum $name { - $( $tname $(( $($targ),+ ))? ),+ + $( + $( #[$tattr] )* + $tname $(( $($targ),+ ))? + ),+ } impl std::error::Error for $name { From 1101dd42f2d694af3d86537ee981ac69625a8961 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Fri, 21 Jun 2024 14:40:59 +0200 Subject: [PATCH 03/30] macros: make_error_type supports struct-like enums as well as tuple enums --- src/macros.rs | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/src/macros.rs b/src/macros.rs index 799d8ba..76ff100 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -5,16 +5,13 @@ /// /// Example usage: /// ```ignore -/// make_error_type!(pub MyError { +/// make_error_type!(pub enum MyError { /// Unknown; "Unknown error", /// IncorrectLength(length: usize); "Incorrect length {length}, expected 123", /// InvalidHexData(source: hex::FromHexError); "Cannot convert hex string: {source}"; source, /// }); /// ``` macro_rules! make_error_type { - (@fmt_args $tname:ident) => { Self::$tname }; - (@fmt_args $tname:ident ( $($targn:ident),+ )) => { Self::$tname($($targn),+) }; - (@fmt_source) => { None }; (@fmt_source $sourcen:expr) => { Some($sourcen) }; @@ -23,8 +20,10 @@ macro_rules! make_error_type { $vis:vis enum $name:ident { $( $( #[$tattr:meta] )* - $tname:ident $(( $($targn:ident : $targ:ty),+ ))? - ; $tmsg:literal + $tname:ident + $(( $( $( #[$t_tuple_arg_attr:meta] )* $t_tuple_arg_name:ident : $t_tuple_arg_ty:ty),+ $(,)? ))? + $({ $( $( #[$t_struct_arg_attr:meta] )* $t_struct_arg_name:ident : $t_struct_arg_ty:ty),+ $(,)? })? + ; $tmsg:literal $(( $($tmsgarg:expr),* ))? $( ; $sourcen:expr )? ),+ $(,)? } @@ -34,15 +33,20 @@ macro_rules! make_error_type { $vis enum $name { $( $( #[$tattr] )* - $tname $(( $($targ),+ ))? + $tname + $(( $( $( #[$t_tuple_arg_attr] )* $t_tuple_arg_ty),+ ))? + $({ $( $( #[$t_struct_arg_attr] )* $t_struct_arg_name : $t_struct_arg_ty),+ })? ),+ } impl std::error::Error for $name { + #[allow(unused_variables)] fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match self { $( - make_error_type!(@fmt_args $tname $(( $($targn),+ ))?) + Self::$tname + $(( $($t_tuple_arg_name),+ ))? + $({ $($t_struct_arg_name),+ })? => make_error_type!(@fmt_source $($sourcen)?) ),+ @@ -54,9 +58,11 @@ macro_rules! make_error_type { fn fmt(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { $( - make_error_type!(@fmt_args $tname $(( $($targn),+ ))?) + Self::$tname + $(( $($t_tuple_arg_name),+ ))? + $({ $($t_struct_arg_name),+ })? => - write!(_f, $tmsg) + write!(_f, $tmsg $($(, $tmsgarg)*)? ) ),+ } } From 15bf1b7153cd9b1aff6db17f66218f3c6744cbb4 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Wed, 26 Jun 2024 10:25:45 +0200 Subject: [PATCH 04/30] add traits for generating placeholder values for use in tests --- src/utils/mod.rs | 65 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index adb6987..5c0840b 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -51,3 +51,68 @@ pub fn add_btreemap( }); m1 } + +/// A trait which indicates that it is possible to acquire a "placeholder" value +/// of a type, which can be used for test purposes. +#[cfg(test)] +pub trait Placeholder : Sized { + /// Gets a placeholder value of this type which can be used for test purposes. + fn placeholder() -> Self; + + /// Gets an array of placeholder values of this type which can be used for test purposes. + fn placeholder_array() -> [Self; N] { + core::array::from_fn(|_| Self::placeholder()) + } +} + +/// A trait which indicates that it is possible to acquire a "placeholder" value +/// of a type, which can be used for test purposes. These placeholder values are consistent +/// across program runs. +#[cfg(test)] +pub trait PlaceholderIndexed : Sized { + /// Gets a dummy valid of this type which can be used for test purposes. + /// + /// This allows acquiring multiple distinct placeholder values which are still consistent + /// between runs. + /// + /// ### Arguments + /// + /// * `index` - the index of the placeholder value to obtain. Two placeholder values generated + /// from the same index are guaranteed to be equal (even across multiple test runs, + /// so long as the value format doesn't change). + fn placeholder_indexed(index: u64) -> Self; + + /// Gets an array of placeholder values of this type which can be used for test purposes. + fn placeholder_array_indexed(base_index: u64) -> [Self; N] { + core::array::from_fn(|n| Self::placeholder_indexed(base_index.wrapping_add(n as u64))) + } +} + +#[cfg(test)] +impl Placeholder for T { + fn placeholder() -> Self { + Self::placeholder_indexed(0) + } +} + +/// Generates the given number of pseudorandom bytes based on the given seed. +/// +/// This is intended to be used in tests, where random but reproducible placeholder values are often +/// required. +/// +/// ### Arguments +/// +/// * `seed_parts` - the parts of the seed, which will be concatenated to form the RNG seed +#[cfg(test)] +pub fn placeholder_bytes(seed_parts: &[&[u8]]) -> [u8; N] { + // Use Shake-256 to generate an arbitrarily large number of random bytes based on the given seed. + let mut shake256 = sha3::Shake256::default(); + for slice in seed_parts { + sha3::digest::Update::update(&mut shake256, slice); + } + let mut reader = sha3::digest::ExtendableOutput::finalize_xof(shake256); + + let mut res = [0u8; N]; + sha3::digest::XofReader::read(&mut reader, &mut res); + res +} From 8d30367bcda9b9fdcc4338dda86197a17a03fa60 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Fri, 17 May 2024 23:20:50 +0200 Subject: [PATCH 05/30] utils: add dedicated modules for serializing and deserializing arrays --- src/utils/mod.rs | 1 + src/utils/serialize_utils.rs | 353 +++++++++++++++++++++++++++++++++++ 2 files changed, 354 insertions(+) create mode 100644 src/utils/serialize_utils.rs diff --git a/src/utils/mod.rs b/src/utils/mod.rs index adb6987..d7b7f3e 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -8,6 +8,7 @@ use crate::primitives::asset::TokenAmount; pub mod druid_utils; pub mod error_utils; pub mod script_utils; +pub mod serialize_utils; pub mod test_utils; pub mod transaction_utils; diff --git a/src/utils/serialize_utils.rs b/src/utils/serialize_utils.rs new file mode 100644 index 0000000..5d211e2 --- /dev/null +++ b/src/utils/serialize_utils.rs @@ -0,0 +1,353 @@ +use std::any::TypeId; +use std::convert::TryFrom; +use std::fmt::Formatter; +use std::marker::PhantomData; + +use bincode::config::*; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use serde::de::{SeqAccess, Visitor}; +use serde::ser::{SerializeTuple}; + +pub fn bincode_default() -> WithOtherTrailing, RejectTrailing> { + DefaultOptions::new() + .with_fixint_encoding() + .reject_trailing_bytes() +} + +pub fn bincode_compact() -> WithOtherTrailing, RejectTrailing> { + DefaultOptions::new() + .with_varint_encoding() + .reject_trailing_bytes() +} + +/// A codec for fixed-size arrays. +pub mod fixed_array_codec { + use super::*; + + pub fn serialize( + values: &[T; N], + serializer: S, + ) -> Result { + if TypeId::of::() == TypeId::of::() && serializer.is_human_readable() { + // We're serializing a byte array for a human-readable format, make it a hex string + vec_codec::serialize(values, serializer) + } else { + // Serialize the array as a tuple, to avoid adding a length prefix + let mut tuple = serializer.serialize_tuple(N)?; + for e in values { + tuple.serialize_element(e)?; + } + tuple.end() + } + } + + pub fn deserialize<'de, T: Deserialize<'de> + 'static, D: Deserializer<'de>, const N: usize>( + deserializer: D, + ) -> Result<[T; N], D::Error> { + if TypeId::of::() == TypeId::of::() && deserializer.is_human_readable() { + // We're deserializing a byte array for a human-readable format, we'll accept two different + // representations: + // - A hexadecimal string + // - An array of byte literals (this format should never be produced by the serializer + // for human-readable formats, but it was in the past, so we'll still support reading + // it for backwards-compatibility). + vec_to_fixed_array(vec_codec::deserialize(deserializer)?) + } else { + // We're deserializing a binary format, read the array as a tuple + // (to avoid adding a length prefix) + + struct FixedArrayVisitor(PhantomData); + impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for FixedArrayVisitor { + type Value = [T; N]; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + write!(formatter, "a sequence") + } + + fn visit_seq>(self, mut seq: A) -> Result { + let mut vec = Vec::with_capacity(N); + while let Some(val) = seq.next_element::()? { + vec.push(val) + } + vec_to_fixed_array(vec) + } + } + + deserializer.deserialize_tuple(N, FixedArrayVisitor(Default::default())) + } + } +} + +/// A codec for variable-length `Vec`s. +pub mod vec_codec { + use super::*; + + pub fn serialize( + values: &[T], + serializer: S, + ) -> Result { + if TypeId::of::() == TypeId::of::() && serializer.is_human_readable() { + // We're serializing a byte array for a human-readable format, make it a hex string + let bytes = unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len()) }; + serializer.serialize_str(&hex::encode(bytes)) + } else { + // Serialize the array as a length-prefixed sequence + values.serialize(serializer) + } + } + + pub fn deserialize<'de, T: Deserialize<'de> + 'static, D: Deserializer<'de>>( + deserializer: D, + ) -> Result, D::Error> { + if TypeId::of::() == TypeId::of::() && deserializer.is_human_readable() { + // We're deserializing a byte array for a human-readable format, we'll accept two different + // representations: + // - A hexadecimal string + // - An array of byte literals (this format should never be produced by the serializer + // for human-readable formats, but it was in the past, so we'll still support reading + // it for backwards-compatibility). + + struct HexStringOrBytesVisitor(); + impl<'de> Visitor<'de> for HexStringOrBytesVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + formatter.write_str("hex string or byte array") + } + + fn visit_str(self, value: &str) -> Result { + hex::decode(value).map_err(E::custom) + } + + fn visit_seq(self, mut seq: A) -> Result where A: SeqAccess<'de> { + let mut vec = Vec::new(); + while let Some(elt) = seq.next_element::()? { + vec.push(elt); + } + Ok(vec) + } + } + + Ok(deserializer.deserialize_any(HexStringOrBytesVisitor())?.into_iter() + // This is a hack to convert the Vec into a Vec, even though we already know + // that T = u8. This could be done in a much nicer way if trait specialization were + // a thing, but unfortunately it's still only available on nightly :( + .map(|b| unsafe { std::mem::transmute_copy::(&b) }) + .collect::>()) + } else { + // Read a length-prefixed sequence as a Vec + >::deserialize(deserializer) + } + } +} + +fn vec_to_fixed_array( + vec: Vec, +) -> Result<[T; N], E> { + <[T; N]>::try_from(vec) + .map_err(|vec| E::custom(format!("expected exactly {} elements, but read {}", N, vec.len()))) +} + +/*---- TESTS ----*/ + +#[cfg(test)] +mod tests { + use std::fmt::Debug; + use bincode::Options; + + use serde::{Deserialize, Serialize}; + use serde::de::DeserializeOwned; + use super::*; + + fn repeat(orig: &str, n: usize) -> String { + let mut res = String::with_capacity(orig.len() * n); + for _ in 0..n { + res.push_str(orig) + } + res + } + + fn test_bin_codec( + options: fn() -> O, + obj: T, + expect: &str, + ) { + let bytes = options().serialize(&obj).unwrap(); + assert_eq!(hex::encode(&bytes), expect); + assert_eq!(options().deserialize::(&bytes).unwrap(), obj); + } + + fn test_json_codec( + obj: T, + expect: &str, + ) { + let json = serde_json::to_string(&obj).unwrap(); + assert_eq!(json, expect); + assert_eq!(serde_json::from_str::(&json).unwrap(), obj); + } + + fn test_json_deserialize( + obj: T, + json: &str, + ) { + assert_eq!(serde_json::from_str::(&json).unwrap(), obj); + } + + macro_rules! test_fixed_array { + ($n:literal) => { + test_bin_codec(bincode_default, [VAL; $n], &repeat(HEX, $n)); + test_json_codec([VAL; $n], &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + macro_rules! test_fixed_array_wrapper { + ($e:ty, $t:ident, $n:literal) => { + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct $t([$e; $n]); + test_bin_codec(bincode_default, $t([VAL; $n]), &repeat(HEX, $n)); + test_json_codec($t([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + #[test] + fn test_fixed_u32_arrays() { + const VAL : u32 = 0xDEADBEEF; + const HEX : &str = "efbeadde"; + + test_fixed_array!(0); + test_fixed_array!(1); + test_fixed_array!(32); + + test_fixed_array_wrapper!(u32, FixedArrayWrapper0, 0); + test_fixed_array_wrapper!(u32, FixedArrayWrapper1, 1); + test_fixed_array_wrapper!(u32, FixedArrayWrapper32, 32); + + macro_rules! test_fixed_array_wrapper_codec { + ($t:ident, $n:literal) => { + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct $t(#[serde(with = "fixed_array_codec")] [u32; $n]); + test_bin_codec(bincode_default, $t([VAL; $n]), &repeat(HEX, $n)); + test_json_codec($t([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper0, 0); + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper1, 1); + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper32, 32); + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper33, 33); + } + + #[test] + fn test_fixed_u8_arrays() { + const VAL : u8 = 123; + const HEX : &str = "7b"; + + test_fixed_array!(0); + test_fixed_array!(1); + test_fixed_array!(32); + + test_fixed_array_wrapper!(u8, FixedArrayWrapper0, 0); + test_fixed_array_wrapper!(u8, FixedArrayWrapper1, 1); + test_fixed_array_wrapper!(u8, FixedArrayWrapper32, 32); + + macro_rules! test_fixed_array_wrapper_codec { + ($t:ident, $n:literal) => { + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct $t(#[serde(with = "fixed_array_codec")] [u8; $n]); + test_bin_codec(bincode_default, $t([VAL; $n]), &repeat(HEX, $n)); + test_json_codec($t([VAL; $n]), &format!("\"{}\"", hex::encode(&[VAL; $n].to_vec()))); + test_json_deserialize($t([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper0, 0); + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper1, 1); + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper32, 32); + test_fixed_array_wrapper_codec!(CodecFixedArrayWrapper33, 33); + } + + fn size_to_hex_default(n: usize) -> String { + hex::encode(bincode_default().serialize(&n).unwrap()) + } + + macro_rules! test_vec { + ($n:literal) => { + test_bin_codec(bincode_default, [VAL; $n].to_vec(), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_json_codec([VAL; $n].to_vec(), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + macro_rules! test_vec_wrapper { + ($n:literal) => { + test_bin_codec(bincode_default, VecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_json_codec(VecWrapper([VAL; $n].to_vec()), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + #[test] + fn test_u32_vecs() { + const VAL : u32 = 0xDEADBEEF; + const HEX : &str = "efbeadde"; + + test_vec!(0); + test_vec!(1); + test_vec!(32); + test_vec!(33); + + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct VecWrapper(Vec); + + test_vec_wrapper!(0); + test_vec_wrapper!(1); + test_vec_wrapper!(32); + test_vec_wrapper!(33); + + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct CodecVecWrapper(#[serde(with = "vec_codec")] Vec); + macro_rules! test_vec_wrapper_codec { + ($n:literal) => { + test_bin_codec(bincode_default, CodecVecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_json_codec(CodecVecWrapper([VAL; $n].to_vec()), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + test_vec_wrapper_codec!(0); + test_vec_wrapper_codec!(1); + test_vec_wrapper_codec!(32); + test_vec_wrapper_codec!(33); + } + + #[test] + fn test_u8_vecs() { + const VAL : u8 = 123; + const HEX : &str = "7b"; + + test_vec!(0); + test_vec!(1); + test_vec!(32); + test_vec!(33); + + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct VecWrapper(Vec); + + test_vec_wrapper!(0); + test_vec_wrapper!(1); + test_vec_wrapper!(32); + test_vec_wrapper!(33); + + #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] + struct CodecVecWrapper(#[serde(with = "vec_codec")] Vec); + macro_rules! test_vec_wrapper_codec { + ($n:literal) => { + test_bin_codec(bincode_default, CodecVecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_json_codec(CodecVecWrapper([VAL; $n].to_vec()), &format!("\"{}\"", hex::encode(&[VAL; $n].to_vec()))); + test_json_deserialize(CodecVecWrapper([VAL; $n].to_vec()), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + }; + } + + test_vec_wrapper_codec!(0); + test_vec_wrapper_codec!(1); + test_vec_wrapper_codec!(32); + test_vec_wrapper_codec!(33); + } +} From 7097e54d4342bcdc1123769240ea227b1bbfdb51 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Wed, 29 May 2024 12:56:17 +0200 Subject: [PATCH 06/30] Add a struct for representing fixed-length byte arrays This allows other structs which wrap a fixed-length byte array to simply use it as a field, and not have to deal with any of the details of formatting or JSON hex (de)serialization. --- src/utils/serialize_utils.rs | 146 +++++++++++++++++++++++++++++++++-- 1 file changed, 141 insertions(+), 5 deletions(-) diff --git a/src/utils/serialize_utils.rs b/src/utils/serialize_utils.rs index 5d211e2..bd199fc 100644 --- a/src/utils/serialize_utils.rs +++ b/src/utils/serialize_utils.rs @@ -1,7 +1,9 @@ use std::any::TypeId; -use std::convert::TryFrom; -use std::fmt::Formatter; +use std::convert::{TryFrom, TryInto}; +use std::fmt; use std::marker::PhantomData; +use std::ops::{Deref, DerefMut}; +use std::str::FromStr; use bincode::config::*; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -20,6 +22,104 @@ pub fn bincode_compact() -> WithOtherTrailing( + #[serde(with = "fixed_array_codec")] + [u8; N], +); + +impl FixedByteArray { + pub fn new(arr: [u8; N]) -> Self { + Self(arr) + } +} + +impl AsRef<[u8]> for FixedByteArray { + fn as_ref(&self) -> &[u8] { + &self.0 + } +} + +impl AsMut<[u8]> for FixedByteArray { + fn as_mut(&mut self) -> &mut [u8] { + &mut self.0 + } +} + +impl Deref for FixedByteArray { + type Target = [u8; N]; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl DerefMut for FixedByteArray { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +impl From<[u8; N]> for FixedByteArray { + fn from(value: [u8; N]) -> Self { + Self(value) + } +} + +impl TryFrom<&[u8]> for FixedByteArray { + type Error = std::array::TryFromSliceError; + + fn try_from(value: &[u8]) -> Result { + value.try_into().map(Self) + } +} + +impl TryFrom<&Vec> for FixedByteArray { + type Error = std::array::TryFromSliceError; + + fn try_from(value: &Vec) -> Result { + value.as_slice().try_into().map(Self) + } +} + +impl fmt::LowerHex for FixedByteArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + // This is hacky because we can't make an array of type [u8; {N * 2}] due to + // generic parameters not being allowed in constant expressions on stable rust + assert_eq!(std::mem::size_of::<[u16; N]>(), std::mem::size_of::<[u8; N]>() * 2); + let mut buf = [0u16; N]; + let slice = unsafe { std::slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u8, N * 2) }; + hex::encode_to_slice(&self.0, slice).unwrap(); + f.write_str(std::str::from_utf8(slice).unwrap()) + } +} + +impl fmt::Display for FixedByteArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(self, f) + } +} + +impl fmt::Debug for FixedByteArray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "FixedByteArray<{N}>({self:x})") + } +} + +impl FromStr for FixedByteArray { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + let mut buf = [0u8; N]; + hex::decode_to_slice(s, &mut buf)?; + Ok(Self(buf)) + } +} + /// A codec for fixed-size arrays. pub mod fixed_array_codec { use super::*; @@ -60,7 +160,7 @@ pub mod fixed_array_codec { impl<'de, T: Deserialize<'de>, const N: usize> Visitor<'de> for FixedArrayVisitor { type Value = [T; N]; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { write!(formatter, "a sequence") } @@ -111,7 +211,7 @@ pub mod vec_codec { impl<'de> Visitor<'de> for HexStringOrBytesVisitor { type Value = Vec; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { + fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result { formatter.write_str("hex string or byte array") } @@ -152,7 +252,7 @@ fn vec_to_fixed_array( #[cfg(test)] mod tests { - use std::fmt::Debug; + use std::fmt::{Debug, Display}; use bincode::Options; use serde::{Deserialize, Serialize}; @@ -193,6 +293,15 @@ mod tests { assert_eq!(serde_json::from_str::(&json).unwrap(), obj); } + fn test_display_fromstr( + obj: T, + expect: &str, + ) { + let string = obj.to_string(); + assert_eq!(string, expect); + assert_eq!(::from_str(&string).ok().unwrap(), obj); + } + macro_rules! test_fixed_array { ($n:literal) => { test_bin_codec(bincode_default, [VAL; $n], &repeat(HEX, $n)); @@ -350,4 +459,31 @@ mod tests { test_vec_wrapper_codec!(32); test_vec_wrapper_codec!(33); } + + #[test] + fn test_fixed_byte_array() { + const VAL : u8 = 123; + const HEX : &str = "7b"; + + macro_rules! test_fixed_byte_array { + ($n:literal) => { + test_bin_codec(bincode_default, FixedByteArray::<$n>([VAL; $n]), &repeat(HEX, $n)); + test_json_codec(FixedByteArray::<$n>([VAL; $n]), &format!("\"{}\"", repeat(HEX, $n))); + test_json_deserialize(FixedByteArray::<$n>([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); + test_display_fromstr(FixedByteArray::<$n>([VAL; $n]), &repeat(HEX, $n)); + assert_eq!(format!("{:x}", FixedByteArray::<$n>([VAL; $n])), repeat(HEX, $n)); + assert_eq!( + format!("{:?}", FixedByteArray::<$n>([VAL; $n])), + format!("FixedByteArray<{}>({})", $n, repeat(HEX, $n))); + assert_eq!( + format!("{:x?}", FixedByteArray::<$n>([VAL; $n])), + format!("FixedByteArray<{}>({})", $n, repeat(HEX, $n))); + }; + } + + test_fixed_byte_array!(0); + test_fixed_byte_array!(1); + test_fixed_byte_array!(32); + test_fixed_byte_array!(33); + } } From 4be53f5a1ed14aac6a73ed340b2ee826799fd81f Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Wed, 29 May 2024 12:56:17 +0200 Subject: [PATCH 07/30] serialize_utils: implement From<&[u8; N]> for FixedByteArray --- src/utils/serialize_utils.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/utils/serialize_utils.rs b/src/utils/serialize_utils.rs index bd199fc..af72618 100644 --- a/src/utils/serialize_utils.rs +++ b/src/utils/serialize_utils.rs @@ -70,6 +70,12 @@ impl From<[u8; N]> for FixedByteArray { } } +impl From<&[u8; N]> for FixedByteArray { + fn from(value: &[u8; N]) -> Self { + Self(*value) + } +} + impl TryFrom<&[u8]> for FixedByteArray { type Error = std::array::TryFromSliceError; From 37b51faa04ec9830e96193fd38943b6bd64a3fe0 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 11 Jul 2024 12:22:30 +0200 Subject: [PATCH 08/30] macros: add traits for generating simple enums --- src/macros.rs | 141 +++++++++++++++++++++++++++++++++++++++++++++++ src/utils/mod.rs | 38 +++++++++++++ 2 files changed, 179 insertions(+) diff --git a/src/macros.rs b/src/macros.rs index 76ff100..99f75b3 100644 --- a/src/macros.rs +++ b/src/macros.rs @@ -69,3 +69,144 @@ macro_rules! make_error_type { } }; } + +/// Generate an enum where each variant represents a unique named value. +/// +/// This will automatically generate the enum, but also implement the following traits: +/// * `ToName` and `FromName` with the variant names +/// * `Display` with the variant names +/// +/// Additionally, a constant array containing every enum variant will be generated with the +/// given name and visibility. +/// +/// Example usage: +/// ```ignore +/// make_trivial_enum!(pub enum MyError { +/// Hello, +/// World, +/// } +/// all_variants=pub(crate) ALL_VARIANTS); +/// ``` +macro_rules! make_trivial_enum { + ( + @impl_toname_fromname_display + $ename:ident { + $( $vname:ident ),+ + } + $all_variants_vis:vis $all_variants:ident + ) => { + impl $ename { + #[allow(unused)] + $all_variants_vis const $all_variants : &'static [Self] = + &[ $( Self::$vname ),+ ]; + } + + impl crate::utils::ToName for $ename { + fn to_name(&self) -> &'static str { + match self { + $( Self::$vname => stringify!($vname) ),+ + } + } + } + + impl crate::utils::FromName for $ename { + const ALL_NAMES: &'static [&'static str] = &[ $( stringify!($vname) ),+ ]; + + fn from_name(name: &str) -> Result { + match name { + $( stringify!($vname) => Ok(Self::$vname), )+ + _ => Err(name), + } + } + } + + impl std::fmt::Display for $ename { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.write_str(crate::utils::ToName::to_name(self)) + } + } + }; + + ( + $( #[$eattr:meta] )* + $evis:vis enum $ename:ident { + $( + $( #[$vattr:meta] )* + $vname:ident, + )+ + } + all_variants=$all_variants_vis:vis $all_variants:ident + ) => { + $( #[$eattr] )* + $evis enum $ename { + $( $( #[$vattr] )* $vname ),+ + } + + make_trivial_enum!( + @impl_toname_fromname_display $ename { + $( $vname ),+ + } + $all_variants_vis $all_variants); + }; +} + +/// Generate an enum where each variant simply wraps a numeric ordinal number. +/// +/// This will automatically generate the enum, but also implement the following traits: +/// * `ToOrdinal` and `FromOrdinal` with the given ordinal numbers +/// * `ToName` and `FromName` with the variant names +/// * `Display` with the variant names +/// +/// Additionally, a constant array containing every enum variant will be generated with the +/// given name and visibility. +/// +/// Example usage: +/// ```ignore +/// make_ordinal_enum!(pub enum MyError { +/// Hello = 2, +/// World = 3, +/// } +/// all_variants=pub(crate) ALL_VARIANTS); +/// ``` +macro_rules! make_ordinal_enum { + ( + $( #[$eattr:meta] )* + $evis:vis enum $ename:ident { + $( + $( #[$vattr:meta] )* + $vname:ident = $vord:literal, + )+ + } + all_variants=$all_variants_vis:vis $all_variants:ident + ) => { + $( #[$eattr] )* + $evis enum $ename { + $( $( #[$vattr] )* $vname = $vord, )+ + } + + make_trivial_enum!( + @impl_toname_fromname_display $ename { + $( $vname ),+ + } + $all_variants_vis $all_variants); + + impl crate::utils::ToOrdinal for $ename { + fn to_ordinal(&self) -> u32 { + match self { + $( Self::$vname => $vord ),+ + } + } + } + + impl crate::utils::FromOrdinal for $ename { + const ALL_ORDINALS: &'static [u32] = &[ $( $vord ),+ ]; + + fn from_ordinal(ordinal: u32) -> Result { + match ordinal { + $( $vord => Ok(Self::$vname), )+ + _ => Err(ordinal), + } + } + } + }; +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index adb6987..fad88ff 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -51,3 +51,41 @@ pub fn add_btreemap( }); m1 } + +/// A trait which indicates that a type can be represented by an ordinal number. +pub trait ToOrdinal { + /// Gets the ordinal number from a value. + fn to_ordinal(&self) -> u32; +} + +/// A trait which indicates that a type can be instantiated from an ordinal number. +pub trait FromOrdinal : Sized { + /// A slice containing every valid ordinal number. + const ALL_ORDINALS : &'static [u32]; + + /// Gets the value corresponding to the given ordinal number. + /// + /// ### Arguments + /// + /// * `ordinal` - The ordinal number + fn from_ordinal(ordinal: u32) -> Result; +} + +/// A trait which indicates that a type can be represented by a string name. +pub trait ToName { + /// Gets a value's string name. + fn to_name(&self) -> &'static str; +} + +/// A trait which indicates that a type can be instantiated from a string name. +pub trait FromName : Sized { + /// A slice containing every valid name. + const ALL_NAMES : &'static [&'static str]; + + /// Gets the value corresponding to the given name. + /// + /// ### Arguments + /// + /// * `name` - The name + fn from_name(name: &str) -> Result; +} \ No newline at end of file From 87f48d6d3e5512b35bdbc498f29cdeffaab08294 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Mon, 3 Jun 2024 09:38:17 +0200 Subject: [PATCH 09/30] always use fully-qualified name for bincode (de)serialize functions this makes it clear which serialization format is being used where, as we are going to be changing this up soon --- src/primitives/block.rs | 7 +++---- src/primitives/transaction.rs | 11 ----------- src/script/interface_ops.rs | 2 -- src/script/lang.rs | 2 -- src/utils/script_utils.rs | 2 -- src/utils/transaction_utils.rs | 10 +++------- 6 files changed, 6 insertions(+), 28 deletions(-) diff --git a/src/primitives/block.rs b/src/primitives/block.rs index 72e07e9..3397424 100644 --- a/src/primitives/block.rs +++ b/src/primitives/block.rs @@ -4,7 +4,6 @@ use crate::crypto::sha3_256::{self, Sha3_256}; use crate::crypto::sign_ed25519::PublicKey; use crate::primitives::asset::Asset; use crate::primitives::transaction::{Transaction, TxIn, TxOut}; -use bincode::{deserialize, serialize}; use bytes::Bytes; use serde::{Deserialize, Serialize}; use std::convert::TryInto; @@ -82,7 +81,7 @@ impl Block { /// Sets the internal number of bits based on length pub fn set_bits(&mut self) { - let bytes = Bytes::from(match serialize(&self) { + let bytes = Bytes::from(match bincode::serialize(&self) { Ok(bytes) => bytes, Err(e) => { warn!("Failed to serialize block: {:?}", e); @@ -94,7 +93,7 @@ impl Block { /// Checks whether a block has hit its maximum size pub fn is_full(&self) -> bool { - let bytes = Bytes::from(match serialize(&self) { + let bytes = Bytes::from(match bincode::serialize(&self) { Ok(bytes) => bytes, Err(e) => { warn!("Failed to serialize block: {:?}", e); @@ -143,7 +142,7 @@ pub fn gen_random_hash() -> String { /// /// * `transactions` - Transactions to construct a merkle tree for pub fn build_hex_txs_hash(transactions: &[String]) -> String { - let txs = match serialize(transactions) { + let txs = match bincode::serialize(transactions) { Ok(bytes) => bytes, Err(e) => { warn!("Failed to serialize transactions: {:?}", e); diff --git a/src/primitives/transaction.rs b/src/primitives/transaction.rs index 08435fd..e1d7236 100644 --- a/src/primitives/transaction.rs +++ b/src/primitives/transaction.rs @@ -8,8 +8,6 @@ use crate::primitives::{ use crate::script::lang::Script; use crate::script::{OpCodes, StackEntry}; use crate::utils::is_valid_amount; -use bincode::serialize; -use bytes::Bytes; use serde::{Deserialize, Serialize}; use std::fmt; @@ -205,15 +203,6 @@ impl Transaction { } } - /// Get the total transaction size in bytes - pub fn get_total_size(&self) -> usize { - let bytes = match serialize(self) { - Ok(bytes) => bytes, - Err(_) => vec![], - }; - bytes.len() - } - /// Gets the create asset assigned to this transaction, if it exists fn get_create_asset(&self) -> Option<&Asset> { let is_create = self.inputs.len() == 1 diff --git a/src/script/interface_ops.rs b/src/script/interface_ops.rs index 7b9875b..ba2a63c 100644 --- a/src/script/interface_ops.rs +++ b/src/script/interface_ops.rs @@ -11,8 +11,6 @@ use crate::utils::error_utils::*; use crate::utils::transaction_utils::{ construct_address, construct_address_temp, construct_address_v0, }; -use bincode::de; -use bincode::serialize; use bytes::Bytes; use hex::encode; use std::collections::BTreeMap; diff --git a/src/script/lang.rs b/src/script/lang.rs index 259465e..2498e5c 100644 --- a/src/script/lang.rs +++ b/src/script/lang.rs @@ -8,8 +8,6 @@ use crate::script::interface_ops::*; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; use crate::utils::transaction_utils::{construct_address, construct_address_for}; -use bincode::serialize; -use bytes::Bytes; use hex::encode; use serde::{Deserialize, Serialize}; use tracing::{error, warn}; diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index a2cd085..fca79da 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -15,8 +15,6 @@ use crate::utils::transaction_utils::{ construct_address, construct_tx_hash, construct_tx_in_out_signable_hash, construct_tx_in_signable_asset_hash, construct_tx_in_signable_hash, }; -use bincode::serialize; -use bytes::Bytes; use hex::encode; use ring::error; use std::collections::{BTreeMap, BTreeSet}; diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index 8e9fd99..9a96af1 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -6,7 +6,6 @@ use crate::primitives::druid::{DdeValues, DruidExpectation}; use crate::primitives::transaction::*; use crate::script::lang::Script; use crate::script::{OpCodes, StackEntry}; -use bincode::serialize; use std::collections::BTreeMap; use tracing::debug; @@ -21,10 +20,7 @@ pub struct ReceiverInfo { /// /// * `script` - Script to build address for pub fn construct_p2sh_address(script: &Script) -> String { - let bytes = match serialize(script) { - Ok(bytes) => bytes, - Err(_) => vec![], - }; + let bytes = bincode::serialize(script).unwrap(); let mut addr = hex::encode(sha3_256::digest(&bytes)); addr.insert(ZERO, P2SH_PREPEND as char); addr.truncate(STANDARD_ADDRESS_LENGTH); @@ -344,7 +340,7 @@ pub fn update_utxo_set(current_utxo: &mut BTreeMap) { /// /// * `tx` - Transaction to hash pub fn construct_tx_hash(tx: &Transaction) -> String { - let bytes = match serialize(tx) { + let bytes = match bincode::serialize(tx) { Ok(bytes) => bytes, Err(_) => vec![], }; @@ -1179,7 +1175,7 @@ mod tests { ..Default::default() }]; - let bytes = match serialize(&tx_ins) { + let bytes = match bincode::serialize(&tx_ins) { Ok(bytes) => bytes, Err(_) => vec![], }; From feef369594a8cc6d7c4f6ca673999f6de17b3f8d Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 6 Jun 2024 14:17:56 +0200 Subject: [PATCH 10/30] Upgrade to bincode 2 This will enable us to keep JSON serialization separate from the binary encoding, and also be much more explicit about the exact binary representation of objects. --- Cargo.lock | 20 ++++++++++++-- Cargo.toml | 2 +- src/primitives/block.rs | 11 ++++---- src/utils/serialize_utils.rs | 50 ++++++++++++++++++---------------- src/utils/transaction_utils.rs | 6 ++-- 5 files changed, 54 insertions(+), 35 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6575dd..009c39b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,13 +108,23 @@ checksum = "0ea22880d78093b0cbe17c89f64a7d457941e65759157ec6cb31a31d652b05e5" [[package]] name = "bincode" -version = "1.3.3" +version = "2.0.0-rc.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +checksum = "f11ea1a0346b94ef188834a65c068a03aec181c94896d481d7a0a40d85b0ce95" dependencies = [ + "bincode_derive", "serde", ] +[[package]] +name = "bincode_derive" +version = "2.0.0-rc.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e30759b3b99a1b802a7a3aa21c85c3ded5c28e1c83170d82d70f08bbf7f3e4c" +dependencies = [ + "virtue", +] + [[package]] name = "bindgen" version = "0.65.1" @@ -1068,6 +1078,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "virtue" +version = "0.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dcc60c0624df774c82a0ef104151231d37da4962957d691c011c852b2473314" + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" diff --git a/Cargo.toml b/Cargo.toml index 5c6fd2e..ee03c21 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ exclude = ["**/tests/**", "**/examples/**", "**/benchmarks/**", "docs/**", ".hoo [dependencies] actix-rt = "2.8.0" base64 = "0.20.0" -bincode = "1.3.3" +bincode = { version = "2.0.0-rc.3", features = ["serde"] } bytes = "1.4.0" colored = { version = "2.1.0", optional = true } hex = "0.4.3" diff --git a/src/primitives/block.rs b/src/primitives/block.rs index 3397424..18cb611 100644 --- a/src/primitives/block.rs +++ b/src/primitives/block.rs @@ -81,25 +81,25 @@ impl Block { /// Sets the internal number of bits based on length pub fn set_bits(&mut self) { - let bytes = Bytes::from(match bincode::serialize(&self) { + let bytes = match bincode::serde::encode_to_vec(&self, bincode::config::legacy()) { Ok(bytes) => bytes, Err(e) => { warn!("Failed to serialize block: {:?}", e); return; } - }); + }; self.header.bits = bytes.len(); } /// Checks whether a block has hit its maximum size pub fn is_full(&self) -> bool { - let bytes = Bytes::from(match bincode::serialize(&self) { + let bytes = match bincode::serde::encode_to_vec(&self, bincode::config::legacy()) { Ok(bytes) => bytes, Err(e) => { warn!("Failed to serialize block: {:?}", e); return false; } - }); + }; bytes.len() >= MAX_BLOCK_SIZE } @@ -142,7 +142,8 @@ pub fn gen_random_hash() -> String { /// /// * `transactions` - Transactions to construct a merkle tree for pub fn build_hex_txs_hash(transactions: &[String]) -> String { - let txs = match bincode::serialize(transactions) { + // TODO: This is bad, it won't produce the same result on 32-bit systems + let txs = match bincode::serde::encode_to_vec(transactions, bincode::config::legacy()) { Ok(bytes) => bytes, Err(e) => { warn!("Failed to serialize transactions: {:?}", e); diff --git a/src/utils/serialize_utils.rs b/src/utils/serialize_utils.rs index af72618..39eb7ea 100644 --- a/src/utils/serialize_utils.rs +++ b/src/utils/serialize_utils.rs @@ -5,21 +5,24 @@ use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::str::FromStr; -use bincode::config::*; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de::{SeqAccess, Visitor}; use serde::ser::{SerializeTuple}; -pub fn bincode_default() -> WithOtherTrailing, RejectTrailing> { - DefaultOptions::new() - .with_fixint_encoding() - .reject_trailing_bytes() +/// Implements `Write` by simply counting the number of bytes written to it. +pub struct ByteCountingWriter { + pub count: usize, } -pub fn bincode_compact() -> WithOtherTrailing, RejectTrailing> { - DefaultOptions::new() - .with_varint_encoding() - .reject_trailing_bytes() +impl std::io::Write for ByteCountingWriter { + fn write(&mut self, buf: &[u8]) -> std::io::Result { + self.count += buf.len(); + Ok(buf.len()) + } + + fn flush(&mut self) -> std::io::Result<()> { + Ok(()) + } } /// Simple wrapper around a fixed-length byte array. @@ -259,7 +262,6 @@ fn vec_to_fixed_array( #[cfg(test)] mod tests { use std::fmt::{Debug, Display}; - use bincode::Options; use serde::{Deserialize, Serialize}; use serde::de::DeserializeOwned; @@ -273,14 +275,14 @@ mod tests { res } - fn test_bin_codec( - options: fn() -> O, + fn test_bin_codec( + config: fn() -> C, obj: T, expect: &str, ) { - let bytes = options().serialize(&obj).unwrap(); + let bytes = bincode::serde::encode_to_vec(&obj, config()).unwrap(); assert_eq!(hex::encode(&bytes), expect); - assert_eq!(options().deserialize::(&bytes).unwrap(), obj); + assert_eq!(bincode::serde::decode_from_slice::(&bytes, config()).unwrap().0, obj); } fn test_json_codec( @@ -310,7 +312,7 @@ mod tests { macro_rules! test_fixed_array { ($n:literal) => { - test_bin_codec(bincode_default, [VAL; $n], &repeat(HEX, $n)); + test_bin_codec(bincode::config::legacy, [VAL; $n], &repeat(HEX, $n)); test_json_codec([VAL; $n], &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; } @@ -319,7 +321,7 @@ mod tests { ($e:ty, $t:ident, $n:literal) => { #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] struct $t([$e; $n]); - test_bin_codec(bincode_default, $t([VAL; $n]), &repeat(HEX, $n)); + test_bin_codec(bincode::config::legacy, $t([VAL; $n]), &repeat(HEX, $n)); test_json_codec($t([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; } @@ -341,7 +343,7 @@ mod tests { ($t:ident, $n:literal) => { #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] struct $t(#[serde(with = "fixed_array_codec")] [u32; $n]); - test_bin_codec(bincode_default, $t([VAL; $n]), &repeat(HEX, $n)); + test_bin_codec(bincode::config::legacy, $t([VAL; $n]), &repeat(HEX, $n)); test_json_codec($t([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; } @@ -369,7 +371,7 @@ mod tests { ($t:ident, $n:literal) => { #[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize)] struct $t(#[serde(with = "fixed_array_codec")] [u8; $n]); - test_bin_codec(bincode_default, $t([VAL; $n]), &repeat(HEX, $n)); + test_bin_codec(bincode::config::legacy, $t([VAL; $n]), &repeat(HEX, $n)); test_json_codec($t([VAL; $n]), &format!("\"{}\"", hex::encode(&[VAL; $n].to_vec()))); test_json_deserialize($t([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; @@ -382,19 +384,19 @@ mod tests { } fn size_to_hex_default(n: usize) -> String { - hex::encode(bincode_default().serialize(&n).unwrap()) + hex::encode(&(n as u64).to_le_bytes()) } macro_rules! test_vec { ($n:literal) => { - test_bin_codec(bincode_default, [VAL; $n].to_vec(), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_bin_codec(bincode::config::legacy, [VAL; $n].to_vec(), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); test_json_codec([VAL; $n].to_vec(), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; } macro_rules! test_vec_wrapper { ($n:literal) => { - test_bin_codec(bincode_default, VecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_bin_codec(bincode::config::legacy, VecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); test_json_codec(VecWrapper([VAL; $n].to_vec()), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; } @@ -421,7 +423,7 @@ mod tests { struct CodecVecWrapper(#[serde(with = "vec_codec")] Vec); macro_rules! test_vec_wrapper_codec { ($n:literal) => { - test_bin_codec(bincode_default, CodecVecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_bin_codec(bincode::config::legacy, CodecVecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); test_json_codec(CodecVecWrapper([VAL; $n].to_vec()), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; } @@ -454,7 +456,7 @@ mod tests { struct CodecVecWrapper(#[serde(with = "vec_codec")] Vec); macro_rules! test_vec_wrapper_codec { ($n:literal) => { - test_bin_codec(bincode_default, CodecVecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); + test_bin_codec(bincode::config::legacy, CodecVecWrapper([VAL; $n].to_vec()), &format!("{}{}", size_to_hex_default($n), repeat(HEX, $n))); test_json_codec(CodecVecWrapper([VAL; $n].to_vec()), &format!("\"{}\"", hex::encode(&[VAL; $n].to_vec()))); test_json_deserialize(CodecVecWrapper([VAL; $n].to_vec()), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); }; @@ -473,7 +475,7 @@ mod tests { macro_rules! test_fixed_byte_array { ($n:literal) => { - test_bin_codec(bincode_default, FixedByteArray::<$n>([VAL; $n]), &repeat(HEX, $n)); + test_bin_codec(bincode::config::legacy, FixedByteArray::<$n>([VAL; $n]), &repeat(HEX, $n)); test_json_codec(FixedByteArray::<$n>([VAL; $n]), &format!("\"{}\"", repeat(HEX, $n))); test_json_deserialize(FixedByteArray::<$n>([VAL; $n]), &serde_json::to_string(&[VAL; $n].to_vec()).unwrap()); test_display_fromstr(FixedByteArray::<$n>([VAL; $n]), &repeat(HEX, $n)); diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index 9a96af1..fbc89e3 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -20,7 +20,7 @@ pub struct ReceiverInfo { /// /// * `script` - Script to build address for pub fn construct_p2sh_address(script: &Script) -> String { - let bytes = bincode::serialize(script).unwrap(); + let bytes = bincode::serde::encode_to_vec(script, bincode::config::legacy()).unwrap(); let mut addr = hex::encode(sha3_256::digest(&bytes)); addr.insert(ZERO, P2SH_PREPEND as char); addr.truncate(STANDARD_ADDRESS_LENGTH); @@ -340,7 +340,7 @@ pub fn update_utxo_set(current_utxo: &mut BTreeMap) { /// /// * `tx` - Transaction to hash pub fn construct_tx_hash(tx: &Transaction) -> String { - let bytes = match bincode::serialize(tx) { + let bytes = match bincode::serde::encode_to_vec(tx, bincode::config::legacy()) { Ok(bytes) => bytes, Err(_) => vec![], }; @@ -1175,7 +1175,7 @@ mod tests { ..Default::default() }]; - let bytes = match bincode::serialize(&tx_ins) { + let bytes = match bincode::serde::encode_to_vec(&tx_ins, bincode::config::legacy()) { Ok(bytes) => bytes, Err(_) => vec![], }; From 36409d524f2589c1777af3fb234b861c73c47d4f Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 11 Jul 2024 12:51:44 +0200 Subject: [PATCH 11/30] serialize_utils: add a bunch of helper methods --- src/utils/serialize_utils.rs | 116 +++++++++++++++++++++++++++++++++-- 1 file changed, 112 insertions(+), 4 deletions(-) diff --git a/src/utils/serialize_utils.rs b/src/utils/serialize_utils.rs index 39eb7ea..9053c02 100644 --- a/src/utils/serialize_utils.rs +++ b/src/utils/serialize_utils.rs @@ -1,20 +1,32 @@ use std::any::TypeId; use std::convert::{TryFrom, TryInto}; use std::fmt; +use std::io::Write; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::str::FromStr; +use bincode::{BorrowDecode, Decode, Encode}; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::de::{SeqAccess, Visitor}; use serde::ser::{SerializeTuple}; /// Implements `Write` by simply counting the number of bytes written to it. +#[derive(Copy, Clone, Debug)] pub struct ByteCountingWriter { pub count: usize, } -impl std::io::Write for ByteCountingWriter { +impl ByteCountingWriter { + /// Creates a new `ByteCountingWriter` with a `count` of 0. + pub fn new() -> Self { + Self { + count: 0, + } + } +} + +impl Write for ByteCountingWriter { fn write(&mut self, buf: &[u8]) -> std::io::Result { self.count += buf.len(); Ok(buf.len()) @@ -29,7 +41,7 @@ impl std::io::Write for ByteCountingWriter { /// /// This can be formatted to and parsed from a hexadecimal string using `Display` and `FromStr`. /// When serialized as JSON, it is also represented as a hexadecimal string. -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)] +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Encode, Decode)] pub struct FixedByteArray( #[serde(with = "fixed_array_codec")] [u8; N], @@ -129,7 +141,7 @@ impl FromStr for FixedByteArray { } } -/// A codec for fixed-size arrays. +/// A serde codec for fixed-size arrays. pub mod fixed_array_codec { use super::*; @@ -187,7 +199,7 @@ pub mod fixed_array_codec { } } -/// A codec for variable-length `Vec`s. +/// A serde codec for variable-length `Vec`s. pub mod vec_codec { use super::*; @@ -257,6 +269,102 @@ fn vec_to_fixed_array( .map_err(|vec| E::custom(format!("expected exactly {} elements, but read {}", N, vec.len()))) } +/// Encodes an object into a `Vec` using bincode 2's standard configuration. +/// +/// This allows using the turbofish operator to explicitly specify the encode type without also +/// having to specify the config type. +/// +/// ### Arguments +/// +/// * `value` - the value to encode +#[inline(always)] +pub fn bincode_encode_to_vec_standard( + value: &T, +) -> Result, bincode::error::EncodeError> { + bincode::encode_to_vec(value, bincode::config::standard()) +} + +/// Encodes an object into the given `Write` using bincode 2's standard configuration. +/// +/// This allows using the turbofish operator to explicitly specify the encode type without also +/// having to specify the config type. +/// +/// ### Arguments +/// +/// * `value` - the value to encode +/// * `write` - the `Write` to encode into +#[inline(always)] +pub fn bincode_encode_to_write_standard( + value: &T, + write: &mut impl Write, +) -> Result { + bincode::encode_into_std_write(value, write, bincode::config::standard()) +} + +/// Calculates the encoded size of the given object using bincode 2's standard configuration. +/// +/// ### Arguments +/// +/// * `value` - the value to encode +#[inline(always)] +pub fn bincode_encoded_size_standard( + value: &T, +) -> Result { + let mut writer = ByteCountingWriter::new(); + bincode::encode_into_std_write(value, &mut writer, bincode::config::standard())?; + Ok(writer.count) +} + +/// Decodes an object from a slice using bincode 2's standard configuration. +/// +/// This allows using the turbofish operator to explicitly specify the decode type without also +/// having to specify the config type. +/// +/// ### Arguments +/// +/// * `slice` - the slice to decode from +#[inline(always)] +pub fn bincode_decode_from_slice_standard( + slice: &[u8], +) -> Result<(T, usize), bincode::error::DecodeError> { + bincode::decode_from_slice(slice, bincode::config::standard()) +} + +/// Decodes an object from a slice using bincode 2's standard configuration. +/// +/// This allows using the turbofish operator to explicitly specify the decode type without also +/// having to specify the config type. +/// +/// ### Arguments +/// +/// * `slice` - the slice to decode from +pub fn bincode_decode_from_slice_standard_full( + slice: &[u8], +) -> Result { + let (result, read_bytes) = bincode_decode_from_slice_standard::(slice)?; + if read_bytes == slice.len() { + Ok(result) + } else { + Err(bincode::error::DecodeError::OtherString( + format!("{} bytes left over after decoding", slice.len() - read_bytes))) + } +} + +/// Decodes an object from a slice using bincode 2's standard configuration. +/// +/// This allows using the turbofish operator to explicitly specify the decode type without also +/// having to specify the config type. +/// +/// ### Arguments +/// +/// * `slice` - the slice to decode from +#[inline(always)] +pub fn bincode_borrow_decode_from_slice_standard<'a, T: BorrowDecode<'a>>( + slice: &'a [u8], +) -> Result<(T, usize), bincode::error::DecodeError> { + bincode::borrow_decode_from_slice(slice, bincode::config::standard()) +} + /*---- TESTS ----*/ #[cfg(test)] From 0e148be1945cb8427cf61a947d1efcf9ae14bfce Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 11 Jul 2024 16:41:31 +0200 Subject: [PATCH 12/30] PlaceholderIndexed -> PlaceholderSeed --- src/utils/mod.rs | 49 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 6 deletions(-) diff --git a/src/utils/mod.rs b/src/utils/mod.rs index 5c0840b..aa17b53 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -69,7 +69,33 @@ pub trait Placeholder : Sized { /// of a type, which can be used for test purposes. These placeholder values are consistent /// across program runs. #[cfg(test)] -pub trait PlaceholderIndexed : Sized { +pub trait PlaceholderSeed: Sized + PartialEq { + /// Gets a dummy valid of this type which can be used for test purposes. + /// + /// This allows acquiring multiple distinct placeholder values which are still consistent + /// between runs. + /// + /// ### Arguments + /// + /// * `seed_parts` - the parts of the seed for the placeholder value to obtain. Two placeholder + /// values generated from the same seed are guaranteed to be equal (even + /// across multiple test runs, so long as the value format doesn't change). + fn placeholder_seed_parts<'a>(seed_parts: impl IntoIterator) -> Self; + + /// Gets a dummy valid of this type which can be used for test purposes. + /// + /// This allows acquiring multiple distinct placeholder values which are still consistent + /// between runs. + /// + /// ### Arguments + /// + /// * `seed` - the seed for the placeholder value to obtain. Two placeholder + /// values generated from the same seed are guaranteed to be equal (even + /// across multiple test runs, so long as the value format doesn't change). + fn placeholder_seed(seed: impl AsRef<[u8]>) -> Self { + Self::placeholder_seed_parts([ seed.as_ref() ]) + } + /// Gets a dummy valid of this type which can be used for test purposes. /// /// This allows acquiring multiple distinct placeholder values which are still consistent @@ -80,18 +106,27 @@ pub trait PlaceholderIndexed : Sized { /// * `index` - the index of the placeholder value to obtain. Two placeholder values generated /// from the same index are guaranteed to be equal (even across multiple test runs, /// so long as the value format doesn't change). - fn placeholder_indexed(index: u64) -> Self; + fn placeholder_indexed(index: u64) -> Self { + Self::placeholder_seed_parts([ index.to_le_bytes().as_slice() ]) + } + + /// Gets an array of placeholder values of this type which can be used for test purposes. + fn placeholder_array_seed(seed: impl AsRef<[u8]>) -> [Self; N] { + core::array::from_fn(|n| Self::placeholder_seed_parts( + [ seed.as_ref(), &(n as u64).to_le_bytes() ] + )) + } /// Gets an array of placeholder values of this type which can be used for test purposes. fn placeholder_array_indexed(base_index: u64) -> [Self; N] { - core::array::from_fn(|n| Self::placeholder_indexed(base_index.wrapping_add(n as u64))) + Self::placeholder_array_seed(base_index.to_le_bytes()) } } #[cfg(test)] -impl Placeholder for T { +impl Placeholder for T { fn placeholder() -> Self { - Self::placeholder_indexed(0) + ::placeholder_seed_parts([]) } } @@ -104,7 +139,9 @@ impl Placeholder for T { /// /// * `seed_parts` - the parts of the seed, which will be concatenated to form the RNG seed #[cfg(test)] -pub fn placeholder_bytes(seed_parts: &[&[u8]]) -> [u8; N] { +pub fn placeholder_bytes<'a, const N: usize>( + seed_parts: impl IntoIterator +) -> [u8; N] { // Use Shake-256 to generate an arbitrarily large number of random bytes based on the given seed. let mut shake256 = sha3::Shake256::default(); for slice in seed_parts { From dcfa232e8f1519191b9700fc9a892b2d7b478a1a Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 11 Jul 2024 11:18:24 +0200 Subject: [PATCH 13/30] crypto: add a dedicated wrapper struct for SHA3-256 hashes --- src/crypto.rs | 59 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 38b51d0..03a3d90 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -308,18 +308,69 @@ pub mod pbkdf2 { } pub mod sha3_256 { + use std::convert::{TryFrom, TryInto}; + use std::fmt::{Display, Formatter}; + use std::ops::Deref; + use std::str::FromStr; + pub use sha3::digest::Output; pub use sha3::Digest; pub use sha3::Sha3_256; - pub fn digest(data: &[u8]) -> Output { - Sha3_256::digest(data) + pub const HASH_LEN : usize = 256 / 8; + + /// A SHA3-256 hash. + #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq)] + pub struct Hash( + [u8; HASH_LEN], + ); + + impl Hash { + pub fn from_slice(slice: &[u8]) -> Option { + Some(Self(slice.try_into().ok()?)) + } + } + + impl Display for Hash { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + f.write_str(&hex::encode(&self.0)) + } + } + + impl FromStr for Hash { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + let mut buf = [0u8; HASH_LEN]; + match hex::decode_to_slice(s, &mut buf) { + Ok(_) => Ok(Self(buf)), + Err(e) => Err(e), + } + } + } + + impl AsRef<[u8]> for Hash { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } + + impl Deref for Hash { + type Target = [u8; HASH_LEN]; + + fn deref(&self) -> &Self::Target { + &self.0 + } + } + + pub fn digest(data: &[u8]) -> Hash { + Hash(Sha3_256::digest(data).try_into().unwrap()) } - pub fn digest_all<'a>(data: impl Iterator) -> Output { + pub fn digest_all<'a>(data: impl Iterator) -> Hash { let mut hasher = Sha3_256::new(); data.for_each(|v| hasher.update(v)); - hasher.finalize() + Hash(hasher.finalize().try_into().unwrap()) } } From 1a7fd05c14a51b9ee3910166c45a02e383b138b4 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Wed, 29 May 2024 14:10:09 +0200 Subject: [PATCH 14/30] Implement all crypto structs using FixedByteArray --- src/crypto.rs | 173 +++++++++++++++----------------------------------- 1 file changed, 52 insertions(+), 121 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 03a3d90..9dd27e3 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,9 +1,42 @@ pub use ring; -use std::convert::TryInto; use tracing::warn; +use crate::utils::serialize_utils::FixedByteArray; + +macro_rules! fixed_bytes_wrapper { + ($vis:vis struct $name:ident, $n:expr, $doc:literal) => { + #[doc = $doc] + #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] + $vis struct $name(crate::utils::serialize_utils::FixedByteArray<$n>); + + impl $name { + pub fn from_slice(slice: &[u8]) -> Option { + Some(Self(slice.try_into().ok()?)) + } + } + + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } + } + + impl std::fmt::Display for $name { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + std::fmt::Display::fmt(&self.0, f) + } + } + + impl std::str::FromStr for $name { + type Err = as std::str::FromStr>::Err; + + fn from_str(s: &str) -> Result { + as std::str::FromStr>::from_str(s).map(Self) + } + } + }; +} pub mod sign_ed25519 { - use super::deserialize_slice; pub use ring::signature::Ed25519KeyPair as SecretKeyBase; use ring::signature::KeyPair; pub use ring::signature::Signature as SignatureBase; @@ -21,53 +54,14 @@ pub mod sign_ed25519 { const SIGNATURE_LEN: usize = ELEM_LEN + SCALAR_LEN; pub const ED25519_SIGNATURE_LEN: usize = SIGNATURE_LEN; - /// Signature data - /// We used sodiumoxide serialization before (treated it as slice with 64 bit length prefix). - #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] - pub struct Signature( - #[serde(serialize_with = "<[_]>::serialize")] - #[serde(deserialize_with = "deserialize_slice")] - [u8; ED25519_SIGNATURE_LEN], - ); - - impl Signature { - pub fn from_slice(slice: &[u8]) -> Option { - Some(Self(slice.try_into().ok()?)) - } - } - - impl AsRef<[u8]> for Signature { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } - } - - /// Public key data - /// We used sodiumoxide serialization before (treated it as slice with 64 bit length prefix). - #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] - pub struct PublicKey( - #[serde(serialize_with = "<[_]>::serialize")] - #[serde(deserialize_with = "deserialize_slice")] - [u8; ED25519_PUBLIC_KEY_LEN], - ); - - impl PublicKey { - pub fn from_slice(slice: &[u8]) -> Option { - Some(Self(slice.try_into().ok()?)) - } - } - - impl AsRef<[u8]> for PublicKey { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } - } + fixed_bytes_wrapper!(pub struct Signature, ED25519_SIGNATURE_LEN, "Signature data"); + fixed_bytes_wrapper!(pub struct PublicKey, ED25519_PUBLIC_KEY_LEN, "Public key data"); /// PKCS8 encoded secret key pair /// We used sodiumoxide serialization before (treated it as slice with 64 bit length prefix). /// Slice and vector are serialized the same. #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] - pub struct SecretKey(Vec); + pub struct SecretKey(#[serde(with = "crate::utils::serialize_utils::vec_codec")] Vec); impl SecretKey { pub fn from_slice(slice: &[u8]) -> Option { @@ -91,7 +85,7 @@ pub mod sign_ed25519 { Ok(secret) => secret, Err(_) => { warn!("Invalid secret key"); - return Signature([0; ED25519_SIGNATURE_LEN]); + return Signature([0; ED25519_SIGNATURE_LEN].into()); } }; @@ -99,7 +93,7 @@ pub mod sign_ed25519 { Ok(signature) => signature, Err(_) => { warn!("Invalid signature"); - return Signature([0; ED25519_SIGNATURE_LEN]); + return Signature([0; ED25519_SIGNATURE_LEN].into()); } }; Signature(signature) @@ -135,7 +129,7 @@ pub mod sign_ed25519 { Ok(pkcs8) => pkcs8, Err(_) => { warn!("Failed to generate secret key base for pkcs8"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN]), SecretKey(vec![])); + return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); } }; @@ -143,7 +137,7 @@ pub mod sign_ed25519 { Ok(secret) => secret, Err(_) => { warn!("Invalid secret key base"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN]), SecretKey(vec![])); + return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); } }; @@ -151,7 +145,7 @@ pub mod sign_ed25519 { Ok(pub_key_gen) => pub_key_gen, Err(_) => { warn!("Invalid public key generation"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN]), SecretKey(vec![])); + return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); } }; let public = PublicKey(pub_key_gen); @@ -159,7 +153,7 @@ pub mod sign_ed25519 { Some(secret) => secret, None => { warn!("Invalid secret key"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN]), SecretKey(vec![])); + return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); } }; @@ -169,7 +163,7 @@ pub mod sign_ed25519 { pub mod secretbox_chacha20_poly1305 { // Use key and nonce separately like rust-tls does - use super::{deserialize_slice, generate_random}; + use super::generate_random; pub use ring::aead::LessSafeKey as KeyBase; pub use ring::aead::Nonce as NonceBase; pub use ring::aead::NONCE_LEN; @@ -179,45 +173,8 @@ pub mod secretbox_chacha20_poly1305 { pub const KEY_LEN: usize = 256 / 8; - /// key data - #[derive(Clone, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] - pub struct Key( - #[serde(serialize_with = "<[_]>::serialize")] - #[serde(deserialize_with = "deserialize_slice")] - [u8; KEY_LEN], - ); - - impl Key { - pub fn from_slice(slice: &[u8]) -> Option { - Some(Self(slice.try_into().ok()?)) - } - } - - impl AsRef<[u8]> for Key { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } - } - - /// Nonce data - #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] - pub struct Nonce( - #[serde(serialize_with = "<[_]>::serialize")] - #[serde(deserialize_with = "deserialize_slice")] - [u8; NONCE_LEN], - ); - - impl Nonce { - pub fn from_slice(slice: &[u8]) -> Option { - Some(Self(slice.try_into().ok()?)) - } - } - - impl AsRef<[u8]> for Nonce { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } - } + fixed_bytes_wrapper!(pub struct Key, KEY_LEN, "Key data"); + fixed_bytes_wrapper!(pub struct Nonce, NONCE_LEN, "Nonce data"); pub fn seal(mut plain_text: Vec, nonce: &Nonce, key: &Key) -> Option> { let key = get_keybase(key)?; @@ -249,7 +206,7 @@ pub mod secretbox_chacha20_poly1305 { } fn get_noncebase(nonce: &Nonce) -> NonceBase { - NonceBase::assume_unique_for_key(nonce.0) + NonceBase::assume_unique_for_key(*nonce.0) } pub fn gen_key() -> Key { @@ -262,7 +219,7 @@ pub mod secretbox_chacha20_poly1305 { } pub mod pbkdf2 { - use super::{deserialize_slice, generate_random}; + use super::generate_random; use ring::pbkdf2::{derive, PBKDF2_HMAC_SHA256}; use serde::{Deserialize, Serialize}; use std::convert::TryInto; @@ -272,24 +229,7 @@ pub mod pbkdf2 { pub const SALT_LEN: usize = 256 / 8; pub const OPSLIMIT_INTERACTIVE: u32 = 100_000; - #[derive(Clone, Copy, Debug, PartialOrd, Ord, PartialEq, Eq, Serialize, Deserialize)] - pub struct Salt( - #[serde(serialize_with = "<[_]>::serialize")] - #[serde(deserialize_with = "deserialize_slice")] - [u8; SALT_LEN], - ); - - impl Salt { - pub fn from_slice(slice: &[u8]) -> Option { - Some(Self(slice.try_into().ok()?)) - } - } - - impl AsRef<[u8]> for Salt { - fn as_ref(&self) -> &[u8] { - self.0.as_ref() - } - } + fixed_bytes_wrapper!(pub struct Salt, SALT_LEN, "Salt data"); pub fn derive_key(key: &mut [u8], passwd: &[u8], salt: &Salt, iterations: u32) { let iterations = match NonZeroU32::new(iterations) { @@ -374,16 +314,7 @@ pub mod sha3_256 { } } -fn deserialize_slice<'de, D: serde::Deserializer<'de>, const N: usize>( - deserializer: D, -) -> Result<[u8; N], D::Error> { - let value: &[u8] = serde::Deserialize::deserialize(deserializer)?; - value - .try_into() - .map_err(|_| serde::de::Error::custom("Invalid array in deserialization".to_string())) -} - -pub fn generate_random() -> [u8; N] { +fn generate_random() -> FixedByteArray { let mut value: [u8; N] = [0; N]; use ring::rand::SecureRandom; @@ -393,5 +324,5 @@ pub fn generate_random() -> [u8; N] { Err(_) => warn!("Failed to generate random bytes"), }; - value + FixedByteArray::new(value) } From 74aa8207646f3b4280284da2576af0eb2b68948c Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 11 Jul 2024 12:03:16 +0200 Subject: [PATCH 15/30] crypto: improve keypair generation Also implemented PlaceholderIndexed for all crypto structs --- src/crypto.rs | 163 ++++++++++++++++++++++++++------------------------ 1 file changed, 85 insertions(+), 78 deletions(-) diff --git a/src/crypto.rs b/src/crypto.rs index 9dd27e3..7e57887 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -1,6 +1,5 @@ pub use ring; use tracing::warn; -use crate::utils::serialize_utils::FixedByteArray; macro_rules! fixed_bytes_wrapper { ($vis:vis struct $name:ident, $n:expr, $doc:literal) => { @@ -33,6 +32,15 @@ macro_rules! fixed_bytes_wrapper { as std::str::FromStr>::from_str(s).map(Self) } } + + #[cfg(test)] + impl crate::utils::PlaceholderSeed for $name { + fn placeholder_seed_parts<'a>(seed_parts: impl IntoIterator) -> Self { + Self(crate::utils::placeholder_bytes( + [ concat!(stringify!($name), ":").as_bytes() ].iter().copied().chain(seed_parts) + ).into()) + } + } }; } @@ -44,9 +52,7 @@ pub mod sign_ed25519 { pub use ring::signature::{ED25519, ED25519_PUBLIC_KEY_LEN}; use serde::{Deserialize, Serialize}; use std::convert::TryInto; - use tracing::warn; - - pub type PublicKeyBase = ::PublicKey; + use crate::crypto::generate_random; // Constants copied from the ring library const SCALAR_LEN: usize = 32; @@ -54,6 +60,8 @@ pub mod sign_ed25519 { const SIGNATURE_LEN: usize = ELEM_LEN + SCALAR_LEN; pub const ED25519_SIGNATURE_LEN: usize = SIGNATURE_LEN; + pub const ED25519_SEED_LEN: usize = 32; + fixed_bytes_wrapper!(pub struct Signature, ED25519_SIGNATURE_LEN, "Signature data"); fixed_bytes_wrapper!(pub struct PublicKey, ED25519_PUBLIC_KEY_LEN, "Public key data"); @@ -64,8 +72,49 @@ pub mod sign_ed25519 { pub struct SecretKey(#[serde(with = "crate::utils::serialize_utils::vec_codec")] Vec); impl SecretKey { + /// Constructs a `SecretKey` from the given PKCS8 document. + /// + /// ### Arguments + /// + /// * `slice` - a slice containing the encoded PKCS8 document pub fn from_slice(slice: &[u8]) -> Option { - Some(Self(slice.to_vec())) + match SecretKeyBase::from_pkcs8(slice) { + Ok(_) => Some(Self(slice.to_vec())), + Err(_) => None, + } + } + + /// Gets the public key corresponding to this secret key. + pub fn get_public_key(&self) -> PublicKey { + let keypair = SecretKeyBase::from_pkcs8(&self.0) + .expect("SecretKey contains invalid PKCS8 document?!?"); + + PublicKey::from_slice(keypair.public_key().as_ref()) + .expect("Keypair public key length is invalid?!?") + } + } + + impl From for SecretKey { + fn from(value: ring::pkcs8::Document) -> Self { + Self(value.as_ref().to_vec()) + } + } + + #[cfg(test)] + impl crate::utils::PlaceholderSeed for SecretKey { + fn placeholder_seed_parts<'a>(seed_parts: impl IntoIterator) -> Self { + gen_keypair_from_seed(&crate::utils::placeholder_bytes( + [ "SecretKey:".as_bytes() ].iter().copied().chain(seed_parts) + )).1 + } + } + + #[cfg(test)] + impl crate::utils::PlaceholderSeed for (PublicKey, SecretKey) { + fn placeholder_seed_parts<'a>(seed_parts: impl IntoIterator) -> Self { + gen_keypair_from_seed(&crate::utils::placeholder_bytes( + [ "(PublicKey, SecretKey):".as_bytes() ].iter().copied().chain(seed_parts) + )) } } @@ -81,83 +130,41 @@ pub mod sign_ed25519 { } pub fn sign_detached(msg: &[u8], sk: &SecretKey) -> Signature { - let secret = match SecretKeyBase::from_pkcs8(sk.as_ref()) { - Ok(secret) => secret, - Err(_) => { - warn!("Invalid secret key"); - return Signature([0; ED25519_SIGNATURE_LEN].into()); - } - }; + let keypair = SecretKeyBase::from_pkcs8(sk.as_ref()) + .expect("Invalid PKCS8 secret key?!?"); - let signature = match secret.sign(msg).as_ref().try_into() { - Ok(signature) => signature, - Err(_) => { - warn!("Invalid signature"); - return Signature([0; ED25519_SIGNATURE_LEN].into()); - } - }; + let signature = keypair.sign(msg).as_ref().try_into() + .expect("Invalid signature?!?"); Signature(signature) } - pub fn verify_append(sm: &[u8], pk: &PublicKey) -> bool { - if sm.len() > ED25519_SIGNATURE_LEN { - let start = sm.len() - ED25519_SIGNATURE_LEN; - let sig = Signature(match sm[start..].try_into() { - Ok(sig) => sig, - Err(_) => { - warn!("Invalid signature"); - return false; - } - }); - let msg = &sm[..start]; - verify_detached(&sig, msg, pk) - } else { - false - } - } - - pub fn sign_append(msg: &[u8], sk: &SecretKey) -> Vec { - let sig = sign_detached(msg, sk); - let mut sm = msg.to_vec(); - sm.extend_from_slice(sig.as_ref()); - sm + /// Generates a completely random Ed25519 keypair. + pub fn gen_keypair() -> (PublicKey, SecretKey) { + let seed = generate_random(); + gen_keypair_from_seed(&seed) } - pub fn gen_keypair() -> (PublicKey, SecretKey) { - let rand = ring::rand::SystemRandom::new(); - let pkcs8 = match SecretKeyBase::generate_pkcs8(&rand) { - Ok(pkcs8) => pkcs8, - Err(_) => { - warn!("Failed to generate secret key base for pkcs8"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); - } + /// Generates an Ed25519 keypair based on the given seed. + /// + /// ### Arguments + /// + /// * `seed` - the seed to generate the keypair from + fn gen_keypair_from_seed(seed: &[u8; ED25519_SEED_LEN]) -> (PublicKey, SecretKey) { + let rand = ring::test::rand::FixedSliceSequenceRandom { + bytes: &[ seed ], + current: core::cell::UnsafeCell::new(0), }; - let secret = match SecretKeyBase::from_pkcs8(pkcs8.as_ref()) { - Ok(secret) => secret, - Err(_) => { - warn!("Invalid secret key base"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); - } - }; + let pkcs8 = SecretKeyBase::generate_pkcs8(&rand) + .expect("Failed to generate secret key base for pkcs8"); - let pub_key_gen = match secret.public_key().as_ref().try_into() { - Ok(pub_key_gen) => pub_key_gen, - Err(_) => { - warn!("Invalid public key generation"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); - } - }; - let public = PublicKey(pub_key_gen); - let secret = match SecretKey::from_slice(pkcs8.as_ref()) { - Some(secret) => secret, - None => { - warn!("Invalid secret key"); - return (PublicKey([0; ED25519_PUBLIC_KEY_LEN].into()), SecretKey(vec![])); - } - }; + let keypair = SecretKeyBase::from_pkcs8(pkcs8.as_ref()) + .expect("Generated PKCS8 document is invalid?!?"); - (public, secret) + let public_key = PublicKey(keypair.public_key().as_ref().try_into() + .expect("Generated keypair contains an invalid public key?!?")); + let secret_key = pkcs8.into(); + (public_key, secret_key) } } @@ -210,11 +217,11 @@ pub mod secretbox_chacha20_poly1305 { } pub fn gen_key() -> Key { - Key(generate_random()) + Key(generate_random().into()) } pub fn gen_nonce() -> Nonce { - Nonce(generate_random()) + Nonce(generate_random().into()) } } @@ -243,12 +250,12 @@ pub mod pbkdf2 { } pub fn gen_salt() -> Salt { - Salt(generate_random()) + Salt(generate_random().into()) } } pub mod sha3_256 { - use std::convert::{TryFrom, TryInto}; + use std::convert::TryInto; use std::fmt::{Display, Formatter}; use std::ops::Deref; use std::str::FromStr; @@ -314,7 +321,7 @@ pub mod sha3_256 { } } -fn generate_random() -> FixedByteArray { +fn generate_random() -> [u8; N] { let mut value: [u8; N] = [0; N]; use ring::rand::SecureRandom; @@ -324,5 +331,5 @@ fn generate_random() -> FixedByteArray { Err(_) => warn!("Failed to generate random bytes"), }; - FixedByteArray::new(value) + value } From 675e0eba0f2b3f8a4d6da72b9f1e7d12cd262f2d Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Fri, 12 Jul 2024 11:18:52 +0200 Subject: [PATCH 16/30] crypto: add tests for object serialization --- src/crypto.rs | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/src/crypto.rs b/src/crypto.rs index 7e57887..25e2bd6 100644 --- a/src/crypto.rs +++ b/src/crypto.rs @@ -333,3 +333,82 @@ fn generate_random() -> [u8; N] { value } + +#[cfg(test)] +mod test { + use std::fmt::{Debug, Display}; + use std::str::FromStr; + use serde::Serialize; + use serde::de::DeserializeOwned; + use crate::utils::PlaceholderSeed; + use super::*; + + fn test_placeholders_different_seed() { + let [v0, v1] = W::placeholder_array_seed::<2>([]); + assert_eq!(v0, v0); + assert_eq!(v1, v1); + assert_ne!(v0, v1); + } + + fn test_fixed_bytes_wrapper + PlaceholderSeed + Serialize + DeserializeOwned>( + expected_placeholder_hex: &str, + ) { + let placeholder = W::placeholder_seed([]); + assert_eq!(placeholder.to_string(), expected_placeholder_hex); + assert_eq!(W::from_str(expected_placeholder_hex).unwrap(), placeholder); + + let expected_json = format!("\"{}\"", expected_placeholder_hex); + assert_eq!(serde_json::to_string(&placeholder).unwrap(), expected_json); + assert_eq!(serde_json::from_str::(&expected_json).unwrap(), placeholder); + + test_placeholders_different_seed::(); + } + + #[test] + fn test_ed25519_signature() { + test_fixed_bytes_wrapper::( + "9c4e2259fc9b47b4c4cf672c7436dc16ace2970955a002b69a495ca96d9dfaf026dbee622284a1cf306a1189af8a462d2ea498d10f14b637c848168b0ba698a7", + ); + } + + #[test] + fn test_ed25519_public_key() { + test_fixed_bytes_wrapper::( + "1d67f7de4c59192568f8e0381fcd1eb9ce044568e4670038e0b42e421540b4f6", + ); + } + + #[test] + fn test_ed25519_secret_key() { + assert_eq!(hex::encode(sign_ed25519::SecretKey::placeholder_seed([])), + "3053020101300506032b6570042204203651dccde39be8697d8e0690acd90e3b8ce7f596c5f205fbd0b3b3e3a68629e1a12303210092bc778f74110b3fcbcf8a4df71ed9a33c62faa8d01417d381745ef700ef6b73"); + + test_placeholders_different_seed::(); + } + + #[test] + fn test_ed25519_keypair() { + test_placeholders_different_seed::<(sign_ed25519::PublicKey, sign_ed25519::SecretKey)>(); + } + + #[test] + fn test_chacha20_key() { + test_fixed_bytes_wrapper::( + "d2678ac6abff79fa16d2a8f762a3c33b227a519ac1830aee33a19605b7f9cd35", + ); + } + + #[test] + fn test_chacha20_nonce() { + test_fixed_bytes_wrapper::( + "b0980a0a073d6b828fb48ad5", + ); + } + + #[test] + fn test_pbkdf2_salt() { + test_fixed_bytes_wrapper::( + "81c4a8cde605d6b51857eb6ebaead0de98cf254d4855725db7aec45a98699e9c", + ); + } +} From 305f3407a304622101b12e33d5470016e1ea5f83 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 11 Jul 2024 13:12:46 +0200 Subject: [PATCH 17/30] Add dedicated structs for addresses --- src/primitives/address.rs | 337 ++++++++++++++++++++++++++++++++++++++ src/primitives/mod.rs | 1 + 2 files changed, 338 insertions(+) create mode 100644 src/primitives/address.rs diff --git a/src/primitives/address.rs b/src/primitives/address.rs new file mode 100644 index 0000000..c164995 --- /dev/null +++ b/src/primitives/address.rs @@ -0,0 +1,337 @@ +use std::fmt::{Display, Formatter}; +use std::ops::Deref; +use std::str::FromStr; +use bincode::{Decode, Encode}; +use serde::{Deserialize, Deserializer, Serialize, Serializer}; +use crate::crypto::sha3_256; +use crate::crypto::sign_ed25519::PublicKey; +use crate::utils::serialize_utils::FixedByteArray; + +const STANDARD_ADDRESS_BYTES : usize = sha3_256::HASH_LEN; + +/// A standard 32-byte address. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)] +struct StandardAddress(FixedByteArray); + +impl StandardAddress { + /// Creates a new address out of the given SHA3-256 `Hash`. + fn new(hash: sha3_256::Hash) -> Self { + Self(hash.deref().into()) + } +} + +#[cfg(test)] +impl crate::utils::PlaceholderSeed for StandardAddress { + fn placeholder_seed_parts<'a>(seed_parts: impl IntoIterator) -> Self { + Self(crate::utils::placeholder_bytes(seed_parts).into()) + } +} + +impl Display for StandardAddress { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + f.write_str(&hex::encode(self.as_ref())) + } +} + +impl FromStr for StandardAddress { + type Err = hex::FromHexError; + + fn from_str(s: &str) -> Result { + s.parse().map(Self) + } +} + +impl AsRef<[u8]> for StandardAddress { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +make_error_type!( +#[derive(PartialEq)] +pub enum ParseAddressError { + BadPrefix(address: String); "Address \"{address}\" has unknown prefix", + ParseFailed(cause: hex::FromHexError); "{cause}"; cause, +}); + +macro_rules! standard_address_type { + ($doc:literal, $name:ident, $prefix:literal $(, wrap: $anyname:ident)?) => { + #[doc = $doc] + #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)] + pub struct $name { standard_address: StandardAddress } + + impl $name { + #[doc = "Gets the SHA3-256 hash which this address wraps"] + pub fn get_hash(&self) -> sha3_256::Hash { + sha3_256::Hash::from_slice(self.standard_address.as_ref()).unwrap() + } + } + + $( impl $name { + #[doc = "Wraps this address into an `AnyAddress`"] + pub fn wrap(self) -> AnyAddress { + AnyAddress::$anyname(self) + } + } + + impl From<$name> for AnyAddress { + fn from(value: $name) -> Self { + value.wrap() + } + } )? + + impl From for $name { + fn from(standard_address: StandardAddress) -> Self { + Self { standard_address } + } + } + + #[cfg(test)] + impl crate::utils::PlaceholderSeed for $name { + fn placeholder_seed_parts<'a>(seed_parts: impl IntoIterator) -> Self { + StandardAddress::placeholder_seed_parts( + [ concat!(stringify!($name), ":").as_bytes() ].iter().cloned().chain(seed_parts) + ).into() + } + } + + impl Display for $name { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + write!(f, concat!($prefix, "{}"), self.standard_address) + } + } + + impl FromStr for $name { + type Err = ParseAddressError; + + fn from_str(s: &str) -> Result { + if let Some(s) = s.strip_prefix($prefix) { + StandardAddress::from_str(s).map(Self::from).map_err(ParseAddressError::ParseFailed) + } else { + Err(ParseAddressError::BadPrefix(s.to_string())) + } + } + } + }; +} + +standard_address_type!("The type of address used for P2PKH outputs", P2PKHAddress, "", wrap: P2PKH); + +impl P2PKHAddress { + /// Creates a new P2PKH address from the hash of the given public key. + pub fn from_pubkey(public_key: &PublicKey) -> Self { + StandardAddress::new(sha3_256::digest(public_key.as_ref())).into() + } +} + +//standard_address_type!("The type of address used for P2SH outputs", P2SHAddress, "H", wrap: P2SH); + +standard_address_type!("The type of address used for DRUID input expectations outputs", TxInsAddress, ""); + +impl TxInsAddress { + /// Creates a new TxIns address from the hash of the given public key. + pub fn from_hash(hash: sha3_256::Hash) -> Self { + StandardAddress::new(hash).into() + } +} + +/// Wrapper enum representing an address of any type. +#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)] +pub enum AnyAddress { + Burn, + P2PKH(P2PKHAddress), + //P2SH(P2SHAddress), +} + +impl AnyAddress { + /// Identifies which sort of address this is. + pub fn sort(&self) -> AddressSort { + match self { + Self::P2PKH(_) => AddressSort::P2PKH, + Self::Burn => AddressSort::Burn, + } + } +} + +impl Display for AnyAddress { + fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { + match self { + AnyAddress::Burn => f.write_str("BURN"), + AnyAddress::P2PKH(address) => address.fmt(f), + //AnyAddress::P2SH(address) => address.fmt(f), + } + } +} + +impl FromStr for AnyAddress { + type Err = ParseAddressError; + + fn from_str(s: &str) -> Result { + if let Ok(addr) = P2PKHAddress::from_str(s) { + Ok(Self::P2PKH(addr)) + //} else if let Ok(addr) = P2SHAddress::from_str(s) { + // Ok(Self::P2SH(addr)) + } else if s == "BURN" { + Ok(Self::Burn) + } else { + Err(ParseAddressError::BadPrefix(s.to_string())) + } + } +} + +impl Serialize for AnyAddress { + fn serialize(&self, serializer: S) -> Result { + assert!(serializer.is_human_readable(), "serializer must be human-readable!"); + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for AnyAddress { + fn deserialize>(deserializer: D) -> Result { + assert!(deserializer.is_human_readable(), "deserializer must be human-readable!"); + + let text : String = serde::Deserialize::deserialize(deserializer)?; + text.parse().map_err(::custom) + } +} + +#[cfg(test)] +impl crate::utils::Placeholder for AnyAddress { + fn placeholder() -> Self { + Self::P2PKH(P2PKHAddress::placeholder()) + } +} + +make_trivial_enum!( +#[doc = "The different kinds of addresses."] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub enum AddressSort { + Burn, + P2PKH, + //P2SH, +} +all_variants=ALL_SORTS); + +/*---- TESTS ----*/ + +#[cfg(test)] +mod tests { + use crate::utils::PlaceholderSeed; + use super::*; + + #[test] + fn test_p2pkh_address() { + let pks : [PublicKey; 2] = PlaceholderSeed::placeholder_array_indexed(1); + + let pk_hashes = pks.each_ref().map(|pk| sha3_256::digest(pk.as_ref())); + let addresses = pks.each_ref().map(P2PKHAddress::from_pubkey); + + // The two addresses are generated from different public keys, so they should be distinct + assert_ne!(addresses[0], addresses[1]); + + // The string representation of each address should be equal to the hex-encoded pubkey hash + assert_eq!( + pk_hashes.each_ref().map(sha3_256::Hash::to_string), + addresses.each_ref().map(P2PKHAddress::to_string)); + + // Converting an address to a string and parsing it should give the same address + assert_eq!( + addresses.each_ref() + .map(|address| P2PKHAddress::from_str(&address.to_string()).unwrap()), + addresses); + } + + #[test] + fn test_p2pkh_address_parse() { + // Valid + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d"), + Ok(StandardAddress::new(sha3_256::digest(b"jeff")).into())); + + // Valid (upper-case hex chars) + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381D"), + Ok(StandardAddress::new(sha3_256::digest(b"jeff")).into())); + + // Too short (even) + assert_eq!( + P2PKHAddress::from_str(""), + Err(ParseAddressError::ParseFailed(hex::FromHexError::InvalidStringLength))); + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb038"), + Err(ParseAddressError::ParseFailed(hex::FromHexError::InvalidStringLength))); + + // Too short (odd) + assert_eq!( + P2PKHAddress::from_str("0"), + Err(ParseAddressError::ParseFailed(hex::FromHexError::OddLength))); + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381"), + Err(ParseAddressError::ParseFailed(hex::FromHexError::OddLength))); + + // Too long (even) + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d00"), + Err(ParseAddressError::ParseFailed(hex::FromHexError::InvalidStringLength))); + + // Too long (odd) + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d0"), + Err(ParseAddressError::ParseFailed(hex::FromHexError::OddLength))); + + // Non-hex chars + assert_eq!( + P2PKHAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381Z"), + Err(ParseAddressError::ParseFailed(hex::FromHexError::InvalidHexCharacter { + c: 'Z', + index: 63, + }))); + } + + #[test] + fn test_any_address_parse() { + // Valid + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d"), + Ok(AnyAddress::P2PKH(StandardAddress::new(sha3_256::digest(b"jeff")).into()))); + assert_eq!( + AnyAddress::from_str("BURN"), + Ok(AnyAddress::Burn)); + + // Valid (upper-case hex chars) + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381D"), + Ok(AnyAddress::P2PKH(StandardAddress::new(sha3_256::digest(b"jeff")).into()))); + + // Too short (even) + assert_eq!( + AnyAddress::from_str(""), + Err(ParseAddressError::BadPrefix("".to_owned()))); + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb038"), + Err(ParseAddressError::BadPrefix("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb038".to_owned()))); + + // Too short (odd) + assert_eq!( + AnyAddress::from_str("0"), + Err(ParseAddressError::BadPrefix("0".to_owned()))); + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381"), + Err(ParseAddressError::BadPrefix("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381".to_owned()))); + + // Too long (even) + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d00"), + Err(ParseAddressError::BadPrefix("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d00".to_owned()))); + + // Too long (odd) + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d0"), + Err(ParseAddressError::BadPrefix("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381d0".to_owned()))); + + // Non-hex chars + assert_eq!( + AnyAddress::from_str("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381Z"), + Err(ParseAddressError::BadPrefix("00f4381fd2f762b0b8532a4a4993be444f75bc8e57bf672c58effabeedb0381Z".to_owned()))); + } +} diff --git a/src/primitives/mod.rs b/src/primitives/mod.rs index bcde86f..1aca0ef 100644 --- a/src/primitives/mod.rs +++ b/src/primitives/mod.rs @@ -1,3 +1,4 @@ +pub mod address; pub mod asset; pub mod block; pub mod druid; From d73c25b07ad7bd6384a4be8bf854f58996a84ca8 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Tue, 21 May 2024 15:09:26 +0200 Subject: [PATCH 18/30] script: use bytes instead of strings --- src/script/interface_ops.rs | 73 +++++++++---- src/script/lang.rs | 16 +-- src/script/mod.rs | 3 +- src/utils/script_utils.rs | 189 +++++++++++++++++---------------- src/utils/test_utils.rs | 2 +- src/utils/transaction_utils.rs | 6 +- 6 files changed, 168 insertions(+), 121 deletions(-) diff --git a/src/script/interface_ops.rs b/src/script/interface_ops.rs index 7b9875b..ba2179c 100644 --- a/src/script/interface_ops.rs +++ b/src/script/interface_ops.rs @@ -601,7 +601,7 @@ pub fn op_cat(stack: &mut Stack) -> bool { error_item_size(op); return false; } - let cat = [s1, s2].join(""); + let cat = [s1, s2].concat(); stack.push(StackEntry::Bytes(cat)) } @@ -648,6 +648,10 @@ pub fn op_substr(stack: &mut Stack) -> bool { return false; } }; + // TODO: As this was previously a hex string, the indices don't exactly correspond to what + // they did originally. However, I don't think there are any existing transactions + // on the chain which actually use this opcode, so I'm fairly confident it won't + // matter. Double-check that this is the case before merging! if n1 >= s.len() { error_item_index(op); return false; @@ -660,7 +664,7 @@ pub fn op_substr(stack: &mut Stack) -> bool { error_item_index(op); return false; } - let substr = s[n1..n1 + n2].to_string(); + let substr = s[n1..n1 + n2].to_vec(); stack.push(StackEntry::Bytes(substr)) } @@ -700,7 +704,11 @@ pub fn op_left(stack: &mut Stack) -> bool { if n >= s.len() { stack.push(StackEntry::Bytes(s)) } else { - let left = s[..n].to_string(); + // TODO: As this was previously a hex string, the indices don't exactly correspond to what + // they did originally. However, I don't think there are any existing transactions + // on the chain which actually use this opcode, so I'm fairly confident it won't + // matter. Double-check that this is the case before merging! + let left = s[..n].to_vec(); stack.push(StackEntry::Bytes(left)) } } @@ -739,9 +747,13 @@ pub fn op_right(stack: &mut Stack) -> bool { } }; if n >= s.len() { - stack.push(StackEntry::Bytes("".to_string())) + stack.push(StackEntry::Bytes(Vec::new())) } else { - let right = s[n..].to_string(); + // TODO: As this was previously a hex string, the indices don't exactly correspond to what + // they did originally. However, I don't think there are any existing transactions + // on the chain which actually use this opcode, so I'm fairly confident it won't + // matter. Double-check that this is the case before merging! + let right = s[n..].to_vec(); stack.push(StackEntry::Bytes(right)) } } @@ -756,8 +768,8 @@ pub fn op_right(stack: &mut Stack) -> bool { pub fn op_size(stack: &mut Stack) -> bool { let (op, desc) = (OPSIZE, OPSIZE_DESC); trace(op, desc); - let s = match stack.last() { - Some(StackEntry::Bytes(s)) => s, + let len = match stack.last() { + Some(StackEntry::Bytes(s)) => s.len(), Some(_) => { error_item_type(op); return false; @@ -767,7 +779,11 @@ pub fn op_size(stack: &mut Stack) -> bool { return false; } }; - stack.push(StackEntry::Num(s.len())) + // TODO: As this was previously a hex string, the length doesn't exactly correspond to what + // it did originally. However, I don't think there are any existing transactions + // on the chain which actually use this opcode, so I'm fairly confident it won't + // matter. Double-check that this is the case before merging! + stack.push(StackEntry::Num(len)) } /*---- BITWISE LOGIC OPS ----*/ @@ -1924,7 +1940,11 @@ pub fn op_sha3(stack: &mut Stack) -> bool { let data = match stack.pop() { Some(StackEntry::Signature(sig)) => sig.as_ref().to_owned(), Some(StackEntry::PubKey(pk)) => pk.as_ref().to_owned(), - Some(StackEntry::Bytes(s)) => s.as_bytes().to_owned(), + Some(StackEntry::Bytes(s)) => { + // For legacy reasons, the hashed data is the hex representation of the data rather than + // the data itself. + hex::encode(&s).as_bytes().to_owned() + }, Some(_) => { error_item_type(op); return false; @@ -1934,7 +1954,8 @@ pub fn op_sha3(stack: &mut Stack) -> bool { return false; } }; - let hash = hex::encode(sha3_256::digest(&data)); + let hash = sha3_256::digest(&data).to_vec(); + // TODO: Originally, the hash was converted back to hex! stack.push(StackEntry::Bytes(hash)) } @@ -1960,7 +1981,7 @@ pub fn op_hash256(stack: &mut Stack) -> bool { } }; let addr = construct_address(&pk); - stack.push(StackEntry::Bytes(addr)) + stack.push(StackEntry::Bytes(hex::decode(addr).unwrap())) } /// OP_HASH256_V0: Creates v0 address from public key and pushes it onto the stack @@ -1989,7 +2010,7 @@ pub fn op_hash256_v0(stack: &mut Stack) -> bool { } }; let addr_v0 = construct_address_v0(&pk); - stack.push(StackEntry::Bytes(addr_v0)) + stack.push(StackEntry::Bytes(hex::decode(addr_v0).unwrap())) } /// OP_HASH256_TEMP: Creates temporary address from public key and pushes it onto the stack @@ -2018,7 +2039,7 @@ pub fn op_hash256_temp(stack: &mut Stack) -> bool { } }; let addr_temp = construct_address_temp(&pk); - stack.push(StackEntry::Bytes(addr_temp)) + stack.push(StackEntry::Bytes(hex::decode(addr_temp).unwrap())) } /// OP_CHECKSIG: Pushes ONE onto the stack if the signature is valid, ZERO otherwise @@ -2067,8 +2088,13 @@ pub fn op_checksig(stack: &mut Stack) -> bool { return false; } }; - trace!("Signature: {:?}", hex::encode(sig)); - if (!sign::verify_detached(&sig, msg.as_bytes(), &pk)) { + + // For legacy reasons, the signed message is the hex representation of the message rather than + // the message itself. + let msg_hex = hex::encode(msg); + + trace!("Signature: {:?}", msg_hex); + if (!sign::verify_detached(&sig, msg_hex.as_bytes(), &pk)) { trace!("Signature verification failed"); stack.push(StackEntry::Num(ZERO)) } else { @@ -2121,8 +2147,13 @@ pub fn op_checksigverify(stack: &mut Stack) -> bool { return false; } }; - trace!("Signature: {:?}", hex::encode(sig)); - if (!sign::verify_detached(&sig, msg.as_bytes(), &pk)) { + + // For legacy reasons, the signed message is the hex representation of the message rather than + // the message itself. + let msg_hex = hex::encode(msg); + + trace!("Signature: {:?}", msg_hex); + if (!sign::verify_detached(&sig, msg_hex.as_bytes(), &pk)) { trace!("Signature verification failed"); error_invalid_signature(op); return false; @@ -2298,13 +2329,17 @@ pub fn op_checkmultisigverify(stack: &mut Stack) -> bool { /// * `sigs` - signatures to verify /// * `msg` - data to verify against /// * `pks` - public keys to match against -fn verify_multisig(sigs: &[Signature], msg: &String, pks: &mut Vec) -> bool { +fn verify_multisig(sigs: &[Signature], msg: &[u8], pks: &mut Vec) -> bool { + // For legacy reasons, the signed message is the hex representation of the message rather than + // the message itself. + let msg_hex = hex::encode(msg); + let mut num_valid_sigs = ZERO; for sig in sigs { if let Some((index, _)) = pks .iter() .enumerate() - .find(|(_, pk)| sign::verify_detached(sig, msg.as_bytes(), pk)) + .find(|(_, pk)| sign::verify_detached(sig, msg_hex.as_bytes(), pk)) { num_valid_sigs += ONE; pks.remove(index); diff --git a/src/script/lang.rs b/src/script/lang.rs index 259465e..4ee2014 100644 --- a/src/script/lang.rs +++ b/src/script/lang.rs @@ -10,7 +10,6 @@ use crate::utils::error_utils::*; use crate::utils::transaction_utils::{construct_address, construct_address_for}; use bincode::serialize; use bytes::Bytes; -use hex::encode; use serde::{Deserialize, Serialize}; use tracing::{error, warn}; @@ -375,7 +374,7 @@ impl Script { StackEntry::Op(OpCodes::OP_CREATE), StackEntry::Num(block_number as usize), StackEntry::Op(OpCodes::OP_DROP), - StackEntry::Bytes(asset_hash), + StackEntry::Bytes(hex::decode(asset_hash).expect("asset_hash contains non-hex characters")), StackEntry::Signature(signature), StackEntry::PubKey(pub_key), StackEntry::Op(OpCodes::OP_CHECKSIG), @@ -391,7 +390,7 @@ impl Script { /// * `signature` - Signature of check data /// * `pub_key` - Public key of the payer pub fn pay2pkh( - check_data: String, + check_data: Vec, signature: Signature, pub_key: PublicKey, address_version: Option, @@ -407,7 +406,8 @@ impl Script { StackEntry::PubKey(pub_key), StackEntry::Op(OpCodes::OP_DUP), StackEntry::Op(op_hash_256), - StackEntry::Bytes(construct_address_for(&pub_key, address_version)), + StackEntry::Bytes(hex::decode(construct_address_for(&pub_key, address_version)) + .expect("address contains non-hex characters?")), StackEntry::Op(OpCodes::OP_EQUALVERIFY), StackEntry::Op(OpCodes::OP_CHECKSIG), ]; @@ -421,7 +421,7 @@ impl Script { /// * `check_data` - Data to be signed for verification /// * `pub_key` - Public key of this party /// * `signature` - Signature of this party - pub fn member_multisig(check_data: String, pub_key: PublicKey, signature: Signature) -> Self { + pub fn member_multisig(check_data: Vec, pub_key: PublicKey, signature: Signature) -> Self { let stack = vec![ StackEntry::Bytes(check_data), StackEntry::Signature(signature), @@ -439,7 +439,7 @@ impl Script { /// * `n` - Number of valid signatures total /// * `check_data` - Data to have checked against signatures /// * `pub_keys` - The constituent public keys - pub fn multisig_lock(m: usize, n: usize, check_data: String, pub_keys: Vec) -> Self { + pub fn multisig_lock(m: usize, n: usize, check_data: Vec, pub_keys: Vec) -> Self { let mut stack = vec![StackEntry::Bytes(check_data), StackEntry::Num(m)]; stack.append(&mut pub_keys.iter().map(|e| StackEntry::PubKey(*e)).collect()); stack.push(StackEntry::Num(n)); @@ -453,7 +453,7 @@ impl Script { /// /// * `check_data` - Data to have signed /// * `signatures` - Signatures to unlock with - pub fn multisig_unlock(check_data: String, signatures: Vec) -> Self { + pub fn multisig_unlock(check_data: Vec, signatures: Vec) -> Self { let mut stack = vec![StackEntry::Bytes(check_data)]; stack.append( &mut signatures @@ -475,7 +475,7 @@ impl Script { pub fn multisig_validation( m: usize, n: usize, - check_data: String, + check_data: Vec, signatures: Vec, pub_keys: Vec, ) -> Self { diff --git a/src/script/mod.rs b/src/script/mod.rs index 3063a46..e7c6ca2 100644 --- a/src/script/mod.rs +++ b/src/script/mod.rs @@ -12,8 +12,9 @@ pub enum StackEntry { Op(OpCodes), Signature(Signature), PubKey(PublicKey), + // TODO: This should probably be u64, as usize doesn't have a consistent range on all platforms Num(usize), - Bytes(String), + Bytes(Vec), } /// Opcodes enum diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index a2cd085..3fc07c1 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -193,7 +193,11 @@ pub fn tx_has_valid_create_script(script: &Script, asset: &Asset) -> bool { it.next(), it.next(), ) { - if b == &asset_hash && script.interpret() { + // For legacy reasons, the hashed data is the hex representation of the data rather than + // the data itself. + let b_hex = hex::encode(b); + + if b_hex == asset_hash && script.interpret() { return true; } } @@ -209,6 +213,7 @@ pub fn tx_has_valid_create_script(script: &Script, asset: &Asset) -> bool { /// * `script` - Script to validate /// * `outpoint_hash` - Hash of the corresponding outpoint /// * `tx_out_pub_key` - Public key of the previous tx_out +// TODO: The last two operands should be converted to the corresponding types fn tx_has_valid_p2pkh_sig(script: &Script, outpoint_hash: &str, tx_out_pub_key: &str) -> bool { let mut it = script.stack.iter(); @@ -238,7 +243,13 @@ fn tx_has_valid_p2pkh_sig(script: &Script, outpoint_hash: &str, tx_out_pub_key: it.next(), ) { debug!("b: {:?}, h: {:?}", b, h); - if h == tx_out_pub_key && b == outpoint_hash && script.interpret() { + + // For legacy reasons, the hashed data is the hex representation of the data rather than + // the data itself. + let h_hex = hex::encode(h); + let b_hex = hex::encode(b); + + if h_hex == tx_out_pub_key && b_hex == outpoint_hash && script.interpret() { return true; } } @@ -359,7 +370,7 @@ mod tests { assert_eq!(cond_stack.first_false_pos, Some(0)); /// error item type let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(String::new())); + stack.push(StackEntry::Bytes(Vec::new())); let mut cond_stack = ConditionStack::new(); let b = op_if(&mut stack, &mut cond_stack); assert!(!b); @@ -404,7 +415,7 @@ mod tests { assert_eq!(cond_stack.first_false_pos, Some(0)); /// error item type let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(String::new())); + stack.push(StackEntry::Bytes(Vec::new())); let mut cond_stack = ConditionStack::new(); let b = op_notif(&mut stack, &mut cond_stack); assert!(!b); @@ -847,7 +858,7 @@ mod tests { /// op_pick([1,"hello"]) -> fail let mut stack = Stack::new(); stack.push(StackEntry::Num(1)); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); let b = op_pick(&mut stack); assert!(!b); /// op_pick([1,1]) -> fail @@ -895,7 +906,7 @@ mod tests { /// op_roll([1,"hello"]) -> fail let mut stack = Stack::new(); stack.push(StackEntry::Num(1)); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); let b = op_roll(&mut stack); assert!(!b); /// op_roll([1,1]) -> fail @@ -977,36 +988,36 @@ mod tests { fn test_cat() { /// op_cat(["hello","world"]) -> ["helloworld"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); - stack.push(StackEntry::Bytes("world".to_string())); - let mut v: Vec = vec![StackEntry::Bytes("helloworld".to_string())]; + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); + stack.push(StackEntry::Bytes("world".as_bytes().to_vec())); + let mut v: Vec = vec![StackEntry::Bytes("helloworld".as_bytes().to_vec())]; op_cat(&mut stack); assert_eq!(stack.main_stack, v); /// op_cat(["hello",""]) -> ["hello"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); - stack.push(StackEntry::Bytes("".to_string())); - let mut v: Vec = vec![StackEntry::Bytes("hello".to_string())]; + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); + stack.push(StackEntry::Bytes("".as_bytes().to_vec())); + let mut v: Vec = vec![StackEntry::Bytes("hello".as_bytes().to_vec())]; op_cat(&mut stack); assert_eq!(stack.main_stack, v); /// op_cat(["a","a"*MAX_SCRIPT_ITEM_SIZE]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("a".to_string())); + stack.push(StackEntry::Bytes("a".as_bytes().to_vec())); let mut s = String::new(); for i in 1..=MAX_SCRIPT_ITEM_SIZE { s.push('a'); } - stack.push(StackEntry::Bytes(s.to_string())); + stack.push(StackEntry::Bytes(s.as_bytes().to_vec())); let b = op_cat(&mut stack); assert!(!b); /// op_cat(["hello"]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); let b = op_cat(&mut stack); assert!(!b); /// op_cat(["hello", 1]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(1)); let b = op_cat(&mut stack); assert!(!b) @@ -1017,62 +1028,62 @@ mod tests { fn test_substr() { /// op_substr(["hello",1,2]) -> ["el"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); for i in 1..=2 { stack.push(StackEntry::Num(i)); } - let mut v: Vec = vec![StackEntry::Bytes("el".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("el".as_bytes().to_vec())]; op_substr(&mut stack); assert_eq!(stack.main_stack, v); /// op_substr(["hello",0,0]) -> [""] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); for i in 1..=2 { stack.push(StackEntry::Num(0)); } - let mut v: Vec = vec![StackEntry::Bytes("".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("".as_bytes().to_vec())]; op_substr(&mut stack); assert_eq!(stack.main_stack, v); /// op_substr(["hello",0,5]) -> ["hello"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(0)); stack.push(StackEntry::Num(5)); - let mut v: Vec = vec![StackEntry::Bytes("hello".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("hello".as_bytes().to_vec())]; op_substr(&mut stack); assert_eq!(stack.main_stack, v); /// op_substr(["hello",5,0]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(5)); stack.push(StackEntry::Num(0)); let b = op_substr(&mut stack); assert!(!b); /// op_substr(["hello",1,5]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(1)); stack.push(StackEntry::Num(5)); let b = op_substr(&mut stack); assert!(!b); /// op_substr(["hello",1]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(1)); let b = op_substr(&mut stack); assert!(!b); /// op_substr(["hello",1,usize::MAX]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(1)); stack.push(StackEntry::Num(usize::MAX)); let b = op_substr(&mut stack); assert!(!b); /// op_substr(["hello",1,""]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(1)); - stack.push(StackEntry::Bytes("".to_string())); + stack.push(StackEntry::Bytes("".as_bytes().to_vec())); let b = op_substr(&mut stack); assert!(!b) } @@ -1082,34 +1093,34 @@ mod tests { fn test_left() { /// op_left(["hello",2]) -> ["he"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(2)); - let mut v: Vec = vec![StackEntry::Bytes("he".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("he".as_bytes().to_vec())]; op_left(&mut stack); assert_eq!(stack.main_stack, v); /// op_left(["hello",0]) -> [""] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(0)); - let mut v: Vec = vec![StackEntry::Bytes("".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("".as_bytes().to_vec())]; op_left(&mut stack); assert_eq!(stack.main_stack, v); /// op_left(["hello",5]) -> ["hello"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(5)); - let mut v: Vec = vec![StackEntry::Bytes("hello".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("hello".as_bytes().to_vec())]; op_left(&mut stack); assert_eq!(stack.main_stack, v); /// op_left(["hello",""]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); - stack.push(StackEntry::Bytes("".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); + stack.push(StackEntry::Bytes("".as_bytes().to_vec())); let b = op_left(&mut stack); assert!(!b); /// op_left(["hello"]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); let b = op_left(&mut stack); assert!(!b) } @@ -1119,34 +1130,34 @@ mod tests { fn test_right() { /// op_right(["hello",0]) -> ["hello"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(0)); - let mut v: Vec = vec![StackEntry::Bytes("hello".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("hello".as_bytes().to_vec())]; op_right(&mut stack); assert_eq!(stack.main_stack, v); /// op_right(["hello",2]) -> ["llo"] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(2)); - let mut v: Vec = vec![StackEntry::Bytes("llo".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("llo".as_bytes().to_vec())]; op_right(&mut stack); assert_eq!(stack.main_stack, v); /// op_right(["hello",5]) -> [""] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); stack.push(StackEntry::Num(5)); - let mut v: Vec = vec![StackEntry::Bytes("".to_string())]; + let mut v: Vec = vec![StackEntry::Bytes("".as_bytes().to_vec())]; op_right(&mut stack); assert_eq!(stack.main_stack, v); /// op_right(["hello",""]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); - stack.push(StackEntry::Bytes("".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); + stack.push(StackEntry::Bytes("".as_bytes().to_vec())); let b = op_right(&mut stack); assert!(!b); /// op_right(["hello"]) -> fail let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); let b = op_right(&mut stack); assert!(!b) } @@ -1156,15 +1167,15 @@ mod tests { fn test_size() { /// op_size(["hello"]) -> ["hello",5] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); let mut v: Vec = - vec![StackEntry::Bytes("hello".to_string()), StackEntry::Num(5)]; + vec![StackEntry::Bytes("hello".as_bytes().to_vec()), StackEntry::Num(5)]; op_size(&mut stack); assert_eq!(stack.main_stack, v); /// op_size([""]) -> ["",0] let mut stack = Stack::new(); - stack.push(StackEntry::Bytes("".to_string())); - let mut v: Vec = vec![StackEntry::Bytes("".to_string()), StackEntry::Num(0)]; + stack.push(StackEntry::Bytes("".as_bytes().to_vec())); + let mut v: Vec = vec![StackEntry::Bytes("".as_bytes().to_vec()), StackEntry::Num(0)]; op_size(&mut stack); assert_eq!(stack.main_stack, v); /// op_size([1]) -> fail @@ -1263,7 +1274,7 @@ mod tests { /// op_equal(["hello","hello"]) -> [1] let mut stack = Stack::new(); for i in 1..=2 { - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); } let mut v: Vec = vec![StackEntry::Num(1)]; op_equal(&mut stack); @@ -1289,7 +1300,7 @@ mod tests { /// op_equalverify(["hello","hello"]) -> [] let mut stack = Stack::new(); for i in 1..=2 { - stack.push(StackEntry::Bytes("hello".to_string())); + stack.push(StackEntry::Bytes("hello".as_bytes().to_vec())); } let mut v: Vec = vec![]; op_equalverify(&mut stack); @@ -1898,22 +1909,22 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let msg = hex::encode(vec![0, 0, 0]); let sig = sign::sign_detached(msg.as_bytes(), &sk); - let h = hex::encode(sha3_256::digest(sig.as_ref())); + let h = sha3_256::digest(sig.as_ref()).to_vec(); let mut stack = Stack::new(); stack.push(StackEntry::Signature(sig)); let mut v: Vec = vec![StackEntry::Bytes(h)]; op_sha3(&mut stack); assert_eq!(stack.main_stack, v); /// op_sha3([pk]) -> [sha3_256(pk)] - let h = hex::encode(sha3_256::digest(pk.as_ref())); + let h = sha3_256::digest(pk.as_ref()).to_vec(); let mut stack = Stack::new(); stack.push(StackEntry::PubKey(pk)); let mut v: Vec = vec![StackEntry::Bytes(h)]; op_sha3(&mut stack); assert_eq!(stack.main_stack, v); /// op_sha3(["hello"]) -> [sha3_256("hello")] - let s = "hello".to_string(); - let h = hex::encode(sha3_256::digest(s.as_bytes())); + let s = "hello".as_bytes().to_vec(); + let h = sha3_256::digest(hex::encode(&s).as_bytes()).to_vec(); let mut stack = Stack::new(); stack.push(StackEntry::Bytes(s)); let mut v: Vec = vec![StackEntry::Bytes(h)]; @@ -1937,7 +1948,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let mut stack = Stack::new(); stack.push(StackEntry::PubKey(pk)); - let mut v: Vec = vec![StackEntry::Bytes(construct_address(&pk))]; + let mut v: Vec = vec![StackEntry::Bytes(hex::decode(construct_address(&pk)).unwrap())]; op_hash256(&mut stack); assert_eq!(stack.main_stack, v); /// op_hash256([]) -> fail @@ -1953,7 +1964,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let mut stack = Stack::new(); stack.push(StackEntry::PubKey(pk)); - let mut v: Vec = vec![StackEntry::Bytes(construct_address_v0(&pk))]; + let mut v: Vec = vec![StackEntry::Bytes(hex::decode(construct_address_v0(&pk)).unwrap())]; op_hash256_v0(&mut stack); assert_eq!(stack.main_stack, v); /// op_hash256([]) -> fail @@ -1969,7 +1980,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let mut stack = Stack::new(); stack.push(StackEntry::PubKey(pk)); - let mut v: Vec = vec![StackEntry::Bytes(construct_address_temp(&pk))]; + let mut v: Vec = vec![StackEntry::Bytes(hex::decode(construct_address_temp(&pk)).unwrap())]; op_hash256_temp(&mut stack); assert_eq!(stack.main_stack, v); /// op_hash256([]) -> fail @@ -1986,7 +1997,7 @@ mod tests { let msg = hex::encode(vec![0, 0, 0]); let sig = sign::sign_detached(msg.as_bytes(), &sk); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig)); stack.push(StackEntry::PubKey(pk)); let mut v: Vec = vec![StackEntry::Num(1)]; @@ -1996,7 +2007,7 @@ mod tests { /// op_checksig([msg',sig,pk]) -> [0] let msg = hex::encode(vec![0, 0, 1]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig)); stack.push(StackEntry::PubKey(pk)); let mut v: Vec = vec![StackEntry::Num(0)]; @@ -2007,7 +2018,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig)); stack.push(StackEntry::PubKey(pk)); let mut v: Vec = vec![StackEntry::Num(0)]; @@ -2030,7 +2041,7 @@ mod tests { let msg = hex::encode(vec![0, 0, 0]); let sig = sign::sign_detached(msg.as_bytes(), &sk); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig)); stack.push(StackEntry::PubKey(pk)); let mut v: Vec = vec![]; @@ -2040,7 +2051,7 @@ mod tests { /// op_checksigverify([msg',sig,pk]) -> fail let msg = hex::encode(vec![0, 0, 1]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig)); stack.push(StackEntry::PubKey(pk)); let b = op_checksigverify(&mut stack); @@ -2050,7 +2061,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig)); stack.push(StackEntry::PubKey(pk)); let b = op_checksigverify(&mut stack); @@ -2076,7 +2087,7 @@ mod tests { let sig1 = sign::sign_detached(msg.as_bytes(), &sk1); let sig2 = sign::sign_detached(msg.as_bytes(), &sk2); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Signature(sig2)); stack.push(StackEntry::Num(2)); @@ -2091,7 +2102,7 @@ mod tests { /// op_checkmultisig([msg,0,pk1,pk2,pk3,3]) -> [1] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Num(0)); stack.push(StackEntry::PubKey(pk1)); stack.push(StackEntry::PubKey(pk2)); @@ -2104,7 +2115,7 @@ mod tests { /// op_checkmultisig([msg,0,0]) -> [1] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Num(0)); stack.push(StackEntry::Num(0)); let mut v: Vec = vec![StackEntry::Num(1)]; @@ -2114,7 +2125,7 @@ mod tests { /// op_checkmultisig([msg,sig1,1,pk1,1]) -> [1] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Num(1)); stack.push(StackEntry::PubKey(pk1)); @@ -2127,7 +2138,7 @@ mod tests { let msg = hex::encode(vec![0, 0, 0]); let sig3 = sign::sign_detached(msg.as_bytes(), &sk3); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig3)); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Num(2)); @@ -2142,7 +2153,7 @@ mod tests { /// op_checkmultisig([msg',sig1,sig2,2,pk1,pk2,pk3,3]) -> [0] let msg = hex::encode(vec![0, 0, 1]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Signature(sig2)); stack.push(StackEntry::Num(2)); @@ -2157,7 +2168,7 @@ mod tests { /// op_checkmultisig([msg,sig1,sig1,2,pk1,pk2,pk3,3]) -> [0] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Num(2)); @@ -2229,7 +2240,7 @@ mod tests { let sig1 = sign::sign_detached(msg.as_bytes(), &sk1); let sig2 = sign::sign_detached(msg.as_bytes(), &sk2); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Signature(sig2)); stack.push(StackEntry::Num(2)); @@ -2244,7 +2255,7 @@ mod tests { /// op_checkmultisigverify([msg,0,pk1,pk2,pk3,3]) -> [] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Num(0)); stack.push(StackEntry::PubKey(pk1)); stack.push(StackEntry::PubKey(pk2)); @@ -2257,7 +2268,7 @@ mod tests { /// op_checkmultisig([msg,0,0]) -> [] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Num(0)); stack.push(StackEntry::Num(0)); let mut v: Vec = vec![]; @@ -2267,7 +2278,7 @@ mod tests { /// op_checkmultisigverify([msg,sig1,1,pk1,1]) -> [] let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Num(1)); stack.push(StackEntry::PubKey(pk1)); @@ -2280,7 +2291,7 @@ mod tests { let msg = hex::encode(vec![0, 0, 0]); let sig3 = sign::sign_detached(msg.as_bytes(), &sk3); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig3)); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Num(2)); @@ -2295,7 +2306,7 @@ mod tests { /// op_checkmultisigverify([msg',sig1,sig2,2,pk1,pk2,pk3,3]) -> fail let msg = hex::encode(vec![0, 0, 1]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Signature(sig2)); stack.push(StackEntry::Num(2)); @@ -2309,7 +2320,7 @@ mod tests { /// op_checkmultisigverify([msg,sig1,sig1,2,pk1,pk2,pk3,3]) -> fail let msg = hex::encode(vec![0, 0, 0]); let mut stack = Stack::new(); - stack.push(StackEntry::Bytes(msg)); + stack.push(StackEntry::Bytes(hex::decode(msg).unwrap())); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Signature(sig1)); stack.push(StackEntry::Num(2)); @@ -2375,11 +2386,11 @@ mod tests { let script = Script::from(v); assert!(script.is_valid()); // script length <= 10000 bytes - let v = vec![StackEntry::Bytes("a".repeat(500)); 20]; + let v = vec![StackEntry::Bytes("a".repeat(500).as_bytes().to_vec()); 20]; let script = Script::from(v); assert!(script.is_valid()); // script length > 10000 bytes - let v = vec![StackEntry::Bytes("a".repeat(500)); 21]; + let v = vec![StackEntry::Bytes("a".repeat(500).as_bytes().to_vec()); 21]; let script = Script::from(v); assert!(!script.is_valid()); // # opcodes <= 201 @@ -2433,11 +2444,11 @@ mod tests { let script = Script::from(v); assert!(script.interpret()); // script length <= 10000 bytes - let v = vec![StackEntry::Bytes("a".repeat(500)); 20]; + let v = vec![StackEntry::Bytes("a".repeat(500).as_bytes().to_vec()); 20]; let script = Script::from(v); assert!(script.interpret()); // script length > 10000 bytes - let v = vec![StackEntry::Bytes("a".repeat(500)); 21]; + let v = vec![StackEntry::Bytes("a".repeat(500).as_bytes().to_vec()); 21]; let script = Script::from(v); assert!(!script.interpret()); // # opcodes <= 201 @@ -2642,7 +2653,7 @@ mod tests { new_tx_in.script_signature = Script::multisig_validation( m, entry.pub_keys.len(), - entry.previous_out.t_hash.clone(), + hex::decode(&entry.previous_out.t_hash).unwrap(), entry.signatures, entry.pub_keys, ); @@ -2661,7 +2672,7 @@ mod tests { for entry in tx_values { let mut new_tx_in = TxIn::new(); new_tx_in.script_signature = Script::member_multisig( - entry.previous_out.t_hash.clone(), + hex::decode(&entry.previous_out.t_hash).unwrap(), entry.pub_keys[0], entry.signatures[0], ); @@ -2953,7 +2964,7 @@ mod tests { new_tx_in .script_signature .stack - .push(StackEntry::Bytes("".to_string())); + .push(StackEntry::Bytes("".as_bytes().to_vec())); new_tx_in.previous_out = Some(entry.previous_out); tx_ins.push(new_tx_in); @@ -3080,19 +3091,19 @@ mod tests { // 0. Happy case: valid test ( vec![ - StackEntry::Bytes(valid_bytes), + StackEntry::Bytes(hex::decode(valid_bytes).unwrap()), StackEntry::Signature(valid_sig), StackEntry::PubKey(pk), StackEntry::Op(OpCodes::OP_DUP), StackEntry::Op(op_hash256), - StackEntry::Bytes(script_public_key), + StackEntry::Bytes(hex::decode(script_public_key).unwrap()), StackEntry::Op(OpCodes::OP_EQUALVERIFY), StackEntry::Op(OpCodes::OP_CHECKSIG), ], true, ), // 2. Empty script - (vec![StackEntry::Bytes("".to_string())], false), + (vec![StackEntry::Bytes("".as_bytes().to_vec())], false), ]; // diff --git a/src/utils/test_utils.rs b/src/utils/test_utils.rs index 0c8fcef..ebe46d4 100644 --- a/src/utils/test_utils.rs +++ b/src/utils/test_utils.rs @@ -66,7 +66,7 @@ pub fn generate_tx_with_ins_and_outs_assets( let signature = sign::sign_detached(signable_hash.as_bytes(), &sk); let tx_in = TxIn::new_from_input( tx_previous_out.clone(), - Script::pay2pkh(signable_hash, signature, pk, None), + Script::pay2pkh(hex::decode(&signable_hash).unwrap(), signature, pk, None), ); utxo_set.insert(tx_previous_out, tx_in_previous_out); tx.inputs.push(tx_in); diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index 8e9fd99..cae01cc 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -162,7 +162,7 @@ pub fn get_stack_entry_signable_string(entry: &StackEntry) -> String { } StackEntry::PubKey(pub_key) => format!("PubKey:{}", hex::encode(pub_key.as_ref())), StackEntry::Num(num) => format!("Num:{num}"), - StackEntry::Bytes(bytes) => format!("Bytes:{bytes}"), + StackEntry::Bytes(bytes) => format!("Bytes:{}", hex::encode(bytes)), } } @@ -578,7 +578,7 @@ pub fn update_input_signatures(tx_ins: &[TxIn], tx_outs: &[TxOut], key_material: let sk = &key_material.get(&previous_out.unwrap()).unwrap().1; let script_signature = Script::pay2pkh( - signable_hash.clone(), + hex::decode(&signable_hash).unwrap(), sign_detached(signable_hash.as_bytes(), sk), pk, None, @@ -1472,7 +1472,7 @@ mod tests { Signature::from_slice(hex::decode(signatures[n]).unwrap().as_ref()).unwrap(); let pk = PublicKey::from_slice(hex::decode(pub_keys[n]).unwrap().as_ref()).unwrap(); - let script = Script::pay2pkh(sig_data, sig, pk, None); + let script = Script::pay2pkh(hex::decode(&sig_data).unwrap(), sig, pk, None); let out_p = previous_out_points[n].clone(); TxIn::new_from_input(out_p, script) From 5bc9cc066b0631c2e8b4edca1562c03175dfaf42 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Tue, 21 May 2024 17:09:43 +0200 Subject: [PATCH 19/30] remove v0 and temp address types --- src/constants.rs | 12 -- src/primitives/transaction.rs | 1 - src/script/interface_ops.rs | 62 +--------- src/script/lang.rs | 14 +-- src/script/mod.rs | 4 +- src/utils/script_utils.rs | 208 ++------------------------------- src/utils/test_utils.rs | 2 +- src/utils/transaction_utils.rs | 184 +++-------------------------- 8 files changed, 29 insertions(+), 458 deletions(-) diff --git a/src/constants.rs b/src/constants.rs index 1946d20..fe6c6d2 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -5,7 +5,6 @@ pub const MAX_METADATA_BYTES: usize = 800; pub const TX_HASH_LENGTH: usize = 32; /*------- ADDRESS CONSTANTS -------*/ -pub const V0_ADDRESS_LENGTH: usize = 16; pub const STANDARD_ADDRESS_LENGTH: usize = 64; // Prepending character for a P2SH address pub const P2SH_PREPEND: u8 = b'H'; @@ -14,11 +13,6 @@ pub const P2SH_PREPEND: u8 = b'H'; // Current network version: Always bump immediately after a version is deployed. pub const NETWORK_VERSION: u32 = 6; pub const NETWORK_VERSION_SERIALIZED: &[u8] = b"6"; -// Network version 0 -pub const NETWORK_VERSION_V0: u64 = 0; -// Network version to support temporary address structure on wallet -// TODO: Deprecate after addresses retire -pub const NETWORK_VERSION_TEMP: u64 = 99999; /*------- VALUE HANDLING CONSTANTS --------*/ // Number of decimal places to divide to in display @@ -229,8 +223,6 @@ pub const OPWITHIN_DESC: &str = "Substitutes the three numbers on top of the the // crypto pub const OPSHA3: &str = "OP_SHA3"; pub const OPHASH256: &str = "OP_HASH256"; -pub const OPHASH256V0: &str = "OP_HASH256_V0"; -pub const OPHASH256TEMP: &str = "OP_HASH256_TEMP"; pub const OPCHECKSIG: &str = "OP_CHECKSIG"; pub const OPCHECKSIGVERIFY: &str = "OP_CHECKSIGVERIFY"; pub const OPCHECKMULTISIG: &str = "OP_CHECKMULTISIG"; @@ -239,10 +231,6 @@ pub const OPCHECKMULTISIGVERIFY: &str = "OP_CHECKMULTISIGVERIFY"; pub const OPSHA3_DESC: &str = "Hashes the top item on the stack using SHA3-256"; pub const OPHASH256_DESC: &str = "Creates standard address from public key and pushes it onto the stack"; -pub const OPHASH256V0_DESC: &str = - "Creates v0 address from public key and pushes it onto the stack"; -pub const OPHASH256TEMP_DESC: &str = - "Creates temporary address from public key and pushes it onto the stack"; pub const OPCHECKSIG_DESC: &str = "Pushes ONE onto the stack if the signature is valid, ZERO otherwise"; pub const OPCHECKSIGVERIFY_DESC: &str = "Runs OP_CHECKSIG and OP_VERIFY in sequence"; diff --git a/src/primitives/transaction.rs b/src/primitives/transaction.rs index 08435fd..284698a 100644 --- a/src/primitives/transaction.rs +++ b/src/primitives/transaction.rs @@ -35,7 +35,6 @@ pub struct TxConstructor { pub previous_out: OutPoint, pub signatures: Vec, pub pub_keys: Vec, - pub address_version: Option, } /// An outpoint - a combination of a transaction hash and an index n into its vout diff --git a/src/script/interface_ops.rs b/src/script/interface_ops.rs index ba2179c..a6222e2 100644 --- a/src/script/interface_ops.rs +++ b/src/script/interface_ops.rs @@ -8,9 +8,7 @@ use crate::primitives::transaction::*; use crate::script::lang::{ConditionStack, Script, Stack}; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; -use crate::utils::transaction_utils::{ - construct_address, construct_address_temp, construct_address_v0, -}; +use crate::utils::transaction_utils::construct_address; use bincode::de; use bincode::serialize; use bytes::Bytes; @@ -1984,64 +1982,6 @@ pub fn op_hash256(stack: &mut Stack) -> bool { stack.push(StackEntry::Bytes(hex::decode(addr).unwrap())) } -/// OP_HASH256_V0: Creates v0 address from public key and pushes it onto the stack -/// -/// Example: OP_HASH256_V0([pk]) -> [addr_v0] -/// -/// Info: Support for old 32-byte addresses -/// -/// TODO: Deprecate after addresses retire -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_hash256_v0(stack: &mut Stack) -> bool { - let (op, desc) = (OPHASH256V0, OPHASH256V0_DESC); - trace(op, desc); - let pk = match stack.pop() { - Some(StackEntry::PubKey(pk)) => pk, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let addr_v0 = construct_address_v0(&pk); - stack.push(StackEntry::Bytes(hex::decode(addr_v0).unwrap())) -} - -/// OP_HASH256_TEMP: Creates temporary address from public key and pushes it onto the stack -/// -/// Example: OP_HASH256_TEMP([pk]) -> [addr_temp] -/// -/// Info: Support for temporary address scheme used in wallet -/// -/// TODO: Deprecate after addresses retire -/// -/// ### Arguments -/// -/// * `stack` - mutable reference to the stack -pub fn op_hash256_temp(stack: &mut Stack) -> bool { - let (op, desc) = (OPHASH256TEMP, OPHASH256TEMP_DESC); - trace(op, desc); - let pk = match stack.pop() { - Some(StackEntry::PubKey(pk)) => pk, - Some(_) => { - error_item_type(op); - return false; - } - _ => { - error_num_items(op); - return false; - } - }; - let addr_temp = construct_address_temp(&pk); - stack.push(StackEntry::Bytes(hex::decode(addr_temp).unwrap())) -} - /// OP_CHECKSIG: Pushes ONE onto the stack if the signature is valid, ZERO otherwise /// /// Example: OP_CHECKSIG([msg, sig, pk]) -> [1] if Verify(sig, msg, pk) == 1 diff --git a/src/script/lang.rs b/src/script/lang.rs index 4ee2014..c784f4b 100644 --- a/src/script/lang.rs +++ b/src/script/lang.rs @@ -7,7 +7,7 @@ use crate::crypto::sign_ed25519::{ use crate::script::interface_ops::*; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; -use crate::utils::transaction_utils::{construct_address, construct_address_for}; +use crate::utils::transaction_utils::construct_address; use bincode::serialize; use bytes::Bytes; use serde::{Deserialize, Serialize}; @@ -311,8 +311,6 @@ impl Script { // crypto OpCodes::OP_SHA3 => test_for_return &= op_sha3(&mut stack), OpCodes::OP_HASH256 => test_for_return &= op_hash256(&mut stack), - OpCodes::OP_HASH256_V0 => test_for_return &= op_hash256_v0(&mut stack), - OpCodes::OP_HASH256_TEMP => test_for_return &= op_hash256_temp(&mut stack), OpCodes::OP_CHECKSIG => test_for_return &= op_checksig(&mut stack), OpCodes::OP_CHECKSIGVERIFY => { test_for_return &= op_checksigverify(&mut stack) @@ -393,20 +391,14 @@ impl Script { check_data: Vec, signature: Signature, pub_key: PublicKey, - address_version: Option, ) -> Self { - let op_hash_256 = match address_version { - Some(NETWORK_VERSION_V0) => OpCodes::OP_HASH256_V0, - Some(NETWORK_VERSION_TEMP) => OpCodes::OP_HASH256_TEMP, - _ => OpCodes::OP_HASH256, - }; let stack = vec![ StackEntry::Bytes(check_data), StackEntry::Signature(signature), StackEntry::PubKey(pub_key), StackEntry::Op(OpCodes::OP_DUP), - StackEntry::Op(op_hash_256), - StackEntry::Bytes(hex::decode(construct_address_for(&pub_key, address_version)) + StackEntry::Op(OpCodes::OP_HASH256), + StackEntry::Bytes(hex::decode(construct_address(&pub_key)) .expect("address contains non-hex characters?")), StackEntry::Op(OpCodes::OP_EQUALVERIFY), StackEntry::Op(OpCodes::OP_CHECKSIG), diff --git a/src/script/mod.rs b/src/script/mod.rs index e7c6ca2..aa3223b 100644 --- a/src/script/mod.rs +++ b/src/script/mod.rs @@ -109,8 +109,6 @@ pub enum OpCodes { // crypto OP_SHA3 = 0x90, OP_HASH256 = 0x91, - OP_HASH256_V0 = 0x92, - OP_HASH256_TEMP = 0x93, OP_CHECKSIG = 0x94, OP_CHECKSIGVERIFY = 0x95, OP_CHECKMULTISIG = 0x96, @@ -128,6 +126,8 @@ pub enum OpCodes { OP_NOP8 = 0xb7, OP_NOP9 = 0xb8, OP_NOP10 = 0xb9, + OP_NOP11 = 0x92, // Formerly OP_HASH256_V0 + OP_NOP12 = 0x93, // Formerly OP_HASH256_TEMP } impl OpCodes { diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index 3fc07c1..26f1c76 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -224,9 +224,7 @@ fn tx_has_valid_p2pkh_sig(script: &Script, outpoint_hash: &str, tx_out_pub_key: Some(StackEntry::Signature(_)), Some(StackEntry::PubKey(_)), Some(StackEntry::Op(OpCodes::OP_DUP)), - Some(StackEntry::Op( - OpCodes::OP_HASH256 | OpCodes::OP_HASH256_V0 | OpCodes::OP_HASH256_TEMP, - )), + Some(StackEntry::Op(OpCodes::OP_HASH256)), Some(StackEntry::Bytes(h)), Some(StackEntry::Op(OpCodes::OP_EQUALVERIFY)), Some(StackEntry::Op(OpCodes::OP_CHECKSIG)), @@ -1957,38 +1955,6 @@ mod tests { assert!(!b) } - #[test] - /// Test OP_HASH256_V0 - fn test_hash256_v0() { - /// op_hash256_v0([pk]) -> [addr_v0] - let (pk, sk) = sign::gen_keypair(); - let mut stack = Stack::new(); - stack.push(StackEntry::PubKey(pk)); - let mut v: Vec = vec![StackEntry::Bytes(hex::decode(construct_address_v0(&pk)).unwrap())]; - op_hash256_v0(&mut stack); - assert_eq!(stack.main_stack, v); - /// op_hash256([]) -> fail - let mut stack = Stack::new(); - let b = op_hash256_v0(&mut stack); - assert!(!b) - } - - #[test] - /// Test OP_HASH256_TEMP - fn test_hash256_temp() { - /// op_hash256_temp([pk]) -> [addr_temp] - let (pk, sk) = sign::gen_keypair(); - let mut stack = Stack::new(); - stack.push(StackEntry::PubKey(pk)); - let mut v: Vec = vec![StackEntry::Bytes(hex::decode(construct_address_temp(&pk)).unwrap())]; - op_hash256_temp(&mut stack); - assert_eq!(stack.main_stack, v); - /// op_hash256([]) -> fail - let mut stack = Stack::new(); - let b = op_hash256_temp(&mut stack); - assert!(!b) - } - #[test] /// Test OP_CHECKSIG fn test_checksig() { @@ -2723,22 +2689,6 @@ mod tests { #[test] /// Checks that correct member multisig scripts are validated as such fn test_pass_member_multisig_valid() { - test_pass_member_multisig_valid_common(None); - } - - #[test] - /// Checks that correct member multisig scripts are validated as such - fn test_pass_member_multisig_valid_v0() { - test_pass_member_multisig_valid_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that correct member multisig scripts are validated as such - fn test_pass_member_multisig_valid_temp() { - test_pass_member_multisig_valid_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_pass_member_multisig_valid_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let t_hash = hex::encode(vec![0, 0, 0]); let signature = sign::sign_detached(t_hash.as_bytes(), &sk); @@ -2747,7 +2697,6 @@ mod tests { previous_out: OutPoint::new(t_hash, 0), signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let tx_ins = create_multisig_member_tx_ins(vec![tx_const]); @@ -2758,22 +2707,6 @@ mod tests { #[test] /// Checks that incorrect member multisig scripts are validated as such fn test_fail_member_multisig_invalid() { - test_fail_member_multisig_invalid_common(None); - } - - #[test] - /// Checks that incorrect member multisig scripts are validated as such - fn test_fail_member_multisig_invalid_v0() { - test_fail_member_multisig_invalid_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that incorrect member multisig scripts are validated as such - fn test_fail_member_multisig_invalid_temp() { - test_fail_member_multisig_invalid_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_fail_member_multisig_invalid_common(address_version: Option) { let (_pk, sk) = sign::gen_keypair(); let (pk, _sk) = sign::gen_keypair(); let t_hash = hex::encode(vec![0, 0, 0]); @@ -2783,7 +2716,6 @@ mod tests { previous_out: OutPoint::new(t_hash, 0), signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let tx_ins = create_multisig_member_tx_ins(vec![tx_const]); @@ -2794,10 +2726,6 @@ mod tests { #[test] /// Checks that correct p2pkh transaction signatures are validated as such fn test_pass_p2pkh_sig_valid() { - test_pass_p2pkh_sig_valid_common(None); - } - - fn test_pass_p2pkh_sig_valid_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let outpoint = OutPoint { t_hash: hex::encode(vec![0, 0, 0]), @@ -2810,15 +2738,14 @@ mod tests { previous_out: outpoint, signatures: vec![], pub_keys: vec![pk], - address_version, }; let tx_outs = vec![]; let mut tx_ins = construct_payment_tx_ins(vec![tx_const]); tx_ins = update_input_signatures(&tx_ins, &tx_outs, &key_material); - + let hash_to_sign = construct_tx_in_out_signable_hash(&tx_ins[0], &tx_outs); - let tx_out_pk = construct_address_for(&pk, address_version); + let tx_out_pk = construct_address(&pk); assert!(tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, @@ -2830,16 +2757,6 @@ mod tests { #[test] /// Checks that invalid p2pkh transaction signatures are validated as such fn test_fail_p2pkh_sig_invalid() { - test_fail_p2pkh_sig_invalid_common(None); - } - - #[test] - /// Checks that invalid p2pkh transaction signatures are validated as such - fn test_fail_p2pkh_sig_invalid_v0() { - test_fail_p2pkh_sig_invalid_common(Some(NETWORK_VERSION_V0)); - } - - fn test_fail_p2pkh_sig_invalid_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let (second_pk, _s) = sign::gen_keypair(); let outpoint = OutPoint { @@ -2854,7 +2771,6 @@ mod tests { previous_out: outpoint, signatures: vec![signature], pub_keys: vec![second_pk], - address_version, }; let tx_ins = construct_payment_tx_ins(vec![tx_const]); @@ -2870,22 +2786,6 @@ mod tests { #[test] /// Checks that invalid p2pkh transaction signatures are validated as such fn test_fail_p2pkh_sig_script_empty() { - test_fail_p2pkh_sig_script_empty_common(None); - } - - #[test] - /// Checks that invalid p2pkh transaction signatures are validated as such - fn test_fail_p2pkh_sig_script_empty_v0() { - test_fail_p2pkh_sig_script_empty_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that invalid p2pkh transaction signatures are validated as such - fn test_fail_p2pkh_sig_script_empty_temp() { - test_fail_p2pkh_sig_script_empty_common(Some(NETWORK_VERSION_V0)); - } - - fn test_fail_p2pkh_sig_script_empty_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let outpoint = OutPoint { t_hash: hex::encode(vec![0, 0, 0]), @@ -2899,7 +2799,6 @@ mod tests { previous_out: outpoint, signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let mut tx_ins = Vec::new(); @@ -2924,22 +2823,6 @@ mod tests { #[test] /// Checks that invalid p2pkh transaction signatures are validated as such fn test_fail_p2pkh_sig_script_invalid_struct() { - test_fail_p2pkh_sig_script_invalid_struct_common(None); - } - - #[test] - /// Checks that invalid p2pkh transaction signatures are validated as such - fn test_fail_p2pkh_sig_script_invalid_struct_v0() { - test_fail_p2pkh_sig_script_invalid_struct_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that invalid p2pkh transaction signatures are validated as such - fn test_fail_p2pkh_sig_script_invalid_struct_temp() { - test_fail_p2pkh_sig_script_invalid_struct_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_fail_p2pkh_sig_script_invalid_struct_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let outpoint = OutPoint { t_hash: hex::encode(vec![0, 0, 0]), @@ -2953,7 +2836,6 @@ mod tests { previous_out: outpoint, signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let mut tx_ins = Vec::new(); @@ -2982,22 +2864,6 @@ mod tests { #[test] /// Checks that correct multisig validation signatures are validated as such fn test_pass_multisig_validation_valid() { - test_pass_multisig_validation_valid_common(None); - } - - #[test] - /// Checks that correct multisig validation signatures are validated as such - fn test_pass_multisig_validation_valid_v0() { - test_pass_multisig_validation_valid_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that correct multisig validation signatures are validated as such - fn test_pass_multisig_validation_valid_temp() { - test_pass_multisig_validation_valid_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_pass_multisig_validation_valid_common(address_version: Option) { let (first_pk, first_sk) = sign::gen_keypair(); let (second_pk, second_sk) = sign::gen_keypair(); let (third_pk, third_sk) = sign::gen_keypair(); @@ -3011,7 +2877,6 @@ mod tests { previous_out: OutPoint::new(check_data, 0), signatures: vec![first_sig, second_sig], pub_keys: vec![first_pk, second_pk, third_pk], - address_version, }; let tx_ins = create_multisig_tx_ins(vec![tx_const], m); @@ -3022,48 +2887,25 @@ mod tests { #[test] /// Validate tx_is_valid for multiple TxIn configurations fn test_tx_is_valid() { - test_tx_is_valid_common(None, OpCodes::OP_HASH256, None, false); - } - - #[test] - /// Validate tx_is_valid for multiple TxIn configurations - fn test_tx_is_valid_v0() { - test_tx_is_valid_common( - Some(NETWORK_VERSION_V0), - OpCodes::OP_HASH256_V0, - None, - false, - ); - } - - #[test] - /// Validate tx_is_valid for multiple TxIn configurations - fn test_tx_is_valid_temp() { - test_tx_is_valid_common( - Some(NETWORK_VERSION_TEMP), - OpCodes::OP_HASH256_TEMP, - None, - false, - ); + test_tx_is_valid_common(OpCodes::OP_HASH256, None, false); } #[test] /// Validate tx_is_valid for locktime fn test_tx_is_valid_locktime() { assert!( - test_tx_is_valid_common(None, OpCodes::OP_HASH256, Some(99), false) - && !test_tx_is_valid_common(None, OpCodes::OP_HASH256, Some(1000000000), false) + test_tx_is_valid_common(OpCodes::OP_HASH256, Some(99), false) + && !test_tx_is_valid_common(OpCodes::OP_HASH256, Some(1000000000), false) ); } #[test] /// Validate tx_is_valid for fees fn test_tx_is_valid_fees() { - test_tx_is_valid_common(None, OpCodes::OP_HASH256, None, true); + test_tx_is_valid_common(OpCodes::OP_HASH256, None, true); } fn test_tx_is_valid_common( - address_version: Option, op_hash256: OpCodes, locktime: Option, with_fees: bool, @@ -3074,7 +2916,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let tx_hash = hex::encode(vec![0, 0, 0]); let tx_outpoint = OutPoint::new(tx_hash, 0); - let script_public_key = construct_address_for(&pk, address_version); + let script_public_key = construct_address(&pk); let tx_in_previous_out = TxOut::new_token_amount(script_public_key.clone(), TokenAmount(5), locktime); let ongoing_tx_outs = vec![tx_in_previous_out.clone()]; @@ -3295,22 +3137,6 @@ mod tests { #[test] /// Checks that incorrect member interpret scripts are validated as such fn test_fail_interpret_valid() { - test_fail_interpret_valid_common(None); - } - - #[test] - /// Checks that incorrect member interpret scripts are validated as such - fn test_fail_interpret_valid_v0() { - test_fail_interpret_valid_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that incorrect member interpret scripts are validated as such - fn test_fail_interpret_valid_temp() { - test_fail_interpret_valid_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_fail_interpret_valid_common(address_version: Option) { let (_pk, sk) = sign::gen_keypair(); let (pk, _sk) = sign::gen_keypair(); let t_hash = hex::encode(vec![0, 0, 0]); @@ -3320,7 +3146,6 @@ mod tests { previous_out: OutPoint::new(t_hash, 0), signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let tx_ins = create_multisig_member_tx_ins(vec![tx_const]); @@ -3331,22 +3156,6 @@ mod tests { #[test] /// Checks that interpret scripts are validated as such fn test_pass_interpret_valid() { - test_pass_interpret_valid_common(None); - } - - #[test] - /// Checks that interpret scripts are validated as such - fn test_pass_interpret_valid_v0() { - test_pass_interpret_valid_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - /// Checks that interpret scripts are validated as such - fn test_pass_interpret_valid_temp() { - test_pass_interpret_valid_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_pass_interpret_valid_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let t_hash = hex::encode(vec![0, 0, 0]); let signature = sign::sign_detached(t_hash.as_bytes(), &sk); @@ -3355,7 +3164,6 @@ mod tests { previous_out: OutPoint::new(t_hash, 0), signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let tx_ins = create_multisig_member_tx_ins(vec![tx_const]); diff --git a/src/utils/test_utils.rs b/src/utils/test_utils.rs index ebe46d4..e7139f8 100644 --- a/src/utils/test_utils.rs +++ b/src/utils/test_utils.rs @@ -66,7 +66,7 @@ pub fn generate_tx_with_ins_and_outs_assets( let signature = sign::sign_detached(signable_hash.as_bytes(), &sk); let tx_in = TxIn::new_from_input( tx_previous_out.clone(), - Script::pay2pkh(hex::decode(&signable_hash).unwrap(), signature, pk, None), + Script::pay2pkh(hex::decode(&signable_hash).unwrap(), signature, pk), ); utxo_set.insert(tx_previous_out, tx_in_previous_out); tx.inputs.push(tx_in); diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index cae01cc..4c70f56 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -31,20 +31,6 @@ pub fn construct_p2sh_address(script: &Script) -> String { addr } -/// Builds an address from a public key and a specified network version -/// -/// ### Arguments -/// -/// * `pub_key` - A public key to build an address from -/// * `address_version` - Network version to use for the address -pub fn construct_address_for(pub_key: &PublicKey, address_version: Option) -> String { - match address_version { - Some(NETWORK_VERSION_V0) => construct_address_v0(pub_key), - Some(NETWORK_VERSION_TEMP) => construct_address_temp(pub_key), - _ => construct_address(pub_key), - } -} - /// Builds an address from a public key /// /// ### Arguments @@ -54,58 +40,6 @@ pub fn construct_address(pub_key: &PublicKey) -> String { hex::encode(sha3_256::digest(pub_key.as_ref())) } -/// Builds an old (network version 0) address from a public key -/// -/// ### Arguments -/// -/// * `pub_key` - A public key to build an address from -pub fn construct_address_v0(pub_key: &PublicKey) -> String { - let first_pubkey_bytes = { - // We used sodiumoxide serialization before with a 64 bit length prefix. - // Make clear what we are using as this was not intended. - let mut v = vec![32, 0, 0, 0, 0, 0, 0, 0]; - v.extend_from_slice(pub_key.as_ref()); - v - }; - let mut first_hash = sha3_256::digest(&first_pubkey_bytes).to_vec(); - first_hash.truncate(V0_ADDRESS_LENGTH); - hex::encode(first_hash) -} - -/// Builds an address from a public key using the -/// temporary address scheme present on the wallet -/// -/// TODO: Deprecate after addresses retire -/// -/// ### Arguments -/// -/// * `pub_key` - A public key to build an address from -pub fn construct_address_temp(pub_key: &PublicKey) -> String { - let base64_encoding = base64::encode(pub_key.as_ref()); - let hex_decoded = decode_base64_as_hex(&base64_encoding); - hex::encode(sha3_256::digest(&hex_decoded)) -} - -/// Decodes a base64 encoded string as hex, invalid character pairs are decoded up to the -/// first character. If the decoding up to the first character fails, a default value of 0 -/// is used. -/// -/// TODO: Deprecate after addresses retire -/// -/// ### Arguments -/// -/// * `s` - Base64 encoded string -pub fn decode_base64_as_hex(s: &str) -> Vec { - (ZERO..s.len()) - .step_by(TWO) - .map(|i| { - u8::from_str_radix(&s[i..i + TWO], SIXTEEN as u32) - .or_else(|_| u8::from_str_radix(&s[i..i + ONE], SIXTEEN as u32)) - .unwrap_or_default() - }) - .collect() -} - /// Constructs signable string for OutPoint /// /// ### Arguments @@ -581,7 +515,6 @@ pub fn update_input_signatures(tx_ins: &[TxIn], tx_outs: &[TxOut], key_material: hex::decode(&signable_hash).unwrap(), sign_detached(signable_hash.as_bytes(), sk), pk, - None, ); tx_in.script_signature = script_signature; @@ -740,25 +673,7 @@ mod tests { use crate::script::OpCodes; use crate::utils::script_utils::{tx_has_valid_p2sh_script, tx_outs_are_valid}; - #[test] - // Creates a valid payment transaction - fn test_construct_a_valid_payment_tx() { - test_construct_a_valid_payment_tx_common(None); - } - - #[test] - // Creates a valid payment transaction - fn test_construct_a_valid_payment_tx_v0() { - test_construct_a_valid_payment_tx_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - // Creates a valid payment transaction - fn test_construct_a_valid_payment_tx_temp() { - test_construct_a_valid_payment_tx_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_construct_valid_inputs(address_version: Option) -> (Vec, String, BTreeMap) { + fn test_construct_valid_inputs() -> (Vec, String, BTreeMap) { let (_pk, sk) = sign::gen_keypair(); let (pk, _sk) = sign::gen_keypair(); let t_hash = vec![0, 0, 0]; @@ -773,7 +688,6 @@ mod tests { previous_out: prev_out, signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let tx_ins = construct_payment_tx_ins(vec![tx_const]); @@ -784,7 +698,7 @@ mod tests { #[test] fn test_construct_a_valid_p2sh_tx() { let token_amount = TokenAmount(400000); - let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(Some(NETWORK_VERSION_V0)); + let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(); let mut script = Script::new_for_coinbase(10); script.stack.push(StackEntry::Op(OpCodes::OP_DROP)); @@ -796,7 +710,6 @@ mod tests { previous_out: OutPoint::new(spending_tx_hash, 0), signatures: vec![], pub_keys: vec![], - address_version: Some(NETWORK_VERSION_V0), }; let redeeming_tx_ins = construct_p2sh_redeem_tx_ins(tx_const, script.clone()); @@ -826,7 +739,7 @@ mod tests { #[test] fn test_construct_a_valid_burn_tx() { let token_amount = TokenAmount(400000); - let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(Some(NETWORK_VERSION_V0)); + let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(); let burn_tx = construct_burn_tx(tx_ins, None, &key_material); @@ -836,7 +749,6 @@ mod tests { previous_out: OutPoint::new(spending_tx_hash, 0), signatures: vec![], pub_keys: vec![], - address_version: Some(NETWORK_VERSION_V0), }; let s = vec![StackEntry::Op(OpCodes::OP_BURN)]; @@ -867,8 +779,9 @@ mod tests { // TODO: Add assertion for full tx validity } - fn test_construct_a_valid_payment_tx_common(address_version: Option) { - let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(address_version); + #[test] + fn test_construct_a_valid_payment_tx() { + let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(); let token_amount = TokenAmount(400000); let payment_tx = construct_payment_tx( @@ -891,7 +804,7 @@ mod tests { #[test] /// Creates a valid payment transaction including fees fn test_construct_valid_payment_tx_with_fees() { - let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(None); + let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(); let token_amount = TokenAmount(400000); let fee_amount = TokenAmount(1000); @@ -929,7 +842,6 @@ mod tests { previous_out: prev_out.clone(), signatures: vec![signature], pub_keys: vec![pk], - address_version: Some(2), }; let tx_ins = construct_payment_tx_ins(vec![tx_const]); @@ -972,7 +884,6 @@ mod tests { previous_out: prev_out.clone(), signatures: vec![signature], pub_keys: vec![pk], - address_version: Some(2), }; let drs_tx_hash = "item_tx_hash".to_string(); @@ -1020,7 +931,6 @@ mod tests { previous_out: prev_out.clone(), signatures: vec![signature], pub_keys: vec![pk], - address_version: Some(2), }; let genesis_hash = "item_tx_hash".to_string(); @@ -1052,22 +962,6 @@ mod tests { #[test] // Creates a valid UTXO set fn test_construct_valid_utxo_set() { - test_construct_valid_utxo_set_common(None); - } - - #[test] - // Creates a valid UTXO set - fn test_construct_valid_utxo_set_v0() { - test_construct_valid_utxo_set_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - // Creates a valid UTXO set - fn test_construct_valid_utxo_set_temp() { - test_construct_valid_utxo_set_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_construct_valid_utxo_set_common(address_version: Option) { let (pk, sk) = sign::gen_keypair(); let t_hash_1 = hex::encode(vec![0, 0, 0]); @@ -1082,7 +976,6 @@ mod tests { previous_out: OutPoint::new("".to_string(), 0), signatures: vec![signed], pub_keys: vec![pk], - address_version, }; let token_amount = TokenAmount(400000); @@ -1106,7 +999,6 @@ mod tests { previous_out: tx_1_out_p.clone(), signatures: vec![signed], pub_keys: vec![pk], - address_version, }; let tx_ins_2 = construct_payment_tx_ins(vec![tx_2]); let tx_outs = vec![TxOut::new_token_amount( @@ -1134,22 +1026,6 @@ mod tests { #[test] // Creates a valid DDE transaction fn test_construct_a_valid_dde_tx() { - test_construct_a_valid_dde_tx_common(None); - } - - #[test] - // Creates a valid DDE transaction - fn test_construct_a_valid_dde_tx_v0() { - test_construct_a_valid_dde_tx_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - // Creates a valid DDE transaction - fn test_construct_a_valid_dde_tx_temp() { - test_construct_a_valid_dde_tx_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_construct_a_valid_dde_tx_common(address_version: Option) { let (_pk, sk) = sign::gen_keypair(); let (pk, _sk) = sign::gen_keypair(); let t_hash = hex::encode(vec![0, 0, 0]); @@ -1169,7 +1045,6 @@ mod tests { previous_out: prev_out.clone(), signatures: vec![signature], pub_keys: vec![pk], - address_version, }; let tx_ins = construct_payment_tx_ins(vec![tx_const]); @@ -1312,22 +1187,6 @@ mod tests { #[test] // Test valid address construction; should correlate with test on wallet fn test_construct_valid_addresses() { - test_construct_valid_addresses_common(None); - } - - #[test] - // Test valid address construction; should correlate with test on wallet - fn test_construct_valid_addresses_v0() { - test_construct_valid_addresses_common(Some(NETWORK_VERSION_V0)); - } - - #[test] - // Test valid address construction; should correlate with test on wallet - fn test_construct_valid_addresses_temp() { - test_construct_valid_addresses_common(Some(NETWORK_VERSION_TEMP)); - } - - fn test_construct_valid_addresses_common(address_version: Option) { // // Arrange // @@ -1346,32 +1205,17 @@ mod tests { // let actual_pub_addresses: Vec = pub_keys .iter() - .map(|pub_key| construct_address_for(pub_key, address_version)) + .map(construct_address) .collect(); // // Assert // - let expected_pub_addresses = match address_version { - // Old Address structure - Some(NETWORK_VERSION_V0) => vec![ - "13bd3351b78beb2d0dadf2058dcc926c", - "abc7c0448465c4507faf2ee588728824", - "6ae52e3870884ab66ec49d3bb359c0bf", - ], - // Temporary address structure present on wallet - Some(NETWORK_VERSION_TEMP) => vec![ - "6c6b6e8e9df8c63d22d9eb687b9671dd1ce5d89f195bb2316e1b1444848cd2b3", - "8ac2fdcb0688abb2727d63ed230665b275a1d3a28373baa92a9afa5afd610e9f", - "0becdaaf6a855f04961208ee992651c11df0be91c08629dfc079d05d2915ec22", - ], - // Current address structure - _ => vec![ - "5423e6bd848e0ce5cd794e55235c23138d8833633cd2d7de7f4a10935178457b", - "77516e2d91606250e625546f86702510d2e893e4a27edfc932fdba03c955cc1b", - "4cfd64a6692021fc417368a866d33d94e1c806747f61ac85e0b3935e7d5ed925", - ], - }; + let expected_pub_addresses = vec![ + "5423e6bd848e0ce5cd794e55235c23138d8833633cd2d7de7f4a10935178457b", + "77516e2d91606250e625546f86702510d2e893e4a27edfc932fdba03c955cc1b", + "4cfd64a6692021fc417368a866d33d94e1c806747f61ac85e0b3935e7d5ed925", + ]; assert_eq!(actual_pub_addresses, expected_pub_addresses); } @@ -1472,7 +1316,7 @@ mod tests { Signature::from_slice(hex::decode(signatures[n]).unwrap().as_ref()).unwrap(); let pk = PublicKey::from_slice(hex::decode(pub_keys[n]).unwrap().as_ref()).unwrap(); - let script = Script::pay2pkh(hex::decode(&sig_data).unwrap(), sig, pk, None); + let script = Script::pay2pkh(hex::decode(&sig_data).unwrap(), sig, pk); let out_p = previous_out_points[n].clone(); TxIn::new_from_input(out_p, script) From f5411dc8f0e26ef087080e3e599d2794bf7bd1b6 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 12:37:38 +0200 Subject: [PATCH 20/30] Completely disable all P2SH code P2SH doesn't work yet, I don't want it to exist at all for the time being --- src/primitives/transaction.rs | 24 ------------------------ src/utils/script_utils.rs | 8 +++++--- src/utils/transaction_utils.rs | 33 +++++++++++++++++++++------------ 3 files changed, 26 insertions(+), 39 deletions(-) diff --git a/src/primitives/transaction.rs b/src/primitives/transaction.rs index 08435fd..ba2a2b9 100644 --- a/src/primitives/transaction.rs +++ b/src/primitives/transaction.rs @@ -164,16 +164,6 @@ impl TxOut { _ => panic!("Cannot create TxOut for asset of type {:?}", asset), } } - - /// Returns whether current tx_out is a P2SH - pub fn is_p2sh_tx_out(&self) -> bool { - if let Some(pk) = &self.script_public_key { - let pk_bytes = pk.as_bytes(); - return pk_bytes[0] == P2SH_PREPEND; - } - - false - } } /// The basic transaction that is broadcasted on the network and contained in @@ -236,18 +226,4 @@ impl Transaction { .map(|a| !a.is_token()) .unwrap_or_default() } - - /// Returns whether current transaction is a P2SH tx - pub fn is_p2sh_tx(&self) -> bool { - if self.outputs.len() != 1 { - return false; - } - - if let Some(pk) = &self.outputs[0].script_public_key { - let pk_bytes = pk.as_bytes(); - return pk_bytes[0] == P2SH_PREPEND; - } - - false - } } diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index a2cd085..f286b28 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -95,7 +95,7 @@ pub fn tx_is_valid<'a>( if let Some(pk) = tx_out_pk { // Check will need to include other signature types here if !tx_has_valid_p2pkh_sig(&tx_in.script_signature, &full_tx_hash, pk) - && !tx_has_valid_p2sh_script(&tx_in.script_signature, pk) + // TODO: jrabil: P2SH && !tx_has_valid_p2sh_script(&tx_in.script_signature, pk) { error!("INVALID SIGNATURE OR SCRIPT TYPE"); return false; @@ -258,8 +258,9 @@ fn tx_has_valid_p2pkh_sig(script: &Script, outpoint_hash: &str, tx_out_pub_key: /// /// * `script` - Script to validate /// * `address` - Address of the P2SH transaction +#[allow(unused_variables)] pub fn tx_has_valid_p2sh_script(script: &Script, address: &str) -> bool { - let p2sh_address = construct_p2sh_address(script); + /*let p2sh_address = construct_p2sh_address(script); if p2sh_address == address { return script.interpret(); @@ -271,7 +272,8 @@ pub fn tx_has_valid_p2sh_script(script: &Script, address: &str) -> bool { address ); - false + false*/ + todo!("P2SH not yet supported!") } /// Checks that a item's metadata conforms to the network size constraint diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index 8e9fd99..c03047b 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -20,15 +20,17 @@ pub struct ReceiverInfo { /// ### Arguments /// /// * `script` - Script to build address for +#[allow(unused_variables)] pub fn construct_p2sh_address(script: &Script) -> String { - let bytes = match serialize(script) { + /*let bytes = match serialize(script) { Ok(bytes) => bytes, Err(_) => vec![], }; let mut addr = hex::encode(sha3_256::digest(&bytes)); addr.insert(ZERO, P2SH_PREPEND as char); addr.truncate(STANDARD_ADDRESS_LENGTH); - addr + addr*/ + todo!("P2SH not yet supported!") } /// Builds an address from a public key and a specified network version @@ -448,6 +450,7 @@ pub fn construct_payment_tx( /// * `drs_block_hash` - Hash of the block containing the original DRS. Only for data trades /// * `asset` - Asset to send /// * `locktime` - Block height below which the payment is restricted. "0" means no locktime +#[allow(unused_variables)] pub fn construct_p2sh_tx( tx_ins: Vec, fee: Option, @@ -456,7 +459,7 @@ pub fn construct_p2sh_tx( locktime: u64, key_material: &BTreeMap ) -> Transaction { - let script_hash = construct_p2sh_address(script); + /*let script_hash = construct_p2sh_address(script); let tx_out = TxOut { value: asset, @@ -466,7 +469,8 @@ pub fn construct_p2sh_tx( let tx_outs = vec![tx_out]; let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, key_material); - construct_tx_core(final_tx_ins, tx_outs, fee) + construct_tx_core(final_tx_ins, tx_outs, fee)*/ + todo!("P2SH not yet supported!") } /// Constructs a P2SH transaction to burn tokens @@ -693,8 +697,9 @@ pub fn construct_payment_tx_ins(tx_values: Vec) -> Vec { /// /// * `tx_values` - Series of values required for TxIn construction /// * `script` - Script to be used in the scriptSig +#[allow(unused_variables)] pub fn construct_p2sh_redeem_tx_ins(tx_values: TxConstructor, script: Script) -> Vec { - let mut tx_ins = Vec::new(); + /*let mut tx_ins = Vec::new(); let previous_out = Some(tx_values.previous_out); tx_ins.push(TxIn { @@ -702,7 +707,8 @@ pub fn construct_p2sh_redeem_tx_ins(tx_values: TxConstructor, script: Script) -> script_signature: script, }); - tx_ins + tx_ins*/ + todo!("P2SH not yet supported!") } /// Constructs a dual double entry tx @@ -737,8 +743,7 @@ mod tests { use super::*; use crate::crypto::sign_ed25519::{self as sign, Signature}; use crate::primitives::asset::{AssetValues, ItemAsset, TokenAmount}; - use crate::script::OpCodes; - use crate::utils::script_utils::{tx_has_valid_p2sh_script, tx_outs_are_valid}; + use crate::utils::script_utils::tx_outs_are_valid; #[test] // Creates a valid payment transaction @@ -782,8 +787,9 @@ mod tests { } #[test] + #[should_panic] fn test_construct_a_valid_p2sh_tx() { - let token_amount = TokenAmount(400000); + /*let token_amount = TokenAmount(400000); let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(Some(NETWORK_VERSION_V0)); let mut script = Script::new_for_coinbase(10); script.stack.push(StackEntry::Op(OpCodes::OP_DROP)); @@ -818,14 +824,16 @@ mod tests { assert!(tx_has_valid_p2sh_script( &redeeming_tx.inputs[0].script_signature, p2sh_tx.outputs[0].script_public_key.as_ref().unwrap() - )); + ));*/ + todo!("P2SH not yet supported!") // TODO: Add assertion for full tx validity } #[test] + #[should_panic] fn test_construct_a_valid_burn_tx() { - let token_amount = TokenAmount(400000); + /*let token_amount = TokenAmount(400000); let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(Some(NETWORK_VERSION_V0)); let burn_tx = construct_burn_tx(tx_ins, None, &key_material); @@ -862,7 +870,8 @@ mod tests { assert!(!tx_has_valid_p2sh_script( &redeeming_tx.inputs[0].script_signature, burn_tx.outputs[0].script_public_key.as_ref().unwrap() - )); + ));*/ + todo!("P2SH not yet supported") // TODO: Add assertion for full tx validity } From 1e12e03a5009d0d49154484e151697e08cfe7c19 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 13:48:15 +0200 Subject: [PATCH 21/30] transaction: Make TxOut not implement Default --- src/primitives/transaction.rs | 7 +------ src/utils/druid_utils.rs | 4 ++-- src/utils/transaction_utils.rs | 7 ++++--- 3 files changed, 7 insertions(+), 11 deletions(-) diff --git a/src/primitives/transaction.rs b/src/primitives/transaction.rs index 30a6238..aa6e946 100644 --- a/src/primitives/transaction.rs +++ b/src/primitives/transaction.rs @@ -117,7 +117,7 @@ impl TxIn { /// An output of a transaction. It contains the public key that the next input /// must be able to sign with to claim it. It also contains the block hash for the /// potential DRS if this is a data asset transaction -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct TxOut { pub value: Asset, pub locktime: u64, @@ -125,11 +125,6 @@ pub struct TxOut { } impl TxOut { - /// Creates a new TxOut instance - pub fn new() -> TxOut { - Default::default() - } - pub fn new_token_amount( to_address: String, amount: TokenAmount, diff --git a/src/utils/druid_utils.rs b/src/utils/druid_utils.rs index ec1012a..b6a0f33 100644 --- a/src/utils/druid_utils.rs +++ b/src/utils/druid_utils.rs @@ -95,14 +95,14 @@ mod tests { // TxOuts let token_tx_out = TxOut { value: alice_asset.clone(), + locktime: 0, script_public_key: Some(bob_addr.clone()), - ..Default::default() }; let data_tx_out = TxOut { value: bob_asset.clone(), + locktime: 0, script_public_key: Some(alice_addr.clone()), - ..Default::default() }; // Expectations (from addresses the same due to empty TxIn) diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index 53afad6..aec1798 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -334,8 +334,8 @@ pub fn construct_item_create_tx( let tx_ins = construct_create_tx_in(block_num, &asset, public_key, secret_key); let tx_out = TxOut { value: asset, + locktime: 0, script_public_key: Some(receiver_address), - ..Default::default() }; construct_tx_core(tx_ins, vec![tx_out], fee) @@ -414,8 +414,9 @@ pub fn construct_burn_tx(tx_ins: Vec, fee: Option, key_mater let script_hash = construct_p2sh_address(&script); let tx_out = TxOut { + value: Default::default(), + locktime: 0, script_public_key: Some(script_hash), - ..Default::default() }; let tx_outs = vec![tx_out]; @@ -1055,8 +1056,8 @@ mod tests { let tx_ins = construct_payment_tx_ins(vec![tx_const]); let tx_outs = vec![TxOut { value: data.clone(), + locktime: 0, script_public_key: Some(to_asset.clone()), - ..Default::default() }]; let bytes = match bincode::serde::encode_to_vec(&tx_ins, bincode::config::legacy()) { From 744b85b3ebe6bbbaf13b79a0e76b7c9a81d7adb8 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:17:14 +0200 Subject: [PATCH 22/30] transaction: Change TxOut.script_public_key to AnyAddress --- src/primitives/address.rs | 6 ++-- src/primitives/druid.rs | 2 +- src/primitives/transaction.rs | 21 ++++++++++---- src/utils/druid_utils.rs | 41 ++++++++++++++------------ src/utils/script_utils.rs | 45 ++++++++++------------------- src/utils/test_utils.rs | 8 ++--- src/utils/transaction_utils.rs | 53 +++++++++++++++++----------------- 7 files changed, 88 insertions(+), 88 deletions(-) diff --git a/src/primitives/address.rs b/src/primitives/address.rs index c164995..5b7bceb 100644 --- a/src/primitives/address.rs +++ b/src/primitives/address.rs @@ -181,14 +181,16 @@ impl FromStr for AnyAddress { impl Serialize for AnyAddress { fn serialize(&self, serializer: S) -> Result { - assert!(serializer.is_human_readable(), "serializer must be human-readable!"); + // TODO: jrabil: re-enable this once refactor/addresses-usages is merged + //assert!(serializer.is_human_readable(), "serializer must be human-readable!"); serializer.serialize_str(&self.to_string()) } } impl<'de> Deserialize<'de> for AnyAddress { fn deserialize>(deserializer: D) -> Result { - assert!(deserializer.is_human_readable(), "deserializer must be human-readable!"); + // TODO: jrabil: re-enable this once refactor/addresses-usages is merged + //assert!(deserializer.is_human_readable(), "deserializer must be human-readable!"); let text : String = serde::Deserialize::deserialize(deserializer)?; text.parse().map_err(::custom) diff --git a/src/primitives/druid.rs b/src/primitives/druid.rs index c0c0319..d78362e 100644 --- a/src/primitives/druid.rs +++ b/src/primitives/druid.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; #[derive(Default, Clone, Debug, Ord, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] pub struct DruidExpectation { pub from: String, - pub to: String, + pub to: String, // TODO: Make this an AnyAddress pub asset: Asset, } diff --git a/src/primitives/transaction.rs b/src/primitives/transaction.rs index aa6e946..91e582e 100644 --- a/src/primitives/transaction.rs +++ b/src/primitives/transaction.rs @@ -10,6 +10,7 @@ use crate::script::{OpCodes, StackEntry}; use crate::utils::is_valid_amount; use serde::{Deserialize, Serialize}; use std::fmt; +use crate::primitives::address::AnyAddress; #[derive(Debug, Clone, Serialize, Deserialize)] pub enum GenesisTxHashSpec { @@ -121,35 +122,43 @@ impl TxIn { pub struct TxOut { pub value: Asset, pub locktime: u64, - pub script_public_key: Option, + pub script_public_key: AnyAddress, } impl TxOut { pub fn new_token_amount( - to_address: String, + to_address: AnyAddress, amount: TokenAmount, locktime: Option, ) -> TxOut { TxOut { value: Asset::Token(amount), locktime: locktime.unwrap_or(ZERO as u64), - script_public_key: Some(to_address), + script_public_key: to_address, } } /// Creates a new TxOut instance for a `Item` asset /// /// **NOTE:** Only create transactions may have `Item` assets that have a `None` `genesis_hash` - pub fn new_item_amount(to_address: String, item: ItemAsset, locktime: Option) -> TxOut { + pub fn new_item_amount( + to_address: AnyAddress, + item: ItemAsset, + locktime: Option, + ) -> TxOut { TxOut { value: Asset::Item(item), locktime: locktime.unwrap_or(ZERO as u64), - script_public_key: Some(to_address), + script_public_key: to_address, } } //TODO: Add handling for `Data' asset variant - pub fn new_asset(to_address: String, asset: Asset, locktime: Option) -> TxOut { + pub fn new_asset( + to_address: AnyAddress, + asset: Asset, + locktime: Option, + ) -> TxOut { match asset { Asset::Token(amount) => TxOut::new_token_amount(to_address, amount, locktime), Asset::Item(item) => TxOut::new_item_amount(to_address, item, locktime), diff --git a/src/utils/druid_utils.rs b/src/utils/druid_utils.rs index b6a0f33..0db51a6 100644 --- a/src/utils/druid_utils.rs +++ b/src/utils/druid_utils.rs @@ -6,6 +6,7 @@ use crate::primitives::transaction::Transaction; use crate::utils::transaction_utils::construct_tx_ins_address; use std::collections::BTreeSet; use std::iter::Extend; +use crate::primitives::address::AnyAddress; /// Verifies that all DDE transaction expectations are met for DRUID-matching transactions /// @@ -33,9 +34,9 @@ pub fn druid_expectations_are_met<'a>( info!("Expectations: {:?}", expects); for out in &tx.outputs { - if let Some(pk) = &out.script_public_key { - tx_source.insert((ins.clone(), pk, &out.value)); - } + // TODO: jrabil: the original code here skipped over outputs with a None + // script_public_key, should we continue doing that for Burn? + tx_source.insert((ins.clone(), &out.script_public_key, &out.value)); } info!("Tx Source: {:?}", tx_source); } @@ -52,8 +53,8 @@ pub fn druid_expectations_are_met<'a>( /// /// * `e` - The expectation to check on /// * `tx_source` - The source transaction source to match against -fn expectation_met(e: &DruidExpectation, tx_source: &BTreeSet<(String, &String, &Asset)>) -> bool { - tx_source.get(&(e.from.clone(), &e.to, &e.asset)).is_some() +fn expectation_met(e: &DruidExpectation, tx_source: &BTreeSet<(String, &AnyAddress, &Asset)>) -> bool { + tx_source.get(&(e.from.clone(), &e.to.parse().expect(&e.to), &e.asset)).is_some() } #[cfg(test)] @@ -63,9 +64,11 @@ mod tests { use super::*; use crate::crypto::sign_ed25519::{self as sign}; + use crate::primitives::address::P2PKHAddress; use crate::primitives::asset::{Asset, ItemAsset, TokenAmount}; use crate::primitives::druid::{DdeValues, DruidExpectation}; use crate::primitives::transaction::*; + use crate::utils::{Placeholder, PlaceholderSeed}; use crate::utils::transaction_utils::*; /// Util function to create valid DDE asset tx's @@ -81,7 +84,7 @@ mod tests { // Alice let amount = TokenAmount(10); - let alice_addr = "3333".to_owned(); + let alice_addr = P2PKHAddress::placeholder_seed("alice").wrap(); let alice_asset = Asset::Token(amount); // Bob @@ -90,31 +93,31 @@ mod tests { amount: 1, genesis_hash: None, }); - let bob_addr = "22222".to_owned(); + let bob_addr = P2PKHAddress::placeholder_seed("bob").wrap(); // TxOuts let token_tx_out = TxOut { value: alice_asset.clone(), locktime: 0, - script_public_key: Some(bob_addr.clone()), + script_public_key: bob_addr.clone(), }; let data_tx_out = TxOut { value: bob_asset.clone(), locktime: 0, - script_public_key: Some(alice_addr.clone()), + script_public_key: alice_addr.clone(), }; // Expectations (from addresses the same due to empty TxIn) let expects = vec![ DruidExpectation { from: from_addr.clone(), - to: bob_addr, + to: bob_addr.to_string(), asset: alice_asset, }, DruidExpectation { from: from_addr, - to: alice_addr, + to: alice_addr.to_string(), asset: bob_asset, }, ]; @@ -151,10 +154,10 @@ mod tests { let tx_input = construct_payment_tx_ins(vec![]); let from_addr = construct_tx_ins_address(&tx_input); - let alice_addr = "1111".to_owned(); - let bob_addr = "00000".to_owned(); + let alice_addr = P2PKHAddress::placeholder_seed("alice").wrap(); + let bob_addr = P2PKHAddress::placeholder_seed("bob").wrap(); - let sender_address_excess = "11112".to_owned(); + let sender_address_excess = P2PKHAddress::placeholder_seed("sender_excess").wrap(); let mut key_material = BTreeMap::new(); // Act @@ -173,7 +176,7 @@ mod tests { let expectation = DruidExpectation { from: from_addr.clone(), - to: alice_addr.clone(), + to: alice_addr.to_string(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -189,7 +192,7 @@ mod tests { Vec::new(), None, ReceiverInfo { - address: bob_addr.clone(), + address: bob_addr.to_string(), asset: Asset::Token(payment), }, 0, @@ -210,7 +213,7 @@ mod tests { }; let expectation = DruidExpectation { from: from_addr, - to: bob_addr, + to: bob_addr.to_string(), asset: Asset::Token(payment), }; @@ -244,7 +247,7 @@ mod tests { let druid_info = change_tx.druid_info.clone(); let mut expects = druid_info.unwrap().expectations; - expects[0].to = "60764505679457".to_string(); + expects[0].to = AnyAddress::placeholder().to_string(); // New druid info let nm_druid_info = DdeValues { @@ -291,7 +294,7 @@ mod tests { /// Checks that item-based payments with non-matching addresses fail fn should_fail_rb_payment_addr_mismatch() { let (send_tx, mut recv_tx) = create_rb_payment_txs(); - recv_tx.outputs[0].script_public_key = Some("11145".to_string()); + recv_tx.outputs[0].script_public_key = Placeholder::placeholder(); // Non-matching address expectation assert!(!druid_expectations_are_met( diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index 7a5b757..434b2ec 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -20,6 +20,7 @@ use ring::error; use std::collections::{BTreeMap, BTreeSet}; use std::thread::current; use tracing::{debug, error, info, trace}; +use crate::primitives::address::AnyAddress; use super::transaction_utils::construct_p2sh_address; @@ -84,22 +85,21 @@ pub fn tx_is_valid<'a>( } // At this point `TxIn` will be valid - let tx_out_pk = tx_out.script_public_key.as_ref(); + let tx_out_pk = &tx_out.script_public_key; let tx_out_hash = construct_tx_in_signable_hash(tx_out_point); let full_tx_hash = construct_tx_in_out_signable_hash(tx_in, &tx.outputs); debug!("full_tx_hash: {:?}", full_tx_hash); - if let Some(pk) = tx_out_pk { - // Check will need to include other signature types here - if !tx_has_valid_p2pkh_sig(&tx_in.script_signature, &full_tx_hash, pk) - // TODO: jrabil: P2SH && !tx_has_valid_p2sh_script(&tx_in.script_signature, pk) - { - error!("INVALID SIGNATURE OR SCRIPT TYPE"); - return false; - } - } else { - return false; + match (tx_out_pk) { + AnyAddress::P2PKH(address) => + if !tx_has_valid_p2pkh_sig(&tx_in.script_signature, &full_tx_hash, &address.to_string()) + // TODO: jrabil: P2SH && !tx_has_valid_p2sh_script(&tx_in.script_signature, pk) + { + error!("INVALID SIGNATURE OR SCRIPT TYPE"); + return false; + } + _ => return false, } let asset = tx_out.value.clone().with_fixed_hash(tx_out_point); @@ -127,27 +127,11 @@ pub fn tx_outs_are_valid(tx_outs: &[TxOut], fees: &[TxOut], tx_ins_spent: AssetV let mut tx_outs_spent: AssetValues = Default::default(); for tx_out in tx_outs { - // Addresses must have valid length - if let Some(addr) = &tx_out.script_public_key { - if !address_has_valid_length(addr) { - trace!("Address has invalid length"); - return false; - } - } - tx_outs_spent.update_add(&tx_out.value); } // Check fees as well for fee in fees { - // Addresses must have valid length - if let Some(addr) = &fee.script_public_key { - if !address_has_valid_length(addr) { - trace!("Address has invalid length"); - return false; - } - } - tx_outs_spent.update_add(&fee.value); } @@ -310,6 +294,7 @@ mod tests { use super::*; use crate::constants::ITEM_ACCEPT_VAL; + use crate::primitives::address::P2PKHAddress; use crate::primitives::asset::Asset; use crate::primitives::druid::DdeValues; use crate::primitives::transaction::OutPoint; @@ -2916,9 +2901,9 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let tx_hash = hex::encode(vec![0, 0, 0]); let tx_outpoint = OutPoint::new(tx_hash, 0); - let script_public_key = construct_address(&pk); + let script_public_key = P2PKHAddress::from_pubkey(&pk); let tx_in_previous_out = - TxOut::new_token_amount(script_public_key.clone(), TokenAmount(5), locktime); + TxOut::new_token_amount(script_public_key.wrap(), TokenAmount(5), locktime); let ongoing_tx_outs = vec![tx_in_previous_out.clone()]; let tx_in = TxIn { script_signature: Script::new(), @@ -2938,7 +2923,7 @@ mod tests { StackEntry::PubKey(pk), StackEntry::Op(OpCodes::OP_DUP), StackEntry::Op(op_hash256), - StackEntry::Bytes(hex::decode(script_public_key).unwrap()), + StackEntry::Bytes(script_public_key.get_hash().to_vec()), StackEntry::Op(OpCodes::OP_EQUALVERIFY), StackEntry::Op(OpCodes::OP_CHECKSIG), ], diff --git a/src/utils/test_utils.rs b/src/utils/test_utils.rs index e7139f8..fa1a3ad 100644 --- a/src/utils/test_utils.rs +++ b/src/utils/test_utils.rs @@ -39,9 +39,9 @@ pub fn generate_tx_with_ins_and_outs_assets( let tx_out = match genesis_hash { Some(drs) => { let item = Asset::item(*output_amount, Some(drs.to_string()), None); - TxOut::new_asset(spk.clone(), item, None) + TxOut::new_asset(spk.parse().expect(&spk), item, None) } - None => TxOut::new_token_amount(spk.clone(), TokenAmount(*output_amount), None), + None => TxOut::new_token_amount(spk.parse().expect(&spk), TokenAmount(*output_amount), None), }; tx.outputs.push(tx_out); } @@ -52,9 +52,9 @@ pub fn generate_tx_with_ins_and_outs_assets( let tx_in_previous_out = match genesis_hash { Some(drs) => { let item = Asset::item(*input_amount, Some(drs.to_string()), md.clone()); - TxOut::new_asset(spk.clone(), item, None) + TxOut::new_asset(spk.parse().expect(&spk), item, None) } - None => TxOut::new_token_amount(spk.clone(), TokenAmount(*input_amount), None), + None => TxOut::new_token_amount(spk.parse().expect(&spk), TokenAmount(*input_amount), None), }; let signable_hash = construct_tx_in_out_signable_hash( &TxIn { diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index aec1798..f18e6ce 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -5,9 +5,10 @@ use crate::primitives::asset::Asset; use crate::primitives::druid::{DdeValues, DruidExpectation}; use crate::primitives::transaction::*; use crate::script::lang::Script; -use crate::script::{OpCodes, StackEntry}; +use crate::script::StackEntry; use std::collections::BTreeMap; use tracing::debug; +use crate::primitives::address::{AnyAddress, P2PKHAddress}; pub struct ReceiverInfo { pub address: String, @@ -329,13 +330,12 @@ pub fn construct_item_create_tx( ) -> Transaction { let genesis_hash = genesis_hash_spec.get_genesis_hash(); let asset = Asset::item(amount, genesis_hash, metadata); - let receiver_address = construct_address(&public_key); let tx_ins = construct_create_tx_in(block_num, &asset, public_key, secret_key); let tx_out = TxOut { value: asset, locktime: 0, - script_public_key: Some(receiver_address), + script_public_key: P2PKHAddress::from_pubkey(&public_key).wrap(), }; construct_tx_core(tx_ins, vec![tx_out], fee) @@ -363,7 +363,7 @@ pub fn construct_payment_tx( let tx_out = TxOut { value: receiver.asset, locktime, - script_public_key: Some(receiver.address), + script_public_key: receiver.address.parse().expect(&receiver.address), }; let tx_outs = vec![tx_out]; let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, key_material); @@ -409,14 +409,11 @@ pub fn construct_p2sh_tx( /// /// * `tx_ins` - Input/s to pay from pub fn construct_burn_tx(tx_ins: Vec, fee: Option, key_material: &BTreeMap) -> Transaction { - let s = vec![StackEntry::Op(OpCodes::OP_BURN)]; - let script = Script::from(s); - let script_hash = construct_p2sh_address(&script); - let tx_out = TxOut { + // TODO: jrabil: The value here should actually be the sum of the input values value: Default::default(), locktime: 0, - script_public_key: Some(script_hash), + script_public_key: AnyAddress::Burn, }; let tx_outs = vec![tx_out]; @@ -446,7 +443,7 @@ pub fn construct_tx_core( Some(fee) => vec![TxOut { value: fee.asset, locktime: 0, - script_public_key: Some(fee.address), + script_public_key: fee.address.parse().expect(&fee.address), }], None => vec![], }; @@ -545,7 +542,7 @@ pub fn construct_rb_payments_send_tx( let out = TxOut { value: receiver.asset, locktime, - script_public_key: Some(receiver.address), + script_public_key: receiver.address.parse().expect(&receiver.address), }; tx_outs.push(out); construct_rb_tx_core( @@ -574,7 +571,7 @@ pub fn construct_rb_receive_payment_tx( tx_ins: Vec, mut tx_outs: Vec, fee: Option, - sender_address: String, + sender_address: AnyAddress, locktime: u64, druid_info: DdeValues, key_material: &BTreeMap @@ -582,7 +579,7 @@ pub fn construct_rb_receive_payment_tx( let out = TxOut { value: Asset::item(1, druid_info.genesis_hash, None), locktime, - script_public_key: Some(sender_address), + script_public_key: sender_address, }; tx_outs.push(out); construct_rb_tx_core( @@ -672,7 +669,9 @@ pub fn construct_dde_tx( mod tests { use super::*; use crate::crypto::sign_ed25519::{self as sign, Signature}; + use crate::primitives::address::P2PKHAddress; use crate::primitives::asset::{AssetValues, ItemAsset, TokenAmount}; + use crate::utils::{Placeholder, PlaceholderSeed}; use crate::utils::script_utils::tx_outs_are_valid; fn test_construct_valid_inputs() -> (Vec, String, BTreeMap) { @@ -790,10 +789,11 @@ mod tests { let (tx_ins, _drs_block_hash, key_material) = test_construct_valid_inputs(); let token_amount = TokenAmount(400000); + let address = AnyAddress::placeholder(); let payment_tx = construct_payment_tx( tx_ins, ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: address.to_string(), asset: Asset::Token(token_amount), }, None, @@ -803,7 +803,7 @@ mod tests { assert_eq!(Asset::Token(token_amount), payment_tx.outputs[0].value); assert_eq!( payment_tx.outputs[0].script_public_key, - Some(hex::encode(vec![0; 32])) + address ); } @@ -977,6 +977,7 @@ mod tests { let mut key_material = BTreeMap::new(); key_material.insert(prev_out.clone(), (pk, sk.clone())); + let addr = AnyAddress::placeholder(); let tx_1 = TxConstructor { previous_out: OutPoint::new("".to_string(), 0), @@ -989,7 +990,7 @@ mod tests { let payment_tx_1 = construct_payment_tx( tx_ins_1, ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: addr.to_string(), asset: Asset::Token(token_amount), }, None, @@ -1008,7 +1009,7 @@ mod tests { }; let tx_ins_2 = construct_payment_tx_ins(vec![tx_2]); let tx_outs = vec![TxOut::new_token_amount( - hex::encode(vec![0; 32]), + addr, token_amount, None, )]; @@ -1040,7 +1041,7 @@ mod tests { let mut key_material = BTreeMap::new(); key_material.insert(prev_out.clone(), (pk, sk)); - let to_asset = "2222".to_owned(); + let to_address = P2PKHAddress::placeholder_seed("to").wrap(); let data = Asset::Item(ItemAsset { metadata: Some("hello".to_string()), amount: 1, @@ -1057,7 +1058,7 @@ mod tests { let tx_outs = vec![TxOut { value: data.clone(), locktime: 0, - script_public_key: Some(to_asset.clone()), + script_public_key: to_address.clone(), }]; let bytes = match bincode::serde::encode_to_vec(&tx_ins, bincode::config::legacy()) { @@ -1071,7 +1072,7 @@ mod tests { let participants = 2; let expects = vec![DruidExpectation { from: from_addr, - to: to_asset, + to: to_address.to_string(), asset: data.clone(), }]; @@ -1101,10 +1102,10 @@ mod tests { let tx_input = construct_payment_tx_ins(vec![]); let from_addr = construct_tx_ins_address(&tx_input); - let alice_addr = "1111".to_owned(); - let bob_addr = "00000".to_owned(); + let alice_addr = P2PKHAddress::placeholder_seed("alice").wrap(); + let bob_addr = P2PKHAddress::placeholder_seed("bob").wrap(); - let sender_address_excess = "11112".to_owned(); + let sender_address_excess = P2PKHAddress::placeholder_seed("sender_excess").wrap(); let (pk, sk) = sign::gen_keypair(); let mut key_material = BTreeMap::new(); @@ -1123,7 +1124,7 @@ mod tests { let expectation = DruidExpectation { from: from_addr.clone(), - to: alice_addr.clone(), + to: alice_addr.to_string(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -1132,7 +1133,7 @@ mod tests { Vec::new(), None, ReceiverInfo { - address: bob_addr.clone(), + address: bob_addr.to_string(), asset: Asset::Token(payment), }, 0, @@ -1158,7 +1159,7 @@ mod tests { }; let expectation = DruidExpectation { from: from_addr, - to: bob_addr, + to: bob_addr.to_string(), asset: Asset::Token(payment), }; From 5dfa5bcd3b0b77f0cde4e99ef9e42a68b8ec82cd Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:18:05 +0200 Subject: [PATCH 23/30] druid: Make DruidExpectation no longer implement Default --- src/primitives/druid.rs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/src/primitives/druid.rs b/src/primitives/druid.rs index d78362e..3128009 100644 --- a/src/primitives/druid.rs +++ b/src/primitives/druid.rs @@ -2,7 +2,7 @@ use crate::primitives::asset::Asset; use serde::{Deserialize, Serialize}; /// The expectation to be met in a specific DRUID transaction -#[derive(Default, Clone, Debug, Ord, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] +#[derive(Clone, Debug, Ord, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] pub struct DruidExpectation { pub from: String, pub to: String, // TODO: Make this an AnyAddress @@ -16,17 +16,10 @@ pub struct DruidExpectation { /// `expect_value` - The value expected by another party for this tx /// `expect_value_amount` - The amount of the asset expected by another party for this tx /// `expect_address` - The address the other party is expected to pay to -#[derive(Default, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub struct DdeValues { pub druid: String, pub participants: usize, pub expectations: Vec, pub genesis_hash: Option, } - -impl DdeValues { - /// Creates a new DdeValues instance - pub fn new() -> Self { - Default::default() - } -} From c062b9fe662c08e87939c62a20ecd591ff23b174 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:20:26 +0200 Subject: [PATCH 24/30] druid: Change DruidExpectation.to to AnyAddress --- src/primitives/druid.rs | 3 ++- src/utils/druid_utils.rs | 12 ++++++------ src/utils/transaction_utils.rs | 6 +++--- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/primitives/druid.rs b/src/primitives/druid.rs index 3128009..5e3da1d 100644 --- a/src/primitives/druid.rs +++ b/src/primitives/druid.rs @@ -1,11 +1,12 @@ use crate::primitives::asset::Asset; use serde::{Deserialize, Serialize}; +use crate::primitives::address::AnyAddress; /// The expectation to be met in a specific DRUID transaction #[derive(Clone, Debug, Ord, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] pub struct DruidExpectation { pub from: String, - pub to: String, // TODO: Make this an AnyAddress + pub to: AnyAddress, pub asset: Asset, } diff --git a/src/utils/druid_utils.rs b/src/utils/druid_utils.rs index 0db51a6..9ee9e2e 100644 --- a/src/utils/druid_utils.rs +++ b/src/utils/druid_utils.rs @@ -54,7 +54,7 @@ pub fn druid_expectations_are_met<'a>( /// * `e` - The expectation to check on /// * `tx_source` - The source transaction source to match against fn expectation_met(e: &DruidExpectation, tx_source: &BTreeSet<(String, &AnyAddress, &Asset)>) -> bool { - tx_source.get(&(e.from.clone(), &e.to.parse().expect(&e.to), &e.asset)).is_some() + tx_source.get(&(e.from.clone(), &e.to, &e.asset)).is_some() } #[cfg(test)] @@ -112,12 +112,12 @@ mod tests { let expects = vec![ DruidExpectation { from: from_addr.clone(), - to: bob_addr.to_string(), + to: bob_addr, asset: alice_asset, }, DruidExpectation { from: from_addr, - to: alice_addr.to_string(), + to: alice_addr, asset: bob_asset, }, ]; @@ -176,7 +176,7 @@ mod tests { let expectation = DruidExpectation { from: from_addr.clone(), - to: alice_addr.to_string(), + to: alice_addr.clone(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -213,7 +213,7 @@ mod tests { }; let expectation = DruidExpectation { from: from_addr, - to: bob_addr.to_string(), + to: bob_addr, asset: Asset::Token(payment), }; @@ -247,7 +247,7 @@ mod tests { let druid_info = change_tx.druid_info.clone(); let mut expects = druid_info.unwrap().expectations; - expects[0].to = AnyAddress::placeholder().to_string(); + expects[0].to = AnyAddress::placeholder(); // New druid info let nm_druid_info = DdeValues { diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index f18e6ce..ed1e1eb 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -1072,7 +1072,7 @@ mod tests { let participants = 2; let expects = vec![DruidExpectation { from: from_addr, - to: to_address.to_string(), + to: to_address, asset: data.clone(), }]; @@ -1124,7 +1124,7 @@ mod tests { let expectation = DruidExpectation { from: from_addr.clone(), - to: alice_addr.to_string(), + to: alice_addr.clone(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -1159,7 +1159,7 @@ mod tests { }; let expectation = DruidExpectation { from: from_addr, - to: bob_addr.to_string(), + to: bob_addr.clone(), asset: Asset::Token(payment), }; From 921a004b2de3a5d98b044173e14d4592fc96e119 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:24:38 +0200 Subject: [PATCH 25/30] transaction_utils: Change ReceiverInfo.address to AnyAddress --- src/utils/druid_utils.rs | 2 +- src/utils/transaction_utils.rs | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/src/utils/druid_utils.rs b/src/utils/druid_utils.rs index 9ee9e2e..7ca1f00 100644 --- a/src/utils/druid_utils.rs +++ b/src/utils/druid_utils.rs @@ -192,7 +192,7 @@ mod tests { Vec::new(), None, ReceiverInfo { - address: bob_addr.to_string(), + address: bob_addr.clone(), asset: Asset::Token(payment), }, 0, diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index ed1e1eb..b95c9f1 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -11,7 +11,7 @@ use tracing::debug; use crate::primitives::address::{AnyAddress, P2PKHAddress}; pub struct ReceiverInfo { - pub address: String, + pub address: AnyAddress, pub asset: Asset, } @@ -363,7 +363,7 @@ pub fn construct_payment_tx( let tx_out = TxOut { value: receiver.asset, locktime, - script_public_key: receiver.address.parse().expect(&receiver.address), + script_public_key: receiver.address, }; let tx_outs = vec![tx_out]; let final_tx_ins = update_input_signatures(&tx_ins, &tx_outs, key_material); @@ -443,7 +443,7 @@ pub fn construct_tx_core( Some(fee) => vec![TxOut { value: fee.asset, locktime: 0, - script_public_key: fee.address.parse().expect(&fee.address), + script_public_key: fee.address, }], None => vec![], }; @@ -542,7 +542,7 @@ pub fn construct_rb_payments_send_tx( let out = TxOut { value: receiver.asset, locktime, - script_public_key: receiver.address.parse().expect(&receiver.address), + script_public_key: receiver.address, }; tx_outs.push(out); construct_rb_tx_core( @@ -793,7 +793,7 @@ mod tests { let payment_tx = construct_payment_tx( tx_ins, ReceiverInfo { - address: address.to_string(), + address: address.clone(), asset: Asset::Token(token_amount), }, None, @@ -817,11 +817,11 @@ mod tests { let payment_tx = construct_payment_tx( tx_ins, ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Token(token_amount), }, Some(ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Token(fee_amount), }), 0, @@ -854,11 +854,11 @@ mod tests { let payment_tx_valid = construct_payment_tx( tx_ins, ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Token(tokens), }, Some(ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Token(fees), }), 0, @@ -899,11 +899,11 @@ mod tests { let payment_tx_valid = construct_payment_tx( tx_ins, ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Item(item_asset_valid), }, Some(ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Token(fees), }), 0, @@ -946,7 +946,7 @@ mod tests { let payment_tx_valid = construct_payment_tx( tx_ins, ReceiverInfo { - address: hex::encode(vec![0; 32]), + address: Placeholder::placeholder(), asset: Asset::Item(item_asset_valid), }, None, @@ -990,7 +990,7 @@ mod tests { let payment_tx_1 = construct_payment_tx( tx_ins_1, ReceiverInfo { - address: addr.to_string(), + address: addr.clone(), asset: Asset::Token(token_amount), }, None, @@ -1133,7 +1133,7 @@ mod tests { Vec::new(), None, ReceiverInfo { - address: bob_addr.to_string(), + address: bob_addr.clone(), asset: Asset::Token(payment), }, 0, From b314b07a17fe24d102752fa987f2e15d5fc49902 Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:32:08 +0200 Subject: [PATCH 26/30] script_utils: Change tx_has_valid_p2pkh_sig to use &P2PKHAddress --- src/utils/script_utils.rs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index 434b2ec..d75787a 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -20,7 +20,7 @@ use ring::error; use std::collections::{BTreeMap, BTreeSet}; use std::thread::current; use tracing::{debug, error, info, trace}; -use crate::primitives::address::AnyAddress; +use crate::primitives::address::{AnyAddress, P2PKHAddress}; use super::transaction_utils::construct_p2sh_address; @@ -93,7 +93,7 @@ pub fn tx_is_valid<'a>( match (tx_out_pk) { AnyAddress::P2PKH(address) => - if !tx_has_valid_p2pkh_sig(&tx_in.script_signature, &full_tx_hash, &address.to_string()) + if !tx_has_valid_p2pkh_sig(&tx_in.script_signature, &full_tx_hash, address) // TODO: jrabil: P2SH && !tx_has_valid_p2sh_script(&tx_in.script_signature, pk) { error!("INVALID SIGNATURE OR SCRIPT TYPE"); @@ -196,7 +196,11 @@ pub fn tx_has_valid_create_script(script: &Script, asset: &Asset) -> bool { /// * `outpoint_hash` - Hash of the corresponding outpoint /// * `tx_out_pub_key` - Public key of the previous tx_out // TODO: The last two operands should be converted to the corresponding types -fn tx_has_valid_p2pkh_sig(script: &Script, outpoint_hash: &str, tx_out_pub_key: &str) -> bool { +fn tx_has_valid_p2pkh_sig( + script: &Script, + outpoint_hash: &str, + tx_out_pub_key: &P2PKHAddress, +) -> bool { let mut it = script.stack.iter(); debug!("script: {:?}", script.stack); @@ -229,7 +233,7 @@ fn tx_has_valid_p2pkh_sig(script: &Script, outpoint_hash: &str, tx_out_pub_key: let h_hex = hex::encode(h); let b_hex = hex::encode(b); - if h_hex == tx_out_pub_key && b_hex == outpoint_hash && script.interpret() { + if h_hex == hex::encode(&tx_out_pub_key.get_hash()) && b_hex == outpoint_hash && script.interpret() { return true; } } @@ -2735,7 +2739,7 @@ mod tests { assert!(tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk + &tx_out_pk.parse().expect(&tx_out_pk), )); } @@ -2764,7 +2768,7 @@ mod tests { assert!(!tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk + &tx_out_pk.parse().expect(&tx_out_pk), )); } @@ -2801,7 +2805,7 @@ mod tests { assert!(!tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk + &tx_out_pk.parse().expect(&tx_out_pk), )); } @@ -2842,7 +2846,7 @@ mod tests { assert!(!tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk + &tx_out_pk.parse().expect(&tx_out_pk), )); } From 103cb2cee74ea513f1905f00f25e4a066ec7080c Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:41:05 +0200 Subject: [PATCH 27/30] Prefer P2PKHAddress::from_pubkey over transaction_utils::construct_address --- src/script/interface_ops.rs | 6 +++--- src/script/lang.rs | 5 ++--- src/utils/script_utils.rs | 24 ++++++++++++------------ src/utils/test_utils.rs | 13 +++++++------ src/utils/transaction_utils.rs | 21 ++++++++------------- 5 files changed, 32 insertions(+), 37 deletions(-) diff --git a/src/script/interface_ops.rs b/src/script/interface_ops.rs index d5bc176..279f2cb 100644 --- a/src/script/interface_ops.rs +++ b/src/script/interface_ops.rs @@ -8,12 +8,12 @@ use crate::primitives::transaction::*; use crate::script::lang::{ConditionStack, Script, Stack}; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; -use crate::utils::transaction_utils::construct_address; use bytes::Bytes; use hex::encode; use std::collections::BTreeMap; use tracing::{debug, error, info, trace}; use tracing_subscriber::field::debug; +use crate::primitives::address::P2PKHAddress; /*---- FLOW CONTROL OPS ----*/ @@ -1976,8 +1976,8 @@ pub fn op_hash256(stack: &mut Stack) -> bool { return false; } }; - let addr = construct_address(&pk); - stack.push(StackEntry::Bytes(hex::decode(addr).unwrap())) + let addr = P2PKHAddress::from_pubkey(&pk); + stack.push(StackEntry::Bytes(addr.get_hash().to_vec())) } /// OP_CHECKSIG: Pushes ONE onto the stack if the signature is valid, ZERO otherwise diff --git a/src/script/lang.rs b/src/script/lang.rs index af25ec7..80883fd 100644 --- a/src/script/lang.rs +++ b/src/script/lang.rs @@ -7,10 +7,10 @@ use crate::crypto::sign_ed25519::{ use crate::script::interface_ops::*; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; -use crate::utils::transaction_utils::construct_address; use hex::encode; use serde::{Deserialize, Serialize}; use tracing::{error, warn}; +use crate::primitives::address::P2PKHAddress; /// Stack for script execution #[derive(Clone, Debug, PartialOrd, Eq, PartialEq, Serialize, Deserialize)] @@ -397,8 +397,7 @@ impl Script { StackEntry::PubKey(pub_key), StackEntry::Op(OpCodes::OP_DUP), StackEntry::Op(OpCodes::OP_HASH256), - StackEntry::Bytes(hex::decode(construct_address(&pub_key)) - .expect("address contains non-hex characters?")), + StackEntry::Bytes(P2PKHAddress::from_pubkey(&pub_key).get_hash().to_vec()), StackEntry::Op(OpCodes::OP_EQUALVERIFY), StackEntry::Op(OpCodes::OP_CHECKSIG), ]; diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index d75787a..7f1645c 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -12,7 +12,7 @@ use crate::script::lang::{ConditionStack, Script, Stack}; use crate::script::{OpCodes, StackEntry}; use crate::utils::error_utils::*; use crate::utils::transaction_utils::{ - construct_address, construct_tx_hash, construct_tx_in_out_signable_hash, + construct_tx_hash, construct_tx_in_out_signable_hash, construct_tx_in_signable_asset_hash, construct_tx_in_signable_hash, }; use hex::encode; @@ -302,6 +302,7 @@ mod tests { use crate::primitives::asset::Asset; use crate::primitives::druid::DdeValues; use crate::primitives::transaction::OutPoint; + use crate::utils::Placeholder; use crate::utils::test_utils::generate_tx_with_ins_and_outs_assets; use crate::utils::transaction_utils::*; @@ -1935,7 +1936,7 @@ mod tests { let (pk, sk) = sign::gen_keypair(); let mut stack = Stack::new(); stack.push(StackEntry::PubKey(pk)); - let mut v: Vec = vec![StackEntry::Bytes(hex::decode(construct_address(&pk)).unwrap())]; + let mut v: Vec = vec![StackEntry::Bytes(P2PKHAddress::from_pubkey(&pk).get_hash().to_vec())]; op_hash256(&mut stack); assert_eq!(stack.main_stack, v); /// op_hash256([]) -> fail @@ -2667,8 +2668,7 @@ mod tests { #[test] /// Checks whether addresses are validated correctly fn test_validate_addresses_correctly() { - let (pk, _) = sign::gen_keypair(); - let address = construct_address(&pk); + let address = P2PKHAddress::placeholder().to_string(); assert!(address_has_valid_length(&address)); assert!(address_has_valid_length(&hex::encode([0; 32]))); @@ -2734,12 +2734,12 @@ mod tests { tx_ins = update_input_signatures(&tx_ins, &tx_outs, &key_material); let hash_to_sign = construct_tx_in_out_signable_hash(&tx_ins[0], &tx_outs); - let tx_out_pk = construct_address(&pk); + let tx_out_pk = P2PKHAddress::from_pubkey(&pk); assert!(tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk.parse().expect(&tx_out_pk), + &tx_out_pk )); } @@ -2763,12 +2763,12 @@ mod tests { }; let tx_ins = construct_payment_tx_ins(vec![tx_const]); - let tx_out_pk = construct_address(&pk); + let tx_out_pk = P2PKHAddress::from_pubkey(&pk); assert!(!tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk.parse().expect(&tx_out_pk), + &tx_out_pk )); } @@ -2800,12 +2800,12 @@ mod tests { tx_ins.push(new_tx_in); } - let tx_out_pk = construct_address(&pk); + let tx_out_pk = P2PKHAddress::from_pubkey(&pk); assert!(!tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk.parse().expect(&tx_out_pk), + &tx_out_pk )); } @@ -2841,12 +2841,12 @@ mod tests { tx_ins.push(new_tx_in); } - let tx_out_pk = construct_address(&pk); + let tx_out_pk = P2PKHAddress::from_pubkey(&pk); assert!(!tx_has_valid_p2pkh_sig( &tx_ins[0].script_signature, &hash_to_sign, - &tx_out_pk.parse().expect(&tx_out_pk), + &tx_out_pk )); } diff --git a/src/utils/test_utils.rs b/src/utils/test_utils.rs index fa1a3ad..09fd389 100644 --- a/src/utils/test_utils.rs +++ b/src/utils/test_utils.rs @@ -5,8 +5,9 @@ use crate::primitives::{ transaction::{OutPoint, Transaction, TxIn, TxOut}, }; use crate::script::lang::Script; -use crate::utils::transaction_utils::{construct_address, construct_tx_in_out_signable_hash}; +use crate::utils::transaction_utils::construct_tx_in_out_signable_hash; use std::collections::BTreeMap; +use crate::primitives::address::P2PKHAddress; /// Generate a transaction with valid Script values /// and accompanying UTXO set for testing a set of @@ -30,7 +31,7 @@ pub fn generate_tx_with_ins_and_outs_assets( output_assets: &[(u64, Option<&str>)], /* Input amount, genesis_hash */ ) -> (BTreeMap, Transaction) { let (pk, sk) = sign::gen_keypair(); - let spk = construct_address(&pk); + let spk = P2PKHAddress::from_pubkey(&pk).wrap(); let mut tx = Transaction::new(); let mut utxo_set: BTreeMap = BTreeMap::new(); @@ -39,9 +40,9 @@ pub fn generate_tx_with_ins_and_outs_assets( let tx_out = match genesis_hash { Some(drs) => { let item = Asset::item(*output_amount, Some(drs.to_string()), None); - TxOut::new_asset(spk.parse().expect(&spk), item, None) + TxOut::new_asset(spk, item, None) } - None => TxOut::new_token_amount(spk.parse().expect(&spk), TokenAmount(*output_amount), None), + None => TxOut::new_token_amount(spk, TokenAmount(*output_amount), None), }; tx.outputs.push(tx_out); } @@ -52,9 +53,9 @@ pub fn generate_tx_with_ins_and_outs_assets( let tx_in_previous_out = match genesis_hash { Some(drs) => { let item = Asset::item(*input_amount, Some(drs.to_string()), md.clone()); - TxOut::new_asset(spk.parse().expect(&spk), item, None) + TxOut::new_asset(spk, item, None) } - None => TxOut::new_token_amount(spk.parse().expect(&spk), TokenAmount(*input_amount), None), + None => TxOut::new_token_amount(spk, TokenAmount(*input_amount), None), }; let signable_hash = construct_tx_in_out_signable_hash( &TxIn { diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index b95c9f1..b74cfd4 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -35,8 +35,9 @@ pub fn construct_p2sh_address(script: &Script) -> String { /// ### Arguments /// /// * `pub_key` - A public key to build an address from -pub fn construct_address(pub_key: &PublicKey) -> String { - hex::encode(sha3_256::digest(pub_key.as_ref())) +#[deprecated = "Use P2PKHAddress::from_pubkey()"] +pub fn construct_address(pub_key: &PublicKey) -> P2PKHAddress { + P2PKHAddress::from_pubkey(pub_key) } /// Constructs signable string for OutPoint @@ -667,6 +668,7 @@ pub fn construct_dde_tx( #[cfg(test)] mod tests { + use std::str::FromStr; use super::*; use crate::crypto::sign_ed25519::{self as sign, Signature}; use crate::primitives::address::P2PKHAddress; @@ -1201,28 +1203,21 @@ mod tests { "5371832122a8e804fa3520ec6861c3fa554a7f6fb617e6f0768452090207e07c", "6e86cc1fc5efbe64c2690efbb966b9fe1957facc497dce311981c68dac88e08c", "8b835e00c57ebff6637ec32276f2c6c0df71129c8f0860131a78a4692a0b59dc", - ] - .iter() - .map(|v| hex::decode(v).unwrap()) - .map(|v| PublicKey::from_slice(&v).unwrap()) - .collect::>(); + ].map(|t| PublicKey::from_str(t).expect(t)); // // Act // - let actual_pub_addresses: Vec = pub_keys - .iter() - .map(construct_address) - .collect(); + let actual_pub_addresses = pub_keys.each_ref().map(P2PKHAddress::from_pubkey); // // Assert // - let expected_pub_addresses = vec![ + let expected_pub_addresses = [ "5423e6bd848e0ce5cd794e55235c23138d8833633cd2d7de7f4a10935178457b", "77516e2d91606250e625546f86702510d2e893e4a27edfc932fdba03c955cc1b", "4cfd64a6692021fc417368a866d33d94e1c806747f61ac85e0b3935e7d5ed925", - ]; + ].map(|t| P2PKHAddress::from_str(t).expect(t)); assert_eq!(actual_pub_addresses, expected_pub_addresses); } From e753cfdd67f47e763970de2df7894eed23ae255e Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:52:44 +0200 Subject: [PATCH 28/30] transaction_utils: Make construct_tx_ins_address return TxInsAddress --- src/utils/druid_utils.rs | 16 ++++++++-------- src/utils/transaction_utils.rs | 18 +++++++++--------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/utils/druid_utils.rs b/src/utils/druid_utils.rs index 7ca1f00..84ffac1 100644 --- a/src/utils/druid_utils.rs +++ b/src/utils/druid_utils.rs @@ -6,7 +6,7 @@ use crate::primitives::transaction::Transaction; use crate::utils::transaction_utils::construct_tx_ins_address; use std::collections::BTreeSet; use std::iter::Extend; -use crate::primitives::address::AnyAddress; +use crate::primitives::address::{AnyAddress, TxInsAddress}; /// Verifies that all DDE transaction expectations are met for DRUID-matching transactions /// @@ -36,7 +36,7 @@ pub fn druid_expectations_are_met<'a>( for out in &tx.outputs { // TODO: jrabil: the original code here skipped over outputs with a None // script_public_key, should we continue doing that for Burn? - tx_source.insert((ins.clone(), &out.script_public_key, &out.value)); + tx_source.insert((ins, &out.script_public_key, &out.value)); } info!("Tx Source: {:?}", tx_source); } @@ -53,8 +53,8 @@ pub fn druid_expectations_are_met<'a>( /// /// * `e` - The expectation to check on /// * `tx_source` - The source transaction source to match against -fn expectation_met(e: &DruidExpectation, tx_source: &BTreeSet<(String, &AnyAddress, &Asset)>) -> bool { - tx_source.get(&(e.from.clone(), &e.to, &e.asset)).is_some() +fn expectation_met(e: &DruidExpectation, tx_source: &BTreeSet<(TxInsAddress, &AnyAddress, &Asset)>) -> bool { + tx_source.get(&(e.from.parse().expect(&e.from), &e.to, &e.asset)).is_some() } #[cfg(test)] @@ -111,12 +111,12 @@ mod tests { // Expectations (from addresses the same due to empty TxIn) let expects = vec![ DruidExpectation { - from: from_addr.clone(), + from: from_addr.to_string(), to: bob_addr, asset: alice_asset, }, DruidExpectation { - from: from_addr, + from: from_addr.to_string(), to: alice_addr, asset: bob_asset, }, @@ -175,7 +175,7 @@ mod tests { key_material.insert(prev_out, (pk, sk)); let expectation = DruidExpectation { - from: from_addr.clone(), + from: from_addr.to_string(), to: alice_addr.clone(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -212,7 +212,7 @@ mod tests { construct_payment_tx_ins(tx_ins_constructor) }; let expectation = DruidExpectation { - from: from_addr, + from: from_addr.to_string(), to: bob_addr, asset: Asset::Token(payment), }; diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index b74cfd4..7e2c8ba 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -8,7 +8,7 @@ use crate::script::lang::Script; use crate::script::StackEntry; use std::collections::BTreeMap; use tracing::debug; -use crate::primitives::address::{AnyAddress, P2PKHAddress}; +use crate::primitives::address::{AnyAddress, P2PKHAddress, TxInsAddress}; pub struct ReceiverInfo { pub address: AnyAddress, @@ -88,7 +88,7 @@ pub fn construct_tx_in_signable_asset_hash(asset: &Asset) -> String { /// ### Arguments /// /// * `entry` - StackEntry to obtain signable string for -pub fn get_stack_entry_signable_string(entry: &StackEntry) -> String { +fn get_stack_entry_signable_string(entry: &StackEntry) -> String { match entry { StackEntry::Op(op) => format!("Op:{op}"), StackEntry::Signature(signature) => { @@ -133,7 +133,7 @@ pub fn construct_tx_in_out_signable_hash(tx_in: &TxIn, tx_out: &[TxOut]) -> Stri /// ### Arguments /// /// * `stack` - StackEntry vector -pub fn get_script_signable_string(stack: &[StackEntry]) -> String { +fn get_script_signable_string(stack: &[StackEntry]) -> String { stack .iter() .map(get_stack_entry_signable_string) @@ -146,7 +146,7 @@ pub fn get_script_signable_string(stack: &[StackEntry]) -> String { /// ### Arguments /// /// * `tx_in` - TxIn value -pub fn get_tx_in_address_signable_string(tx_in: &TxIn) -> String { +fn get_tx_in_address_signable_string(tx_in: &TxIn) -> String { let out_point_signable_string = match &tx_in.previous_out { Some(out_point) => get_out_point_signable_string(out_point), None => "null".to_owned(), @@ -161,13 +161,13 @@ pub fn get_tx_in_address_signable_string(tx_in: &TxIn) -> String { /// ### Arguments /// /// * `tx_ins` - TxIn collection -pub fn construct_tx_ins_address(tx_ins: &[TxIn]) -> String { +pub fn construct_tx_ins_address(tx_ins: &[TxIn]) -> TxInsAddress { let signable_tx_ins = tx_ins .iter() .map(get_tx_in_address_signable_string) .collect::>() .join("-"); - hex::encode(sha3_256::digest(signable_tx_ins.as_bytes())) + TxInsAddress::from_hash(sha3_256::digest(signable_tx_ins.as_bytes())) } /// Get all the hash to remove from UTXO set for the utxo_entries @@ -1125,7 +1125,7 @@ mod tests { TxOut::new_token_amount(sender_address_excess, amount - payment, None); let expectation = DruidExpectation { - from: from_addr.clone(), + from: from_addr.to_string(), to: alice_addr.clone(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -1160,7 +1160,7 @@ mod tests { construct_payment_tx_ins(tx_ins_constructor) }; let expectation = DruidExpectation { - from: from_addr, + from: from_addr.to_string(), to: bob_addr.clone(), asset: Asset::Token(payment), }; @@ -1326,7 +1326,7 @@ mod tests { .collect(); let expected = - "c8b62d379f07602956207ea473ce20d9752d24ad6e6cd43cb042d024d7c6a468".to_owned(); + "c8b62d379f07602956207ea473ce20d9752d24ad6e6cd43cb042d024d7c6a468".parse().unwrap(); let actual = construct_tx_ins_address(&tx_ins); // From dea460ae5994758ba68c5416e1dbbd7e5a14fc8e Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Thu, 18 Jul 2024 14:56:31 +0200 Subject: [PATCH 29/30] druid: Change DruidExpectation.from to TxInsAddress --- src/primitives/address.rs | 18 ++++++++++++++++++ src/primitives/druid.rs | 4 ++-- src/utils/druid_utils.rs | 10 +++++----- src/utils/transaction_utils.rs | 10 +++------- 4 files changed, 28 insertions(+), 14 deletions(-) diff --git a/src/primitives/address.rs b/src/primitives/address.rs index 5b7bceb..d20c573 100644 --- a/src/primitives/address.rs +++ b/src/primitives/address.rs @@ -135,6 +135,24 @@ impl TxInsAddress { } } +impl Serialize for TxInsAddress { + fn serialize(&self, serializer: S) -> Result { + // TODO: jrabil: re-enable this once refactor/addresses-usages is merged + //assert!(serializer.is_human_readable(), "serializer must be human-readable!"); + serializer.serialize_str(&self.to_string()) + } +} + +impl<'de> Deserialize<'de> for TxInsAddress { + fn deserialize>(deserializer: D) -> Result { + // TODO: jrabil: re-enable this once refactor/addresses-usages is merged + //assert!(deserializer.is_human_readable(), "deserializer must be human-readable!"); + + let text : String = serde::Deserialize::deserialize(deserializer)?; + text.parse().map_err(::custom) + } +} + /// Wrapper enum representing an address of any type. #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd, Encode, Decode)] pub enum AnyAddress { diff --git a/src/primitives/druid.rs b/src/primitives/druid.rs index 5e3da1d..1a1cc31 100644 --- a/src/primitives/druid.rs +++ b/src/primitives/druid.rs @@ -1,11 +1,11 @@ use crate::primitives::asset::Asset; use serde::{Deserialize, Serialize}; -use crate::primitives::address::AnyAddress; +use crate::primitives::address::{AnyAddress, TxInsAddress}; /// The expectation to be met in a specific DRUID transaction #[derive(Clone, Debug, Ord, Eq, PartialEq, Serialize, Deserialize, PartialOrd)] pub struct DruidExpectation { - pub from: String, + pub from: TxInsAddress, pub to: AnyAddress, pub asset: Asset, } diff --git a/src/utils/druid_utils.rs b/src/utils/druid_utils.rs index 84ffac1..8c74f6e 100644 --- a/src/utils/druid_utils.rs +++ b/src/utils/druid_utils.rs @@ -54,7 +54,7 @@ pub fn druid_expectations_are_met<'a>( /// * `e` - The expectation to check on /// * `tx_source` - The source transaction source to match against fn expectation_met(e: &DruidExpectation, tx_source: &BTreeSet<(TxInsAddress, &AnyAddress, &Asset)>) -> bool { - tx_source.get(&(e.from.parse().expect(&e.from), &e.to, &e.asset)).is_some() + tx_source.get(&(e.from, &e.to, &e.asset)).is_some() } #[cfg(test)] @@ -111,12 +111,12 @@ mod tests { // Expectations (from addresses the same due to empty TxIn) let expects = vec![ DruidExpectation { - from: from_addr.to_string(), + from: from_addr, to: bob_addr, asset: alice_asset, }, DruidExpectation { - from: from_addr.to_string(), + from: from_addr, to: alice_addr, asset: bob_asset, }, @@ -175,7 +175,7 @@ mod tests { key_material.insert(prev_out, (pk, sk)); let expectation = DruidExpectation { - from: from_addr.to_string(), + from: from_addr, to: alice_addr.clone(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -212,7 +212,7 @@ mod tests { construct_payment_tx_ins(tx_ins_constructor) }; let expectation = DruidExpectation { - from: from_addr.to_string(), + from: from_addr, to: bob_addr, asset: Asset::Token(payment), }; diff --git a/src/utils/transaction_utils.rs b/src/utils/transaction_utils.rs index 7e2c8ba..12110e9 100644 --- a/src/utils/transaction_utils.rs +++ b/src/utils/transaction_utils.rs @@ -1063,11 +1063,7 @@ mod tests { script_public_key: to_address.clone(), }]; - let bytes = match bincode::serde::encode_to_vec(&tx_ins, bincode::config::legacy()) { - Ok(bytes) => bytes, - Err(_) => vec![], - }; - let from_addr = hex::encode(bytes); + let from_addr = construct_tx_ins_address(&tx_ins); // DDE params let druid = hex::encode(vec![1, 2, 3, 4, 5]); @@ -1125,7 +1121,7 @@ mod tests { TxOut::new_token_amount(sender_address_excess, amount - payment, None); let expectation = DruidExpectation { - from: from_addr.to_string(), + from: from_addr, to: alice_addr.clone(), asset: Asset::item(1, Some("genesis_hash".to_owned()), None), }; @@ -1160,7 +1156,7 @@ mod tests { construct_payment_tx_ins(tx_ins_constructor) }; let expectation = DruidExpectation { - from: from_addr.to_string(), + from: from_addr, to: bob_addr.clone(), asset: Asset::Token(payment), }; From 06e3bbe186cac35b0237808c7dd43fbb25449dad Mon Sep 17 00:00:00 2001 From: DaPorkchop_ Date: Fri, 2 Aug 2024 16:11:47 +0200 Subject: [PATCH 30/30] refactor/address-usages: fix build after merge --- src/primitives/transaction.rs | 3 ++- src/utils/script_utils.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/primitives/transaction.rs b/src/primitives/transaction.rs index c51ee5e..2230330 100644 --- a/src/primitives/transaction.rs +++ b/src/primitives/transaction.rs @@ -229,11 +229,12 @@ impl Transaction { mod tests { use crate::primitives::asset::{Asset, TokenAmount}; use crate::primitives::transaction::TxOut; + use crate::utils::Placeholder; #[test] fn test_overflow_tx_out() { let amount = TokenAmount(u64::MAX); - let tx_out = TxOut::new_token_amount("test".to_string(), amount, None); + let tx_out = TxOut::new_token_amount(Placeholder::placeholder(), amount, None); assert_eq!(tx_out.value, Asset::Token(TokenAmount::default())); } diff --git a/src/utils/script_utils.rs b/src/utils/script_utils.rs index a457f37..07ae19c 100644 --- a/src/utils/script_utils.rs +++ b/src/utils/script_utils.rs @@ -2910,7 +2910,7 @@ mod tests { let tx_out = TxOut { value: Asset::Token(TokenAmount(0)), locktime: 0, - script_public_key: Some("".to_string()), + script_public_key: Placeholder::placeholder(), }; tx.outputs.push(tx_out);