openzeppelin_relayer/models/signer/
repository.rs

1//! Repository layer models and data persistence for signers.
2//!
3//! This module provides the data layer representation of signers, including:
4//!
5//! - **Repository Models**: Data structures optimized for storage and retrieval
6//! - **Data Conversions**: Mapping between domain objects and repository representations
7//! - **Persistence Logic**: Storage-specific validation and constraints
8//!
9//! Acts as the bridge between the domain layer and actual data storage implementations
10//! (in-memory, Redis, etc.), ensuring consistent data representation across repositories.
11//!
12
13use crate::{
14    models::{
15        signer::{
16            AwsKmsSignerConfig, CdpSignerConfig, GoogleCloudKmsSignerConfig,
17            GoogleCloudKmsSignerKeyConfig, GoogleCloudKmsSignerServiceAccountConfig,
18            LocalSignerConfig, Signer, SignerConfig, SignerValidationError, TurnkeySignerConfig,
19            VaultSignerConfig, VaultTransitSignerConfig,
20        },
21        SecretString,
22    },
23    utils::{
24        deserialize_secret_string, deserialize_secret_vec, serialize_secret_string,
25        serialize_secret_vec,
26    },
27};
28use secrets::SecretVec;
29use serde::{Deserialize, Serialize};
30/// Repository model for signer storage and retrieval
31#[derive(Debug, Clone, Serialize, Deserialize)]
32pub struct SignerRepoModel {
33    pub id: String,
34    pub config: SignerConfigStorage,
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize)]
38pub enum SignerConfigStorage {
39    Local(LocalSignerConfigStorage),
40    Vault(VaultSignerConfigStorage),
41    VaultTransit(VaultTransitSignerConfigStorage),
42    AwsKms(AwsKmsSignerConfigStorage),
43    Turnkey(TurnkeySignerConfigStorage),
44    Cdp(CdpSignerConfigStorage),
45    GoogleCloudKms(Box<GoogleCloudKmsSignerConfigStorage>),
46}
47
48/// Local signer configuration for storage (with base64 encoding)
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct LocalSignerConfigStorage {
51    #[serde(
52        serialize_with = "serialize_secret_vec",
53        deserialize_with = "deserialize_secret_vec"
54    )]
55    pub raw_key: SecretVec<u8>,
56}
57
58impl From<LocalSignerConfig> for LocalSignerConfigStorage {
59    fn from(config: LocalSignerConfig) -> Self {
60        Self {
61            raw_key: config.raw_key,
62        }
63    }
64}
65
66impl From<LocalSignerConfigStorage> for LocalSignerConfig {
67    fn from(storage: LocalSignerConfigStorage) -> Self {
68        Self {
69            raw_key: storage.raw_key,
70        }
71    }
72}
73
74/// Storage representations for other signer types (these are simpler as they don't contain secrets that need encoding)
75#[derive(Debug, Clone, Serialize, Deserialize)]
76pub struct AwsKmsSignerConfigStorage {
77    pub region: Option<String>,
78    pub key_id: String,
79}
80
81#[derive(Debug, Clone, Serialize, Deserialize)]
82pub struct VaultSignerConfigStorage {
83    pub address: String,
84    pub namespace: Option<String>,
85    #[serde(
86        serialize_with = "serialize_secret_string",
87        deserialize_with = "deserialize_secret_string"
88    )]
89    pub role_id: SecretString,
90    #[serde(
91        serialize_with = "serialize_secret_string",
92        deserialize_with = "deserialize_secret_string"
93    )]
94    pub secret_id: SecretString,
95    pub key_name: String,
96    pub mount_point: Option<String>,
97}
98
99#[derive(Debug, Clone, Serialize, Deserialize)]
100pub struct VaultTransitSignerConfigStorage {
101    pub key_name: String,
102    pub address: String,
103    pub namespace: Option<String>,
104    #[serde(
105        serialize_with = "serialize_secret_string",
106        deserialize_with = "deserialize_secret_string"
107    )]
108    pub role_id: SecretString,
109    #[serde(
110        serialize_with = "serialize_secret_string",
111        deserialize_with = "deserialize_secret_string"
112    )]
113    pub secret_id: SecretString,
114    pub pubkey: String,
115    pub mount_point: Option<String>,
116}
117
118#[derive(Debug, Clone, Serialize, Deserialize)]
119pub struct TurnkeySignerConfigStorage {
120    pub api_public_key: String,
121    #[serde(
122        serialize_with = "serialize_secret_string",
123        deserialize_with = "deserialize_secret_string"
124    )]
125    pub api_private_key: SecretString,
126    pub organization_id: String,
127    pub private_key_id: String,
128    pub public_key: String,
129}
130
131#[derive(Debug, Clone, Serialize, Deserialize)]
132pub struct CdpSignerConfigStorage {
133    pub api_key_id: String,
134    #[serde(
135        serialize_with = "serialize_secret_string",
136        deserialize_with = "deserialize_secret_string"
137    )]
138    pub api_key_secret: SecretString,
139    #[serde(
140        serialize_with = "serialize_secret_string",
141        deserialize_with = "deserialize_secret_string"
142    )]
143    pub wallet_secret: SecretString,
144    pub account_address: String,
145}
146
147/// Storage model for Google Cloud KMS service account configuration.
148#[derive(Debug, Clone, Serialize, Deserialize)]
149pub struct GoogleCloudKmsSignerServiceAccountConfigStorage {
150    #[serde(
151        serialize_with = "serialize_secret_string",
152        deserialize_with = "deserialize_secret_string"
153    )]
154    pub private_key: SecretString,
155    #[serde(
156        serialize_with = "serialize_secret_string",
157        deserialize_with = "deserialize_secret_string"
158    )]
159    pub private_key_id: SecretString,
160    #[serde(
161        serialize_with = "serialize_secret_string",
162        deserialize_with = "deserialize_secret_string"
163    )]
164    pub project_id: SecretString,
165    #[serde(
166        serialize_with = "serialize_secret_string",
167        deserialize_with = "deserialize_secret_string"
168    )]
169    pub client_email: SecretString,
170    #[serde(
171        serialize_with = "serialize_secret_string",
172        deserialize_with = "deserialize_secret_string"
173    )]
174    pub client_id: SecretString,
175    #[serde(
176        serialize_with = "serialize_secret_string",
177        deserialize_with = "deserialize_secret_string"
178    )]
179    pub auth_uri: SecretString,
180    #[serde(
181        serialize_with = "serialize_secret_string",
182        deserialize_with = "deserialize_secret_string"
183    )]
184    pub token_uri: SecretString,
185    #[serde(
186        serialize_with = "serialize_secret_string",
187        deserialize_with = "deserialize_secret_string"
188    )]
189    pub auth_provider_x509_cert_url: SecretString,
190    #[serde(
191        serialize_with = "serialize_secret_string",
192        deserialize_with = "deserialize_secret_string"
193    )]
194    pub client_x509_cert_url: SecretString,
195    #[serde(
196        serialize_with = "serialize_secret_string",
197        deserialize_with = "deserialize_secret_string"
198    )]
199    pub universe_domain: SecretString,
200}
201
202/// Storage model for Google Cloud KMS key configuration.
203///
204/// All string fields are encrypted at rest to prevent attackers from
205/// modifying key identifiers to point to different keys.
206#[derive(Debug, Clone, Serialize, Deserialize)]
207pub struct GoogleCloudKmsSignerKeyConfigStorage {
208    #[serde(
209        serialize_with = "serialize_secret_string",
210        deserialize_with = "deserialize_secret_string"
211    )]
212    pub location: SecretString,
213    #[serde(
214        serialize_with = "serialize_secret_string",
215        deserialize_with = "deserialize_secret_string"
216    )]
217    pub key_ring_id: SecretString,
218    #[serde(
219        serialize_with = "serialize_secret_string",
220        deserialize_with = "deserialize_secret_string"
221    )]
222    pub key_id: SecretString,
223    pub key_version: u32,
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
227pub struct GoogleCloudKmsSignerConfigStorage {
228    pub service_account: GoogleCloudKmsSignerServiceAccountConfigStorage,
229    pub key: GoogleCloudKmsSignerKeyConfigStorage,
230}
231
232/// Convert from domain model to repository model
233impl From<Signer> for SignerRepoModel {
234    fn from(signer: Signer) -> Self {
235        Self {
236            id: signer.id,
237            config: signer.config.into(),
238        }
239    }
240}
241
242/// Convert from repository model to domain model
243impl From<SignerRepoModel> for Signer {
244    fn from(repo_model: SignerRepoModel) -> Self {
245        Self {
246            id: repo_model.id,
247            config: repo_model.config.into(),
248        }
249    }
250}
251
252impl From<AwsKmsSignerConfig> for AwsKmsSignerConfigStorage {
253    fn from(config: AwsKmsSignerConfig) -> Self {
254        Self {
255            region: config.region,
256            key_id: config.key_id,
257        }
258    }
259}
260
261impl From<AwsKmsSignerConfigStorage> for AwsKmsSignerConfig {
262    fn from(storage: AwsKmsSignerConfigStorage) -> Self {
263        Self {
264            region: storage.region,
265            key_id: storage.key_id,
266        }
267    }
268}
269
270impl From<VaultSignerConfig> for VaultSignerConfigStorage {
271    fn from(config: VaultSignerConfig) -> Self {
272        Self {
273            address: config.address,
274            namespace: config.namespace,
275            role_id: config.role_id,
276            secret_id: config.secret_id,
277            key_name: config.key_name,
278            mount_point: config.mount_point,
279        }
280    }
281}
282
283impl From<VaultSignerConfigStorage> for VaultSignerConfig {
284    fn from(storage: VaultSignerConfigStorage) -> Self {
285        Self {
286            address: storage.address,
287            namespace: storage.namespace,
288            role_id: storage.role_id,
289            secret_id: storage.secret_id,
290            key_name: storage.key_name,
291            mount_point: storage.mount_point,
292        }
293    }
294}
295
296impl From<VaultTransitSignerConfig> for VaultTransitSignerConfigStorage {
297    fn from(config: VaultTransitSignerConfig) -> Self {
298        Self {
299            key_name: config.key_name,
300            address: config.address,
301            namespace: config.namespace,
302            role_id: config.role_id,
303            secret_id: config.secret_id,
304            pubkey: config.pubkey,
305            mount_point: config.mount_point,
306        }
307    }
308}
309
310impl From<VaultTransitSignerConfigStorage> for VaultTransitSignerConfig {
311    fn from(storage: VaultTransitSignerConfigStorage) -> Self {
312        Self {
313            key_name: storage.key_name,
314            address: storage.address,
315            namespace: storage.namespace,
316            role_id: storage.role_id,
317            secret_id: storage.secret_id,
318            pubkey: storage.pubkey,
319            mount_point: storage.mount_point,
320        }
321    }
322}
323
324impl From<TurnkeySignerConfig> for TurnkeySignerConfigStorage {
325    fn from(config: TurnkeySignerConfig) -> Self {
326        Self {
327            api_public_key: config.api_public_key,
328            api_private_key: config.api_private_key,
329            organization_id: config.organization_id,
330            private_key_id: config.private_key_id,
331            public_key: config.public_key,
332        }
333    }
334}
335
336impl From<TurnkeySignerConfigStorage> for TurnkeySignerConfig {
337    fn from(storage: TurnkeySignerConfigStorage) -> Self {
338        Self {
339            api_public_key: storage.api_public_key,
340            api_private_key: storage.api_private_key,
341            organization_id: storage.organization_id,
342            private_key_id: storage.private_key_id,
343            public_key: storage.public_key,
344        }
345    }
346}
347
348impl From<CdpSignerConfig> for CdpSignerConfigStorage {
349    fn from(config: CdpSignerConfig) -> Self {
350        Self {
351            api_key_id: config.api_key_id,
352            api_key_secret: config.api_key_secret,
353            wallet_secret: config.wallet_secret,
354            account_address: config.account_address,
355        }
356    }
357}
358
359impl From<CdpSignerConfigStorage> for CdpSignerConfig {
360    fn from(storage: CdpSignerConfigStorage) -> Self {
361        Self {
362            api_key_id: storage.api_key_id,
363            api_key_secret: storage.api_key_secret,
364            wallet_secret: storage.wallet_secret,
365            account_address: storage.account_address,
366        }
367    }
368}
369
370impl From<GoogleCloudKmsSignerConfig> for GoogleCloudKmsSignerConfigStorage {
371    fn from(config: GoogleCloudKmsSignerConfig) -> Self {
372        Self {
373            service_account: config.service_account.into(),
374            key: config.key.into(),
375        }
376    }
377}
378
379impl From<GoogleCloudKmsSignerConfigStorage> for GoogleCloudKmsSignerConfig {
380    fn from(storage: GoogleCloudKmsSignerConfigStorage) -> Self {
381        Self {
382            service_account: storage.service_account.into(),
383            key: storage.key.into(),
384        }
385    }
386}
387
388impl From<GoogleCloudKmsSignerServiceAccountConfig>
389    for GoogleCloudKmsSignerServiceAccountConfigStorage
390{
391    fn from(config: GoogleCloudKmsSignerServiceAccountConfig) -> Self {
392        Self {
393            private_key: config.private_key,
394            private_key_id: config.private_key_id,
395            project_id: config.project_id,
396            client_email: config.client_email,
397            client_id: config.client_id,
398            auth_uri: config.auth_uri,
399            token_uri: config.token_uri,
400            auth_provider_x509_cert_url: config.auth_provider_x509_cert_url,
401            client_x509_cert_url: config.client_x509_cert_url,
402            universe_domain: config.universe_domain,
403        }
404    }
405}
406
407impl From<GoogleCloudKmsSignerServiceAccountConfigStorage>
408    for GoogleCloudKmsSignerServiceAccountConfig
409{
410    fn from(storage: GoogleCloudKmsSignerServiceAccountConfigStorage) -> Self {
411        Self {
412            private_key: storage.private_key,
413            private_key_id: storage.private_key_id,
414            project_id: storage.project_id,
415            client_email: storage.client_email,
416            client_id: storage.client_id,
417            auth_uri: storage.auth_uri,
418            token_uri: storage.token_uri,
419            auth_provider_x509_cert_url: storage.auth_provider_x509_cert_url,
420            client_x509_cert_url: storage.client_x509_cert_url,
421            universe_domain: storage.universe_domain,
422        }
423    }
424}
425
426impl From<GoogleCloudKmsSignerKeyConfig> for GoogleCloudKmsSignerKeyConfigStorage {
427    fn from(config: GoogleCloudKmsSignerKeyConfig) -> Self {
428        Self {
429            location: config.location,
430            key_ring_id: config.key_ring_id,
431            key_id: config.key_id,
432            key_version: config.key_version,
433        }
434    }
435}
436
437impl From<GoogleCloudKmsSignerKeyConfigStorage> for GoogleCloudKmsSignerKeyConfig {
438    fn from(storage: GoogleCloudKmsSignerKeyConfigStorage) -> Self {
439        Self {
440            location: storage.location,
441            key_ring_id: storage.key_ring_id,
442            key_id: storage.key_id,
443            key_version: storage.key_version,
444        }
445    }
446}
447
448impl SignerRepoModel {
449    /// Validates the repository model using core validation logic
450    pub fn validate(&self) -> Result<(), SignerValidationError> {
451        let core_signer = Signer::from(self.clone());
452        core_signer.validate()
453    }
454}
455
456impl From<SignerConfig> for SignerConfigStorage {
457    fn from(config: SignerConfig) -> Self {
458        match config {
459            SignerConfig::Local(local) => SignerConfigStorage::Local(local.into()),
460            SignerConfig::Vault(vault) => SignerConfigStorage::Vault(vault.into()),
461            SignerConfig::VaultTransit(vault_transit) => {
462                SignerConfigStorage::VaultTransit(vault_transit.into())
463            }
464            SignerConfig::AwsKms(aws_kms) => SignerConfigStorage::AwsKms(aws_kms.into()),
465            SignerConfig::Turnkey(turnkey) => SignerConfigStorage::Turnkey(turnkey.into()),
466            SignerConfig::Cdp(cdp) => SignerConfigStorage::Cdp(cdp.into()),
467            SignerConfig::GoogleCloudKms(gcp) => {
468                SignerConfigStorage::GoogleCloudKms(Box::new((*gcp).into()))
469            }
470        }
471    }
472}
473
474impl From<SignerConfigStorage> for SignerConfig {
475    fn from(storage: SignerConfigStorage) -> Self {
476        match storage {
477            SignerConfigStorage::Local(local) => SignerConfig::Local(local.into()),
478            SignerConfigStorage::Vault(vault) => SignerConfig::Vault(vault.into()),
479            SignerConfigStorage::VaultTransit(vault_transit) => {
480                SignerConfig::VaultTransit(vault_transit.into())
481            }
482            SignerConfigStorage::AwsKms(aws_kms) => SignerConfig::AwsKms(aws_kms.into()),
483            SignerConfigStorage::Turnkey(turnkey) => SignerConfig::Turnkey(turnkey.into()),
484            SignerConfigStorage::Cdp(cdp) => SignerConfig::Cdp(cdp.into()),
485            SignerConfigStorage::GoogleCloudKms(gcp) => {
486                SignerConfig::GoogleCloudKms(Box::new((*gcp).into()))
487            }
488        }
489    }
490}
491
492impl SignerConfigStorage {
493    /// Get local signer config, returns error if not a local signer
494    pub fn get_local(&self) -> Option<&LocalSignerConfigStorage> {
495        match self {
496            Self::Local(config) => Some(config),
497            _ => None,
498        }
499    }
500
501    /// Get vault transit signer config, returns error if not a vault transit signer
502    pub fn get_vault_transit(&self) -> Option<&VaultTransitSignerConfigStorage> {
503        match self {
504            Self::VaultTransit(config) => Some(config),
505            _ => None,
506        }
507    }
508
509    /// Get vault signer config, returns error if not a vault signer
510    pub fn get_vault(&self) -> Option<&VaultSignerConfigStorage> {
511        match self {
512            Self::Vault(config) => Some(config),
513            _ => None,
514        }
515    }
516
517    /// Get turnkey signer config, returns error if not a turnkey signer
518    pub fn get_turnkey(&self) -> Option<&TurnkeySignerConfigStorage> {
519        match self {
520            Self::Turnkey(config) => Some(config),
521            _ => None,
522        }
523    }
524
525    /// Get CDP signer config, returns error if not a CDP signer
526    pub fn get_cdp(&self) -> Option<&CdpSignerConfigStorage> {
527        match self {
528            Self::Cdp(config) => Some(config),
529            _ => None,
530        }
531    }
532
533    /// Get google cloud kms signer config, returns error if not a google cloud kms signer
534    pub fn get_google_cloud_kms(&self) -> Option<&GoogleCloudKmsSignerConfigStorage> {
535        match self {
536            Self::GoogleCloudKms(config) => Some(config),
537            _ => None,
538        }
539    }
540
541    /// Get aws kms signer config, returns error if not an aws kms signer
542    pub fn get_aws_kms(&self) -> Option<&AwsKmsSignerConfigStorage> {
543        match self {
544            Self::AwsKms(config) => Some(config),
545            _ => None,
546        }
547    }
548}
549
550#[cfg(test)]
551mod tests {
552    use super::*;
553    use crate::models::signer::{LocalSignerConfig, SignerConfig};
554    use secrets::SecretVec;
555
556    #[test]
557    fn test_from_core_signer() {
558        let config = LocalSignerConfig {
559            raw_key: SecretVec::new(32, |v| v.fill(1)),
560        };
561
562        let core =
563            crate::models::signer::Signer::new("test-id".to_string(), SignerConfig::Local(config));
564
565        let repo_model = SignerRepoModel::from(core);
566        assert_eq!(repo_model.id, "test-id");
567        assert!(matches!(repo_model.config, SignerConfigStorage::Local(_)));
568    }
569
570    #[test]
571    fn test_to_core_signer() {
572        use crate::models::signer::AwsKmsSignerConfigStorage;
573
574        let domain_config = AwsKmsSignerConfigStorage {
575            region: Some("us-east-1".to_string()),
576            key_id: "test-key".to_string(),
577        };
578
579        let repo_model = SignerRepoModel {
580            id: "test-id".to_string(),
581            config: SignerConfigStorage::AwsKms(domain_config),
582        };
583
584        let core = Signer::from(repo_model);
585        assert_eq!(core.id, "test-id");
586        assert_eq!(
587            core.signer_type(),
588            crate::models::signer::SignerType::AwsKms
589        );
590    }
591
592    #[test]
593    fn test_validation() {
594        use secrets::SecretVec;
595
596        let domain_config = LocalSignerConfig {
597            raw_key: SecretVec::new(32, |v| v.fill(1)),
598        };
599        // Convert to storage config properly
600        let storage_config = LocalSignerConfigStorage::from(domain_config);
601
602        let repo_model = SignerRepoModel {
603            id: "test-id".to_string(),
604            config: SignerConfigStorage::Local(storage_config),
605        };
606
607        assert!(repo_model.validate().is_ok());
608    }
609
610    #[test]
611    fn test_local_config_storage_conversion() {
612        let domain_config = LocalSignerConfig {
613            raw_key: SecretVec::new(4, |v| v.copy_from_slice(&[1, 2, 3, 4])),
614        };
615
616        let storage_config = LocalSignerConfigStorage::from(domain_config.clone());
617        let converted_back = LocalSignerConfig::from(storage_config);
618
619        // Compare the actual secret data
620        let original_data = domain_config.raw_key.borrow();
621        let converted_data = converted_back.raw_key.borrow();
622        assert_eq!(*original_data, *converted_data);
623    }
624
625    #[test]
626    fn test_cdp_config_storage_conversion() {
627        use crate::models::SecretString;
628
629        let domain_config = CdpSignerConfig {
630            api_key_id: "test-api-key-id".to_string(),
631            api_key_secret: SecretString::new("test-api-secret"),
632            wallet_secret: SecretString::new("test-wallet-secret"),
633            account_address: "0x1234567890123456789012345678901234567890".to_string(),
634        };
635
636        let storage_config = CdpSignerConfigStorage::from(domain_config.clone());
637        let converted_back = CdpSignerConfig::from(storage_config);
638
639        assert_eq!(domain_config.api_key_id, converted_back.api_key_id);
640        assert_eq!(
641            domain_config.account_address,
642            converted_back.account_address
643        );
644        assert_eq!(
645            domain_config.api_key_secret.to_str(),
646            converted_back.api_key_secret.to_str()
647        );
648        assert_eq!(
649            domain_config.wallet_secret.to_str(),
650            converted_back.wallet_secret.to_str()
651        );
652    }
653
654    #[test]
655    fn test_signer_config_storage_get_cdp() {
656        use crate::models::SecretString;
657
658        let cdp_storage = CdpSignerConfigStorage {
659            api_key_id: "test-id".to_string(),
660            api_key_secret: SecretString::new("secret"),
661            wallet_secret: SecretString::new("wallet-secret"),
662            account_address: "0x1234567890123456789012345678901234567890".to_string(),
663        };
664
665        let config_storage = SignerConfigStorage::Cdp(cdp_storage);
666        let retrieved_cdp = config_storage.get_cdp();
667        assert!(retrieved_cdp.is_some());
668        assert_eq!(retrieved_cdp.unwrap().api_key_id, "test-id");
669    }
670
671    #[test]
672    fn test_signer_config_storage_get_cdp_from_non_cdp() {
673        let aws_storage = AwsKmsSignerConfigStorage {
674            region: Some("us-east-1".to_string()),
675            key_id: "test-key".to_string(),
676        };
677
678        let config_storage = SignerConfigStorage::AwsKms(aws_storage);
679        let retrieved_cdp = config_storage.get_cdp();
680        assert!(retrieved_cdp.is_none());
681    }
682}