diff --git a/src/core/error/common_error.rs b/src/core/error/common_error.rs index 20f25f89e..cef62787b 100644 --- a/src/core/error/common_error.rs +++ b/src/core/error/common_error.rs @@ -52,8 +52,8 @@ pub enum CommonError { #[error("String not hex {bad_value}")] StringNotHex { bad_value: String } = 10011, - #[error("Invalid byte count, expected 32, found: {bad_value}")] - InvalidByteCountExpected32 { bad_value: u64 } = 10012, + #[error("Invalid byte count, expected {expected}, found: {found}")] + InvalidByteCount { expected: u64, found: u64 } = 10012, #[error("Invalid BIP32 path '{bad_value}'.")] InvalidBIP32Path { bad_value: String } = 10013, diff --git a/src/core/secure_random_bytes.rs b/src/core/secure_random_bytes.rs index d4ec3c8f3..f94ecc9f6 100644 --- a/src/core/secure_random_bytes.rs +++ b/src/core/secure_random_bytes.rs @@ -17,6 +17,13 @@ pub fn generate_32_bytes() -> Vec { generate_bytes::<32>() } +/// Generates `64` random bytes using a cryptographically +/// secure random generator and returns these bytes as +/// a Vec. +pub fn generate_64_bytes() -> Vec { + generate_bytes::<64>() +} + #[cfg(test)] mod tests { use std::collections::HashSet; diff --git a/src/core/types/epoch.rs b/src/core/types/epoch.rs new file mode 100644 index 000000000..5fea3e208 --- /dev/null +++ b/src/core/types/epoch.rs @@ -0,0 +1,62 @@ +pub use crate::prelude::*; + +// use radix_engine_common::types::Epoch as ScryptoEpoch; + +// Generate the FfiConverter needed by UniFFI for newtype `Epoch`. +uniffi::custom_newtype!(Epoch, u64); + +/// A type-safe consensus epoch number. +#[derive( + Clone, + Copy, + PartialEq, + Eq, + Hash, + Ord, + PartialOrd, + derive_more::Display, + derive_more::Debug, +)] +pub struct Epoch(pub u64); + +impl From for Epoch { + fn from(value: u64) -> Self { + Self(value) + } +} + +impl From for u64 { + fn from(value: Epoch) -> Self { + value.0 + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn from_u64() { + let test = + |u: u64| assert_eq!(Into::::into(Into::::into(u)), u); + test(0); + test(1); + test(2); + test(1337); + } + + #[test] + fn to_u64() { + let test = |u: u64| { + assert_eq!( + Into::::into(Into::::into(Into::::into(u))) + .0, + u + ) + }; + test(0); + test(1); + test(2); + test(1337); + } +} diff --git a/src/core/types/hex_32bytes.rs b/src/core/types/hex_32bytes.rs index 6a0489ad0..1eef512c1 100644 --- a/src/core/types/hex_32bytes.rs +++ b/src/core/types/hex_32bytes.rs @@ -2,168 +2,268 @@ use crate::prelude::*; use delegate::delegate; use radix_engine_common::crypto::{Hash, IsHash}; -/// Serializable 32 bytes which **always** serializes as a **hex** string, this is useful -/// since in Radix Wallet Kit we almost always want to serialize bytes into hex and this -/// allows us to skip using -#[derive( - Clone, - PartialEq, - Eq, - PartialOrd, - Ord, - Hash, - SerializeDisplay, - DeserializeFromStr, - derive_more::Display, - derive_more::Debug, - uniffi::Record, -)] -#[display("{}", self.to_hex())] -#[debug("{}", self.to_hex())] -pub struct Hex32Bytes { - bag_of_bytes: BagOfBytes, -} - -impl TryFrom for Hex32Bytes { - type Error = CommonError; - - fn try_from(value: BagOfBytes) -> Result { - if value.len() != 32 { - return Err(CommonError::InvalidByteCountExpected32 { - bad_value: value.len() as u64, - }); +macro_rules! decl_bag_of_n_bytes { + ($struct_name:ident, $byte_count:expr) => { + /// Serializable $byte_count bytes which **always** serializes as a **hex** string, this is useful + /// since in Radix Wallet Kit we almost always want to serialize bytes into hex and this + /// allows us to skip using + #[derive( + Clone, + PartialEq, + Eq, + PartialOrd, + Ord, + Hash, + SerializeDisplay, + DeserializeFromStr, + derive_more::Display, + derive_more::Debug, + uniffi::Record, + )] + #[display("{}", self.to_hex())] + #[debug("{}", self.to_hex())] + pub struct $struct_name { + bag_of_bytes: BagOfBytes, } - Ok(Self { - bag_of_bytes: value, - }) - } -} -impl Hex32Bytes { - /// Instantiates a new `BagOfBytes` from bytes generated by - /// a CSPRNG. - pub fn generate() -> Self { - Hex32Bytes { - bag_of_bytes: BagOfBytes::from(generate_32_bytes()), + impl TryFrom for $struct_name { + type Error = CommonError; + + fn try_from(value: BagOfBytes) -> Result { + if value.len() != $byte_count { + return Err(CommonError::InvalidByteCount { + expected: $byte_count as u64, + found: value.len() as u64, + }); + } + Ok(Self { + bag_of_bytes: value, + }) + } } - } - /// Tries to decode the string `s` into a `Hex32Bytes`. Will fail - /// if the string is not valid hex or if the decoded bytes does - /// not have length 32. - pub fn from_hex(s: &str) -> Result { - Self::from_str(s) - } + impl $struct_name { + /// Instantiates a new `BagOfBytes` from bytes generated by + /// a CSPRNG. + pub fn generate() -> Self { + $struct_name { + bag_of_bytes: BagOfBytes::from( + generate_bytes::<$byte_count>(), + ), + } + } + + /// Tries to decode the string `s` into a `$struct_name`. Will fail + /// if the string is not valid hex or if the decoded bytes does + /// not have length 32. + pub fn from_hex(s: &str) -> Result { + Self::from_str(s) + } + + /// Instantiates a new `$struct_name` from the $byte_count bytes, by cloning them. + pub fn from_bytes(bytes: &[u8; $byte_count]) -> Self { + let bytes: &[u8] = bytes.as_slice().into(); + let bag_of_bytes: BagOfBytes = bytes.into(); + Self { bag_of_bytes } + } + } - /// Instantiates a new `Hex32Bytes` from the 32 bytes, by cloning them. - pub fn from_bytes(bytes: &[u8; 32]) -> Self { - Self { - bag_of_bytes: bytes.into(), + impl $struct_name { + /// Returns a references to the inner array slice. + pub fn bytes(&self) -> [u8; $byte_count] { + self.bag_of_bytes + .to_vec() + .as_slice() + .try_into() + .expect("$byte_count bytes") + } } - } -} -impl Hex32Bytes { - /// Returns a references to the inner array slice. - pub fn bytes(&self) -> [u8; 32] { - self.bag_of_bytes - .to_vec() - .as_slice() - .try_into() - .expect("32 bytes") - } -} + impl TryFrom> for $struct_name { + type Error = CommonError; -impl TryFrom> for Hex32Bytes { - type Error = CommonError; + fn try_from(value: Vec) -> Result { + BagOfBytes::from(value).try_into() + } + } - fn try_from(value: Vec) -> Result { - BagOfBytes::from(value).try_into() - } -} + impl FromStr for $struct_name { + type Err = CommonError; -impl FromStr for Hex32Bytes { - type Err = CommonError; + fn from_str(s: &str) -> Result { + s.parse::().and_then(|v| v.try_into()) + } + } - fn from_str(s: &str) -> Result { - s.parse::().and_then(|v| v.try_into()) - } + impl $struct_name { + delegate! { + to self.bag_of_bytes{ + pub fn to_hex(&self) -> String; + pub fn to_vec(&self) -> Vec; + } + } + } + }; } -impl From for Hex32Bytes { - /// Instantiates a new `Hex32Bytes` from the `Hash` (32 bytes). - fn from(value: Hash) -> Self { - Self::from_bytes(&value.into_bytes()) - } -} +decl_bag_of_n_bytes!(Hex32Bytes, 32); +decl_bag_of_n_bytes!(Hex64Bytes, 64); +decl_bag_of_n_bytes!(Hex33Bytes, 33); +decl_bag_of_n_bytes!(Hex65Bytes, 65); + +macro_rules! decl_placeholders_for_bag_of_n_bytes { + ($struct_name:ident, $byte_count:expr) => { + impl HasPlaceholder for $struct_name { + /// `deadbeef...`` + /// A placeholder used to facilitate unit tests. + fn placeholder() -> Self { + Self::placeholder_dead() + } + + /// A placeholder used to facilitate unit tests. + fn placeholder_other() -> Self { + Self::placeholder_fade() + } + } -impl Hex32Bytes { - delegate! { - to self.bag_of_bytes{ - pub fn to_hex(&self) -> String; - pub fn to_vec(&self) -> Vec; + impl $struct_name { + /// `aced...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_aced() -> Self { + Self::from_str(&"aced".repeat($byte_count / 2)) + .expect("aced...") + } + + /// `babe...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_babe() -> Self { + Self::from_str(&"babe".repeat($byte_count / 2)) + .expect("babe...") + } + + /// `cafe...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_cafe() -> Self { + Self::from_str(&"cafe".repeat($byte_count / 2)) + .expect("cafe...") + } + + /// `dead...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_dead() -> Self { + Self::from_str(&"dead".repeat($byte_count / 2)) + .expect("dead...") + } + + /// `ecad...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_ecad() -> Self { + Self::from_str(&"ecad".repeat(16)).expect("ecad...") + } + + /// `fade...`` + /// A placeholder used to facilitate unit tests. + pub fn placeholder_fade() -> Self { + Self::from_str(&"fade".repeat($byte_count / 2)) + .expect("fade...") + } } - } + }; } -impl HasPlaceholder for Hex32Bytes { - /// `deadbeef...`` +// The impl of placeholders require an equal number of bytes +decl_placeholders_for_bag_of_n_bytes!(Hex32Bytes, 32); +decl_placeholders_for_bag_of_n_bytes!(Hex64Bytes, 64); + +impl HasPlaceholder for Hex33Bytes { + /// `33deadbeefdead...`` /// A placeholder used to facilitate unit tests. fn placeholder() -> Self { - Self::placeholder_dead() + let s = "dead".repeat(16); + Self::from_str(&format!("33{s}")) + .expect("Should have declared a valid Hex33Bytes placeholder") } - /// A placeholder used to facilitate unit tests. + /// `33beefbeefbeef...`` + /// Another placeholder used to facilitate unit tests. fn placeholder_other() -> Self { - Self::placeholder_fade() + let s = "beef".repeat(16); + Self::from_str(&format!("33{s}")) + .expect("Should have declared a valid Hex33Bytes placeholder") } } -impl Hex32Bytes { - /// `aced...`` +impl HasPlaceholder for Hex65Bytes { + /// `65deadbeefdead...`` /// A placeholder used to facilitate unit tests. - pub fn placeholder_aced() -> Self { - Self::from_str(&"aced".repeat(16)).expect("aced...") + fn placeholder() -> Self { + let s = "dead".repeat(32); + Self::from_str(&format!("65{s}")) + .expect("Should have declared a valid Hex65Bytes placeholder") } - /// `babe...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_babe() -> Self { - Self::from_str(&"babe".repeat(16)).expect("babe...") + /// `65beefbeefbeef...`` + /// Another placeholder used to facilitate unit tests. + fn placeholder_other() -> Self { + let s = "beef".repeat(32); + Self::from_str(&format!("65{s}")) + .expect("Should have declared a valid Hex65Bytes placeholder") } +} - /// `cafe...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_cafe() -> Self { - Self::from_str(&"cafe".repeat(16)).expect("cafe...") - } +#[uniffi::export] +pub fn new_hex32_bytes_from(bytes: Vec) -> Result { + Hex32Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex32_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex32Bytes::try_from(bag_of_bytes) +} - /// `dead...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_dead() -> Self { - Self::from_str(&"dead".repeat(16)).expect("dead...") +impl From for Hex32Bytes { + /// Instantiates a new `$struct_name` from the `Hash` ($byte_count bytes). + fn from(value: Hash) -> Self { + Self::from_bytes(&value.into_bytes()) } +} - /// `ecad...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_ecad() -> Self { - Self::from_str(&"ecad".repeat(16)).expect("ecad...") - } +#[uniffi::export] +pub fn new_hex64_bytes_from(bytes: Vec) -> Result { + Hex64Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex64_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex64Bytes::try_from(bag_of_bytes) +} - /// `fade...`` - /// A placeholder used to facilitate unit tests. - pub fn placeholder_fade() -> Self { - Self::from_str(&"fade".repeat(16)).expect("fade...") - } +#[uniffi::export] +pub fn new_hex33_bytes_from(bytes: Vec) -> Result { + Hex33Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex33_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex33Bytes::try_from(bag_of_bytes) } #[uniffi::export] -pub fn new_hex32_bytes_from(bytes: Vec) -> Result { - Hex32Bytes::try_from(bytes) +pub fn new_hex65_bytes_from(bytes: Vec) -> Result { + Hex65Bytes::try_from(bytes) +} +#[uniffi::export] +pub fn new_hex65_bytes_from_bag( + bag_of_bytes: BagOfBytes, +) -> Result { + Hex65Bytes::try_from(bag_of_bytes) } #[cfg(test)] -mod tests { +mod tests_hex32_bytes { use crate::prelude::*; @@ -256,7 +356,10 @@ mod tests { fn invalid_len() { assert_eq!( SUT::try_from(Vec::from([0u8; 5])), - Err(CommonError::InvalidByteCountExpected32 { bad_value: 5 }) + Err(CommonError::InvalidByteCount { + expected: 32, + found: 5 + }) ) } @@ -273,7 +376,7 @@ mod tests { } #[cfg(test)] -mod uniffi_tests { +mod hex32_uniffi_tests { use crate::prelude::*; #[test] @@ -285,8 +388,454 @@ mod uniffi_tests { ); } + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<32>(); + assert_eq!( + new_hex32_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + #[test] fn new_fail() { assert!(new_hex32_bytes_from(generate_bytes::<5>()).is_err()); } } + +// Copy paste + +#[cfg(test)] +mod tests_hex64_bytes { + + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Hex64Bytes; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn from_string_roundtrip() { + let str = + "10000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; + assert_eq!(SUT::from_hex(str).unwrap().to_string(), str); + } + + #[test] + fn debug() { + let str = + "deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn display() { + let str = + "deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{}", hex_bytes), str); + } + + #[test] + fn to_hex() { + let str = + "deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(hex_bytes.to_string(), str); + } + + #[test] + fn json_roundtrip() { + let model = SUT::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::(json!("not even hex")); + assert_json_value_fails::(json!("deadbeef")); + assert_json_value_fails::(json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")); + } + + #[test] + fn from_bytes_roundtrip() { + let bytes = [0u8; 64]; + assert_eq!(SUT::from_bytes(&bytes).bytes(), bytes); + } + + #[test] + fn from_vec_roundtrip() { + let vec = Vec::from([0u8; 64]); + let sut: SUT = vec.clone().try_into().unwrap(); + assert_eq!(sut.to_vec(), vec); + } + + #[test] + fn invalid_str() { + let s = "invalid str"; + assert_eq!( + SUT::from_str(s), + Err(CommonError::StringNotHex { + bad_value: s.to_owned() + }) + ); + } + + #[test] + fn invalid_len() { + assert_eq!( + SUT::try_from(Vec::from([0u8; 5])), + Err(CommonError::InvalidByteCount { + expected: 64, + found: 5 + }) + ) + } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = SUT::generate(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } +} + +#[cfg(test)] +mod hex64_uniffi_tests { + use crate::prelude::*; + + #[test] + fn new_ok() { + let bytes = generate_64_bytes(); + assert_eq!( + new_hex64_bytes_from(bytes.clone()).unwrap().to_vec(), + bytes + ); + } + + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<64>(); + assert_eq!( + new_hex64_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + + #[test] + fn new_fail() { + assert!(new_hex64_bytes_from(generate_bytes::<5>()).is_err()); + } +} + +// Copy paste + +#[cfg(test)] +mod tests_hex33_bytes { + + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Hex33Bytes; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn from_string_roundtrip() { + let str = + "100000000000000000000000000000000000000000000000000000000000000002"; + assert_eq!(SUT::from_hex(str).unwrap().to_string(), str); + } + + #[test] + fn debug() { + let str = + "33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn display() { + let str = + "33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{}", hex_bytes), str); + } + + #[test] + fn to_hex() { + let str = + "33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(hex_bytes.to_string(), str); + } + + #[test] + fn json_roundtrip() { + let model = SUT::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("33deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::(json!("not even hex")); + assert_json_value_fails::(json!("deadbeef")); + assert_json_value_fails::(json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")); + } + + #[test] + fn from_bytes_roundtrip() { + let bytes = [0u8; 33]; + assert_eq!(SUT::from_bytes(&bytes).bytes(), bytes); + } + + #[test] + fn from_vec_roundtrip() { + let vec = Vec::from([0u8; 33]); + let sut: SUT = vec.clone().try_into().unwrap(); + assert_eq!(sut.to_vec(), vec); + } + + #[test] + fn invalid_str() { + let s = "invalid str"; + assert_eq!( + SUT::from_str(s), + Err(CommonError::StringNotHex { + bad_value: s.to_owned() + }) + ); + } + + #[test] + fn invalid_len() { + assert_eq!( + SUT::try_from(Vec::from([0u8; 5])), + Err(CommonError::InvalidByteCount { + expected: 33, + found: 5 + }) + ) + } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = SUT::generate(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } +} + +#[cfg(test)] +mod hex33_uniffi_tests { + use crate::prelude::*; + + #[test] + fn new_ok() { + let bytes = generate_bytes::<33>(); + assert_eq!( + new_hex33_bytes_from(bytes.clone()).unwrap().to_vec(), + bytes + ); + } + + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<33>(); + assert_eq!( + new_hex33_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + + #[test] + fn new_fail() { + assert!(new_hex33_bytes_from(generate_bytes::<5>()).is_err()); + } +} + +// Copy paste + +#[cfg(test)] +mod tests_hex65_bytes { + + use crate::prelude::*; + + #[allow(clippy::upper_case_acronyms)] + type SUT = Hex65Bytes; + + #[test] + fn equality() { + assert_eq!(SUT::placeholder(), SUT::placeholder()); + assert_eq!(SUT::placeholder_other(), SUT::placeholder_other()); + } + + #[test] + fn inequality() { + assert_ne!(SUT::placeholder(), SUT::placeholder_other()); + } + + #[test] + fn from_string_roundtrip() { + let str = + "6510000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002"; + assert_eq!(SUT::from_hex(str).unwrap().to_string(), str); + } + + #[test] + fn debug() { + let str = + "65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{:?}", hex_bytes), str); + } + + #[test] + fn display() { + let str = + "65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(format!("{}", hex_bytes), str); + } + + #[test] + fn to_hex() { + let str = + "65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"; + let hex_bytes = SUT::placeholder(); + assert_eq!(hex_bytes.to_string(), str); + } + + #[test] + fn json_roundtrip() { + let model = SUT::placeholder(); + assert_json_value_eq_after_roundtrip( + &model, + json!("65deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead"), + ); + } + + #[test] + fn json_roundtrip_fails_for_invalid() { + assert_json_value_fails::(json!("not even hex")); + assert_json_value_fails::(json!("deadbeef")); + assert_json_value_fails::(json!("deaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddeaddead")); + } + + #[test] + fn from_bytes_roundtrip() { + let bytes = [0u8; 65]; + assert_eq!(SUT::from_bytes(&bytes).bytes(), bytes); + } + + #[test] + fn from_vec_roundtrip() { + let vec = Vec::from([0u8; 65]); + let sut: SUT = vec.clone().try_into().unwrap(); + assert_eq!(sut.to_vec(), vec); + } + + #[test] + fn invalid_str() { + let s = "invalid str"; + assert_eq!( + SUT::from_str(s), + Err(CommonError::StringNotHex { + bad_value: s.to_owned() + }) + ); + } + + #[test] + fn invalid_len() { + assert_eq!( + SUT::try_from(Vec::from([0u8; 5])), + Err(CommonError::InvalidByteCount { + expected: 65, + found: 5 + }) + ) + } + + #[test] + fn random() { + let mut set: HashSet> = HashSet::new(); + let n = 100; + for _ in 0..n { + let bytes = SUT::generate(); + set.insert(bytes.to_vec()); + } + assert_eq!(set.len(), n); + } +} + +#[cfg(test)] +mod hex65_uniffi_tests { + use crate::prelude::*; + + #[test] + fn new_ok() { + let bytes = generate_bytes::<65>(); + assert_eq!( + new_hex65_bytes_from(bytes.clone()).unwrap().to_vec(), + bytes + ); + } + + #[test] + fn new_from_bag_of_bytes() { + let bytes = generate_bytes::<65>(); + assert_eq!( + new_hex65_bytes_from_bag(bytes.clone().into()) + .unwrap() + .to_vec(), + bytes + ); + } + + #[test] + fn new_fail() { + assert!(new_hex65_bytes_from(generate_bytes::<5>()).is_err()); + } +} diff --git a/src/core/types/mod.rs b/src/core/types/mod.rs index ab8b4e5e4..acaf86ec6 100644 --- a/src/core/types/mod.rs +++ b/src/core/types/mod.rs @@ -1,21 +1,27 @@ mod bag_of_bytes; mod decimal192; mod entity_kind; +mod epoch; mod hex_32bytes; mod identified_vec_via; mod keys; mod locale_config; mod logged_result; +mod nonce; mod rounding_mode; mod safe_to_log; +mod signatures; pub use bag_of_bytes::*; pub use decimal192::*; pub use entity_kind::*; +pub use epoch::*; pub use hex_32bytes::*; pub use identified_vec_via::*; pub use keys::*; pub use locale_config::*; pub use logged_result::*; +pub use nonce::*; pub use rounding_mode::*; pub use safe_to_log::*; +pub use signatures::*; diff --git a/src/core/types/nonce.rs b/src/core/types/nonce.rs new file mode 100644 index 000000000..61ab5cb08 --- /dev/null +++ b/src/core/types/nonce.rs @@ -0,0 +1,79 @@ +pub use crate::prelude::*; + +// Generate the FfiConverter needed by UniFFI for newtype `Nonce`. +uniffi::custom_newtype!(Nonce, u32); + +/// A random number generated part of a transaction header, +/// ensuring every transaction os unique even though its +/// transaction manifest might be equal. This nonce is +/// generated by wallets for incoming transactions. +#[derive( + Clone, Copy, PartialEq, Eq, Hash, derive_more::Display, derive_more::Debug, +)] +pub struct Nonce(pub u32); + +impl Nonce { + /// Generates 4 random bytes and creates a Nonce (u32) from it. + pub fn random() -> Self { + u32::from_be_bytes( + generate_bytes::<4>() + .as_slice() + .try_into() + .expect("It is 4 bytes."), + ) + .into() + } +} + +impl From for Nonce { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl From for u32 { + fn from(value: Nonce) -> Self { + value.0 + } +} + +#[cfg(test)] +mod tests { + use crate::prelude::*; + + #[test] + fn from_u32() { + let test = + |u: u32| assert_eq!(Into::::into(Into::::into(u)), u); + test(0); + test(1); + test(2); + test(1337); + } + + #[test] + fn to_u32() { + let test = |u: u32| { + assert_eq!( + Into::::into(Into::::into(Into::::into(u))) + .0, + u + ) + }; + test(0); + test(1); + test(2); + test(1337); + } + + #[test] + fn generate_new() { + let mut set: HashSet = HashSet::new(); + let n = 100; + for _ in 0..n { + let sut = Nonce::random(); + set.insert(sut); + } + assert_eq!(set.len(), n); // with a low probability this might fail yes. + } +} diff --git a/src/core/types/signatures/ed25519_signature.rs b/src/core/types/signatures/ed25519_signature.rs new file mode 100644 index 000000000..6101f3a47 --- /dev/null +++ b/src/core/types/signatures/ed25519_signature.rs @@ -0,0 +1,23 @@ +// use crate::prelude::*; + +// use radix_engine_common::crypto::Ed25519Signature as ScryptoEd25519Signature; + +// /// Represents an ED25519 signature. +// #[derive( +// Clone, +// PartialEq, +// Eq, +// PartialOrd, +// Ord, +// Hash, +// SerializeDisplay, +// DeserializeFromStr, +// derive_more::Display, +// derive_more::Debug, +// uniffi::Record, +// )] +// #[display("{}", self.to_hex())] +// #[debug("{}", self.to_hex())] +// pub struct Ed25519Signature { +// pub bytes: BagOfBytes, +// } diff --git a/src/core/types/signatures/mod.rs b/src/core/types/signatures/mod.rs new file mode 100644 index 000000000..985f78b57 --- /dev/null +++ b/src/core/types/signatures/mod.rs @@ -0,0 +1,9 @@ +mod ed25519_signature; +mod secp256k1_signature; +mod signature; +mod signature_with_public_key; + +pub use ed25519_signature::*; +pub use secp256k1_signature::*; +pub use signature::*; +pub use signature_with_public_key::*; diff --git a/src/core/types/signatures/secp256k1_signature.rs b/src/core/types/signatures/secp256k1_signature.rs new file mode 100644 index 000000000..061b8f8f6 --- /dev/null +++ b/src/core/types/signatures/secp256k1_signature.rs @@ -0,0 +1,3 @@ +// use crate::prelude::*; + +// use radix_engine_common::crypto::Secp256k1Signature as ScryptoSecp256k1Signature; diff --git a/src/core/types/signatures/signature.rs b/src/core/types/signatures/signature.rs new file mode 100644 index 000000000..5773a3250 --- /dev/null +++ b/src/core/types/signatures/signature.rs @@ -0,0 +1,23 @@ +// use crate::prelude::*; + +// #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +// pub enum SignatureV1 { +// Secp256k1(Secp256k1Signature), +// Ed25519(Ed25519Signature), +// } + +// impl From for SignatureV1 { +// fn from(signature: Secp256k1Signature) -> Self { +// Self::Secp256k1(signature) +// } +// } + +// impl From for SignatureV1 { +// fn from(signature: Ed25519Signature) -> Self { +// Self::Ed25519(signature) +// } +// } + +// #[derive(Debug, Clone, Eq, PartialEq, ManifestSbor)] +// #[sbor(transparent)] +// pub struct NotarySignatureV1(pub SignatureV1); diff --git a/src/core/types/signatures/signature_with_public_key.rs b/src/core/types/signatures/signature_with_public_key.rs new file mode 100644 index 000000000..24f088283 --- /dev/null +++ b/src/core/types/signatures/signature_with_public_key.rs @@ -0,0 +1,42 @@ +// use crate::prelude::*; + +// use radix_engine_common::crypto::Ed25519Signature as ScryptoEd25519Signature; +// use radix_engine_common::crypto::Secp256k1Signature as ScryptoSecp256k1Signature; + +// /// Represents any natively supported signature, including public key. +// #[derive(Debug, Clone, PartialEq, Eq, Hash, uniffi::Enum, EnumAsInner)] +// pub enum SignatureWithPublicKeyV1 { +// Secp256k1 { +// signature: ScryptoSecp256k1Signature, +// }, +// Ed25519 { +// public_key: Ed25519PublicKey, +// signature: ScryptoEd25519Signature, +// }, +// } + +// impl SignatureWithPublicKeyV1 { +// pub fn signature(&self) -> SignatureV1 { +// match &self { +// Self::Secp256k1 { signature } => signature.clone().into(), +// Self::Ed25519 { signature, .. } => signature.clone().into(), +// } +// } +// } + +// impl From for SignatureWithPublicKeyV1 { +// fn from(signature: Secp256k1Signature) -> Self { +// Self::Secp256k1 { signature } +// } +// } + +// impl From<(Ed25519PublicKey, Ed25519Signature)> for SignatureWithPublicKeyV1 { +// fn from( +// (public_key, signature): (Ed25519PublicKey, Ed25519Signature), +// ) -> Self { +// Self::Ed25519 { +// public_key, +// signature, +// } +// } +// } diff --git a/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs b/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs index f343ed091..402960329 100644 --- a/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs +++ b/src/profile/v100/networks/network/authorized_dapp/shared_with_dapp.rs @@ -32,7 +32,7 @@ macro_rules! declare_shared_with_dapp { impl $struct_name { /// Constructs a new $struct_name where `ids` "fulfills" the `request`. /// - /// # Panic + /// # Panics /// Panics if `ids` does not fulfill `request`, for more information /// see [`RequestedQuantity::is_fulfilled_by_ids`] pub fn new( diff --git a/src/wrapped_radix_engine_toolkit/execution_summary.rs b/src/wrapped_radix_engine_toolkit/execution_summary.rs index e69de29bb..c8103ebd0 100644 --- a/src/wrapped_radix_engine_toolkit/execution_summary.rs +++ b/src/wrapped_radix_engine_toolkit/execution_summary.rs @@ -0,0 +1,6 @@ +use crate::prelude::*; + +pub type Blob = BagOfBytes; +pub type Blobs = Vec; + +dummy_sargon!(ExecutionSummary); diff --git a/src/wrapped_radix_engine_toolkit/manifest_summary.rs b/src/wrapped_radix_engine_toolkit/manifest_summary.rs new file mode 100644 index 000000000..9e02ad856 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/manifest_summary.rs @@ -0,0 +1,21 @@ +use crate::prelude::*; + +dummy_sargon!(ManifestSummary); + +impl ManifestSummary { + pub fn addresses_of_accounts_deposited_into(&self) -> Vec { + todo!() + } + + pub fn addresses_of_accounts_withdrawn_from(&self) -> Vec { + todo!() + } + + pub fn addresses_of_accounts_requiring_auth(&self) -> Vec { + todo!() + } + + pub fn addresses_of_personas_requiring_auth(&self) -> Vec { + todo!() + } +} diff --git a/src/wrapped_radix_engine_toolkit/message.rs b/src/wrapped_radix_engine_toolkit/message.rs new file mode 100644 index 000000000..b8e72cee6 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/message.rs @@ -0,0 +1,16 @@ +use crate::prelude::*; + +#[derive( + Clone, + Debug, + PartialEq, + EnumAsInner, + Eq, + Hash, + PartialOrd, + Ord, + uniffi::Enum, +)] +pub enum Message { + PlainText { string: String }, +} diff --git a/src/wrapped_radix_engine_toolkit/mod.rs b/src/wrapped_radix_engine_toolkit/mod.rs index e22c206f3..388b17602 100644 --- a/src/wrapped_radix_engine_toolkit/mod.rs +++ b/src/wrapped_radix_engine_toolkit/mod.rs @@ -1,3 +1,23 @@ +#[macro_use] mod dummy_types; +mod execution_summary; +mod manifest_summary; +mod message; +mod notarized_transaction; +mod signed_intent; +mod transaction_hash; +mod transaction_header; +mod transaction_intent; +mod transaction_manifest; + pub use dummy_types::*; +pub use execution_summary::*; +pub use manifest_summary::*; +pub use message::*; +pub use notarized_transaction::*; +pub use signed_intent::*; +pub use transaction_hash::*; +pub use transaction_header::*; +pub use transaction_intent::*; +pub use transaction_manifest::*; diff --git a/src/wrapped_radix_engine_toolkit/notarized_transaction.rs b/src/wrapped_radix_engine_toolkit/notarized_transaction.rs new file mode 100644 index 000000000..0c61ba1ef --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/notarized_transaction.rs @@ -0,0 +1,19 @@ +use crate::prelude::*; + +dummy_sargon!(NotarizedTransaction); + +// impl NotarizedTransaction { +// pub fn new +/* +public init( + signedIntent: SignedIntent, + notarySignature: SLIP10.Signature + ) throws { + sargon() + } + + public func compile() throws -> Data { + sargon() + } +*/ +// } diff --git a/src/wrapped_radix_engine_toolkit/signed_intent.rs b/src/wrapped_radix_engine_toolkit/signed_intent.rs new file mode 100644 index 000000000..e0c7e6c0f --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/signed_intent.rs @@ -0,0 +1,39 @@ +// use crate::prelude::*; + +// #[derive( +// Clone, +// Debug, +// Default, +// PartialEq, +// Eq, +// Hash, +// uniffi::Record, +// )] +// pub struct IntentSignature(pub SignatureWithPublicKeyV1); + +// #[derive( +// Clone, +// Debug, +// Default, +// PartialEq, +// Eq, +// Hash, +// uniffi::Record, +// )] +// pub struct IntentSignatures { +// pub signatures: Vec, +// } + +// #[derive( +// Clone, +// Debug, +// Default, +// PartialEq, +// Eq, +// Hash, +// uniffi::Record, +// )] +// pub struct SignedIntent { +// pub intent: TransactionIntent, +// pub intent_signatures: IntentSignatures, +// } diff --git a/src/wrapped_radix_engine_toolkit/transaction_hash.rs b/src/wrapped_radix_engine_toolkit/transaction_hash.rs new file mode 100644 index 000000000..82e4c638c --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_hash.rs @@ -0,0 +1,7 @@ +use radix_engine_toolkit::models::transaction_hash::TransactionHash as RETTransactionHash; + +use crate::prelude::*; + +dummy_sargon!(TransactionHash); + +impl TransactionIntent {} diff --git a/src/wrapped_radix_engine_toolkit/transaction_header.rs b/src/wrapped_radix_engine_toolkit/transaction_header.rs new file mode 100644 index 000000000..56d0504b9 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_header.rs @@ -0,0 +1,45 @@ +use crate::prelude::*; + +#[derive( + Debug, Clone, PartialEq, Eq, Hash, derive_more::Display, uniffi::Record, +)] +#[display("{} nonce: {}", network_id, nonce)] +pub struct TransactionHeader { + pub network_id: NetworkID, + pub start_epoch_inclusive: Epoch, + pub end_epoch_exclusive: Epoch, + pub nonce: Nonce, + pub notary_public_key: PublicKey, + pub notary_is_signatory: bool, + pub tip_percentage: u16, +} + +impl TransactionHeader { + /// Creates a new `TransactionHeader` + /// + /// # Panics + /// Panics if `end_epoch_exclusive < start_epoch_inclusive` + pub fn new( + network_id: NetworkID, + start_epoch_inclusive: Epoch, + end_epoch_exclusive: Epoch, + nonce: Nonce, + notary_public_key: PublicKey, + notary_is_signatory: bool, + tip_percentage: u16, + ) -> Self { + assert!( + end_epoch_exclusive >= start_epoch_inclusive, + "End epoch MUST be greater than or equal start epoch." + ); + Self { + network_id, + start_epoch_inclusive, + end_epoch_exclusive, + nonce, + notary_public_key, + notary_is_signatory, + tip_percentage, + } + } +} diff --git a/src/wrapped_radix_engine_toolkit/transaction_intent.rs b/src/wrapped_radix_engine_toolkit/transaction_intent.rs new file mode 100644 index 000000000..82132b8d1 --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_intent.rs @@ -0,0 +1,22 @@ +use crate::prelude::*; + +dummy_sargon!(TransactionIntent); + +#[allow(unused_variables)] +impl TransactionIntent { + pub fn new( + header: TransactionHeader, + manifest: TransactionManifest, + message: Message, + ) -> Self { + todo!() + } + + pub fn intent_hash(&self) -> TransactionHash { + todo!() + } + + pub fn compile(&self) -> Result { + todo!() + } +} diff --git a/src/wrapped_radix_engine_toolkit/transaction_manifest.rs b/src/wrapped_radix_engine_toolkit/transaction_manifest.rs new file mode 100644 index 000000000..f040eee3f --- /dev/null +++ b/src/wrapped_radix_engine_toolkit/transaction_manifest.rs @@ -0,0 +1,40 @@ +use crate::prelude::*; + +dummy_sargon!(TransactionManifest); + +#[allow(unused_variables)] +impl TransactionManifest { + pub fn new( + instructions_string: String, + network_id: NetworkID, + blobs: Blobs, + ) -> Self { + todo!() + } + + pub fn resource_addresses_to_refresh( + &self, + ) -> Option> { + todo!() + } + + pub fn instructions_string(&self) -> String { + todo!() + } + + pub fn blobs(&self) -> Blobs { + todo!() + } + + pub fn summary(&self, network_id: NetworkID) -> ManifestSummary { + todo!() + } + + pub fn execution_summary( + &self, + network_id: NetworkID, + encoded_receipt: BagOfBytes, // TODO: Replace with TYPE - read from GW. + ) -> ExecutionSummary { + todo!() + } +}