snarkvm_console_algorithms/ecdsa/
mod.rs1#[cfg(test)]
17pub mod tests;
18
19mod serialize;
20
21use super::*;
22use snarkvm_utilities::bytes_from_bits_le;
23
24use k256::{
25 Secp256k1,
26 ecdsa::{
27 RecoveryId as ECDSARecoveryId,
28 Signature,
29 SigningKey,
30 VerifyingKey,
31 signature::hazmat::{PrehashSigner, PrehashVerifier},
32 },
33 elliptic_curve::{Curve, generic_array::typenum::Unsigned},
34};
35
36#[derive(Clone, PartialEq, Eq)]
38pub struct RecoveryID {
39 pub recovery_id: ECDSARecoveryId,
41 pub chain_id: Option<u64>,
46}
47
48impl RecoveryID {
49 const ETH_EIP155_OFFSET: u8 = 35;
51 const ETH_LEGACY_OFFSET: u8 = 27;
53
54 #[inline]
59 fn encoded_byte(&self) -> Result<u8> {
60 let recovery_id = self.recovery_id.to_byte();
61 match self.chain_id {
62 None => Ok(recovery_id),
64 Some(0) => Ok(recovery_id.saturating_add(Self::ETH_LEGACY_OFFSET)), Some(chain_id) => {
68 let recovery_id = (recovery_id as u64)
69 .saturating_add(chain_id.saturating_mul(2))
70 .saturating_add(Self::ETH_EIP155_OFFSET as u64);
71 Ok(u8::try_from(recovery_id)?)
72 }
73 }
74 }
75}
76
77impl ToBytes for RecoveryID {
78 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
79 let encoded_byte = self.encoded_byte().map_err(error)?;
80 encoded_byte.write_le(&mut writer)
81 }
82}
83
84impl FromBytes for RecoveryID {
85 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
86 let recovery_id_byte = u8::read_le(&mut reader)?;
88
89 let (recovery_id_without_offset, chain_id) = match recovery_id_byte {
91 27 | 28 => (recovery_id_byte.saturating_sub(Self::ETH_LEGACY_OFFSET), Some(0u64)),
92 v if v >= Self::ETH_EIP155_OFFSET => {
93 let y = (v.saturating_sub(Self::ETH_EIP155_OFFSET)) % 2;
94 let id = (v.saturating_sub(Self::ETH_EIP155_OFFSET)) / 2;
95 (y, Some(id as u64))
96 }
97 _ => (recovery_id_byte, None),
98 };
99
100 let recovery_id = ECDSARecoveryId::from_byte(recovery_id_without_offset)
102 .ok_or_else(|| error(format!("Invalid recovery ID byte {recovery_id_byte}")))?;
103 Ok(Self { recovery_id, chain_id })
104 }
105}
106
107#[derive(Clone, PartialEq, Eq)]
109pub struct ECDSASignature {
110 pub signature: Signature,
112 pub recovery_id: RecoveryID,
114}
115
116impl ECDSASignature {
117 pub const BASE_SIGNATURE_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE * 2;
119 pub const ETHEREUM_ADDRESS_SIZE_IN_BYTES: usize = 20;
121 pub const PREHASH_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE;
123 pub const SIGNATURE_SIZE_IN_BYTES: usize = Self::BASE_SIGNATURE_SIZE_IN_BYTES + 1;
125 pub const VERIFYING_KEY_SIZE_IN_BYTES: usize = <Secp256k1 as Curve>::FieldBytesSize::USIZE + 1;
127
128 pub const fn recovery_id(&self) -> ECDSARecoveryId {
130 self.recovery_id.recovery_id
131 }
132
133 pub fn sign<H: Hash<Output = Vec<bool>>>(
135 signing_key: &SigningKey,
136 hasher: &H,
137 message: &[H::Input],
138 ) -> Result<Self> {
139 let hash_bits = hasher.hash(message)?;
141 let hash_bytes = bytes_from_bits_le(&hash_bits);
143
144 signing_key
146 .sign_prehash(&hash_bytes)
147 .map(|(signature, recovery_id)| {
148 let recovery_id = RecoveryID { recovery_id, chain_id: None };
149 Self { signature, recovery_id }
150 })
151 .map_err(|e| anyhow!("Failed to sign message: {e:?}"))
152 }
153
154 pub fn recover_public_key<H: Hash<Output = Vec<bool>>>(
156 &self,
157 hasher: &H,
158 message: &[H::Input],
159 ) -> Result<VerifyingKey> {
160 let hash_bits = hasher.hash(message)?;
162
163 self.recover_public_key_with_digest(&hash_bits)
165 }
166
167 pub fn recover_public_key_with_digest(&self, digest_bits: &[bool]) -> Result<VerifyingKey> {
169 let digest = bytes_from_bits_le(digest_bits);
171
172 VerifyingKey::recover_from_prehash(&digest, &self.signature, self.recovery_id())
174 .map_err(|e| anyhow!("Failed to recover public key: {e:?}"))
175 }
176
177 pub fn verify<H: Hash<Output = Vec<bool>>>(
179 &self,
180 verifying_key: &VerifyingKey,
181 hasher: &H,
182 message: &[H::Input],
183 ) -> Result<()> {
184 let hash_bits = hasher.hash(message)?;
186
187 self.verify_with_digest(verifying_key, &hash_bits)
189 }
190
191 pub fn verify_with_digest(&self, verifying_key: &VerifyingKey, digest_bits: &[bool]) -> Result<()> {
193 let digest = bytes_from_bits_le(digest_bits);
195
196 verifying_key.verify_prehash(&digest, &self.signature).map_err(|e| anyhow!("Failed to verify signature: {e:?}"))
198 }
199
200 pub fn verify_ethereum<H: Hash<Output = Vec<bool>>>(
202 &self,
203 ethereum_address: &[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES],
204 hasher: &H,
205 message: &[H::Input],
206 ) -> Result<()> {
207 let hash_bits = hasher.hash(message)?;
209
210 self.verify_ethereum_with_digest(ethereum_address, &hash_bits)
212 }
213
214 pub fn verify_ethereum_with_digest(
216 &self,
217 ethereum_address: &[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES],
218 digest_bits: &[bool],
219 ) -> Result<()> {
220 let verifying_key = self.recover_public_key_with_digest(digest_bits)?;
222
223 let derived_ethereum_address = Self::ethereum_address_from_public_key(&verifying_key)?;
225 ensure!(
226 &derived_ethereum_address == ethereum_address,
227 "Derived Ethereum address does not match the provided address."
228 );
229
230 Ok(())
231 }
232
233 pub fn ethereum_address_from_public_key(
235 verifying_key: &VerifyingKey,
236 ) -> Result<[u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES]> {
237 let public_key_point = verifying_key.to_encoded_point(false);
239 let public_key_bytes = public_key_point.as_bytes();
240
241 let coordinates_only = &public_key_bytes[1..]; let address_hash = Keccak256::default().hash(&coordinates_only.to_bits_le())?;
246 let address_bytes = bytes_from_bits_le(&address_hash);
247
248 let mut ethereum_address = [0u8; Self::ETHEREUM_ADDRESS_SIZE_IN_BYTES];
250 ethereum_address.copy_from_slice(&address_bytes[12..32]);
251
252 Ok(ethereum_address)
253 }
254
255 pub fn verifying_key_from_bytes(bytes: &[u8]) -> Result<VerifyingKey> {
257 VerifyingKey::from_sec1_bytes(bytes).map_err(|e| anyhow!("Failed to parse verifying key: {e:?}"))
258 }
259}
260
261impl ToBytes for ECDSASignature {
262 fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
263 self.signature.to_bytes().to_vec().write_le(&mut writer)?;
265 self.recovery_id.write_le(&mut writer)
267 }
268}
269
270impl FromBytes for ECDSASignature {
271 fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
272 let mut bytes = vec![0u8; Self::BASE_SIGNATURE_SIZE_IN_BYTES];
274 reader.read_exact(&mut bytes)?;
275 let signature = Signature::from_slice(&bytes).map_err(error)?;
277
278 let recovery_id = RecoveryID::read_le(&mut reader)?;
280
281 Ok(Self { signature, recovery_id })
282 }
283}
284
285impl FromStr for ECDSASignature {
286 type Err = Error;
287
288 fn from_str(signature: &str) -> Result<Self, Self::Err> {
290 let mut s = signature.trim();
291
292 if let Some(rest) = s.strip_prefix("0x").or_else(|| s.strip_prefix("0X")) {
294 s = rest;
295 }
296
297 let bytes = hex::decode(s)?;
299
300 Self::from_bytes_le(&bytes)
302 }
303}
304
305impl Debug for ECDSASignature {
306 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
307 Display::fmt(self, f)
308 }
309}
310
311impl Display for ECDSASignature {
312 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
314 write!(f, "{}", hex::encode(self.to_bytes_le().map_err(|_| fmt::Error)?))
315 }
316}
317
318#[cfg(test)]
319mod test_helpers {
320 use super::*;
321
322 pub(crate) type DefaultHasher = Keccak256;
323
324 pub(super) fn sample_ecdsa_signature<H: Hash<Output = Vec<bool>, Input = bool>>(
326 num_bytes: usize,
327 hasher: &H,
328 rng: &mut TestRng,
329 ) -> (SigningKey, Vec<u8>, ECDSASignature) {
330 let signing_key = SigningKey::random(rng);
332
333 let message: Vec<u8> = (0..num_bytes).map(|_| rng.r#gen()).collect::<Vec<_>>();
335
336 let signature = ECDSASignature::sign::<H>(&signing_key, hasher, &message.to_bits_le()).unwrap();
338
339 (signing_key, message, signature)
341 }
342}