bc_components/id/
xid.rs

1use dcbor::prelude::*;
2
3use crate::{
4    Digest, Error, PrivateKeyBase, PublicKeys, Reference, ReferenceProvider,
5    Result, SigningPrivateKey, SigningPublicKey, tags,
6};
7
8/// A XID (eXtensible IDentifier).
9///
10/// A XID is a unique 32-byte identifier for a subject entity (person,
11/// organization, device, or any other entity). XIDs have the following
12/// characteristics:
13///
14/// - They're cryptographically tied to a public key at inception (the
15///   "inception key")
16/// - They remain stable throughout their lifecycle even as their keys and
17///   permissions change
18/// - They can be extended to XID documents containing keys, endpoints,
19///   permissions, and delegation info
20/// - They support key rotation and multiple verification schemes
21/// - They allow for delegation of specific permissions to other entities
22/// - They can include resolution methods to locate and verify the XID document
23///
24/// A XID is created by taking the SHA-256 hash of the CBOR encoding of a public
25/// signing key. This ensures the XID is cryptographically tied to the key.
26///
27/// As defined in [BCR-2024-010](https://github.com/BlockchainCommons/Research/blob/master/papers/bcr-2024-010-xid.md).
28#[derive(Clone, Copy, Eq, PartialEq, Hash)]
29pub struct XID([u8; Self::XID_SIZE]);
30
31impl XID {
32    pub const XID_SIZE: usize = 32;
33
34    /// Create a new XID from data.
35    pub fn from_data(data: [u8; Self::XID_SIZE]) -> Self { Self(data) }
36
37    /// Create a new XID from data.
38    ///
39    /// Returns `None` if the data is not the correct length.
40    pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
41        let data = data.as_ref();
42        if data.len() != Self::XID_SIZE {
43            return Err(Error::invalid_size("XID", Self::XID_SIZE, data.len()));
44        }
45        let mut arr = [0u8; Self::XID_SIZE];
46        arr.copy_from_slice(data.as_ref());
47        Ok(Self::from_data(arr))
48    }
49
50    /// Return the data of the XID.
51    pub fn data(&self) -> &[u8; Self::XID_SIZE] { self.into() }
52
53    /// Get the data of the XID as a byte slice.
54    pub fn as_bytes(&self) -> &[u8] { self.as_ref() }
55
56    /// Create a new XID from the given public key (the "genesis key").
57    ///
58    /// The XID is the SHA-256 digest of the CBOR encoding of the public key.
59    pub fn new(genesis_key: impl AsRef<SigningPublicKey>) -> Self {
60        let key_cbor_data = genesis_key.as_ref().to_cbor_data();
61        let digest = Digest::from_image(key_cbor_data);
62        Self::from_data(*digest.data())
63    }
64
65    /// Validate the XID against the given public key.
66    pub fn validate(&self, key: &SigningPublicKey) -> bool {
67        let key_data = key.to_cbor_data();
68        let digest = Digest::from_image(key_data);
69        *digest.data() == self.0
70    }
71
72    /// Create a new XID from the given hexadecimal string.
73    ///
74    /// # Panics
75    /// Panics if the string is not exactly 64 hexadecimal digits.
76    pub fn from_hex(hex: impl AsRef<str>) -> Self {
77        Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
78    }
79
80    /// The data as a hexadecimal string.
81    pub fn to_hex(&self) -> String { hex::encode(self.0) }
82
83    /// The first four bytes of the XID as a hexadecimal string.
84    pub fn short_description(&self) -> String { self.ref_hex_short() }
85
86    /// The first four bytes of the XID as upper-case ByteWords.
87    pub fn bytewords_identifier(&self, prefix: bool) -> String {
88        self.ref_bytewords(if prefix { Some("๐Ÿ…ง") } else { None })
89    }
90
91    /// The first four bytes of the XID as Bytemoji.
92    pub fn bytemoji_identifier(&self, prefix: bool) -> String {
93        self.ref_bytemoji(if prefix { Some("๐Ÿ…ง") } else { None })
94    }
95}
96
97/// A provider trait for obtaining XIDs from various objects.
98pub trait XIDProvider {
99    /// Returns the XID for this object.
100    fn xid(&self) -> XID;
101}
102
103/// Implements XIDProvider for XID to return itself.
104impl XIDProvider for XID {
105    fn xid(&self) -> XID { *self }
106}
107
108/// Implements XIDProvider for SigningPublicKey to generate an XID from the key.
109impl XIDProvider for SigningPublicKey {
110    fn xid(&self) -> XID { XID::new(self) }
111}
112
113/// Implements ReferenceProvider for XID to generate a Reference from the XID.
114impl ReferenceProvider for XID {
115    fn reference(&self) -> Reference { Reference::from_data(*self.data()) }
116}
117
118/// Implements conversion from a XID reference to a byte array reference.
119impl<'a> From<&'a XID> for &'a [u8; XID::XID_SIZE] {
120    fn from(value: &'a XID) -> Self { &value.0 }
121}
122
123/// Implements conversion from a XID reference to a byte slice reference.
124impl<'a> From<&'a XID> for &'a [u8] {
125    fn from(value: &'a XID) -> Self { &value.0 }
126}
127
128/// Implements AsRef<[u8]> to allow XID to be treated as a byte slice.
129impl AsRef<[u8]> for XID {
130    fn as_ref(&self) -> &[u8] { &self.0 }
131}
132
133/// Implements PartialOrd to allow XIDs to be compared and partially ordered.
134impl std::cmp::PartialOrd for XID {
135    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
136        Some(self.0.cmp(&other.0))
137    }
138}
139
140/// Implements Ord to allow XIDs to be fully ordered.
141impl std::cmp::Ord for XID {
142    fn cmp(&self, other: &Self) -> std::cmp::Ordering { self.0.cmp(&other.0) }
143}
144
145/// Implements Debug formatting for XID showing the full hex representation.
146impl std::fmt::Debug for XID {
147    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
148        write!(f, "XID({})", hex::encode(self.0))
149    }
150}
151
152/// Implements Display formatting for XID showing a shortened hex
153/// representation.
154impl std::fmt::Display for XID {
155    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
156        write!(f, "XID({})", self.short_description())
157    }
158}
159
160/// Implements CBORTagged trait to provide CBOR tag information for XID.
161impl CBORTagged for XID {
162    fn cbor_tags() -> Vec<Tag> { tags_for_values(&[tags::TAG_XID]) }
163}
164
165/// Implements conversion from XID to CBOR for serialization.
166impl From<XID> for CBOR {
167    fn from(value: XID) -> Self { value.tagged_cbor() }
168}
169
170/// Implements conversion from SigningPublicKey reference to XID.
171impl From<&SigningPublicKey> for XID {
172    fn from(key: &SigningPublicKey) -> Self { Self::new(key) }
173}
174
175/// Implements conversion from SigningPrivateKey reference to XID via the public
176/// key.
177impl TryFrom<&SigningPrivateKey> for XID {
178    type Error = Error;
179
180    fn try_from(
181        key: &SigningPrivateKey,
182    ) -> std::result::Result<Self, Self::Error> {
183        Ok(Self::new(&key.public_key()?))
184    }
185}
186
187/// Implements conversion from PublicKeys reference to XID via the signing
188/// public key.
189impl From<&PublicKeys> for XID {
190    fn from(key: &PublicKeys) -> Self { Self::new(key.signing_public_key()) }
191}
192
193/// Implements conversion from PrivateKeyBase reference to XID via the Schnorr
194/// signing key.
195impl From<&PrivateKeyBase> for XID {
196    fn from(key: &PrivateKeyBase) -> Self {
197        Self::new(key.schnorr_signing_private_key().public_key().unwrap())
198    }
199}
200
201/// Implements CBORTaggedEncodable to provide CBOR encoding functionality for
202/// XID.
203impl CBORTaggedEncodable for XID {
204    fn untagged_cbor(&self) -> CBOR { CBOR::to_byte_string(self.0) }
205}
206
207/// Implements conversion from CBOR to XID for deserialization.
208impl TryFrom<CBOR> for XID {
209    type Error = dcbor::Error;
210
211    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
212        Self::from_tagged_cbor(cbor)
213    }
214}
215
216/// Implements CBORTaggedDecodable to provide CBOR decoding functionality for
217/// XID.
218impl CBORTaggedDecodable for XID {
219    fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
220        let data = CBOR::try_into_byte_string(cbor)?;
221        Ok(Self::from_data_ref(data)?)
222    }
223}
224
225/// Implements conversion from XID to `Vec<u8>` to allow access to the raw
226/// bytes.
227impl From<XID> for Vec<u8> {
228    fn from(xid: XID) -> Self { xid.0.to_vec() }
229}
230
231#[cfg(test)]
232mod tests {
233    use bc_ur::prelude::*;
234    use hex_literal::hex;
235    use indoc::indoc;
236
237    use super::*;
238    use crate::{ECPrivateKey, SigningPrivateKey};
239
240    #[test]
241    fn test_xid() {
242        crate::register_tags();
243        let xid = XID::from_data_ref(hex!(
244            "de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037"
245        ))
246        .unwrap();
247        assert_eq!(
248            xid.to_hex(),
249            "de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037"
250        );
251        assert_eq!(xid.short_description(), "de285368");
252        assert_eq!(
253            xid.data(),
254            &hex!(
255                "de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037"
256            )
257        );
258        assert_eq!(
259            format!("{:?}", xid),
260            "XID(de2853684ae55803a08b36dd7f4e566649970601927330299fd333f33fecc037)"
261        );
262        assert_eq!(format!("{}", xid), "XID(de285368)");
263
264        let xid_string = xid.ur_string();
265        assert_eq!(
266            xid_string,
267            "ur:xid/hdcxuedeguisgevwhdaxnbluenutlbglhfiygamsamadmojkdydtneteeowffhwprtemcaatledk"
268        );
269        assert_eq!(XID::from_ur_string(xid_string).unwrap(), xid);
270        assert_eq!(xid.bytewords_identifier(true), "๐Ÿ…ง URGE DICE GURU IRIS");
271        assert_eq!(xid.bytemoji_identifier(true), "๐Ÿ…ง ๐Ÿป ๐Ÿ˜ป ๐Ÿž ๐Ÿ’");
272    }
273
274    #[test]
275    fn test_xid_from_key() {
276        crate::register_tags();
277        let private_key = SigningPrivateKey::new_schnorr(
278            ECPrivateKey::from_data(hex!(
279                "322b5c1dd5a17c3481c2297990c85c232ed3c17b52ce9905c6ec5193ad132c36"
280            )),
281        );
282        let public_key = private_key.public_key().unwrap();
283
284        let key_cbor = public_key.to_cbor();
285        #[rustfmt::skip]
286        assert_eq!(key_cbor.diagnostic(), indoc! {"
287            40022(
288                h'e8251dc3a17e0f2c07865ed191139ecbcddcbdd070ec1ff65df5148c7ef4005a'
289            )
290        "}.trim());
291        #[rustfmt::skip]
292        assert_eq!(key_cbor.hex_annotated(), indoc! {"
293            d9 9c56                                 # tag(40022) signing-public-key
294                5820                                # bytes(32)
295                    e8251dc3a17e0f2c07865ed191139ecbcddcbdd070ec1ff65df5148c7ef4005a
296        "}.trim());
297        let key_cbor_data = key_cbor.to_cbor_data();
298        assert_eq!(
299            key_cbor_data,
300            hex!(
301                "d99c565820e8251dc3a17e0f2c07865ed191139ecbcddcbdd070ec1ff65df5148c7ef4005a"
302            )
303        );
304        let digest = Digest::from_image(&key_cbor_data);
305        assert_eq!(
306            digest.data(),
307            &hex!(
308                "d40e0602674df1b732f5e025d04c45f2e74ed1652c5ae1740f6a5502dbbdcd47"
309            )
310        );
311
312        let xid = XID::new(&public_key);
313        assert_eq!(
314            format!("{:?}", xid),
315            "XID(d40e0602674df1b732f5e025d04c45f2e74ed1652c5ae1740f6a5502dbbdcd47)"
316        );
317        xid.validate(&public_key);
318
319        assert_eq!(format!("{}", xid), "XID(d40e0602)");
320        let reference = xid.reference();
321        assert_eq!(format!("{reference}"), "Reference(d40e0602)");
322
323        assert_eq!(reference.bytewords_identifier(None), "TINY BETA ATOM ALSO");
324        assert_eq!(reference.bytemoji_identifier(None), "๐Ÿงฆ ๐Ÿคจ ๐Ÿ˜Ž ๐Ÿ˜†");
325
326        assert_eq!(digest.data(), xid.data());
327    }
328}