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 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 if r_bytes.len() > 32 || s_bytes.len() > 32 {
73 return Err(anyhow::anyhow!("Signature component too long"));
74 }
75
76 let mut final_r = [0u8; 32];
78 let mut final_s = [0u8; 32];
79
80 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 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}