spacetimedb_lib/
identity.rs

1use crate::from_hex_pad;
2use blake3;
3use core::mem;
4use spacetimedb_bindings_macro::{Deserialize, Serialize};
5use spacetimedb_sats::hex::HexString;
6use spacetimedb_sats::{impl_st, u256, AlgebraicType, AlgebraicValue};
7use std::{fmt, str::FromStr};
8
9pub type RequestId = u32;
10
11#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
12pub struct AuthCtx {
13    pub owner: Identity,
14    pub caller: Identity,
15}
16
17impl AuthCtx {
18    pub fn new(owner: Identity, caller: Identity) -> Self {
19        Self { owner, caller }
20    }
21    /// For when the owner == caller
22    pub fn for_current(owner: Identity) -> Self {
23        Self { owner, caller: owner }
24    }
25    /// Does `owner == caller`
26    pub fn is_owner(&self) -> bool {
27        self.owner == self.caller
28    }
29    /// WARNING: Use this only for simple test were the `auth` don't matter
30    pub fn for_testing() -> Self {
31        AuthCtx {
32            owner: Identity::__dummy(),
33            caller: Identity::__dummy(),
34        }
35    }
36}
37
38/// An `Identity` for something interacting with the database.
39///
40/// <!-- TODO: docs for OpenID stuff. -->
41///
42/// An `Identity` is a 256-bit unsigned integer. These are encoded in various ways.
43/// - In JSON, an `Identity` is represented as a hexadecimal number wrapped in a string, `"0x[64 hex characters]"`.
44/// - In BSATN, an `Identity` is represented as a LITTLE-ENDIAN number 32 bytes long.
45/// - In memory, an `Identity` is stored as a 256-bit number with the endianness of the host system.
46///
47/// If you are manually converting a hexadecimal string to a byte array like so:
48/// ```ignore
49/// "0xb0b1b2..."
50/// ->
51/// [0xb0, 0xb1, 0xb2, ...]
52/// ```
53/// Make sure you call `Identity::from_be_byte_array` and NOT `Identity::from_byte_array`.
54/// The standard way of writing hexadecimal numbers follows a big-endian convention, if you
55/// index the characters in written text in increasing order from left to right.
56#[derive(Default, Eq, PartialEq, PartialOrd, Ord, Clone, Copy, Hash, Serialize, Deserialize)]
57pub struct Identity {
58    __identity__: u256,
59}
60
61impl_st!([] Identity, AlgebraicType::identity());
62
63#[cfg(feature = "metrics_impls")]
64impl spacetimedb_metrics::typed_prometheus::AsPrometheusLabel for Identity {
65    fn as_prometheus_str(&self) -> impl AsRef<str> + '_ {
66        self.to_hex()
67    }
68}
69
70impl Identity {
71    /// The 0x0 `Identity`
72    pub const ZERO: Self = Self::from_u256(u256::ZERO);
73
74    /// The 0x1 `Identity`
75    pub const ONE: Self = Self::from_u256(u256::ONE);
76
77    /// Create an `Identity` from a LITTLE-ENDIAN byte array.
78    ///
79    /// If you are parsing an `Identity` from a string, you probably want `from_be_byte_array` instead.
80    pub const fn from_byte_array(bytes: [u8; 32]) -> Self {
81        // SAFETY: The transmute is an implementation of `u256::from_le_bytes`,
82        // but works in a const context.
83        Self::from_u256(u256::from_le(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
84    }
85
86    /// Create an `Identity` from a BIG-ENDIAN byte array.
87    ///
88    /// This method is the correct choice if you have converted the bytes of a hexadecimal-formatted `Identity`
89    /// to a byte array in the following way:
90    /// ```ignore
91    /// "0xb0b1b2..."
92    /// ->
93    /// [0xb0, 0xb1, 0xb2, ...]
94    /// ```
95    pub const fn from_be_byte_array(bytes: [u8; 32]) -> Self {
96        // SAFETY: The transmute is an implementation of `u256::from_be_bytes`,
97        // but works in a const context.
98        Self::from_u256(u256::from_be(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
99    }
100
101    /// Converts `__identity__: u256` to `Identity`.
102    pub const fn from_u256(__identity__: u256) -> Self {
103        Self { __identity__ }
104    }
105
106    /// Converts this identity to an `u256`.
107    pub const fn to_u256(&self) -> u256 {
108        self.__identity__
109    }
110
111    #[doc(hidden)]
112    pub fn __dummy() -> Self {
113        Self::ZERO
114    }
115
116    pub fn from_claims(issuer: &str, subject: &str) -> Self {
117        let input = format!("{}|{}", issuer, subject);
118        let first_hash = blake3::hash(input.as_bytes());
119        let id_hash = &first_hash.as_bytes()[..26];
120        let mut checksum_input = [0u8; 28];
121        // TODO: double check this gets the right number...
122        checksum_input[2..].copy_from_slice(id_hash);
123        checksum_input[0] = 0xc2;
124        checksum_input[1] = 0x00;
125        let checksum_hash = &blake3::hash(&checksum_input);
126
127        let mut final_bytes = [0u8; 32];
128        final_bytes[0] = 0xc2;
129        final_bytes[1] = 0x00;
130        final_bytes[2..6].copy_from_slice(&checksum_hash.as_bytes()[..4]);
131        final_bytes[6..].copy_from_slice(id_hash);
132
133        // We want the leading two bytes of the Identity to be `c200` when formatted.
134        // This means that these should be the MOST significant bytes.
135        // This corresponds to a BIG-ENDIAN byte order of our buffer above.
136        Identity::from_be_byte_array(final_bytes)
137    }
138
139    /// Returns this `Identity` as a byte array.
140    pub fn to_byte_array(&self) -> [u8; 32] {
141        self.__identity__.to_le_bytes()
142    }
143
144    /// Convert this `Identity` to a BIG-ENDIAN byte array.
145    pub fn to_be_byte_array(&self) -> [u8; 32] {
146        self.__identity__.to_be_bytes()
147    }
148
149    /// Convert this `Identity` to a hexadecimal string.
150    pub fn to_hex(&self) -> HexString<32> {
151        spacetimedb_sats::hex::encode(&self.to_be_byte_array())
152    }
153
154    /// Extract the first 8 bytes of this `Identity` as if it was stored in BIG-ENDIAN
155    /// format. (That is, the most significant bytes.)
156    pub fn abbreviate(&self) -> [u8; 8] {
157        self.to_be_byte_array()[..8].try_into().unwrap()
158    }
159
160    /// Extract the first 16 characters of this `Identity`'s hexadecimal representation.
161    pub fn to_abbreviated_hex(&self) -> HexString<8> {
162        spacetimedb_sats::hex::encode(&self.abbreviate())
163    }
164
165    pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, hex::FromHexError> {
166        hex::FromHex::from_hex(hex)
167    }
168}
169
170impl fmt::Display for Identity {
171    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172        f.pad(&self.to_hex())
173    }
174}
175
176impl fmt::Debug for Identity {
177    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178        f.debug_tuple("Identity").field(&self.to_hex()).finish()
179    }
180}
181
182impl hex::FromHex for Identity {
183    type Error = hex::FromHexError;
184
185    fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
186        from_hex_pad(hex).map(Identity::from_be_byte_array)
187    }
188}
189
190impl FromStr for Identity {
191    type Err = <Self as hex::FromHex>::Error;
192
193    fn from_str(s: &str) -> Result<Self, Self::Err> {
194        Self::from_hex(s)
195    }
196}
197
198impl From<Identity> for AlgebraicValue {
199    fn from(value: Identity) -> Self {
200        AlgebraicValue::product([value.to_u256().into()])
201    }
202}
203
204#[cfg(feature = "serde")]
205impl serde::Serialize for Identity {
206    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
207        spacetimedb_sats::ser::serde::serialize_to(&self.to_be_byte_array(), serializer)
208    }
209}
210
211#[cfg(feature = "serde")]
212impl<'de> serde::Deserialize<'de> for Identity {
213    fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
214        let arr = spacetimedb_sats::de::serde::deserialize_from(deserializer)?;
215        Ok(Identity::from_be_byte_array(arr))
216    }
217}
218
219#[cfg(test)]
220mod tests {
221    use super::*;
222    use proptest::prelude::*;
223    use proptest::string::string_regex;
224    use spacetimedb_sats::{de::serde::DeserializeWrapper, ser::serde::SerializeWrapper, GroundSpacetimeType as _};
225
226    #[test]
227    fn identity_is_special() {
228        assert!(Identity::get_type().is_special());
229    }
230
231    #[test]
232    fn identity_json_serialization_big_endian() {
233        let id = Identity::from_be_byte_array([
234            0xff, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27,
235            28, 29, 30, 31,
236        ]);
237
238        let hex = id.to_hex();
239        assert!(
240            hex.as_str().starts_with("ff01"),
241            "expected {hex:?} to start with \"ff01\""
242        );
243
244        let json1 = serde_json::to_string(&id).unwrap();
245        let json2 = serde_json::to_string(SerializeWrapper::from_ref(&id)).unwrap();
246
247        assert!(
248            json1.contains(hex.as_str()),
249            "expected {json1} to contain {hex} but it didn't"
250        );
251        assert!(
252            json2.contains(hex.as_str()),
253            "expected {json2} to contain {hex} but it didn't"
254        );
255    }
256
257    /// Make sure the checksum is valid.
258    fn validate_checksum(id: &[u8; 32]) -> bool {
259        let checksum_input = &id[6..];
260        let mut checksum_input_with_prefix = [0u8; 28];
261        checksum_input_with_prefix[2..].copy_from_slice(checksum_input);
262        checksum_input_with_prefix[0] = 0xc2;
263        checksum_input_with_prefix[1] = 0x00;
264        let checksum_hash = &blake3::hash(&checksum_input_with_prefix);
265        checksum_hash.as_bytes()[0..4] == id[2..6]
266    }
267
268    proptest! {
269        #[test]
270        fn identity_conversions(w0: u128, w1: u128) {
271            let v = Identity::from_u256(u256::from_words(w0, w1));
272
273            prop_assert_eq!(Identity::from_byte_array(v.to_byte_array()), v);
274            prop_assert_eq!(Identity::from_be_byte_array(v.to_be_byte_array()), v);
275            prop_assert_eq!(Identity::from_hex(v.to_hex()).unwrap(), v);
276
277            let de1: Identity = serde_json::from_str(&serde_json::to_string(&v).unwrap()).unwrap();
278            prop_assert_eq!(de1, v);
279            let DeserializeWrapper(de2): DeserializeWrapper<Identity> = serde_json::from_str(&serde_json::to_string(SerializeWrapper::from_ref(&v)).unwrap()).unwrap();
280            prop_assert_eq!(de2, v);
281        }
282
283        #[test]
284        fn from_claims_formats_correctly(s1 in string_regex(r".{3,5}").unwrap(), s2 in string_regex(r".{3,5}").unwrap()) {
285            let id = Identity::from_claims(&s1, &s2);
286            prop_assert!(id.to_hex().starts_with("c200"));
287            prop_assert!(validate_checksum(&id.to_be_byte_array()));
288        }
289    }
290}