bc_components/
reference.rs

1use std::borrow::Cow;
2use bc_ur::prelude::*;
3use crate::{ digest_provider::DigestProvider, tags, Digest };
4use anyhow::{ bail, Result };
5
6/// Implementers of this trait provide a globally unique reference to themselves.
7///
8/// The `ReferenceProvider` trait is used to create a unique, cryptographic reference
9/// to an object. This is particularly useful for distributed systems where objects need
10/// to be uniquely identified across networks or storage systems.
11///
12/// The reference is derived from a cryptographic digest of the object's serialized form,
13/// ensuring that the reference uniquely identifies the object's contents.
14pub trait ReferenceProvider {
15    /// Returns a cryptographic reference that uniquely identifies this object.
16    ///
17    /// The reference is derived from a digest of the object's serialized form,
18    /// ensuring that it uniquely identifies the object's contents.
19    fn reference(&self) -> Reference;
20
21    /// Returns the reference data as a hexadecimal string.
22    ///
23    /// This is a convenience method that returns the full 32-byte reference
24    /// as a 64-character hexadecimal string.
25    fn ref_hex(&self) -> String {
26        self.reference().ref_hex()
27    }
28
29    /// Returns the first four bytes of the reference.
30    ///
31    /// This is a convenience method for when a shorter, more user-friendly
32    /// representation is needed, such as for display or comparison purposes.
33    fn ref_data_short(&self) -> [u8; 4] {
34        self.reference().ref_data_short()
35    }
36
37    /// Returns the first four bytes of the reference as a hexadecimal string.
38    ///
39    /// This produces an 8-character string that is useful for display purposes,
40    /// such as in debug output or logs.
41    fn ref_hex_short(&self) -> String {
42        self.reference().ref_hex_short()
43    }
44
45    /// Returns the first four bytes of the reference as upper-case ByteWords.
46    ///
47    /// ByteWords is a human-readable encoding format that uses common English words
48    /// to represent binary data, making it easier to communicate verbally or in text.
49    ///
50    /// # Parameters
51    ///
52    /// * `prefix` - An optional prefix to add before the ByteWords representation
53    fn ref_bytewords(&self, prefix: Option<&str>) -> String {
54        self.reference().bytewords_identifier(prefix)
55    }
56
57    /// Returns the first four bytes of the reference as Bytemoji.
58    ///
59    /// Bytemoji is an emoji-based encoding that represents binary data using
60    /// emoji characters, which can be more visually distinctive and memorable.
61    ///
62    /// # Parameters
63    ///
64    /// * `prefix` - An optional prefix to add before the Bytemoji representation
65    fn ref_bytemoji(&self, prefix: Option<&str>) -> String {
66        self.reference().bytemoji_identifier(prefix)
67    }
68}
69
70/// A globally unique reference to a globally unique object.
71///
72/// `Reference` provides a cryptographically secure way to uniquely identify
73/// objects based on their content. It is a fixed-size (32 bytes) identifier,
74/// typically derived from the SHA-256 hash of the object's serialized form.
75///
76/// References are useful in distributed systems for:
77/// - Unique identification of objects across networks
78/// - Verification that an object hasn't been modified
79/// - Content-addressable storage systems
80/// - Linking between objects in a content-addressed way
81///
82/// A `Reference` can be displayed in various formats, including hexadecimal,
83/// ByteWords (a human-readable partial hash as words), and Bytemoji (a
84/// human-readable partial hash based on emojis).
85#[derive(Clone, PartialEq, Eq, Hash)]
86pub struct Reference([u8; Self::REFERENCE_SIZE]);
87
88impl Reference {
89    pub const REFERENCE_SIZE: usize = 32;
90
91    /// Create a new reference from data.
92    pub fn from_data(data: [u8; Self::REFERENCE_SIZE]) -> Self {
93        Self(data)
94    }
95
96    /// Create a new reference from data.
97    ///
98    /// Returns `None` if the data is not the correct length.
99    pub fn from_data_ref(data: impl AsRef<[u8]>) -> Result<Self> {
100        let data = data.as_ref();
101        if data.len() != Self::REFERENCE_SIZE {
102            bail!("Invalid reference size");
103        }
104        let mut arr = [0u8; Self::REFERENCE_SIZE];
105        arr.copy_from_slice(data.as_ref());
106        Ok(Self::from_data(arr))
107    }
108
109    /// Create a new reference from the given digest.
110    pub fn from_digest(digest: Digest) -> Self {
111        Self::from_data(*digest.data())
112    }
113
114    /// Get the data of the reference.
115    pub fn data(&self) -> &[u8; Self::REFERENCE_SIZE] {
116        self.into()
117    }
118
119    /// Create a new reference from the given hexadecimal string.
120    ///
121    /// # Panics
122    /// Panics if the string is not exactly 64 hexadecimal digits.
123    pub fn from_hex(hex: impl AsRef<str>) -> Self {
124        Self::from_data_ref(hex::decode(hex.as_ref()).unwrap()).unwrap()
125    }
126
127    /// The data as a hexadecimal string.
128    pub fn ref_hex(&self) -> String {
129        hex::encode(self.0)
130    }
131
132    /// The first four bytes of the reference
133    pub fn ref_data_short(&self) -> [u8; 4] {
134        self.0[0..4].try_into().unwrap()
135    }
136
137    /// The first four bytes of the reference as a hexadecimal string.
138    pub fn ref_hex_short(&self) -> String {
139        hex::encode(self.ref_data_short())
140    }
141
142    /// The first four bytes of the XID as upper-case ByteWords.
143    pub fn bytewords_identifier(&self, prefix: Option<&str>) -> String {
144        let s = bytewords::identifier(&self.ref_data_short()).to_uppercase();
145        if let Some(prefix) = prefix {
146            format!("{prefix} {s}")
147        } else {
148            s
149        }
150    }
151
152    /// The first four bytes of the XID as Bytemoji.
153    pub fn bytemoji_identifier(&self, prefix: Option<&str>) -> String {
154        let s = bytewords::bytemoji_identifier(&self.0[..4].try_into().unwrap()).to_uppercase();
155        if let Some(prefix) = prefix {
156            format!("{prefix} {s}")
157        } else {
158            s
159        }
160    }
161}
162
163/// Implement the `ReferenceProvider` trait for `Reference`.
164///
165/// Yes, this creates a Reference to a Reference.
166impl ReferenceProvider for Reference {
167    fn reference(&self) -> Reference {
168        Reference::from_digest(self.digest().into_owned())
169    }
170}
171
172impl<'a> From<&'a Reference> for &'a [u8; Reference::REFERENCE_SIZE] {
173    fn from(value: &'a Reference) -> Self {
174        &value.0
175    }
176}
177
178impl<'a> From<&'a Reference> for &'a [u8] {
179    fn from(value: &'a Reference) -> Self {
180        &value.0
181    }
182}
183
184impl AsRef<[u8]> for Reference {
185    fn as_ref(&self) -> &[u8] {
186        &self.0
187    }
188}
189
190impl AsRef<Reference> for Reference {
191    fn as_ref(&self) -> &Reference {
192        self
193    }
194}
195
196impl std::cmp::PartialOrd for Reference {
197    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
198        Some(self.0.cmp(&other.0))
199    }
200}
201
202impl std::cmp::Ord for Reference {
203    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
204        self.0.cmp(&other.0)
205    }
206}
207
208impl DigestProvider for Reference {
209    fn digest(&self) -> Cow<'_, Digest> {
210        Cow::Owned(Digest::from_image(self.tagged_cbor().to_cbor_data()))
211    }
212}
213
214impl std::fmt::Debug for Reference {
215    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
216        write!(f, "Reference({})", self.ref_hex())
217    }
218}
219
220impl std::fmt::Display for Reference {
221    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
222        write!(f, "Reference({})", self.ref_hex_short())
223    }
224}
225
226impl CBORTagged for Reference {
227    fn cbor_tags() -> Vec<Tag> {
228        tags_for_values(&[tags::TAG_REFERENCE])
229    }
230}
231
232impl From<Reference> for CBOR {
233    fn from(value: Reference) -> Self {
234        value.tagged_cbor()
235    }
236}
237
238impl CBORTaggedEncodable for Reference {
239    fn untagged_cbor(&self) -> CBOR {
240        CBOR::to_byte_string(self.0)
241    }
242}
243
244impl TryFrom<CBOR> for Reference {
245    type Error = dcbor::Error;
246
247    fn try_from(cbor: CBOR) -> dcbor::Result<Self> {
248        Self::from_tagged_cbor(cbor)
249    }
250}
251
252impl CBORTaggedDecodable for Reference {
253    fn from_untagged_cbor(cbor: CBOR) -> dcbor::Result<Self> {
254        let data = CBOR::try_into_byte_string(cbor)?;
255        Ok(Self::from_data_ref(data)?)
256    }
257}
258
259// Convert from an instance reference to an instance.
260impl From<&Reference> for Reference {
261    fn from(digest: &Reference) -> Self {
262        digest.clone()
263    }
264}
265
266// Convert from a byte vector to an instance.
267impl From<Reference> for Vec<u8> {
268    fn from(digest: Reference) -> Self {
269        digest.0.to_vec()
270    }
271}
272
273// Convert a reference to an instance to a byte vector.
274impl From<&Reference> for Vec<u8> {
275    fn from(digest: &Reference) -> Self {
276        digest.0.to_vec()
277    }
278}