lyquor_primitives/
id.rs

1use std::fmt;
2
3use crate::hex::{self, FromHex};
4use serde::{Deserialize, Serialize};
5use sha2::Digest;
6
7use super::Address;
8
9/// The ID of a node in the network.
10/// The ID is 35 bytes long, the first 32 bytes are the node's ed25519 public key,
11/// and the following 2 bytes are checksums, the last byte is the version number (0)
12#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
13pub struct NodeID(pub [u8; 32], pub [u8; 3]);
14
15impl NodeID {
16    pub fn new(id: [u8; 32]) -> Self {
17        let mut checksum = [0; 3];
18        // Calculate SHA-256 hash of the ID
19        let mut hash = sha2::Sha256::default();
20        hash.update(&id);
21        let hash = hash.finalize();
22        // Take the first two bytes of the hash as the checksum
23        checksum[0] = hash[0];
24        checksum[1] = hash[1];
25        checksum[2] = 0; // v0
26        Self(id, checksum)
27    }
28}
29
30impl NodeID {
31    const ALPHABET: base32::Alphabet = base32::Alphabet::Rfc4648Lower { padding: false };
32
33    pub fn as_u64(&self) -> u64 {
34        u64::from_be_bytes(self.0[32 - 8..].try_into().unwrap())
35    }
36
37    pub fn as_dns_label(&self) -> String {
38        // Combine the ID and checksum into a single array
39        let mut id: [u8; 35] = [0; 35];
40        id[..32].copy_from_slice(&self.0);
41        id[32..].copy_from_slice(&self.1);
42        base32::encode(Self::ALPHABET, &id)
43    }
44}
45
46impl From<u64> for NodeID {
47    fn from(x: u64) -> NodeID {
48        let mut id = [0; 32];
49        id[32 - 8..].copy_from_slice(&x.to_be_bytes());
50        NodeID::new(id)
51    }
52}
53
54impl fmt::Display for NodeID {
55    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
56        write!(f, "Node-{}", self.as_dns_label())
57    }
58}
59
60impl fmt::Debug for NodeID {
61    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
62        fmt::Display::fmt(self, f)
63    }
64}
65
66impl From<NodeID> for [u8; 32] {
67    fn from(val: NodeID) -> Self {
68        val.0
69    }
70}
71
72impl From<[u8; 32]> for NodeID {
73    fn from(bytes: [u8; 32]) -> Self {
74        Self::new(bytes)
75    }
76}
77
78impl<'a> TryFrom<&'a [u8]> for NodeID {
79    type Error = IDError;
80
81    fn try_from(bytes: &'a [u8]) -> Result<Self, Self::Error> {
82        if bytes.len() != 32 {
83            return Err(IDError::Length);
84        }
85
86        let mut id = [0; 32];
87        id.copy_from_slice(bytes);
88        Ok(Self::new(id))
89    }
90}
91
92impl FromHex for NodeID {
93    type Error = hex::FromHexError;
94
95    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
96        let bytes = <[u8; 32]>::from_hex(hex)?;
97        Ok(Self::new(bytes))
98    }
99}
100
101impl std::str::FromStr for NodeID {
102    type Err = IDError;
103    fn from_str(s: &str) -> Result<Self, Self::Err> {
104        const PREFIX: &str = "Node-";
105        if s.len() < PREFIX.len() || &s[..PREFIX.len()] != PREFIX {
106            return Err(IDError::Prefix);
107        }
108        let label = &s[PREFIX.len()..];
109        let bytes = base32::decode(NodeID::ALPHABET, label).ok_or(IDError::Base32)?;
110        if bytes.len() != 35 {
111            return Err(IDError::Length);
112        }
113        let mut id = [0u8; 32];
114        id.copy_from_slice(&bytes[..32]);
115        let mut checksum = [0u8; 3];
116        checksum.copy_from_slice(&bytes[32..]);
117        let provided = NodeID(id, checksum);
118        let computed = NodeID::new(id);
119        if provided != computed {
120            return Err(IDError::Checksum);
121        }
122        Ok(computed)
123    }
124}
125
126impl AsRef<[u8]> for NodeID {
127    fn as_ref(&self) -> &[u8] {
128        &self.0
129    }
130}
131
132impl Serialize for NodeID {
133    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
134    where
135        S: serde::Serializer,
136    {
137        if serializer.is_human_readable() {
138            serializer.serialize_str(&self.to_string())
139        } else {
140            (self.0, self.1).serialize(serializer)
141        }
142    }
143}
144
145impl<'de> Deserialize<'de> for NodeID {
146    fn deserialize<D>(deserializer: D) -> Result<NodeID, D::Error>
147    where
148        D: serde::Deserializer<'de>,
149    {
150        if deserializer.is_human_readable() {
151            let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
152            s.parse().map_err(|e| serde::de::Error::custom(format!("{e:?}")))
153        } else {
154            let (id, checksum) = <([u8; 32], [u8; 3]) as Deserialize>::deserialize(deserializer)?;
155            Ok(NodeID(id, checksum))
156        }
157    }
158}
159
160#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize, Default)]
161pub struct RequiredLyquid(pub LyquidID);
162
163#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
164pub struct LyquidID(pub [u8; 20]);
165
166impl Serialize for LyquidID {
167    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
168    where
169        S: serde::Serializer,
170    {
171        if serializer.is_human_readable() {
172            serializer.serialize_str(&self.to_string())
173        } else {
174            self.0.serialize(serializer)
175        }
176    }
177}
178
179impl<'de> Deserialize<'de> for LyquidID {
180    fn deserialize<D>(deserializer: D) -> Result<LyquidID, D::Error>
181    where
182        D: serde::Deserializer<'de>,
183    {
184        if deserializer.is_human_readable() {
185            let s: std::borrow::Cow<'de, str> = Deserialize::deserialize(deserializer)?;
186            s.parse().map_err(|e| serde::de::Error::custom(format!("{e:?}")))
187        } else {
188            let arr = <[u8; 20] as Deserialize>::deserialize(deserializer)?;
189            Ok(LyquidID(arr))
190        }
191    }
192}
193
194impl From<LyquidID> for [u8; 20] {
195    fn from(id: LyquidID) -> Self {
196        id.0
197    }
198}
199
200impl From<LyquidID> for Address {
201    fn from(id: LyquidID) -> Address {
202        Address(id.0.into())
203    }
204}
205
206impl From<[u8; 20]> for LyquidID {
207    fn from(bytes: [u8; 20]) -> Self {
208        Self(bytes)
209    }
210}
211
212impl From<&[u8; 20]> for LyquidID {
213    fn from(bytes: &[u8; 20]) -> Self {
214        Self(*bytes)
215    }
216}
217
218impl From<Address> for LyquidID {
219    fn from(addr: Address) -> Self {
220        Self(addr.into())
221    }
222}
223
224impl TryFrom<&[u8]> for LyquidID {
225    type Error = std::array::TryFromSliceError;
226    fn try_from(s: &[u8]) -> Result<LyquidID, Self::Error> {
227        Ok(Self(s.try_into()?))
228    }
229}
230
231impl From<u64> for LyquidID {
232    fn from(x: u64) -> Self {
233        let mut id = [0; 20];
234        id[20 - 8..].copy_from_slice(&x.to_be_bytes());
235        Self(id)
236    }
237}
238
239impl fmt::Display for LyquidID {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
241        write!(f, "Lyquid-{}", cb58::cb58_encode(self.0))
242    }
243}
244
245impl fmt::Debug for LyquidID {
246    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
247        fmt::Display::fmt(self, f)
248    }
249}
250
251impl LyquidID {
252    pub fn from_owner_nonce(owner: &Address, nonce: u64) -> Self {
253        let mut hasher = blake3::Hasher::new();
254        hasher.update(owner.as_slice());
255        hasher.update(&nonce.to_be_bytes());
256        let hash: [u8; 32] = hasher.finalize().into();
257        hash[12..].try_into().unwrap()
258    }
259
260    pub fn readable_short(&self) -> String {
261        let s = self.to_string();
262        format!("{}..{}", &s[..15], &s[s.len() - 8..])
263    }
264}
265
266impl FromHex for LyquidID {
267    type Error = hex::FromHexError;
268
269    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
270        let bytes = <[u8; 20]>::from_hex(hex)?;
271        Ok(Self(bytes))
272    }
273}
274
275#[derive(Debug)]
276pub enum IDError {
277    Prefix,
278    CB58,
279    Base32,
280    Checksum,
281    Length,
282}
283
284impl std::str::FromStr for LyquidID {
285    type Err = IDError;
286    fn from_str(s: &str) -> Result<Self, Self::Err> {
287        const PREFIX: &str = "Lyquid-";
288        if s.len() < PREFIX.len() || &s[..PREFIX.len()] != PREFIX {
289            return Err(IDError::Prefix);
290        }
291        let bytes = cb58::cb58_decode(&s[PREFIX.len()..]).ok_or(IDError::CB58)?;
292        Ok(LyquidID(bytes.try_into().map_err(|_| IDError::Length)?))
293    }
294}
295
296impl AsRef<[u8]> for LyquidID {
297    fn as_ref(&self) -> &[u8] {
298        &self.0
299    }
300}
301
302#[derive(Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy, Debug)]
303/// The verison number that uniquely identifies (and determines) the state of network variables.
304pub struct LyquidNumber {
305    /// The version number for the Lyquid code image.
306    pub image: u32,
307    /// The version number for the Lyquid network variables.
308    pub var: u32,
309}
310
311impl fmt::Display for LyquidNumber {
312    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
313        write!(f, "LyquidNumber(image={}, var={})", self.image, self.var)
314    }
315}
316
317impl LyquidNumber {
318    pub const ZERO: Self = LyquidNumber { image: 0, var: 0 };
319}
320
321impl From<u64> for LyquidNumber {
322    fn from(x: u64) -> Self {
323        Self {
324            image: (x >> 32) as u32,
325            var: x as u32,
326        }
327    }
328}
329
330impl From<&LyquidNumber> for u64 {
331    fn from(n: &LyquidNumber) -> u64 {
332        ((n.image as u64) << 32) | n.var as u64
333    }
334}
335
336impl From<LyquidNumber> for u64 {
337    fn from(n: LyquidNumber) -> u64 {
338        (&n).into()
339    }
340}
341
342impl std::str::FromStr for LyquidNumber {
343    type Err = std::num::ParseIntError;
344    fn from_str(s: &str) -> Result<Self, Self::Err> {
345        let x: u64 = s.parse()?;
346        Ok(x.into())
347    }
348}
349
350#[cfg(test)]
351mod tests {
352    use super::*;
353    use ed25519_compact::KeyPair;
354
355    #[test]
356    fn test_hex() {
357        let kp = KeyPair::from_seed([42u8; 32].into());
358        let id = NodeID::from(*kp.pk);
359        let hex = hex::encode(*kp.pk);
360        let decoded_id = NodeID::from_hex(&hex).unwrap();
361        assert_eq!(id, decoded_id);
362        assert_eq!(hex, "197f6b23e16c8532c6abc838facd5ea789be0c76b2920334039bfa8b3d368d61");
363        assert_eq!(
364            id.to_string(),
365            "Node-df7wwi7bnsctfrvlza4pvtk6u6e34ddwwkjagnadtp5iwpjwrvq3maaa"
366        );
367        assert_eq!(id.to_string().parse::<NodeID>().unwrap(), id);
368        assert_eq!(
369            id.as_dns_label(),
370            "df7wwi7bnsctfrvlza4pvtk6u6e34ddwwkjagnadtp5iwpjwrvq3maaa"
371        );
372    }
373
374    #[test]
375    fn test_u64() {
376        for &x in &[0u64, 1u64, 42u64, u64::MAX] {
377            let id = NodeID::from(x);
378            assert_eq!(id.as_u64(), x, "NodeID <-> u64 roundtrip failed for {x}");
379        }
380    }
381}