mixin_sdk_rs/
safe.rs

1use crate::{
2    auth,
3    error::Error,
4    request::{ApiResponse, DEFAULT_API_HOST, DEFAULT_USER_AGENT, HTTP_CLIENT},
5};
6use reqwest::header::{AUTHORIZATION, CONTENT_TYPE, USER_AGENT};
7use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Serialize, Deserialize)]
10pub struct SafeUser {
11    #[serde(rename = "app_id")]
12    pub user_id: String,
13    #[serde(rename = "session_id")]
14    pub session_id: String,
15    #[serde(rename = "session_private_key")]
16    pub session_private_key: String,
17    #[serde(rename = "server_public_key")]
18    pub server_public_key: String,
19    #[serde(rename = "spend_private_key")]
20    pub spend_private_key: String,
21    #[serde(skip)]
22    pub is_spend_private_sum: bool,
23}
24
25#[derive(Debug, Serialize, Deserialize)]
26pub struct GhostKeys {
27    #[serde(rename = "type")]
28    pub key_type: String,
29    pub mask: String,
30    pub keys: Vec<String>,
31}
32
33#[derive(Debug, Serialize, Deserialize)]
34pub struct GhostKeyRequest {
35    pub receivers: Vec<String>,
36    pub index: u32,
37    pub hint: String,
38}
39
40impl SafeUser {
41    pub fn new(
42        user_id: String,
43        session_id: String,
44        session_private_key: String,
45        server_public_key: String,
46        spend_private_key: String,
47    ) -> Self {
48        Self {
49            user_id,
50            session_id,
51            session_private_key,
52            server_public_key,
53            spend_private_key,
54            is_spend_private_sum: false,
55        }
56    }
57
58    pub fn new_from_file(path: &str) -> Result<Self, Error> {
59        let file = std::fs::read(path).unwrap();
60        let safe_user: SafeUser = serde_json::from_slice(&file).unwrap();
61        Ok(safe_user)
62    }
63
64    pub fn new_from_env() -> Result<Self, Error> {
65        Self::new_from_env_str("")
66    }
67
68    pub fn new_from_env_str(env: &str) -> Result<Self, Error> {
69        let env = if env.is_empty() {
70            "TEST_KEYSTORE_PATH"
71        } else {
72            env
73        };
74        let env = std::env::var(env).unwrap();
75        let path = std::path::Path::new(&env);
76        Self::new_from_file(path.to_str().unwrap())
77    }
78}
79
80impl GhostKeys {
81    pub fn keys_slice(&self) -> Vec<crypto::Key> {
82        self.keys
83            .iter()
84            .map(|k| crypto::Key::from_string(k).expect("Invalid key format"))
85            .collect()
86    }
87}
88
89pub async fn request_safe_ghost_keys(
90    gkr: &[GhostKeyRequest],
91    user: &SafeUser,
92) -> Result<Vec<GhostKeys>, Error> {
93    let data = serde_json::to_vec(gkr)?;
94    let path = "/safe/keys";
95
96    let token = auth::sign_authentication_token(
97        reqwest::Method::POST.as_str(),
98        path,
99        &String::from_utf8_lossy(&data),
100        user,
101    )?;
102
103    let response = HTTP_CLIENT
104        .post(DEFAULT_API_HOST.to_string() + path)
105        .header(AUTHORIZATION, format!("Bearer {}", token))
106        .header(CONTENT_TYPE, "application/json")
107        .header(USER_AGENT, DEFAULT_USER_AGENT)
108        .json(&gkr)
109        .send()
110        .await?;
111
112    let body = response.bytes().await?;
113
114    let parsed: ApiResponse<Vec<GhostKeys>> = serde_json::from_slice(&body)?;
115
116    if let Some(api_error) = parsed.error {
117        return Err(Error::Api(api_error));
118    }
119
120    parsed
121        .data
122        .ok_or_else(|| Error::DataNotFound("API response did not contain user data".to_string()))
123}
124
125#[derive(Debug, Deserialize)]
126pub struct OAuthError {
127    pub code: i32,
128    pub description: String,
129}
130
131pub mod crypto {
132    use serde::{Deserialize, Serialize};
133
134    #[derive(Debug, Clone, Serialize, Deserialize)]
135    pub struct Key {
136        pub value: String,
137    }
138
139    impl Key {
140        pub fn from_string(s: &str) -> Result<Self, Box<dyn std::error::Error>> {
141            Ok(Self {
142                value: s.to_string(),
143            })
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150    use std::env;
151
152    use super::*;
153
154    #[tokio::test]
155    async fn test_new_from_env() {
156        if env::var("TEST_KEYSTORE_PATH").is_err() {
157            println!("TEST_KEYSTORE_PATH is not set");
158            return;
159        }
160        let safe_user = SafeUser::new_from_env().expect("Failed to init user from env");
161        println!("{:?}", safe_user);
162    }
163}