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