1use crate::{Error, Result};
4
5#[cfg(feature = "algorithm")]
6use {
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#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
41pub struct RecoveryId(pub(crate) u8);
42
43impl RecoveryId {
44 pub const MAX: u8 = 3;
46
47 #[must_use]
52 #[allow(clippy::as_conversions, reason = "const fn")]
53 pub const fn new(is_y_odd: bool, is_x_reduced: bool) -> Self {
54 Self(((is_x_reduced as u8) << 1) | (is_y_odd as u8))
55 }
56
57 #[must_use]
59 pub const fn is_x_reduced(self) -> bool {
60 (self.0 & 0b10) != 0
61 }
62
63 #[must_use]
65 pub const fn is_y_odd(self) -> bool {
66 (self.0 & 1) != 0
67 }
68
69 #[must_use]
71 pub const fn from_byte(byte: u8) -> Option<Self> {
72 if byte <= Self::MAX {
73 Some(Self(byte))
74 } else {
75 None
76 }
77 }
78
79 #[must_use]
81 pub const fn to_byte(self) -> u8 {
82 self.0
83 }
84}
85
86#[cfg(feature = "algorithm")]
87impl RecoveryId {
88 pub fn trial_recovery_from_msg<C>(
95 verifying_key: &VerifyingKey<C>,
96 msg: &[u8],
97 signature: &Signature<C>,
98 ) -> Result<Self>
99 where
100 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
101 AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>,
102 FieldBytesSize<C>: sec1::ModulusSize,
103 {
104 Self::trial_recovery_from_digest(verifying_key, C::Digest::new_with_prefix(msg), signature)
105 }
106
107 pub fn trial_recovery_from_digest<C, D>(
114 verifying_key: &VerifyingKey<C>,
115 digest: D,
116 signature: &Signature<C>,
117 ) -> Result<Self>
118 where
119 C: EcdsaCurve + CurveArithmetic,
120 D: Digest,
121 AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>,
122 FieldBytesSize<C>: sec1::ModulusSize,
123 {
124 Self::trial_recovery_from_prehash(verifying_key, &digest.finalize(), signature)
125 }
126
127 pub fn trial_recovery_from_prehash<C>(
134 verifying_key: &VerifyingKey<C>,
135 prehash: &[u8],
136 signature: &Signature<C>,
137 ) -> Result<Self>
138 where
139 C: EcdsaCurve + CurveArithmetic,
140 AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>,
141 FieldBytesSize<C>: sec1::ModulusSize,
142 {
143 verify_prehashed::<C>(
145 &ProjectivePoint::<C>::from(*verifying_key.as_affine()),
146 prehash,
147 signature,
148 )?;
149
150 for id in 0..=Self::MAX {
151 let recovery_id = RecoveryId(id);
152
153 if let Ok(vk) = VerifyingKey::recover_from_prehash(prehash, signature, recovery_id) {
154 if verifying_key == &vk {
155 return Ok(recovery_id);
156 }
157 }
158 }
159
160 Err(Error::new())
161 }
162}
163
164impl TryFrom<u8> for RecoveryId {
165 type Error = Error;
166
167 fn try_from(byte: u8) -> Result<Self> {
168 Self::from_byte(byte).ok_or_else(Error::new)
169 }
170}
171
172impl From<RecoveryId> for u8 {
173 fn from(id: RecoveryId) -> u8 {
174 id.0
175 }
176}
177
178#[cfg(feature = "algorithm")]
179impl<C> SigningKey<C>
180where
181 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
182 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
183{
184 pub fn sign_prehash_recoverable_with_rng<R: TryCryptoRng + ?Sized>(
190 &self,
191 rng: &mut R,
192 prehash: &[u8],
193 ) -> core::result::Result<(Signature<C>, RecoveryId), R::Error> {
194 let mut ad = FieldBytes::<C>::default();
195 rng.try_fill_bytes(&mut ad)?;
196 Ok(sign_prehashed_rfc6979::<C, C::Digest>(
197 self.as_nonzero_scalar(),
198 prehash,
199 &ad,
200 ))
201 }
202
203 pub fn sign_prehash_recoverable(&self, prehash: &[u8]) -> (Signature<C>, RecoveryId) {
205 sign_prehashed_rfc6979::<C, C::Digest>(self.as_nonzero_scalar(), prehash, b"")
206 }
207
208 pub fn sign_digest_recoverable<D: Digest>(&self, msg_digest: D) -> (Signature<C>, RecoveryId) {
210 self.sign_prehash_recoverable(&msg_digest.finalize())
211 }
212
213 pub fn sign_recoverable(&self, msg: &[u8]) -> (Signature<C>, RecoveryId) {
216 self.sign_digest_recoverable(C::Digest::new_with_prefix(msg))
217 }
218}
219
220#[cfg(feature = "algorithm")]
221impl<C, D> DigestSigner<D, (Signature<C>, RecoveryId)> for SigningKey<C>
222where
223 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
224 D: Digest + Update,
225 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
226{
227 fn try_sign_digest<F: Fn(&mut D) -> Result<()>>(
228 &self,
229 f: F,
230 ) -> Result<(Signature<C>, RecoveryId)> {
231 let mut digest = D::new();
232 f(&mut digest)?;
233 Ok(self.sign_digest_recoverable(digest))
234 }
235}
236
237#[cfg(feature = "algorithm")]
238impl<C> RandomizedPrehashSigner<(Signature<C>, RecoveryId)> for SigningKey<C>
239where
240 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
241 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
242{
243 fn sign_prehash_with_rng<R: TryCryptoRng + ?Sized>(
244 &self,
245 rng: &mut R,
246 prehash: &[u8],
247 ) -> Result<(Signature<C>, RecoveryId)> {
248 self.sign_prehash_recoverable_with_rng(rng, prehash)
249 .map_err(|_| Error::new())
250 }
251}
252
253#[cfg(feature = "algorithm")]
254impl<C, D> RandomizedDigestSigner<D, (Signature<C>, RecoveryId)> for SigningKey<C>
255where
256 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
257 D: Digest + Update,
258 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
259{
260 fn try_sign_digest_with_rng<R: TryCryptoRng + ?Sized, F: Fn(&mut D) -> Result<()>>(
261 &self,
262 rng: &mut R,
263 f: F,
264 ) -> Result<(Signature<C>, RecoveryId)> {
265 let mut digest = D::new();
266 f(&mut digest)?;
267 self.sign_prehash_with_rng(rng, &digest.finalize())
268 }
269}
270
271#[cfg(feature = "algorithm")]
272impl<C> PrehashSigner<(Signature<C>, RecoveryId)> for SigningKey<C>
273where
274 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
275 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
276{
277 fn sign_prehash(&self, prehash: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
278 Ok(self.sign_prehash_recoverable(prehash))
279 }
280}
281
282#[cfg(feature = "algorithm")]
283impl<C> Signer<(Signature<C>, RecoveryId)> for SigningKey<C>
284where
285 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
286 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
287{
288 fn try_sign(&self, msg: &[u8]) -> Result<(Signature<C>, RecoveryId)> {
289 self.try_multipart_sign(&[msg])
290 }
291}
292
293#[cfg(feature = "algorithm")]
294impl<C> MultipartSigner<(Signature<C>, RecoveryId)> for SigningKey<C>
295where
296 C: EcdsaCurve + CurveArithmetic + DigestAlgorithm,
297 Scalar<C>: Invert<Output = CtOption<Scalar<C>>>,
298{
299 fn try_multipart_sign(&self, msg: &[&[u8]]) -> Result<(Signature<C>, RecoveryId)> {
300 let mut digest = C::Digest::new();
301 msg.iter()
302 .for_each(|slice| Update::update(&mut digest, slice));
303 Ok(self.sign_digest_recoverable(digest))
304 }
305}
306
307#[cfg(feature = "algorithm")]
308impl<C> VerifyingKey<C>
309where
310 C: EcdsaCurve + CurveArithmetic,
311 AffinePoint<C>: DecompressPoint<C> + FromSec1Point<C> + ToSec1Point<C>,
312 FieldBytesSize<C>: sec1::ModulusSize,
313{
314 pub fn recover_from_msg(
321 msg: &[u8],
322 signature: &Signature<C>,
323 recovery_id: RecoveryId,
324 ) -> Result<Self>
325 where
326 C: DigestAlgorithm,
327 {
328 Self::recover_from_digest(C::Digest::new_with_prefix(msg), signature, recovery_id)
329 }
330
331 pub fn recover_from_digest<D>(
336 msg_digest: D,
337 signature: &Signature<C>,
338 recovery_id: RecoveryId,
339 ) -> Result<Self>
340 where
341 D: Digest,
342 {
343 Self::recover_from_prehash(&msg_digest.finalize(), signature, recovery_id)
344 }
345
346 #[allow(non_snake_case)]
363 pub fn recover_from_prehash(
364 prehash: &[u8],
365 signature: &Signature<C>,
366 recovery_id: RecoveryId,
367 ) -> Result<Self> {
368 let (r, s) = signature.split_scalars();
369 let z = bytes2scalar::<C>(prehash);
370
371 let r_bytes = if recovery_id.is_x_reduced() {
372 let uint = field::bytes_to_uint::<C>(&r.to_repr())
373 .checked_add(&C::ORDER)
374 .into_option()
375 .ok_or_else(Error::new)?;
376
377 field::uint_to_bytes::<C>(&uint)
378 } else {
379 r.to_repr()
380 };
381
382 let R: ProjectivePoint<C> =
383 AffinePoint::<C>::decompress(&r_bytes, u8::from(recovery_id.is_y_odd()).into())
384 .into_option()
385 .ok_or_else(Error::new)?
386 .into();
387
388 let r_inv = *r.invert();
389 let u1 = -(r_inv * z);
390 let u2 = r_inv * *s;
391 let pk = ProjectivePoint::<C>::mul_by_generator_and_mul_add_vartime(&u1, &u2, &R);
392 Self::from_affine(pk.to_affine())
393 }
394}
395
396#[cfg(test)]
397mod 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}