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
40impl From<&[u8; 32]> for Signer {
41 fn from(bytes: &[u8; 32]) -> Self {
42 Signer::new(SigningKey::from_bytes(bytes))
43 }
44}
45
46#[derive(Clone)]
52pub struct Signer {
53 signing_key: SigningKey,
55 public_key: PublicKey,
57 account_id: AccountId,
59}
60
61impl Signer {
62 pub fn new(signing_key: SigningKey) -> Self {
72 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
73 let account_id = AccountId(XDRPublicKey::PublicKeyTypeEd25519(public_key.0.into()));
74
75 Self {
76 signing_key,
77 public_key,
78 account_id,
79 }
80 }
81
82 pub fn public_key(&self) -> PublicKey {
88 self.public_key
89 }
90
91 pub fn account_id(&self) -> AccountId {
97 self.account_id.clone()
98 }
99
100 pub fn sign_transaction(
117 &self,
118 tx: &Transaction,
119 network_id: &Hash,
120 ) -> Result<DecoratedSignature, SorobanHelperError> {
121 let signature_payload = TransactionSignaturePayload {
122 network_id: network_id.clone(),
123 tagged_transaction: TransactionSignaturePayloadTaggedTransaction::Tx(tx.clone()),
124 };
125
126 let tx_hash: [u8; 32] = Sha256::digest(
127 signature_payload
128 .to_xdr(Limits::none())
129 .map_err(|e| SorobanHelperError::XdrEncodingFailed(e.to_string()))?,
130 )
131 .into();
132
133 let hint = SignatureHint(
134 self.signing_key.verifying_key().to_bytes()[28..]
135 .try_into()
136 .map_err(|_| {
137 SorobanHelperError::SigningFailed("Failed to create signature hint".to_string())
138 })?,
139 );
140
141 let signature = Signature(
142 self.signing_key
143 .clone()
144 .sign(&tx_hash)
145 .to_bytes()
146 .to_vec()
147 .try_into()
148 .map_err(|_| {
149 SorobanHelperError::SigningFailed(
150 "Failed to convert signature to XDR".to_string(),
151 )
152 })?,
153 );
154
155 Ok(DecoratedSignature { hint, signature })
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use stellar_xdr::curr::BytesM;
162
163 use crate::mock::mock_transaction;
164
165 use super::*;
166
167 #[test]
168 fn test_public_key() {
169 let signing_key = SigningKey::from_bytes(&[42; 32]);
170 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
171
172 let signer = Signer::new(signing_key);
173 assert_eq!(signer.public_key(), public_key);
174 }
175
176 #[test]
177 fn test_account_id() {
178 let signing_key = SigningKey::from_bytes(&[42; 32]);
179 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
180 let account_id = AccountId(XDRPublicKey::PublicKeyTypeEd25519(public_key.0.into()));
181
182 let signer = Signer::new(signing_key);
183 assert_eq!(signer.account_id(), account_id);
184 }
185
186 #[test]
187 fn test_sign_transaction() {
188 let signing_key = SigningKey::from_bytes(&[42; 32]);
189 let public_key = PublicKey(*signing_key.verifying_key().as_bytes());
190 let account_id = AccountId(XDRPublicKey::PublicKeyTypeEd25519(public_key.0.into()));
191
192 let signer = Signer::new(signing_key);
193
194 let transaction = mock_transaction(account_id, vec![]);
195 let network_id = Hash::from([42; 32]);
196
197 let decorated_signature = signer.sign_transaction(&transaction, &network_id).unwrap();
198
199 let hint_vec = hex::decode("3d368d61").expect("Invalid hex");
201 let hint: [u8; 4] = hint_vec[..4]
202 .try_into()
203 .expect("slice with incorrect length");
204
205 let signature_vec = hex::decode("c84612be60b83b3e13e18880b6f35c94bda449a53103367b78e211f0a7614dc0df02e45539a4879fc37fb908d7983efba2d7019c1ef5732f0c1331b808eec102").expect("Invalid hex");
207 let signature_bytes: BytesM<64> = signature_vec
208 .try_into()
209 .expect("slice with incorrect length");
210
211 assert_eq!(decorated_signature.hint, SignatureHint(hint));
212 assert_eq!(decorated_signature.signature, Signature(signature_bytes));
213 }
214}