1use anyhow::{Error, Result, bail};
2use dcbor::prelude::*;
3
4use crate::{
5 Digest, PrivateKeyBase, PublicKeys, Reference, ReferenceProvider,
6 SigningPrivateKey, SigningPublicKey, tags,
7};
8
9#[derive(Clone, Copy, Eq, PartialEq, Hash)]
30pub struct XID([u8; Self::XID_SIZE]);
31
32impl XID {
33 pub const XID_SIZE: usize = 32;
34
35 pub fn from_data(data: [u8; Self::XID_SIZE]) -> Self { Self(data) }
37
38 pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
42 let data = data.as_ref();
43 if data.len() != Self::XID_SIZE {
44 bail!("Invalid XID size");
45 }
46 let mut arr = [0u8; Self::XID_SIZE];
47 arr.copy_from_slice(data.as_ref());
48 Ok(Self::from_data(arr))
49 }
50
51 pub fn data(&self) -> &[u8; Self::XID_SIZE] { self.into() }
53
54 pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
56
57 pub fn new(genesis_key: impl AsRef<SigningPublicKey>) -> Self {
61 let key_cbor_data = genesis_key.as_ref().to_cbor_data();
62 let digest = Digest::from_image(key_cbor_data);
63 Self::from_data(*digest.data())
64 }
65
66 pub fn validate(&self, key: &SigningPublicKey) -> bool {
68 let key_data = key.to_cbor_data();
69 let digest = Digest::from_image(key_data);
70 *digest.data() == self.0
71 }
72
73 pub fn from_hex(hex: impl AsRef<str>) -> Self {
78 Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
79 }
80
81 pub fn to_hex(&self) -> String { hex::encode(self.0) }
83
84 pub fn short_description(&self) -> String { self.ref_hex_short() }
86
87 pub fn bytewords_identifier(&self, prefix: bool) -> String {
89 self.ref_bytewords(if prefix { Some("๐
ง") } else { None })
90 }
91
92 pub fn bytemoji_identifier(&self, prefix: bool) -> String {
94 self.ref_bytemoji(if prefix { Some("๐
ง") } else { None })
95 }
96}
97
98pub trait XIDProvider {
100 fn xid(&self) -> XID;
102}
103
104impl XIDProvider for XID {
106 fn xid(&self) -> XID { *self }
107}
108
109impl XIDProvider for SigningPublicKey {
111 fn xid(&self) -> XID { XID::new(self) }
112}
113
114impl ReferenceProvider for XID {
116 fn reference(&self) -> Reference { Reference::from_data(*self.data()) }
117}
118
119impl<'a> From<&'a XID> for &'a [u8; XID::XID_SIZE] {
121 fn from(value: &'a XID) -> Self { &value.0 }
122}
123
124impl<'a> From<&'a XID> for &'a [u8] {
126 fn from(value: &'a XID) -> Self { &value.0 }
127}
128
129impl AsRef<[u8]> for XID {
131 fn as_ref(&self) -> &[u8] { &self.0 }
132}
133
134impl std::cmp::PartialOrd for XID {
136 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
137 Some(self.0.cmp(&other.0))
138 }
139}
140
141impl std::cmp::Ord for XID {
143 fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) }
144}
145
146impl std::fmt::Debug for XID {
148 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149 write!(f, "XID({})", hex::encode(self.0))
150 }
151}
152
153impl std::fmt::Display for XID {
156 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157 write!(f, "XID({})", self.short_description())
158 }
159}
160
161impl CBORTagged for XID {
163 fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_XID]) }
164}
165
166impl From<XID> for CBOR {
168 fn from(value: XID) -> Self { value.tagged_cbor() }
169}
170
171impl From<&SigningPublicKey> for XID {
173 fn from(key: &SigningPublicKey) -> Self { Self::new(key) }
174}
175
176impl TryFrom<&SigningPrivateKey> for XID {
179 type Error = Error;
180
181 fn try_from(key: &SigningPrivateKey) -> Result<Self, Self::Error> {
182 Ok(Self::new(&key.public_key()?))
183 }
184}
185
186impl From<&PublicKeys> for XID {
189 fn from(key: &PublicKeys) -> Self { Self::new(key.signing_public_key()) }
190}
191
192impl From<&PrivateKeyBase> for XID {
195 fn from(key: &PrivateKeyBase) -> Self {
196 Self::new(key.schnorr_signing_private_key().public_key().unwrap())
197 }
198}
199
200impl CBORTaggedEncodable for XID {
203 fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.0) }
204}
205
206impl TryFrom<CBOR> for XID {
208 type Error = dcbor::Error;
209
210 fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
211 Self::from_tagged_cbor(cbor)
212 }
213}
214
215impl CBORTaggedDecodable for XID {
218 fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
219 let data = CBOR::try_into_byte_string(cbor)?;
220 Ok(Self::from_data_ref(data)?)
221 }
222}
223
224impl From<XID> for Vec<u8> {
227 fn from(xid: XID) -> Self { xid.0.to_vec() }
228}
229
230#[cfg(test)]
231mod tests {
232 use bc_ur::prelude::*;
233 use hex_literal::hex;
234 use indoc::indoc;
235
236 use super::*;
237 use crate::{ECPrivateKey, SigningPrivateKey};
238
239 #[test]
240 fn test_xid() {
241 crate::register_tags();
242 let xid = XID::from_data_ref(hex!(
243 "de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037"
244 ))
245 .unwrap();
246 assert_eq!(
247 xid.to_hex(),
248 "de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037"
249 );
250 assert_eq!(xid.short_description(), "de285368");
251 assert_eq!(
252 xid.data(),
253 &hex!(
254 "de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037"
255 )
256 );
257 assert_eq!(
258 format!("{:?}", xid),
259 "XID(de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037)"
260 );
261 assert_eq!(format!("{}", xid), "XID(de285368)");
262
263 let xid_string = xid.ur_string();
264 assert_eq!(
265 xid_string,
266 "ur:xid/hdcxuedeguisgevwhdaxnbluenutlbglhfiygamsamadmojkdydtneteeowffhwprtemcaatledk"
267 );
268 assert_eq!(XID::from_ur_string(xid_string).unwrap(), xid);
269 assert_eq!(xid.bytewords_identifier(true), "๐
ง URGE DICE GURU IRIS");
270 assert_eq!(xid.bytemoji_identifier(true), "๐
ง ๐ป ๐ป ๐ ๐");
271 }
272
273 #[test]
274 fn test_xid_from_key() {
275 crate::register_tags();
276 let private_key = SigningPrivateKey::new_schnorr(
277 ECPrivateKey::from_data(hex!(
278 "322b5c1dd5a17c3481c2297990c85c232ed3c17b52ce9905c6ec5193ad132c36"
279 )),
280 );
281 let public_key = private_key.public_key().unwrap();
282
283 let key_cbor = public_key.to_cbor();
284 #[rustfmt::skip]
285 assert_eq!(key_cbor.diagnostic(), indoc! {"
286 40022(
287 h'e8251dc3a17e0f2c07865ed191139ecbcddcbdd070ec1ff65df5148c7ef4005a'
288 )
289 "}.trim());
290 #[rustfmt::skip]
291 assert_eq!(key_cbor.hex_annotated(), indoc! {"
292 d9 9c56 # tag(40022) signing-public-key
293 5820 # bytes(32)
294 e8251dc3a17e0f2c07865ed191139ecbcddcbdd070ec1ff65df5148c7ef4005a
295 "}.trim());
296 let key_cbor_data = key_cbor.to_cbor_data();
297 assert_eq!(
298 key_cbor_data,
299 hex!(
300 "d99c565820e8251dc3a17e0f2c07865ed191139ecbcddcbdd070ec1ff65df5148c7ef4005a"
301 )
302 );
303 let digest = Digest::from_image(&key_cbor_data);
304 assert_eq!(
305 digest.data(),
306 &hex!(
307 "d40e0602674df1b732f5e025d04c45f2e74ed1652c5ae1740f6a5502dbbdcd47"
308 )
309 );
310
311 let xid = XID::new(&public_key);
312 assert_eq!(
313 format!("{:?}", xid),
314 "XID(d40e0602674df1b732f5e025d04c45f2e74ed1652c5ae1740f6a5502dbbdcd47)"
315 );
316 xid.validate(&public_key);
317
318 assert_eq!(format!("{}", xid), "XID(d40e0602)");
319 let reference = xid.reference();
320 assert_eq!(format!("{reference}"), "Reference(d40e0602)");
321
322 assert_eq!(reference.bytewords_identifier(None), "TINY BETA ATOM ALSO");
323 assert_eq!(reference.bytemoji_identifier(None), "๐งฆ ๐คจ ๐ ๐");
324
325 assert_eq!(digest.data(), xid.data());
326 }
327}