bc_components/id/
xid.rs

1use anyhow::{Error, Result, bail};
2use dcbor::prelude::*;
3
4use crate::{
5    Digest, PrivateKeyBase, PublicKeys, Reference, ReferenceProvider,
6    SigningPrivateKey, SigningPublicKey, tags,
7};
8
9/// A XID (eXtensible IDentifier).
10///
11/// A XID is a unique 32-byte identifier for a subject entity (person,
12/// organization, device, or any other entity). XIDs have the following
13/// characteristics:
14///
15/// - They're cryptographically tied to a public key at inception (the
16///   "inception key")
17/// - They remain stable throughout their lifecycle even as their keys and
18///   permissions change
19/// - They can be extended to XID documents containing keys, endpoints,
20///   permissions, and delegation info
21/// - They support key rotation and multiple verification schemes
22/// - They allow for delegation of specific permissions to other entities
23/// - They can include resolution methods to locate and verify the XID document
24///
25/// A XID is created by taking the SHA-256 hash of the CBOR encoding of a public
26/// signing key. This ensures the XID is cryptographically tied to the key.
27///
28/// As defined in [BCR-2024-010](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2024-010-xid.md).
29#[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    /// Create a new XID from data.
36    pub fn from_data(data: [u8; Self::XID_SIZE]) -> Self { Self(data) }
37
38    /// Create a new XID from data.
39    ///
40    /// Returns `None` if the data is not the correct length.
41    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    /// Return the data of the XID.
52    pub fn data(&self) -> &[u8; Self::XID_SIZE] { self.into() }
53
54    /// Get the data of the XID as a byte slice.
55    pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
56
57    /// Create a new XID from the given public key (the "genesis key").
58    ///
59    /// The XID is the SHA-256 digest of the CBOR encoding of the public key.
60    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    /// Validate the XID against the given public key.
67    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    /// Create a new XID from the given hexadecimal string.
74    ///
75    /// # Panics
76    /// Panics if the string is not exactly 64 hexadecimal digits.
77    pub fn from_hex(hex: impl AsRef<str>) -> Self {
78        Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
79    }
80
81    /// The data as a hexadecimal string.
82    pub fn to_hex(&self) -> String { hex::encode(self.0) }
83
84    /// The first four bytes of the XID as a hexadecimal string.
85    pub fn short_description(&self) -> String { self.ref_hex_short() }
86
87    /// The first four bytes of the XID as upper-case ByteWords.
88    pub fn bytewords_identifier(&self, prefix: bool) -> String {
89        self.ref_bytewords(if prefix { Some("๐Ÿ…ง") } else { None })
90    }
91
92    /// The first four bytes of the XID as Bytemoji.
93    pub fn bytemoji_identifier(&self, prefix: bool) -> String {
94        self.ref_bytemoji(if prefix { Some("๐Ÿ…ง") } else { None })
95    }
96}
97
98/// A provider trait for obtaining XIDs from various objects.
99pub trait XIDProvider {
100    /// Returns the XID for this object.
101    fn xid(&self) -> XID;
102}
103
104/// Implements XIDProvider for XID to return itself.
105impl XIDProvider for XID {
106    fn xid(&self) -> XID { *self }
107}
108
109/// Implements XIDProvider for SigningPublicKey to generate an XID from the key.
110impl XIDProvider for SigningPublicKey {
111    fn xid(&self) -> XID { XID::new(self) }
112}
113
114/// Implements ReferenceProvider for XID to generate a Reference from the XID.
115impl ReferenceProvider for XID {
116    fn reference(&self) -> Reference { Reference::from_data(*self.data()) }
117}
118
119/// Implements conversion from a XID reference to a byte array reference.
120impl<'a> From<&'a XID> for &'a [u8; XID::XID_SIZE] {
121    fn from(value: &'a XID) -> Self { &value.0 }
122}
123
124/// Implements conversion from a XID reference to a byte slice reference.
125impl<'a> From<&'a XID> for &'a [u8] {
126    fn from(value: &'a XID) -> Self { &value.0 }
127}
128
129/// Implements AsRef<[u8]> to allow XID to be treated as a byte slice.
130impl AsRef<[u8]> for XID {
131    fn as_ref(&self) -> &[u8] { &self.0 }
132}
133
134/// Implements PartialOrd to allow XIDs to be compared and partially ordered.
135impl 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
141/// Implements Ord to allow XIDs to be fully ordered.
142impl std::cmp::Ord for XID {
143    fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) }
144}
145
146/// Implements Debug formatting for XID showing the full hex representation.
147impl 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
153/// Implements Display formatting for XID showing a shortened hex
154/// representation.
155impl 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
161/// Implements CBORTagged trait to provide CBOR tag information for XID.
162impl CBORTagged for XID {
163    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_XID]) }
164}
165
166/// Implements conversion from XID to CBOR for serialization.
167impl From<XID> for CBOR {
168    fn from(value: XID) -> Self { value.tagged_cbor() }
169}
170
171/// Implements conversion from SigningPublicKey reference to XID.
172impl From<&SigningPublicKey> for XID {
173    fn from(key: &SigningPublicKey) -> Self { Self::new(key) }
174}
175
176/// Implements conversion from SigningPrivateKey reference to XID via the public
177/// key.
178impl 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
186/// Implements conversion from PublicKeys reference to XID via the signing
187/// public key.
188impl From<&PublicKeys> for XID {
189    fn from(key: &PublicKeys) -> Self { Self::new(key.signing_public_key()) }
190}
191
192/// Implements conversion from PrivateKeyBase reference to XID via the Schnorr
193/// signing key.
194impl From<&PrivateKeyBase> for XID {
195    fn from(key: &PrivateKeyBase) -> Self {
196        Self::new(key.schnorr_signing_private_key().public_key().unwrap())
197    }
198}
199
200/// Implements CBORTaggedEncodable to provide CBOR encoding functionality for
201/// XID.
202impl CBORTaggedEncodable for XID {
203    fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.0) }
204}
205
206/// Implements conversion from CBOR to XID for deserialization.
207impl 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
215/// Implements CBORTaggedDecodable to provide CBOR decoding functionality for
216/// XID.
217impl 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
224/// Implements conversion from XID to `Vec<u8>` to allow access to the raw
225/// bytes.
226impl 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}