From 0326235b69f1ba4b4e2ec58604673860eeded261 Mon Sep 17 00:00:00 2001 From: Adrian Pauli Date: Thu, 5 May 2022 15:49:04 +0200 Subject: [PATCH 1/6] Add message signer & message signature verifier --- CHANGELOG.md | 1 + src/keys/mod.rs | 1 + src/keys/msgsig.rs | 128 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/keys/msgsig.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a720dfb59..c5d8344b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] - New MSRV set to `1.56.1` - Fee sniping discouraging through nLockTime - if the user specifies a `current_height`, we use that as a nlocktime, otherwise we use the last sync height (or 0 if we never synced) +- Add `MessageSigner` and `MessageSignatureVerifier` with ECDSA implementations ## [v0.19.0] - [v0.18.0] diff --git a/src/keys/mod.rs b/src/keys/mod.rs index 20ff58184..e94c32f6f 100644 --- a/src/keys/mod.rs +++ b/src/keys/mod.rs @@ -36,6 +36,7 @@ use crate::wallet::utils::SecpCtx; #[cfg(feature = "keys-bip39")] #[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))] pub mod bip39; +pub mod msgsig; /// Set of valid networks for a key pub type ValidNetworks = HashSet; diff --git a/src/keys/msgsig.rs b/src/keys/msgsig.rs new file mode 100644 index 000000000..0e7b05054 --- /dev/null +++ b/src/keys/msgsig.rs @@ -0,0 +1,128 @@ + +/// Signing arbitrary messages and verify message signatures + +use bitcoin::hashes::Hash; +use bitcoin::secp256k1::recovery::RecoverableSignature; +use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; +use bitcoin::util::misc::{signed_msg_hash, MessageSignature}; +use bitcoin::{Address, Network, PrivateKey, PublicKey}; + +/// Trait for message signers +pub trait MessageSigner { + /// Sign an arbitrary message + fn sign(&self, msg: &str) -> S; +} + +/// Trait for message signature verifiers +pub trait MessageSignatureVerifier { + /// Verify a signature against an arbitrary message + fn verify(&self, sig: S, msg: &str) -> bool; +} + +/// A message signer using ECDSA +pub struct EcdsaMessageSigner { + secp: Secp256k1, + secret_key: SecretKey, +} + +impl EcdsaMessageSigner { + + /// Creates message signer from a bitcoin ECDSA private key + pub fn from_prv(prv: PrivateKey) -> Self { + Self::from_secret_key(prv.key) + } + + /// Creates message signer from an ECDSA secret key + pub fn from_secret_key(secret_key: SecretKey) -> Self { + EcdsaMessageSigner { + secret_key, + secp: Secp256k1::new(), + } + } +} + +impl MessageSigner for EcdsaMessageSigner { + fn sign(&self, message: &str) -> RecoverableSignature { + let msg_hash = signed_msg_hash(message); + self.secp.sign_recoverable( + &Message::from_slice(&msg_hash.into_inner()[..]).unwrap(), + &self.secret_key, + ) + } +} + +/// A message signature verifier using ECDSA +pub struct EcdsaMessageSignatureVerifier { + secp: Secp256k1, + address: Address, +} + +impl EcdsaMessageSignatureVerifier { + + /// Creates a message signature verifier from a public key + pub fn from_pub(public_key: PublicKey) -> Self { + let address = Address::p2pkh(&public_key, Network::Bitcoin); + Self::from_address(address) + } + + /// Creates a message signature verifier from an address + pub fn from_address(address: Address) -> Self { + EcdsaMessageSignatureVerifier { + address, + secp: Secp256k1::new(), + } + } +} + +impl MessageSignatureVerifier for EcdsaMessageSignatureVerifier { + fn verify(&self, sig: RecoverableSignature, msg: &str) -> bool { + let message_sig = MessageSignature::new(sig, false); + let msg_hash = signed_msg_hash(msg); + return match message_sig.is_signed_by_address(&self.secp, &self.address, msg_hash) { + Ok(is_signed) => is_signed, + Err(_) => false, + }; + } +} + +#[cfg(test)] +mod ecdsa_msg_sign { + use bitcoin::secp256k1::Secp256k1; + use bitcoin::util::misc::MessageSignature; + use bitcoin::{Address, Network}; + + use super::*; + + pub const PRV_WIF: &str = "5KYZdUEo39z3FPrtuX2QbbwGnNP5zTd7yyr2SC1j299sBCnWjss"; + + const MSG: &str = "fix the money"; + + #[test] + fn test_sign_message() { + let secp = Secp256k1::new(); + let prv = PrivateKey::from_wif(PRV_WIF).unwrap(); + + let signer = EcdsaMessageSigner::from_prv(prv); + let sig = signer.sign(MSG); + let pub_key = prv.public_key(&secp); + let address = Address::p2pkh(&pub_key, Network::Bitcoin); + let message_sig = MessageSignature::new(sig, false); + + assert_eq!( + message_sig + .is_signed_by_address(&secp, &address, signed_msg_hash(MSG)) + .unwrap(), + true + ); + } + + #[test] + fn test_verify_signed_message() { + let secp = Secp256k1::new(); + let prv = PrivateKey::from_wif(PRV_WIF).unwrap(); + let sig = EcdsaMessageSigner::from_prv(prv).sign(MSG); + let verifier = EcdsaMessageSignatureVerifier::from_pub(prv.public_key(&secp)); + + assert_eq!(verifier.verify(sig, MSG), true); + } +} From 44e63e55cc7210e14c3503ef62a1d408dde04313 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 12 May 2022 14:44:33 +0200 Subject: [PATCH 2/6] Run cargo fmt --- src/keys/msgsig.rs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/keys/msgsig.rs b/src/keys/msgsig.rs index 0e7b05054..796c4f1bb 100644 --- a/src/keys/msgsig.rs +++ b/src/keys/msgsig.rs @@ -1,6 +1,4 @@ - /// Signing arbitrary messages and verify message signatures - use bitcoin::hashes::Hash; use bitcoin::secp256k1::recovery::RecoverableSignature; use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; @@ -26,7 +24,6 @@ pub struct EcdsaMessageSigner { } impl EcdsaMessageSigner { - /// Creates message signer from a bitcoin ECDSA private key pub fn from_prv(prv: PrivateKey) -> Self { Self::from_secret_key(prv.key) @@ -58,7 +55,6 @@ pub struct EcdsaMessageSignatureVerifier { } impl EcdsaMessageSignatureVerifier { - /// Creates a message signature verifier from a public key pub fn from_pub(public_key: PublicKey) -> Self { let address = Address::p2pkh(&public_key, Network::Bitcoin); From a3283c472f2c98d6e68a9d30f635ae5bc21ffc02 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 12 May 2022 14:35:57 +0200 Subject: [PATCH 3/6] Simplify return statement --- src/keys/msgsig.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/keys/msgsig.rs b/src/keys/msgsig.rs index 796c4f1bb..c19d89152 100644 --- a/src/keys/msgsig.rs +++ b/src/keys/msgsig.rs @@ -74,10 +74,9 @@ impl MessageSignatureVerifier for EcdsaMessageSignatureVer fn verify(&self, sig: RecoverableSignature, msg: &str) -> bool { let message_sig = MessageSignature::new(sig, false); let msg_hash = signed_msg_hash(msg); - return match message_sig.is_signed_by_address(&self.secp, &self.address, msg_hash) { - Ok(is_signed) => is_signed, - Err(_) => false, - }; + message_sig + .is_signed_by_address(&self.secp, &self.address, msg_hash) + .unwrap_or(false) } } From fb772ed44768a899c82083bc38ed19d1393b606a Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 30 Jun 2022 14:46:55 +0200 Subject: [PATCH 4/6] Fix message signing --- src/keys/msgsig.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/keys/msgsig.rs b/src/keys/msgsig.rs index c19d89152..c05262930 100644 --- a/src/keys/msgsig.rs +++ b/src/keys/msgsig.rs @@ -1,6 +1,6 @@ /// Signing arbitrary messages and verify message signatures use bitcoin::hashes::Hash; -use bitcoin::secp256k1::recovery::RecoverableSignature; +use bitcoin::secp256k1::ecdsa::RecoverableSignature; use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; use bitcoin::util::misc::{signed_msg_hash, MessageSignature}; use bitcoin::{Address, Network, PrivateKey, PublicKey}; @@ -26,7 +26,7 @@ pub struct EcdsaMessageSigner { impl EcdsaMessageSigner { /// Creates message signer from a bitcoin ECDSA private key pub fn from_prv(prv: PrivateKey) -> Self { - Self::from_secret_key(prv.key) + Self::from_secret_key(prv.inner) } /// Creates message signer from an ECDSA secret key @@ -41,8 +41,9 @@ impl EcdsaMessageSigner { impl MessageSigner for EcdsaMessageSigner { fn sign(&self, message: &str) -> RecoverableSignature { let msg_hash = signed_msg_hash(message); - self.secp.sign_recoverable( - &Message::from_slice(&msg_hash.into_inner()[..]).unwrap(), + self.secp.sign_ecdsa_recoverable( + &Message::from_slice(&msg_hash.into_inner()[..]) + .expect("Message to be signed is not a valid Hash"), &self.secret_key, ) } From 707f6c3de09a8e1d3cf2b8cd6401724f7490104f Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Thu, 12 May 2022 14:48:41 +0200 Subject: [PATCH 5/6] Store Secp256k1 as a reference to avoid copying --- src/keys/msgsig.rs | 46 ++++++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 26 deletions(-) diff --git a/src/keys/msgsig.rs b/src/keys/msgsig.rs index c05262930..0ae3cd8c9 100644 --- a/src/keys/msgsig.rs +++ b/src/keys/msgsig.rs @@ -18,27 +18,24 @@ pub trait MessageSignatureVerifier { } /// A message signer using ECDSA -pub struct EcdsaMessageSigner { - secp: Secp256k1, +pub struct EcdsaMessageSigner<'a> { + secp: &'a Secp256k1, secret_key: SecretKey, } -impl EcdsaMessageSigner { +impl<'a> EcdsaMessageSigner<'a> { /// Creates message signer from a bitcoin ECDSA private key - pub fn from_prv(prv: PrivateKey) -> Self { - Self::from_secret_key(prv.inner) + pub fn from_prv(prv: PrivateKey, secp: &'a Secp256k1) -> Self { + Self::from_secret_key(prv.inner, secp) } /// Creates message signer from an ECDSA secret key - pub fn from_secret_key(secret_key: SecretKey) -> Self { - EcdsaMessageSigner { - secret_key, - secp: Secp256k1::new(), - } + pub fn from_secret_key(secret_key: SecretKey, secp: &'a Secp256k1) -> Self { + EcdsaMessageSigner { secret_key, secp } } } -impl MessageSigner for EcdsaMessageSigner { +impl<'a> MessageSigner for EcdsaMessageSigner<'a> { fn sign(&self, message: &str) -> RecoverableSignature { let msg_hash = signed_msg_hash(message); self.secp.sign_ecdsa_recoverable( @@ -50,33 +47,30 @@ impl MessageSigner for EcdsaMessageSigner { } /// A message signature verifier using ECDSA -pub struct EcdsaMessageSignatureVerifier { - secp: Secp256k1, +pub struct EcdsaMessageSignatureVerifier<'a> { + secp: &'a Secp256k1, address: Address, } -impl EcdsaMessageSignatureVerifier { +impl<'a> EcdsaMessageSignatureVerifier<'a> { /// Creates a message signature verifier from a public key - pub fn from_pub(public_key: PublicKey) -> Self { + pub fn from_pub(public_key: PublicKey, secp: &'a Secp256k1) -> Self { let address = Address::p2pkh(&public_key, Network::Bitcoin); - Self::from_address(address) + Self::from_address(address, secp) } /// Creates a message signature verifier from an address - pub fn from_address(address: Address) -> Self { - EcdsaMessageSignatureVerifier { - address, - secp: Secp256k1::new(), - } + pub fn from_address(address: Address, secp: &'a Secp256k1) -> Self { + EcdsaMessageSignatureVerifier { address, secp } } } -impl MessageSignatureVerifier for EcdsaMessageSignatureVerifier { +impl<'a> MessageSignatureVerifier for EcdsaMessageSignatureVerifier<'a> { fn verify(&self, sig: RecoverableSignature, msg: &str) -> bool { let message_sig = MessageSignature::new(sig, false); let msg_hash = signed_msg_hash(msg); message_sig - .is_signed_by_address(&self.secp, &self.address, msg_hash) + .is_signed_by_address(self.secp, &self.address, msg_hash) .unwrap_or(false) } } @@ -98,7 +92,7 @@ mod ecdsa_msg_sign { let secp = Secp256k1::new(); let prv = PrivateKey::from_wif(PRV_WIF).unwrap(); - let signer = EcdsaMessageSigner::from_prv(prv); + let signer = EcdsaMessageSigner::from_prv(prv, &secp); let sig = signer.sign(MSG); let pub_key = prv.public_key(&secp); let address = Address::p2pkh(&pub_key, Network::Bitcoin); @@ -116,8 +110,8 @@ mod ecdsa_msg_sign { fn test_verify_signed_message() { let secp = Secp256k1::new(); let prv = PrivateKey::from_wif(PRV_WIF).unwrap(); - let sig = EcdsaMessageSigner::from_prv(prv).sign(MSG); - let verifier = EcdsaMessageSignatureVerifier::from_pub(prv.public_key(&secp)); + let sig = EcdsaMessageSigner::from_prv(prv, &secp).sign(MSG); + let verifier = EcdsaMessageSignatureVerifier::from_pub(prv.public_key(&secp), &secp); assert_eq!(verifier.verify(sig, MSG), true); } From f3fcefdefdd37a6742c507df8ba39e4827905415 Mon Sep 17 00:00:00 2001 From: Gabriel Comte Date: Fri, 1 Jul 2022 11:19:07 +0200 Subject: [PATCH 6/6] Remove generic aspects; There's only one sig. algo --- src/keys/msgsig.rs | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/src/keys/msgsig.rs b/src/keys/msgsig.rs index 0ae3cd8c9..7872ca4e4 100644 --- a/src/keys/msgsig.rs +++ b/src/keys/msgsig.rs @@ -5,18 +5,6 @@ use bitcoin::secp256k1::{All, Message, Secp256k1, SecretKey}; use bitcoin::util::misc::{signed_msg_hash, MessageSignature}; use bitcoin::{Address, Network, PrivateKey, PublicKey}; -/// Trait for message signers -pub trait MessageSigner { - /// Sign an arbitrary message - fn sign(&self, msg: &str) -> S; -} - -/// Trait for message signature verifiers -pub trait MessageSignatureVerifier { - /// Verify a signature against an arbitrary message - fn verify(&self, sig: S, msg: &str) -> bool; -} - /// A message signer using ECDSA pub struct EcdsaMessageSigner<'a> { secp: &'a Secp256k1, @@ -33,9 +21,7 @@ impl<'a> EcdsaMessageSigner<'a> { pub fn from_secret_key(secret_key: SecretKey, secp: &'a Secp256k1) -> Self { EcdsaMessageSigner { secret_key, secp } } -} -impl<'a> MessageSigner for EcdsaMessageSigner<'a> { fn sign(&self, message: &str) -> RecoverableSignature { let msg_hash = signed_msg_hash(message); self.secp.sign_ecdsa_recoverable( @@ -63,9 +49,7 @@ impl<'a> EcdsaMessageSignatureVerifier<'a> { pub fn from_address(address: Address, secp: &'a Secp256k1) -> Self { EcdsaMessageSignatureVerifier { address, secp } } -} -impl<'a> MessageSignatureVerifier for EcdsaMessageSignatureVerifier<'a> { fn verify(&self, sig: RecoverableSignature, msg: &str) -> bool { let message_sig = MessageSignature::new(sig, false); let msg_hash = signed_msg_hash(msg);