1use async_trait::async_trait;
19mod local_signer;
20use local_signer::*;
21
22mod vault_transit_signer;
23use vault_transit_signer::*;
24
25mod turnkey_signer;
26use turnkey_signer::*;
27
28mod google_cloud_kms_signer;
29use google_cloud_kms_signer::*;
30
31use solana_sdk::signature::Signature;
32
33use crate::{
34 domain::{
35 SignDataRequest, SignDataResponse, SignDataResponseEvm, SignTransactionResponse,
36 SignTypedDataRequest,
37 },
38 models::{
39 Address, NetworkTransactionData, SignerConfig, SignerRepoModel, SignerType,
40 TransactionRepoModel,
41 },
42 services::{GoogleCloudKmsService, TurnkeyService, VaultConfig, VaultService},
43};
44use eyre::Result;
45
46use super::{Signer, SignerError, SignerFactoryError};
47#[cfg(test)]
48use mockall::automock;
49
50pub enum SolanaSigner {
51 Local(LocalSigner),
52 Vault(LocalSigner),
53 VaultCloud(LocalSigner),
54 VaultTransit(VaultTransitSigner),
55 Turnkey(TurnkeySigner),
56 GoogleCloudKms(GoogleCloudKmsSigner),
57}
58
59#[async_trait]
60impl Signer for SolanaSigner {
61 async fn address(&self) -> Result<Address, SignerError> {
62 match self {
63 Self::Local(signer) | Self::Vault(signer) | Self::VaultCloud(signer) => {
64 signer.address().await
65 }
66 Self::VaultTransit(signer) => signer.address().await,
67 Self::Turnkey(signer) => signer.address().await,
68 Self::GoogleCloudKms(signer) => signer.address().await,
69 }
70 }
71
72 async fn sign_transaction(
73 &self,
74 transaction: NetworkTransactionData,
75 ) -> Result<SignTransactionResponse, SignerError> {
76 match self {
77 Self::Local(signer) | Self::Vault(signer) | Self::VaultCloud(signer) => {
78 signer.sign_transaction(transaction).await
79 }
80 Self::VaultTransit(signer) => signer.sign_transaction(transaction).await,
81 Self::Turnkey(signer) => signer.sign_transaction(transaction).await,
82 Self::GoogleCloudKms(signer) => signer.sign_transaction(transaction).await,
83 }
84 }
85}
86
87#[async_trait]
88#[cfg_attr(test, automock)]
89pub trait SolanaSignTrait: Sync + Send {
94 async fn pubkey(&self) -> Result<Address, SignerError>;
96
97 async fn sign(&self, message: &[u8]) -> Result<Signature, SignerError>;
107}
108
109#[async_trait]
110impl SolanaSignTrait for SolanaSigner {
111 async fn pubkey(&self) -> Result<Address, SignerError> {
112 match self {
113 Self::Local(signer) | Self::Vault(signer) | Self::VaultCloud(signer) => {
114 signer.pubkey().await
115 }
116 Self::VaultTransit(signer) => signer.pubkey().await,
117 Self::Turnkey(signer) => signer.pubkey().await,
118 Self::GoogleCloudKms(signer) => signer.pubkey().await,
119 }
120 }
121
122 async fn sign(&self, message: &[u8]) -> Result<Signature, SignerError> {
123 match self {
124 Self::Local(signer) | Self::Vault(signer) | Self::VaultCloud(signer) => {
125 Ok(signer.sign(message).await?)
126 }
127 Self::VaultTransit(signer) => Ok(signer.sign(message).await?),
128 Self::Turnkey(signer) => Ok(signer.sign(message).await?),
129 Self::GoogleCloudKms(signer) => Ok(signer.sign(message).await?),
130 }
131 }
132}
133
134pub struct SolanaSignerFactory;
135
136impl SolanaSignerFactory {
137 pub fn create_solana_signer(
138 signer_model: &SignerRepoModel,
139 ) -> Result<SolanaSigner, SignerFactoryError> {
140 let signer = match &signer_model.config {
141 SignerConfig::Local(_)
142 | SignerConfig::Test(_)
143 | SignerConfig::Vault(_)
144 | SignerConfig::VaultCloud(_) => SolanaSigner::Local(LocalSigner::new(signer_model)?),
145 SignerConfig::VaultTransit(vault_transit_signer_config) => {
146 let vault_service = VaultService::new(VaultConfig {
147 address: vault_transit_signer_config.address.clone(),
148 namespace: vault_transit_signer_config.namespace.clone(),
149 role_id: vault_transit_signer_config.role_id.clone(),
150 secret_id: vault_transit_signer_config.secret_id.clone(),
151 mount_path: "transit".to_string(),
152 token_ttl: None,
153 });
154
155 return Ok(SolanaSigner::VaultTransit(VaultTransitSigner::new(
156 signer_model,
157 vault_service,
158 )));
159 }
160 SignerConfig::AwsKms(_) => {
161 return Err(SignerFactoryError::UnsupportedType("AWS KMS".into()));
162 }
163 SignerConfig::Turnkey(turnkey_signer_config) => {
164 let turnkey_service =
165 TurnkeyService::new(turnkey_signer_config.clone()).map_err(|e| {
166 SignerFactoryError::InvalidConfig(format!(
167 "Failed to create Turnkey service: {}",
168 e
169 ))
170 })?;
171
172 return Ok(SolanaSigner::Turnkey(TurnkeySigner::new(turnkey_service)));
173 }
174 SignerConfig::GoogleCloudKms(google_cloud_kms_signer_config) => {
175 let google_cloud_kms_service =
176 GoogleCloudKmsService::new(google_cloud_kms_signer_config).map_err(|e| {
177 SignerFactoryError::InvalidConfig(format!(
178 "Failed to create Google Cloud KMS service: {}",
179 e
180 ))
181 })?;
182 return Ok(SolanaSigner::GoogleCloudKms(GoogleCloudKmsSigner::new(
183 google_cloud_kms_service,
184 )));
185 }
186 };
187
188 Ok(signer)
189 }
190}
191
192#[cfg(test)]
193mod solana_signer_factory_tests {
194 use super::*;
195 use crate::models::{
196 AwsKmsSignerConfig, GoogleCloudKmsSignerConfig, GoogleCloudKmsSignerKeyConfig,
197 GoogleCloudKmsSignerServiceAccountConfig, LocalSignerConfig, SecretString, SignerConfig,
198 SignerRepoModel, SolanaTransactionData, TurnkeySignerConfig, VaultTransitSignerConfig,
199 };
200 use mockall::predicate::*;
201 use secrets::SecretVec;
202 use std::sync::Arc;
203
204 fn test_key_bytes() -> SecretVec<u8> {
205 let key_bytes = vec![
206 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24,
207 25, 26, 27, 28, 29, 30, 31, 32,
208 ];
209 SecretVec::new(key_bytes.len(), |v| v.copy_from_slice(&key_bytes))
210 }
211
212 fn test_key_bytes_pubkey() -> Address {
213 Address::Solana("9C6hybhQ6Aycep9jaUnP6uL9ZYvDjUp1aSkFWPUFJtpj".to_string())
214 }
215
216 #[test]
217 fn test_create_solana_signer_local() {
218 let signer_model = SignerRepoModel {
219 id: "test".to_string(),
220 config: SignerConfig::Local(LocalSignerConfig {
221 raw_key: test_key_bytes(),
222 }),
223 };
224
225 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
226
227 match signer {
228 SolanaSigner::Local(_) => {}
229 _ => panic!("Expected Local signer"),
230 }
231 }
232
233 #[test]
234 fn test_create_solana_signer_test() {
235 let signer_model = SignerRepoModel {
236 id: "test".to_string(),
237 config: SignerConfig::Test(LocalSignerConfig {
238 raw_key: test_key_bytes(),
239 }),
240 };
241
242 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
243
244 match signer {
245 SolanaSigner::Local(_) => {}
246 _ => panic!("Expected Local signer"),
247 }
248 }
249
250 #[test]
251 fn test_create_solana_signer_vault() {
252 let signer_model = SignerRepoModel {
253 id: "test".to_string(),
254 config: SignerConfig::Vault(LocalSignerConfig {
255 raw_key: test_key_bytes(),
256 }),
257 };
258
259 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
260
261 match signer {
262 SolanaSigner::Local(_) => {}
263 _ => panic!("Expected Local signer"),
264 }
265 }
266
267 #[test]
268 fn test_create_solana_signer_vault_cloud() {
269 let signer_model = SignerRepoModel {
270 id: "test".to_string(),
271 config: SignerConfig::VaultCloud(LocalSignerConfig {
272 raw_key: test_key_bytes(),
273 }),
274 };
275
276 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
277
278 match signer {
279 SolanaSigner::Local(_) => {}
280 _ => panic!("Expected Local signer"),
281 }
282 }
283
284 #[test]
285 fn test_create_solana_signer_vault_transit() {
286 let signer_model = SignerRepoModel {
287 id: "test".to_string(),
288 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
289 key_name: "test".to_string(),
290 address: "address".to_string(),
291 namespace: None,
292 role_id: SecretString::new("role_id"),
293 secret_id: SecretString::new("secret_id"),
294 pubkey: "pubkey".to_string(),
295 mount_point: None,
296 }),
297 };
298
299 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
300
301 match signer {
302 SolanaSigner::VaultTransit(_) => {}
303 _ => panic!("Expected Transit signer"),
304 }
305 }
306
307 #[test]
308 fn test_create_solana_signer_turnkey() {
309 let signer_model = SignerRepoModel {
310 id: "test".to_string(),
311 config: SignerConfig::Turnkey(TurnkeySignerConfig {
312 api_private_key: SecretString::new("api_private_key"),
313 api_public_key: "api_public_key".to_string(),
314 organization_id: "organization_id".to_string(),
315 private_key_id: "private_key_id".to_string(),
316 public_key: "public_key".to_string(),
317 }),
318 };
319
320 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
321
322 match signer {
323 SolanaSigner::Turnkey(_) => {}
324 _ => panic!("Expected Turnkey signer"),
325 }
326 }
327
328 #[tokio::test]
329 async fn test_create_solana_signer_google_cloud_kms() {
330 let signer_model = SignerRepoModel {
331 id: "test".to_string(),
332 config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
333 service_account: GoogleCloudKmsSignerServiceAccountConfig {
334 project_id: "project_id".to_string(),
335 private_key_id: SecretString::new("private_key_id"),
336 private_key: SecretString::new("private_key"),
337 client_email: SecretString::new("client_email"),
338 client_id: "client_id".to_string(),
339 auth_uri: "auth_uri".to_string(),
340 token_uri: "token_uri".to_string(),
341 auth_provider_x509_cert_url: "auth_provider_x509_cert_url".to_string(),
342 client_x509_cert_url: "client_x509_cert_url".to_string(),
343 universe_domain: "universe_domain".to_string(),
344 },
345 key: GoogleCloudKmsSignerKeyConfig {
346 location: "global".to_string(),
347 key_id: "id".to_string(),
348 key_ring_id: "key_ring".to_string(),
349 key_version: 1,
350 },
351 }),
352 };
353
354 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
355
356 match signer {
357 SolanaSigner::GoogleCloudKms(_) => {}
358 _ => panic!("Expected Google Cloud KMS signer"),
359 }
360 }
361
362 #[tokio::test]
363 async fn test_address_solana_signer_local() {
364 let signer_model = SignerRepoModel {
365 id: "test".to_string(),
366 config: SignerConfig::Local(LocalSignerConfig {
367 raw_key: test_key_bytes(),
368 }),
369 };
370
371 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
372 let signer_address = signer.address().await.unwrap();
373 let signer_pubkey = signer.pubkey().await.unwrap();
374
375 assert_eq!(test_key_bytes_pubkey(), signer_address);
376 assert_eq!(test_key_bytes_pubkey(), signer_pubkey);
377 }
378
379 #[tokio::test]
380 async fn test_address_solana_signer_test() {
381 let signer_model = SignerRepoModel {
382 id: "test".to_string(),
383 config: SignerConfig::Test(LocalSignerConfig {
384 raw_key: test_key_bytes(),
385 }),
386 };
387
388 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
389 let signer_address = signer.address().await.unwrap();
390 let signer_pubkey = signer.pubkey().await.unwrap();
391
392 assert_eq!(test_key_bytes_pubkey(), signer_address);
393 assert_eq!(test_key_bytes_pubkey(), signer_pubkey);
394 }
395
396 #[tokio::test]
397 async fn test_address_solana_signer_vault() {
398 let signer_model = SignerRepoModel {
399 id: "test".to_string(),
400 config: SignerConfig::Vault(LocalSignerConfig {
401 raw_key: test_key_bytes(),
402 }),
403 };
404
405 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
406 let signer_address = signer.address().await.unwrap();
407 let signer_pubkey = signer.pubkey().await.unwrap();
408
409 assert_eq!(test_key_bytes_pubkey(), signer_address);
410 assert_eq!(test_key_bytes_pubkey(), signer_pubkey);
411 }
412
413 #[tokio::test]
414 async fn test_address_solana_signer_vault_cloud() {
415 let signer_model = SignerRepoModel {
416 id: "test".to_string(),
417 config: SignerConfig::VaultCloud(LocalSignerConfig {
418 raw_key: test_key_bytes(),
419 }),
420 };
421
422 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
423 let signer_address = signer.address().await.unwrap();
424 let signer_pubkey = signer.pubkey().await.unwrap();
425
426 assert_eq!(test_key_bytes_pubkey(), signer_address);
427 assert_eq!(test_key_bytes_pubkey(), signer_pubkey);
428 }
429
430 #[tokio::test]
431 async fn test_address_solana_signer_vault_transit() {
432 let signer_model = SignerRepoModel {
433 id: "test".to_string(),
434 config: SignerConfig::VaultTransit(VaultTransitSignerConfig {
435 key_name: "test".to_string(),
436 address: "address".to_string(),
437 namespace: None,
438 role_id: SecretString::new("role_id"),
439 secret_id: SecretString::new("secret_id"),
440 pubkey: "fV060x5X3Eo4uK/kTqQbSVL/qmMNaYKF2oaTa15hNfU=".to_string(),
441 mount_point: None,
442 }),
443 };
444 let expected_pubkey =
445 Address::Solana("9SNR5Sf993aphA7hzWSQsGv63x93trfuN8WjaToXcqKA".to_string());
446
447 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
448 let signer_address = signer.address().await.unwrap();
449 let signer_pubkey = signer.pubkey().await.unwrap();
450
451 assert_eq!(expected_pubkey, signer_address);
452 assert_eq!(expected_pubkey, signer_pubkey);
453 }
454
455 #[tokio::test]
456 async fn test_address_solana_signer_turnkey() {
457 let signer_model = SignerRepoModel {
458 id: "test".to_string(),
459 config: SignerConfig::Turnkey(TurnkeySignerConfig {
460 api_private_key: SecretString::new("api_private_key"),
461 api_public_key: "api_public_key".to_string(),
462 organization_id: "organization_id".to_string(),
463 private_key_id: "private_key_id".to_string(),
464 public_key: "5720be8aa9d2bb4be8e91f31d2c44c8629e42da16981c2cebabd55cafa0b76bd"
465 .to_string(),
466 }),
467 };
468 let expected_pubkey =
469 Address::Solana("6s7RsvzcdXFJi1tXeDoGfSKZFzN3juVt9fTar6WEhEm2".to_string());
470
471 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
472 let signer_address = signer.address().await.unwrap();
473 let signer_pubkey = signer.pubkey().await.unwrap();
474
475 assert_eq!(expected_pubkey, signer_address);
476 assert_eq!(expected_pubkey, signer_pubkey);
477 }
478
479 #[tokio::test]
480 async fn test_address_solana_signer_google_cloud_kms() {
481 let signer_model = SignerRepoModel {
482 id: "test".to_string(),
483 config: SignerConfig::GoogleCloudKms(GoogleCloudKmsSignerConfig {
484 service_account: GoogleCloudKmsSignerServiceAccountConfig {
485 project_id: "project_id".to_string(),
486 private_key_id: SecretString::new("private_key_id"),
487 private_key: SecretString::new("private_key"),
488 client_email: SecretString::new("client_email"),
489 client_id: "client_id".to_string(),
490 auth_uri: "auth_uri".to_string(),
491 token_uri: "token_uri".to_string(),
492 auth_provider_x509_cert_url: "auth_provider_x509_cert_url".to_string(),
493 client_x509_cert_url: "client_x509_cert_url".to_string(),
494 universe_domain: "universe_domain".to_string(),
495 },
496 key: GoogleCloudKmsSignerKeyConfig {
497 location: "global".to_string(),
498 key_id: "id".to_string(),
499 key_ring_id: "key_ring".to_string(),
500 key_version: 1,
501 },
502 }),
503 };
504
505 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
506 let signer_address = signer.address().await;
507 let signer_pubkey = signer.pubkey().await;
508
509 assert!(signer_address.is_err());
511 assert!(signer_pubkey.is_err());
512 }
513
514 #[tokio::test]
515 async fn test_sign_solana_signer_local() {
516 let signer_model = SignerRepoModel {
517 id: "test".to_string(),
518 config: SignerConfig::Local(LocalSignerConfig {
519 raw_key: test_key_bytes(),
520 }),
521 };
522
523 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
524 let message = b"test message";
525 let signature = signer.sign(message).await;
526
527 assert!(signature.is_ok());
528 }
529
530 #[tokio::test]
531 async fn test_sign_solana_signer_test() {
532 let signer_model = SignerRepoModel {
533 id: "test".to_string(),
534 config: SignerConfig::Test(LocalSignerConfig {
535 raw_key: test_key_bytes(),
536 }),
537 };
538
539 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
540 let message = b"test message";
541 let signature = signer.sign(message).await;
542
543 assert!(signature.is_ok());
544 }
545
546 #[tokio::test]
547 async fn test_sign_solana_signer_vault() {
548 let signer_model = SignerRepoModel {
549 id: "test".to_string(),
550 config: SignerConfig::Vault(LocalSignerConfig {
551 raw_key: test_key_bytes(),
552 }),
553 };
554
555 let signer = SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
556 let message = b"test message";
557 let signature = signer.sign(message).await;
558
559 assert!(signature.is_ok());
560 }
561
562 #[tokio::test]
563 async fn test_sign_solana_signer_vault_cloud() {
564 let signer_model = SignerRepoModel {
565 id: "test".to_string(),
566 config: SignerConfig::VaultCloud(LocalSignerConfig {
567 raw_key: test_key_bytes(),
568 }),
569 };
570
571 let signer: SolanaSigner =
572 SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
573 let message = b"test message";
574 let signature = signer.sign(message).await;
575
576 assert!(signature.is_ok());
577 }
578
579 #[tokio::test]
580 async fn test_sign_transaction_not_implemented() {
581 let signer_model = SignerRepoModel {
582 id: "test".to_string(),
583 config: SignerConfig::VaultCloud(LocalSignerConfig {
584 raw_key: test_key_bytes(),
585 }),
586 };
587
588 let signer: SolanaSigner =
589 SolanaSignerFactory::create_solana_signer(&signer_model).unwrap();
590 let transaction_data = NetworkTransactionData::Solana(SolanaTransactionData {
591 fee_payer: "test".to_string(),
592 hash: None,
593 recent_blockhash: None,
594 instructions: vec![],
595 });
596
597 let result = signer.sign_transaction(transaction_data).await;
598
599 match result {
600 Err(SignerError::NotImplemented(msg)) => {
601 assert_eq!(msg, "sign_transaction is not implemented".to_string());
602 }
603 _ => panic!("Expected SignerError::NotImplemented"),
604 }
605 }
606}