Skip to content

Commit

Permalink
ECDSA types (#73)
Browse files Browse the repository at this point in the history
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
tarcieri authored Jul 14, 2020
1 parent bf40e82 commit 2adf766
Show file tree
Hide file tree
Showing 12 changed files with 331 additions and 19 deletions.
67 changes: 67 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@ members = [
]

[patch.crates-io]
ecdsa = { git = "https://github.com/RustCrypto/signatures" }
elliptic-curve = { git = "https://github.com/RustCrypto/traits" }
15 changes: 5 additions & 10 deletions k256/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,10 @@ keywords = ["bitcoin", "crypto", "ecc", "ethereum", "secp256k1"]

[dependencies]
cfg-if = "0.1"

[dependencies.elliptic-curve]
version = "= 0.5.0-pre"
default-features = false
features = ["weierstrass"]

[dependencies.zeroize]
version = "1"
optional = true
default-features = false
ecdsa = { version = "= 0.7.0-pre", optional = true, default-features = false }
elliptic-curve = { version = "= 0.5.0-pre", default-features = false, features = ["weierstrass"] }
sha2 = { version = "0.9", optional = true }
zeroize = { version = "1", optional = true, default-features = false }

[dev-dependencies]
hex = "0.4"
Expand All @@ -36,6 +30,7 @@ arithmetic = []
field-montgomery = []
force-32-bit = []
rand = ["elliptic-curve/rand_core"]
sha256 = ["ecdsa/digest", "ecdsa/hazmat", "sha2"]
test-vectors = []
std = ["elliptic-curve/std"]

Expand Down
99 changes: 99 additions & 0 deletions k256/src/ecdsa.rs
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);
}
}
105 changes: 105 additions & 0 deletions k256/src/ecdsa/recoverable.rs
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
}
}
4 changes: 4 additions & 0 deletions k256/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@
#[cfg(feature = "arithmetic")]
mod arithmetic;

#[cfg(feature = "ecdsa")]
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
pub mod ecdsa;

#[cfg(any(feature = "test-vectors", test))]
#[cfg_attr(docsrs, doc(cfg(feature = "test-vectors")))]
pub mod test_vectors;
Expand Down
Loading

0 comments on commit 2adf766

Please sign in to comment.