1use starknet_types_core::felt::Felt;
2
3pub use starknet_crypto::{pedersen_hash, ExtendedSignature, Signature};
4use starknet_crypto::{rfc6979_generate_k, sign, verify, SignError, VerifyError};
5
6mod errors {
7 use core::fmt::{Display, Formatter, Result};
8
9 #[derive(Debug)]
11 pub enum EcdsaSignError {
12 MessageHashOutOfRange,
14 }
15
16 #[derive(Debug)]
17 pub enum EcdsaVerifyError {
19 MessageHashOutOfRange,
21 InvalidPublicKey,
23 SignatureROutOfRange,
25 SignatureSOutOfRange,
27 }
28
29 #[cfg(feature = "std")]
30 impl std::error::Error for EcdsaSignError {}
31
32 impl Display for EcdsaSignError {
33 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
34 match self {
35 Self::MessageHashOutOfRange => write!(f, "message hash out of range"),
36 }
37 }
38 }
39
40 #[cfg(feature = "std")]
41 impl std::error::Error for EcdsaVerifyError {}
42
43 impl Display for EcdsaVerifyError {
44 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
45 match self {
46 Self::MessageHashOutOfRange => write!(f, "message hash out of range"),
47 Self::InvalidPublicKey => write!(f, "invalid public key"),
48 Self::SignatureROutOfRange => write!(f, "signature r value out of range"),
49 Self::SignatureSOutOfRange => write!(f, "signature s value out of range"),
50 }
51 }
52 }
53}
54pub use errors::{EcdsaSignError, EcdsaVerifyError};
55
56pub fn compute_hash_on_elements<'a, ESI, II>(data: II) -> Felt
67where
68 ESI: ExactSizeIterator<Item = &'a Felt>,
69 II: IntoIterator<IntoIter = ESI>,
70{
71 let mut current_hash = Felt::ZERO;
72 let data_iter = data.into_iter();
73 let data_len = Felt::from(data_iter.len());
74
75 for elem in data_iter {
76 current_hash = pedersen_hash(¤t_hash, elem);
77 }
78
79 pedersen_hash(¤t_hash, &data_len)
80}
81
82pub fn ecdsa_sign(
85 private_key: &Felt,
86 message_hash: &Felt,
87) -> Result<ExtendedSignature, EcdsaSignError> {
88 let mut seed = None;
90 loop {
91 let k = rfc6979_generate_k(message_hash, private_key, seed.as_ref());
92
93 match sign(private_key, message_hash, &k) {
94 Ok(sig) => {
95 return Ok(sig);
96 }
97 Err(SignError::InvalidMessageHash) => {
98 return Err(EcdsaSignError::MessageHashOutOfRange)
99 }
100 Err(SignError::InvalidK) => {
101 seed = match seed {
103 Some(prev_seed) => Some(prev_seed + Felt::ONE),
104 None => Some(Felt::ONE),
105 };
106 }
107 };
108 }
109}
110
111pub fn ecdsa_verify(
113 public_key: &Felt,
114 message_hash: &Felt,
115 signature: &Signature,
116) -> Result<bool, EcdsaVerifyError> {
117 match verify(public_key, message_hash, &signature.r, &signature.s) {
118 Ok(result) => Ok(result),
119 Err(VerifyError::InvalidMessageHash) => Err(EcdsaVerifyError::MessageHashOutOfRange),
120 Err(VerifyError::InvalidPublicKey) => Err(EcdsaVerifyError::InvalidPublicKey),
121 Err(VerifyError::InvalidR) => Err(EcdsaVerifyError::SignatureROutOfRange),
122 Err(VerifyError::InvalidS) => Err(EcdsaVerifyError::SignatureSOutOfRange),
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
131 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
132 fn test_compute_hash_on_elements() {
133 let hash = compute_hash_on_elements(&[
135 Felt::from_hex("0xaa").unwrap(),
136 Felt::from_hex("0xbb").unwrap(),
137 Felt::from_hex("0xcc").unwrap(),
138 Felt::from_hex("0xdd").unwrap(),
139 ]);
140 let expected_hash =
141 Felt::from_hex("025cde77210b1c223b2c6e69db6e9021aa1599177ab177474d5326cd2a62cb69")
142 .unwrap();
143
144 assert_eq!(expected_hash, hash);
145 }
146
147 #[test]
148 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
149 fn test_compute_hash_on_elements_empty_data() {
150 let hash = compute_hash_on_elements(&[]);
152 let expected_hash =
153 Felt::from_hex("049ee3eba8c1600700ee1b87eb599f16716b0b1022947733551fde4050ca6804")
154 .unwrap();
155
156 assert_eq!(expected_hash, hash);
157 }
158
159 #[test]
160 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
161 fn test_ecdsa_sign() {
162 let signature = ecdsa_sign(
164 &Felt::from_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")
165 .unwrap(),
166 &Felt::from_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")
167 .unwrap(),
168 )
169 .unwrap();
170 let expected_r =
171 Felt::from_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f")
172 .unwrap();
173 let expected_s =
174 Felt::from_hex("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a")
175 .unwrap();
176
177 assert_eq!(signature.r, expected_r);
178 assert_eq!(signature.s, expected_s);
179 }
180
181 #[test]
182 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
183 fn test_ecdsa_sign_message_hash_out_of_range() {
184 match ecdsa_sign(
185 &Felt::from_hex("0139fe4d6f02e666e86a6f58e65060f115cd3c185bd9e98bd829636931458f79")
186 .unwrap(),
187 &Felt::from_hex("0800000000000000000000000000000000000000000000000000000000000000")
188 .unwrap(),
189 ) {
190 Err(EcdsaSignError::MessageHashOutOfRange) => {}
191 _ => panic!("Should throw error on out of range message hash"),
192 };
193 }
194
195 #[test]
196 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
197 fn test_ecdsa_verify_valid_signature() {
198 let public_key =
200 Felt::from_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159")
201 .unwrap();
202 let message_hash =
203 Felt::from_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")
204 .unwrap();
205 let r = Felt::from_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f")
206 .unwrap();
207 let s = Felt::from_hex("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9a")
208 .unwrap();
209
210 assert!(ecdsa_verify(&public_key, &message_hash, &Signature { r, s }).unwrap());
211 }
212
213 #[test]
214 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
215 fn test_ecdsa_verify_invalid_signature() {
216 let public_key =
218 Felt::from_hex("02c5dbad71c92a45cc4b40573ae661f8147869a91d57b8d9b8f48c8af7f83159")
219 .unwrap();
220 let message_hash =
221 Felt::from_hex("06fea80189363a786037ed3e7ba546dad0ef7de49fccae0e31eb658b7dd4ea76")
222 .unwrap();
223 let r = Felt::from_hex("061ec782f76a66f6984efc3a1b6d152a124c701c00abdd2bf76641b4135c770f")
224 .unwrap();
225 let s = Felt::from_hex("04e44e759cea02c23568bb4d8a09929bbca8768ab68270d50c18d214166ccd9b")
226 .unwrap();
227
228 assert!(!ecdsa_verify(&public_key, &message_hash, &Signature { r, s }).unwrap());
229 }
230}