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