1use std::fmt;
2
3use crate::hex::{self, FromHex};
4use serde::{Deserialize, Serialize};
5use sha2::Digest;
6
7use super::Address;
8
9#[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 let mut hash = sha2::Sha256::default();
20 hash.update(&id);
21 let hash = hash.finalize();
22 checksum[0] = hash[0];
24 checksum[1] = hash[1];
25 checksum[2] = 0; 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 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)]
303pub struct LyquidNumber {
305 pub image: u32,
307 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}