Skip to main content

calimero_primitives/
identity.rs

1use core::fmt;
2use core::ops::Deref;
3use core::str::FromStr;
4
5#[cfg(feature = "rand")]
6use rand::{CryptoRng, RngCore};
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9use zeroize::Zeroize;
10
11use crate::context::ContextId;
12use crate::hash::{Hash, HashError};
13
14use ed25519_dalek::{Signature, SignatureError, Signer, SigningKey, Verifier, VerifyingKey};
15
16#[cfg_attr(
17    feature = "borsh",
18    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
19)]
20pub struct PrivateKey(Hash);
21
22impl fmt::Debug for PrivateKey {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        f.pad("PrivateKey")
25    }
26}
27
28impl Drop for PrivateKey {
29    fn drop(&mut self) {
30        // Zeroize the key material to prevent it from remaining in memory.
31        //
32        // SAFETY:
33        // - The pointer is valid and properly aligned because it comes from a valid
34        //   mutable reference (`&mut self.0`).
35        // - The size is correct as we use `size_of::<Hash>()` on the actual type.
36        // - We have exclusive access to this memory via `&mut self`.
37        // - Hash doesn't expose `DerefMut` or implement `Zeroize`, so we use pointer
38        //   casting to get a mutable byte slice over the entire structure.
39        unsafe {
40            let hash_ptr = &mut self.0 as *mut Hash as *mut u8;
41            let hash_size = core::mem::size_of::<Hash>();
42            core::slice::from_raw_parts_mut(hash_ptr, hash_size).zeroize();
43        }
44    }
45}
46
47impl From<[u8; 32]> for PrivateKey {
48    fn from(id: [u8; 32]) -> Self {
49        Self(id.into())
50    }
51}
52
53impl AsRef<[u8; 32]> for PrivateKey {
54    fn as_ref(&self) -> &[u8; 32] {
55        &self.0
56    }
57}
58
59impl Deref for PrivateKey {
60    type Target = [u8; 32];
61
62    fn deref(&self) -> &Self::Target {
63        &self.0
64    }
65}
66
67impl PrivateKey {
68    #[must_use]
69    pub fn public_key(&self) -> PublicKey {
70        SigningKey::from_bytes(self)
71            .verifying_key()
72            .to_bytes()
73            .into()
74    }
75
76    #[cfg(feature = "rand")]
77    pub fn random<R: CryptoRng + RngCore>(csprng: &mut R) -> Self {
78        let mut secret = [0; 32];
79
80        csprng.fill_bytes(&mut secret);
81
82        Self::from(secret)
83    }
84
85    pub fn sign(&self, message: &[u8]) -> Result<Signature, SignatureError> {
86        SigningKey::from_bytes(self).try_sign(message)
87    }
88}
89
90#[derive(Eq, Ord, Copy, Clone, Debug, PartialEq, PartialOrd, Serialize, Deserialize)]
91#[cfg_attr(
92    feature = "borsh",
93    derive(borsh::BorshDeserialize, borsh::BorshSerialize)
94)]
95pub struct PublicKey(Hash);
96
97impl From<[u8; 32]> for PublicKey {
98    fn from(id: [u8; 32]) -> Self {
99        Self(id.into())
100    }
101}
102
103impl AsRef<[u8; 32]> for PublicKey {
104    fn as_ref(&self) -> &[u8; 32] {
105        &self.0
106    }
107}
108
109impl AsRef<[u8]> for PublicKey {
110    fn as_ref(&self) -> &[u8] {
111        self.0.as_ref() // self.0 is a Hash, which is [u8; 32], which can be AsRef'd to &[u8]
112    }
113}
114
115impl Deref for PublicKey {
116    type Target = [u8; 32];
117
118    fn deref(&self) -> &Self::Target {
119        &self.0
120    }
121}
122
123impl PublicKey {
124    #[must_use]
125    pub fn as_str(&self) -> &str {
126        self.0.as_str()
127    }
128
129    /// Verify a signature against this public key.
130    pub fn verify(&self, message: &[u8], signature: &Signature) -> Result<(), SignatureError> {
131        VerifyingKey::from_bytes(self.as_ref())?.verify(message, signature)
132    }
133
134    /// Verify a signature passed as a raw bytes against this public key.
135    pub fn verify_raw_signature(
136        &self,
137        message: &[u8],
138        signature_bytes: &[u8; 64],
139    ) -> Result<(), SignatureError> {
140        let signature = Signature::from_bytes(&signature_bytes);
141        self.verify(message, &signature)
142    }
143
144    // Return represented as a 32-byte array
145    pub fn digest(&self) -> &[u8; 32] {
146        &self.0
147    }
148}
149
150impl fmt::Display for PublicKey {
151    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
152        f.pad(self.as_str())
153    }
154}
155
156impl From<PublicKey> for String {
157    fn from(id: PublicKey) -> Self {
158        id.as_str().to_owned()
159    }
160}
161
162impl From<&PublicKey> for String {
163    fn from(id: &PublicKey) -> Self {
164        id.as_str().to_owned()
165    }
166}
167
168#[derive(Clone, Copy, Debug, Error)]
169#[error(transparent)]
170pub struct InvalidPublicKey(HashError);
171
172impl FromStr for PublicKey {
173    type Err = InvalidPublicKey;
174
175    fn from_str(s: &str) -> Result<Self, Self::Err> {
176        Ok(Self(s.parse().map_err(InvalidPublicKey)?))
177    }
178}
179
180#[derive(Clone, Debug, Deserialize, Serialize)]
181#[non_exhaustive]
182pub struct Did {
183    pub id: String,
184    pub root_keys: Vec<RootKey>,
185    pub client_keys: Vec<ClientKey>,
186}
187
188impl Did {
189    #[must_use]
190    pub const fn new(id: String, root_keys: Vec<RootKey>, client_keys: Vec<ClientKey>) -> Self {
191        Self {
192            id,
193            root_keys,
194            client_keys,
195        }
196    }
197}
198
199#[derive(Clone, Debug, Deserialize, Serialize)]
200#[non_exhaustive]
201pub struct RootKey {
202    pub signing_key: String,
203    #[serde(rename = "wallet")]
204    pub wallet_type: WalletType,
205    pub wallet_address: String,
206    pub created_at: u64,
207}
208
209impl RootKey {
210    #[must_use]
211    pub const fn new(
212        signing_key: String,
213        wallet_type: WalletType,
214        wallet_address: String,
215        created_at: u64,
216    ) -> Self {
217        Self {
218            signing_key,
219            wallet_type,
220            wallet_address,
221            created_at,
222        }
223    }
224}
225
226#[derive(Clone, Debug, Deserialize, Serialize)]
227#[serde(rename_all = "camelCase")]
228#[non_exhaustive]
229pub struct ClientKey {
230    #[serde(rename = "wallet")]
231    pub wallet_type: WalletType,
232    pub signing_key: String,
233    pub created_at: u64,
234    pub context_id: Option<ContextId>,
235}
236
237impl ClientKey {
238    #[must_use]
239    pub const fn new(
240        wallet_type: WalletType,
241        signing_key: String,
242        created_at: u64,
243        context_id: Option<ContextId>,
244    ) -> Self {
245        Self {
246            wallet_type,
247            signing_key,
248            created_at,
249            context_id,
250        }
251    }
252}
253
254#[derive(Clone, Debug, Deserialize, Serialize)]
255#[serde(rename_all = "camelCase")]
256#[non_exhaustive]
257pub struct ContextUser {
258    pub user_id: String,
259    pub joined_at: u64,
260}
261
262#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
263#[serde(rename_all = "UPPERCASE")]
264#[serde(tag = "type")]
265#[non_exhaustive]
266pub enum WalletType {
267    NEAR {
268        #[serde(rename = "networkId")]
269        network_id: NearNetworkId,
270    },
271}
272
273#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
274#[serde(rename_all = "lowercase")]
275#[non_exhaustive]
276pub enum NearNetworkId {
277    Mainnet,
278    Testnet,
279    #[serde(untagged)]
280    Custom(String),
281}
282
283#[cfg(test)]
284mod tests {
285    use core::mem::ManuallyDrop;
286
287    use super::*;
288
289    #[test]
290    fn test_private_key_zeroize_on_drop() {
291        // Create a non-zero key wrapped in ManuallyDrop to control when drop occurs
292        let secret_bytes: [u8; 32] = [0x42; 32];
293        let mut key = ManuallyDrop::new(PrivateKey::from(secret_bytes));
294
295        // Verify the key contains the expected bytes before drop
296        assert_eq!(key.as_ref(), &secret_bytes);
297
298        // Get a raw pointer to the key's memory location before dropping
299        let key_ptr = &*key as *const PrivateKey as *const u8;
300        let hash_size = core::mem::size_of::<Hash>();
301
302        // Manually drop the key, which will call our Drop implementation.
303        // SAFETY: The key was created with ManuallyDrop::new, so we need to
304        // manually drop it. After this, the ManuallyDrop wrapper prevents
305        // double-drop.
306        unsafe {
307            ManuallyDrop::drop(&mut key);
308        }
309
310        // NOTE: Reading memory after drop is technically undefined behavior in Rust's
311        // memory model, even though the stack memory is still allocated. We accept
312        // this UB in a test-only context to verify the security property that
313        // sensitive key material is zeroized. The ManuallyDrop wrapper ensures
314        // the stack memory hasn't been reused yet.
315        //
316        // SAFETY: We're reading stack memory that was just zeroized. While this is
317        // technically UB (the value has been invalidated by drop), it's acceptable
318        // here for verifying the security-critical zeroization behavior.
319        let zeroed = unsafe { core::slice::from_raw_parts(key_ptr, hash_size) };
320
321        // Check that the entire Hash structure is zeroed, not just the key bytes
322        assert!(
323            zeroed.iter().all(|&b| b == 0),
324            "Key material was not properly zeroized on drop"
325        );
326    }
327
328    #[test]
329    fn test_private_key_can_sign_before_drop() {
330        // Ensure PrivateKey still works correctly with the Drop implementation
331        let secret_bytes: [u8; 32] = [0x42; 32];
332        let key = PrivateKey::from(secret_bytes);
333
334        // Key should be usable for signing
335        let message = b"test message";
336        let signature = key.sign(message);
337        assert!(signature.is_ok());
338
339        // Key should be usable for deriving public key
340        let public_key = key.public_key();
341        assert!(!AsRef::<[u8; 32]>::as_ref(&public_key)
342            .iter()
343            .all(|&b| b == 0));
344
345        // Signature should verify with the public key
346        let sig = signature.unwrap();
347        assert!(public_key.verify(message, &sig).is_ok());
348    }
349}