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 = "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 pub const ZERO: Self = Self::from_u256(u256::ZERO);
73
74 pub const ONE: Self = Self::from_u256(u256::ONE);
76
77 pub const fn from_byte_array(bytes: [u8; 32]) -> Self {
81 Self::from_u256(u256::from_le(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
84 }
85
86 pub const fn from_be_byte_array(bytes: [u8; 32]) -> Self {
96 Self::from_u256(u256::from_be(unsafe { mem::transmute::<[u8; 32], u256>(bytes) }))
99 }
100
101 pub const fn from_u256(__identity__: u256) -> Self {
103 Self { __identity__ }
104 }
105
106 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 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 Identity::from_be_byte_array(final_bytes)
137 }
138
139 pub fn to_byte_array(&self) -> [u8; 32] {
141 self.__identity__.to_le_bytes()
142 }
143
144 pub fn to_be_byte_array(&self) -> [u8; 32] {
146 self.__identity__.to_be_bytes()
147 }
148
149 pub fn to_hex(&self) -> HexString<32> {
151 spacetimedb_sats::hex::encode(&self.to_be_byte_array())
152 }
153
154 pub fn abbreviate(&self) -> [u8; 8] {
157 self.to_be_byte_array()[..8].try_into().unwrap()
158 }
159
160 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 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}