Merge rust-bitcoin/rust-bitcoin#1259: Move/re-name the `util::misc` module
e24c91e9ca
sign_message: Run cargo fmt (Tobin C. Harding)041d6a8097
Move and deprecate script_find_and_remove (Tobin C. Harding) Pull request description: Done as part of [flattening util](https://github.com/rust-bitcoin/rust-bitcoin/issues/639). Move some code out of `misc` then re-name the module to `signature` and move it to the crate root. - Patch 1: Move a single public function, needs review that destination module is ok. I did consider re-naming the function to remove `script_` prefix but decided to leave it as is. - Patch 2: Re-names `misc` -> `signature` and puts it in the crate root - Patch 3: Runs the formatter on `signature` module All changes include deprecated re-exports. ACKs for top commit: apoelstra: ACKe24c91e9ca
Kixunil: ACKe24c91e9ca
Tree-SHA512: 37efc69595cbacd75c27f8fa6edd6bc168c04f1cdd230b49ab97f8a07e5b25ea87c00de017b315987bfe84d1b652a2c301c8f0132e4da988af1d15b687b47333
This commit is contained in:
commit
3b05c7ffef
|
@ -87,6 +87,7 @@ pub mod consensus;
|
|||
pub mod error;
|
||||
pub mod hash_types;
|
||||
pub mod policy;
|
||||
pub mod sign_message;
|
||||
pub mod util;
|
||||
|
||||
#[cfg(feature = "std")]
|
||||
|
|
|
@ -1,39 +1,34 @@
|
|||
// Written in 2014 by Andrew Poelstra <apoelstra@wpsoftware.net>
|
||||
// SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
//! Miscellaneous functions.
|
||||
//! Signature
|
||||
//!
|
||||
//! This module provides various utility functions including secp256k1 signature
|
||||
//! recovery when library is used with the `secp-recovery` feature.
|
||||
//! This module provides signature related functions including secp256k1 signature recovery when
|
||||
//! library is used with the `secp-recovery` feature.
|
||||
//!
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::hashes::{sha256d, Hash, HashEngine};
|
||||
|
||||
use crate::blockdata::opcodes;
|
||||
use crate::consensus::{encode, Encodable};
|
||||
|
||||
#[cfg(feature = "secp-recovery")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "secp-recovery")))]
|
||||
pub use self::message_signing::{MessageSignature, MessageSignatureError};
|
||||
use crate::consensus::{encode, Encodable};
|
||||
use crate::hashes::{sha256d, Hash, HashEngine};
|
||||
|
||||
/// The prefix for signed messages using Bitcoin's message signing protocol.
|
||||
pub const BITCOIN_SIGNED_MSG_PREFIX: &[u8] = b"\x18Bitcoin Signed Message:\n";
|
||||
|
||||
#[cfg(feature = "secp-recovery")]
|
||||
mod message_signing {
|
||||
#[cfg(feature = "base64")] use crate::prelude::*;
|
||||
|
||||
use core::fmt;
|
||||
|
||||
use bitcoin_internals::write_err;
|
||||
use crate::hashes::sha256d;
|
||||
use secp256k1;
|
||||
use secp256k1::ecdsa::{RecoveryId, RecoverableSignature};
|
||||
use secp256k1::ecdsa::{RecoverableSignature, RecoveryId};
|
||||
|
||||
use crate::util::key::PublicKey;
|
||||
use crate::address::{Address, AddressType};
|
||||
use crate::hashes::sha256d;
|
||||
#[cfg(feature = "base64")]
|
||||
use crate::prelude::*;
|
||||
use crate::util::key::PublicKey;
|
||||
|
||||
/// An error used for dealing with Bitcoin Signed Messages.
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "secp-recovery")))]
|
||||
|
@ -54,9 +49,11 @@ mod message_signing {
|
|||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match *self {
|
||||
MessageSignatureError::InvalidLength => write!(f, "length not 65 bytes"),
|
||||
MessageSignatureError::InvalidEncoding(ref e) => write_err!(f, "invalid encoding"; e),
|
||||
MessageSignatureError::InvalidEncoding(ref e) =>
|
||||
write_err!(f, "invalid encoding"; e),
|
||||
MessageSignatureError::InvalidBase64 => write!(f, "invalid base64"),
|
||||
MessageSignatureError::UnsupportedAddressType(ref address_type) => write!(f, "unsupported address type: {}", address_type),
|
||||
MessageSignatureError::UnsupportedAddressType(ref address_type) =>
|
||||
write!(f, "unsupported address type: {}", address_type),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -98,10 +95,7 @@ mod message_signing {
|
|||
impl MessageSignature {
|
||||
/// Create a new [MessageSignature].
|
||||
pub fn new(signature: RecoverableSignature, compressed: bool) -> MessageSignature {
|
||||
MessageSignature {
|
||||
signature,
|
||||
compressed,
|
||||
}
|
||||
MessageSignature { signature, compressed }
|
||||
}
|
||||
|
||||
/// Serialize to bytes.
|
||||
|
@ -124,7 +118,9 @@ mod message_signing {
|
|||
}
|
||||
// We just check this here so we can safely subtract further.
|
||||
if bytes[0] < 27 {
|
||||
return Err(MessageSignatureError::InvalidEncoding(secp256k1::Error::InvalidRecoveryId));
|
||||
return Err(MessageSignatureError::InvalidEncoding(
|
||||
secp256k1::Error::InvalidRecoveryId,
|
||||
));
|
||||
};
|
||||
let recid = RecoveryId::from_i32(((bytes[0] - 27) & 0x03) as i32)?;
|
||||
Ok(MessageSignature {
|
||||
|
@ -139,14 +135,11 @@ mod message_signing {
|
|||
pub fn recover_pubkey<C: secp256k1::Verification>(
|
||||
&self,
|
||||
secp_ctx: &secp256k1::Secp256k1<C>,
|
||||
msg_hash: sha256d::Hash
|
||||
msg_hash: sha256d::Hash,
|
||||
) -> Result<PublicKey, MessageSignatureError> {
|
||||
let msg = secp256k1::Message::from(msg_hash);
|
||||
let pubkey = secp_ctx.recover_ecdsa(&msg, &self.signature)?;
|
||||
Ok(PublicKey {
|
||||
inner: pubkey,
|
||||
compressed: self.compressed,
|
||||
})
|
||||
Ok(PublicKey { inner: pubkey, compressed: self.compressed })
|
||||
}
|
||||
|
||||
/// Verify that the signature signs the message and was signed by the given address.
|
||||
|
@ -156,14 +149,15 @@ mod message_signing {
|
|||
&self,
|
||||
secp_ctx: &secp256k1::Secp256k1<C>,
|
||||
address: &Address,
|
||||
msg_hash: sha256d::Hash
|
||||
msg_hash: sha256d::Hash,
|
||||
) -> Result<bool, MessageSignatureError> {
|
||||
match address.address_type() {
|
||||
Some(AddressType::P2pkh) => {
|
||||
let pubkey = self.recover_pubkey(secp_ctx, msg_hash)?;
|
||||
Ok(*address == Address::p2pkh(&pubkey, address.network))
|
||||
}
|
||||
Some(address_type) => Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
||||
Some(address_type) =>
|
||||
Err(MessageSignatureError::UnsupportedAddressType(address_type)),
|
||||
None => Ok(false),
|
||||
}
|
||||
}
|
||||
|
@ -179,9 +173,7 @@ mod message_signing {
|
|||
/// Convert to base64 encoding.
|
||||
#[cfg(feature = "base64")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "base64")))]
|
||||
pub fn to_base64(self) -> String {
|
||||
base64::encode(&self.serialize()[..])
|
||||
}
|
||||
pub fn to_base64(self) -> String { base64::encode(&self.serialize()[..]) }
|
||||
}
|
||||
|
||||
#[cfg(feature = "base64")]
|
||||
|
@ -190,8 +182,11 @@ mod message_signing {
|
|||
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::with_config(
|
||||
&bytes[..], base64::STANDARD))
|
||||
write!(
|
||||
f,
|
||||
"{}",
|
||||
base64::display::Base64Display::with_config(&bytes[..], base64::STANDARD)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -205,47 +200,6 @@ mod message_signing {
|
|||
}
|
||||
}
|
||||
|
||||
/// Search for `needle` in the vector `haystack` and remove every
|
||||
/// instance of it, returning the number of instances removed.
|
||||
/// Loops through the vector opcode by opcode, skipping pushed data.
|
||||
pub fn script_find_and_remove(haystack: &mut Vec<u8>, needle: &[u8]) -> usize {
|
||||
if needle.len() > haystack.len() {
|
||||
return 0;
|
||||
}
|
||||
if needle.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut top = haystack.len() - needle.len();
|
||||
let mut n_deleted = 0;
|
||||
|
||||
let mut i = 0;
|
||||
while i <= top {
|
||||
if &haystack[i..(i + needle.len())] == needle {
|
||||
for j in i..top {
|
||||
haystack.swap(j + needle.len(), j);
|
||||
}
|
||||
n_deleted += 1;
|
||||
// This is ugly but prevents infinite loop in case of overflow
|
||||
let overflow = top < needle.len();
|
||||
top = top.wrapping_sub(needle.len());
|
||||
if overflow {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i += match opcodes::All::from((*haystack)[i]).classify(opcodes::ClassifyContext::Legacy) {
|
||||
opcodes::Class::PushBytes(n) => n as usize + 1,
|
||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => 2,
|
||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => 3,
|
||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) => 5,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
}
|
||||
haystack.truncate(top.wrapping_add(needle.len()));
|
||||
n_deleted
|
||||
}
|
||||
|
||||
/// Hash message for signature using Bitcoin's message signing format.
|
||||
pub fn signed_msg_hash(msg: &str) -> sha256d::Hash {
|
||||
let mut engine = sha256d::Hash::engine();
|
||||
|
@ -260,73 +214,33 @@ pub fn signed_msg_hash(msg: &str) -> sha256d::Hash {
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::hashes::hex::ToHex;
|
||||
use super::script_find_and_remove;
|
||||
use super::signed_msg_hash;
|
||||
|
||||
#[test]
|
||||
fn test_script_find_and_remove() {
|
||||
let mut v = vec![101u8, 102, 103, 104, 102, 103, 104, 102, 103, 104, 105, 106, 107, 108, 109];
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[]), 0);
|
||||
assert_eq!(script_find_and_remove(&mut v, &[105, 105, 105]), 0);
|
||||
assert_eq!(v, vec![101, 102, 103, 104, 102, 103, 104, 102, 103, 104, 105, 106, 107, 108, 109]);
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[105, 106, 107]), 1);
|
||||
assert_eq!(v, vec![101, 102, 103, 104, 102, 103, 104, 102, 103, 104, 108, 109]);
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[104, 108, 109]), 1);
|
||||
assert_eq!(v, vec![101, 102, 103, 104, 102, 103, 104, 102, 103]);
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[101]), 1);
|
||||
assert_eq!(v, vec![102, 103, 104, 102, 103, 104, 102, 103]);
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[102]), 3);
|
||||
assert_eq!(v, vec![103, 104, 103, 104, 103]);
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[103, 104]), 2);
|
||||
assert_eq!(v, vec![103]);
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[105, 105, 5]), 0);
|
||||
assert_eq!(script_find_and_remove(&mut v, &[105]), 0);
|
||||
assert_eq!(script_find_and_remove(&mut v, &[103]), 1);
|
||||
assert_eq!(v, Vec::<u8>::new());
|
||||
|
||||
assert_eq!(script_find_and_remove(&mut v, &[105, 105, 5]), 0);
|
||||
assert_eq!(script_find_and_remove(&mut v, &[105]), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_script_codesep_remove() {
|
||||
let mut s = vec![33u8, 3, 132, 121, 160, 250, 153, 140, 211, 82, 89, 162, 239, 10, 122, 92, 104, 102, 44, 20, 116, 248, 140, 203, 109, 8, 167, 103, 123, 190, 199, 242, 32, 65, 173, 171, 33, 3, 132, 121, 160, 250, 153, 140, 211, 82, 89, 162, 239, 10, 122, 92, 104, 102, 44, 20, 116, 248, 140, 203, 109, 8, 167, 103, 123, 190, 199, 242, 32, 65, 173, 171, 81];
|
||||
assert_eq!(script_find_and_remove(&mut s, &[171]), 2);
|
||||
assert_eq!(s, vec![33, 3, 132, 121, 160, 250, 153, 140, 211, 82, 89, 162, 239, 10, 122, 92, 104, 102, 44, 20, 116, 248, 140, 203, 109, 8, 167, 103, 123, 190, 199, 242, 32, 65, 173, 33, 3, 132, 121, 160, 250, 153, 140, 211, 82, 89, 162, 239, 10, 122, 92, 104, 102, 44, 20, 116, 248, 140, 203, 109, 8, 167, 103, 123, 190, 199, 242, 32, 65, 173, 81]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_signed_msg_hash() {
|
||||
let hash = signed_msg_hash("test");
|
||||
assert_eq!(hash.to_hex(), "a6f87fe6d58a032c320ff8d1541656f0282c2c7bfcc69d61af4c8e8ed528e49c");
|
||||
assert_eq!(
|
||||
hash.to_hex(),
|
||||
"a6f87fe6d58a032c320ff8d1541656f0282c2c7bfcc69d61af4c8e8ed528e49c"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[cfg(all(feature = "secp-recovery", feature = "base64"))]
|
||||
fn test_message_signature() {
|
||||
use core::str::FromStr;
|
||||
|
||||
use secp256k1;
|
||||
use crate::{Address, Network, AddressType};
|
||||
|
||||
use crate::{Address, AddressType, Network};
|
||||
|
||||
let secp = secp256k1::Secp256k1::new();
|
||||
let message = "rust-bitcoin MessageSignature test";
|
||||
let msg_hash = super::signed_msg_hash(message);
|
||||
let msg = secp256k1::Message::from(msg_hash);
|
||||
|
||||
|
||||
let privkey = secp256k1::SecretKey::new(&mut secp256k1::rand::thread_rng());
|
||||
let secp_sig = secp.sign_ecdsa_recoverable(&msg, &privkey);
|
||||
let signature = super::MessageSignature {
|
||||
signature: secp_sig,
|
||||
compressed: true,
|
||||
};
|
||||
let signature = super::MessageSignature { signature: secp_sig, compressed: true };
|
||||
|
||||
assert_eq!(signature.to_base64(), signature.to_string());
|
||||
let signature2 = super::MessageSignature::from_str(&signature.to_string()).unwrap();
|
||||
|
@ -352,6 +266,7 @@ mod tests {
|
|||
#[cfg(all(feature = "secp-recovery", feature = "base64"))]
|
||||
fn test_incorrect_message_signature() {
|
||||
use secp256k1;
|
||||
|
||||
use crate::util::key::PublicKey;
|
||||
use crate::{Address, Network};
|
||||
|
||||
|
@ -363,11 +278,12 @@ mod tests {
|
|||
// Signed with pk "UuOGDsfLPr4HIMKQX0ipjJeRaj1geCq3yPUF2COP5ME="
|
||||
let signature_base64 = "IAM2qX24tYx/bdBTIgVLhD8QEAjrPlJpmjB4nZHdRYGIBa4DmVulAcwjPnWe6Q5iEwXH6F0pUCJP/ZeHPWS1h1o=";
|
||||
let pubkey_base64 = "A1FTfMEntPpAty3qkEo0q2Dc1FEycI10a3jmwEFy+Qr6";
|
||||
let signature = super::MessageSignature::from_base64(signature_base64).expect("message signature");
|
||||
let signature =
|
||||
super::MessageSignature::from_base64(signature_base64).expect("message signature");
|
||||
|
||||
let pubkey = PublicKey::from_slice(
|
||||
&::base64::decode(&pubkey_base64).expect("base64 string")
|
||||
).expect("pubkey slice");
|
||||
let pubkey =
|
||||
PublicKey::from_slice(&::base64::decode(&pubkey_base64).expect("base64 string"))
|
||||
.expect("pubkey slice");
|
||||
|
||||
let p2pkh = Address::p2pkh(&pubkey, Network::Bitcoin);
|
||||
assert_eq!(signature.is_signed_by_address(&secp, &p2pkh, msg_hash), Ok(false));
|
|
@ -14,7 +14,6 @@ pub mod base58;
|
|||
pub mod bip152;
|
||||
pub mod hash;
|
||||
pub mod merkleblock;
|
||||
pub mod misc;
|
||||
pub mod psbt;
|
||||
pub mod taproot;
|
||||
pub mod uint;
|
||||
|
@ -121,3 +120,53 @@ pub use crate::bip32;
|
|||
|
||||
#[deprecated(since = "0.30.0", note = "Please use crate::bip158")]
|
||||
pub use crate::bip158;
|
||||
|
||||
/// The `misc` module was moved and re-named to `sign_message`.
|
||||
pub mod misc {
|
||||
use crate::prelude::*;
|
||||
|
||||
/// Search for `needle` in the vector `haystack` and remove every
|
||||
/// instance of it, returning the number of instances removed.
|
||||
/// Loops through the vector opcode by opcode, skipping pushed data.
|
||||
// For why we deprecated see: https://github.com/rust-bitcoin/rust-bitcoin/pull/1259#discussion_r968613736
|
||||
#[deprecated(since = "0.30.0", note = "No longer supported")]
|
||||
pub fn script_find_and_remove(haystack: &mut Vec<u8>, needle: &[u8]) -> usize {
|
||||
use crate::blockdata::opcodes;
|
||||
|
||||
if needle.len() > haystack.len() {
|
||||
return 0;
|
||||
}
|
||||
if needle.is_empty() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
let mut top = haystack.len() - needle.len();
|
||||
let mut n_deleted = 0;
|
||||
|
||||
let mut i = 0;
|
||||
while i <= top {
|
||||
if &haystack[i..(i + needle.len())] == needle {
|
||||
for j in i..top {
|
||||
haystack.swap(j + needle.len(), j);
|
||||
}
|
||||
n_deleted += 1;
|
||||
// This is ugly but prevents infinite loop in case of overflow
|
||||
let overflow = top < needle.len();
|
||||
top = top.wrapping_sub(needle.len());
|
||||
if overflow {
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
i += match opcodes::All::from((*haystack)[i]).classify(opcodes::ClassifyContext::Legacy) {
|
||||
opcodes::Class::PushBytes(n) => n as usize + 1,
|
||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => 2,
|
||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => 3,
|
||||
opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) => 5,
|
||||
_ => 1
|
||||
};
|
||||
}
|
||||
}
|
||||
haystack.truncate(top.wrapping_add(needle.len()));
|
||||
n_deleted
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue