hns-primitives 0.1.0

Strongly typed primitive values shared by Rust Handshake components
Documentation
#![doc = "Strongly typed, allocation-free Handshake protocol values."]

use core::{cmp::Ordering, fmt};

use thiserror::Error;

macro_rules! semantic_bytes {
    ($name:ident, $size:expr) => {
        #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
        pub struct $name([u8; $size]);

        impl Default for $name {
            fn default() -> Self {
                Self([0; $size])
            }
        }

        impl $name {
            pub const LENGTH: usize = $size;

            pub const fn new(bytes: [u8; $size]) -> Self {
                Self(bytes)
            }

            pub const fn into_bytes(self) -> [u8; $size] {
                self.0
            }

            pub const fn as_bytes(&self) -> &[u8; $size] {
                &self.0
            }

            pub fn from_hex(value: &str) -> Result<Self, HexValueError> {
                let mut bytes = [0_u8; $size];
                hex::decode_to_slice(value, &mut bytes).map_err(|_| HexValueError::InvalidHex {
                    expected_bytes: $size,
                })?;
                Ok(Self(bytes))
            }
        }

        impl fmt::Debug for $name {
            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                write!(formatter, "{}({})", stringify!($name), hex::encode(self.0))
            }
        }

        impl fmt::Display for $name {
            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
                formatter.write_str(&hex::encode(self.0))
            }
        }

        impl From<[u8; $size]> for $name {
            fn from(bytes: [u8; $size]) -> Self {
                Self(bytes)
            }
        }
    };
}

macro_rules! semantic_integer {
    ($name:ident, $inner:ty) => {
        #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
        pub struct $name($inner);

        impl $name {
            pub const fn new(value: $inner) -> Self {
                Self(value)
            }

            pub const fn get(self) -> $inner {
                self.0
            }
        }

        impl From<$inner> for $name {
            fn from(value: $inner) -> Self {
                Self(value)
            }
        }
    };
}

#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum HexValueError {
    #[error("invalid hexadecimal value; expected exactly {expected_bytes} bytes")]
    InvalidHex { expected_bytes: usize },
}

semantic_bytes!(BlockHash, 32);
semantic_bytes!(TransactionHash, 32);
semantic_bytes!(NameHash, 32);
semantic_bytes!(TreeRoot, 32);
semantic_bytes!(MerkleRoot, 32);
semantic_bytes!(WitnessRoot, 32);
semantic_bytes!(ReservedRoot, 32);
semantic_bytes!(PowMask, 32);
semantic_bytes!(ShareHash, 32);
semantic_bytes!(PowHash, 32);
semantic_bytes!(ScriptHash, 32);
semantic_bytes!(OfferId, 32);
semantic_bytes!(PeerIdentity, 33);
semantic_bytes!(RegistryFingerprint, 32);

semantic_integer!(Height, u32);
semantic_integer!(BlockTime, u64);
semantic_integer!(Dollarydoos, u64);
semantic_integer!(CompactTarget, u32);
semantic_integer!(RequestId, u64);
semantic_integer!(EventSequence, u64);
semantic_integer!(PolicyGeneration, u64);

impl RequestId {
    pub const fn is_valid(self) -> bool {
        self.0 != 0
    }
}

#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
pub enum ArithmeticError {
    #[error("numeric overflow")]
    Overflow,
    #[error("numeric underflow")]
    Underflow,
}

impl Dollarydoos {
    pub fn checked_add(self, other: Self) -> Result<Self, ArithmeticError> {
        self.0
            .checked_add(other.0)
            .map(Self)
            .ok_or(ArithmeticError::Overflow)
    }

    pub fn checked_sub(self, other: Self) -> Result<Self, ArithmeticError> {
        self.0
            .checked_sub(other.0)
            .map(Self)
            .ok_or(ArithmeticError::Underflow)
    }
}

/// Unsigned 256-bit chainwork in little-endian 64-bit limbs.
#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Chainwork([u64; 4]);

impl Chainwork {
    pub const ZERO: Self = Self([0; 4]);

    pub const fn from_limbs_le(limbs: [u64; 4]) -> Self {
        Self(limbs)
    }

    pub const fn limbs_le(self) -> [u64; 4] {
        self.0
    }

    pub fn from_be_bytes(bytes: [u8; 32]) -> Self {
        let mut limbs = [0_u64; 4];
        for (index, limb) in limbs.iter_mut().rev().enumerate() {
            let start = index * 8;
            *limb = u64::from_be_bytes(
                bytes[start..start + 8]
                    .try_into()
                    .expect("eight-byte chunk"),
            );
        }
        Self(limbs)
    }

    pub fn to_be_bytes(self) -> [u8; 32] {
        let mut bytes = [0_u8; 32];
        for (index, limb) in self.0.iter().rev().enumerate() {
            let start = index * 8;
            bytes[start..start + 8].copy_from_slice(&limb.to_be_bytes());
        }
        bytes
    }

    pub fn checked_add(self, other: Self) -> Result<Self, ArithmeticError> {
        let mut output = [0_u64; 4];
        let mut carry = false;
        for (index, output_limb) in output.iter_mut().enumerate() {
            let (sum, first_carry) = self.0[index].overflowing_add(other.0[index]);
            let (sum, second_carry) = sum.overflowing_add(u64::from(carry));
            *output_limb = sum;
            carry = first_carry || second_carry;
        }
        if carry {
            Err(ArithmeticError::Overflow)
        } else {
            Ok(Self(output))
        }
    }

    pub fn checked_sub(self, other: Self) -> Result<Self, ArithmeticError> {
        if self < other {
            return Err(ArithmeticError::Underflow);
        }
        let mut output = [0_u64; 4];
        let mut borrow = false;
        for (index, output_limb) in output.iter_mut().enumerate() {
            let (difference, first_borrow) = self.0[index].overflowing_sub(other.0[index]);
            let (difference, second_borrow) = difference.overflowing_sub(u64::from(borrow));
            *output_limb = difference;
            borrow = first_borrow || second_borrow;
        }
        debug_assert!(!borrow);
        Ok(Self(output))
    }

    pub fn checked_mul_u64(self, multiplier: u64) -> Result<Self, ArithmeticError> {
        let mut output = [0_u64; 4];
        let mut carry = 0_u128;
        for (index, output_limb) in output.iter_mut().enumerate() {
            let product = u128::from(self.0[index]) * u128::from(multiplier) + carry;
            *output_limb = product as u64;
            carry = product >> 64;
        }
        if carry == 0 {
            Ok(Self(output))
        } else {
            Err(ArithmeticError::Overflow)
        }
    }

    pub fn checked_div_u64(self, divisor: u64) -> Result<Self, ArithmeticError> {
        if divisor == 0 {
            return Err(ArithmeticError::Underflow);
        }
        let mut output = [0_u64; 4];
        let mut remainder = 0_u128;
        for index in (0..4).rev() {
            let dividend = (remainder << 64) | u128::from(self.0[index]);
            output[index] = (dividend / u128::from(divisor)) as u64;
            remainder = dividend % u128::from(divisor);
        }
        Ok(Self(output))
    }
}

impl Ord for Chainwork {
    fn cmp(&self, other: &Self) -> Ordering {
        for index in (0..4).rev() {
            match self.0[index].cmp(&other.0[index]) {
                Ordering::Equal => {}
                ordering => return ordering,
            }
        }
        Ordering::Equal
    }
}

impl PartialOrd for Chainwork {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn semantic_hashes_do_not_interchange() {
        let block = BlockHash::new([7; 32]);
        let transaction = TransactionHash::new([7; 32]);
        assert_eq!(block.as_bytes(), transaction.as_bytes());
        assert_eq!(block.to_string().len(), 64);
    }

    #[test]
    fn chainwork_addition_carries_and_detects_overflow() {
        let left = Chainwork::from_limbs_le([u64::MAX, 1, 0, 0]);
        let right = Chainwork::from_limbs_le([1, 2, 0, 0]);
        assert_eq!(
            left.checked_add(right).expect("fits").limbs_le(),
            [0, 4, 0, 0]
        );
        assert_eq!(
            Chainwork::from_limbs_le([u64::MAX; 4]).checked_add(right),
            Err(ArithmeticError::Overflow)
        );
    }

    #[test]
    fn chainwork_numeric_order_and_checked_operations_are_256_bit() {
        let lower = Chainwork::from_limbs_le([u64::MAX, 0, 0, 0]);
        let higher = Chainwork::from_limbs_le([0, 1, 0, 0]);
        assert!(higher > lower);
        assert_eq!(
            higher.checked_sub(lower).expect("fits").limbs_le(),
            [1, 0, 0, 0]
        );
        assert_eq!(
            Chainwork::from_limbs_le([u64::MAX, 0, 0, 0])
                .checked_mul_u64(2)
                .expect("fits")
                .limbs_le(),
            [u64::MAX - 1, 1, 0, 0]
        );
        assert_eq!(
            Chainwork::from_limbs_le([0, 1, 0, 0])
                .checked_div_u64(2)
                .expect("fits")
                .limbs_le(),
            [1_u64 << 63, 0, 0, 0]
        );
        let bytes = [0x5a; 32];
        assert_eq!(Chainwork::from_be_bytes(bytes).to_be_bytes(), bytes);
    }

    #[test]
    fn amounts_never_use_floating_point() {
        assert_eq!(
            Dollarydoos::new(2)
                .checked_add(Dollarydoos::new(3))
                .expect("fits")
                .get(),
            5
        );
        assert_eq!(
            Dollarydoos::new(0).checked_sub(Dollarydoos::new(1)),
            Err(ArithmeticError::Underflow)
        );
    }
}