bc_components/
reference.rs

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