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(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#[derive(Debug, Clone, Serialize, Deserialize)]
148pub struct GoogleCloudKmsSignerServiceAccountConfigStorage {
149    #[serde(
150        serialize_with = "serialize_secret_string",
151        deserialize_with = "deserialize_secret_string"
152    )]
153    pub private_key: SecretString,
154    #[serde(
155        serialize_with = "serialize_secret_string",
156        deserialize_with = "deserialize_secret_string"
157    )]
158    pub private_key_id: SecretString,
159    pub project_id: String,
160    #[serde(
161        serialize_with = "serialize_secret_string",
162        deserialize_with = "deserialize_secret_string"
163    )]
164    pub client_email: SecretString,
165    pub client_id: String,
166    pub auth_uri: String,
167    pub token_uri: String,
168    pub auth_provider_x509_cert_url: String,
169    pub client_x509_cert_url: String,
170    pub universe_domain: String,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
174pub struct GoogleCloudKmsSignerKeyConfigStorage {
175    pub location: String,
176    pub key_ring_id: String,
177    pub key_id: String,
178    pub key_version: u32,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
182pub struct GoogleCloudKmsSignerConfigStorage {
183    pub service_account: GoogleCloudKmsSignerServiceAccountConfigStorage,
184    pub key: GoogleCloudKmsSignerKeyConfigStorage,
185}
186
187/// Convert from domain model to repository model
188impl From<Signer> for SignerRepoModel {
189    fn from(signer: Signer) -> Self {
190        Self {
191            id: signer.id,
192            config: signer.config.into(),
193        }
194    }
195}
196
197/// Convert from repository model to domain model
198impl From<SignerRepoModel> for Signer {
199    fn from(repo_model: SignerRepoModel) -> Self {
200        Self {
201            id: repo_model.id,
202            config: repo_model.config.into(),
203        }
204    }
205}
206
207impl From<AwsKmsSignerConfig> for AwsKmsSignerConfigStorage {
208    fn from(config: AwsKmsSignerConfig) -> Self {
209        Self {
210            region: config.region,
211            key_id: config.key_id,
212        }
213    }
214}
215
216impl From<AwsKmsSignerConfigStorage> for AwsKmsSignerConfig {
217    fn from(storage: AwsKmsSignerConfigStorage) -> Self {
218        Self {
219            region: storage.region,
220            key_id: storage.key_id,
221        }
222    }
223}
224
225impl From<VaultSignerConfig> for VaultSignerConfigStorage {
226    fn from(config: VaultSignerConfig) -> Self {
227        Self {
228            address: config.address,
229            namespace: config.namespace,
230            role_id: config.role_id,
231            secret_id: config.secret_id,
232            key_name: config.key_name,
233            mount_point: config.mount_point,
234        }
235    }
236}
237
238impl From<VaultSignerConfigStorage> for VaultSignerConfig {
239    fn from(storage: VaultSignerConfigStorage) -> Self {
240        Self {
241            address: storage.address,
242            namespace: storage.namespace,
243            role_id: storage.role_id,
244            secret_id: storage.secret_id,
245            key_name: storage.key_name,
246            mount_point: storage.mount_point,
247        }
248    }
249}
250
251impl From<VaultTransitSignerConfig> for VaultTransitSignerConfigStorage {
252    fn from(config: VaultTransitSignerConfig) -> Self {
253        Self {
254            key_name: config.key_name,
255            address: config.address,
256            namespace: config.namespace,
257            role_id: config.role_id,
258            secret_id: config.secret_id,
259            pubkey: config.pubkey,
260            mount_point: config.mount_point,
261        }
262    }
263}
264
265impl From<VaultTransitSignerConfigStorage> for VaultTransitSignerConfig {
266    fn from(storage: VaultTransitSignerConfigStorage) -> Self {
267        Self {
268            key_name: storage.key_name,
269            address: storage.address,
270            namespace: storage.namespace,
271            role_id: storage.role_id,
272            secret_id: storage.secret_id,
273            pubkey: storage.pubkey,
274            mount_point: storage.mount_point,
275        }
276    }
277}
278
279impl From<TurnkeySignerConfig> for TurnkeySignerConfigStorage {
280    fn from(config: TurnkeySignerConfig) -> Self {
281        Self {
282            api_public_key: config.api_public_key,
283            api_private_key: config.api_private_key,
284            organization_id: config.organization_id,
285            private_key_id: config.private_key_id,
286            public_key: config.public_key,
287        }
288    }
289}
290
291impl From<TurnkeySignerConfigStorage> for TurnkeySignerConfig {
292    fn from(storage: TurnkeySignerConfigStorage) -> Self {
293        Self {
294            api_public_key: storage.api_public_key,
295            api_private_key: storage.api_private_key,
296            organization_id: storage.organization_id,
297            private_key_id: storage.private_key_id,
298            public_key: storage.public_key,
299        }
300    }
301}
302
303impl From<CdpSignerConfig> for CdpSignerConfigStorage {
304    fn from(config: CdpSignerConfig) -> Self {
305        Self {
306            api_key_id: config.api_key_id,
307            api_key_secret: config.api_key_secret,
308            wallet_secret: config.wallet_secret,
309            account_address: config.account_address,
310        }
311    }
312}
313
314impl From<CdpSignerConfigStorage> for CdpSignerConfig {
315    fn from(storage: CdpSignerConfigStorage) -> Self {
316        Self {
317            api_key_id: storage.api_key_id,
318            api_key_secret: storage.api_key_secret,
319            wallet_secret: storage.wallet_secret,
320            account_address: storage.account_address,
321        }
322    }
323}
324
325impl From<GoogleCloudKmsSignerConfig> for GoogleCloudKmsSignerConfigStorage {
326    fn from(config: GoogleCloudKmsSignerConfig) -> Self {
327        Self {
328            service_account: config.service_account.into(),
329            key: config.key.into(),
330        }
331    }
332}
333
334impl From<GoogleCloudKmsSignerConfigStorage> for GoogleCloudKmsSignerConfig {
335    fn from(storage: GoogleCloudKmsSignerConfigStorage) -> Self {
336        Self {
337            service_account: storage.service_account.into(),
338            key: storage.key.into(),
339        }
340    }
341}
342
343impl From<GoogleCloudKmsSignerServiceAccountConfig>
344    for GoogleCloudKmsSignerServiceAccountConfigStorage
345{
346    fn from(config: GoogleCloudKmsSignerServiceAccountConfig) -> Self {
347        Self {
348            private_key: config.private_key,
349            private_key_id: config.private_key_id,
350            project_id: config.project_id,
351            client_email: config.client_email,
352            client_id: config.client_id,
353            auth_uri: config.auth_uri,
354            token_uri: config.token_uri,
355            auth_provider_x509_cert_url: config.auth_provider_x509_cert_url,
356            client_x509_cert_url: config.client_x509_cert_url,
357            universe_domain: config.universe_domain,
358        }
359    }
360}
361
362impl From<GoogleCloudKmsSignerServiceAccountConfigStorage>
363    for GoogleCloudKmsSignerServiceAccountConfig
364{
365    fn from(storage: GoogleCloudKmsSignerServiceAccountConfigStorage) -> Self {
366        Self {
367            private_key: storage.private_key,
368            private_key_id: storage.private_key_id,
369            project_id: storage.project_id,
370            client_email: storage.client_email,
371            client_id: storage.client_id,
372            auth_uri: storage.auth_uri,
373            token_uri: storage.token_uri,
374            auth_provider_x509_cert_url: storage.auth_provider_x509_cert_url,
375            client_x509_cert_url: storage.client_x509_cert_url,
376            universe_domain: storage.universe_domain,
377        }
378    }
379}
380
381impl From<GoogleCloudKmsSignerKeyConfig> for GoogleCloudKmsSignerKeyConfigStorage {
382    fn from(config: GoogleCloudKmsSignerKeyConfig) -> Self {
383        Self {
384            location: config.location,
385            key_ring_id: config.key_ring_id,
386            key_id: config.key_id,
387            key_version: config.key_version,
388        }
389    }
390}
391
392impl From<GoogleCloudKmsSignerKeyConfigStorage> for GoogleCloudKmsSignerKeyConfig {
393    fn from(storage: GoogleCloudKmsSignerKeyConfigStorage) -> Self {
394        Self {
395            location: storage.location,
396            key_ring_id: storage.key_ring_id,
397            key_id: storage.key_id,
398            key_version: storage.key_version,
399        }
400    }
401}
402
403impl SignerRepoModel {
404    /// Validates the repository model using core validation logic
405    pub fn validate(&self) -> Result<(), SignerValidationError> {
406        let core_signer = Signer::from(self.clone());
407        core_signer.validate()
408    }
409}
410
411impl From<SignerConfig> for SignerConfigStorage {
412    fn from(config: SignerConfig) -> Self {
413        match config {
414            SignerConfig::Local(local) => SignerConfigStorage::Local(local.into()),
415            SignerConfig::Vault(vault) => SignerConfigStorage::Vault(vault.into()),
416            SignerConfig::VaultTransit(vault_transit) => {
417                SignerConfigStorage::VaultTransit(vault_transit.into())
418            }
419            SignerConfig::AwsKms(aws_kms) => SignerConfigStorage::AwsKms(aws_kms.into()),
420            SignerConfig::Turnkey(turnkey) => SignerConfigStorage::Turnkey(turnkey.into()),
421            SignerConfig::Cdp(cdp) => SignerConfigStorage::Cdp(cdp.into()),
422            SignerConfig::GoogleCloudKms(gcp) => SignerConfigStorage::GoogleCloudKms(gcp.into()),
423        }
424    }
425}
426
427impl From<SignerConfigStorage> for SignerConfig {
428    fn from(storage: SignerConfigStorage) -> Self {
429        match storage {
430            SignerConfigStorage::Local(local) => SignerConfig::Local(local.into()),
431            SignerConfigStorage::Vault(vault) => SignerConfig::Vault(vault.into()),
432            SignerConfigStorage::VaultTransit(vault_transit) => {
433                SignerConfig::VaultTransit(vault_transit.into())
434            }
435            SignerConfigStorage::AwsKms(aws_kms) => SignerConfig::AwsKms(aws_kms.into()),
436            SignerConfigStorage::Turnkey(turnkey) => SignerConfig::Turnkey(turnkey.into()),
437            SignerConfigStorage::Cdp(cdp) => SignerConfig::Cdp(cdp.into()),
438            SignerConfigStorage::GoogleCloudKms(gcp) => SignerConfig::GoogleCloudKms(gcp.into()),
439        }
440    }
441}
442
443impl SignerConfigStorage {
444    /// Get local signer config, returns error if not a local signer
445    pub fn get_local(&self) -> Option<&LocalSignerConfigStorage> {
446        match self {
447            Self::Local(config) => Some(config),
448            _ => None,
449        }
450    }
451
452    /// Get vault transit signer config, returns error if not a vault transit signer
453    pub fn get_vault_transit(&self) -> Option<&VaultTransitSignerConfigStorage> {
454        match self {
455            Self::VaultTransit(config) => Some(config),
456            _ => None,
457        }
458    }
459
460    /// Get vault signer config, returns error if not a vault signer
461    pub fn get_vault(&self) -> Option<&VaultSignerConfigStorage> {
462        match self {
463            Self::Vault(config) => Some(config),
464            _ => None,
465        }
466    }
467
468    /// Get turnkey signer config, returns error if not a turnkey signer
469    pub fn get_turnkey(&self) -> Option<&TurnkeySignerConfigStorage> {
470        match self {
471            Self::Turnkey(config) => Some(config),
472            _ => None,
473        }
474    }
475
476    /// Get CDP signer config, returns error if not a CDP signer
477    pub fn get_cdp(&self) -> Option<&CdpSignerConfigStorage> {
478        match self {
479            Self::Cdp(config) => Some(config),
480            _ => None,
481        }
482    }
483
484    /// Get google cloud kms signer config, returns error if not a google cloud kms signer
485    pub fn get_google_cloud_kms(&self) -> Option<&GoogleCloudKmsSignerConfigStorage> {
486        match self {
487            Self::GoogleCloudKms(config) => Some(config),
488            _ => None,
489        }
490    }
491
492    /// Get aws kms signer config, returns error if not an aws kms signer
493    pub fn get_aws_kms(&self) -> Option<&AwsKmsSignerConfigStorage> {
494        match self {
495            Self::AwsKms(config) => Some(config),
496            _ => None,
497        }
498    }
499}
500
501#[cfg(test)]
502mod tests {
503    use super::*;
504    use crate::models::signer::{LocalSignerConfig, SignerConfig};
505    use secrets::SecretVec;
506
507    #[test]
508    fn test_from_core_signer() {
509        let config = LocalSignerConfig {
510            raw_key: SecretVec::new(32, |v| v.fill(1)),
511        };
512
513        let core =
514            crate::models::signer::Signer::new("test-id".to_string(), SignerConfig::Local(config));
515
516        let repo_model = SignerRepoModel::from(core);
517        assert_eq!(repo_model.id, "test-id");
518        assert!(matches!(repo_model.config, SignerConfigStorage::Local(_)));
519    }
520
521    #[test]
522    fn test_to_core_signer() {
523        use crate::models::signer::AwsKmsSignerConfigStorage;
524
525        let domain_config = AwsKmsSignerConfigStorage {
526            region: Some("us-east-1".to_string()),
527            key_id: "test-key".to_string(),
528        };
529
530        let repo_model = SignerRepoModel {
531            id: "test-id".to_string(),
532            config: SignerConfigStorage::AwsKms(domain_config),
533        };
534
535        let core = Signer::from(repo_model);
536        assert_eq!(core.id, "test-id");
537        assert_eq!(
538            core.signer_type(),
539            crate::models::signer::SignerType::AwsKms
540        );
541    }
542
543    #[test]
544    fn test_validation() {
545        use secrets::SecretVec;
546
547        let domain_config = LocalSignerConfig {
548            raw_key: SecretVec::new(32, |v| v.fill(1)),
549        };
550        // Convert to storage config properly
551        let storage_config = LocalSignerConfigStorage::from(domain_config);
552
553        let repo_model = SignerRepoModel {
554            id: "test-id".to_string(),
555            config: SignerConfigStorage::Local(storage_config),
556        };
557
558        assert!(repo_model.validate().is_ok());
559    }
560
561    #[test]
562    fn test_local_config_storage_conversion() {
563        let domain_config = LocalSignerConfig {
564            raw_key: SecretVec::new(4, |v| v.copy_from_slice(&[1, 2, 3, 4])),
565        };
566
567        let storage_config = LocalSignerConfigStorage::from(domain_config.clone());
568        let converted_back = LocalSignerConfig::from(storage_config);
569
570        // Compare the actual secret data
571        let original_data = domain_config.raw_key.borrow();
572        let converted_data = converted_back.raw_key.borrow();
573        assert_eq!(*original_data, *converted_data);
574    }
575
576    #[test]
577    fn test_cdp_config_storage_conversion() {
578        use crate::models::SecretString;
579
580        let domain_config = CdpSignerConfig {
581            api_key_id: "test-api-key-id".to_string(),
582            api_key_secret: SecretString::new("test-api-secret"),
583            wallet_secret: SecretString::new("test-wallet-secret"),
584            account_address: "0x1234567890123456789012345678901234567890".to_string(),
585        };
586
587        let storage_config = CdpSignerConfigStorage::from(domain_config.clone());
588        let converted_back = CdpSignerConfig::from(storage_config);
589
590        assert_eq!(domain_config.api_key_id, converted_back.api_key_id);
591        assert_eq!(
592            domain_config.account_address,
593            converted_back.account_address
594        );
595        assert_eq!(
596            domain_config.api_key_secret.to_str(),
597            converted_back.api_key_secret.to_str()
598        );
599        assert_eq!(
600            domain_config.wallet_secret.to_str(),
601            converted_back.wallet_secret.to_str()
602        );
603    }
604
605    #[test]
606    fn test_signer_config_storage_get_cdp() {
607        use crate::models::SecretString;
608
609        let cdp_storage = CdpSignerConfigStorage {
610            api_key_id: "test-id".to_string(),
611            api_key_secret: SecretString::new("secret"),
612            wallet_secret: SecretString::new("wallet-secret"),
613            account_address: "0x1234567890123456789012345678901234567890".to_string(),
614        };
615
616        let config_storage = SignerConfigStorage::Cdp(cdp_storage);
617        let retrieved_cdp = config_storage.get_cdp();
618        assert!(retrieved_cdp.is_some());
619        assert_eq!(retrieved_cdp.unwrap().api_key_id, "test-id");
620    }
621
622    #[test]
623    fn test_signer_config_storage_get_cdp_from_non_cdp() {
624        let aws_storage = AwsKmsSignerConfigStorage {
625            region: Some("us-east-1".to_string()),
626            key_id: "test-key".to_string(),
627        };
628
629        let config_storage = SignerConfigStorage::AwsKms(aws_storage);
630        let retrieved_cdp = config_storage.get_cdp();
631        assert!(retrieved_cdp.is_none());
632    }
633}