/rust/registry/src/index.crates.io-1949cf8c6b5b557f/ecdsa-0.17.0/src/recovery.rs
Line | Count | Source |
1 | | //! Public key recovery support. |
2 | | |
3 | | use crate::{Error, Result}; |
4 | | |
5 | | #[cfg(feature = "algorithm")] |
6 | | use { |
7 | | crate::{ |
8 | | DigestAlgorithm, EcdsaCurve, Signature, SigningKey, VerifyingKey, |
9 | | hazmat::{bytes2scalar, sign_prehashed_rfc6979, verify_prehashed}, |
10 | | }, |
11 | | digest::{Digest, Update}, |
12 | | elliptic_curve::{ |
13 | | AffinePoint, CurveArithmetic, CurveGroup, FieldBytes, FieldBytesSize, PrimeField, |
14 | | ProjectivePoint, Scalar, |
15 | | bigint::CheckedAdd, |
16 | | field, |
17 | | ops::{Invert, MulByGeneratorVartime}, |
18 | | point::DecompressPoint, |
19 | | sec1::{self, FromSec1Point, ToSec1Point}, |
20 | | subtle::CtOption, |
21 | | }, |
22 | | signature::{ |
23 | | DigestSigner, MultipartSigner, RandomizedDigestSigner, Signer, |
24 | | hazmat::{PrehashSigner, RandomizedPrehashSigner}, |
25 | | rand_core::TryCryptoRng, |
26 | | }, |
27 | | }; |
28 | | |
29 | | /// Recovery IDs, a.k.a. "recid". |
30 | | /// |
31 | | /// This is an integer value `0`, `1`, `2`, or `3` included along with a signature which is used |
32 | | /// during the recovery process to select the correct public key from the signature. |
33 | | /// |
34 | | /// It consists of two bits of information: |
35 | | /// |
36 | | /// 1. low bit (0/1): was the y-coordinate of the affine point resulting from the fixed-base |
37 | | /// multiplication 𝑘×𝑮 odd? This part of the algorithm functions similar to point decompression. |
38 | | /// 2. hi bit (2/3): did the affine x-coordinate of 𝑘×𝑮 overflow the order of the scalar field `n`, |
39 | | /// requiring a reduction when computing `r`? |
40 | | #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] |
41 | | pub struct RecoveryId(pub(crate) u8); |
42 | | |
43 | | impl RecoveryId { |
44 | | /// Maximum supported value for the recovery ID (inclusive). |
45 | | pub const MAX: u8 = 3; |
46 | | |
47 | | /// Create a new [`RecoveryId`] from the following 1-bit arguments: |
48 | | /// |
49 | | /// - `is_y_odd`: is the affine y-coordinate of 𝑘×𝑮 odd? |
50 | | /// - `is_x_reduced`: did the affine x-coordinate of 𝑘×𝑮 overflow the curve order? |
51 | | #[must_use] |
52 | | #[allow(clippy::as_conversions, reason = "const fn")] |
53 | 2.16k | pub const fn new(is_y_odd: bool, is_x_reduced: bool) -> Self { |
54 | 2.16k | Self(((is_x_reduced as u8) << 1) | (is_y_odd as u8)) |
55 | 2.16k | } |
56 | | |
57 | | /// Did the affine x-coordinate of 𝑘×𝑮 overflow the curve order? |
58 | | #[must_use] |
59 | 0 | pub const fn is_x_reduced(self) -> bool { |
60 | 0 | (self.0 & 0b10) != 0 |
61 | 0 | } |
62 | | |
63 | | /// Is the affine y-coordinate of 𝑘×𝑮 odd? |
64 | | #[must_use] |
65 | 0 | pub const fn is_y_odd(self) -> bool { |
66 | 0 | (self.0 & 1) != 0 |
67 | 0 | } |
68 | | |
69 | | /// Convert a `u8` into a [`RecoveryId`]. |
70 | | #[must_use] |
71 | 0 | pub const fn from_byte(byte: u8) -> Option<Self> { |
72 | 0 | if byte <= Self::MAX { |
73 | 0 | Some(Self(byte)) |
74 | | } else { |
75 | 0 | None |
76 | | } |
77 | 0 | } |
78 | | |
79 | | /// Convert this [`RecoveryId`] into a `u8`. |
80 | | #[must_use] |
81 | 0 | pub const fn to_byte(self) -> u8 { |
82 | 0 | self.0 |
83 | 0 | } |
84 | | } |
85 | | |
86 | | #[cfg(feature = "algorithm")] |
87 | | impl RecoveryId { |
88 | | /// Given a public key, message, and signature, use trial recovery to determine if a suitable |
89 | | /// recovery ID exists. |
90 | | /// |
91 | | /// # Errors |
92 | | /// Returns an error if a suitable solution could not be found and/or the signature does not |
93 | | /// verify. |
94 | 0 | pub fn trial_recovery_from_msg<C>( |
95 | 0 | verifying_key: &VerifyingKey<C>, |
96 | 0 | msg: &[u8], |
97 | 0 | signature: &Signature<C>, |
98 | 0 | ) -> Result<Self> |
99 | 0 | where |
100 | 0 | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
101 | 0 | AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>, |
102 | 0 | FieldBytesSize<C>: sec1::ModulusSize, |
103 | | { |
104 | 0 | Self::trial_recovery_from_digest(verifying_key, C::Digest::new_with_prefix(msg), signature) |
105 | 0 | } |
106 | | |
107 | | /// Given a public key, message digest, and signature, use trial recovery to determine if a |
108 | | /// suitable recovery ID exists. |
109 | | /// |
110 | | /// # Errors |
111 | | /// Returns an error if a suitable solution could not be found and/or the signature does not |
112 | | /// verify. |
113 | 0 | pub fn trial_recovery_from_digest<C, D>( |
114 | 0 | verifying_key: &VerifyingKey<C>, |
115 | 0 | digest: D, |
116 | 0 | signature: &Signature<C>, |
117 | 0 | ) -> Result<Self> |
118 | 0 | where |
119 | 0 | C: EcdsaCurve + CurveArithmetic, |
120 | 0 | D: Digest, |
121 | 0 | AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>, |
122 | 0 | FieldBytesSize<C>: sec1::ModulusSize, |
123 | | { |
124 | 0 | Self::trial_recovery_from_prehash(verifying_key, &digest.finalize(), signature) |
125 | 0 | } |
126 | | |
127 | | /// Given a public key, message digest, and signature, use trial recovery to determine if a |
128 | | /// suitable recovery ID exists. |
129 | | /// |
130 | | /// # Errors |
131 | | /// Returns an error if a suitable solution could not be found and/or the signature does not |
132 | | /// verify. |
133 | 0 | pub fn trial_recovery_from_prehash<C>( |
134 | 0 | verifying_key: &VerifyingKey<C>, |
135 | 0 | prehash: &[u8], |
136 | 0 | signature: &Signature<C>, |
137 | 0 | ) -> Result<Self> |
138 | 0 | where |
139 | 0 | C: EcdsaCurve + CurveArithmetic, |
140 | 0 | AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>, |
141 | 0 | FieldBytesSize<C>: sec1::ModulusSize, |
142 | | { |
143 | | // Ensure signature verifies with the provided key |
144 | 0 | verify_prehashed::<C>( |
145 | 0 | &ProjectivePoint::<C>::from(*verifying_key.as_affine()), |
146 | 0 | prehash, |
147 | 0 | signature, |
148 | 0 | )?; |
149 | | |
150 | 0 | for id in 0..=Self::MAX { |
151 | 0 | let recovery_id = RecoveryId(id); |
152 | | |
153 | 0 | if let Ok(vk) = VerifyingKey::recover_from_prehash(prehash, signature, recovery_id) { |
154 | 0 | if verifying_key == &vk { |
155 | 0 | return Ok(recovery_id); |
156 | 0 | } |
157 | 0 | } |
158 | | } |
159 | | |
160 | 0 | Err(Error::new()) |
161 | 0 | } |
162 | | } |
163 | | |
164 | | impl TryFrom<u8> for RecoveryId { |
165 | | type Error = Error; |
166 | | |
167 | 0 | fn try_from(byte: u8) -> Result<Self> { |
168 | 0 | Self::from_byte(byte).ok_or_else(Error::new) |
169 | 0 | } |
170 | | } |
171 | | |
172 | | impl From<RecoveryId> for u8 { |
173 | 0 | fn from(id: RecoveryId) -> u8 { |
174 | 0 | id.0 |
175 | 0 | } |
176 | | } |
177 | | |
178 | | #[cfg(feature = "algorithm")] |
179 | | impl<C> SigningKey<C> |
180 | | where |
181 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
182 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
183 | | { |
184 | | /// Sign the given message prehash, using the given rng for the RFC6979 Section 3.6 "additional |
185 | | /// data", returning a signature and recovery ID. |
186 | | /// |
187 | | /// # Errors |
188 | | /// |
189 | 0 | pub fn sign_prehash_recoverable_with_rng<R: TryCryptoRng + ?Sized>( |
190 | 0 | &self, |
191 | 0 | rng: &mut R, |
192 | 0 | prehash: &[u8], |
193 | 0 | ) -> core::result::Result<(Signature<C>, RecoveryId), R::Error> { |
194 | 0 | let mut ad = FieldBytes::<C>::default(); |
195 | 0 | rng.try_fill_bytes(&mut ad)?; |
196 | 0 | Ok(sign_prehashed_rfc6979::<C, C::Digest>( |
197 | 0 | self.as_nonzero_scalar(), |
198 | 0 | prehash, |
199 | 0 | &ad, |
200 | 0 | )) |
201 | 0 | } |
202 | | |
203 | | /// Sign the given message prehash, returning a signature and recovery ID. |
204 | 0 | pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> (Signature<C>, RecoveryId) { |
205 | 0 | sign_prehashed_rfc6979::<C, C::Digest>(self.as_nonzero_scalar(), prehash, b"") |
206 | 0 | } |
207 | | |
208 | | /// Sign the given message digest, returning a signature and recovery ID. |
209 | 0 | pub fn sign_digest_recoverable<D: Digest>(&self, msg_digest: D) -> (Signature<C>, RecoveryId) { |
210 | 0 | self.sign_prehash_recoverable(&msg_digest.finalize()) |
211 | 0 | } |
212 | | |
213 | | /// Sign the given message, hashing it with the curve's default digest |
214 | | /// function, and returning a signature and recovery ID. |
215 | 0 | pub fn sign_recoverable(&self, msg: &[u8]) -> (Signature<C>, RecoveryId) { |
216 | 0 | self.sign_digest_recoverable(C::Digest::new_with_prefix(msg)) |
217 | 0 | } |
218 | | } |
219 | | |
220 | | #[cfg(feature = "algorithm")] |
221 | | impl<C, D> DigestSigner<D, (Signature<C>, RecoveryId)> for SigningKey<C> |
222 | | where |
223 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
224 | | D: Digest + Update, |
225 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
226 | | { |
227 | 0 | fn try_sign_digest<F: Fn(&mut D) -> Result<()>>( |
228 | 0 | &self, |
229 | 0 | f: F, |
230 | 0 | ) -> Result<(Signature<C>, RecoveryId)> { |
231 | 0 | let mut digest = D::new(); |
232 | 0 | f(&mut digest)?; |
233 | 0 | Ok(self.sign_digest_recoverable(digest)) |
234 | 0 | } |
235 | | } |
236 | | |
237 | | #[cfg(feature = "algorithm")] |
238 | | impl<C> RandomizedPrehashSigner<(Signature<C>, RecoveryId)> for SigningKey<C> |
239 | | where |
240 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
241 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
242 | | { |
243 | 0 | fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>( |
244 | 0 | &self, |
245 | 0 | rng: &mut R, |
246 | 0 | prehash: &[u8], |
247 | 0 | ) -> Result<(Signature<C>, RecoveryId)> { |
248 | 0 | self.sign_prehash_recoverable_with_rng(rng, prehash) |
249 | 0 | .map_err(|_| Error::new()) |
250 | 0 | } |
251 | | } |
252 | | |
253 | | #[cfg(feature = "algorithm")] |
254 | | impl<C, D> RandomizedDigestSigner<D, (Signature<C>, RecoveryId)> for SigningKey<C> |
255 | | where |
256 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
257 | | D: Digest + Update, |
258 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
259 | | { |
260 | 0 | fn try_sign_digest_with_rng<R: TryCryptoRng + ?Sized, F: Fn(&mut D) -> Result<()>>( |
261 | 0 | &self, |
262 | 0 | rng: &mut R, |
263 | 0 | f: F, |
264 | 0 | ) -> Result<(Signature<C>, RecoveryId)> { |
265 | 0 | let mut digest = D::new(); |
266 | 0 | f(&mut digest)?; |
267 | 0 | self.sign_prehash_with_rng(rng, &digest.finalize()) |
268 | 0 | } |
269 | | } |
270 | | |
271 | | #[cfg(feature = "algorithm")] |
272 | | impl<C> PrehashSigner<(Signature<C>, RecoveryId)> for SigningKey<C> |
273 | | where |
274 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
275 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
276 | | { |
277 | 0 | fn sign_prehash(&self, prehash: &[u8]) -> Result<(Signature<C>, RecoveryId)> { |
278 | 0 | Ok(self.sign_prehash_recoverable(prehash)) |
279 | 0 | } |
280 | | } |
281 | | |
282 | | #[cfg(feature = "algorithm")] |
283 | | impl<C> Signer<(Signature<C>, RecoveryId)> for SigningKey<C> |
284 | | where |
285 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
286 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
287 | | { |
288 | 0 | fn try_sign(&self, msg: &[u8]) -> Result<(Signature<C>, RecoveryId)> { |
289 | 0 | self.try_multipart_sign(&[msg]) |
290 | 0 | } |
291 | | } |
292 | | |
293 | | #[cfg(feature = "algorithm")] |
294 | | impl<C> MultipartSigner<(Signature<C>, RecoveryId)> for SigningKey<C> |
295 | | where |
296 | | C: EcdsaCurve + CurveArithmetic + DigestAlgorithm, |
297 | | Scalar<C>: Invert<Output = CtOption<Scalar<C>>>, |
298 | | { |
299 | 0 | fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<(Signature<C>, RecoveryId)> { |
300 | 0 | let mut digest = C::Digest::new(); |
301 | 0 | msg.iter() |
302 | 0 | .for_each(|slice| Update::update(&mut digest, slice)); |
303 | 0 | Ok(self.sign_digest_recoverable(digest)) |
304 | 0 | } |
305 | | } |
306 | | |
307 | | #[cfg(feature = "algorithm")] |
308 | | impl<C> VerifyingKey<C> |
309 | | where |
310 | | C: EcdsaCurve + CurveArithmetic, |
311 | | AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>, |
312 | | FieldBytesSize<C>: sec1::ModulusSize, |
313 | | { |
314 | | /// Recover a [`VerifyingKey`] from the given message, signature, and [`RecoveryId`]. |
315 | | /// |
316 | | /// The message is first hashed using this curve's [`DigestAlgorithm`]. |
317 | | /// |
318 | | /// # Errors |
319 | | /// Returns [`Error`] if the recovered elliptic curve point is the additive identity. |
320 | 0 | pub fn recover_from_msg( |
321 | 0 | msg: &[u8], |
322 | 0 | signature: &Signature<C>, |
323 | 0 | recovery_id: RecoveryId, |
324 | 0 | ) -> Result<Self> |
325 | 0 | where |
326 | 0 | C: DigestAlgorithm, |
327 | | { |
328 | 0 | Self::recover_from_digest(C::Digest::new_with_prefix(msg), signature, recovery_id) |
329 | 0 | } |
330 | | |
331 | | /// Recover a [`VerifyingKey`] from the given message [`Digest`], signature, and [`RecoveryId`]. |
332 | | /// |
333 | | /// # Errors |
334 | | /// Returns [`Error`] if the recovered elliptic curve point is the additive identity. |
335 | 0 | pub fn recover_from_digest<D>( |
336 | 0 | msg_digest: D, |
337 | 0 | signature: &Signature<C>, |
338 | 0 | recovery_id: RecoveryId, |
339 | 0 | ) -> Result<Self> |
340 | 0 | where |
341 | 0 | D: Digest, |
342 | | { |
343 | 0 | Self::recover_from_prehash(&msg_digest.finalize(), signature, recovery_id) |
344 | 0 | } |
345 | | |
346 | | /// Recover a [`VerifyingKey`] from the given `prehash` of a message, the signature over that |
347 | | /// prehashed message, and a [`RecoveryId`]. |
348 | | /// |
349 | | /// <div class="warning"> |
350 | | /// <b>Security Warning</b> |
351 | | /// |
352 | | /// The `prehash` argument must be the output of a secure digest function, e.g. Keccak256 |
353 | | /// or SHA-256. |
354 | | /// |
355 | | /// Failure to use such a digest algorithm to compute `prehash` allows an attacker to solve for |
356 | | /// it in a system of linear equations that can cause the recovery function to output any public |
357 | | /// key the attacker wants. |
358 | | /// </div> |
359 | | /// |
360 | | /// # Errors |
361 | | /// Returns [`Error`] if the recovered elliptic curve point is the additive identity. |
362 | | #[allow(non_snake_case)] |
363 | 0 | pub fn recover_from_prehash( |
364 | 0 | prehash: &[u8], |
365 | 0 | signature: &Signature<C>, |
366 | 0 | recovery_id: RecoveryId, |
367 | 0 | ) -> Result<Self> { |
368 | 0 | let (r, s) = signature.split_scalars(); |
369 | 0 | let z = bytes2scalar::<C>(prehash); |
370 | | |
371 | 0 | let r_bytes = if recovery_id.is_x_reduced() { |
372 | 0 | let uint = field::bytes_to_uint::<C>(&r.to_repr()) |
373 | 0 | .checked_add(&C::ORDER) |
374 | 0 | .into_option() |
375 | 0 | .ok_or_else(Error::new)?; |
376 | | |
377 | 0 | field::uint_to_bytes::<C>(&uint) |
378 | | } else { |
379 | 0 | r.to_repr() |
380 | | }; |
381 | | |
382 | 0 | let R: ProjectivePoint<C> = |
383 | 0 | AffinePoint::<C>::decompress(&r_bytes, u8::from(recovery_id.is_y_odd()).into()) |
384 | 0 | .into_option() |
385 | 0 | .ok_or_else(Error::new)? |
386 | 0 | .into(); |
387 | | |
388 | 0 | let r_inv = *r.invert(); |
389 | 0 | let u1 = -(r_inv * z); |
390 | 0 | let u2 = r_inv * *s; |
391 | 0 | let pk = ProjectivePoint::<C>::mul_by_generator_and_mul_add_vartime(&u1, &u2, &R); |
392 | 0 | Self::from_affine(pk.to_affine()) |
393 | 0 | } |
394 | | } |
395 | | |
396 | | #[cfg(test)] |
397 | | mod tests { |
398 | | use super::RecoveryId; |
399 | | |
400 | | #[test] |
401 | | fn new() { |
402 | | assert_eq!(RecoveryId::new(false, false).to_byte(), 0); |
403 | | assert_eq!(RecoveryId::new(true, false).to_byte(), 1); |
404 | | assert_eq!(RecoveryId::new(false, true).to_byte(), 2); |
405 | | assert_eq!(RecoveryId::new(true, true).to_byte(), 3); |
406 | | } |
407 | | |
408 | | #[test] |
409 | | fn try_from() { |
410 | | for n in 0u8..=3 { |
411 | | assert_eq!(RecoveryId::try_from(n).expect("RecoveryId").to_byte(), n); |
412 | | } |
413 | | |
414 | | for n in 4u8..=255 { |
415 | | assert!(RecoveryId::try_from(n).is_err()); |
416 | | } |
417 | | } |
418 | | |
419 | | #[test] |
420 | | fn is_x_reduced() { |
421 | | assert!(!RecoveryId::try_from(0).expect("RecoveryId").is_x_reduced()); |
422 | | assert!(!RecoveryId::try_from(1).expect("RecoveryId").is_x_reduced()); |
423 | | assert!(RecoveryId::try_from(2).expect("RecoveryId").is_x_reduced()); |
424 | | assert!(RecoveryId::try_from(3).expect("RecoveryId").is_x_reduced()); |
425 | | } |
426 | | |
427 | | #[test] |
428 | | fn is_y_odd() { |
429 | | assert!(!RecoveryId::try_from(0).expect("RecoveryId").is_y_odd()); |
430 | | assert!(RecoveryId::try_from(1).expect("RecoveryId").is_y_odd()); |
431 | | assert!(!RecoveryId::try_from(2).expect("RecoveryId").is_y_odd()); |
432 | | assert!(RecoveryId::try_from(3).expect("RecoveryId").is_y_odd()); |
433 | | } |
434 | | } |