1use super::{LocalSigner, LocalSignerError};
4use alloy_primitives::{hex, B256};
5use alloy_signer::utils::secret_key_to_address;
6use k256::{
7 ecdsa::{self, SigningKey},
8 FieldBytes, NonZeroScalar, SecretKey as K256SecretKey,
9};
10use rand::{CryptoRng, Rng};
11use std::str::FromStr;
12
13#[cfg(feature = "keystore")]
14use std::path::Path;
15
16impl LocalSigner<SigningKey> {
17 #[doc(alias = "from_private_key")]
22 #[doc(alias = "new_private_key")]
23 #[doc(alias = "new_pk")]
24 #[inline]
25 pub fn from_signing_key(credential: SigningKey) -> Self {
26 let address = secret_key_to_address(&credential);
27 Self::new_with_credential(credential, address, None)
28 }
29
30 #[inline]
35 pub fn from_bytes(bytes: &B256) -> Result<Self, ecdsa::Error> {
36 Self::from_field_bytes((&bytes.0).into())
37 }
38
39 #[inline]
42 pub fn from_field_bytes(bytes: &FieldBytes) -> Result<Self, ecdsa::Error> {
43 SigningKey::from_bytes(bytes).map(Self::from_signing_key)
44 }
45
46 #[inline]
50 pub fn from_slice(bytes: &[u8]) -> Result<Self, ecdsa::Error> {
51 SigningKey::from_slice(bytes).map(Self::from_signing_key)
52 }
53
54 #[inline]
56 pub fn random() -> Self {
57 Self::random_with(&mut rand::thread_rng())
58 }
59
60 #[inline]
62 pub fn random_with<R: Rng + CryptoRng>(rng: &mut R) -> Self {
63 Self::from_signing_key(SigningKey::random(rng))
64 }
65
66 #[inline]
74 pub fn as_nonzero_scalar(&self) -> &NonZeroScalar {
75 self.credential.as_nonzero_scalar()
76 }
77
78 #[inline]
80 pub fn to_bytes(&self) -> B256 {
81 B256::new(<[u8; 32]>::from(self.to_field_bytes()))
82 }
83
84 #[inline]
86 pub fn to_field_bytes(&self) -> FieldBytes {
87 self.credential.to_bytes()
88 }
89}
90
91#[cfg(feature = "keystore")]
92impl LocalSigner<SigningKey> {
93 #[inline]
98 pub fn new_keystore<P, R, S>(
99 dir: P,
100 rng: &mut R,
101 password: S,
102 name: Option<&str>,
103 ) -> Result<(Self, String), LocalSignerError>
104 where
105 P: AsRef<Path>,
106 R: Rng + CryptoRng,
107 S: AsRef<[u8]>,
108 {
109 let (secret, uuid) = eth_keystore::new(dir, rng, password, name)?;
110 Ok((Self::from_slice(&secret)?, uuid))
111 }
112
113 #[inline]
115 pub fn decrypt_keystore<P, S>(keypath: P, password: S) -> Result<Self, LocalSignerError>
116 where
117 P: AsRef<Path>,
118 S: AsRef<[u8]>,
119 {
120 let secret = eth_keystore::decrypt_key(keypath, password)?;
121 Ok(Self::from_slice(&secret)?)
122 }
123
124 #[inline]
129 pub fn encrypt_keystore<P, R, B, S>(
130 keypath: P,
131 rng: &mut R,
132 pk: B,
133 password: S,
134 name: Option<&str>,
135 ) -> Result<(Self, String), LocalSignerError>
136 where
137 P: AsRef<Path>,
138 R: Rng + CryptoRng,
139 B: AsRef<[u8]>,
140 S: AsRef<[u8]>,
141 {
142 let pk = pk.as_ref();
143 let uuid = eth_keystore::encrypt_key(keypath, rng, pk, password, name)?;
144 Ok((Self::from_slice(pk)?, uuid))
145 }
146}
147
148impl PartialEq for LocalSigner<SigningKey> {
149 fn eq(&self, other: &Self) -> bool {
150 self.credential.to_bytes().eq(&other.credential.to_bytes())
151 && self.address == other.address
152 && self.chain_id == other.chain_id
153 }
154}
155
156impl From<SigningKey> for LocalSigner<SigningKey> {
157 fn from(value: SigningKey) -> Self {
158 Self::from_signing_key(value)
159 }
160}
161
162impl From<K256SecretKey> for LocalSigner<SigningKey> {
163 fn from(value: K256SecretKey) -> Self {
164 Self::from_signing_key(value.into())
165 }
166}
167
168impl FromStr for LocalSigner<SigningKey> {
169 type Err = LocalSignerError;
170
171 fn from_str(src: &str) -> Result<Self, Self::Err> {
172 let array = hex::decode_to_array::<_, 32>(src)?;
173 Ok(Self::from_slice(&array)?)
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use super::*;
180 use crate::{PrivateKeySigner, SignerSync};
181 use alloy_primitives::{address, b256};
182
183 #[cfg(feature = "keystore")]
184 use tempfile::tempdir;
185
186 #[test]
187 fn parse_pk() {
188 let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b";
189 let _pk: PrivateKeySigner = s.parse().unwrap();
190 }
191
192 #[test]
193 fn parse_short_key() {
194 let s = "6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea3";
195 assert!(s.len() < 64);
196 let pk = s.parse::<PrivateKeySigner>().unwrap_err();
197 match pk {
198 LocalSignerError::HexError(hex::FromHexError::InvalidStringLength) => {}
199 _ => panic!("Unexpected error"),
200 }
201 }
202
203 #[cfg(feature = "keystore")]
204 fn test_encrypted_json_keystore(key: LocalSigner<SigningKey>, uuid: &str, dir: &Path) {
205 let message = "Some data";
207 let signature = key.sign_message_sync(message.as_bytes()).unwrap();
208
209 let path = Path::new(dir).join(uuid);
212 let key2 = LocalSigner::<SigningKey>::decrypt_keystore(path.clone(), "randpsswd").unwrap();
213
214 let signature2 = key2.sign_message_sync(message.as_bytes()).unwrap();
215 assert_eq!(signature, signature2);
216
217 std::fs::remove_file(&path).unwrap();
218 }
219
220 #[test]
221 #[cfg(feature = "keystore")]
222 fn encrypted_json_keystore_new() {
223 let dir = tempdir().unwrap();
225 let mut rng = rand::thread_rng();
226 let (key, uuid) =
227 LocalSigner::<SigningKey>::new_keystore(&dir, &mut rng, "randpsswd", None).unwrap();
228
229 test_encrypted_json_keystore(key, &uuid, dir.path());
230 }
231
232 #[test]
233 #[cfg(feature = "keystore")]
234 fn encrypted_json_keystore_from_pk() {
235 let dir = tempdir().unwrap();
237 let mut rng = rand::thread_rng();
238
239 let private_key =
240 hex::decode("6f142508b4eea641e33cb2a0161221105086a84584c74245ca463a49effea30b")
241 .unwrap();
242
243 let (key, uuid) = LocalSigner::<SigningKey>::encrypt_keystore(
244 &dir,
245 &mut rng,
246 private_key,
247 "randpsswd",
248 None,
249 )
250 .unwrap();
251
252 test_encrypted_json_keystore(key, &uuid, dir.path());
253 }
254
255 #[test]
256 #[cfg(feature = "keystore-geth-compat")]
257 fn test_encrypted_json_keystore_with_address() {
258 use std::fs::File;
261
262 use eth_keystore::EthKeystore;
263 let dir = tempdir().unwrap();
264 let mut rng = rand::thread_rng();
265 let (key, uuid) =
266 LocalSigner::<SigningKey>::new_keystore(&dir, &mut rng, "randpsswd", None).unwrap();
267
268 let path = Path::new(dir.path()).join(uuid.clone());
269 let file = File::open(path).unwrap();
270 let keystore = serde_json::from_reader::<_, EthKeystore>(file).unwrap();
271
272 assert!(!keystore.address.is_zero());
273
274 test_encrypted_json_keystore(key, &uuid, dir.path());
275 }
276
277 #[test]
278 fn signs_msg() {
279 let message = "Some data";
280 let hash = alloy_primitives::utils::eip191_hash_message(message);
281 let key = LocalSigner::<SigningKey>::random_with(&mut rand::thread_rng());
282 let address = key.address;
283
284 let signature = key.sign_message_sync(message.as_bytes()).unwrap();
286
287 let recovered = signature.recover_address_from_msg(message).unwrap();
289 assert_eq!(recovered, address);
290
291 let recovered2 = signature.recover_address_from_prehash(&hash).unwrap();
293 assert_eq!(recovered2, address);
294 }
295
296 #[test]
297 #[cfg(feature = "eip712")]
298 fn typed_data() {
299 use alloy_dyn_abi::eip712::TypedData;
300 use alloy_primitives::{keccak256, Address, I256, U256};
301 use alloy_sol_types::{eip712_domain, sol, SolStruct};
302 use serde::Serialize;
303
304 sol! {
305 #[derive(Debug, Serialize)]
306 struct FooBar {
307 int256 foo;
308 uint256 bar;
309 bytes fizz;
310 bytes32 buzz;
311 string far;
312 address out;
313 }
314 }
315
316 let domain = eip712_domain! {
317 name: "Eip712Test",
318 version: "1",
319 chain_id: 1,
320 verifying_contract: address!("0000000000000000000000000000000000000001"),
321 salt: keccak256("eip712-test-75F0CCte"),
322 };
323 let foo_bar = FooBar {
324 foo: I256::try_from(10u64).unwrap(),
325 bar: U256::from(20u64),
326 fizz: b"fizz".to_vec().into(),
327 buzz: keccak256("buzz"),
328 far: "space".into(),
329 out: Address::ZERO,
330 };
331 let signer = LocalSigner::random();
332 let hash = foo_bar.eip712_signing_hash(&domain);
333 let sig = signer.sign_typed_data_sync(&foo_bar, &domain).unwrap();
334 assert_eq!(sig.recover_address_from_prehash(&hash).unwrap(), signer.address());
335 assert_eq!(signer.sign_hash_sync(&hash).unwrap(), sig);
336 let foo_bar_dynamic = TypedData::from_struct(&foo_bar, Some(domain));
337 let dynamic_hash = foo_bar_dynamic.eip712_signing_hash().unwrap();
338 let sig_dynamic = signer.sign_dynamic_typed_data_sync(&foo_bar_dynamic).unwrap();
339 assert_eq!(
340 sig_dynamic.recover_address_from_prehash(&dynamic_hash).unwrap(),
341 signer.address()
342 );
343 assert_eq!(signer.sign_hash_sync(&dynamic_hash).unwrap(), sig_dynamic);
344 }
345
346 #[test]
347 fn key_to_address() {
348 let signer: LocalSigner<SigningKey> =
349 "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
350 assert_eq!(signer.address, address!("7E5F4552091A69125d5DfCb7b8C2659029395Bdf"));
351
352 let signer: LocalSigner<SigningKey> =
353 "0000000000000000000000000000000000000000000000000000000000000002".parse().unwrap();
354 assert_eq!(signer.address, address!("2B5AD5c4795c026514f8317c7a215E218DcCD6cF"));
355
356 let signer: LocalSigner<SigningKey> =
357 "0000000000000000000000000000000000000000000000000000000000000003".parse().unwrap();
358 assert_eq!(signer.address, address!("6813Eb9362372EEF6200f3b1dbC3f819671cBA69"));
359 }
360
361 #[test]
362 fn conversions() {
363 let key = b256!("0000000000000000000000000000000000000000000000000000000000000001");
364
365 let signer_b256: LocalSigner<SigningKey> = LocalSigner::from_bytes(&key).unwrap();
366 assert_eq!(signer_b256.address, address!("7E5F4552091A69125d5DfCb7b8C2659029395Bdf"));
367 assert_eq!(signer_b256.chain_id, None);
368 assert_eq!(signer_b256.credential, SigningKey::from_bytes((&key.0).into()).unwrap());
369
370 let signer_str = LocalSigner::from_str(
371 "0000000000000000000000000000000000000000000000000000000000000001",
372 )
373 .unwrap();
374 assert_eq!(signer_str.address, signer_b256.address);
375 assert_eq!(signer_str.chain_id, signer_b256.chain_id);
376 assert_eq!(signer_str.credential, signer_b256.credential);
377 assert_eq!(signer_str.to_bytes(), key);
378 assert_eq!(signer_str.to_field_bytes(), key.0.into());
379
380 let signer_slice = LocalSigner::from_slice(&key[..]).unwrap();
381 assert_eq!(signer_slice.address, signer_b256.address);
382 assert_eq!(signer_slice.chain_id, signer_b256.chain_id);
383 assert_eq!(signer_slice.credential, signer_b256.credential);
384 assert_eq!(signer_slice.to_bytes(), key);
385 assert_eq!(signer_slice.to_field_bytes(), key.0.into());
386
387 let signer_field_bytes = LocalSigner::from_field_bytes((&key.0).into()).unwrap();
388 assert_eq!(signer_field_bytes.address, signer_b256.address);
389 assert_eq!(signer_field_bytes.chain_id, signer_b256.chain_id);
390 assert_eq!(signer_field_bytes.credential, signer_b256.credential);
391 assert_eq!(signer_field_bytes.to_bytes(), key);
392 assert_eq!(signer_field_bytes.to_field_bytes(), key.0.into());
393 }
394
395 #[test]
396 fn key_from_str() {
397 let signer: LocalSigner<SigningKey> =
398 "0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
399
400 let signer_0x: LocalSigner<SigningKey> =
402 "0x0000000000000000000000000000000000000000000000000000000000000001".parse().unwrap();
403 assert_eq!(signer.address, signer_0x.address);
404 assert_eq!(signer.chain_id, signer_0x.chain_id);
405 assert_eq!(signer.credential, signer_0x.credential);
406
407 "0z0000000000000000000000000000000000000000000000000000000000000001"
409 .parse::<LocalSigner<SigningKey>>()
410 .unwrap_err();
411 }
412}