use rand_core::OsRng;
use p256::ecdsa::signature::{Signer as _, Verifier as _};
use crate::asymmetric::KeyPairHex;
use crate::contract::{Identified, Signer};
use crate::error::{Error, Result};
use crate::internal::{hex_decode, hex_encode};
macro_rules! ecdsa_signer {
($(#[$doc:meta])* $name:ident, $label:literal, $identifier:literal, $curve:ident) => {
$(#[$doc])*
#[derive(Debug, Default, Clone, Copy)]
pub struct $name;
impl $name {
pub const IDENTIFIER: &'static str = $identifier;
pub fn new() -> Self {
Self
}
fn sign_hex(message: &[u8], private_key_hex: &str) -> Result<String> {
let private_bytes = hex_decode(private_key_hex)
.map_err(|_| Error::InvalidKey { label: $label })?;
let signing_key = $curve::ecdsa::SigningKey::from_slice(&private_bytes)
.map_err(|_| Error::InvalidKey { label: $label })?;
let signature: $curve::ecdsa::Signature = signing_key
.try_sign(message)
.map_err(|_| Error::SignFailed { label: $label })?;
let signature = signature.normalize_s().unwrap_or(signature);
Ok(hex_encode(signature.to_bytes().as_slice()))
}
fn verify_hex(
message: &[u8],
signature_hex: &str,
public_key_hex: &str,
) -> Result<()> {
let public_bytes = hex_decode(public_key_hex)
.map_err(|_| Error::VerificationFailed { label: $label })?;
let verifying_key = $curve::ecdsa::VerifyingKey::from_sec1_bytes(&public_bytes)
.map_err(|_| Error::VerificationFailed { label: $label })?;
let signature_bytes = hex_decode(signature_hex)
.map_err(|_| Error::VerificationFailed { label: $label })?;
let signature = $curve::ecdsa::Signature::from_slice(&signature_bytes)
.map_err(|_| Error::VerificationFailed { label: $label })?;
verifying_key
.verify(message, &signature)
.map_err(|_| Error::VerificationFailed { label: $label })
}
pub fn generate_key_pair_hex() -> Result<KeyPairHex> {
let signing_key = $curve::ecdsa::SigningKey::random(&mut OsRng);
let public_key = signing_key.verifying_key().to_encoded_point(false);
Ok(KeyPairHex {
private_key_hex: hex_encode(signing_key.to_bytes().as_slice()),
public_key_hex: hex_encode(public_key.as_bytes()),
})
}
}
impl Identified for $name {
fn identifier(&self) -> &str {
Self::IDENTIFIER
}
}
impl Signer for $name {
fn sign(&self, message: &[u8], private_key_hex: &str) -> Result<String> {
Self::sign_hex(message, private_key_hex)
}
fn verify(
&self,
message: &[u8],
signature_hex: &str,
public_key_hex: &str,
) -> Result<()> {
Self::verify_hex(message, signature_hex, public_key_hex)
}
}
};
}
ecdsa_signer!(
EcdsaP256Signer,
"ECDSA P-256",
"ecdsa-p256-sha256",
p256
);
ecdsa_signer!(
EcdsaP384Signer,
"ECDSA P-384",
"ecdsa-p384-sha384",
p384
);
ecdsa_signer!(
EcdsaSecp256k1Signer,
"ECDSA secp256k1",
"ecdsa-secp256k1-sha256",
k256
);
#[cfg(test)]
mod tests {
use super::*;
fn assert_roundtrip(signer: &dyn Signer, pair: &KeyPairHex, other: &KeyPairHex) {
assert_eq!(
pair.private_key_hex.len() * 2 + 2,
pair.public_key_hex.len()
);
assert!(pair.public_key_hex.starts_with("04"));
let signature = signer.sign(b"payload", &pair.private_key_hex).unwrap();
assert_eq!(signature.len(), pair.private_key_hex.len() * 2);
signer
.verify(b"payload", &signature, &pair.public_key_hex)
.unwrap();
let error = signer
.verify(b"payloaD", &signature, &pair.public_key_hex)
.unwrap_err();
assert!(matches!(error, Error::VerificationFailed { .. }));
let error = signer
.verify(b"payload", &signature, &other.public_key_hex)
.unwrap_err();
assert!(matches!(error, Error::VerificationFailed { .. }));
}
#[test]
fn p256_roundtrip_and_hex_shapes() {
let pair = EcdsaP256Signer::generate_key_pair_hex().unwrap();
let other = EcdsaP256Signer::generate_key_pair_hex().unwrap();
assert_eq!(pair.private_key_hex.len(), 64);
assert_eq!(pair.public_key_hex.len(), 130);
assert_roundtrip(&EcdsaP256Signer::new(), &pair, &other);
}
#[test]
fn p384_roundtrip_and_hex_shapes() {
let pair = EcdsaP384Signer::generate_key_pair_hex().unwrap();
let other = EcdsaP384Signer::generate_key_pair_hex().unwrap();
assert_eq!(pair.private_key_hex.len(), 96);
assert_eq!(pair.public_key_hex.len(), 194);
assert_roundtrip(&EcdsaP384Signer::new(), &pair, &other);
}
#[test]
fn secp256k1_roundtrip_and_hex_shapes() {
let pair = EcdsaSecp256k1Signer::generate_key_pair_hex().unwrap();
let other = EcdsaSecp256k1Signer::generate_key_pair_hex().unwrap();
assert_eq!(pair.private_key_hex.len(), 64);
assert_eq!(pair.public_key_hex.len(), 130);
assert_roundtrip(&EcdsaSecp256k1Signer::new(), &pair, &other);
}
#[test]
fn rfc6979_p256_sample_matches_vector() {
const X: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721";
const PUBLIC_KEY: &str = concat!(
"0460fed4ba255a9d31c961eb74c6356d68c049b8923b61fa6ce669622e60f29fb6",
"7903fe1008b8bc99a41ae9e95628bc64f2f1b20c2d7e9f5177a3c294d4462299",
);
const EXPECTED: &str = concat!(
"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716",
"0834e36ad29a83bf2bc9385e491d6099c8fdf9d1ed67aa7ea5f51f93782857a9",
);
const VECTOR: &str = concat!(
"efd48b2aacb6a8fd1140dd9cd45e81d69d2c877b56aaf991c34d0ea84eaf3716",
"f7cb1c942d657c41d436c7a1b6e29f65f3e900dbb9aff4064dc4ab2f843acda8",
);
let signer = EcdsaP256Signer::new();
let signature = signer.sign(b"sample", X).unwrap();
assert_eq!(signature, EXPECTED);
signer.verify(b"sample", &signature, PUBLIC_KEY).unwrap();
signer.verify(b"sample", VECTOR, PUBLIC_KEY).unwrap();
assert!(signer.verify(b"sampl3", &signature, PUBLIC_KEY).is_err());
}
#[test]
fn rfc6979_p256_test_message_matches_vector() {
const X: &str = "c9afa9d845ba75166b5c215767b1d6934e50c3db36e89b127b8a622b120f6721";
const EXPECTED: &str = concat!(
"f1abb023518351cd71d881567b1ea663ed3efcf6c5132b354f28d3b0b7d38367",
"019f4113742a2b14bd25926b49c649155f267e60d3814b4c0cc84250e46f0083",
);
let signature = EcdsaP256Signer::new().sign(b"test", X).unwrap();
assert_eq!(signature, EXPECTED);
}
#[test]
fn rfc6979_p384_sample_matches_vector() {
const X: &str = concat!(
"6b9d3dad2e1b8c1c05b19875b6659f4de23c3b667bf297ba9aa47740787137d8",
"96d5724e4c70a825f872c9ea60d2edf5",
);
const EXPECTED: &str = concat!(
"94edbb92a5ecb8aad4736e56c691916b3f88140666ce9fa73d64c4ea95ad133c",
"81a648152e44acf96e36dd1e80fabe46",
"6610b514ea0e87315e01bf24d9fcec70ecf18bf5e69dbad9a727ea302393848f",
"b4f04c6cd0423fdc7169525094629eab",
);
let signature = EcdsaP384Signer::new().sign(b"sample", X).unwrap();
assert_eq!(signature, EXPECTED);
}
#[test]
fn rfc6979_secp256k1_satoshi_vector_matches() {
const X: &str = "0000000000000000000000000000000000000000000000000000000000000001";
const EXPECTED: &str = concat!(
"934b1ea10a4b3c1757e2b0c017d0b6143ce3c9a7e6a4a49860d7a6ab210ee3d8",
"2442ce9d2b916064108014783e923ec36b49743e2ffa1c4496f01a512aafd9e5",
);
const PUBLIC_KEY: &str = concat!(
"0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
"483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
);
let signer = EcdsaSecp256k1Signer::new();
let signature = signer.sign(b"Satoshi Nakamoto", X).unwrap();
assert_eq!(signature, EXPECTED);
signer
.verify(b"Satoshi Nakamoto", &signature, PUBLIC_KEY)
.unwrap();
assert!(
signer
.verify(b"Satoshi Nakamot0", &signature, PUBLIC_KEY)
.is_err()
);
}
#[test]
fn signatures_are_deterministic() {
let pair = EcdsaP256Signer::generate_key_pair_hex().unwrap();
let signer = EcdsaP256Signer::new();
assert_eq!(
signer.sign(b"same", &pair.private_key_hex).unwrap(),
signer.sign(b"same", &pair.private_key_hex).unwrap(),
);
}
#[test]
fn malformed_keys_are_rejected() {
let pair = EcdsaP256Signer::generate_key_pair_hex().unwrap();
let signer = EcdsaP256Signer::new();
assert_eq!(
signer.sign(b"x", "zz").unwrap_err(),
Error::InvalidKey {
label: "ECDSA P-256"
}
);
assert_eq!(
signer.sign(b"x", "abcd").unwrap_err(),
Error::InvalidKey {
label: "ECDSA P-256"
}
);
let signature = signer.sign(b"x", &pair.private_key_hex).unwrap();
assert_eq!(
signer.verify(b"x", &signature, "04deadbeef").unwrap_err(),
Error::VerificationFailed {
label: "ECDSA P-256"
}
);
assert_eq!(
signer.verify(b"x", "00", &pair.public_key_hex).unwrap_err(),
Error::VerificationFailed {
label: "ECDSA P-256"
}
);
}
#[test]
fn identifiers_are_stable() {
assert_eq!(EcdsaP256Signer::new().identifier(), "ecdsa-p256-sha256");
assert_eq!(EcdsaP384Signer::new().identifier(), "ecdsa-p384-sha384");
assert_eq!(
EcdsaSecp256k1Signer::new().identifier(),
"ecdsa-secp256k1-sha256"
);
}
}