bitcoin/
sign_message.rs

1// SPDX-License-Identifier: CC0-1.0
2
3//! Signature
4//!
5//! This module provides signature related functions including secp256k1 signature recovery when
6//! library is used with the `secp-recovery` feature.
7//!
8
9use hashes::{sha256d, Hash, HashEngine};
10
11use crate::consensus::{encode, Encodable};
12
13#[rustfmt::skip]
14#[doc(inline)]
15#[cfg(feature = "secp-recovery")]
16pub use self::message_signing::{MessageSignature, MessageSignatureError};
17
18/// The prefix for signed messages using Bitcoin's message signing protocol.
19pub const BITCOIN_SIGNED_MSG_PREFIX: &[u8] = b"\x18Bitcoin Signed Message:\n";
20
21#[cfg(feature = "secp-recovery")]
22mod message_signing {
23    use core::fmt;
24
25    use hashes::{sha256d, Hash};
26    use internals::write_err;
27    use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
28
29    use crate::address::{Address, AddressType};
30    use crate::crypto::key::PublicKey;
31
32    /// An error used for dealing with Bitcoin Signed Messages.
33    #[derive(Debug, Clone, PartialEq, Eq)]
34    #[non_exhaustive]
35    pub enum MessageSignatureError {
36        /// Signature is expected to be 65 bytes.
37        InvalidLength,
38        /// The signature is invalidly constructed.
39        InvalidEncoding(secp256k1::Error),
40        /// Invalid base64 encoding.
41        InvalidBase64,
42        /// Unsupported Address Type
43        UnsupportedAddressType(AddressType),
44    }
45
46    internals::impl_from_infallible!(MessageSignatureError);
47
48    impl fmt::Display for MessageSignatureError {
49        fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
50            use MessageSignatureError::*;
51
52            match *self {
53                InvalidLength => write!(f, "length not 65 bytes"),
54                InvalidEncoding(ref e) => write_err!(f, "invalid encoding"; e),
55                InvalidBase64 => write!(f, "invalid base64"),
56                UnsupportedAddressType(ref address_type) =>
57                    write!(f, "unsupported address type: {}", address_type),
58            }
59        }
60    }
61
62    #[cfg(feature = "std")]
63    impl std::error::Error for MessageSignatureError {
64        fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
65            use MessageSignatureError::*;
66
67            match *self {
68                InvalidEncoding(ref e) => Some(e),
69                InvalidLength | InvalidBase64 | UnsupportedAddressType(_) => None,
70            }
71        }
72    }
73
74    impl From<secp256k1::Error> for MessageSignatureError {
75        fn from(e: secp256k1::Error) -> MessageSignatureError {
76            MessageSignatureError::InvalidEncoding(e)
77        }
78    }
79
80    /// A signature on a Bitcoin Signed Message.
81    ///
82    /// In order to use the `to_base64` and `from_base64` methods, as well as the
83    /// `fmt::Display` and `str::FromStr` implementations, the `base64` feature
84    /// must be enabled.
85    #[derive(Copy, Clone, PartialEq, Eq, Debug)]
86    pub struct MessageSignature {
87        /// The inner recoverable signature.
88        pub signature: RecoverableSignature,
89        /// Whether or not this signature was created with a compressed key.
90        pub compressed: bool,
91    }
92
93    impl MessageSignature {
94        /// Create a new [MessageSignature].
95        pub fn new(signature: RecoverableSignature, compressed: bool) -> MessageSignature {
96            MessageSignature { signature, compressed }
97        }
98
99        /// Serialize to bytes.
100        pub fn serialize(&self) -> [u8; 65] {
101            let (recid, raw) = self.signature.serialize_compact();
102            let mut serialized = [0u8; 65];
103            serialized[0] = 27;
104            serialized[0] += recid.to_i32() as u8;
105            if self.compressed {
106                serialized[0] += 4;
107            }
108            serialized[1..].copy_from_slice(&raw[..]);
109            serialized
110        }
111
112        /// Create from a byte slice.
113        pub fn from_slice(bytes: &[u8]) -> Result<MessageSignature, MessageSignatureError> {
114            if bytes.len() != 65 {
115                return Err(MessageSignatureError::InvalidLength);
116            }
117            // We just check this here so we can safely subtract further.
118            if bytes[0] < 27 {
119                return Err(MessageSignatureError::InvalidEncoding(
120                    secp256k1::Error::InvalidRecoveryId,
121                ));
122            };
123            let recid = RecoveryId::from_i32(((bytes[0] - 27) & 0x03) as i32)?;
124            Ok(MessageSignature {
125                signature: RecoverableSignature::from_compact(&bytes[1..], recid)?,
126                compressed: ((bytes[0] - 27) & 0x04) != 0,
127            })
128        }
129
130        /// Attempt to recover a public key from the signature and the signed message.
131        ///
132        /// To get the message hash from a message, use [super::signed_msg_hash].
133        pub fn recover_pubkey<C: secp256k1::Verification>(
134            &self,
135            secp_ctx: &secp256k1::Secp256k1<C>,
136            msg_hash: sha256d::Hash,
137        ) -> Result<PublicKey, MessageSignatureError> {
138            let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
139            let pubkey = secp_ctx.recover_ecdsa(&msg, &self.signature)?;
140            Ok(PublicKey { inner: pubkey, compressed: self.compressed })
141        }
142
143        /// Verify that the signature signs the message and was signed by the given address.
144        ///
145        /// To get the message hash from a message, use [super::signed_msg_hash].
146        pub fn is_signed_by_address<C: secp256k1::Verification>(
147            &self,
148            secp_ctx: &secp256k1::Secp256k1<C>,
149            address: &Address,
150            msg_hash: sha256d::Hash,
151        ) -> Result<bool, MessageSignatureError> {
152            match address.address_type() {
153                Some(AddressType::P2pkh) => {
154                    let pubkey = self.recover_pubkey(secp_ctx, msg_hash)?;
155                    Ok(address.pubkey_hash() == Some(pubkey.pubkey_hash()))
156                }
157                Some(address_type) =>
158                    Err(MessageSignatureError::UnsupportedAddressType(address_type)),
159                None => Ok(false),
160            }
161        }
162    }
163
164    #[cfg(feature = "base64")]
165    mod base64_impls {
166        use base64::prelude::{Engine as _, BASE64_STANDARD};
167
168        use super::*;
169        use crate::prelude::*;
170
171        impl MessageSignature {
172            /// Convert a signature from base64 encoding.
173            pub fn from_base64(s: &str) -> Result<MessageSignature, MessageSignatureError> {
174                let bytes =
175                    BASE64_STANDARD.decode(s).map_err(|_| MessageSignatureError::InvalidBase64)?;
176                MessageSignature::from_slice(&bytes)
177            }
178
179            /// Convert to base64 encoding.
180            pub fn to_base64(self) -> String { BASE64_STANDARD.encode(self.serialize()) }
181        }
182
183        impl fmt::Display for MessageSignature {
184            fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
185                let bytes = self.serialize();
186                // This avoids the allocation of a String.
187                write!(f, "{}", base64::display::Base64Display::new(&bytes, &BASE64_STANDARD))
188            }
189        }
190
191        impl core::str::FromStr for MessageSignature {
192            type Err = MessageSignatureError;
193            fn from_str(s: &str) -> Result<MessageSignature, MessageSignatureError> {
194                MessageSignature::from_base64(s)
195            }
196        }
197    }
198}
199
200/// Hash message for signature using Bitcoin's message signing format.
201pub fn signed_msg_hash(msg: &str) -> sha256d::Hash {
202    let mut engine = sha256d::Hash::engine();
203    engine.input(BITCOIN_SIGNED_MSG_PREFIX);
204    let msg_len = encode::VarInt::from(msg.len());
205    msg_len.consensus_encode(&mut engine).expect("engines don't error");
206    engine.input(msg.as_bytes());
207    sha256d::Hash::from_engine(engine)
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn test_signed_msg_hash() {
216        let hash = signed_msg_hash("test");
217        assert_eq!(
218            hash.to_string(),
219            "a6f87fe6d58a032c320ff8d1541656f0282c2c7bfcc69d61af4c8e8ed528e49c"
220        );
221    }
222
223    #[test]
224    #[cfg(all(feature = "secp-recovery", feature = "base64", feature = "rand-std"))]
225    fn test_message_signature() {
226        use core::str::FromStr;
227
228        use secp256k1;
229
230        use crate::{Address, AddressType, Network, NetworkKind};
231
232        let secp = secp256k1::Secp256k1::new();
233        let message = "rust-bitcoin MessageSignature test";
234        let msg_hash = super::signed_msg_hash(message);
235        let msg = secp256k1::Message::from_digest(msg_hash.to_byte_array());
236        let privkey = secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng());
237        let secp_sig = secp.sign_ecdsa_recoverable(&msg, &privkey);
238        let signature = super::MessageSignature { signature: secp_sig, compressed: true };
239
240        assert_eq!(signature.to_base64(), signature.to_string());
241        let signature2 = super::MessageSignature::from_str(&signature.to_string()).unwrap();
242        let pubkey = signature2
243            .recover_pubkey(&secp, msg_hash)
244            .unwrap()
245            .try_into()
246            .expect("compressed was set to true");
247
248        let p2pkh = Address::p2pkh(pubkey, NetworkKind::Main);
249        assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
250        let p2wpkh = Address::p2wpkh(&pubkey, Network::Bitcoin);
251        assert_eq!(
252            signature2.is_signed_by_address(&secp, &p2wpkh, msg_hash),
253            Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2wpkh))
254        );
255        let p2shwpkh = Address::p2shwpkh(&pubkey, NetworkKind::Main);
256        assert_eq!(
257            signature2.is_signed_by_address(&secp, &p2shwpkh, msg_hash),
258            Err(MessageSignatureError::UnsupportedAddressType(AddressType::P2sh))
259        );
260        let p2pkh = Address::p2pkh(pubkey, Network::Bitcoin);
261        assert_eq!(signature2.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(true));
262
263        assert_eq!(pubkey.0, secp256k1::PublicKey::from_secret_key(&secp, &privkey));
264    }
265
266    #[test]
267    #[cfg(all(feature = "secp-recovery", feature = "base64"))]
268    fn test_incorrect_message_signature() {
269        use base64::prelude::{Engine as _, BASE64_STANDARD};
270        use secp256k1;
271
272        use crate::crypto::key::PublicKey;
273        use crate::{Address, NetworkKind};
274
275        let secp = secp256k1::Secp256k1::new();
276        let message = "a different message from what was signed";
277        let msg_hash = super::signed_msg_hash(message);
278
279        // Signature of msg = "rust-bitcoin MessageSignature test"
280        // Signed with pk "UuOGDsfLPr4HIMKQX0ipjJeRaj1geCq3yPUF2COP5ME="
281        let signature_base64 = "IAM2qX24tYx/bdBTIgVLhD8QEAjrPlJpmjB4nZHdRYGIBa4DmVulAcwjPnWe6Q5iEwXH6F0pUCJP/ZeHPWS1h1o=";
282        let pubkey_base64 = "A1FTfMEntPpAty3qkEo0q2Dc1FEycI10a3jmwEFy+Qr6";
283        let signature =
284            super::MessageSignature::from_base64(signature_base64).expect("message signature");
285
286        let pubkey =
287            PublicKey::from_slice(&BASE64_STANDARD.decode(pubkey_base64).expect("base64 string"))
288                .expect("pubkey slice");
289
290        let p2pkh = Address::p2pkh(pubkey, NetworkKind::Main);
291        assert_eq!(signature.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(false));
292    }
293}