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 fn create_stellar_signer(
151 m: &SignerDomainModel,
152 ) -> Result<StellarSigner, SignerFactoryError> {
153 let signer = match &m.config {
154 SignerConfig::Local(_) => {
155 let local_signer = LocalSigner::new(m)?;
156 StellarSigner::Local(Box::new(local_signer))
157 }
158 SignerConfig::Vault(config) => {
159 let vault_config = VaultConfig::new(
160 config.address.clone(),
161 config.role_id.clone(),
162 config.secret_id.clone(),
163 config.namespace.clone(),
164 config
165 .mount_point
166 .clone()
167 .unwrap_or_else(|| "secret".to_string()),
168 None,
169 );
170 let vault_service = VaultService::new(vault_config);
171
172 StellarSigner::Vault(VaultSigner::new(
173 m.id.clone(),
174 config.clone(),
175 vault_service,
176 ))
177 }
178 SignerConfig::GoogleCloudKms(config) => {
179 let service = GoogleCloudKmsService::new(config)
180 .map_err(|e| SignerFactoryError::CreationFailed(e.to_string()))?;
181 StellarSigner::GoogleCloudKms(Box::new(GoogleCloudKmsSigner::new(service)))
182 }
183 SignerConfig::Turnkey(config) => {
184 let service = TurnkeyService::new(config.clone())
185 .map_err(|e| SignerFactoryError::CreationFailed(e.to_string()))?;
186 StellarSigner::Turnkey(TurnkeySigner::new(service))
187 }
188 SignerConfig::AwsKms(config) => {
189 let aws_kms_service = futures::executor::block_on(AwsKmsService::new(
190 config.clone(),
191 ))
192 .map_err(|e| {
193 SignerFactoryError::InvalidConfig(format!(
194 "Failed to create AWS KMS service: {e}"
195 ))
196 })?;
197 StellarSigner::AwsKms(AwsKmsSigner::new(aws_kms_service))
198 }
199 SignerConfig::VaultTransit(_) => {
200 return Err(SignerFactoryError::UnsupportedType("Vault Transit".into()))
201 }
202 SignerConfig::Cdp(_) => return Err(SignerFactoryError::UnsupportedType("CDP".into())),
203 };
204 Ok(signer)
205 }
206}
207
208#[cfg(test)]
209mod tests {
210 use super::*;
211
212 #[test]
213 fn test_derive_signature_hint_valid_stellar_address() {
214 let pk = stellar_strkey::ed25519::PublicKey([0u8; 32]);
215 let address = Address::Stellar(pk.to_string());
216
217 let hint = derive_signature_hint(&address).unwrap();
218 assert_eq!(hint.0, [0u8; 4]);
220 }
221
222 #[test]
223 fn test_derive_signature_hint_extracts_last_four_bytes() {
224 let mut key_bytes = [0u8; 32];
225 key_bytes[28] = 0xAA;
226 key_bytes[29] = 0xBB;
227 key_bytes[30] = 0xCC;
228 key_bytes[31] = 0xDD;
229 let pk = stellar_strkey::ed25519::PublicKey(key_bytes);
230 let address = Address::Stellar(pk.to_string());
231
232 let hint = derive_signature_hint(&address).unwrap();
233 assert_eq!(hint.0, [0xAA, 0xBB, 0xCC, 0xDD]);
234 }
235
236 #[test]
237 fn test_derive_signature_hint_invalid_stellar_address() {
238 let address = Address::Stellar("INVALID_ADDRESS".to_string());
239 let result = derive_signature_hint(&address);
240 assert!(result.is_err());
241 match result.unwrap_err() {
242 SignerError::SigningError(msg) => {
243 assert!(msg.contains("Failed to parse Stellar address"));
244 }
245 e => panic!("Expected SigningError, got: {e:?}"),
246 }
247 }
248
249 #[test]
250 fn test_derive_signature_hint_non_stellar_address() {
251 let address = Address::Evm([0u8; 20]);
252 let result = derive_signature_hint(&address);
253 assert!(result.is_err());
254 match result.unwrap_err() {
255 SignerError::SigningError(msg) => {
256 assert!(msg.contains("Expected Stellar address"));
257 }
258 e => panic!("Expected SigningError, got: {e:?}"),
259 }
260 }
261
262 #[test]
263 fn test_derive_signature_hint_solana_address_rejected() {
264 let address = Address::Solana("SomeBase58Address".to_string());
265 let result = derive_signature_hint(&address);
266 assert!(result.is_err());
267 }
268}