2022-06-29 04:05:31 +00:00
|
|
|
// SPDX-License-Identifier: CC0-1.0
|
2014-07-18 13:56:17 +00:00
|
|
|
|
2022-09-12 04:50:37 +00:00
|
|
|
//! Signature
|
2021-11-05 21:58:18 +00:00
|
|
|
//!
|
2022-09-12 04:50:37 +00:00
|
|
|
//! This module provides signature related functions including secp256k1 signature recovery when
|
|
|
|
//! library is used with the `secp-recovery` feature.
|
2014-07-18 13:56:17 +00:00
|
|
|
//!
|
|
|
|
|
2023-03-22 03:09:58 +00:00
|
|
|
use hashes::{sha256d, Hash, HashEngine};
|
|
|
|
|
2023-10-10 04:08:50 +00:00
|
|
|
use crate::consensus::{encode, Encodable};
|
|
|
|
|
|
|
|
#[rustfmt::skip]
|
|
|
|
#[doc(inline)]
|
2020-10-07 15:46:48 +00:00
|
|
|
#[cfg(feature = "secp-recovery")]
|
|
|
|
pub use self::message_signing::{MessageSignature, MessageSignatureError};
|
|
|
|
|
2020-12-08 17:31:28 +00:00
|
|
|
/// The prefix for signed messages using Bitcoin's message signing protocol.
|
|
|
|
pub const BITCOIN_SIGNED_MSG_PREFIX: &[u8] = b"\x18Bitcoin Signed Message:\n";
|
2019-04-28 05:59:20 +00:00
|
|
|
|
2020-10-07 15:46:48 +00:00
|
|
|
#[cfg(feature = "secp-recovery")]
|
|
|
|
mod message_signing {
|
2021-06-09 10:40:41 +00:00
|
|
|
use core::fmt;
|
2020-10-07 15:46:48 +00:00
|
|
|
|
2023-08-16 05:30:04 +00:00
|
|
|
use hashes::{sha256d, Hash};
|
2023-03-28 01:16:47 +00:00
|
|
|
use internals::write_err;
|
2022-09-12 05:00:41 +00:00
|
|
|
use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
|
2020-10-07 15:46:48 +00:00
|
|
|
|
2022-08-05 03:37:41 +00:00
|
|
|
use crate::address::{Address, AddressType};
|
2022-11-08 00:36:52 +00:00
|
|
|
use crate::crypto::key::PublicKey;
|
2020-10-07 15:46:48 +00:00
|
|
|
|
|
|
|
/// An error used for dealing with Bitcoin Signed Messages.
|
2023-07-27 01:10:22 +00:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
2022-05-31 04:29:50 +00:00
|
|
|
#[non_exhaustive]
|
2020-10-07 15:46:48 +00:00
|
|
|
pub enum MessageSignatureError {
|
|
|
|
/// Signature is expected to be 65 bytes.
|
|
|
|
InvalidLength,
|
|
|
|
/// The signature is invalidly constructed.
|
|
|
|
InvalidEncoding(secp256k1::Error),
|
|
|
|
/// Invalid base64 encoding.
|
|
|
|
InvalidBase64,
|
2022-02-05 21:29:42 +00:00
|
|
|
/// Unsupported Address Type
|
|
|
|
UnsupportedAddressType(AddressType),
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Display for MessageSignatureError {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
|
|
|
use MessageSignatureError::*;
|
|
|
|
|
2020-10-07 15:46:48 +00:00
|
|
|
match *self {
|
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
|
|
|
InvalidLength => write!(f, "length not 65 bytes"),
|
|
|
|
InvalidEncoding(ref e) => write_err!(f, "invalid encoding"; e),
|
|
|
|
InvalidBase64 => write!(f, "invalid base64"),
|
|
|
|
UnsupportedAddressType(ref address_type) =>
|
2022-09-12 05:00:41 +00:00
|
|
|
write!(f, "unsupported address type: {}", address_type),
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-09 10:34:44 +00:00
|
|
|
#[cfg(feature = "std")]
|
2022-05-04 05:56:24 +00:00
|
|
|
impl std::error::Error for MessageSignatureError {
|
|
|
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
|
|
|
use MessageSignatureError::*;
|
2022-05-04 05:56:24 +00:00
|
|
|
|
Make error types uniform
On our way to v1.0.0 we are defining a standard for our error types,
this includes:
- Uses the following derives (unless not possible, usually because of `io::Error`)
`#[derive(Debug, Clone, PartialEq, Eq)]`
- Has `non_exhaustive` unless we really know we can commit to not adding
anything.
Furthermore, we are trying to make the codebase easy to read. Error code
is write-once-read-many (well it should be) so if we make all the error
code super uniform the users can flick to an error and quickly see what
it includes. In an effort to achieve this I have made up a style and
over recent times have change much of the error code to that new style,
this PR audits _all_ error types in the code base and enforces the
style, specifically:
- Is layed out: definition, [impl block], Display impl, error::Error impl, From impls
- `error::Error` impl matches on enum even if it returns `None` for all variants
- Display/Error impls import enum variants locally
- match uses *self and `ref e`
- error::Error variants that return `Some` come first, `None` after
Re: non_exhaustive
To make dev and review easier I have added `non_exhaustive` to _every_
error type. We can then remove it error by error as we see fit. This is
because it takes a bit of thinking to do and review where as this patch
should not take much brain power to review.
2023-10-04 02:55:45 +00:00
|
|
|
match *self {
|
|
|
|
InvalidEncoding(ref e) => Some(e),
|
2022-05-04 05:56:24 +00:00
|
|
|
InvalidLength | InvalidBase64 | UnsupportedAddressType(_) => None,
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<secp256k1::Error> for MessageSignatureError {
|
|
|
|
fn from(e: secp256k1::Error) -> MessageSignatureError {
|
|
|
|
MessageSignatureError::InvalidEncoding(e)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// A signature on a Bitcoin Signed Message.
|
|
|
|
///
|
|
|
|
/// In order to use the `to_base64` and `from_base64` methods, as well as the
|
|
|
|
/// `fmt::Display` and `str::FromStr` implementations, the `base64` feature
|
|
|
|
/// must be enabled.
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
|
|
|
|
pub struct MessageSignature {
|
|
|
|
/// The inner recoverable signature.
|
|
|
|
pub signature: RecoverableSignature,
|
|
|
|
/// Whether or not this signature was created with a compressed key.
|
|
|
|
pub compressed: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MessageSignature {
|
|
|
|
/// Create a new [MessageSignature].
|
|
|
|
pub fn new(signature: RecoverableSignature, compressed: bool) -> MessageSignature {
|
2022-09-12 05:00:41 +00:00
|
|
|
MessageSignature { signature, compressed }
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Serialize to bytes.
|
|
|
|
pub fn serialize(&self) -> [u8; 65] {
|
|
|
|
let (recid, raw) = self.signature.serialize_compact();
|
|
|
|
let mut serialized = [0u8; 65];
|
|
|
|
serialized[0] = 27;
|
|
|
|
serialized[0] += recid.to_i32() as u8;
|
|
|
|
if self.compressed {
|
|
|
|
serialized[0] += 4;
|
|
|
|
}
|
|
|
|
serialized[1..].copy_from_slice(&raw[..]);
|
|
|
|
serialized
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Create from a byte slice.
|
|
|
|
pub fn from_slice(bytes: &[u8]) -> Result<MessageSignature, MessageSignatureError> {
|
|
|
|
if bytes.len() != 65 {
|
|
|
|
return Err(MessageSignatureError::InvalidLength);
|
|
|
|
}
|
|
|
|
// We just check this here so we can safely subtract further.
|
|
|
|
if bytes[0] < 27 {
|
2022-09-12 05:00:41 +00:00
|
|
|
return Err(MessageSignatureError::InvalidEncoding(
|
|
|
|
secp256k1::Error::InvalidRecoveryId,
|
|
|
|
));
|
2020-10-07 15:46:48 +00:00
|
|
|
};
|
|
|
|
let recid = RecoveryId::from_i32(((bytes[0] - 27) & 0x03) as i32)?;
|
|
|
|
Ok(MessageSignature {
|
|
|
|
signature: RecoverableSignature::from_compact(&bytes[1..], recid)?,
|
|
|
|
compressed: ((bytes[0] - 27) & 0x04) != 0,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Attempt to recover a public key from the signature and the signed message.
|
|
|
|
///
|
2021-05-03 09:41:58 +00:00
|
|
|
/// To get the message hash from a message, use [super::signed_msg_hash].
|
2020-10-07 15:46:48 +00:00
|
|
|
pub fn recover_pubkey<C: secp256k1::Verification>(
|
|
|
|
&self,
|
|
|
|
secp_ctx: &secp256k1::Secp256k1<C>,
|
2022-09-12 05:00:41 +00:00
|
|
|
msg_hash: sha256d::Hash,
|
2022-02-05 21:29:42 +00:00
|
|
|
) -> Result<PublicKey, MessageSignatureError> {
|
2023-10-10 21:16:25 +00:00
|
|
|
let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
|
2022-01-03 02:52:44 +00:00
|
|
|
let pubkey = secp_ctx.recover_ecdsa(&msg, &self.signature)?;
|
2022-09-12 05:00:41 +00:00
|
|
|
Ok(PublicKey { inner: pubkey, compressed: self.compressed })
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Verify that the signature signs the message and was signed by the given address.
|
|
|
|
///
|
2021-05-03 09:41:58 +00:00
|
|
|
/// To get the message hash from a message, use [super::signed_msg_hash].
|
2020-10-07 15:46:48 +00:00
|
|
|
pub fn is_signed_by_address<C: secp256k1::Verification>(
|
|
|
|
&self,
|
|
|
|
secp_ctx: &secp256k1::Secp256k1<C>,
|
|
|
|
address: &Address,
|
2022-09-12 05:00:41 +00:00
|
|
|
msg_hash: sha256d::Hash,
|
2022-02-05 21:29:42 +00:00
|
|
|
) -> Result<bool, MessageSignatureError> {
|
|
|
|
match address.address_type() {
|
2020-10-07 15:46:48 +00:00
|
|
|
Some(AddressType::P2pkh) => {
|
2022-02-05 21:29:42 +00:00
|
|
|
let pubkey = self.recover_pubkey(secp_ctx, msg_hash)?;
|
2023-05-04 02:03:25 +00:00
|
|
|
Ok(address.pubkey_hash() == Some(pubkey.pubkey_hash()))
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
2022-09-12 05:00:41 +00:00
|
|
|
Some(address_type) =>
|
|
|
|
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
2022-02-05 21:29:42 +00:00
|
|
|
None => Ok(false),
|
|
|
|
}
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(feature = "base64")]
|
2023-08-28 00:02:58 +00:00
|
|
|
mod base64_impls {
|
|
|
|
use base64::prelude::{Engine as _, BASE64_STANDARD};
|
|
|
|
|
|
|
|
use super::*;
|
2024-02-20 05:11:14 +00:00
|
|
|
use crate::prelude::*;
|
2023-08-28 00:02:58 +00:00
|
|
|
|
|
|
|
impl MessageSignature {
|
|
|
|
/// Convert a signature from base64 encoding.
|
|
|
|
pub fn from_base64(s: &str) -> Result<MessageSignature, MessageSignatureError> {
|
|
|
|
let bytes =
|
|
|
|
BASE64_STANDARD.decode(s).map_err(|_| MessageSignatureError::InvalidBase64)?;
|
|
|
|
MessageSignature::from_slice(&bytes)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Convert to base64 encoding.
|
|
|
|
pub fn to_base64(self) -> String { BASE64_STANDARD.encode(self.serialize()) }
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
|
2023-08-28 00:02:58 +00:00
|
|
|
impl fmt::Display for MessageSignature {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
let bytes = self.serialize();
|
|
|
|
// This avoids the allocation of a String.
|
|
|
|
write!(f, "{}", base64::display::Base64Display::new(&bytes, &BASE64_STANDARD))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl core::str::FromStr for MessageSignature {
|
|
|
|
type Err = MessageSignatureError;
|
|
|
|
fn from_str(s: &str) -> Result<MessageSignature, MessageSignatureError> {
|
|
|
|
MessageSignature::from_base64(s)
|
|
|
|
}
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-12-08 17:31:28 +00:00
|
|
|
/// Hash message for signature using Bitcoin's message signing format.
|
2019-04-28 05:59:20 +00:00
|
|
|
pub fn signed_msg_hash(msg: &str) -> sha256d::Hash {
|
2020-04-08 12:01:47 +00:00
|
|
|
let mut engine = sha256d::Hash::engine();
|
2020-12-08 17:31:28 +00:00
|
|
|
engine.input(BITCOIN_SIGNED_MSG_PREFIX);
|
2023-08-24 00:37:53 +00:00
|
|
|
let msg_len = encode::VarInt::from(msg.len());
|
2021-11-24 23:01:41 +00:00
|
|
|
msg_len.consensus_encode(&mut engine).expect("engines don't error");
|
2020-04-08 12:01:47 +00:00
|
|
|
engine.input(msg.as_bytes());
|
|
|
|
sha256d::Hash::from_engine(engine)
|
2019-04-28 05:59:20 +00:00
|
|
|
}
|
|
|
|
|
2014-07-18 13:56:17 +00:00
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2022-02-05 21:29:42 +00:00
|
|
|
use super::*;
|
2014-08-13 03:56:16 +00:00
|
|
|
|
2019-04-28 05:59:20 +00:00
|
|
|
#[test]
|
|
|
|
fn test_signed_msg_hash() {
|
|
|
|
let hash = signed_msg_hash("test");
|
2022-09-12 05:00:41 +00:00
|
|
|
assert_eq!(
|
2023-01-07 15:39:11 +00:00
|
|
|
hash.to_string(),
|
2022-09-12 05:00:41 +00:00
|
|
|
"a6f87fe6d58a032c320ff8d1541656f0282c2c7bfcc69d61af4c8e8ed528e49c"
|
|
|
|
);
|
2019-04-28 05:59:20 +00:00
|
|
|
}
|
2020-10-07 15:46:48 +00:00
|
|
|
|
|
|
|
#[test]
|
2022-11-16 21:16:01 +00:00
|
|
|
#[cfg(all(feature = "secp-recovery", feature = "base64", feature = "rand-std"))]
|
2020-10-07 15:46:48 +00:00
|
|
|
fn test_message_signature() {
|
2021-06-09 10:40:41 +00:00
|
|
|
use core::str::FromStr;
|
2022-09-12 05:00:41 +00:00
|
|
|
|
2020-10-07 15:46:48 +00:00
|
|
|
use secp256k1;
|
2022-09-12 05:00:41 +00:00
|
|
|
|
2023-11-27 23:40:08 +00:00
|
|
|
use crate::{Address, AddressType, Network, NetworkKind};
|
2020-10-07 15:46:48 +00:00
|
|
|
|
|
|
|
let secp = secp256k1::Secp256k1::new();
|
|
|
|
let message = "rust-bitcoin MessageSignature test";
|
2022-07-20 02:57:30 +00:00
|
|
|
let msg_hash = super::signed_msg_hash(message);
|
2023-10-10 21:16:25 +00:00
|
|
|
let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
|
2020-10-07 15:46:48 +00:00
|
|
|
let privkey = secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng());
|
2022-01-07 20:44:57 +00:00
|
|
|
let secp_sig = secp.sign_ecdsa_recoverable(&msg, &privkey);
|
2022-09-12 05:00:41 +00:00
|
|
|
let signature = super::MessageSignature { signature: secp_sig, compressed: true };
|
2020-10-07 15:46:48 +00:00
|
|
|
|
|
|
|
assert_eq!(signature.to_base64(), signature.to_string());
|
|
|
|
let signature2 = super::MessageSignature::from_str(&signature.to_string()).unwrap();
|
2023-12-17 00:59:05 +00:00
|
|
|
let pubkey = signature2
|
|
|
|
.recover_pubkey(&secp, msg_hash)
|
2023-12-11 20:10:32 +00:00
|
|
|
.unwrap()
|
|
|
|
.try_into()
|
|
|
|
.expect("compressed was set to true");
|
2020-10-07 15:46:48 +00:00
|
|
|
|
2023-11-27 23:18:53 +00:00
|
|
|
let p2pkh = Address::p2pkh(pubkey, NetworkKind::Main);
|
|
|
|
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
|
2023-12-11 20:10:32 +00:00
|
|
|
let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin);
|
2022-02-05 21:29:42 +00:00
|
|
|
assert_eq!(
|
|
|
|
signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash),
|
|
|
|
Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2wpkh))
|
|
|
|
);
|
2023-11-27 23:18:53 +00:00
|
|
|
let p2shwpkh = Address::p2shwpkh(&pubkey, NetworkKind::Main);
|
2022-02-05 21:29:42 +00:00
|
|
|
assert_eq!(
|
|
|
|
signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash),
|
|
|
|
Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2sh))
|
|
|
|
);
|
2023-12-11 20:10:32 +00:00
|
|
|
let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
|
|
|
|
assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
|
|
|
|
|
|
|
|
assert_eq!(pubkey.0, secp256k1::PublicKey::from_secret_key(&secp, &privkey));
|
2020-10-07 15:46:48 +00:00
|
|
|
}
|
2022-02-09 20:56:13 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[cfg(all(feature = "secp-recovery", feature = "base64"))]
|
|
|
|
fn test_incorrect_message_signature() {
|
2023-08-28 00:02:58 +00:00
|
|
|
use base64::prelude::{Engine as _, BASE64_STANDARD};
|
2022-02-09 20:56:13 +00:00
|
|
|
use secp256k1;
|
2022-09-12 05:00:41 +00:00
|
|
|
|
2022-11-08 00:36:52 +00:00
|
|
|
use crate::crypto::key::PublicKey;
|
2023-11-27 23:18:53 +00:00
|
|
|
use crate::{Address, NetworkKind};
|
2022-02-09 20:56:13 +00:00
|
|
|
|
|
|
|
let secp = secp256k1::Secp256k1::new();
|
|
|
|
let message = "a different message from what was signed";
|
2022-07-20 02:57:30 +00:00
|
|
|
let msg_hash = super::signed_msg_hash(message);
|
2022-02-09 20:56:13 +00:00
|
|
|
|
|
|
|
// Signature of msg = "rust-bitcoin MessageSignature test"
|
|
|
|
// Signed with pk "UuOGDsfLPr4HIMKQX0ipjJeRaj1geCq3yPUF2COP5ME="
|
|
|
|
let signature_base64 = "IAM2qX24tYx/bdBTIgVLhD8QEAjrPlJpmjB4nZHdRYGIBa4DmVulAcwjPnWe6Q5iEwXH6F0pUCJP/ZeHPWS1h1o=";
|
|
|
|
let pubkey_base64 = "A1FTfMEntPpAty3qkEo0q2Dc1FEycI10a3jmwEFy+Qr6";
|
2022-09-12 05:00:41 +00:00
|
|
|
let signature =
|
|
|
|
super::MessageSignature::from_base64(signature_base64).expect("message signature");
|
2022-02-09 20:56:13 +00:00
|
|
|
|
2023-08-28 00:02:58 +00:00
|
|
|
let pubkey =
|
|
|
|
PublicKey::from_slice(&BASE64_STANDARD.decode(pubkey_base64).expect("base64 string"))
|
|
|
|
.expect("pubkey slice");
|
2022-02-09 20:56:13 +00:00
|
|
|
|
2023-11-27 23:18:53 +00:00
|
|
|
let p2pkh = Address::p2pkh(pubkey, NetworkKind::Main);
|
2022-02-09 20:56:13 +00:00
|
|
|
assert_eq!(signature.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(false));
|
|
|
|
}
|
2014-07-18 13:56:17 +00:00
|
|
|
}
|