Skip to main content

hns_primitives/
lib.rs

1#![doc = "Strongly typed, allocation-free Handshake protocol values."]
2
3use core::{cmp::Ordering, fmt};
4
5use thiserror::Error;
6
7macro_rules! semantic_bytes {
8    ($name:ident, $size:expr) => {
9        #[derive(Clone, Copy, Eq, Hash, Ord, PartialEq, PartialOrd)]
10        pub struct $name([u8; $size]);
11
12        impl Default for $name {
13            fn default() -> Self {
14                Self([0; $size])
15            }
16        }
17
18        impl $name {
19            pub const LENGTH: usize = $size;
20
21            pub const fn new(bytes: [u8; $size]) -> Self {
22                Self(bytes)
23            }
24
25            pub const fn into_bytes(self) -> [u8; $size] {
26                self.0
27            }
28
29            pub const fn as_bytes(&self) -> &[u8; $size] {
30                &self.0
31            }
32
33            pub fn from_hex(value: &str) -> Result<Self, HexValueError> {
34                let mut bytes = [0_u8; $size];
35                hex::decode_to_slice(value, &mut bytes).map_err(|_| HexValueError::InvalidHex {
36                    expected_bytes: $size,
37                })?;
38                Ok(Self(bytes))
39            }
40        }
41
42        impl fmt::Debug for $name {
43            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
44                write!(formatter, "{}({})", stringify!($name), hex::encode(self.0))
45            }
46        }
47
48        impl fmt::Display for $name {
49            fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
50                formatter.write_str(&hex::encode(self.0))
51            }
52        }
53
54        impl From<[u8; $size]> for $name {
55            fn from(bytes: [u8; $size]) -> Self {
56                Self(bytes)
57            }
58        }
59    };
60}
61
62macro_rules! semantic_integer {
63    ($name:ident, $inner:ty) => {
64        #[derive(Clone, Copy, Debug, Default, Eq, Hash, Ord, PartialEq, PartialOrd)]
65        pub struct $name($inner);
66
67        impl $name {
68            pub const fn new(value: $inner) -> Self {
69                Self(value)
70            }
71
72            pub const fn get(self) -> $inner {
73                self.0
74            }
75        }
76
77        impl From<$inner> for $name {
78            fn from(value: $inner) -> Self {
79                Self(value)
80            }
81        }
82    };
83}
84
85#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
86pub enum HexValueError {
87    #[error("invalid hexadecimal value; expected exactly {expected_bytes} bytes")]
88    InvalidHex { expected_bytes: usize },
89}
90
91semantic_bytes!(BlockHash, 32);
92semantic_bytes!(TransactionHash, 32);
93semantic_bytes!(NameHash, 32);
94semantic_bytes!(TreeRoot, 32);
95semantic_bytes!(MerkleRoot, 32);
96semantic_bytes!(WitnessRoot, 32);
97semantic_bytes!(ReservedRoot, 32);
98semantic_bytes!(PowMask, 32);
99semantic_bytes!(ShareHash, 32);
100semantic_bytes!(PowHash, 32);
101semantic_bytes!(ScriptHash, 32);
102semantic_bytes!(OfferId, 32);
103semantic_bytes!(PeerIdentity, 33);
104semantic_bytes!(RegistryFingerprint, 32);
105
106semantic_integer!(Height, u32);
107semantic_integer!(BlockTime, u64);
108semantic_integer!(Dollarydoos, u64);
109semantic_integer!(CompactTarget, u32);
110semantic_integer!(RequestId, u64);
111semantic_integer!(EventSequence, u64);
112semantic_integer!(PolicyGeneration, u64);
113
114impl RequestId {
115    pub const fn is_valid(self) -> bool {
116        self.0 != 0
117    }
118}
119
120#[derive(Clone, Copy, Debug, Eq, Error, PartialEq)]
121pub enum ArithmeticError {
122    #[error("numeric overflow")]
123    Overflow,
124    #[error("numeric underflow")]
125    Underflow,
126}
127
128impl Dollarydoos {
129    pub fn checked_add(self, other: Self) -> Result<Self, ArithmeticError> {
130        self.0
131            .checked_add(other.0)
132            .map(Self)
133            .ok_or(ArithmeticError::Overflow)
134    }
135
136    pub fn checked_sub(self, other: Self) -> Result<Self, ArithmeticError> {
137        self.0
138            .checked_sub(other.0)
139            .map(Self)
140            .ok_or(ArithmeticError::Underflow)
141    }
142}
143
144/// Unsigned 256-bit chainwork in little-endian 64-bit limbs.
145#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
146pub struct Chainwork([u64; 4]);
147
148impl Chainwork {
149    pub const ZERO: Self = Self([0; 4]);
150
151    pub const fn from_limbs_le(limbs: [u64; 4]) -> Self {
152        Self(limbs)
153    }
154
155    pub const fn limbs_le(self) -> [u64; 4] {
156        self.0
157    }
158
159    pub fn from_be_bytes(bytes: [u8; 32]) -> Self {
160        let mut limbs = [0_u64; 4];
161        for (index, limb) in limbs.iter_mut().rev().enumerate() {
162            let start = index * 8;
163            *limb = u64::from_be_bytes(
164                bytes[start..start + 8]
165                    .try_into()
166                    .expect("eight-byte chunk"),
167            );
168        }
169        Self(limbs)
170    }
171
172    pub fn to_be_bytes(self) -> [u8; 32] {
173        let mut bytes = [0_u8; 32];
174        for (index, limb) in self.0.iter().rev().enumerate() {
175            let start = index * 8;
176            bytes[start..start + 8].copy_from_slice(&limb.to_be_bytes());
177        }
178        bytes
179    }
180
181    pub fn checked_add(self, other: Self) -> Result<Self, ArithmeticError> {
182        let mut output = [0_u64; 4];
183        let mut carry = false;
184        for (index, output_limb) in output.iter_mut().enumerate() {
185            let (sum, first_carry) = self.0[index].overflowing_add(other.0[index]);
186            let (sum, second_carry) = sum.overflowing_add(u64::from(carry));
187            *output_limb = sum;
188            carry = first_carry || second_carry;
189        }
190        if carry {
191            Err(ArithmeticError::Overflow)
192        } else {
193            Ok(Self(output))
194        }
195    }
196
197    pub fn checked_sub(self, other: Self) -> Result<Self, ArithmeticError> {
198        if self < other {
199            return Err(ArithmeticError::Underflow);
200        }
201        let mut output = [0_u64; 4];
202        let mut borrow = false;
203        for (index, output_limb) in output.iter_mut().enumerate() {
204            let (difference, first_borrow) = self.0[index].overflowing_sub(other.0[index]);
205            let (difference, second_borrow) = difference.overflowing_sub(u64::from(borrow));
206            *output_limb = difference;
207            borrow = first_borrow || second_borrow;
208        }
209        debug_assert!(!borrow);
210        Ok(Self(output))
211    }
212
213    pub fn checked_mul_u64(self, multiplier: u64) -> Result<Self, ArithmeticError> {
214        let mut output = [0_u64; 4];
215        let mut carry = 0_u128;
216        for (index, output_limb) in output.iter_mut().enumerate() {
217            let product = u128::from(self.0[index]) * u128::from(multiplier) + carry;
218            *output_limb = product as u64;
219            carry = product >> 64;
220        }
221        if carry == 0 {
222            Ok(Self(output))
223        } else {
224            Err(ArithmeticError::Overflow)
225        }
226    }
227
228    pub fn checked_div_u64(self, divisor: u64) -> Result<Self, ArithmeticError> {
229        if divisor == 0 {
230            return Err(ArithmeticError::Underflow);
231        }
232        let mut output = [0_u64; 4];
233        let mut remainder = 0_u128;
234        for index in (0..4).rev() {
235            let dividend = (remainder << 64) | u128::from(self.0[index]);
236            output[index] = (dividend / u128::from(divisor)) as u64;
237            remainder = dividend % u128::from(divisor);
238        }
239        Ok(Self(output))
240    }
241}
242
243impl Ord for Chainwork {
244    fn cmp(&self, other: &Self) -> Ordering {
245        for index in (0..4).rev() {
246            match self.0[index].cmp(&other.0[index]) {
247                Ordering::Equal => {}
248                ordering => return ordering,
249            }
250        }
251        Ordering::Equal
252    }
253}
254
255impl PartialOrd for Chainwork {
256    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
257        Some(self.cmp(other))
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use super::*;
264
265    #[test]
266    fn semantic_hashes_do_not_interchange() {
267        let block = BlockHash::new([7; 32]);
268        let transaction = TransactionHash::new([7; 32]);
269        assert_eq!(block.as_bytes(), transaction.as_bytes());
270        assert_eq!(block.to_string().len(), 64);
271    }
272
273    #[test]
274    fn chainwork_addition_carries_and_detects_overflow() {
275        let left = Chainwork::from_limbs_le([u64::MAX, 1, 0, 0]);
276        let right = Chainwork::from_limbs_le([1, 2, 0, 0]);
277        assert_eq!(
278            left.checked_add(right).expect("fits").limbs_le(),
279            [0, 4, 0, 0]
280        );
281        assert_eq!(
282            Chainwork::from_limbs_le([u64::MAX; 4]).checked_add(right),
283            Err(ArithmeticError::Overflow)
284        );
285    }
286
287    #[test]
288    fn chainwork_numeric_order_and_checked_operations_are_256_bit() {
289        let lower = Chainwork::from_limbs_le([u64::MAX, 0, 0, 0]);
290        let higher = Chainwork::from_limbs_le([0, 1, 0, 0]);
291        assert!(higher > lower);
292        assert_eq!(
293            higher.checked_sub(lower).expect("fits").limbs_le(),
294            [1, 0, 0, 0]
295        );
296        assert_eq!(
297            Chainwork::from_limbs_le([u64::MAX, 0, 0, 0])
298                .checked_mul_u64(2)
299                .expect("fits")
300                .limbs_le(),
301            [u64::MAX - 1, 1, 0, 0]
302        );
303        assert_eq!(
304            Chainwork::from_limbs_le([0, 1, 0, 0])
305                .checked_div_u64(2)
306                .expect("fits")
307                .limbs_le(),
308            [1_u64 << 63, 0, 0, 0]
309        );
310        let bytes = [0x5a; 32];
311        assert_eq!(Chainwork::from_be_bytes(bytes).to_be_bytes(), bytes);
312    }
313
314    #[test]
315    fn amounts_never_use_floating_point() {
316        assert_eq!(
317            Dollarydoos::new(2)
318                .checked_add(Dollarydoos::new(3))
319                .expect("fits")
320                .get(),
321            5
322        );
323        assert_eq!(
324            Dollarydoos::new(0).checked_sub(Dollarydoos::new(1)),
325            Err(ArithmeticError::Underflow)
326        );
327    }
328}