diff --git a/src/common.rs b/src/common.rs index 78368c2..09051a4 100644 --- a/src/common.rs +++ b/src/common.rs @@ -31,9 +31,9 @@ pub struct Hash256([u8; 32]); impl Serialize for Hash256 { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) + String::serialize(&self.to_string(), serializer) } else { - self.0.serialize(serializer) + <[u8; 32]>::serialize(&self.0, serializer) } } } diff --git a/src/currency.rs b/src/currency.rs index a7dbd58..9ea89f0 100644 --- a/src/currency.rs +++ b/src/currency.rs @@ -17,7 +17,7 @@ pub struct Currency(u128); impl Serialize for Currency { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) + String::serialize(&self.to_string(), serializer) } else { let currency_buf = self.to_be_bytes(); let i = currency_buf @@ -41,7 +41,7 @@ impl<'de> Deserialize<'de> for Currency { return Err(serde::de::Error::custom("invalid currency length")); } let mut buf = [0; 16]; - buf[..data.len()].copy_from_slice(&data); + buf[16 - data.len()..].copy_from_slice(&data); Ok(Currency(u128::from_be_bytes(buf))) } } diff --git a/src/signing.rs b/src/signing.rs index 6f3f382..f1444cf 100644 --- a/src/signing.rs +++ b/src/signing.rs @@ -3,18 +3,45 @@ use std::time::SystemTime; use crate::{ChainIndex, Hash256, HexParseError}; use ed25519_dalek::{Signature as ED25519Signature, Signer, SigningKey, Verifier, VerifyingKey}; -use serde::Serialize; +use serde::{de::Error, Deserialize, Serialize}; /// An ed25519 public key that can be used to verify a signature #[derive(Debug, PartialEq, Clone, Copy)] pub struct PublicKey([u8; 32]); +impl PublicKey { + const PREFIX: &'static str = "ed25519:"; +} + impl Serialize for PublicKey { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) + String::serialize( + &format!("{}{}", Self::PREFIX, &self.to_string()), + serializer, + ) } else { - serializer.serialize_bytes(&self.0) + <[u8; 32]>::serialize(&self.0, serializer) + } + } +} + +impl<'de> Deserialize<'de> for PublicKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + let s = s.strip_prefix(Self::PREFIX).ok_or(Error::custom(format!( + "key must have prefix '{}'", + Self::PREFIX + )))?; + let mut pk = [0; 32]; + hex::decode_to_slice(s, &mut pk).map_err(|e| Error::custom(format!("{:?}", e)))?; + Ok(Self::new(pk)) + } else { + Ok(PublicKey(<[u8; 32]>::deserialize(deserializer)?)) } } } @@ -90,9 +117,9 @@ pub struct Signature([u8; 64]); impl Serialize for Signature { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) + String::serialize(&self.to_string(), serializer) } else { - serializer.serialize_bytes(&self.0) // prefixed with length + <[u8]>::serialize(&self.0, serializer) // prefixed with length } } } @@ -183,19 +210,31 @@ impl SigningState { #[cfg(test)] mod tests { + use crate::encoding::{from_reader, to_bytes}; + use super::*; #[test] - fn test_json_serialize_public_key() { + fn test_serialize_publickey() { + let public_key_str = "9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6"; + let public_key = PublicKey::new(hex::decode(public_key_str).unwrap().try_into().unwrap()); + + // binary + let public_key_serialized = to_bytes(&public_key).unwrap(); + let public_key_deserialized: PublicKey = + from_reader(&mut &public_key_serialized[..]).unwrap(); + assert_eq!(public_key_serialized, hex::decode(public_key_str).unwrap()); + assert_eq!(public_key_deserialized, public_key); + + // json + let public_key_serialized = serde_json::to_string(&public_key).unwrap(); + let public_key_deserialized: PublicKey = + serde_json::from_str(&public_key_serialized).unwrap(); assert_eq!( - serde_json::to_string(&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, - ])) - .unwrap(), - "\"9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"" + public_key_serialized, + format!("\"ed25519:{0}\"", public_key_str) ); + assert_eq!(public_key_deserialized, public_key); } #[test] diff --git a/src/specifier.rs b/src/specifier.rs index 53d0ed5..0776124 100644 --- a/src/specifier.rs +++ b/src/specifier.rs @@ -1,10 +1,10 @@ use core::{fmt, str}; -use serde::Serialize; +use serde::{Deserialize, Serialize}; pub const SPECIFIER_SIZE: usize = 16; -#[derive(Debug, PartialEq, Serialize)] +#[derive(Debug, PartialEq, Serialize, Deserialize)] pub struct Specifier([u8; SPECIFIER_SIZE]); impl Specifier { diff --git a/src/spendpolicy.rs b/src/spendpolicy.rs index 601982c..aaa7ade 100644 --- a/src/spendpolicy.rs +++ b/src/spendpolicy.rs @@ -208,11 +208,11 @@ impl SpendPolicy { SpendPolicy::UnlockConditions(uc) => { if uc.timelock > signing_state.index.height { return Err(ValidationError::InvalidHeight); - } else if uc.required_signatures > 255 { + } else if uc.signatures_required > 255 { return Err(ValidationError::InvalidPolicy); } - let mut remaining = uc.required_signatures; + 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) { @@ -268,7 +268,7 @@ impl SpendPolicy { impl Serialize for SpendPolicy { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) + String::serialize(&self.to_string(), serializer) } else { // unknown length since policie are recursive and need custom // serialize/deserialize implementations anyway. @@ -305,7 +305,7 @@ impl fmt::Display for SpendPolicy { SpendPolicy::Opaque(addr) => write!(f, "opaque(0x{})", hex::encode(addr)), #[allow(deprecated)] SpendPolicy::UnlockConditions(uc) => { - write!(f, "uc({},{},[", uc.timelock, uc.required_signatures)?; + write!(f, "uc({},{},[", uc.timelock, uc.signatures_required)?; for (i, pk) in uc.public_keys.iter().enumerate() { if i > 0 { write!(f, ",")?; diff --git a/src/transactions.rs b/src/transactions.rs index 704ec82..b283190 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -6,11 +6,37 @@ use crate::{Address, Currency}; use crate::{Hash256, HexParseError}; use blake2b_simd::{Params, State}; use core::fmt; -use serde::Serialize; +use serde::{Deserialize, Serialize}; -#[derive(Debug, Copy, Clone, Serialize)] +#[derive(Debug, Copy, Clone, PartialEq)] pub struct SiacoinOutputID(Hash256); +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) @@ -48,23 +74,52 @@ impl fmt::Display for SiacoinOutputID { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SiacoinInput { + #[serde(rename = "parentID")] pub parent_id: SiacoinOutputID, pub unlock_conditions: UnlockConditions, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SiacoinOutput { pub value: Currency, pub address: Address, } -#[derive(Debug, Clone, Serialize)] -pub struct SiafundOutputID([u8; 32]); +#[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) -> [u8; 32] { + pub fn as_bytes(&self) -> Hash256 { self.0 } @@ -80,7 +135,7 @@ impl SiafundOutputID { let mut data = [0u8; 32]; hex::decode_to_slice(s, &mut data).map_err(HexParseError::HexError)?; - Ok(SiafundOutputID(data)) + Ok(SiafundOutputID(Hash256::new(data))) } } @@ -90,14 +145,17 @@ impl fmt::Display for SiafundOutputID { } } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SiafundInput { + #[serde(rename = "parentID")] pub parent_id: SiafundOutputID, pub unlock_conditions: UnlockConditions, pub claim_address: Address, } -#[derive(Debug, Clone, Serialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct SiafundOutput { pub value: Currency, pub address: Address, @@ -141,6 +199,7 @@ impl fmt::Display for FileContractID { } #[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] pub struct FileContract { pub file_size: u64, pub file_merkle_root: Hash256, @@ -154,7 +213,9 @@ pub struct FileContract { } #[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] pub struct FileContractRevision { + #[serde(rename = "parentID")] pub parent_id: FileContractID, pub unlock_conditions: UnlockConditions, pub revision_number: u64, @@ -168,7 +229,9 @@ pub struct FileContractRevision { } #[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] pub struct StorageProof { + #[serde(rename = "parentID")] pub parent_id: FileContractID, #[serde(serialize_with = "serialize_array")] pub leaf: [u8; 64], @@ -238,6 +301,7 @@ impl fmt::Display for TransactionID { } #[derive(Default, Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] pub struct Transaction { pub siacoin_inputs: Vec, pub siacoin_outputs: Vec, @@ -528,21 +592,16 @@ impl Transaction { state.update(Self::SIAFUND_OUTPUT_ID_PREFIX.as_bytes()); self.hash_no_sigs(&mut state); - SiafundOutputID( - state - .update(&i.to_le_bytes()) - .finalize() - .as_bytes() - .try_into() - .unwrap(), - ) + let h: Hash256 = state.update(&i.to_le_bytes()).finalize().into(); + SiafundOutputID(h) } } #[cfg(test)] mod tests { - use crate::encoding::to_bytes; - use crate::signing::NetworkHardforks; + use crate::encoding::{from_reader, to_bytes}; + use crate::signing::{NetworkHardforks, PublicKey}; + use crate::unlock_conditions::{Algorithm, UnlockKey}; use crate::ChainIndex; use std::time::SystemTime; @@ -599,20 +658,173 @@ mod tests { } #[test] - fn test_siacoin_output() { + fn test_serialize_siacoin_input() { + let siacoin_input = SiacoinInput { + parent_id: SiacoinOutputID::parse_string( + "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24", + ) + .unwrap(), + 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, + ), + }; + + // binary + let siacoin_input_serialized = to_bytes(&siacoin_input).unwrap(); + let siacoin_input_deserialized: SiacoinInput = + from_reader(&mut &siacoin_input_serialized[..]).unwrap(); + assert_eq!( + siacoin_input_serialized, + [ + 179, 99, 58, 19, 112, 167, 32, 2, 174, 42, 149, 109, 33, 232, 212, 129, 195, 166, + 158, 20, 102, 51, 71, 12, 246, 37, 236, 216, 63, 222, 170, 36, 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 + ] + ); + assert_eq!(siacoin_input_deserialized, siacoin_input); + + // json + let siacoin_input_serialized = serde_json::to_string(&siacoin_input).unwrap(); + let siacoin_input_deserialized: SiacoinInput = + serde_json::from_str(&siacoin_input_serialized).unwrap(); + assert_eq!(siacoin_input_serialized, "{\"parentID\":\"scoid:b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1}}"); + assert_eq!(siacoin_input_deserialized, siacoin_input); + } + + #[test] + fn test_serialize_siafund_input() { + let siafund_input = SiafundInput { + parent_id: SiafundOutputID::parse_string( + "b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24", + ) + .unwrap(), + 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, + ), + claim_address: Address::new( + hex::decode("8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8c") + .unwrap() + .try_into() + .unwrap(), + ), + }; + + // binary + let siafund_input_serialized = to_bytes(&siafund_input).unwrap(); + let siafund_input_deserialized: SiafundInput = + from_reader(&mut &siafund_input_serialized[..]).unwrap(); + assert_eq!( + siafund_input_serialized, + [ + 179, 99, 58, 19, 112, 167, 32, 2, 174, 42, 149, 109, 33, 232, 212, 129, 195, 166, + 158, 20, 102, 51, 71, 12, 246, 37, 236, 216, 63, 222, 170, 36, 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, 143, 180, 156, 207, 23, 223, 220, 201, 82, + 109, 236, 110, 232, 165, 204, 162, 15, 248, 36, 115, 2, 5, 61, 55, 119, 65, 11, + 155, 4, 148, 186, 140 + ] + ); + assert_eq!(siafund_input_deserialized, siafund_input); + + // json + let siafund_input_serialized = serde_json::to_string(&siafund_input).unwrap(); + let siafund_input_deserialized: SiafundInput = + serde_json::from_str(&siafund_input_serialized).unwrap(); + assert_eq!(siafund_input_serialized, "{\"parentID\":\"sfoid:b3633a1370a72002ae2a956d21e8d481c3a69e146633470cf625ecd83fdeaa24\",\"unlockConditions\":{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1},\"claimAddress\":\"addr:8fb49ccf17dfdcc9526dec6ee8a5cca20ff8247302053d3777410b9b0494ba8cdf32abee86f0\"}"); + assert_eq!(siafund_input_deserialized, siafund_input); + } + + #[test] + fn test_serialize_siacoin_output() { + let addr_str = + "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"; let output = SiacoinOutput { value: Currency::new(67856467336433871), - address: Address::parse_string( - "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69", + address: Address::parse_string(addr_str).unwrap(), + }; + + // binary + let output_serialized = to_bytes(&output).unwrap(); + let output_deserialized: SiacoinOutput = from_reader(&mut &output_serialized[..]).unwrap(); + assert_eq!( + output_serialized, + [ + 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, + ] + ); + assert_eq!(output_deserialized, output); + + // json + let output_serialized = serde_json::to_string(&output).unwrap(); + let output_deserialized: SiacoinOutput = serde_json::from_str(&output_serialized).unwrap(); + assert_eq!( + output_serialized, + format!( + "{{\"value\":\"67856467336433871\",\"address\":\"{}\"}}", + addr_str ) - .unwrap(), + ); + assert_eq!(output_deserialized, output); + } + + #[test] + fn test_serialize_siafund_output() { + let addr_str = + "addr:000000000000000000000000000000000000000000000000000000000000000089eb0d6a8a69"; + let output = SiafundOutput { + value: Currency::new(67856467336433871), + address: Address::parse_string(addr_str).unwrap(), + claim_start: Currency::new(123456789), }; - let result = to_bytes(&output).expect("failed to serialize output"); - let expected: [u8; 47] = [ - 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, - ]; - assert_eq!(result, expected); + + // binary + let output_serialized = to_bytes(&output).unwrap(); + let output_deserialized: SiafundOutput = from_reader(&mut &output_serialized[..]).unwrap(); + assert_eq!( + output_serialized, + [ + 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, 4, 0, 0, 0, 0, + 0, 0, 0, 7, 91, 205, 21 + ] + ); + assert_eq!(output_deserialized, output); + + // json + let output_serialized = serde_json::to_string(&output).unwrap(); + let output_deserialized: SiafundOutput = serde_json::from_str(&output_serialized).unwrap(); + assert_eq!( + output_serialized, + format!( + "{{\"value\":\"67856467336433871\",\"address\":\"{}\",\"claimStart\":\"123456789\"}}", + addr_str + ) + ); + assert_eq!(output_deserialized, output); } #[test] diff --git a/src/unlock_conditions.rs b/src/unlock_conditions.rs index 09f0f04..193bc09 100644 --- a/src/unlock_conditions.rs +++ b/src/unlock_conditions.rs @@ -7,8 +7,8 @@ use crate::HexParseError; use blake2b_simd::Params; #[deprecated] use core::fmt; -use serde::ser::SerializeStruct; -use serde::Serialize; +use serde::de::Error; +use serde::{Deserialize, Serialize}; /// A generic public key that can be used to spend a utxo or revise a file /// contract @@ -23,12 +23,31 @@ pub struct UnlockKey { impl Serialize for UnlockKey { fn serialize(&self, serializer: S) -> Result { if serializer.is_human_readable() { - serializer.serialize_str(&self.to_string()) + String::serialize(&self.to_string(), serializer) } else { - let mut s = serializer.serialize_struct("UnlockKey", 2)?; - s.serialize_field("algorithm", &self.algorithm)?; - s.serialize_field("public_key", &self.public_key)?; - s.end() + <(Algorithm, &[u8])>::serialize(&(self.algorithm, self.public_key.as_ref()), serializer) + } + } +} + +impl<'de> Deserialize<'de> for UnlockKey { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + UnlockKey::parse_string(&s).map_err(|e| Error::custom(format!("{:?}", e))) + } else { + let (algorithm, raw_key) = <(Algorithm, Vec)>::deserialize(deserializer)?; + Ok(Self { + algorithm, + public_key: PublicKey::new( + raw_key + .try_into() + .map_err(|e| Error::custom(format!("Invalid key: {:?}", e)))?, + ), + }) } } } @@ -69,15 +88,35 @@ impl Serialize for Algorithm { S: serde::Serializer, { let spec: Specifier = self.as_specifier(); - if serializer.is_human_readable() { - serializer.serialize_str(&spec.to_string()) + String::serialize(&self.to_string(), serializer) } else { spec.serialize(serializer) } } } +impl<'de> Deserialize<'de> for Algorithm { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + if deserializer.is_human_readable() { + let s = String::deserialize(deserializer)?; + match s.as_str() { + "ed25519" => Ok(Algorithm::ED25519), + _ => Err(Error::custom("Invalid algorithm")), + } + } else { + let spec = Specifier::deserialize(deserializer)?; + match spec { + Self::ED25519_SPECIFIER => Ok(Algorithm::ED25519), + _ => Err(Error::custom("Invalid algorithm")), + } + } + } +} + impl UnlockKey { /// Creates a new UnlockKey pub fn new(algorithm: Algorithm, public_key: PublicKey) -> UnlockKey { @@ -117,12 +156,12 @@ impl From for UnlockKey { } // specifies the conditions for spending an output or revising a file contract. -#[derive(Debug, PartialEq, Clone, Serialize)] +#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct UnlockConditions { pub timelock: u64, pub public_keys: Vec, - pub required_signatures: u64, + pub signatures_required: u64, } impl UnlockConditions { @@ -134,7 +173,7 @@ impl UnlockConditions { UnlockConditions { timelock, public_keys, - required_signatures, + signatures_required: required_signatures, } } @@ -142,7 +181,7 @@ impl UnlockConditions { UnlockConditions { timelock: 0, public_keys: vec![UnlockKey::new(Algorithm::ED25519, public_key)], - required_signatures: 1, + signatures_required: 1, } } @@ -175,7 +214,7 @@ impl UnlockConditions { .hash_length(32) .to_state() .update(LEAF_HASH_PREFIX) - .update(&self.required_signatures.to_le_bytes()) + .update(&self.signatures_required.to_le_bytes()) .finalize(); let mut leaf = [0u8; 32]; @@ -189,7 +228,7 @@ impl UnlockConditions { #[cfg(test)] mod tests { use super::*; - use crate::encoding::to_bytes; + use crate::encoding::{from_reader, to_bytes}; use crate::seed::Seed; #[test] @@ -212,57 +251,44 @@ mod tests { #[test] fn test_serialize_unlock_key() { - let key: [u8; 32] = [ - 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, - ]; - let pk = UnlockKey::new(Algorithm::ED25519, PublicKey::new(key)); + let unlock_key = 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, + ]), + ); + + // binary + let unlock_key_serialized = to_bytes(&unlock_key).unwrap(); + let unlock_key_deserialized: UnlockKey = + from_reader(&mut &unlock_key_serialized[..]).unwrap(); assert_eq!( - &to_bytes(&pk).unwrap(), - &[ + unlock_key_serialized, + [ 0x65, 0x64, 0x32, 0x35, 0x35, 0x31, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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 ] ); - } + assert_eq!(unlock_key_deserialized, unlock_key); - #[test] - fn test_json_serialize_unlock_key() { + // json + let unlock_key_serialized = serde_json::to_string(&unlock_key).unwrap(); + let unlock_key_deserialized: UnlockKey = + serde_json::from_str(&unlock_key_serialized).unwrap(); assert_eq!( - serde_json::to_string( - &UnlockKey::parse_string( - "ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6" - ) - .unwrap() - ) - .unwrap(), + unlock_key_serialized, "\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"" ); - } - - #[test] - fn test_json_serialize_unlock_conditions() { - let uc = 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, - ); - assert_eq!(serde_json::to_string(&uc).unwrap(), "{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"requiredSignatures\":1}") + assert_eq!(unlock_key_deserialized, unlock_key); } #[test] fn test_serialize_unlock_conditions() { - let uc = UnlockConditions::new( + let unlock_conditions = UnlockConditions::new( 123, vec![UnlockKey::new( Algorithm::ED25519, @@ -274,15 +300,28 @@ mod tests { )], 1, ); + + // binary + let unlock_conditions_serialized = to_bytes(&unlock_conditions).unwrap(); + let unlock_conditions_deserialized: UnlockConditions = + from_reader(&mut &unlock_conditions_serialized[..]).unwrap(); assert_eq!( - to_bytes(&uc).unwrap(), + unlock_conditions_serialized, [ 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 ] - ) + ); + assert_eq!(unlock_conditions_deserialized, unlock_conditions); + + // json + let unlock_conditions_serialized = serde_json::to_string(&unlock_conditions).unwrap(); + let unlock_conditions_deserialized: UnlockConditions = + serde_json::from_str(&unlock_conditions_serialized).unwrap(); + assert_eq!(unlock_conditions_serialized, "{\"timelock\":123,\"publicKeys\":[\"ed25519:9aac1ffb1cfd1079a8c6c87b47da1d567e35b97234993c288c1ad0db1d1ce1b6\"],\"signaturesRequired\":1}"); + assert_eq!(unlock_conditions_deserialized, unlock_conditions); } #[test]