starknet_rust_signers/
key_pair.rs1use crypto_bigint::{Encoding, NonZero, U256};
2use rand::{rngs::StdRng, Rng, SeedableRng};
3use starknet_rust_core::{
4 crypto::{ecdsa_sign, ecdsa_verify, EcdsaSignError, EcdsaVerifyError, Signature},
5 types::Felt,
6};
7use starknet_rust_crypto::get_public_key;
8
9#[derive(Debug, Clone)]
11pub struct SigningKey {
12 secret_scalar: Felt,
13}
14
15#[derive(Debug, Clone)]
17pub struct VerifyingKey {
18 scalar: Felt,
19}
20
21#[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
23#[derive(Debug, thiserror::Error)]
24pub enum KeystoreError {
25 #[error("invalid path")]
27 InvalidPath,
28 #[error("invalid decrypted secret scalar")]
30 InvalidScalar,
31 #[error(transparent)]
33 Inner(eth_keystore::KeystoreError),
34}
35
36impl SigningKey {
37 pub fn from_random() -> Self {
39 const PRIME: NonZero<U256> = NonZero::from_uint(U256::from_be_hex(
40 "0800000000000011000000000000000000000000000000000000000000000001",
41 ));
42
43 let mut rng = StdRng::from_entropy();
44 let mut buffer = [0u8; 32];
45 rng.fill(&mut buffer);
46
47 let random_u256 = U256::from_be_slice(&buffer);
48 let secret_scalar = random_u256.rem(&PRIME);
49
50 let secret_scalar = Felt::from_bytes_be_slice(&secret_scalar.to_be_bytes());
52
53 Self { secret_scalar }
54 }
55
56 pub const fn from_secret_scalar(secret_scalar: Felt) -> Self {
58 Self { secret_scalar }
59 }
60
61 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
63 pub fn from_keystore<P>(path: P, password: &str) -> Result<Self, KeystoreError>
64 where
65 P: AsRef<std::path::Path>,
66 {
67 let key = eth_keystore::decrypt_key(path, password).map_err(KeystoreError::Inner)?;
68 let secret_scalar = Felt::from_bytes_be_slice(&key);
69 Ok(Self::from_secret_scalar(secret_scalar))
70 }
71
72 #[cfg(not(all(target_arch = "wasm32", target_os = "unknown")))]
74 pub fn save_as_keystore<P>(&self, path: P, password: &str) -> Result<(), KeystoreError>
75 where
76 P: AsRef<std::path::Path>,
77 {
78 let mut path = path.as_ref().to_path_buf();
81 let file_name = path
82 .file_name()
83 .ok_or(KeystoreError::InvalidPath)?
84 .to_str()
85 .ok_or(KeystoreError::InvalidPath)?
86 .to_owned();
87 path.pop();
88
89 let mut rng = StdRng::from_entropy();
90 eth_keystore::encrypt_key(
91 path,
92 &mut rng,
93 self.secret_scalar.to_bytes_be(),
94 password,
95 Some(&file_name),
96 )
97 .map_err(KeystoreError::Inner)?;
98
99 Ok(())
100 }
101
102 pub const fn secret_scalar(&self) -> Felt {
104 self.secret_scalar
105 }
106
107 pub fn verifying_key(&self) -> VerifyingKey {
109 VerifyingKey::from_scalar(get_public_key(&self.secret_scalar))
110 }
111
112 pub fn sign(&self, hash: &Felt) -> Result<Signature, EcdsaSignError> {
114 ecdsa_sign(&self.secret_scalar, hash).map(|sig| sig.into())
115 }
116}
117
118impl VerifyingKey {
119 pub const fn from_scalar(scalar: Felt) -> Self {
121 Self { scalar }
122 }
123
124 pub const fn scalar(&self) -> Felt {
126 self.scalar
127 }
128
129 pub fn verify(&self, hash: &Felt, signature: &Signature) -> Result<bool, EcdsaVerifyError> {
132 ecdsa_verify(&self.scalar, hash, signature)
133 }
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
142 fn test_get_secret_scalar() {
143 let private_key =
145 Felt::from_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")
146 .unwrap();
147
148 let signing_key = SigningKey::from_secret_scalar(private_key);
149
150 assert_eq!(signing_key.secret_scalar(), private_key);
151 }
152
153 #[test]
154 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
155 fn test_get_verifying_key() {
156 let private_key =
158 Felt::from_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")
159 .unwrap();
160 let expected_public_key =
161 Felt::from_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159")
162 .unwrap();
163
164 let signing_key = SigningKey::from_secret_scalar(private_key);
165 let verifying_key = signing_key.verifying_key();
166
167 assert_eq!(verifying_key.scalar(), expected_public_key);
168 }
169
170 #[test]
171 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
172 fn test_sign() {
173 let private_key =
175 Felt::from_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")
176 .unwrap();
177 let hash =
178 Felt::from_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")
179 .unwrap();
180 let expected_r =
181 Felt::from_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f")
182 .unwrap();
183 let expected_s =
184 Felt::from_hex("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a")
185 .unwrap();
186
187 let signing_key = SigningKey::from_secret_scalar(private_key);
188 let signature = signing_key.sign(&hash).unwrap();
189
190 assert_eq!(signature.r, expected_r);
191 assert_eq!(signature.s, expected_s);
192 }
193
194 #[test]
195 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
196 fn test_hash_out_of_range() {
197 let private_key =
198 Felt::from_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")
199 .unwrap();
200 let hash =
201 Felt::from_hex("0800000000000000000000000000000000000000000000000000000000000000")
202 .unwrap();
203
204 let signing_key = SigningKey::from_secret_scalar(private_key);
205
206 match signing_key.sign(&hash) {
207 Err(EcdsaSignError::MessageHashOutOfRange) => {}
208 _ => panic!("Should throw error on out of range hash"),
209 };
210 }
211
212 #[test]
213 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
214 fn test_verify_valid_signature() {
215 let public_key =
217 Felt::from_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159")
218 .unwrap();
219 let hash =
220 Felt::from_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")
221 .unwrap();
222 let r = Felt::from_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f")
223 .unwrap();
224 let s = Felt::from_hex("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a")
225 .unwrap();
226
227 let verifying_key = VerifyingKey::from_scalar(public_key);
228
229 assert!(verifying_key.verify(&hash, &Signature { r, s }).unwrap());
230 }
231
232 #[test]
233 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
234 fn test_verify_invalid_signature() {
235 let public_key =
237 Felt::from_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159")
238 .unwrap();
239 let hash =
240 Felt::from_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")
241 .unwrap();
242 let r = Felt::from_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f")
243 .unwrap();
244 let s = Felt::from_hex("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b")
245 .unwrap();
246
247 let verifying_key = VerifyingKey::from_scalar(public_key);
248
249 assert!(!verifying_key.verify(&hash, &Signature { r, s }).unwrap());
250 }
251}