openzeppelin_relayer/services/signer/stellar/
mod.rs1mod aws_kms_signer;
5mod google_cloud_kms_signer;
6mod local_signer;
7mod turnkey_signer;
8mod vault_signer;
9
10use async_trait::async_trait;
11use aws_kms_signer::*;
12use google_cloud_kms_signer::*;
13use local_signer::*;
14use turnkey_signer::*;
15use vault_signer::*;
16
17use soroban_rs::xdr::SignatureHint;
18
19use crate::{
20 domain::{SignDataRequest, SignDataResponse, SignTransactionResponse, SignTypedDataRequest},
21 models::{
22 Address, NetworkTransactionData, Signer as SignerDomainModel, SignerConfig,
23 SignerRepoModel, SignerType, TransactionRepoModel, VaultSignerConfig,
24 },
25 services::{
26 signer::{SignXdrTransactionResponseStellar, Signer, SignerError, SignerFactoryError},
27 AwsKmsService, GoogleCloudKmsService, TurnkeyService, VaultConfig, VaultService,
28 },
29};
30
31use super::DataSignerTrait;
32
33fn derive_signature_hint(address: &Address) -> Result<SignatureHint, SignerError> {
35 match address {
36 Address::Stellar(addr) => {
37 let pk = stellar_strkey::ed25519::PublicKey::from_string(addr).map_err(|e| {
38 SignerError::SigningError(format!("Failed to parse Stellar address '{addr}': {e}"))
39 })?;
40 let hint_bytes: [u8; 4] = pk.0[28..].try_into().map_err(|_| {
42 SignerError::SigningError(
43 "Failed to create signature hint from public key".to_string(),
44 )
45 })?;
46 Ok(SignatureHint(hint_bytes))
47 }
48 _ => Err(SignerError::SigningError(format!(
49 "Expected Stellar address, got: {address:?}"
50 ))),
51 }
52}
53
54#[cfg(test)]
55use mockall::automock;
56
57#[cfg_attr(test, automock)]
58#[async_trait]
63pub trait StellarSignTrait: Sync + Send {
64 async fn sign_xdr_transaction(
75 &self,
76 unsigned_xdr: &str,
77 network_passphrase: &str,
78 ) -> Result<SignXdrTransactionResponseStellar, SignerError>;
79}
80
81pub enum StellarSigner {
82 Local(Box<LocalSigner>),
83 Vault(VaultSigner<VaultService>),
84 GoogleCloudKms(Box<GoogleCloudKmsSigner>),
85 AwsKms(AwsKmsSigner),
86 Turnkey(TurnkeySigner),
87}
88
89#[async_trait]
90impl Signer for StellarSigner {
91 async fn address(&self) -> Result<Address, SignerError> {
92 match self {
93 Self::Local(s) => s.address().await,
94 Self::Vault(s) => s.address().await,
95 Self::GoogleCloudKms(s) => s.address().await,
96 Self::AwsKms(s) => s.address().await,
97 Self::Turnkey(s) => s.address().await,
98 }
99 }
100
101 async fn sign_transaction(
102 &self,
103 tx: NetworkTransactionData,
104 ) -> Result<SignTransactionResponse, SignerError> {
105 match self {
106 Self::Local(s) => s.sign_transaction(tx).await,
107 Self::Vault(s) => s.sign_transaction(tx).await,
108 Self::GoogleCloudKms(s) => s.sign_transaction(tx).await,
109 Self::AwsKms(s) => s.sign_transaction(tx).await,
110 Self::Turnkey(s) => s.sign_transaction(tx).await,
111 }
112 }
113}
114
115#[async_trait]
116impl StellarSignTrait for StellarSigner {
117 async fn sign_xdr_transaction(
118 &self,
119 unsigned_xdr: &str,
120 network_passphrase: &str,
121 ) -> Result<SignXdrTransactionResponseStellar, SignerError> {
122 match self {
123 Self::Local(s) => {
124 s.sign_xdr_transaction(unsigned_xdr, network_passphrase)
125 .await
126 }
127 Self::Vault(s) => {
128 s.sign_xdr_transaction(unsigned_xdr, network_passphrase)
129 .await
130 }
131 Self::GoogleCloudKms(s) => {
132 s.sign_xdr_transaction(unsigned_xdr, network_passphrase)
133 .await
134 }
135 Self::AwsKms(s) => {
136 s.sign_xdr_transaction(unsigned_xdr, network_passphrase)
137 .await
138 }
139 Self::Turnkey(s) => {
140 s.sign_xdr_transaction(unsigned_xdr, network_passphrase)
141 .await
142 }
143 }
144 }
145}
146
147pub struct StellarSignerFactory;
148
149impl StellarSignerFactory {
150 pub async fn create_stellar_signer(
151 m: SignerDomainModel,
152 ) -> Result<StellarSigner, SignerFactoryError> {
153 let signer = match &m.config {
157 SignerConfig::Local(_) => {
158 let local_signer = LocalSigner::new(&m)?;
159 StellarSigner::Local(Box::new(local_signer))
160 }
161 SignerConfig::Vault(config) => {
162 let vault_config = VaultConfig::new(
163 config.address.clone(),
164 config.role_id.clone(),
165 config.secret_id.clone(),
166 config.namespace.clone(),
167 config
168 .mount_point
169 .clone()
170 .unwrap_or_else(|| "secret".to_string()),
171 None,
172 );
173 let vault_service = VaultService::new(vault_config);
174
175 StellarSigner::Vault(VaultSigner::new(
176 m.id.clone(),
177 config.clone(),
178 vault_service,
179 ))
180 }
181 SignerConfig::GoogleCloudKms(config) => {
182 let service = GoogleCloudKmsService::new(config)
183 .map_err(|e| SignerFactoryError::CreationFailed(e.to_string()))?;
184 StellarSigner::GoogleCloudKms(Box::new(GoogleCloudKmsSigner::new(service)))
185 }
186 SignerConfig::Turnkey(config) => {
187 let service = TurnkeyService::new(config.clone())
188 .map_err(|e| SignerFactoryError::CreationFailed(e.to_string()))?;
189 StellarSigner::Turnkey(TurnkeySigner::new(service))
190 }
191 SignerConfig::AwsKms(config) => {
192 let aws_kms_service = AwsKmsService::new(config.clone()).await.map_err(|e| {
196 SignerFactoryError::InvalidConfig(format!(
197 "Failed to create AWS KMS service: {e}"
198 ))
199 })?;
200 StellarSigner::AwsKms(AwsKmsSigner::new(aws_kms_service))
201 }
202 SignerConfig::AzureKeyVault(_) => {
203 return Err(SignerFactoryError::UnsupportedType(
204 "Azure Key Vault".into(),
205 ))
206 }
207 SignerConfig::VaultTransit(_) => {
208 return Err(SignerFactoryError::UnsupportedType("Vault Transit".into()))
209 }
210 SignerConfig::Cdp(_) => return Err(SignerFactoryError::UnsupportedType("CDP".into())),
211 };
212 Ok(signer)
213 }
214}
215
216#[cfg(test)]
217mod tests {
218 use super::*;
219
220 #[test]
221 fn test_derive_signature_hint_valid_stellar_address() {
222 let pk = stellar_strkey::ed25519::PublicKey([0u8; 32]);
223 let address = Address::Stellar(pk.to_string());
224
225 let hint = derive_signature_hint(&address).unwrap();
226 assert_eq!(hint.0, [0u8; 4]);
228 }
229
230 #[test]
231 fn test_derive_signature_hint_extracts_last_four_bytes() {
232 let mut key_bytes = [0u8; 32];
233 key_bytes[28] = 0xAA;
234 key_bytes[29] = 0xBB;
235 key_bytes[30] = 0xCC;
236 key_bytes[31] = 0xDD;
237 let pk = stellar_strkey::ed25519::PublicKey(key_bytes);
238 let address = Address::Stellar(pk.to_string());
239
240 let hint = derive_signature_hint(&address).unwrap();
241 assert_eq!(hint.0, [0xAA, 0xBB, 0xCC, 0xDD]);
242 }
243
244 #[test]
245 fn test_derive_signature_hint_invalid_stellar_address() {
246 let address = Address::Stellar("INVALID_ADDRESS".to_string());
247 let result = derive_signature_hint(&address);
248 assert!(result.is_err());
249 match result.unwrap_err() {
250 SignerError::SigningError(msg) => {
251 assert!(msg.contains("Failed to parse Stellar address"));
252 }
253 e => panic!("Expected SigningError, got: {e:?}"),
254 }
255 }
256
257 #[test]
258 fn test_derive_signature_hint_non_stellar_address() {
259 let address = Address::Evm([0u8; 20]);
260 let result = derive_signature_hint(&address);
261 assert!(result.is_err());
262 match result.unwrap_err() {
263 SignerError::SigningError(msg) => {
264 assert!(msg.contains("Expected Stellar address"));
265 }
266 e => panic!("Expected SigningError, got: {e:?}"),
267 }
268 }
269
270 #[test]
271 fn test_derive_signature_hint_solana_address_rejected() {
272 let address = Address::Solana("SomeBase58Address".to_string());
273 let result = derive_signature_hint(&address);
274 assert!(result.is_err());
275 }
276}