From 26e3c9a4b8188d65ed5f11eb2ae52ae377cc9471 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 11 Jul 2024 13:08:17 +0200 Subject: [PATCH 1/4] transactions: fix FileContract serialization --- src/transactions.rs | 75 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 58 insertions(+), 17 deletions(-) diff --git a/src/transactions.rs b/src/transactions.rs index b283190..2310981 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -198,9 +198,10 @@ impl fmt::Display for FileContractID { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FileContract { + #[serde(rename = "filesize")] pub file_size: u64, pub file_merkle_root: Hash256, pub window_start: u64, @@ -208,7 +209,7 @@ pub struct FileContract { pub payout: Currency, pub valid_proof_outputs: Vec, pub missed_proof_outputs: Vec, - pub unlock_hash: Address, + pub unlock_hash: Hash256, pub revision_number: u64, } @@ -828,22 +829,62 @@ mod tests { } #[test] - fn test_siafund_output() { - let output = SiafundOutput { - claim_start: Currency::new(123), - value: Currency::new(67856467336433871), - address: Address::parse_string( - "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69", - ) - .unwrap(), + fn test_serialize_filecontract() { + let contract = FileContract { + file_size: 1, + file_merkle_root: Hash256::new([ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + window_start: 2, + window_end: 3, + payout: Currency::new(456), + valid_proof_outputs: vec![SiacoinOutput { + value: Currency::new(789), + address: Address::new([ + 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }], + missed_proof_outputs: vec![SiacoinOutput { + value: Currency::new(101112), + address: Address::new([ + 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }], + unlock_hash: Hash256::new([ + 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + revision_number: 4, }; - let result = to_bytes(&output).expect("failed to serialize output"); - let expected: [u8; 56] = [ - 7, 0, 0, 0, 0, 0, 0, 0, 241, 19, 24, 247, 77, 16, 207, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, - 123, - ]; - assert_eq!(result, expected); + + // binary + let contract_serialized = to_bytes(&contract).unwrap(); + let contract_deserialized: FileContract = + from_reader(&mut &contract_serialized[..]).unwrap(); + assert_eq!( + contract_serialized, + [ + 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, + 2, 0, 0, 0, 0, 0, 0, 0, 1, 200, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, + 21, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 1, 138, 248, 3, + 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, 0, 0 + ], + ); + assert_eq!(contract_deserialized, contract); + + // json + let contract_serialized = serde_json::to_string(&contract).unwrap(); + let contract_deserialized: FileContract = + serde_json::from_str(&contract_serialized).unwrap(); + assert_eq!(contract_serialized, "{\"filesize\":1,\"fileMerkleRoot\":\"h:0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"payout\":\"456\",\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"101112\",\"address\":\"addr:0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"h:0404040000000000000000000000000000000000000000000000000000000000\",\"revisionNumber\":4}"); + assert_eq!(contract_deserialized, contract); } #[test] From abeec083f17001dbcf64b478b6f55137d872e05c Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Thu, 11 Jul 2024 15:15:12 +0200 Subject: [PATCH 2/4] fix test_serialize_filecontracdt_revision --- src/currency.rs | 7 ++ src/transactions.rs | 191 +++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 189 insertions(+), 9 deletions(-) diff --git a/src/currency.rs b/src/currency.rs index 9ea89f0..6628bef 100644 --- a/src/currency.rs +++ b/src/currency.rs @@ -1,5 +1,6 @@ use core::num::ParseIntError; use core::ops::{Add, Deref, DerefMut, Div, Mul, Sub}; +use std::iter::Sum; use serde::{Deserialize, Serialize}; @@ -200,6 +201,12 @@ impl Div for Currency { } } +impl Sum for Currency { + fn sum>(iter: I) -> Self { + iter.fold(Currency::new(0), Add::add) + } +} + #[derive(Debug, PartialEq)] pub enum CurrencyParseError { ParseIntErr(ParseIntError), diff --git a/src/transactions.rs b/src/transactions.rs index 2310981..dce83c7 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -162,14 +162,36 @@ pub struct SiafundOutput { pub claim_start: Currency, } -#[derive(Debug, Clone, Serialize)] -pub struct FileContractID([u8; 32]); +#[derive(Debug, Clone, PartialEq)] +pub struct FileContractID(Hash256); -impl FileContractID { - pub fn as_bytes(&self) -> [u8; 32] { - self.0 +impl Serialize for FileContractID { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + String::serialize(&self.to_string(), serializer) + } else { + Hash256::serialize(&self.0, serializer) + } + } +} + +impl<'de> Deserialize<'de> for FileContractID { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + FileContractID::parse_string(&s) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } else { + let data = Hash256::deserialize(deserializer)?; + Ok(FileContractID(data)) + } } +} +impl FileContractID { pub fn parse_string(s: &str) -> Result { let s = match s.split_once(':') { Some((_prefix, suffix)) => suffix, @@ -182,13 +204,13 @@ impl FileContractID { let mut data = [0u8; 32]; hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(FileContractID(data)) + Ok(FileContractID(Hash256::new(data))) } } impl AsRef<[u8]> for FileContractID { fn as_ref(&self) -> &[u8] { - &self.0 + self.0.as_bytes() } } @@ -213,20 +235,93 @@ pub struct FileContract { pub revision_number: u64, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FileContractRevision { #[serde(rename = "parentID")] pub parent_id: FileContractID, pub unlock_conditions: UnlockConditions, pub revision_number: u64, + #[serde(rename = "filesize")] pub file_size: u64, pub file_merkle_root: Hash256, pub window_start: u64, pub window_end: u64, pub valid_proof_outputs: Vec, pub missed_proof_outputs: Vec, - pub unlock_hash: Address, + pub unlock_hash: Hash256, +} + +impl Serialize for FileContractRevision { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct WithPayout<'a> { + #[serde(rename = "parentID")] + parent_id: &'a FileContractID, + unlock_conditions: &'a UnlockConditions, + revision_number: u64, + #[serde(rename = "filesize")] + file_size: u64, + file_merkle_root: &'a Hash256, + window_start: u64, + window_end: u64, + payout: &'a Currency, + valid_proof_outputs: &'a Vec, + missed_proof_outputs: &'a Vec, + unlock_hash: &'a Hash256, + } + ::serialize( + &WithPayout { + parent_id: &self.parent_id, + unlock_conditions: &self.unlock_conditions, + revision_number: self.revision_number, + file_size: self.file_size, + file_merkle_root: &self.file_merkle_root, + window_start: self.window_start, + window_end: self.window_end, + payout: &self.valid_proof_outputs.iter().map(|o| o.value).sum(), + valid_proof_outputs: &self.valid_proof_outputs, + missed_proof_outputs: &self.missed_proof_outputs, + unlock_hash: &self.unlock_hash, + }, + serializer, + ) + } else { + #[derive(Serialize)] + #[serde(rename_all = "camelCase")] + struct WithoutPayout<'a> { + #[serde(rename = "parentID")] + parent_id: &'a FileContractID, + unlock_conditions: &'a UnlockConditions, + revision_number: u64, + #[serde(rename = "filesize")] + file_size: u64, + file_merkle_root: &'a Hash256, + window_start: u64, + window_end: u64, + valid_proof_outputs: &'a Vec, + missed_proof_outputs: &'a Vec, + unlock_hash: &'a Hash256, + } + ::serialize( + &WithoutPayout { + parent_id: &self.parent_id, + unlock_conditions: &self.unlock_conditions, + revision_number: self.revision_number, + file_size: self.file_size, + file_merkle_root: &self.file_merkle_root, + window_start: self.window_start, + window_end: self.window_end, + valid_proof_outputs: &self.valid_proof_outputs, + missed_proof_outputs: &self.missed_proof_outputs, + unlock_hash: &self.unlock_hash, + }, + serializer, + ) + } + } } #[derive(Debug, Clone, Serialize)] @@ -887,6 +982,84 @@ mod tests { assert_eq!(contract_deserialized, contract); } + #[test] + fn test_serialize_filecontract_revision() { + let revision = FileContractRevision { + parent_id: FileContractID(Hash256::new([ + 9, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ])), + file_size: 1, + file_merkle_root: Hash256::new([ + 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + window_start: 2, + window_end: 3, + valid_proof_outputs: vec![SiacoinOutput { + value: Currency::new(789), + address: Address::new([ + 2, 2, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }], + missed_proof_outputs: vec![SiacoinOutput { + value: Currency::new(789), + address: Address::new([ + 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, + ]), + }], + unlock_conditions: UnlockConditions::new( + 123, + vec![UnlockKey::new( + Algorithm::ED25519, + PublicKey::new([ + 0x9a, 0xac, 0x1f, 0xfb, 0x1c, 0xfd, 0x10, 0x79, 0xa8, 0xc6, 0xc8, 0x7b, + 0x47, 0xda, 0x1d, 0x56, 0x7e, 0x35, 0xb9, 0x72, 0x34, 0x99, 0x3c, 0x28, + 0x8c, 0x1a, 0xd0, 0xdb, 0x1d, 0x1c, 0xe1, 0xb6, + ]), + )], + 1, + ), + unlock_hash: Hash256::new([ + 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, + ]), + revision_number: 4, + }; + + // binary + let revision_serialized = to_bytes(&revision).unwrap(); + let revision_deserialized: FileContractRevision = + from_reader(&mut &revision_serialized[..]).unwrap(); + assert_eq!( + revision_serialized, + [ + 9, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 123, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 101, 100, 50, 53, 53, + 49, 57, 0, 0, 0, 0, 0, 0, 0, 0, 0, 32, 0, 0, 0, 0, 0, 0, 0, 154, 172, 31, 251, 28, + 253, 16, 121, 168, 198, 200, 123, 71, 218, 29, 86, 126, 53, 185, 114, 52, 153, 60, + 40, 140, 26, 208, 219, 29, 28, 225, 182, 1, 0, 0, 0, 0, 0, 0, 0, 4, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, + 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 21, 2, 2, 2, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, + 0, 0, 0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 3, 21, 3, 3, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 4, 4, 4, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + ], + ); + assert_eq!(revision_deserialized, revision); + + // json + let revision_serialized = serde_json::to_string(&revision).unwrap(); + let revision_deserialized: FileContractRevision = + serde_json::from_str(&revision_serialized).unwrap(); + assert_eq!(revision_serialized, "{\"parentID\":\"fcid:0908070000000000000000000000000000000000000000000000000000000000\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1},\"revisionNumber\":4,\"filesize\":1,\"fileMerkleRoot\":\"h:0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"payout\":\"789\",\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"h:0404040000000000000000000000000000000000000000000000000000000000\"}"); + assert_eq!(revision_deserialized, revision); + } + #[test] fn test_transaction_id() { let txn = Transaction::default(); From 803f2e70b3e22d597891db89efeca521ae64e889 Mon Sep 17 00:00:00 2001 From: Chris Schinnerl Date: Fri, 12 Jul 2024 11:17:01 +0200 Subject: [PATCH 3/4] ImplHashID macro --- src/common.rs | 167 +++++++++++++++++------------- src/signing.rs | 2 +- src/spendpolicy.rs | 22 ++-- src/transactions.rs | 242 +++++--------------------------------------- 4 files changed, 131 insertions(+), 302 deletions(-) diff --git a/src/common.rs b/src/common.rs index 09051a4..377b1be 100644 --- a/src/common.rs +++ b/src/common.rs @@ -1,4 +1,4 @@ -use blake2b_simd::{Hash, Params}; +use blake2b_simd::Params; use core::fmt; use serde::{Deserialize, Serialize}; use std::fmt::Debug; @@ -14,92 +14,115 @@ impl fmt::Display for ChainIndex { } } -/// encapsulates the various errors that can occur when parsing a Sia object -/// from a string -#[derive(Debug, PartialEq)] -pub enum HexParseError { - MissingPrefix, - InvalidLength, - InvalidPrefix, - InvalidChecksum, // not every object has a checksum - HexError(hex::FromHexError), -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] -pub struct Hash256([u8; 32]); +// Macro to implement types used as identifiers which are 32 byte hashes and are +// serialized with a prefix +#[macro_export] +macro_rules! ImplHashID { + ($name:ident, $prefix:expr) => { + #[derive(Debug, Clone, Copy, PartialEq)] + pub struct $name([u8; 32]); + + impl serde::Serialize for $name { + fn serialize(&self, serializer: S) -> Result { + if serializer.is_human_readable() { + String::serialize(&self.to_string(), serializer) + } else { + <[u8; 32]>::serialize(&self.0, serializer) + } + } + } -impl Serialize for Hash256 { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize(&self.to_string(), serializer) - } else { - <[u8; 32]>::serialize(&self.0, serializer) + impl<'de> serde::Deserialize<'de> for $name { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + $name::parse_string(&s) + .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) + } else { + let data = <[u8; 32]>::deserialize(deserializer)?; + Ok($name(data)) + } + } } - } -} -impl<'de> Deserialize<'de> for Hash256 { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - Hash256::parse_string(&s).map_err(|e| serde::de::Error::custom(format!("{:?}", e))) - } else { - let data = <[u8; 32]>::deserialize(deserializer)?; - Ok(Hash256(data)) + impl $name { + // Example method that might be used in serialization/deserialization + pub fn parse_string(s: &str) -> Result { + let s = match s.split_once(':') { + Some((_prefix, suffix)) => suffix, + None => s, + }; + + if s.len() != 64 { + return Err(HexParseError::InvalidLength); + } + + let mut data = [0u8; 32]; + hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; + Ok($name(data)) + } } - } -} -impl Hash256 { - pub fn new(data: [u8; 32]) -> Self { - Hash256(data) - } + impl fmt::Display for $name { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}:{}", $prefix, hex::encode(self.0)) + } + } - pub fn as_bytes(&self) -> &[u8] { - self.0.as_ref() - } + impl From for $name { + fn from(hash: blake2b_simd::Hash) -> Self { + let mut h = [0; 32]; + h.copy_from_slice(&hash.as_bytes()[..32]); + Self(h) + } + } - pub fn as_array(&self) -> &[u8; 32] { - &self.0 - } + impl From<[u8; 32]> for $name { + fn from(data: [u8; 32]) -> Self { + $name(data) + } + } - pub fn parse_string(s: &str) -> Result { - let s = match s.split_once(':') { - Some((_prefix, suffix)) => suffix, - None => s, - }; + impl From<$name> for [u8; 32] { + fn from(hash: $name) -> [u8; 32] { + hash.0 + } + } - if s.len() != 64 { - return Err(HexParseError::InvalidLength); + impl AsRef<[u8; 32]> for $name { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } } - let mut data = [0u8; 32]; - hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(Hash256(data)) - } -} + impl AsRef<[u8]> for $name { + fn as_ref(&self) -> &[u8] { + &self.0 + } + } -impl fmt::Display for Hash256 { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "h:{}", hex::encode(self.0)) - } + impl Default for $name { + fn default() -> Self { + $name([0; 32]) + } + } + }; } -impl From for Hash256 { - fn from(hash: Hash) -> Self { - let mut h = [0; 32]; - h.copy_from_slice(&hash.as_bytes()[..32]); - Self(h) - } -} +ImplHashID!(Hash256, "h"); -impl AsRef<[u8]> for Hash256 { - fn as_ref(&self) -> &[u8] { - &self.0 - } +/// encapsulates the various errors that can occur when parsing a Sia object +/// from a string +#[derive(Debug, PartialEq)] +pub enum HexParseError { + MissingPrefix, + InvalidLength, + InvalidPrefix, + InvalidChecksum, // not every object has a checksum + HexError(hex::FromHexError), } /// An address that can be used to receive UTXOs diff --git a/src/signing.rs b/src/signing.rs index f1444cf..41d0604 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -98,7 +98,7 @@ impl PrivateKey { impl From for PrivateKey { fn from(hash: Hash256) -> Self { - PrivateKey::from_seed(hash.as_array()) + PrivateKey::from_seed(hash.as_ref()) } } diff --git a/src/spendpolicy.rs b/src/spendpolicy.rs index aaa7ade..2262fea 100644 --- a/src/spendpolicy.rs +++ b/src/spendpolicy.rs @@ -161,7 +161,7 @@ impl SpendPolicy { .next() .ok_or(ValidationError::MissingSignature) .and_then(|sig| { - pk.verify(hash.as_bytes(), sig) + pk.verify(hash.as_ref(), sig) .then_some(()) .ok_or(ValidationError::InvalidSignature) }), @@ -215,7 +215,7 @@ impl SpendPolicy { let mut remaining = uc.signatures_required; for pk in uc.public_keys.iter() { let sig = signatures.next().ok_or(ValidationError::MissingSignature)?; - if pk.public_key().verify(hash.as_bytes(), sig) { + if pk.public_key().verify(hash.as_ref(), sig) { remaining -= 1; if remaining == 0 { break; @@ -512,7 +512,7 @@ mod tests { }, { let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::new(random()); + let sig_hash = Hash256::from(random::<[u8; 32]>()); PolicyTest { policy: SpendPolicy::PublicKey(pk.public_key()), @@ -525,14 +525,14 @@ mod tests { hardforks: NetworkHardforks::default(), }, hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_bytes())], + signatures: vec![pk.sign(sig_hash.as_ref())], preimages: vec![], result: Ok(()), } }, { let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::new(random()); + let sig_hash = Hash256::from(random::<[u8; 32]>()); PolicyTest { policy: SpendPolicy::Threshold( @@ -551,14 +551,14 @@ mod tests { hardforks: NetworkHardforks::default(), }, hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_bytes())], + signatures: vec![pk.sign(sig_hash.as_ref())], preimages: vec![], result: Err(ValidationError::ThresholdNotMet), } }, { let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::new(random()); + let sig_hash = Hash256::from(random::<[u8; 32]>()); PolicyTest { policy: SpendPolicy::Threshold( @@ -584,7 +584,7 @@ mod tests { }, { let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::new(random()); + let sig_hash = Hash256::from(random::<[u8; 32]>()); PolicyTest { policy: SpendPolicy::Threshold( @@ -603,14 +603,14 @@ mod tests { hardforks: NetworkHardforks::default(), }, hash: sig_hash, - signatures: vec![pk.sign(sig_hash.as_bytes())], + signatures: vec![pk.sign(sig_hash.as_ref())], preimages: vec![], result: Ok(()), } }, { let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::new(random()); + let sig_hash = Hash256::from(random::<[u8; 32]>()); PolicyTest { policy: SpendPolicy::Threshold( @@ -636,7 +636,7 @@ mod tests { }, { let pk = PrivateKey::from_seed(&random()); - let sig_hash = Hash256::new(random()); + let sig_hash = Hash256::from(random::<[u8; 32]>()); PolicyTest { policy: SpendPolicy::Threshold( diff --git a/src/transactions.rs b/src/transactions.rs index dce83c7..ad0fe45 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -2,77 +2,15 @@ use crate::encoding::{serialize_array, to_writer, SerializeError}; use crate::signing::{PrivateKey, Signature, SigningState}; use crate::specifier::{specifier, Specifier}; use crate::unlock_conditions::UnlockConditions; -use crate::{Address, Currency}; +use crate::{Address, Currency, ImplHashID}; use crate::{Hash256, HexParseError}; use blake2b_simd::{Params, State}; use core::fmt; use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, PartialEq)] -pub struct SiacoinOutputID(Hash256); +ImplHashID!(SiacoinOutputID, "scoid"); -impl Serialize for SiacoinOutputID { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize(&self.to_string(), serializer) - } else { - Hash256::serialize(&self.0, serializer) - } - } -} - -impl<'de> Deserialize<'de> for SiacoinOutputID { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - SiacoinOutputID::parse_string(&s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) - } else { - let data = Hash256::deserialize(deserializer)?; - Ok(SiacoinOutputID(data)) - } - } -} - -impl SiacoinOutputID { - pub fn new(data: Hash256) -> Self { - SiacoinOutputID(data) - } - - pub fn from_bytes(data: [u8; 32]) -> Self { - SiacoinOutputID(Hash256::new(data)) - } - - pub fn parse_string(s: &str) -> Result { - let s = match s.split_once(':') { - Some((_prefix, suffix)) => suffix, - None => s, - }; - - if s.len() != 64 { - return Err(HexParseError::InvalidLength); - } - - let mut data = [0u8; 32]; - hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(SiacoinOutputID(Hash256::new(data))) - } -} - -impl From for Hash256 { - fn from(val: SiacoinOutputID) -> Self { - val.0 - } -} - -impl fmt::Display for SiacoinOutputID { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "scoid:{}", hex::encode(self.0)) - } -} +ImplHashID!(SiafundOutputID, "sfoid"); #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -89,62 +27,6 @@ pub struct SiacoinOutput { pub address: Address, } -#[derive(Debug, Clone, PartialEq)] -pub struct SiafundOutputID(Hash256); - -impl Serialize for SiafundOutputID { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize(&self.to_string(), serializer) - } else { - Hash256::serialize(&self.0, serializer) - } - } -} - -impl<'de> Deserialize<'de> for SiafundOutputID { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - SiafundOutputID::parse_string(&s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) - } else { - let data = Hash256::deserialize(deserializer)?; - Ok(SiafundOutputID(data)) - } - } -} - -impl SiafundOutputID { - pub fn as_bytes(&self) -> Hash256 { - self.0 - } - - pub fn parse_string(s: &str) -> Result { - let s = match s.split_once(':') { - Some((_prefix, suffix)) => suffix, - None => s, - }; - - if s.len() != 64 { - return Err(HexParseError::InvalidLength); - } - - let mut data = [0u8; 32]; - hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(SiafundOutputID(Hash256::new(data))) - } -} - -impl fmt::Display for SiafundOutputID { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "sfoid:{}", hex::encode(self.0)) - } -} - #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct SiafundInput { @@ -162,63 +44,7 @@ pub struct SiafundOutput { pub claim_start: Currency, } -#[derive(Debug, Clone, PartialEq)] -pub struct FileContractID(Hash256); - -impl Serialize for FileContractID { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - String::serialize(&self.to_string(), serializer) - } else { - Hash256::serialize(&self.0, serializer) - } - } -} - -impl<'de> Deserialize<'de> for FileContractID { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - if deserializer.is_human_readable() { - let s = String::deserialize(deserializer)?; - FileContractID::parse_string(&s) - .map_err(|e| serde::de::Error::custom(format!("{:?}", e))) - } else { - let data = Hash256::deserialize(deserializer)?; - Ok(FileContractID(data)) - } - } -} - -impl FileContractID { - pub fn parse_string(s: &str) -> Result { - let s = match s.split_once(':') { - Some((_prefix, suffix)) => suffix, - None => s, - }; - - if s.len() != 64 { - return Err(HexParseError::InvalidLength); - } - - let mut data = [0u8; 32]; - hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(FileContractID(Hash256::new(data))) - } -} - -impl AsRef<[u8]> for FileContractID { - fn as_ref(&self) -> &[u8] { - self.0.as_bytes() - } -} - -impl fmt::Display for FileContractID { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "fcid:{}", hex::encode(self.0)) - } -} +ImplHashID!(FileContractID, "fcid"); #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -370,31 +196,7 @@ pub struct TransactionSignature { pub signature: Signature, } -#[derive(Debug, Clone, PartialEq)] -pub struct TransactionID([u8; 32]); - -impl TransactionID { - pub fn parse_string(s: &str) -> Result { - let s = match s.split_once(':') { - Some((_prefix, suffix)) => suffix, - None => s, - }; - - if s.len() != 64 { - return Err(HexParseError::InvalidLength); - } - - let mut data = [0u8; 32]; - hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(TransactionID(data)) - } -} - -impl fmt::Display for TransactionID { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "txn:{}", hex::encode(self.0)) - } -} +ImplHashID!(TransactionID, "txn"); #[derive(Default, Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] @@ -659,7 +461,7 @@ impl Transaction { public_key_index, timelock, covered_fields: covered_fields.clone(), - signature: private_key.sign(sig_hash.as_bytes()), + signature: private_key.sign(sig_hash.as_ref()), }) } @@ -678,8 +480,8 @@ impl Transaction { state.update(Self::SIACOIN_OUTPUT_ID_PREFIX.as_bytes()); self.hash_no_sigs(&mut state); - let h: Hash256 = state.update(&i.to_le_bytes()).finalize().into(); - SiacoinOutputID(h) + let h = state.update(&i.to_le_bytes()).finalize(); + SiacoinOutputID::from(h) } pub fn siafund_output_id(&self, i: usize) -> SiafundOutputID { @@ -688,8 +490,8 @@ impl Transaction { state.update(Self::SIAFUND_OUTPUT_ID_PREFIX.as_bytes()); self.hash_no_sigs(&mut state); - let h: Hash256 = state.update(&i.to_le_bytes()).finalize().into(); - SiafundOutputID(h) + let h = state.update(&i.to_le_bytes()).finalize(); + SiafundOutputID::from(h) } } @@ -927,7 +729,7 @@ mod tests { fn test_serialize_filecontract() { let contract = FileContract { file_size: 1, - file_merkle_root: Hash256::new([ + file_merkle_root: Hash256::from([ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -948,7 +750,7 @@ mod tests { 0, 0, 0, 0, 0, 0, ]), }], - unlock_hash: Hash256::new([ + unlock_hash: Hash256::from([ 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -985,12 +787,12 @@ mod tests { #[test] fn test_serialize_filecontract_revision() { let revision = FileContractRevision { - parent_id: FileContractID(Hash256::new([ + parent_id: FileContractID::from([ 9, 8, 7, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, - ])), + ]), file_size: 1, - file_merkle_root: Hash256::new([ + file_merkle_root: Hash256::from([ 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -1022,7 +824,7 @@ mod tests { )], 1, ), - unlock_hash: Hash256::new([ + unlock_hash: Hash256::from([ 4, 4, 4, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ]), @@ -1108,7 +910,7 @@ mod tests { Transaction { siacoin_inputs: vec![ SiacoinInput{ - parent_id: SiacoinOutputID::from_bytes([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), + parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), } ], @@ -1135,7 +937,9 @@ mod tests { let sig_hash = txn .whole_sig_hash( &state, - &txn.siacoin_inputs[0].parent_id.into(), + &Hash256::from(>::into( + txn.siacoin_inputs[0].parent_id, + )), 0, 0, &vec![], @@ -1170,7 +974,7 @@ mod tests { Transaction { siacoin_inputs: vec![ SiacoinInput{ - parent_id: SiacoinOutputID::from_bytes([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), + parent_id: SiacoinOutputID::from([32,11,215,36,166,174,135,0,92,215,179,18,74,229,52,154,221,194,213,216,219,47,225,205,251,84,248,2,69,252,37,117]), unlock_conditions: UnlockConditions::standard_unlock_conditions(pk.public_key()), } ], @@ -1198,7 +1002,9 @@ mod tests { .sign( &state, &CoveredFields::whole_transaction(), - txn.siacoin_inputs[0].parent_id.into(), + Hash256::from(>::into( + txn.siacoin_inputs[0].parent_id, + )), 0, 0, &pk, From 8bef221964ab0c3e0d58276d56edc4b71bfa4481 Mon Sep 17 00:00:00 2001 From: Nate Maninger Date: Mon, 22 Jul 2024 22:13:40 -0700 Subject: [PATCH 4/4] transaction: don't serialize payout field --- src/transactions.rs | 76 ++------------------------------------------- 1 file changed, 2 insertions(+), 74 deletions(-) diff --git a/src/transactions.rs b/src/transactions.rs index ad0fe45..742c196 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -61,7 +61,7 @@ pub struct FileContract { pub revision_number: u64, } -#[derive(Debug, Clone, PartialEq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct FileContractRevision { #[serde(rename = "parentID")] @@ -78,78 +78,6 @@ pub struct FileContractRevision { pub unlock_hash: Hash256, } -impl Serialize for FileContractRevision { - fn serialize(&self, serializer: S) -> Result { - if serializer.is_human_readable() { - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - struct WithPayout<'a> { - #[serde(rename = "parentID")] - parent_id: &'a FileContractID, - unlock_conditions: &'a UnlockConditions, - revision_number: u64, - #[serde(rename = "filesize")] - file_size: u64, - file_merkle_root: &'a Hash256, - window_start: u64, - window_end: u64, - payout: &'a Currency, - valid_proof_outputs: &'a Vec, - missed_proof_outputs: &'a Vec, - unlock_hash: &'a Hash256, - } - ::serialize( - &WithPayout { - parent_id: &self.parent_id, - unlock_conditions: &self.unlock_conditions, - revision_number: self.revision_number, - file_size: self.file_size, - file_merkle_root: &self.file_merkle_root, - window_start: self.window_start, - window_end: self.window_end, - payout: &self.valid_proof_outputs.iter().map(|o| o.value).sum(), - valid_proof_outputs: &self.valid_proof_outputs, - missed_proof_outputs: &self.missed_proof_outputs, - unlock_hash: &self.unlock_hash, - }, - serializer, - ) - } else { - #[derive(Serialize)] - #[serde(rename_all = "camelCase")] - struct WithoutPayout<'a> { - #[serde(rename = "parentID")] - parent_id: &'a FileContractID, - unlock_conditions: &'a UnlockConditions, - revision_number: u64, - #[serde(rename = "filesize")] - file_size: u64, - file_merkle_root: &'a Hash256, - window_start: u64, - window_end: u64, - valid_proof_outputs: &'a Vec, - missed_proof_outputs: &'a Vec, - unlock_hash: &'a Hash256, - } - ::serialize( - &WithoutPayout { - parent_id: &self.parent_id, - unlock_conditions: &self.unlock_conditions, - revision_number: self.revision_number, - file_size: self.file_size, - file_merkle_root: &self.file_merkle_root, - window_start: self.window_start, - window_end: self.window_end, - valid_proof_outputs: &self.valid_proof_outputs, - missed_proof_outputs: &self.missed_proof_outputs, - unlock_hash: &self.unlock_hash, - }, - serializer, - ) - } - } -} - #[derive(Debug, Clone, Serialize)] #[serde(rename_all = "camelCase")] pub struct StorageProof { @@ -858,7 +786,7 @@ mod tests { let revision_serialized = serde_json::to_string(&revision).unwrap(); let revision_deserialized: FileContractRevision = serde_json::from_str(&revision_serialized).unwrap(); - assert_eq!(revision_serialized, "{\"parentID\":\"fcid:0908070000000000000000000000000000000000000000000000000000000000\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1},\"revisionNumber\":4,\"filesize\":1,\"fileMerkleRoot\":\"h:0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"payout\":\"789\",\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"h:0404040000000000000000000000000000000000000000000000000000000000\"}"); + assert_eq!(revision_serialized, "{\"parentID\":\"fcid:0908070000000000000000000000000000000000000000000000000000000000\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1},\"revisionNumber\":4,\"filesize\":1,\"fileMerkleRoot\":\"h:0101010000000000000000000000000000000000000000000000000000000000\",\"windowStart\":2,\"windowEnd\":3,\"validProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:02020200000000000000000000000000000000000000000000000000000000008749787b31db\"}],\"missedProofOutputs\":[{\"value\":\"789\",\"address\":\"addr:0303030000000000000000000000000000000000000000000000000000000000c596d559a239\"}],\"unlockHash\":\"h:0404040000000000000000000000000000000000000000000000000000000000\"}"); assert_eq!(revision_deserialized, revision); }