bc_components/
reference.rs

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