From 261b9cc18fe18912fe6b4c8987f0a9c07992ef0c Mon Sep 17 00:00:00 2001 From: aumetra Date: Tue, 1 Nov 2022 23:06:51 +0100 Subject: [PATCH 1/2] ecdsa: Initial implementation of recoverable signatures --- ecdsa/src/lib.rs | 2 +- ecdsa/src/recovery.rs | 185 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 186 insertions(+), 1 deletion(-) diff --git a/ecdsa/src/lib.rs b/ecdsa/src/lib.rs index 6185fe12..0cf6313b 100644 --- a/ecdsa/src/lib.rs +++ b/ecdsa/src/lib.rs @@ -77,7 +77,7 @@ mod sign; #[cfg(feature = "verify")] mod verify; -pub use crate::recovery::RecoveryId; +pub use crate::recovery::*; // Re-export the `elliptic-curve` crate (and select types) pub use elliptic_curve::{self, sec1::EncodedPoint, PrimeCurve}; diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index c923bbad..8d71daf4 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -69,6 +69,191 @@ impl From for u8 { } } +#[cfg(all(feature = "arithmetic", feature = "verify"))] +mod signature { + use core::ops::Add; + + use crate::{ + hazmat::{DigestPrimitive, VerifyPrimitive}, + RecoveryId, SignatureSize, VerifyingKey, + }; + use elliptic_curve::{ + generic_array::{ + typenum::{Add1, Unsigned, B1}, + ArrayLength, GenericArray, + }, + ops::Reduce, + ops::{Invert, LinearCombination}, + sec1::{self, FromEncodedPoint, ToEncodedPoint}, + AffinePoint, DecompressPoint, FieldBytes, FieldSize, Group, PrimeCurve, PrimeField, + ProjectiveArithmetic, ProjectivePoint, Scalar, + }; + use signature::{digest::Digest, hazmat::PrehashVerifier, Error, Result, SignatureEncoding}; + + /// ECDSA signature with Ethereum-style "recoverable signatures" support + #[derive(Clone, Eq, PartialEq)] + pub struct Signature + where + C: PrimeCurve, + { + inner: crate::Signature, + recovery_id: RecoveryId, + } + + impl Signature + where + C: PrimeCurve, + { + /// Create a new signature with recovery support + pub fn new(inner: crate::Signature, recovery_id: RecoveryId) -> Self { + Self { inner, recovery_id } + } + } + + impl Signature + where + C: DigestPrimitive + ProjectiveArithmetic + PrimeCurve, + SignatureSize: ArrayLength, + Scalar: Reduce, + AffinePoint: DecompressPoint + + From> + + FromEncodedPoint + + ToEncodedPoint + + VerifyPrimitive, + FieldSize: sec1::ModulusSize, + { + /// Attempt to create a recoverable signature from a verifying key, message and its signature + /// + /// Uses the curve associated digest for hashing the message + pub fn from_trail_recovery( + verifying_key: &VerifyingKey, + message: &[u8], + signature: crate::Signature, + ) -> Result + where + ::Digest: Digest>, + { + Self::from_digest_trial_recovery( + verifying_key, + <::Digest>::new_with_prefix(message), + signature, + ) + } + + /// Attempt to create a recoverable signature from a verifying key, processed message and its signature + #[allow(clippy::unwrap_used)] + pub fn from_digest_trial_recovery( + verifying_key: &VerifyingKey, + digest: D, + signature: crate::Signature, + ) -> Result + where + D: Digest>, + { + let hash = digest.finalize(); + + for i in 0..=1 { + let recoverable_signature = + Self::new(signature.clone(), RecoveryId::from_byte(i).unwrap()); + + if let Ok(recovered_key) = + recoverable_signature.recover_verifying_key_from_digest_bytes(hash.clone()) + { + if recovered_key == *verifying_key + && recovered_key.verify_prehash(&hash, &signature).is_ok() + { + return Ok(recoverable_signature); + } + } + } + + Err(Error::new()) + } + + /// Recover the verifying key via the digest + pub fn recover_verifying_key_from_digest(&self, digest: D) -> Result> + where + D: Digest>, + { + self.recover_verifying_key_from_digest_bytes(digest.finalize()) + } + + /// Recover the verifying key via the digest bytes + #[allow(non_snake_case)] + pub fn recover_verifying_key_from_digest_bytes( + &self, + digest_bytes: FieldBytes, + ) -> Result> { + let r = self.inner.r(); + let s = self.inner.s(); + let z = as Reduce>::from_be_bytes_reduced(digest_bytes); + let R = AffinePoint::::decompress( + &r.to_repr(), + u8::from(self.recovery_id.is_y_odd()).into(), + ); + + if R.is_none().into() { + return Err(Error::new()); + } + + let R = ProjectivePoint::::from(R.unwrap()); + let r_inv = *r.invert(); + let u1 = -(r_inv * z); + let u2 = r_inv * *s; + let pk = + ProjectivePoint::::lincomb(&ProjectivePoint::::generator(), &u1, &R, &u2) + .into(); + + VerifyingKey::from_affine(pk) + } + } + + impl SignatureEncoding for Signature + where + C: PrimeCurve, + SignatureSize: Add + ArrayLength, + as Add>::Output: ArrayLength, + { + type Repr = GenericArray>>; + } + + impl From> for GenericArray>> + where + C: PrimeCurve, + SignatureSize: Add + ArrayLength, + as Add>::Output: ArrayLength, + { + fn from(sig: Signature) -> Self { + let mut serialised_array = as SignatureEncoding>::Repr::default(); + let mut_serialised_array = serialised_array.as_mut_slice(); + let inner_sig = sig.inner.to_bytes(); + + mut_serialised_array[..inner_sig.len()].copy_from_slice(&inner_sig); + mut_serialised_array[inner_sig.len()] = sig.recovery_id.to_byte(); + serialised_array + } + } + + impl TryFrom<&[u8]> for Signature + where + C: PrimeCurve, + SignatureSize: Add + ArrayLength, + as Add>::Output: ArrayLength, + { + type Error = Error; + + fn try_from(value: &[u8]) -> Result { + let signature = crate::Signature::try_from(&value[..SignatureSize::::to_usize()])?; + let recovery_id = RecoveryId::from_byte(value[SignatureSize::::to_usize()]) + .ok_or_else(Error::new)?; + Ok(Self::new(signature, recovery_id)) + } + } +} + +#[cfg(all(feature = "arithmetic", feature = "verify"))] +pub use self::signature::Signature as RecoverySignature; + #[cfg(test)] mod tests { use super::RecoveryId; From 18ca30e47325c809d83e2d594480c6551173d201 Mon Sep 17 00:00:00 2001 From: aumetra Date: Tue, 1 Nov 2022 23:24:22 +0100 Subject: [PATCH 2/2] Fix feature flags --- ecdsa/src/recovery.rs | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/ecdsa/src/recovery.rs b/ecdsa/src/recovery.rs index 8d71daf4..1b6851f3 100644 --- a/ecdsa/src/recovery.rs +++ b/ecdsa/src/recovery.rs @@ -69,21 +69,19 @@ impl From for u8 { } } -#[cfg(all(feature = "arithmetic", feature = "verify"))] +#[cfg(feature = "verify")] mod signature { - use core::ops::Add; - use crate::{ hazmat::{DigestPrimitive, VerifyPrimitive}, RecoveryId, SignatureSize, VerifyingKey, }; + use core::ops::Add; use elliptic_curve::{ generic_array::{ typenum::{Add1, Unsigned, B1}, ArrayLength, GenericArray, }, - ops::Reduce, - ops::{Invert, LinearCombination}, + ops::{Invert, LinearCombination, Reduce}, sec1::{self, FromEncodedPoint, ToEncodedPoint}, AffinePoint, DecompressPoint, FieldBytes, FieldSize, Group, PrimeCurve, PrimeField, ProjectiveArithmetic, ProjectivePoint, Scalar, @@ -251,7 +249,7 @@ mod signature { } } -#[cfg(all(feature = "arithmetic", feature = "verify"))] +#[cfg(feature = "verify")] pub use self::signature::Signature as RecoverySignature; #[cfg(test)]