kora_lib/signer/
keypair_util.rs1use crate::{error::KoraError, sanitize_error};
2use serde_json;
3use solana_sdk::signature::Keypair;
4use std::fs;
5
6pub struct KeypairUtil;
8
9impl KeypairUtil {
10 pub fn from_private_key_string(private_key: &str) -> Result<Keypair, KoraError> {
15 if let Ok(file_content) = fs::read_to_string(private_key) {
17 return Self::from_json_keypair(&file_content);
18 }
19
20 if private_key.trim().starts_with('[') && private_key.trim().ends_with(']') {
22 return Self::from_u8_array_string(private_key);
23 }
24
25 Self::from_base58_safe(private_key)
27 }
28
29 pub fn from_base58_safe(private_key: &str) -> Result<Keypair, KoraError> {
31 let decoded = bs58::decode(private_key).into_vec().map_err(|e| {
33 KoraError::SigningError(format!("Invalid base58 string: {}", sanitize_error!(e)))
34 })?;
35
36 if decoded.len() != 64 {
37 return Err(KoraError::SigningError(format!(
38 "Invalid private key length: expected 64 bytes, got {}",
39 decoded.len()
40 )));
41 }
42
43 let keypair = Keypair::try_from(&decoded[..]).map_err(|e| {
44 KoraError::SigningError(format!("Invalid private key bytes: {}", sanitize_error!(e)))
45 })?;
46
47 Ok(keypair)
48 }
49
50 pub fn from_u8_array_string(array_str: &str) -> Result<Keypair, KoraError> {
52 let trimmed = array_str.trim();
53
54 if !trimmed.starts_with('[') || !trimmed.ends_with(']') {
55 return Err(KoraError::SigningError(
56 "U8Array string must start with '[' and end with ']'".to_string(),
57 ));
58 }
59
60 let inner = &trimmed[1..trimmed.len() - 1];
61
62 if inner.trim().is_empty() {
63 return Err(KoraError::SigningError("U8Array string cannot be empty".to_string()));
64 }
65
66 let bytes: Result<Vec<u8>, _> = inner.split(',').map(|s| s.trim().parse::<u8>()).collect();
67
68 match bytes {
69 Ok(byte_array) => {
70 if byte_array.len() != 64 {
71 return Err(KoraError::SigningError(format!(
72 "Private key must be exactly 64 bytes, got {}",
73 byte_array.len()
74 )));
75 }
76 Keypair::try_from(&byte_array[..]).map_err(|e| {
77 KoraError::SigningError(format!(
78 "Invalid private key bytes: {}",
79 sanitize_error!(e)
80 ))
81 })
82 }
83 Err(e) => Err(KoraError::SigningError(format!(
84 "Failed to parse U8Array: {}",
85 sanitize_error!(e)
86 ))),
87 }
88 }
89
90 pub fn from_json_keypair(json_content: &str) -> Result<Keypair, KoraError> {
92 if let Ok(byte_array) = serde_json::from_str::<Vec<u8>>(json_content) {
94 if byte_array.len() != 64 {
95 return Err(KoraError::SigningError(format!(
96 "JSON keypair must be exactly 64 bytes, got {}",
97 byte_array.len()
98 )));
99 }
100 return Keypair::try_from(&byte_array[..]).map_err(|e| {
101 KoraError::SigningError(format!(
102 "Invalid private key bytes: {}",
103 sanitize_error!(e)
104 ))
105 });
106 }
107
108 Err(KoraError::SigningError(
109 "Invalid JSON keypair format. Expected either a JSON array of 64 bytes or an object with a 'keypair' field".to_string()
110 ))
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117 use solana_sdk::{signature::Keypair, signer::Signer};
118 use std::fs;
119 use tempfile::NamedTempFile;
120
121 #[test]
122 fn test_from_base58_format() {
123 let keypair = Keypair::new();
124 let base58_key = bs58::encode(keypair.to_bytes()).into_string();
125
126 let parsed_keypair = KeypairUtil::from_private_key_string(&base58_key).unwrap();
127 assert_eq!(parsed_keypair.pubkey(), keypair.pubkey());
128 }
129
130 #[test]
131 fn test_from_u8_array_format() {
132 let keypair = Keypair::new();
133 let bytes = keypair.to_bytes();
134
135 let u8_array_str =
136 format!("[{}]", bytes.iter().map(|b| b.to_string()).collect::<Vec<_>>().join(", "));
137
138 let parsed_keypair = KeypairUtil::from_private_key_string(&u8_array_str).unwrap();
139 assert_eq!(parsed_keypair.pubkey(), keypair.pubkey());
140 }
141
142 #[test]
143 fn test_from_json_file_path() {
144 let keypair = Keypair::new();
145 let bytes = keypair.to_bytes();
146
147 let temp_file = NamedTempFile::new().unwrap();
148 let json_str = serde_json::to_string(&bytes.to_vec()).unwrap();
149 fs::write(temp_file.path(), json_str).unwrap();
150
151 let parsed_keypair =
152 KeypairUtil::from_private_key_string(temp_file.path().to_str().unwrap()).unwrap();
153 assert_eq!(parsed_keypair.pubkey(), keypair.pubkey());
154 }
155
156 #[test]
157 fn test_invalid_formats() {
158 let result = KeypairUtil::from_private_key_string("[1, 2, 3]");
160 assert!(result.is_err());
161
162 let result = KeypairUtil::from_private_key_string("{invalid json}");
164 assert!(result.is_err());
165
166 let result = KeypairUtil::from_private_key_string("/nonexistent/file.json");
168 assert!(result.is_err());
169 }
170}