//! Newtype wrappers for primitive data types with semantic meaning. use std::{ fmt, io::{self, Read}, }; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use chrono::{DateTime, TimeZone, Utc}; #[cfg(test)] use proptest_derive::Arbitrary; use crate::serialization::{ ReadZcashExt, SerializationError, WriteZcashExt, ZcashDeserialize, ZcashSerialize, }; pub mod amount; /// A u32 which represents a block height value. /// /// # Invariants /// /// Users should not construct block heights greater than or equal to `500_000_000`. #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord, Hash, Serialize, Deserialize)] pub struct BlockHeight(pub u32); #[cfg(test)] impl Arbitrary for BlockHeight { type Parameters = (); fn arbitrary_with(_args: ()) -> Self::Strategy { (0u32..500_000_000_u32).prop_map(BlockHeight).boxed() } type Strategy = BoxedStrategy; } /// A Bitcoin-style `locktime`, representing either a block height or an epoch /// time. /// /// # Invariants /// /// Users should not construct a `LockTime` with `BlockHeight` greater than or /// equal to `500_000_000` or a timestamp before 4 November 1985 (Unix timestamp /// less than `500_000_000`). #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum LockTime { /// Unlock at a particular block height. Height(BlockHeight), /// Unlock at a particular time. Time(DateTime), } impl ZcashSerialize for LockTime { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { // This implementation does not check the invariants on `LockTime` so that the // serialization is fallible only if the underlying writer is. This ensures that // we can always compute a hash of a transaction object. use LockTime::*; match self { Height(BlockHeight(n)) => writer.write_u32::(*n)?, Time(t) => writer.write_u32::(t.timestamp() as u32)?, } Ok(()) } } impl ZcashDeserialize for LockTime { fn zcash_deserialize(mut reader: R) -> Result { let n = reader.read_u32::()?; if n < 500_000_000 { Ok(LockTime::Height(BlockHeight(n))) } else { Ok(LockTime::Time(Utc.timestamp(n as i64, 0))) } } } #[cfg(test)] impl Arbitrary for LockTime { type Parameters = (); fn arbitrary_with(_args: ()) -> Self::Strategy { prop_oneof![ (0u32..500_000_000_u32).prop_map(|n| LockTime::Height(BlockHeight(n))), // XXX Setting max to i64::MAX doesn't work, this is 2**32. (500_000_000i64..4_294_967_296).prop_map(|n| { LockTime::Time(Utc.timestamp(n, 0)) }) ] .boxed() } type Strategy = BoxedStrategy; } /// An encoding of a Bitcoin script. #[derive(Clone, Eq, PartialEq, Serialize, Deserialize)] #[cfg_attr(test, derive(Arbitrary))] pub struct Script(pub Vec); impl fmt::Debug for Script { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Script") .field(&hex::encode(&self.0)) .finish() } } impl ZcashSerialize for Script { fn zcash_serialize(&self, mut writer: W) -> Result<(), io::Error> { writer.write_compactsize(self.0.len() as u64)?; writer.write_all(&self.0[..])?; Ok(()) } } impl ZcashDeserialize for Script { fn zcash_deserialize(mut reader: R) -> Result { // XXX what is the max length of a script? let len = reader.read_compactsize()?; let mut bytes = Vec::new(); reader.take(len).read_to_end(&mut bytes)?; Ok(Script(bytes)) } } /// A 4-byte checksum using truncated double-SHA256 (two rounds of SHA256). #[derive(Copy, Clone, Eq, PartialEq)] pub struct Sha256dChecksum(pub [u8; 4]); impl<'a> From<&'a [u8]> for Sha256dChecksum { fn from(bytes: &'a [u8]) -> Self { use sha2::{Digest, Sha256}; let hash1 = Sha256::digest(bytes); let hash2 = Sha256::digest(&hash1); let mut checksum = [0u8; 4]; checksum[0..4].copy_from_slice(&hash2[0..4]); Self(checksum) } } impl fmt::Debug for Sha256dChecksum { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.debug_tuple("Sha256dChecksum") .field(&hex::encode(&self.0)) .finish() } } #[cfg(test)] use proptest::prelude::*; #[cfg(test)] mod tests { use super::*; #[test] fn sha256d_checksum() { // https://en.bitcoin.it/wiki/Protocol_documentation#Hashes let input = b"hello"; let checksum = Sha256dChecksum::from(&input[..]); let expected = Sha256dChecksum([0x95, 0x95, 0xc9, 0xdf]); assert_eq!(checksum, expected); } #[test] fn sha256d_checksum_debug() { let input = b"hello"; let checksum = Sha256dChecksum::from(&input[..]); assert_eq!(format!("{:?}", checksum), "Sha256dChecksum(\"9595c9df\")"); } } #[cfg(test)] mod proptests { use std::io::Cursor; use proptest::prelude::*; use super::{LockTime, Script}; use crate::serialization::{ZcashDeserialize, ZcashSerialize}; proptest! { #[test] fn locktime_roundtrip(locktime in any::()) { let mut bytes = Cursor::new(Vec::new()); locktime.zcash_serialize(&mut bytes)?; bytes.set_position(0); let other_locktime = LockTime::zcash_deserialize(&mut bytes)?; prop_assert_eq![locktime, other_locktime]; } #[test] fn script_roundtrip(script in any::