nostr_types/types/
id.rs

1use crate::Error;
2use bech32::{FromBase32, ToBase32};
3use derive_more::{AsMut, AsRef, Deref, Display, From, FromStr, Into};
4use serde::de::{Deserializer, Visitor};
5use serde::ser::Serializer;
6use serde::{Deserialize, Serialize};
7use std::fmt;
8
9/// An event identifier, constructed as a SHA256 hash of the event fields according to NIP-01
10#[derive(
11    AsMut, AsRef, Clone, Copy, Debug, Deref, Eq, From, Hash, Into, Ord, PartialEq, PartialOrd,
12)]
13pub struct Id(pub [u8; 32]);
14
15impl Id {
16    /// Render into a hexadecimal string
17    ///
18    /// Consider converting `.into()` an `IdHex` which is a wrapped type rather than a naked `String`
19    pub fn as_hex_string(&self) -> String {
20        hex::encode(self.0)
21    }
22
23    /// Create from a hexadecimal string
24    pub fn try_from_hex_string(v: &str) -> Result<Id, Error> {
25        let vec: Vec<u8> = hex::decode(v)?;
26        Ok(Id(vec
27            .try_into()
28            .map_err(|_| Error::WrongLengthHexString)?))
29    }
30
31    /// Export as a bech32 encoded string ("note")
32    pub fn try_as_bech32_string(&self) -> Result<String, Error> {
33        Ok(bech32::encode(
34            "note",
35            self.0.to_vec().to_base32(),
36            bech32::Variant::Bech32,
37        )?)
38    }
39
40    /// Import from a bech32 encoded string ("note")
41    pub fn try_from_bech32_string(s: &str) -> Result<Id, Error> {
42        let data = bech32::decode(s)?;
43        if data.0 != "note" {
44            Err(Error::WrongBech32("note".to_string(), data.0))
45        } else {
46            let decoded = Vec::<u8>::from_base32(&data.1)?;
47            if decoded.len() != 32 {
48                Err(Error::InvalidId)
49            } else {
50                match <[u8; 32]>::try_from(decoded) {
51                    Ok(array) => Ok(Id(array)),
52                    _ => Err(Error::InvalidId),
53                }
54            }
55        }
56    }
57
58    // Mock data for testing
59    #[allow(dead_code)]
60    pub(crate) fn mock() -> Id {
61        Id::try_from_hex_string("5df64b33303d62afc799bdc36d178c07b2e1f0d824f31b7dc812219440affab6")
62            .unwrap()
63    }
64}
65
66impl Serialize for Id {
67    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
68    where
69        S: Serializer,
70    {
71        serializer.serialize_str(&hex::encode(self.0))
72    }
73}
74
75impl<'de> Deserialize<'de> for Id {
76    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
77    where
78        D: Deserializer<'de>,
79    {
80        deserializer.deserialize_str(IdVisitor)
81    }
82}
83
84struct IdVisitor;
85
86impl Visitor<'_> for IdVisitor {
87    type Value = Id;
88
89    fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
90        write!(f, "a hexadecimal string representing 32 bytes")
91    }
92
93    fn visit_str<E>(self, v: &str) -> Result<Id, E>
94    where
95        E: serde::de::Error,
96    {
97        let vec: Vec<u8> =
98            hex::decode(v).map_err(|e| serde::de::Error::custom(format!("{}", e)))?;
99
100        Ok(Id(vec.try_into().map_err(|e: Vec<u8>| {
101            E::custom(format!(
102                "Id is not 32 bytes long. Was {} bytes long",
103                e.len()
104            ))
105        })?))
106    }
107}
108
109/// An event identifier, constructed as a SHA256 hash of the event fields according to NIP-01, as a hex string
110///
111/// You can convert from an `Id` into this with `From`/`Into`.  You can convert this back to an `Id` with `TryFrom`/`TryInto`.
112#[derive(
113    AsMut,
114    AsRef,
115    Clone,
116    Debug,
117    Deref,
118    Deserialize,
119    Display,
120    Eq,
121    From,
122    FromStr,
123    Hash,
124    Into,
125    PartialEq,
126    Serialize,
127)]
128pub struct IdHex(String);
129
130impl IdHex {
131    // Mock data for testing
132    #[allow(dead_code)]
133    pub(crate) fn mock() -> IdHex {
134        From::from(Id::mock())
135    }
136
137    /// Try from &str
138    pub fn try_from_str(s: &str) -> Result<IdHex, Error> {
139        Self::try_from_string(s.to_owned())
140    }
141
142    /// Try from String
143    pub fn try_from_string(s: String) -> Result<IdHex, Error> {
144        if s.len() != 64 {
145            return Err(Error::InvalidId);
146        }
147        let vec: Vec<u8> = hex::decode(&s)?;
148        if vec.len() != 32 {
149            return Err(Error::InvalidId);
150        }
151        Ok(IdHex(s))
152    }
153
154    /// As &str
155    pub fn as_str(&self) -> &str {
156        &self.0
157    }
158
159    /// Into String
160    pub fn into_string(self) -> String {
161        self.0
162    }
163
164    /// Prefix of
165    pub fn prefix(&self, mut chars: usize) -> IdHexPrefix {
166        if chars > 64 {
167            chars = 64;
168        }
169        IdHexPrefix(self.0[0..chars].to_owned())
170    }
171}
172
173impl TryFrom<&str> for IdHex {
174    type Error = Error;
175
176    fn try_from(s: &str) -> Result<IdHex, Error> {
177        IdHex::try_from_str(s)
178    }
179}
180
181impl From<Id> for IdHex {
182    fn from(i: Id) -> IdHex {
183        IdHex(i.as_hex_string())
184    }
185}
186
187impl From<IdHex> for Id {
188    fn from(h: IdHex) -> Id {
189        // could only fail if IdHex is invalid
190        Id::try_from_hex_string(&h.0).unwrap()
191    }
192}
193
194/// An event identifier prefix, constructed as a SHA256 hash of the event fields according to NIP-01, as a hex string
195#[derive(
196    AsMut,
197    AsRef,
198    Clone,
199    Debug,
200    Deref,
201    Deserialize,
202    Display,
203    Eq,
204    From,
205    FromStr,
206    Hash,
207    Into,
208    PartialEq,
209    Serialize,
210)]
211pub struct IdHexPrefix(String);
212
213impl IdHexPrefix {
214    // Mock data for testing
215    #[allow(dead_code)]
216    pub(crate) fn mock() -> IdHexPrefix {
217        IdHexPrefix("a872bee017".to_owned())
218    }
219
220    /// Try from &str
221    pub fn try_from_str(s: &str) -> Result<IdHexPrefix, Error> {
222        Self::try_from_string(s.to_owned())
223    }
224
225    /// Try from String
226    pub fn try_from_string(s: String) -> Result<IdHexPrefix, Error> {
227        if s.len() > 64 {
228            return Err(Error::InvalidIdPrefix);
229        }
230        if s.chars().any(|c| !c.is_ascii_hexdigit()) {
231            return Err(Error::InvalidPublicKeyPrefix);
232        }
233        // let vec: Vec<u8> = hex::decode(&s)?;
234        // if vec.len() > 32 {
235        //     return Err(Error::InvalidIdPrefix);
236        // }
237        Ok(IdHexPrefix(s))
238    }
239
240    /// As &str
241    pub fn as_str(&self) -> &str {
242        &self.0
243    }
244
245    /// Into String
246    pub fn into_string(self) -> String {
247        self.0
248    }
249
250    /// Matches a PublicKeyhex
251    pub fn matches(&self, id: &IdHex) -> bool {
252        id.0.starts_with(&self.0)
253    }
254}
255
256impl From<IdHex> for IdHexPrefix {
257    fn from(id: IdHex) -> IdHexPrefix {
258        IdHexPrefix(id.0)
259    }
260}
261
262impl TryFrom<&str> for IdHexPrefix {
263    type Error = Error;
264
265    fn try_from(s: &str) -> Result<IdHexPrefix, Error> {
266        IdHexPrefix::try_from_str(s)
267    }
268}
269
270#[cfg(test)]
271mod test {
272    use super::*;
273
274    test_serde! {Id, test_id_serde}
275    test_serde! {IdHex, test_id_hex_serde}
276    test_serde! {IdHexPrefix, test_id_hex_prefix_serde}
277
278    #[test]
279    fn test_id_bech32() {
280        let bech32 = Id::mock().try_as_bech32_string().unwrap();
281        println!("{}", bech32);
282        assert_eq!(Id::mock(), Id::try_from_bech32_string(&bech32).unwrap());
283    }
284}