-
Notifications
You must be signed in to change notification settings - Fork 198
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The equivalents of these types used to live in the `ecdsa` crate, but were removed in this PR: RustCrypto/signatures#96 The goal of that PR was to reverse the previous relationship where the `ecdsa` crate depended on the `k256`/`p256`/`p384` crates, and instead have the curve implementation crates consume the `ecdsa` crate as an (optional) dependency. It makes each curve implementation a one-stop-shop for everything related to that curve, while allowing the ECDSA crate to provide some common functionality like ASN.1 (de)serialization, in addition to allowing it to export "primitive" traits which can be used with the goal of a reusable high-level ECDSA implementation which is generic over elliptic curves. This commit ports over equivalent types that were removed in `RustCrypto/signatures#96`, but also incorporates these changes: RustCrypto/signatures#98 Where the `ecdsa` crate previously had `Asn1Signature` and `FixedSignature` types generic over a curve, the PR above refactored it to make the "fixed" form the preferred `Signature` type, and refactoring ASN.1 DER support into an `ecdsa::asn1::Document` type. The nice advantage of that approach is it means the curve implementations no longer need to worry about an `Asn1Signature` type and can focus on `ecdsa::Signature` as the type they need to support.
- Loading branch information
Showing
12 changed files
with
331 additions
and
19 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
//! Elliptic Curve Digital Signature Algorithm (ECDSA) | ||
pub mod recoverable; | ||
|
||
pub use recoverable::{RecoverableSignature, RecoveryId}; | ||
|
||
use super::Secp256k1; | ||
|
||
#[cfg(feature = "arithmetic")] | ||
use crate::{elliptic_curve::subtle::ConditionallySelectable, Scalar}; | ||
|
||
#[cfg(feature = "arithmetic")] | ||
use ecdsa::Error; | ||
|
||
/// ECDSA/secp256k1 signature (fixed-size) | ||
pub type Signature = ::ecdsa::Signature<Secp256k1>; | ||
|
||
#[cfg(feature = "sha256")] | ||
#[cfg_attr(docsrs, doc(cfg(feature = "sha256")))] | ||
impl ecdsa::hazmat::DigestPrimitive for Secp256k1 { | ||
type Digest = sha2::Sha256; | ||
} | ||
|
||
#[cfg(feature = "arithmetic")] | ||
#[cfg_attr(docsrs, doc(cfg(feature = "arithmetic")))] | ||
/// Normalize signature into "low S" form as described in | ||
/// [BIP 0062: Dealing with Malleability][1]. | ||
/// | ||
/// [1]: https://github.com/bitcoin/bips/blob/master/bip-0062.mediawiki | ||
pub fn normalize_s(signature: &Signature) -> Result<Signature, Error> { | ||
use core::convert::TryInto; | ||
let s_option = Scalar::from_bytes(signature.as_ref()[32..].try_into().unwrap()); | ||
|
||
// Not constant time, but we're operating on public values | ||
let s = if s_option.is_some().into() { | ||
s_option.unwrap() | ||
} else { | ||
return Err(Error::new()); | ||
}; | ||
|
||
// Negate `s` if it's within the upper half of the modulus | ||
let s_neg = -s; | ||
let low_s = Scalar::conditional_select(&s, &s_neg, s.is_high()); | ||
|
||
Ok(Signature::from_scalars( | ||
signature.r(), | ||
&low_s.to_bytes().into(), | ||
)) | ||
} | ||
|
||
#[cfg(all(test, feature = "arithmetic"))] | ||
mod tests { | ||
use super::*; | ||
use ecdsa::signature::Signature as _; | ||
|
||
#[test] | ||
fn already_normalized() { | ||
#[rustfmt::skip] | ||
let sig = Signature::from_bytes(&[ | ||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, | ||
]).unwrap(); | ||
|
||
let sig_normalized = normalize_s(&sig).unwrap(); | ||
assert_eq!(sig, sig_normalized); | ||
} | ||
|
||
// Test vectors generated using rust-secp256k1 | ||
#[test] | ||
#[rustfmt::skip] | ||
fn not_normalized() { | ||
let sig_hi = Signature::from_bytes(&[ | ||
0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10, | ||
0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30, | ||
0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c, | ||
0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc, | ||
0xee, 0x2f, 0x11, 0xef, 0x8c, 0xb0, 0x0a, 0x49, | ||
0x61, 0x7d, 0x13, 0x57, 0xf4, 0xd5, 0x56, 0x41, | ||
0x09, 0x0a, 0x48, 0xf2, 0x01, 0xe9, 0xb9, 0x59, | ||
0xc4, 0x8f, 0x6f, 0x6b, 0xec, 0x6f, 0x93, 0x8f, | ||
]).unwrap(); | ||
|
||
let sig_lo = Signature::from_bytes(&[ | ||
0x20, 0xc0, 0x1a, 0x91, 0x0e, 0xbb, 0x26, 0x10, | ||
0xaf, 0x2d, 0x76, 0x3f, 0xa0, 0x9b, 0x3b, 0x30, | ||
0x92, 0x3c, 0x8e, 0x40, 0x8b, 0x11, 0xdf, 0x2c, | ||
0x61, 0xad, 0x76, 0xd9, 0x70, 0xa2, 0xf1, 0xbc, | ||
0x11, 0xd0, 0xee, 0x10, 0x73, 0x4f, 0xf5, 0xb6, | ||
0x9e, 0x82, 0xec, 0xa8, 0x0b, 0x2a, 0xa9, 0xbd, | ||
0xb1, 0xa4, 0x93, 0xf4, 0xad, 0x5e, 0xe6, 0xe1, | ||
0xfb, 0x42, 0xef, 0x20, 0xe3, 0xc6, 0xad, 0xb2, | ||
]).unwrap(); | ||
|
||
let sig_normalized = normalize_s(&sig_hi).unwrap(); | ||
assert_eq!(sig_lo, sig_normalized); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
//! Ethereum-style "recoverable signatures" | ||
use super::Signature; | ||
use core::{ | ||
convert::{TryFrom, TryInto}, | ||
fmt::{self, Debug}, | ||
}; | ||
use ecdsa::{signature::Signature as _, Error}; | ||
|
||
#[cfg(docsrs)] | ||
use crate::PublicKey; | ||
|
||
/// Size of an Ethereum-style recoverable signature in bytes | ||
pub const SIZE: usize = 65; | ||
|
||
/// Ethereum-style "recoverable signatures" which allow for the recovery of | ||
/// the signer's [`PublicKey`] from the signature itself. | ||
/// | ||
/// This format consists of [`Signature`] followed by a 1-byte | ||
/// [`RecoveryId`] (65-bytes total): | ||
/// | ||
/// - `r`: 32-byte integer, big endian | ||
/// - `s`: 32-byte integer, big endian | ||
/// - `v`: 1-byte [`RecoveryId`] | ||
#[derive(Copy, Clone)] | ||
pub struct RecoverableSignature { | ||
bytes: [u8; SIZE], | ||
} | ||
|
||
impl RecoverableSignature { | ||
/// Get the [`RecoveryId`] for this signature | ||
pub fn recovery_id(self) -> RecoveryId { | ||
self.bytes[0].try_into().expect("invalid recovery ID") | ||
} | ||
} | ||
|
||
impl ecdsa::signature::Signature for RecoverableSignature { | ||
fn from_bytes(bytes: &[u8]) -> Result<Self, Error> { | ||
bytes.try_into() | ||
} | ||
} | ||
|
||
impl AsRef<[u8]> for RecoverableSignature { | ||
fn as_ref(&self) -> &[u8] { | ||
&self.bytes[..] | ||
} | ||
} | ||
|
||
impl Debug for RecoverableSignature { | ||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||
write!(f, "RecoverableSignature {{ bytes: {:?}) }}", self.as_ref()) | ||
} | ||
} | ||
|
||
// TODO(tarcieri): derive `Eq` after const generics are available | ||
impl Eq for RecoverableSignature {} | ||
|
||
// TODO(tarcieri): derive `PartialEq` after const generics are available | ||
impl PartialEq for RecoverableSignature { | ||
fn eq(&self, other: &Self) -> bool { | ||
self.as_ref().eq(other.as_ref()) | ||
} | ||
} | ||
|
||
impl TryFrom<&[u8]> for RecoverableSignature { | ||
type Error = Error; | ||
|
||
fn try_from(bytes: &[u8]) -> Result<Self, Error> { | ||
if bytes.len() == SIZE && RecoveryId::try_from(bytes[64]).is_ok() { | ||
let mut arr = [0u8; SIZE]; | ||
arr.copy_from_slice(bytes); | ||
Ok(Self { bytes: arr }) | ||
} else { | ||
Err(Error::new()) | ||
} | ||
} | ||
} | ||
|
||
impl From<RecoverableSignature> for Signature { | ||
fn from(sig: RecoverableSignature) -> Signature { | ||
Signature::from_bytes(&sig.bytes[..64]).unwrap() | ||
} | ||
} | ||
|
||
/// Identifier used to compute a `PublicKey` from a [`RecoverableSignature`] | ||
#[derive(Copy, Clone, Debug)] | ||
pub struct RecoveryId(u8); | ||
|
||
impl TryFrom<u8> for RecoveryId { | ||
type Error = Error; | ||
|
||
fn try_from(byte: u8) -> Result<Self, Error> { | ||
if byte < 4 { | ||
Ok(Self(byte)) | ||
} else { | ||
Err(Error::new()) | ||
} | ||
} | ||
} | ||
|
||
impl From<RecoveryId> for u8 { | ||
fn from(recovery_id: RecoveryId) -> u8 { | ||
recovery_id.0 | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.