bc_components/id/
xid.rs

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