lyquor_primitives/
id.rs

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