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 {
123 let input = format!("{issuer}|{subject}");
124 let first_hash = blake3::hash(input.as_bytes());
125 let id_hash = &first_hash.as_bytes()[..26];
126 let mut checksum_input = [0u8; 28];
127 checksum_input[2..].copy_from_slice(id_hash);
129 checksum_input[0] = 0xc2;
130 checksum_input[1] = 0x00;
131 let checksum_hash = &blake3::hash(&checksum_input);
132
133 let mut final_bytes = [0u8; 32];
134 final_bytes[0] = 0xc2;
135 final_bytes[1] = 0x00;
136 final_bytes[2..6].copy_from_slice(&checksum_hash.as_bytes()[..4]);
137 final_bytes[6..].copy_from_slice(id_hash);
138
139 Identity::from_be_byte_array(final_bytes)
143 }
144
145 pub fn to_byte_array(&self) -> [u8; 32] {
147 self.__identity__.to_le_bytes()
148 }
149
150 pub fn to_be_byte_array(&self) -> [u8; 32] {
152 self.__identity__.to_be_bytes()
153 }
154
155 pub fn to_hex(&self) -> HexString<32> {
157 spacetimedb_sats::hex::encode(&self.to_be_byte_array())
158 }
159
160 pub fn abbreviate(&self) -> [u8; 8] {
163 self.to_be_byte_array()[..8].try_into().unwrap()
164 }
165
166 pub fn to_abbreviated_hex(&self) -> HexString<8> {
168 spacetimedb_sats::hex::encode(&self.abbreviate())
169 }
170
171 pub fn from_hex(hex: impl AsRef<[u8]>) -> Result<Self, hex::FromHexError> {
172 hex::FromHex::from_hex(hex)
173 }
174}
175
176impl fmt::Display for Identity {
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 f.pad(&self.to_hex())
179 }
180}
181
182impl fmt::Debug for Identity {
183 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184 f.debug_tuple("Identity").field(&self.to_hex()).finish()
185 }
186}
187
188impl hex::FromHex for Identity {
189 type Error = hex::FromHexError;
190
191 fn from_hex<T: AsRef<[u8]>>(hex: T) -> Result<Self, Self::Error> {
192 from_hex_pad(hex).map(Identity::from_be_byte_array)
193 }
194}
195
196impl FromStr for Identity {
197 type Err = <Self as hex::FromHex>::Error;
198
199 fn from_str(s: &str) -> Result<Self, Self::Err> {
200 Self::from_hex(s)
201 }
202}
203
204impl From<Identity> for AlgebraicValue {
205 fn from(value: Identity) -> Self {
206 AlgebraicValue::product([value.to_u256().into()])
207 }
208}
209
210#[cfg(feature = "serde")]
211impl serde::Serialize for Identity {
212 fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
213 spacetimedb_sats::ser::serde::serialize_to(&self.to_be_byte_array(), serializer)
214 }
215}
216
217#[cfg(feature = "serde")]
218impl<'de> serde::Deserialize<'de> for Identity {
219 fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
220 let arr = spacetimedb_sats::de::serde::deserialize_from(deserializer)?;
221 Ok(Identity::from_be_byte_array(arr))
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use proptest::prelude::*;
229 use proptest::string::string_regex;
230 use spacetimedb_sats::{de::serde::DeserializeWrapper, ser::serde::SerializeWrapper, GroundSpacetimeType as _};
231
232 #[test]
233 fn identity_is_special() {
234 assert!(Identity::get_type().is_special());
235 }
236
237 #[test]
238 fn identity_json_serialization_big_endian() {
239 let id = Identity::from_be_byte_array([
240 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,
241 28, 29, 30, 31,
242 ]);
243
244 let hex = id.to_hex();
245 assert!(
246 hex.as_str().starts_with("ff01"),
247 "expected {hex:?} to start with \"ff01\""
248 );
249
250 let json1 = serde_json::to_string(&id).unwrap();
251 let json2 = serde_json::to_string(SerializeWrapper::from_ref(&id)).unwrap();
252
253 assert!(
254 json1.contains(hex.as_str()),
255 "expected {json1} to contain {hex} but it didn't"
256 );
257 assert!(
258 json2.contains(hex.as_str()),
259 "expected {json2} to contain {hex} but it didn't"
260 );
261 }
262
263 fn validate_checksum(id: &[u8; 32]) -> bool {
265 let checksum_input = &id[6..];
266 let mut checksum_input_with_prefix = [0u8; 28];
267 checksum_input_with_prefix[2..].copy_from_slice(checksum_input);
268 checksum_input_with_prefix[0] = 0xc2;
269 checksum_input_with_prefix[1] = 0x00;
270 let checksum_hash = &blake3::hash(&checksum_input_with_prefix);
271 checksum_hash.as_bytes()[0..4] == id[2..6]
272 }
273
274 proptest! {
275 #[test]
276 fn identity_conversions(w0: u128, w1: u128) {
277 let v = Identity::from_u256(u256::from_words(w0, w1));
278
279 prop_assert_eq!(Identity::from_byte_array(v.to_byte_array()), v);
280 prop_assert_eq!(Identity::from_be_byte_array(v.to_be_byte_array()), v);
281 prop_assert_eq!(Identity::from_hex(v.to_hex()).unwrap(), v);
282
283 let de1: Identity = serde_json::from_str(&serde_json::to_string(&v).unwrap()).unwrap();
284 prop_assert_eq!(de1, v);
285 let DeserializeWrapper(de2): DeserializeWrapper<Identity> = serde_json::from_str(&serde_json::to_string(SerializeWrapper::from_ref(&v)).unwrap()).unwrap();
286 prop_assert_eq!(de2, v);
287 }
288
289 #[test]
290 fn from_claims_formats_correctly(s1 in string_regex(r".{3,5}").unwrap(), s2 in string_regex(r".{3,5}").unwrap()) {
291 let id = Identity::from_claims(&s1, &s2);
292 prop_assert!(id.to_hex().starts_with("c200"));
293 prop_assert!(validate_checksum(&id.to_be_byte_array()));
294 }
295 }
296}