diff --git a/Cargo.lock b/Cargo.lock index dc0bfde..3efd23a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2892,6 +2892,9 @@ dependencies = [ [[package]] name = "zingo_common_components" version = "0.3.0" +dependencies = [ + "hex", +] [[package]] name = "zip32" diff --git a/Cargo.toml b/Cargo.toml index 9cabebf..259815b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,4 +20,9 @@ thiserror = "1.0.64" x509-parser = "0.18" lightwallet-protocol = "0.3.0" tokio-stream = "0.1" -zcash_client_backend = { version = "0.22", features = ["lightwalletd-tonic"] } + +# encoding +hex = "0.4.3" + +# zcash +zcash_client_backend = { version = "0.22", features = ["lightwalletd-tonic"] } \ No newline at end of file diff --git a/zingo_common_components/Cargo.toml b/zingo_common_components/Cargo.toml index e26e4fc..4d1e1e3 100644 --- a/zingo_common_components/Cargo.toml +++ b/zingo_common_components/Cargo.toml @@ -13,3 +13,4 @@ license = "MIT" maintenance = { status = "actively-developed" } [dependencies] +hex.workspace = true diff --git a/zingo_common_components/src/protocol.rs b/zingo_common_components/src/protocol.rs index 5933e3c..d7730da 100644 --- a/zingo_common_components/src/protocol.rs +++ b/zingo_common_components/src/protocol.rs @@ -1,5 +1,193 @@ //! Module for types associated with the zcash protocol and consensus. +use core::cmp::Ordering; +use core::fmt; +use core::ops::{Add, Sub}; +use std::io::{self, Read, Write}; + +/// A wrapper type representing blockchain heights. +/// +/// Safe conversion from various integer types, as well as addition and subtraction, are +/// provided. +#[repr(transparent)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub struct BlockHeight(u32); + +/// The height of the genesis block on a network. +pub const H0: BlockHeight = BlockHeight(0); + +impl BlockHeight { + /// Constructs [`BlockHeight`] from `u32`. + pub const fn from_u32(v: u32) -> BlockHeight { + BlockHeight(v) + } + + /// Subtracts the provided value from this height, returning [`H0`] if this would result in + /// underflow of the wrapped `u32`. + pub fn saturating_sub(self, v: u32) -> BlockHeight { + BlockHeight(self.0.saturating_sub(v)) + } +} + +impl fmt::Display for BlockHeight { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(formatter) + } +} + +impl Ord for BlockHeight { + fn cmp(&self, other: &Self) -> Ordering { + self.0.cmp(&other.0) + } +} + +impl PartialOrd for BlockHeight { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl From for BlockHeight { + fn from(value: u32) -> Self { + BlockHeight(value) + } +} + +impl From for u32 { + fn from(value: BlockHeight) -> u32 { + value.0 + } +} + +impl TryFrom for BlockHeight { + type Error = core::num::TryFromIntError; + + fn try_from(value: u64) -> Result { + u32::try_from(value).map(BlockHeight) + } +} + +impl From for u64 { + fn from(value: BlockHeight) -> u64 { + value.0 as u64 + } +} + +impl TryFrom for BlockHeight { + type Error = core::num::TryFromIntError; + + fn try_from(value: i32) -> Result { + u32::try_from(value).map(BlockHeight) + } +} + +impl TryFrom for BlockHeight { + type Error = core::num::TryFromIntError; + + fn try_from(value: i64) -> Result { + u32::try_from(value).map(BlockHeight) + } +} + +impl From for i64 { + fn from(value: BlockHeight) -> i64 { + value.0 as i64 + } +} + +impl Add for BlockHeight { + type Output = Self; + + fn add(self, other: u32) -> Self { + BlockHeight(self.0.saturating_add(other)) + } +} + +impl Sub for BlockHeight { + type Output = Self; + + fn sub(self, other: u32) -> Self { + BlockHeight(self.0.saturating_sub(other)) + } +} + +impl Sub for BlockHeight { + type Output = u32; + + fn sub(self, other: BlockHeight) -> u32 { + self.0.saturating_sub(other.0) + } +} + +/// The identifier for a Zcash transaction. +/// +/// - For v1-4 transactions, this is a double-SHA-256 hash of the encoded transaction. +/// This means that it is malleable, and only a reliable identifier for transactions +/// that have been mined. +/// - For v5 transactions onwards, this identifier is derived only from "effecting" data, +/// and is non-malleable in all contexts. +#[derive(Clone, Copy, PartialOrd, Ord, PartialEq, Eq, Hash)] +pub struct TxId([u8; 32]); + +impl fmt::Debug for TxId { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + // The (byte-flipped) hex string is more useful than the raw bytes, because we can + // look that up in RPC methods and block explorers. + let txid_str = self.to_string(); + f.debug_tuple("TxId").field(&txid_str).finish() + } +} + +impl fmt::Display for TxId { + fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut data = self.0; + data.reverse(); + formatter.write_str(&hex::encode(data)) + } +} + +impl AsRef<[u8; 32]> for TxId { + fn as_ref(&self) -> &[u8; 32] { + &self.0 + } +} + +impl From for [u8; 32] { + fn from(value: TxId) -> Self { + value.0 + } +} + +impl TxId { + /// The all-zeros txid. This is reserved as the txid of the transparent input to a coinbase + /// transaction. + pub const NULL: TxId = TxId([0u8; 32]); + + /// Wraps the given byte array as a TxId value + pub const fn from_bytes(bytes: [u8; 32]) -> Self { + TxId(bytes) + } + + /// Reads a 32-byte txid directly from the provided reader. + pub fn read(mut reader: R) -> io::Result { + let mut hash = [0u8; 32]; + reader.read_exact(&mut hash)?; + Ok(TxId::from_bytes(hash)) + } + + /// Writes the 32-byte payload directly to the provided writer. + pub fn write(&self, mut writer: W) -> io::Result<()> { + writer.write_all(&self.0)?; + Ok(()) + } + + /// Returns true when the txid consists of all zeros, indicating the input + /// to a coinbase transaction. + pub fn is_null(&self) -> bool { + *self == Self::NULL + } +} + /// Network types. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum NetworkType {