Skip to main content

tk_rs/
mod.rs

1use std::str::FromStr;
2
3use base64::Engine;
4use p256::ecdsa::signature::Signer;
5use reqwest::Client;
6
7mod types;
8mod utils;
9
10pub use types::*;
11pub use utils::*;
12
13impl TurnkeySigner {
14    pub fn new(
15        api_public_key: String,
16        api_private_key: String,
17        organization_id: String,
18        private_key_id: String,
19        public_key: String,
20    ) -> Result<Self, anyhow::Error> {
21        Ok(Self {
22            api_public_key,
23            api_private_key,
24            organization_id,
25            private_key_id,
26            public_key,
27            client: Client::new(),
28        })
29    }
30
31    pub async fn sign(&self, message: &[u8]) -> Result<Vec<u8>, anyhow::Error> {
32        let hex_message = hex::encode(message);
33
34        let request = SignRequest {
35            activity_type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2".to_string(),
36            timestamp_ms: chrono::Utc::now().timestamp_millis().to_string(),
37            organization_id: self.organization_id.clone(),
38            parameters: SignParameters {
39                sign_with: self.private_key_id.clone(),
40                payload: hex_message,
41                encoding: "PAYLOAD_ENCODING_HEXADECIMAL".to_string(),
42                hash_function: "HASH_FUNCTION_NOT_APPLICABLE".to_string(),
43            },
44        };
45
46        let body = serde_json::to_string(&request).map_err(|e| anyhow::anyhow!(e.to_string()))?;
47
48        let stamp = self.create_stamp(&body)?;
49
50        let response = self
51            .client
52            .post("https://api.turnkey.com/public/v1/submit/sign_raw_payload")
53            .header("Content-Type", "application/json")
54            .header("X-Stamp", stamp)
55            .body(body)
56            .send()
57            .await
58            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
59
60        let response = serde_json::from_str::<ActivityResponse>(&response.text().await.unwrap())
61            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
62
63        if let Some(result) = response.activity.result {
64            if let Some(sign_result) = result.sign_raw_payload_result {
65                // Decode r and s components
66                let r_bytes = hex::decode(&sign_result.r)
67                    .map_err(|e| anyhow::anyhow!(format!("Invalid r component: {}", e)))?;
68                let s_bytes = hex::decode(&sign_result.s)
69                    .map_err(|e| anyhow::anyhow!(format!("Invalid s component: {}", e)))?;
70
71                // Ensure each component is exactly 32 bytes
72                if r_bytes.len() > 32 || s_bytes.len() > 32 {
73                    return Err(anyhow::anyhow!("Signature component too long"));
74                }
75
76                // Create properly padded 32-byte arrays
77                let mut final_r = [0u8; 32];
78                let mut final_s = [0u8; 32];
79
80                // Copy bytes with proper padding (right-aligned)
81                final_r[32 - r_bytes.len()..].copy_from_slice(&r_bytes);
82                final_s[32 - s_bytes.len()..].copy_from_slice(&s_bytes);
83
84                // Combine r and s into final 64-byte signature
85                let mut signature = Vec::with_capacity(64);
86                signature.extend_from_slice(&final_r);
87                signature.extend_from_slice(&final_s);
88
89                return Ok(signature);
90            }
91        }
92
93        Err(anyhow::anyhow!("Failed to get signature from response"))
94    }
95
96    pub async fn sign_solana(
97        &self,
98        message: &[u8],
99    ) -> Result<solana_sdk::signature::Signature, anyhow::Error> {
100        let sig = self.sign(message).await?;
101        let sig_bytes: [u8; 64] = sig.try_into().unwrap();
102        Ok(solana_sdk::signature::Signature::from(sig_bytes))
103    }
104
105    fn create_stamp(&self, message: &str) -> Result<String, anyhow::Error> {
106        let private_key_bytes =
107            hex_to_bytes(&self.api_private_key).map_err(|e| anyhow::anyhow!(e.to_string()))?;
108        let private_key_array: [u8; 32] = private_key_bytes
109            .try_into()
110            .map_err(|_| anyhow::anyhow!("Invalid private key length"))?;
111        let signing_key = p256::ecdsa::SigningKey::from_slice(&private_key_array)
112            .map_err(|e| anyhow::anyhow!(e.to_string()))?;
113
114        let signature: p256::ecdsa::Signature = signing_key.sign(message.as_bytes());
115        let signature_der = signature.to_der().to_bytes();
116        let signature_hex = bytes_to_hex(&signature_der).unwrap();
117
118        let stamp = serde_json::json!({
119            "public_key": self.api_public_key,
120            "signature": signature_hex,
121            "scheme": "SIGNATURE_SCHEME_TK_API_P256"
122        });
123
124        let json_stamp =
125            serde_json::to_string(&stamp).map_err(|e| anyhow::anyhow!(e.to_string()))?;
126
127        Ok(base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(json_stamp.as_bytes()))
128    }
129
130    pub fn solana_pubkey(&self) -> solana_sdk::pubkey::Pubkey {
131        solana_sdk::pubkey::Pubkey::from_str(&self.public_key).unwrap()
132    }
133}