1use crate::error::SorobanHelperError;
31use ed25519_dalek::{SigningKey, ed25519::signature::SignerMut};
32use sha2::{Digest, Sha256};
33use stellar_strkey::ed25519::PublicKey;
34use stellar_xdr::curr::{
35 AccountId, DecoratedSignature, Hash, Limits, PublicKey as XDRPublicKey, Signature,
36 SignatureHint, Transaction, TransactionSignaturePayload,
37 TransactionSignaturePayloadTaggedTransaction, WriteXdr,
38};
39
40#[derive(Clone)]
46pub struct Signer {
47 signing_key: SigningKey,
49 public_key: PublicKey,
51 account_id: AccountId,
53}
54
55impl Signer {
56 pub fn new(signing_key: SigningKey) -> Self {
66 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
67 let account_id = AccountId(XDRPublicKey::PublicKeyTypeEd25519(public_key.0.into()));
68
69 Self {
70 signing_key,
71 public_key,
72 account_id,
73 }
74 }
75
76 pub fn public_key(&self) -> PublicKey {
82 self.public_key
83 }
84
85 pub fn account_id(&self) -> AccountId {
91 self.account_id.clone()
92 }
93
94 pub fn sign_transaction(
111 &self,
112 tx: &Transaction,
113 network_id: &Hash,
114 ) -> Result<DecoratedSignature, SorobanHelperError> {
115 let signature_payload = TransactionSignaturePayload {
116 network_id: network_id.clone(),
117 tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()),
118 };
119
120 let tx_hash: [u8; 32] = Sha256::digest(
121 signature_payload
122 .to_xdr(Limits::none())
123 .map_err(|e| SorobanHelperError::XdrEncodingFailed(e.to_string()))?,
124 )
125 .into();
126
127 let hint = SignatureHint(
128 self.signing_key.verifying_key().to_bytes()[28..]
129 .try_into()
130 .map_err(|_| {
131 SorobanHelperError::SigningFailed("Failed to create signature hint".to_string())
132 })?,
133 );
134
135 let signature = Signature(
136 self.signing_key
137 .clone()
138 .sign(&tx_hash)
139 .to_bytes()
140 .to_vec()
141 .try_into()
142 .map_err(|_| {
143 SorobanHelperError::SigningFailed(
144 "Failed to convert signature to XDR".to_string(),
145 )
146 })?,
147 );
148
149 Ok(DecoratedSignature { hint, signature })
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use stellar_xdr::curr::BytesM;
156
157 use crate::mock::mock_transaction;
158
159 use super::*;
160
161 #[test]
162 fn test_public_key() {
163 let signing_key = SigningKey::from_bytes(&[42; 32]);
164 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
165
166 let signer = Signer::new(signing_key);
167 assert_eq!(signer.public_key(), public_key);
168 }
169
170 #[test]
171 fn test_account_id() {
172 let signing_key = SigningKey::from_bytes(&[42; 32]);
173 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
174 let account_id = AccountId(XDRPublicKey::PublicKeyTypeEd25519(public_key.0.into()));
175
176 let signer = Signer::new(signing_key);
177 assert_eq!(signer.account_id(), account_id);
178 }
179
180 #[test]
181 fn test_sign_transaction() {
182 let signing_key = SigningKey::from_bytes(&[42; 32]);
183 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
184 let account_id = AccountId(XDRPublicKey::PublicKeyTypeEd25519(public_key.0.into()));
185
186 let signer = Signer::new(signing_key);
187
188 let transaction = mock_transaction(account_id);
189 let network_id = Hash::from([42; 32]);
190
191 let decorated_signature = signer.sign_transaction(&transaction, &network_id).unwrap();
192
193 let hint_vec = hex::decode("3d368d61").expect("Invalid hex");
195 let hint: [u8; 4] = hint_vec[..4]
196 .try_into()
197 .expect("slice with incorrect length");
198
199 let signature_vec = hex::decode("c84612be60b83b3e13e18880b6f35c94bda449a53103367b78e211f0a7614dc0df02e45539a4879fc37fb908d7983efba2d7019c1ef5732f0c1331b808eec102").expect("Invalid hex");
201 let signature_bytes: BytesM<64> = signature_vec
202 .try_into()
203 .expect("slice with incorrect length");
204
205 assert_eq!(decorated_signature.hint, SignatureHint(hint));
206 assert_eq!(decorated_signature.signature, Signature(signature_bytes));
207 }
208}