Skip to content

Commit a6f3bd6

Browse files
authored
k256: use Decompress to impl ECDSA pubkey recovery (#158)
Uses the point decompression trait rather than duplicating the point decompression logic.
1 parent 533f01a commit a6f3bd6

File tree

5 files changed

+57
-55
lines changed

5 files changed

+57
-55
lines changed

Cargo.lock

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

k256/src/ecdsa.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
//! [`signature::Signer`] trait. Example use cases for this include other
1111
//! software implementations of ECDSA/secp256k1 and wrappers for cloud KMS
1212
//! services or hardware devices (HSM or crypto hardware wallet).
13-
//! - `ecdsa`: provides the [`Signature`], [`Signer`], and [`Verifier`] types
14-
//! which natively implement ECDSA/secp256k1 signing and verification.
13+
//! - `ecdsa`: provides `ecdsa-core` plus the [`SigningKey`] and [`VerifyKey`]
14+
//! types which natively implement ECDSA/secp256k1 signing and verification.
1515
//!
1616
//! ## Signing/Verification Example
1717
//!

k256/src/ecdsa/recoverable.rs

Lines changed: 25 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,16 @@
1818
//!
1919
//! // Signing
2020
//! let signing_key = SigningKey::generate(&mut OsRng); // Serialize with `::to_bytes()`
21-
//! let public_key = EncodedPoint::from(&signing_key.verify_key());
21+
//! let verify_key = signing_key.verify_key();
2222
//! let message = b"ECDSA proves knowledge of a secret number in the context of a single message";
2323
//!
2424
//! // Note: the signature type must be annotated or otherwise inferrable as
2525
//! // `Signer` has many impls of the `Signer` trait (for both regular and
2626
//! // recoverable signature types).
2727
//! let signature: recoverable::Signature = signing_key.sign(message);
28-
//! let recovered_pubkey = signature.recover_public_key(message).expect("couldn't recover pubkey");
28+
//! let recovered_key = signature.recover_verify_key(message).expect("couldn't recover pubkey");
2929
//!
30-
//! assert_eq!(&public_key, &recovered_pubkey);
30+
//! assert_eq!(&verify_key, &recovered_key);
3131
//! # }
3232
//! ```
3333
@@ -39,12 +39,10 @@ use ecdsa_core::{signature::Signature as _, Error};
3939

4040
#[cfg(feature = "ecdsa")]
4141
use crate::{
42-
arithmetic::{FieldElement, CURVE_EQUATION_B},
42+
ecdsa::VerifyKey,
4343
elliptic_curve::{
44-
consts::U32,
45-
ops::Invert,
46-
subtle::{Choice, ConditionallySelectable},
47-
Digest, FromBytes, FromDigest,
44+
consts::U32, ops::Invert, subtle::Choice, weierstrass::point::Decompress, Digest,
45+
FromBytes, FromDigest,
4846
},
4947
AffinePoint, NonZeroScalar, ProjectivePoint, Scalar,
5048
};
@@ -108,8 +106,8 @@ impl Signature {
108106

109107
for recovery_id in 0..=1 {
110108
if let Ok(recoverable_signature) = Signature::new(&signature, Id(recovery_id)) {
111-
if let Ok(recovered_key) = recoverable_signature.recover_public_key(msg) {
112-
if public_key == &recovered_key {
109+
if let Ok(recovered_key) = recoverable_signature.recover_verify_key(msg) {
110+
if public_key == &EncodedPoint::from(&recovered_key) {
113111
return Ok(recoverable_signature);
114112
}
115113
}
@@ -123,52 +121,38 @@ impl Signature {
123121
/// [`EncodedPoint`].
124122
#[cfg(all(feature = "ecdsa", feature = "keccak256"))]
125123
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")), doc(cfg(feature = "keccak256")))]
126-
pub fn recover_public_key(&self, msg: &[u8]) -> Result<EncodedPoint, Error> {
127-
self.recover_public_key_from_prehash(Keccak256::new().chain(msg))
124+
pub fn recover_verify_key(&self, msg: &[u8]) -> Result<VerifyKey, Error> {
125+
self.recover_verify_key_from_digest(Keccak256::new().chain(msg))
128126
}
129127

130128
/// Recover the public key used to create the given signature as an
131129
/// [`EncodedPoint`] from the provided precomputed [`Digest`].
132130
#[cfg(feature = "ecdsa")]
133131
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
134132
#[allow(non_snake_case, clippy::many_single_char_names)]
135-
pub fn recover_public_key_from_prehash<D>(&self, msg_prehash: D) -> Result<EncodedPoint, Error>
133+
pub fn recover_verify_key_from_digest<D>(&self, msg_prehash: D) -> Result<VerifyKey, Error>
136134
where
137135
D: Digest<OutputSize = U32>,
138136
{
139137
let r = self.r();
140138
let s = self.s();
141139
let z = Scalar::from_digest(msg_prehash);
142-
let x = FieldElement::from_bytes(&r.to_bytes());
143-
144-
let pk = x.and_then(|x| {
145-
let alpha = (x * &x * &x) + &CURVE_EQUATION_B;
146-
let beta = alpha.sqrt().unwrap();
147-
148-
let y = FieldElement::conditional_select(
149-
&beta.negate(1),
150-
&beta,
151-
// beta.is_odd() == recovery_id.is_y_odd()
152-
!(beta.normalize().is_odd() ^ self.recovery_id().is_y_odd()),
153-
);
154-
155-
let R = ProjectivePoint::from(AffinePoint {
156-
x,
157-
y: y.normalize(),
158-
});
140+
let R = AffinePoint::decompress(&r.to_bytes(), self.recovery_id().is_y_odd());
159141

142+
// TODO(tarcieri): replace with into conversion when available (see subtle#73)
143+
if R.is_some().into() {
144+
let R = ProjectivePoint::from(R.unwrap());
160145
let r_inv = r.invert().unwrap();
161146
let u1 = -(r_inv * &z);
162-
let u2 = r_inv * s.as_ref();
163-
((&ProjectivePoint::generator() * &u1) + &(R * &u2)).to_affine()
164-
});
147+
let u2 = r_inv * &*s;
148+
let pk = ((&ProjectivePoint::generator() * &u1) + &(R * &u2)).to_affine();
165149

166-
// TODO(tarcieri): replace with into conversion when available (see subtle#73)
167-
if pk.is_some().into() {
168-
Ok(pk.unwrap().into())
169-
} else {
170-
Err(Error::new())
150+
if pk.is_some().into() {
151+
return Ok(VerifyKey::from(&pk.unwrap()));
152+
}
171153
}
154+
155+
Err(Error::new())
172156
}
173157

174158
/// Parse the `r` component of this signature to a [`Scalar`]
@@ -298,6 +282,7 @@ impl From<Id> for u8 {
298282
#[cfg(all(test, feature = "ecdsa", feature = "sha256"))]
299283
mod tests {
300284
use super::Signature;
285+
use crate::EncodedPoint;
301286
use core::convert::TryFrom;
302287
use hex_literal::hex;
303288
use sha2::{Digest, Sha256};
@@ -335,8 +320,8 @@ mod tests {
335320
for vector in VECTORS {
336321
let sig = Signature::try_from(&vector.sig[..]).unwrap();
337322
let prehash = Sha256::new().chain(vector.msg);
338-
let pk = sig.recover_public_key_from_prehash(prehash).unwrap();
339-
assert_eq!(&vector.pk[..], pk.as_bytes());
323+
let pk = sig.recover_verify_key_from_digest(prehash).unwrap();
324+
assert_eq!(&vector.pk[..], EncodedPoint::from(&pk).as_bytes());
340325
}
341326
}
342327
}

k256/src/ecdsa/verify.rs

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,17 @@
11
//! ECDSA verifier
22
33
use super::{recoverable, Error, Signature};
4-
use crate::{AffinePoint, EncodedPoint, NonZeroScalar, ProjectivePoint, Scalar, Secp256k1};
4+
use crate::{
5+
AffinePoint, CompressedPoint, EncodedPoint, NonZeroScalar, ProjectivePoint, Scalar, Secp256k1,
6+
};
7+
use core::convert::TryFrom;
58
use ecdsa_core::{hazmat::VerifyPrimitive, signature};
69
use elliptic_curve::{consts::U32, ops::Invert, sec1::ToEncodedPoint, FromBytes};
710
use signature::{digest::Digest, DigestVerifier, PrehashSignature};
811

912
/// ECDSA/secp256k1 verification key (i.e. public key)
1013
#[cfg_attr(docsrs, doc(cfg(feature = "ecdsa")))]
14+
#[derive(Clone, Debug, Eq, PartialEq)]
1115
pub struct VerifyKey {
1216
/// Core ECDSA verify key
1317
pub(super) key: ecdsa_core::VerifyKey<Secp256k1>,
@@ -19,15 +23,17 @@ impl VerifyKey {
1923
ecdsa_core::VerifyKey::new(bytes).map(|key| VerifyKey { key })
2024
}
2125

22-
/// Initialize [`VerifyKey`] from an [`EncodedPoint`].
26+
/// Initialize [`VerifyKey`] from a SEC1 [`EncodedPoint`].
2327
pub fn from_encoded_point(public_key: &EncodedPoint) -> Result<Self, Error> {
2428
ecdsa_core::VerifyKey::from_encoded_point(public_key).map(|key| VerifyKey { key })
2529
}
2630

27-
/// Serialize this [`VerifyKey`] as a SEC1 [`EncodedPoint`], optionally
28-
/// applying point compression.
29-
pub fn to_encoded_point(&self, compress: bool) -> EncodedPoint {
30-
self.key.to_encoded_point(compress)
31+
/// Serialize this [`VerifyKey`] as a SEC1-encoded bytestring
32+
/// (with point compression applied)
33+
pub fn to_bytes(&self) -> CompressedPoint {
34+
let mut result = [0u8; 33];
35+
result.copy_from_slice(EncodedPoint::from(self).as_ref());
36+
result
3137
}
3238
}
3339

@@ -67,7 +73,15 @@ impl From<&AffinePoint> for VerifyKey {
6773

6874
impl From<&VerifyKey> for EncodedPoint {
6975
fn from(verify_key: &VerifyKey) -> EncodedPoint {
70-
verify_key.to_encoded_point(true)
76+
verify_key.key.to_encoded_point(true)
77+
}
78+
}
79+
80+
impl TryFrom<&EncodedPoint> for VerifyKey {
81+
type Error = Error;
82+
83+
fn try_from(encoded_point: &EncodedPoint) -> Result<Self, Error> {
84+
Self::from_encoded_point(encoded_point)
7185
}
7286
}
7387

k256/src/lib.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,16 @@ impl elliptic_curve::Identifier for Secp256k1 {
104104
const OID: ObjectIdentifier = ObjectIdentifier::new(&[1, 3, 132, 0, 10]);
105105
}
106106

107-
/// K-256 Serialized Field Element.
107+
/// Compressed SEC1-encoded secp256k1 (K-256) point (i.e. public key)
108+
pub type CompressedPoint = [u8; 33];
109+
110+
/// secp256k1 (K-256) serialized field element.
108111
///
109112
/// Byte array containing a serialized field element value (base field or scalar).
110113
pub type ElementBytes = elliptic_curve::ElementBytes<Secp256k1>;
111114

112-
/// K-256 (secp256k1) SEC1 Encoded Point.
115+
/// SEC1-encoded secp256k1 (K-256) curve point.
113116
pub type EncodedPoint = elliptic_curve::sec1::EncodedPoint<Secp256k1>;
114117

115-
/// K-256 (secp256k1) Secret Key.
118+
/// secp256k1 (K-256) secret key.
116119
pub type SecretKey = elliptic_curve::SecretKey<Secp256k1>;

0 commit comments

Comments
 (0)