diff --git a/bitcoin/Cargo.toml b/bitcoin/Cargo.toml index f31d7906..2c4cbba2 100644 --- a/bitcoin/Cargo.toml +++ b/bitcoin/Cargo.toml @@ -44,6 +44,7 @@ bitcoinconsensus = { version = "0.20.2-0.5.0", optional = true, default-features core2 = { version = "0.3.0", default-features = false, features = ["alloc"], optional = true } # Do NOT use this as a feature! Use the `serde` feature instead. actual-serde = { package = "serde", version = "1.0.103", default-features = false, features = [ "derive", "alloc" ], optional = true } +hex_lit = "0.1.1" [dev-dependencies] serde_json = "1.0.0" diff --git a/bitcoin/fuzz/fuzz_targets/deserialize_script.rs b/bitcoin/fuzz/fuzz_targets/deserialize_script.rs index 94875076..8c42ca38 100644 --- a/bitcoin/fuzz/fuzz_targets/deserialize_script.rs +++ b/bitcoin/fuzz/fuzz_targets/deserialize_script.rs @@ -22,7 +22,7 @@ fn do_test(data: &[u8]) { // reserialized as numbers. (For -1 through 16, this will use special ops; for // others it'll just reserialize them as pushes.) if bytes.len() == 1 && bytes[0] != 0x80 && bytes[0] != 0x00 { - if let Ok(num) = script::read_scriptint(bytes) { + if let Ok(num) = script::read_scriptint(bytes.as_bytes()) { b = b.push_int(num); } else { b = b.push_slice(bytes); diff --git a/bitcoin/src/address.rs b/bitcoin/src/address.rs index aa27076e..e1cdc0ea 100644 --- a/bitcoin/src/address.rs +++ b/bitcoin/src/address.rs @@ -27,7 +27,7 @@ //! bitcoin = { version = "...", features = ["rand-std"] } //! ``` -use core::convert::TryFrom; +use core::convert::{TryFrom, TryInto}; use core::fmt; use core::marker::PhantomData; use core::str::FromStr; @@ -43,7 +43,7 @@ use crate::blockdata::constants::{ }; use crate::blockdata::opcodes; use crate::blockdata::opcodes::all::*; -use crate::blockdata::script::{self, Instruction, Script, ScriptBuf}; +use crate::blockdata::script::{self, Instruction, Script, ScriptBuf, PushBytes, PushBytesBuf, PushBytesErrorReport}; use crate::crypto::key::PublicKey; use crate::crypto::schnorr::{TapTweak, TweakedPublicKey, UntweakedPublicKey}; use crate::error::ParseIntError; @@ -402,12 +402,14 @@ pub struct WitnessProgram { /// The witness program version. version: WitnessVersion, /// The witness program. (Between 2 and 40 bytes) - program: Vec, + program: PushBytesBuf, } impl WitnessProgram { /// Creates a new witness program. - pub fn new(version: WitnessVersion, program: Vec) -> Result { + pub fn new

(version: WitnessVersion, program: P) -> Result where P: TryInto,

>::Error: PushBytesErrorReport { + let program = program.try_into() + .map_err(|error| Error::InvalidWitnessProgramLength(error.input_len()))?; if program.len() < 2 || program.len() > 40 { return Err(Error::InvalidWitnessProgramLength(program.len())); } @@ -425,7 +427,7 @@ impl WitnessProgram { } /// Returns the witness program. - pub fn program(&self) -> &[u8] { + pub fn program(&self) -> &PushBytes { &self.program } } @@ -444,9 +446,13 @@ impl Payload { } else if script.is_witness_program() { let opcode = script.first_opcode().expect("witness_version guarantees len() > 4"); + let witness_program = script + .as_bytes()[2..] + .to_vec(); + let witness_program = WitnessProgram::new( WitnessVersion::try_from(opcode)?, - script.as_bytes()[2..].to_vec(), + witness_program, )?; Payload::WitnessProgram(witness_program) } else { @@ -481,7 +487,7 @@ impl Payload { pub fn p2wpkh(pk: &PublicKey) -> Result { let prog = WitnessProgram::new( WitnessVersion::V0, - pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?.into_inner().to_vec() + pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)? )?; Ok(Payload::WitnessProgram(prog)) } @@ -490,7 +496,7 @@ impl Payload { pub fn p2shwpkh(pk: &PublicKey) -> Result { let builder = script::Builder::new() .push_int(0) - .push_slice(pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?.as_ref()); + .push_slice(pk.wpubkey_hash().ok_or(Error::UncompressedPubkey)?); Ok(Payload::ScriptHash(builder.into_script().script_hash())) } @@ -499,7 +505,7 @@ impl Payload { pub fn p2wsh(script: &Script) -> Payload { let prog = WitnessProgram::new( WitnessVersion::V0, - script.wscript_hash().as_inner().to_vec() + script.wscript_hash() ).expect("wscript_hash has len 32 compatible with segwitv0"); Payload::WitnessProgram(prog) } @@ -507,7 +513,7 @@ impl Payload { /// Create a pay to script payload that embeds a witness pay to script hash address pub fn p2shwsh(script: &Script) -> Payload { let ws = - script::Builder::new().push_int(0).push_slice(script.wscript_hash().as_ref()).into_script(); + script::Builder::new().push_int(0).push_slice(script.wscript_hash()).into_script(); Payload::ScriptHash(ws.script_hash()) } @@ -521,7 +527,7 @@ impl Payload { let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root); let prog = WitnessProgram::new( WitnessVersion::V1, - output_key.to_inner().serialize().to_vec() + output_key.to_inner().serialize() ).expect("taproot output key has len 32 <= 40"); Payload::WitnessProgram(prog) } @@ -532,7 +538,7 @@ impl Payload { pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Payload { let prog = WitnessProgram::new( WitnessVersion::V1, - output_key.to_inner().serialize().to_vec() + output_key.to_inner().serialize() ).expect("taproot output key has len 32 <= 40"); Payload::WitnessProgram(prog) } @@ -543,7 +549,7 @@ impl Payload { match self { Payload::ScriptHash(hash) => hash.as_ref(), Payload::PubkeyHash(hash) => hash.as_ref(), - Payload::WitnessProgram(prog) => prog.program(), + Payload::WitnessProgram(prog) => prog.program().as_bytes(), } } } @@ -588,7 +594,7 @@ impl<'a> fmt::Display for AddressEncoding<'a> { let mut bech32_writer = bech32::Bech32Writer::new(self.bech32_hrp, version.bech32_variant(), writer)?; bech32::WriteBase32::write_u5(&mut bech32_writer, version.into())?; - bech32::ToBase32::write_base32(&prog, &mut bech32_writer) + bech32::ToBase32::write_base32(&prog.as_bytes(), &mut bech32_writer) } } } @@ -1139,7 +1145,7 @@ mod tests { use super::*; use crate::crypto::key::PublicKey; - use crate::internal_macros::hex; + use hex_lit::hex; use crate::network::constants::Network::{Bitcoin, Testnet}; fn roundtrips(addr: &Address) { @@ -1272,10 +1278,8 @@ mod tests { #[test] fn test_non_existent_segwit_version() { // 40-byte program - let program = hex!( - "654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4" - ); - let witness_prog = WitnessProgram::new(WitnessVersion::V13, program).unwrap(); + let program = hex!("654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4"); + let witness_prog = WitnessProgram::new(WitnessVersion::V13, program.to_vec()).unwrap(); let addr = Address::new(Bitcoin, Payload::WitnessProgram(witness_prog)); roundtrips(&addr); } diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 947598af..3e525cc9 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -334,7 +334,7 @@ impl Block { match push.map_err(|_| Bip34Error::NotPresent)? { script::Instruction::PushBytes(b) => { // Check that the number is encoded in the minimal way. - let h = script::read_scriptint(b).map_err(|_e| Bip34Error::UnexpectedPush(b.to_vec()))?; + let h = script::read_scriptint(b.as_bytes()).map_err(|_e| Bip34Error::UnexpectedPush(b.as_bytes().to_vec()))?; if h < 0 { Err(Bip34Error::NegativeHeight) } else { diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index 4e7b8e39..3ae56bb5 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -8,13 +8,11 @@ //! single transaction. //! -use crate::prelude::*; - use core::default::Default; use bitcoin_internals::impl_array_newtype; +use hex_lit::hex; -use crate::hashes::hex::{self, HexIterator}; use crate::hashes::{Hash, sha256d}; use crate::blockdata::script; use crate::blockdata::opcodes::all::*; @@ -85,11 +83,9 @@ fn bitcoin_genesis_tx() -> Transaction { }); // Outputs - let script_bytes: Result, hex::Error> = - HexIterator::new("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f").unwrap() - .collect(); + let script_bytes = hex!("04678afdb0fe5548271967f1a67130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef38c4f35504e51ec112de5c384df7ba0b8d578a4c702b6bf11d5f"); let out_script = script::Builder::new() - .push_slice(script_bytes.unwrap().as_slice()) + .push_slice(script_bytes) .push_opcode(OP_CHECKSIG) .into_script(); ret.output.push(TxOut { diff --git a/bitcoin/src/blockdata/script/borrowed.rs b/bitcoin/src/blockdata/script/borrowed.rs index 027cf7bb..94e0d1e2 100644 --- a/bitcoin/src/blockdata/script/borrowed.rs +++ b/bitcoin/src/blockdata/script/borrowed.rs @@ -1,7 +1,7 @@ // Written in 2014 by Andrew Poelstra // SPDX-License-Identifier: CC0-1.0 -use core::convert::TryFrom; +use core::convert::{TryFrom, TryInto}; use core::fmt; #[cfg(rust_v_1_53)] use core::ops::Bound; @@ -277,6 +277,14 @@ impl Script { && self.0[1] == OP_PUSHBYTES_20.to_u8() } + pub(crate) fn v0_p2wpkh(&self) -> Option<&[u8; 20]> { + if self.is_v0_p2wpkh() { + Some(self.0[2..].try_into().expect("is_v0_p2wpkh checks the length")) + } else { + None + } + } + /// Checks whether a script pubkey is a P2TR output. #[inline] pub fn is_v1_p2tr(&self) -> bool { diff --git a/bitcoin/src/blockdata/script/builder.rs b/bitcoin/src/blockdata/script/builder.rs index d622befc..0415fe72 100644 --- a/bitcoin/src/blockdata/script/builder.rs +++ b/bitcoin/src/blockdata/script/builder.rs @@ -9,7 +9,7 @@ use secp256k1::XOnlyPublicKey; use crate::blockdata::locktime::absolute; use crate::blockdata::opcodes::{self, all::*}; -use crate::blockdata::script::{write_scriptint, opcode_to_verify, Script, ScriptBuf}; +use crate::blockdata::script::{write_scriptint, opcode_to_verify, Script, ScriptBuf, PushBytes}; use crate::blockdata::transaction::Sequence; use crate::key::PublicKey; use crate::prelude::*; @@ -56,11 +56,11 @@ impl Builder { pub(in crate::blockdata) fn push_int_non_minimal(self, data: i64) -> Builder { let mut buf = [0u8; 8]; let len = write_scriptint(&mut buf, data); - self.push_slice(&buf[..len]) + self.push_slice(&<&PushBytes>::from(&buf)[..len]) } /// Adds instructions to push some arbitrary data onto the stack. - pub fn push_slice(mut self, data: &[u8]) -> Builder { + pub fn push_slice>(mut self, data: T) -> Builder { self.0.push_slice(data); self.1 = None; self @@ -69,15 +69,15 @@ impl Builder { /// Adds instructions to push a public key onto the stack. pub fn push_key(self, key: &PublicKey) -> Builder { if key.compressed { - self.push_slice(&key.inner.serialize()[..]) + self.push_slice(key.inner.serialize()) } else { - self.push_slice(&key.inner.serialize_uncompressed()[..]) + self.push_slice(key.inner.serialize_uncompressed()) } } /// Adds instructions to push an XOnly public key onto the stack. pub fn push_x_only_key(self, x_only_key: &XOnlyPublicKey) -> Builder { - self.push_slice(&x_only_key.serialize()) + self.push_slice(x_only_key.serialize()) } /// Adds a single opcode to the script. diff --git a/bitcoin/src/blockdata/script/instruction.rs b/bitcoin/src/blockdata/script/instruction.rs index c1bd2e65..4f457c5f 100644 --- a/bitcoin/src/blockdata/script/instruction.rs +++ b/bitcoin/src/blockdata/script/instruction.rs @@ -1,14 +1,15 @@ // Written in 2014 by Andrew Poelstra // SPDX-License-Identifier: CC0-1.0 +use core::convert::TryInto; use crate::blockdata::opcodes; -use crate::blockdata::script::{read_uint_iter, Error, Script, ScriptBuf, UintError}; +use crate::blockdata::script::{read_uint_iter, Error, Script, ScriptBuf, UintError, PushBytes}; /// A "parsed opcode" which allows iterating over a [`Script`] in a more sensible way. #[derive(Debug, PartialEq, Eq, Copy, Clone)] pub enum Instruction<'a> { /// Push a bunch of data. - PushBytes(&'a [u8]), + PushBytes(&'a PushBytes), /// Some non-push opcode. Op(opcodes::All), } @@ -22,8 +23,8 @@ impl<'a> Instruction<'a> { } } - /// Returns the opcode if the instruction is not a data push. - pub fn push_bytes(&self) -> Option<&[u8]> { + /// Returns the pushed bytes if the instruction is a data push. + pub fn push_bytes(&self) -> Option<&PushBytes> { match self { Instruction::Op(_) => None, Instruction::PushBytes(bytes) => Some(bytes), @@ -64,22 +65,23 @@ impl<'a> Instructions<'a> { /// /// If the iterator is not long enough [`Error::EarlyEndOfScript`] is returned and the iterator /// is killed to avoid returning an infinite stream of errors. - pub(super) fn take_slice_or_kill(&mut self, len: usize) -> Result<&'a [u8], Error> { + pub(super) fn take_slice_or_kill(&mut self, len: u32) -> Result<&'a PushBytes, Error> { + let len = len as usize; if self.data.len() >= len { let slice = &self.data.as_slice()[..len]; if len > 0 { self.data.nth(len - 1); } - Ok(slice) + Ok(slice.try_into().expect("len was created from u32, so can't happen")) } else { self.kill(); Err(Error::EarlyEndOfScript) } } - pub(super) fn next_push_data_len(&mut self, len: usize, min_push_len: usize) -> Option, Error>> { - let n = match read_uint_iter(&mut self.data, len) { + pub(super) fn next_push_data_len(&mut self, len: PushDataLenLen, min_push_len: usize) -> Option, Error>> { + let n = match read_uint_iter(&mut self.data, len as usize) { Ok(n) => n, // We do exhaustive matching to not forget to handle new variants if we extend // `UintError` type. @@ -94,10 +96,24 @@ impl<'a> Instructions<'a> { self.kill(); return Some(Err(Error::NonMinimalPush)); } - Some(self.take_slice_or_kill(n).map(Instruction::PushBytes)) + let result = n + .try_into() + .map_err(|_| Error::NumericOverflow) + .and_then(|n| self.take_slice_or_kill(n)) + .map(Instruction::PushBytes); + Some(result) } } +/// Allowed length of push data length. +/// +/// This makes it easier to prove correctness of `next_push_data_len`. +pub(super) enum PushDataLenLen { + One = 1, + Two = 2, + Four = 4, +} + impl<'a> Iterator for Instructions<'a> { type Item = Result, Error>; @@ -110,8 +126,6 @@ impl<'a> Iterator for Instructions<'a> { opcodes::Class::PushBytes(n) => { // make sure safety argument holds across refactorings let n: u32 = n; - // casting is safe because we don't support 16-bit architectures - let n = n as usize; let op_byte = self.data.as_slice().first(); match (self.enforce_minimal, op_byte, n) { @@ -122,7 +136,7 @@ impl<'a> Iterator for Instructions<'a> { (_, None, 0) => { // the iterator is already empty, may as well use this information to avoid // whole take_slice_or_kill function - Some(Ok(Instruction::PushBytes(&[]))) + Some(Ok(Instruction::PushBytes(PushBytes::empty()))) }, _ => { Some(self.take_slice_or_kill(n).map(Instruction::PushBytes)) @@ -130,13 +144,13 @@ impl<'a> Iterator for Instructions<'a> { } } opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA1) => { - self.next_push_data_len(1, 76) + self.next_push_data_len(PushDataLenLen::One, 76) } opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA2) => { - self.next_push_data_len(2, 0x100) + self.next_push_data_len(PushDataLenLen::Two, 0x100) } opcodes::Class::Ordinary(opcodes::Ordinary::OP_PUSHDATA4) => { - self.next_push_data_len(4, 0x10000) + self.next_push_data_len(PushDataLenLen::Four, 0x10000) } // Everything else we can push right through _ => { diff --git a/bitcoin/src/blockdata/script/mod.rs b/bitcoin/src/blockdata/script/mod.rs index 1e6f0cef..f857af8b 100644 --- a/bitcoin/src/blockdata/script/mod.rs +++ b/bitcoin/src/blockdata/script/mod.rs @@ -71,11 +71,13 @@ mod instruction; mod owned; #[cfg(test)] mod tests; +mod push_bytes; pub use self::borrowed::*; pub use self::builder::*; pub use self::instruction::*; pub use self::owned::*; +pub use self::push_bytes::*; /// Encodes an integer in script(minimal CScriptNum) format. /// diff --git a/bitcoin/src/blockdata/script/owned.rs b/bitcoin/src/blockdata/script/owned.rs index d8157caf..89157634 100644 --- a/bitcoin/src/blockdata/script/owned.rs +++ b/bitcoin/src/blockdata/script/owned.rs @@ -8,7 +8,7 @@ use secp256k1::{Secp256k1, Verification}; use crate::address::{WitnessVersion, WitnessProgram}; use crate::blockdata::opcodes::{self, all::*}; -use crate::blockdata::script::{opcode_to_verify, Builder, Instruction, Script}; +use crate::blockdata::script::{opcode_to_verify, Builder, Instruction, Script, PushBytes}; use crate::hashes::hex; use crate::hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash}; use crate::key::PublicKey; @@ -98,7 +98,7 @@ impl ScriptBuf { Builder::new() .push_opcode(OP_DUP) .push_opcode(OP_HASH160) - .push_slice(&pubkey_hash[..]) + .push_slice(pubkey_hash) .push_opcode(OP_EQUALVERIFY) .push_opcode(OP_CHECKSIG) .into_script() @@ -108,7 +108,7 @@ impl ScriptBuf { pub fn new_p2sh(script_hash: &ScriptHash) -> Self { Builder::new() .push_opcode(OP_HASH160) - .push_slice(&script_hash[..]) + .push_slice(script_hash) .push_opcode(OP_EQUAL) .into_script() } @@ -116,13 +116,13 @@ impl ScriptBuf { /// Generates P2WPKH-type of scriptPubkey. pub fn new_v0_p2wpkh(pubkey_hash: &WPubkeyHash) -> Self { // pubkey hash is 20 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V0, &pubkey_hash[..]) + ScriptBuf::new_witness_program_unchecked(WitnessVersion::V0, pubkey_hash) } /// Generates P2WSH-type of scriptPubkey with a given hash of the redeem script. pub fn new_v0_p2wsh(script_hash: &WScriptHash) -> Self { // script hash is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V0, &script_hash[..]) + ScriptBuf::new_witness_program_unchecked(WitnessVersion::V0, script_hash) } /// Generates P2TR for script spending path using an internal public key and some optional @@ -130,13 +130,13 @@ impl ScriptBuf { pub fn new_v1_p2tr(secp: &Secp256k1, internal_key: UntweakedPublicKey, merkle_root: Option) -> Self { let (output_key, _) = internal_key.tap_tweak(secp, merkle_root); // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, &output_key.serialize()) + ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) } /// Generates P2TR for key spending path for a known [`TweakedPublicKey`]. pub fn new_v1_p2tr_tweaked(output_key: TweakedPublicKey) -> Self { // output key is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv1) - ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, &output_key.serialize()) + ScriptBuf::new_witness_program_unchecked(WitnessVersion::V1, output_key.serialize()) } /// Generates P2WSH-type of scriptPubkey with a given [`WitnessProgram`]. @@ -152,7 +152,8 @@ impl ScriptBuf { /// /// Convenience method used by `new_v0_p2wpkh`, `new_v0_p2wsh`, `new_v1_p2tr`, and /// `new_v1_p2tr_tweaked`. - fn new_witness_program_unchecked(version: WitnessVersion, program: &[u8]) -> Self { + fn new_witness_program_unchecked>(version: WitnessVersion, program: T) -> Self { + let program = program.as_ref(); debug_assert!(program.len() >= 2 && program.len() <= 40); // In segwit v0, the program must be 20 or 32 bytes long. debug_assert!(version != WitnessVersion::V0 || program.len() == 20 || program.len() == 32); @@ -163,7 +164,7 @@ impl ScriptBuf { } /// Generates OP_RETURN-type of scriptPubkey for the given data. - pub fn new_op_return(data: &[u8]) -> Self { + pub fn new_op_return>(data: &T) -> Self { Builder::new() .push_opcode(OP_RETURN) .push_slice(data) @@ -200,18 +201,16 @@ impl ScriptBuf { /// /// [BIP143]: pub fn p2wpkh_script_code(&self) -> Option { - if !self.is_v0_p2wpkh() { - return None - } - let script = Builder::new() - .push_opcode(OP_DUP) - .push_opcode(OP_HASH160) - .push_slice(&self.as_bytes()[2..]) // The `self` script is 0x00, 0x14, - .push_opcode(OP_EQUALVERIFY) - .push_opcode(OP_CHECKSIG) - .into_script(); - - Some(script) + self.v0_p2wpkh().map(|wpkh| { + Builder::new() + .push_opcode(OP_DUP) + .push_opcode(OP_HASH160) + // The `self` script is 0x00, 0x14, + .push_slice(wpkh) + .push_opcode(OP_EQUALVERIFY) + .push_opcode(OP_CHECKSIG) + .into_script() + }) } /// Adds a single opcode to the script. @@ -220,17 +219,14 @@ impl ScriptBuf { } /// Adds instructions to push some arbitrary data onto the stack. - /// - /// ## Panics - /// - /// The method panics if `data` length is greater or equal to 0x100000000. - pub fn push_slice(&mut self, data: &[u8]) { + pub fn push_slice>(&mut self, data: T) { + let data = data.as_ref(); self.reserve(Self::reserved_len_for_slice(data.len())); self.push_slice_no_opt(data); } /// Pushes the slice without reserving - fn push_slice_no_opt(&mut self, data: &[u8]) { + fn push_slice_no_opt(&mut self, data: &PushBytes) { // Start with a PUSH opcode match data.len() as u64 { n if n < opcodes::Ordinary::OP_PUSHDATA1 as u64 => { self.0.push(n as u8); }, @@ -253,7 +249,7 @@ impl ScriptBuf { _ => panic!("tried to put a 4bn+ sized object into a script!") } // Then push the raw bytes - self.0.extend_from_slice(data); + self.0.extend_from_slice(data.as_bytes()); } /// Computes the sum of `len` and the lenght of an appropriate push opcode. diff --git a/bitcoin/src/blockdata/script/push_bytes.rs b/bitcoin/src/blockdata/script/push_bytes.rs new file mode 100644 index 00000000..55b4dbc7 --- /dev/null +++ b/bitcoin/src/blockdata/script/push_bytes.rs @@ -0,0 +1,463 @@ +//! Contains `PushBytes` & co + +use core::ops::{Deref, DerefMut}; +use core::borrow::{Borrow, BorrowMut}; +#[allow(unused)] +use crate::prelude::*; + +pub use primitive::*; + +/// This module only contains required operations so that outside functions wouldn't accidentally +/// break invariants. Therefore auditing this module should be sufficient. +mod primitive { + #[allow(unused)] + use crate::prelude::*; + + use super::PushBytesError; + use core::convert::{TryFrom, TryInto}; + use core::ops::{Index, Range, RangeFull, RangeFrom, RangeTo, RangeInclusive, RangeToInclusive}; + #[cfg(feature = "rust_v_1_53")] + use core::ops::Bound; + + #[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] + fn check_limit(len: usize) -> Result<(), PushBytesError> { + Ok(()) + } + + #[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] + fn check_limit(len: usize) -> Result<(), PushBytesError> { + if len < 0x100000000 { + Ok(()) + } else { + Err(PushBytesError { len }) + } + } + + /// Byte slices that can be in Bitcoin script. + /// + /// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long. + /// This type represents slices that are guaranteed to be within the limit so they can be put in + /// the script safely. + #[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] + #[repr(transparent)] + pub struct PushBytes([u8]); + + impl PushBytes { + /// Creates `&Self` without checking the length. + /// + /// ## Safety + /// + /// The caller is responsible for checking that the length is less than the [`LIMIT`]. + unsafe fn from_slice_unchecked(bytes: &[u8]) -> &Self { + &*(bytes as *const [u8] as *const PushBytes) + } + + /// Creates `&mut Self` without checking the length. + /// + /// ## Safety + /// + /// The caller is responsible for checking that the length is less than the [`LIMIT`]. + unsafe fn from_mut_slice_unchecked(bytes: &mut [u8]) -> &mut Self { + &mut *(bytes as *mut [u8] as *mut PushBytes) + } + + /// Creates an empty `PushBytes`. + pub fn empty() -> &'static Self { + // 0 < LIMIT + unsafe { Self::from_slice_unchecked(&[]) } + } + + /// Returns the underlying bytes. + pub fn as_bytes(&self) -> &[u8] { + &self.0 + } + + /// Returns the underlying mutbale bytes. + pub fn as_mut_bytes(&mut self) -> &mut [u8] { + &mut self.0 + } + } + + macro_rules! delegate_index { + ($($type:ty),* $(,)?) => { + $( + /// Script subslicing operation - read [slicing safety](#slicing-safety)! + impl Index<$type> for PushBytes { + type Output = Self; + + #[inline] + #[cfg_attr(rust_v_1_46, track_caller)] + fn index(&self, index: $type) -> &Self::Output { + // Slicing can not make slices longer + unsafe { + Self::from_slice_unchecked(&self.0[index]) + } + } + } + )* + } + } + + delegate_index!(Range, RangeFrom, RangeTo, RangeFull, RangeInclusive, RangeToInclusive); + #[cfg(feature = "rust_v_1_53")] + #[cfg_attr(docsrs, doc(cfg(feature = "rust_v_1_53")))] + delegate_index!((Bound, Bound)); + + impl Index for PushBytes { + type Output = u8; + + #[inline] + #[cfg_attr(rust_v_1_46, track_caller)] + fn index(&self, index: usize) -> &Self::Output { + &self.0[index] + } + } + + impl<'a> TryFrom<&'a [u8]> for &'a PushBytes { + type Error = PushBytesError; + + fn try_from(bytes: &'a [u8]) -> Result { + check_limit(bytes.len())?; + // We've just checked the length + Ok(unsafe { PushBytes::from_slice_unchecked(bytes) }) + } + } + + impl<'a> TryFrom<&'a mut [u8]> for &'a mut PushBytes { + type Error = PushBytesError; + + fn try_from(bytes: &'a mut [u8]) -> Result { + check_limit(bytes.len())?; + // We've just checked the length + Ok(unsafe { PushBytes::from_mut_slice_unchecked(bytes) }) + } + } + + macro_rules! from_array { + ($($len:literal),* $(,)?) => { + $( + impl<'a> From<&'a [u8; $len]> for &'a PushBytes { + fn from(bytes: &'a [u8; $len]) -> Self { + // Check that the macro wasn't called with a wrong number. + const _: () = [(); 1][($len >= 0x100000000u64) as usize]; + // We know the size of array statically and we checked macro input. + unsafe { PushBytes::from_slice_unchecked(bytes) } + } + } + + impl<'a> From<&'a mut [u8; $len]> for &'a mut PushBytes { + fn from(bytes: &'a mut [u8; $len]) -> Self { + // Macro check already above, no need to duplicate. + // We know the size of array statically and we checked macro input. + unsafe { PushBytes::from_mut_slice_unchecked(bytes) } + } + } + + impl AsRef for [u8; $len] { + fn as_ref(&self) -> &PushBytes { + self.into() + } + } + + impl AsMut for [u8; $len] { + fn as_mut(&mut self) -> &mut PushBytes { + self.into() + } + } + + impl From<[u8; $len]> for PushBytesBuf { + fn from(bytes: [u8; $len]) -> Self { + PushBytesBuf(Vec::from(&bytes as &[_])) + } + } + + impl<'a> From<&'a [u8; $len]> for PushBytesBuf { + fn from(bytes: &'a [u8; $len]) -> Self { + PushBytesBuf(Vec::from(bytes as &[_])) + } + } + )* + } + } + + // Sizes up to 73 to support all pubkey and signature sizes + from_array! { + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, + 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, + 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, + 71, 72, 73, + } + + /// Owned, growable counterpart to `PushBytes`. + #[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)] + pub struct PushBytesBuf(Vec); + + impl PushBytesBuf { + /// Creates a new empty `PushBytesBuf`. + pub fn new() -> Self { + PushBytesBuf(Vec::new()) + } + + /// Creates a new empty `PushBytesBuf` with reserved capacity. + pub fn with_capacity(capacity: usize) -> Self { + PushBytesBuf(Vec::with_capacity(capacity)) + } + + /// Reserve capacity for `additional_capacity` bytes. + pub fn reserve(&mut self, additional_capacity: usize) { + self.0.reserve(additional_capacity) + } + + /// Try pushing a single byte. + /// + /// ## Errors + /// + /// This method fails if `self` would exceed the limit. + #[allow(deprecated)] + pub fn push(&mut self, byte: u8) -> Result<(), PushBytesError> { + // This is OK on 32 bit archs since vec has its own check and this check is pointless. + check_limit(self.0.len().saturating_add(1))?; + self.0.push(byte); + Ok(()) + } + + /// Try appending a slice to `PushBytesBuf` + /// + /// ## Errors + /// + /// This method fails if `self` would exceed the limit. + pub fn extend_from_slice(&mut self, bytes: &[u8]) -> Result<(), PushBytesError> { + let len = self.0.len().saturating_add(bytes.len()); + check_limit(len)?; + self.0.extend_from_slice(bytes); + Ok(()) + } + + /// Remove the last byte from buffer if any. + pub fn pop(&mut self) -> Option { + self.0.pop() + } + + /// Remove the byte at `index` and return it. + /// + /// ## Panics + /// + /// This method panics if `index` is out of bounds. + #[cfg_attr(rust_v_1_46, track_caller)] + pub fn remove(&mut self, index: usize) -> u8 { + self.0.remove(index) + } + + /// Remove all bytes from buffer without affecting capacity. + pub fn clear(&mut self) { + self.0.clear() + } + + /// Remove bytes from buffer past `len`. + pub fn truncate(&mut self, len: usize) { + self.0.truncate(len) + } + + /// Extracts `PushBytes` slice + pub fn as_push_bytes(&self) -> &PushBytes { + // length guaranteed by our invariant + unsafe { PushBytes::from_slice_unchecked(&self.0) } + } + + /// Extracts mutable `PushBytes` slice + pub fn as_mut_push_bytes(&mut self) -> &mut PushBytes { + // length guaranteed by our invariant + unsafe { PushBytes::from_mut_slice_unchecked(&mut self.0) } + } + + /// Accesses inner `Vec` - provided for `super` to impl other methods. + pub(super) fn inner(&self) -> &Vec { + &self.0 + } + } + + impl From for Vec { + fn from(value: PushBytesBuf) -> Self { + value.0 + } + } + + impl TryFrom> for PushBytesBuf { + type Error = PushBytesError; + + fn try_from(vec: Vec) -> Result { + // check len + let _: &PushBytes = vec.as_slice().try_into()?; + Ok(PushBytesBuf(vec)) + } + } + + impl ToOwned for PushBytes { + type Owned = PushBytesBuf; + + fn to_owned(&self) -> Self::Owned { + PushBytesBuf(self.0.to_owned()) + } + } +} + +impl PushBytes { + /// Returns the number of bytes in buffer. + pub fn len(&self) -> usize { + self.as_bytes().len() + } + + /// Returns true if the buffer contains zero bytes. + pub fn is_empty(&self) -> bool { + self.as_bytes().is_empty() + } +} + +impl PushBytesBuf { + /// Returns the number of bytes in buffer. + pub fn len(&self) -> usize { + self.inner().len() + } + + /// Returns the number of bytes the buffer can contain without reallocating. + pub fn capacity(&self) -> usize { + self.inner().capacity() + } + + /// Returns true if the buffer contains zero bytes. + pub fn is_empty(&self) -> bool { + self.inner().is_empty() + } +} + +impl AsRef<[u8]> for PushBytes { + fn as_ref(&self) -> &[u8] { + self.as_bytes() + } +} + +impl AsMut<[u8]> for PushBytes { + fn as_mut(&mut self) -> &mut [u8] { + self.as_mut_bytes() + } +} + +impl Deref for PushBytesBuf { + type Target = PushBytes; + + fn deref(&self) -> &Self::Target { + self.as_push_bytes() + } +} + +impl DerefMut for PushBytesBuf { + fn deref_mut(&mut self) -> &mut Self::Target { + self.as_mut_push_bytes() + } +} + +impl AsRef for PushBytes { + fn as_ref(&self) -> &PushBytes { + self + } +} + +impl AsMut for PushBytes { + fn as_mut(&mut self) -> &mut PushBytes { + self + } +} + +impl AsRef for PushBytesBuf { + fn as_ref(&self) -> &PushBytes { + self.as_push_bytes() + } +} + +impl AsMut for PushBytesBuf { + fn as_mut(&mut self) -> &mut PushBytes { + self.as_mut_push_bytes() + } +} + +impl Borrow for PushBytesBuf { + fn borrow(&self) -> &PushBytes { + self.as_push_bytes() + } +} + +impl BorrowMut for PushBytesBuf { + fn borrow_mut(&mut self) -> &mut PushBytes { + self.as_mut_push_bytes() + } +} + +/// Reports information about failed conversion into `PushBytes`. +/// +/// This should not be needed by general public, except as an additional bound on `TryFrom` when +/// converting to `WitnessProgram`. +pub trait PushBytesErrorReport { + /// How many bytes the input had. + fn input_len(&self) -> usize; +} + +impl PushBytesErrorReport for core::convert::Infallible { + #[inline] + fn input_len(&self) -> usize { + match *self {} + } +} + +pub use error::*; + +#[cfg(any(target_pointer_width = "16", target_pointer_width = "32"))] +mod error { + use core::fmt; + + /// Error returned on attempt to create too large `PushBytes`. + #[allow(unused)] + #[derive(Debug, Clone)] + pub struct PushBytesError { + never: core::convert::Infallible, + } + + impl super::PushBytesErrorReport for PushBytesError { + #[inline] + fn input_len(&self) -> usize { + match self.never {} + } + } + + impl fmt::Display for PushBytesError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self.never {} + } + } +} + +#[cfg(not(any(target_pointer_width = "16", target_pointer_width = "32")))] +mod error { + use core::fmt; + + /// Error returned on attempt to create too large `PushBytes`. + #[derive(Debug, Clone)] + pub struct PushBytesError { + /// How long the input was. + pub(super) len: usize + } + + impl super::PushBytesErrorReport for PushBytesError { + #[inline] + fn input_len(&self) -> usize { + self.len + } + } + + impl fmt::Display for PushBytesError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "attempt to prepare {} bytes to be pushed into script but the limit is 2^32-1", self.len) + } + } +} + +crate::error::impl_std_error!(PushBytesError); diff --git a/bitcoin/src/blockdata/script/tests.rs b/bitcoin/src/blockdata/script/tests.rs index 708e6373..fdd528e9 100644 --- a/bitcoin/src/blockdata/script/tests.rs +++ b/bitcoin/src/blockdata/script/tests.rs @@ -3,13 +3,12 @@ use core::str::FromStr; use super::*; use crate::hashes::Hash; -use crate::hashes::hex::FromHex; use crate::hash_types::{PubkeyHash, WPubkeyHash, ScriptHash, WScriptHash}; use crate::consensus::encode::{deserialize, serialize}; use crate::blockdata::opcodes; use crate::crypto::key::{PublicKey, XOnlyPublicKey}; use crate::psbt::serialize::Serialize; -use crate::internal_macros::hex; +use hex_lit::hex; #[test] fn script() { @@ -32,7 +31,7 @@ fn script() { script = script.push_int(-10000000); comp.extend([4u8, 128, 150, 152, 128].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); // data - script = script.push_slice("NRA4VR".as_bytes()); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); + script = script.push_slice(b"NRA4VR"); comp.extend([6u8, 78, 82, 65, 52, 86, 82].iter().cloned()); assert_eq!(script.as_bytes(), &comp[..]); // keys const KEYSTR1: &str = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49a81564629ded8156bebd2ffd1af"; @@ -88,9 +87,9 @@ fn p2pk_pubkey_bytes_different_op_code_returns_none() { #[test] fn p2pk_pubkey_bytes_incorrect_key_size_returns_none() { // 63 byte key - let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1"; + let malformed_key = b"21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1"; let invalid_p2pk_script = Script::builder() - .push_slice(malformed_key.as_bytes()) + .push_slice(malformed_key) .push_opcode(OP_CHECKSIG) .into_script(); assert!(invalid_p2pk_script.p2pk_pubkey_bytes().is_none()); @@ -98,9 +97,9 @@ fn p2pk_pubkey_bytes_incorrect_key_size_returns_none() { #[test] fn p2pk_pubkey_bytes_invalid_key_returns_some() { - let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1ux"; + let malformed_key = b"21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1ux"; let invalid_key_script = Script::builder() - .push_slice(malformed_key.as_bytes()) + .push_slice(malformed_key) .push_opcode(OP_CHECKSIG) .into_script(); assert!(invalid_key_script.p2pk_pubkey_bytes().is_some()); @@ -154,9 +153,9 @@ fn p2pk_public_key_different_op_code_returns_none() { #[test] fn p2pk_public_key_incorrect_size_returns_none() { - let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1"; + let malformed_key = b"21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1"; let malformed_key_script = Script::builder() - .push_slice(malformed_key.as_bytes()) + .push_slice(malformed_key) .push_opcode(OP_CHECKSIG) .into_script(); assert!(malformed_key_script.p2pk_public_key().is_none()); @@ -165,9 +164,9 @@ fn p2pk_public_key_incorrect_size_returns_none() { #[test] fn p2pk_public_key_invalid_key_returns_none() { - let malformed_key = "21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1ux"; + let malformed_key = b"21032e58afe51f9ed8ad3cc7897f634d881fdbe49816429ded8156bebd2ffd1ux"; let invalid_key_script = Script::builder() - .push_slice(malformed_key.as_bytes()) + .push_slice(malformed_key) .push_opcode(OP_CHECKSIG) .into_script(); assert!(invalid_key_script.p2pk_public_key().is_none()); @@ -190,7 +189,7 @@ fn script_x_only_key() { const KEYSTR: &str = "209997a497d964fc1a62885b05a51166a65a90df00492c8d7cf61d6accf54803be"; let x_only_key = XOnlyPublicKey::from_str(&KEYSTR[2..]).unwrap(); let script = Builder::new().push_x_only_key(&x_only_key); - assert_eq!(script.into_bytes(), hex!(KEYSTR)); + assert_eq!(script.into_bytes(), &hex!(KEYSTR) as &[u8]); } #[test] @@ -198,7 +197,7 @@ fn script_builder() { // from txid 3bb5e6434c11fb93f64574af5d116736510717f2c595eb45b52c28e31622dfff which was in my mempool when I wrote the test let script = Builder::new().push_opcode(OP_DUP) .push_opcode(OP_HASH160) - .push_slice(&hex!("16e1ae70ff0fa102905d4af297f6912bda6cce19")) + .push_slice(hex!("16e1ae70ff0fa102905d4af297f6912bda6cce19")) .push_opcode(OP_EQUALVERIFY) .push_opcode(OP_CHECKSIG) .into_script(); @@ -231,7 +230,7 @@ fn script_generators() { // Test data are taken from the second output of // 2ccb3a1f745eb4eefcf29391460250adda5fab78aaddb902d25d3cd97d9d8e61 transaction - let data = Vec::::from_hex("aa21a9ed20280f53f2d21663cac89e6bd2ad19edbabb048cda08e73ed19e9268d0afea2a").unwrap(); + let data = hex!("aa21a9ed20280f53f2d21663cac89e6bd2ad19edbabb048cda08e73ed19e9268d0afea2a"); let op_return = ScriptBuf::new_op_return(&data); assert!(op_return.is_op_return()); assert_eq!(op_return.to_hex_string(), "6a24aa21a9ed20280f53f2d21663cac89e6bd2ad19edbabb048cda08e73ed19e9268d0afea2a"); @@ -299,7 +298,7 @@ fn script_builder_verify() { assert_eq!(checkmultisig2.to_hex_string(), "af"); let trick_slice = Builder::new() - .push_slice(&[0xae]) // OP_CHECKMULTISIG + .push_slice([0xae]) // OP_CHECKMULTISIG .push_verify() .into_script(); assert_eq!(trick_slice.to_hex_string(), "01ae69"); @@ -314,7 +313,7 @@ fn script_serialize() { let hex_script = hex!("6c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52"); let script: Result = deserialize(&hex_script); assert!(script.is_ok()); - assert_eq!(serialize(&script.unwrap()), hex_script); + assert_eq!(serialize(&script.unwrap()), &hex_script as &[u8]); } #[test] @@ -499,19 +498,19 @@ fn test_iterator() { unwrap_all!(v_zero, v_zeropush, v_min, v_nonmin_alt, slop_v_min, slop_v_nonmin, slop_v_nonmin_alt); - assert_eq!(v_zero, vec![(0, Instruction::PushBytes(&[]))]); - assert_eq!(v_zeropush, vec![(0, Instruction::PushBytes(&[0]))]); + assert_eq!(v_zero, vec![(0, Instruction::PushBytes(PushBytes::empty()))]); + assert_eq!(v_zeropush, vec![(0, Instruction::PushBytes([0].as_ref()))]); assert_eq!( v_min, - vec![(0, Instruction::PushBytes(&[105])), (2, Instruction::Op(opcodes::OP_NOP3))] + vec![(0, Instruction::PushBytes([105].as_ref())), (2, Instruction::Op(opcodes::OP_NOP3))] ); assert_eq!(v_nonmin.unwrap_err(), Error::NonMinimalPush); assert_eq!( v_nonmin_alt, - vec![(0, Instruction::PushBytes(&[105, 0])), (3, Instruction::Op(opcodes::OP_NOP3))] + vec![(0, Instruction::PushBytes([105, 0].as_ref())), (3, Instruction::Op(opcodes::OP_NOP3))] ); assert_eq!(v_min, slop_v_min); @@ -526,7 +525,7 @@ fn test_iterator() { #[test] fn script_ord() { - let script_1 = Builder::new().push_slice(&[1, 2, 3, 4]).into_script(); + let script_1 = Builder::new().push_slice([1, 2, 3, 4]).into_script(); let script_2 = Builder::new().push_int(10).into_script(); let script_3 = Builder::new().push_int(15).into_script(); let script_4 = Builder::new().push_opcode(OP_RETURN).into_script(); @@ -547,23 +546,24 @@ fn script_ord() { #[cfg(feature = "bitcoinconsensus")] fn test_bitcoinconsensus () { // a random segwit transaction from the blockchain using native segwit - let spent = Builder::from(hex!("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d")).into_script(); + let spent_bytes = hex!("0020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d"); + let spent = Script::from_bytes(&spent_bytes); let spending = hex!("010000000001011f97548fbbe7a0db7588a66e18d803d0089315aa7d4cc28360b6ec50ef36718a0100000000ffffffff02df1776000000000017a9146c002a686959067f4866b8fb493ad7970290ab728757d29f0000000000220020701a8d401c84fb13e6baf169d59684e17abd9fa216c8cc5b9fc63d622ff8c58d04004730440220565d170eed95ff95027a69b313758450ba84a01224e1f7f130dda46e94d13f8602207bdd20e307f062594022f12ed5017bbf4a055a06aea91c10110a0e3bb23117fc014730440220647d2dc5b15f60bc37dc42618a370b2a1490293f9e5c8464f53ec4fe1dfe067302203598773895b4b16d37485cbe21b337f4e4b650739880098c592553add7dd4355016952210375e00eb72e29da82b89367947f29ef34afb75e8654f6ea368e0acdfd92976b7c2103a1b26313f430c4b15bb1fdce663207659d8cac749a0e53d70eff01874496feff2103c96d495bfdd5ba4145e3e046fee45e84a8a48ad05bd8dbb395c011a32cf9f88053ae00000000"); - spent.verify(0, crate::Amount::from_sat(18393430), spending.as_slice()).unwrap(); + spent.verify(0, crate::Amount::from_sat(18393430), &spending).unwrap(); } #[test] fn defult_dust_value_tests() { // Check that our dust_value() calculator correctly calculates the dust limit on common // well-known scriptPubKey types. - let script_p2wpkh = Builder::new().push_int(0).push_slice(&[42; 20]).into_script(); + let script_p2wpkh = Builder::new().push_int(0).push_slice([42; 20]).into_script(); assert!(script_p2wpkh.is_v0_p2wpkh()); assert_eq!(script_p2wpkh.dust_value(), crate::Amount::from_sat(294)); let script_p2pkh = Builder::new() .push_opcode(OP_DUP) .push_opcode(OP_HASH160) - .push_slice(&[42; 20]) + .push_slice([42; 20]) .push_opcode(OP_EQUALVERIFY) .push_opcode(OP_CHECKSIG) .into_script(); @@ -611,7 +611,7 @@ fn script_extend() { let script_5_items = [ Instruction::Op(OP_DUP), Instruction::Op(OP_HASH160), - Instruction::PushBytes(&[42; 20]), + Instruction::PushBytes([42; 20].as_ref()), Instruction::Op(OP_EQUALVERIFY), Instruction::Op(OP_CHECKSIG), ]; @@ -621,7 +621,7 @@ fn script_extend() { let script_6_items = [ Instruction::Op(OP_DUP), Instruction::Op(OP_HASH160), - Instruction::PushBytes(&[42; 20]), + Instruction::PushBytes([42; 20].as_ref()), Instruction::Op(OP_EQUALVERIFY), Instruction::Op(OP_CHECKSIG), Instruction::Op(OP_NOP), @@ -632,7 +632,7 @@ fn script_extend() { let script_7_items = [ Instruction::Op(OP_DUP), Instruction::Op(OP_HASH160), - Instruction::PushBytes(&[42; 20]), + Instruction::PushBytes([42; 20].as_ref()), Instruction::Op(OP_EQUALVERIFY), Instruction::Op(OP_CHECKSIG), Instruction::Op(OP_NOP), diff --git a/bitcoin/src/crypto/ecdsa.rs b/bitcoin/src/crypto/ecdsa.rs index d4b50199..58ee6644 100644 --- a/bitcoin/src/crypto/ecdsa.rs +++ b/bitcoin/src/crypto/ecdsa.rs @@ -9,11 +9,15 @@ use core::str::FromStr; use core::{fmt, iter}; use bitcoin_internals::write_err; +use bitcoin_internals::hex::display::DisplayHex; use secp256k1; use crate::prelude::*; use crate::hashes::hex::{self, FromHex}; use crate::sighash::{EcdsaSighashType, NonStandardSighashType}; +use crate::script::PushBytes; + +const MAX_SIG_LEN: usize = 73; /// An ECDSA signature with the corresponding hash type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] @@ -47,6 +51,23 @@ impl Signature { } /// Serializes an ECDSA signature (inner secp256k1 signature in DER format). + /// + /// This does **not** perform extra heap allocation. + pub fn serialize(&self) -> SerializedSignature { + let mut buf = [0u8; MAX_SIG_LEN]; + let signature = self.sig.serialize_der(); + buf[..signature.len()].copy_from_slice(&signature); + buf[signature.len()] = self.hash_ty as u8; + SerializedSignature { + data: buf, + len: signature.len() + 1, + } + } + + /// Serializes an ECDSA signature (inner secp256k1 signature in DER format) into `Vec`. + /// + /// Note: this performs an extra heap allocation, you might prefer the + /// [`serialize`](Self::serialize) method instead. pub fn to_vec(self) -> Vec { // TODO: add support to serialize to a writer to SerializedSig self.sig.serialize_der() @@ -77,6 +98,125 @@ impl FromStr for Signature { } } +/// Holds signature serialized in-line (not in `Vec`). +/// +/// This avoids allocation and allows proving maximum size of the signature (73 bytes). +/// The type can be used largely as a byte slice. It implements all standard traits one would +/// expect and has familiar methods. +/// However, the usual use case is to push it into a script. This can be done directly passing it +/// into [`push_slice`](crate::script::ScriptBuf::push_slice). +#[derive(Copy, Clone)] +pub struct SerializedSignature { + data: [u8; MAX_SIG_LEN], + len: usize, +} + +impl SerializedSignature { + /// Returns an iterator over bytes of the signature. + #[inline] + pub fn iter(&self) -> core::slice::Iter<'_, u8> { + self.into_iter() + } +} + +impl core::ops::Deref for SerializedSignature { + type Target = [u8]; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.data[..self.len] + } +} + +impl core::ops::DerefMut for SerializedSignature { + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.data[..self.len] + } +} + +impl AsRef<[u8]> for SerializedSignature { + #[inline] + fn as_ref(&self) -> &[u8] { + self + } +} + +impl AsMut<[u8]> for SerializedSignature { + #[inline] + fn as_mut(&mut self) -> &mut [u8] { + self + } +} + +impl AsRef for SerializedSignature { + #[inline] + fn as_ref(&self) -> &PushBytes { + &<&PushBytes>::from(&self.data)[..self.len()] + } +} + +impl core::borrow::Borrow<[u8]> for SerializedSignature { + #[inline] + fn borrow(&self) -> &[u8] { + self + } +} + +impl core::borrow::BorrowMut<[u8]> for SerializedSignature { + #[inline] + fn borrow_mut(&mut self) -> &mut [u8] { + self + } +} + +impl fmt::Debug for SerializedSignature { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } +} + +impl fmt::Display for SerializedSignature { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(self, f) } +} + +impl fmt::LowerHex for SerializedSignature { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::LowerHex::fmt(&(**self).as_hex(), f) + } +} + +impl fmt::UpperHex for SerializedSignature { + #[inline] + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + fmt::UpperHex::fmt(&(**self).as_hex(), f) + } +} + +impl PartialEq for SerializedSignature { + #[inline] + fn eq(&self, other: &SerializedSignature) -> bool { **self == **other } +} + +impl Eq for SerializedSignature {} + +impl core::hash::Hash for SerializedSignature { + fn hash(&self, state: &mut H) { + core::hash::Hash::hash(&**self, state) + } +} + +impl<'a> IntoIterator for &'a SerializedSignature { + type IntoIter = core::slice::Iter<'a, u8>; + type Item = &'a u8; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + (*self).iter() + } +} + /// A key-related error. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] #[non_exhaustive] diff --git a/bitcoin/src/hash_types.rs b/bitcoin/src/hash_types.rs index c7a32e41..78c0342d 100644 --- a/bitcoin/src/hash_types.rs +++ b/bitcoin/src/hash_types.rs @@ -27,6 +27,27 @@ macro_rules! impl_hashencode { }; } +#[rustfmt::skip] +macro_rules! impl_asref_push_bytes { + ($($hashtype:ident),*) => { + $( + impl AsRef<$crate::blockdata::script::PushBytes> for $hashtype { + fn as_ref(&self) -> &$crate::blockdata::script::PushBytes { + use $crate::hashes::Hash; + self.as_inner().as_ref() + } + } + + impl From<$hashtype> for $crate::blockdata::script::PushBytesBuf { + fn from(hash: $hashtype) -> Self { + use $crate::hashes::Hash; + hash.as_inner().into() + } + } + )* + }; +} + // newtypes module is solely here so we can rustfmt::skip. pub use newtypes::*; @@ -75,4 +96,6 @@ See [`hashes::Hash::DISPLAY_BACKWARD`] for more details. impl_hashencode!(FilterHash); impl_hashencode!(FilterHeader); + + impl_asref_push_bytes!(PubkeyHash, ScriptHash, WPubkeyHash, WScriptHash); } diff --git a/bitcoin/src/sighash.rs b/bitcoin/src/sighash.rs index f6e1afdd..bc9dae04 100644 --- a/bitcoin/src/sighash.rs +++ b/bitcoin/src/sighash.rs @@ -1641,15 +1641,15 @@ mod tests { let cache = cache.segwit_cache(); // Parse hex into Vec because BIP143 test vector displays forwards but our sha256d::Hash displays backwards. assert_eq!( - cache.prevouts.into_inner().as_ref(), + cache.prevouts.into_inner(), &Vec::from_hex("96b827c8483d4e9b96712b6713a7b68d6e8003a781feba36c31143470b4efd37").unwrap()[..], ); assert_eq!( - cache.sequences.into_inner().as_ref(), + cache.sequences.into_inner(), &Vec::from_hex("52b0a642eea2fb7ae638c36f6252b6750293dbe574a806984b8e4d8548339a3b").unwrap()[..], ); assert_eq!( - cache.outputs.into_inner().as_ref(), + cache.outputs.into_inner(), &Vec::from_hex("863ef3e1a92afbfdb97f31ad0fc7683ee943e9abcf2501590ff8f6551f47e5e5").unwrap()[..], ); } @@ -1677,15 +1677,15 @@ mod tests { let cache = cache.segwit_cache(); // Parse hex into Vec because BIP143 test vector displays forwards but our sha256d::Hash displays backwards. assert_eq!( - cache.prevouts.into_inner().as_ref(), + cache.prevouts.into_inner(), &Vec::from_hex("b0287b4a252ac05af83d2dcef00ba313af78a3e9c329afa216eb3aa2a7b4613a").unwrap()[..], ); assert_eq!( - cache.sequences.into_inner().as_ref(), + cache.sequences.into_inner(), &Vec::from_hex("18606b350cd8bf565266bc352f0caddcf01e8fa789dd8a15386327cf8cabe198").unwrap()[..], ); assert_eq!( - cache.outputs.into_inner().as_ref(), + cache.outputs.into_inner(), &Vec::from_hex("de984f44532e2173ca0d64314fcefe6d30da6f8cf27bafa706da61df8a226c83").unwrap()[..], ); } @@ -1718,15 +1718,15 @@ mod tests { let cache = cache.segwit_cache(); // Parse hex into Vec because BIP143 test vector displays forwards but our sha256d::Hash displays backwards. assert_eq!( - cache.prevouts.into_inner().as_ref(), + cache.prevouts.into_inner(), &Vec::from_hex("74afdc312af5183c4198a40ca3c1a275b485496dd3929bca388c4b5e31f7aaa0").unwrap()[..], ); assert_eq!( - cache.sequences.into_inner().as_ref(), + cache.sequences.into_inner(), &Vec::from_hex("3bb13029ce7b1f559ef5e747fcac439f1455a2ec7c5f09b72290795e70665044").unwrap()[..], ); assert_eq!( - cache.outputs.into_inner().as_ref(), + cache.outputs.into_inner(), &Vec::from_hex("bc4d309071414bed932f98832b27b4d76dad7e6c1346f487a8fdbb8eb90307cc").unwrap()[..], ); } diff --git a/bitcoin/tests/psbt.rs b/bitcoin/tests/psbt.rs index 354a6a30..0a353b4e 100644 --- a/bitcoin/tests/psbt.rs +++ b/bitcoin/tests/psbt.rs @@ -3,6 +3,7 @@ use std::collections::BTreeMap; use std::str::FromStr; +use core::convert::TryFrom; use bitcoin::bip32::{ExtendedPrivKey, ExtendedPubKey, Fingerprint, IntoDerivationPath, KeySource}; use bitcoin::blockdata::opcodes::OP_0; @@ -15,6 +16,7 @@ use bitcoin::{ absolute, Amount, Denomination, Network, OutPoint, PrivateKey, PublicKey, ScriptBuf, Sequence, Transaction, TxIn, TxOut, Witness, }; +use bitcoin::script::PushBytes; const NETWORK: Network = Network::Testnet; @@ -422,9 +424,9 @@ fn finalize_psbt(mut psbt: Psbt) -> Psbt { let sigs: Vec<_> = psbt.inputs[0].partial_sigs.values().collect(); let script_sig = script::Builder::new() .push_opcode(OP_0) // OP_CHECKMULTISIG bug pops +1 value when evaluating so push OP_0. - .push_slice(&sigs[0].to_vec()) - .push_slice(&sigs[1].to_vec()) - .push_slice(psbt.inputs[0].redeem_script.clone().unwrap().as_bytes()) + .push_slice(sigs[0].serialize()) + .push_slice(sigs[1].serialize()) + .push_slice(<&PushBytes>::try_from(psbt.inputs[0].redeem_script.as_ref().unwrap().as_bytes()).unwrap()) .into_script(); psbt.inputs[0].final_script_sig = Some(script_sig); @@ -437,7 +439,7 @@ fn finalize_psbt(mut psbt: Psbt) -> Psbt { // Input 1: SegWit UTXO let script_sig = script::Builder::new() - .push_slice(psbt.inputs[1].redeem_script.as_ref().unwrap().as_bytes()) + .push_slice(<&PushBytes>::try_from(psbt.inputs[1].redeem_script.as_ref().unwrap().as_bytes()).unwrap()) .into_script(); psbt.inputs[1].final_script_sig = Some(script_sig);