From 01df1417c751510258309bcea05dad11fd7e228e Mon Sep 17 00:00:00 2001 From: conduition Date: Sat, 23 Dec 2023 17:59:25 +0000 Subject: [PATCH] use arrayvec to represent witness programs --- bitcoin/src/address/mod.rs | 15 +++---- .../src/blockdata/script/witness_program.rs | 44 ++++++++++++------- bitcoin/src/lib.rs | 1 + bitcoin/src/psbt/mod.rs | 2 +- 4 files changed, 35 insertions(+), 27 deletions(-) diff --git a/bitcoin/src/address/mod.rs b/bitcoin/src/address/mod.rs index 79ae4c80..42eb0b0a 100644 --- a/bitcoin/src/address/mod.rs +++ b/bitcoin/src/address/mod.rs @@ -44,7 +44,7 @@ use crate::blockdata::constants::{ }; use crate::blockdata::script::witness_program::WitnessProgram; use crate::blockdata::script::witness_version::WitnessVersion; -use crate::blockdata::script::{self, PushBytesBuf, Script, ScriptBuf, ScriptHash}; +use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash}; use crate::crypto::key::{ CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey, }; @@ -520,10 +520,8 @@ impl Address { } else if script.is_witness_program() { let opcode = script.first_opcode().expect("is_witness_program guarantees len > 4"); - let buf = PushBytesBuf::try_from(script.as_bytes()[2..].to_vec()) - .expect("is_witness_program guarantees len <= 42 bytes"); let version = WitnessVersion::try_from(opcode)?; - let program = WitnessProgram::new(version, buf)?; + let program = WitnessProgram::new(version, &script.as_bytes()[2..])?; Ok(Address::from_witness_program(program, network)) } else { Err(Error::UnrecognizedScript) @@ -727,8 +725,7 @@ impl FromStr for Address { fn from_str(s: &str) -> Result, ParseError> { if let Ok((hrp, witness_version, data)) = bech32::segwit::decode(s) { let version = WitnessVersion::try_from(witness_version)?; - let buf = PushBytesBuf::try_from(data).expect("bech32 guarantees valid program length"); - let program = WitnessProgram::new(version, buf) + let program = WitnessProgram::new(version, &data) .expect("bech32 guarantees valid program length for witness"); let hrp = KnownHrp::from_hrp(hrp)?; @@ -924,10 +921,10 @@ mod tests { #[test] fn test_non_existent_segwit_version() { // 40-byte program - let program = PushBytesBuf::from(hex!( + let program = hex!( "654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4" - )); - let program = WitnessProgram::new(WitnessVersion::V13, program).expect("valid program"); + ); + let program = WitnessProgram::new(WitnessVersion::V13, &program).expect("valid program"); let addr = Address::from_witness_program(program, Bitcoin); roundtrips(&addr, Bitcoin); diff --git a/bitcoin/src/blockdata/script/witness_program.rs b/bitcoin/src/blockdata/script/witness_program.rs index d2b67024..fea2d6e8 100644 --- a/bitcoin/src/blockdata/script/witness_program.rs +++ b/bitcoin/src/blockdata/script/witness_program.rs @@ -10,13 +10,20 @@ use core::fmt; use hashes::Hash as _; +use internals::array_vec::ArrayVec; use secp256k1::{Secp256k1, Verification}; use crate::blockdata::script::witness_version::WitnessVersion; -use crate::blockdata::script::{PushBytes, PushBytesBuf, PushBytesErrorReport, Script}; +use crate::blockdata::script::{PushBytes, Script}; use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey}; use crate::taproot::TapNodeHash; +/// The minimum byte size of a segregated witness program. +pub const MIN_SIZE: usize = 2; + +/// The maximum byte size of a segregated witness program. +pub const MAX_SIZE: usize = 40; + /// The segregated witness program. /// /// The segregated witness program is technically only the program bytes _excluding_ the witness @@ -27,43 +34,41 @@ pub struct WitnessProgram { /// The segwit version associated with this witness program. version: WitnessVersion, /// The witness program (between 2 and 40 bytes). - program: PushBytesBuf, + program: ArrayVec, } impl WitnessProgram { - /// Creates a new witness program. - pub fn new

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

>::Error: PushBytesErrorReport, - { + /// Creates a new witness program, copying the content from the given byte slice. + pub fn new(version: WitnessVersion, bytes: &[u8]) -> Result { use Error::*; - let program = program.try_into().map_err(|error| InvalidLength(error.input_len()))?; - if program.len() < 2 || program.len() > 40 { - return Err(InvalidLength(program.len())); + let program_len = bytes.len(); + if program_len < MIN_SIZE || program_len > MAX_SIZE { + return Err(InvalidLength(program_len)); } // Specific segwit v0 check. These addresses can never spend funds sent to them. - if version == WitnessVersion::V0 && (program.len() != 20 && program.len() != 32) { - return Err(InvalidSegwitV0Length(program.len())); + if version == WitnessVersion::V0 && (program_len != 20 && program_len != 32) { + return Err(InvalidSegwitV0Length(program_len)); } + + let program = ArrayVec::from_slice(bytes); Ok(WitnessProgram { version, program }) } /// Creates a [`WitnessProgram`] from a 20 byte pubkey hash. fn new_p2wpkh(program: [u8; 20]) -> Self { - WitnessProgram { version: WitnessVersion::V0, program: program.into() } + WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) } } /// Creates a [`WitnessProgram`] from a 32 byte script hash. fn new_p2wsh(program: [u8; 32]) -> Self { - WitnessProgram { version: WitnessVersion::V0, program: program.into() } + WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) } } /// Creates a [`WitnessProgram`] from a 32 byte serialize taproot xonly pubkey. fn new_p2tr(program: [u8; 32]) -> Self { - WitnessProgram { version: WitnessVersion::V1, program: program.into() } + WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&program) } } /// Creates a [`WitnessProgram`] from `pk` for a P2WPKH output. @@ -99,7 +104,12 @@ impl WitnessProgram { pub fn version(&self) -> WitnessVersion { self.version } /// Returns the witness program. - pub fn program(&self) -> &PushBytes { &self.program } + pub fn program(&self) -> &PushBytes { + self.program + .as_slice() + .try_into() + .expect("witness programs are always smaller than max size of PushBytes") + } /// Returns true if this witness program is for a P2WPKH output. pub fn is_p2wpkh(&self) -> bool { diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index 51474e2a..fca86019 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -39,6 +39,7 @@ // Exclude clippy lints we don't think are valuable #![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134 #![allow(clippy::uninhabited_references)] // falsely claims that 100% safe code is UB +#![allow(clippy::manual_range_contains)] // more readable than clippy's format // Disable 16-bit support at least for now as we can't guarantee it yet. #[cfg(target_pointer_width = "16")] diff --git a/bitcoin/src/psbt/mod.rs b/bitcoin/src/psbt/mod.rs index 4581e770..b59d55f6 100644 --- a/bitcoin/src/psbt/mod.rs +++ b/bitcoin/src/psbt/mod.rs @@ -2087,7 +2087,7 @@ mod tests { psbt.inputs[0].bip32_derivation = map; // Second input is unspendable by us e.g., from another wallet that supports future upgrades. - let unknown_prog = WitnessProgram::new(WitnessVersion::V4, vec![0xaa; 34]).unwrap(); + let unknown_prog = WitnessProgram::new(WitnessVersion::V4, &[0xaa; 34]).unwrap(); let txout_unknown_future = TxOut { value: Amount::from_sat(10), script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),