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 pub fn for_current(owner: Identity) -> Self {
23 Self { owner, caller: owner }
24 }
25 pub fn is_owner(&self) -> bool {
27 self.owner == self.caller
28 }
29 pub fn for_testing() -> Self {
31 AuthCtx {
32 owner: Identity::__dummy(),
33 caller: Identity::__dummy(),
34 }
35 }
36}
37
38#[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 = "memory-usage")]
64impl spacetimedb_memory_usage::MemoryUsage for Identity {}
65
66#[cfg(feature = "metrics_impls")]
67impl spacetimedb_metrics::typed_prometheus::AsPrometheusLabel for Identity {
68 fn as_prometheus_str(&self) -> impl AsRef<str> + '_ {
69 self.to_hex()
70 }
71}
72
73impl Identity {
74 pub const ZERO: Self = Self::from_u256(u256::ZERO);
76
77 pub const ONE: Self = Self::from_u256(u256::ONE);
79
80 pub const fn from_byte_array(bytes: [u8; 32]) -> Self {
84 Self::from_u256(u256::from_le(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
87 }
88
89 pub const fn from_be_byte_array(bytes: [u8; 32]) -> Self {
99 Self::from_u256(u256::from_be(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
102 }
103
104 pub const fn from_u256(__identity__: u256) -> Self {
106 Self { __identity__ }
107 }
108
109 pub const fn to_u256(&self) -> u256 {
111 self.__identity__
112 }
113
114 #[doc(hidden)]
115 pub fn __dummy() -> Self {
116 Self::ZERO
117 }
118
119 pub fn from_claims(issuer: &str, subject: &str) -> Self {
120 let input = format!("{issuer}|{subject}");
121 let first_hash = blake3::hash(input.as_bytes());
122 let id_hash = &first_hash.as_bytes()[..26];
123 let mut checksum_input = [0u8; 28];
124 checksum_input[2..].copy_from_slice(id_hash);
126 checksum_input[0] = 0xc2;
127 checksum_input[1] = 0x00;
128 let checksum_hash = &blake3::hash(&checksum_input);
129
130 let mut final_bytes = [0u8; 32];
131 final_bytes[0] = 0xc2;
132 final_bytes[1] = 0x00;
133 final_bytes[2..6].copy_from_slice(&checksum_hash.as_bytes()[..4]);
134 final_bytes[6..].copy_from_slice(id_hash);
135
136 Identity::from_be_byte_array(final_bytes)
140 }
141
142 pub fn to_byte_array(&self) -> [u8; 32] {
144 self.__identity__.to_le_bytes()
145 }
146
147 pub fn to_be_byte_array(&self) -> [u8; 32] {
149 self.__identity__.to_be_bytes()
150 }
151
152 pub fn to_hex(&self) -> HexString<32> {
154 spacetimedb_sats::hex::encode(&self.to_be_byte_array())
155 }
156
157 pub fn abbreviate(&self) -> [u8; 8] {
160 self.to_be_byte_array()[..8].try_into().unwrap()
161 }
162
163 pub fn to_abbreviated_hex(&self) -> HexString<8> {
165 spacetimedb_sats::hex::encode(&self.abbreviate())
166 }
167
168 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, hex::FromHexError> {
169 hex::FromHex::from_hex(hex)
170 }
171}
172
173impl fmt::Display for Identity {
174 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
175 f.pad(&self.to_hex())
176 }
177}
178
179impl fmt::Debug for Identity {
180 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
181 f.debug_tuple("Identity").field(&self.to_hex()).finish()
182 }
183}
184
185impl hex::FromHex for Identity {
186 type Error = hex::FromHexError;
187
188 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
189 from_hex_pad(hex).map(Identity::from_be_byte_array)
190 }
191}
192
193impl FromStr for Identity {
194 type Err = <Self as hex::FromHex>::Error;
195
196 fn from_str(s: &str) -> Result<Self, Self::Err> {
197 Self::from_hex(s)
198 }
199}
200
201impl From<Identity> for AlgebraicValue {
202 fn from(value: Identity) -> Self {
203 AlgebraicValue::product([value.to_u256().into()])
204 }
205}
206
207#[cfg(feature = "serde")]
208impl serde::Serialize for Identity {
209 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
210 spacetimedb_sats::ser::serde::serialize_to(&self.to_be_byte_array(), serializer)
211 }
212}
213
214#[cfg(feature = "serde")]
215impl<'de> serde::Deserialize<'de> for Identity {
216 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
217 let arr = spacetimedb_sats::de::serde::deserialize_from(deserializer)?;
218 Ok(Identity::from_be_byte_array(arr))
219 }
220}
221
222#[cfg(test)]
223mod tests {
224 use super::*;
225 use proptest::prelude::*;
226 use proptest::string::string_regex;
227 use spacetimedb_sats::{de::serde::DeserializeWrapper, ser::serde::SerializeWrapper, GroundSpacetimeType as _};
228
229 #[test]
230 fn identity_is_special() {
231 assert!(Identity::get_type().is_special());
232 }
233
234 #[test]
235 fn identity_json_serialization_big_endian() {
236 let id = Identity::from_be_byte_array([
237 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,
238 28, 29, 30, 31,
239 ]);
240
241 let hex = id.to_hex();
242 assert!(
243 hex.as_str().starts_with("ff01"),
244 "expected {hex:?} to start with \"ff01\""
245 );
246
247 let json1 = serde_json::to_string(&id).unwrap();
248 let json2 = serde_json::to_string(SerializeWrapper::from_ref(&id)).unwrap();
249
250 assert!(
251 json1.contains(hex.as_str()),
252 "expected {json1} to contain {hex} but it didn't"
253 );
254 assert!(
255 json2.contains(hex.as_str()),
256 "expected {json2} to contain {hex} but it didn't"
257 );
258 }
259
260 fn validate_checksum(id: &[u8; 32]) -> bool {
262 let checksum_input = &id[6..];
263 let mut checksum_input_with_prefix = [0u8; 28];
264 checksum_input_with_prefix[2..].copy_from_slice(checksum_input);
265 checksum_input_with_prefix[0] = 0xc2;
266 checksum_input_with_prefix[1] = 0x00;
267 let checksum_hash = &blake3::hash(&checksum_input_with_prefix);
268 checksum_hash.as_bytes()[0..4] == id[2..6]
269 }
270
271 proptest! {
272 #[test]
273 fn identity_conversions(w0: u128, w1: u128) {
274 let v = Identity::from_u256(u256::from_words(w0, w1));
275
276 prop_assert_eq!(Identity::from_byte_array(v.to_byte_array()), v);
277 prop_assert_eq!(Identity::from_be_byte_array(v.to_be_byte_array()), v);
278 prop_assert_eq!(Identity::from_hex(v.to_hex()).unwrap(), v);
279
280 let de1: Identity = serde_json::from_str(&serde_json::to_string(&v).unwrap()).unwrap();
281 prop_assert_eq!(de1, v);
282 let DeserializeWrapper(de2): DeserializeWrapper<Identity> = serde_json::from_str(&serde_json::to_string(SerializeWrapper::from_ref(&v)).unwrap()).unwrap();
283 prop_assert_eq!(de2, v);
284 }
285
286 #[test]
287 fn from_claims_formats_correctly(s1 in string_regex(r".{3,5}").unwrap(), s2 in string_regex(r".{3,5}").unwrap()) {
288 let id = Identity::from_claims(&s1, &s2);
289 prop_assert!(id.to_hex().starts_with("c200"));
290 prop_assert!(validate_checksum(&id.to_be_byte_array()));
291 }
292 }
293}