#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
#![cfg_attr(feature = "std", doc = "```")]
#![cfg_attr(not(feature = "std"), doc = "```ignore")]
pub use ecdsa_core::{
signature::{self, Error},
RecoveryId,
};
#[cfg(feature = "ecdsa")]
pub use ecdsa_core::hazmat;
use crate::Secp256k1;
#[cfg(feature = "ecdsa")]
use {
crate::{AffinePoint, FieldBytes, ProjectivePoint, Scalar, U256},
core::borrow::Borrow,
ecdsa_core::hazmat::{SignPrimitive, VerifyPrimitive},
elliptic_curve::{
ops::{Invert, Reduce},
subtle::CtOption,
IsHigh,
},
};
pub type Signature = ecdsa_core::Signature<Secp256k1>;
pub type DerSignature = ecdsa_core::der::Signature<Secp256k1>;
#[cfg(feature = "ecdsa")]
pub type SigningKey = ecdsa_core::SigningKey<Secp256k1>;
#[cfg(feature = "ecdsa")]
pub type VerifyingKey = ecdsa_core::VerifyingKey<Secp256k1>;
#[cfg(feature = "sha256")]
impl ecdsa_core::hazmat::DigestPrimitive for Secp256k1 {
type Digest = sha2::Sha256;
}
#[cfg(feature = "ecdsa")]
impl SignPrimitive<Secp256k1> for Scalar {
#[allow(non_snake_case, clippy::many_single_char_names)]
fn try_sign_prehashed<K>(
&self,
ephemeral_scalar: K,
z: FieldBytes,
) -> Result<(Signature, Option<RecoveryId>), Error>
where
K: Borrow<Scalar> + Invert<Output = CtOption<Scalar>>,
{
let z = <Self as Reduce<U256>>::from_be_bytes_reduced(z);
let k_inverse = ephemeral_scalar.invert();
let k = ephemeral_scalar.borrow();
if k_inverse.is_none().into() || k.is_zero().into() {
return Err(Error::new());
}
let k_inverse = k_inverse.unwrap();
let R = ProjectivePoint::mul_by_generator(k).to_affine();
let r = <Scalar as Reduce<U256>>::from_be_bytes_reduced(R.x.to_bytes());
let s = k_inverse * (z + (r * self));
if s.is_zero().into() {
return Err(Error::new());
}
let signature = Signature::from_scalars(r, s)?;
let is_r_odd = R.y.normalize().is_odd();
let is_s_high = signature.s().is_high();
let is_y_odd = is_r_odd ^ is_s_high;
let signature_low = signature.normalize_s().unwrap_or(signature);
let recovery_id = ecdsa_core::RecoveryId::new(is_y_odd.into(), false);
Ok((signature_low, Some(recovery_id)))
}
}
#[cfg(feature = "ecdsa")]
impl VerifyPrimitive<Secp256k1> for AffinePoint {}
#[cfg(all(test, feature = "ecdsa", feature = "arithmetic"))]
mod tests {
mod normalize {
use crate::ecdsa::Signature;
#[test]
#[rustfmt::skip]
fn s_high() {
let sig_hi = Signature::try_from([
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,
].as_slice()).unwrap();
let sig_lo = Signature::try_from([
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,
].as_slice()).unwrap();
let sig_normalized = sig_hi.normalize_s().unwrap();
assert_eq!(sig_lo, sig_normalized);
}
#[test]
fn s_low() {
#[rustfmt::skip]
let sig = Signature::try_from([
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,
].as_slice()).unwrap();
assert_eq!(sig.normalize_s(), None);
}
}
#[cfg(feature = "sha256")]
mod recovery {
use crate::{
ecdsa::{RecoveryId, Signature, VerifyingKey},
EncodedPoint,
};
use hex_literal::hex;
use sha2::{Digest, Sha256};
struct RecoveryTestVector {
pk: [u8; 33],
msg: &'static [u8],
sig: [u8; 64],
recid: RecoveryId,
}
const RECOVERY_TEST_VECTORS: &[RecoveryTestVector] = &[
RecoveryTestVector {
pk: hex!("021a7a569e91dbf60581509c7fc946d1003b60c7dee85299538db6353538d59574"),
msg: b"example message",
sig: hex!(
"ce53abb3721bafc561408ce8ff99c909f7f0b18a2f788649d6470162ab1aa032
3971edc523a6d6453f3fb6128d318d9db1a5ff3386feb1047d9816e780039d52"
),
recid: RecoveryId::new(false, false),
},
RecoveryTestVector {
pk: hex!("036d6caac248af96f6afa7f904f550253a0f3ef3f5aa2fe6838a95b216691468e2"),
msg: b"example message",
sig: hex!(
"46c05b6368a44b8810d79859441d819b8e7cdc8bfd371e35c53196f4bcacdb51
35c7facce2a97b95eacba8a586d87b7958aaf8368ab29cee481f76e871dbd9cb"
),
recid: RecoveryId::new(true, false),
},
];
#[test]
fn public_key_recovery() {
for vector in RECOVERY_TEST_VECTORS {
let digest = Sha256::new_with_prefix(vector.msg);
let sig = Signature::try_from(vector.sig.as_slice()).unwrap();
let recid = vector.recid;
let pk = VerifyingKey::recover_from_digest(digest, &sig, recid).unwrap();
assert_eq!(&vector.pk[..], EncodedPoint::from(&pk).as_bytes());
}
}
}
mod wycheproof {
use crate::{EncodedPoint, Secp256k1};
use ecdsa_core::{signature::Verifier, Signature};
#[test]
fn wycheproof() {
use blobby::Blob5Iterator;
use elliptic_curve::bigint::Encoding as _;
fn element_from_padded_slice<C: elliptic_curve::Curve>(
data: &[u8],
) -> elliptic_curve::FieldBytes<C> {
let point_len = C::UInt::BYTE_SIZE;
if data.len() >= point_len {
let offset = data.len() - point_len;
for v in data.iter().take(offset) {
assert_eq!(*v, 0, "EcdsaVerifier: point too large");
}
elliptic_curve::FieldBytes::<C>::clone_from_slice(&data[offset..])
} else {
let iter = core::iter::repeat(0)
.take(point_len - data.len())
.chain(data.iter().cloned());
elliptic_curve::FieldBytes::<C>::from_exact_iter(iter).unwrap()
}
}
fn run_test(
wx: &[u8],
wy: &[u8],
msg: &[u8],
sig: &[u8],
pass: bool,
) -> Option<&'static str> {
let x = element_from_padded_slice::<Secp256k1>(wx);
let y = element_from_padded_slice::<Secp256k1>(wy);
let q_encoded =
EncodedPoint::from_affine_coordinates(&x, &y, false);
let verifying_key =
ecdsa_core::VerifyingKey::from_encoded_point(&q_encoded).unwrap();
let sig = match Signature::<Secp256k1>::from_der(sig) {
Ok(s) => s.normalize_s().unwrap_or(s),
Err(_) if !pass => return None,
Err(_) => return Some("failed to parse signature ASN.1"),
};
match verifying_key.verify(msg, &sig) {
Ok(_) if pass => None,
Ok(_) => Some("signature verify unexpectedly succeeded"),
Err(_) if !pass => None,
Err(_) => Some("signature verify failed"),
}
}
let data = include_bytes!(concat!("test_vectors/data/", "wycheproof", ".blb"));
for (i, row) in Blob5Iterator::new(data).unwrap().enumerate() {
let [wx, wy, msg, sig, status] = row.unwrap();
let pass = match status[0] {
0 => false,
1 => true,
_ => panic!("invalid value for pass flag"),
};
if let Some(desc) = run_test(wx, wy, msg, sig, pass) {
panic!(
"\n\
Failed test №{}: {}\n\
wx:\t{:?}\n\
wy:\t{:?}\n\
msg:\t{:?}\n\
sig:\t{:?}\n\
pass:\t{}\n",
i, desc, wx, wy, msg, sig, pass,
);
}
}
}
}
}