Merge rust-bitcoin/rust-bitcoin#2310: use arrayvec to represent witness programs internally

01df1417c7 use arrayvec to represent witness programs (conduition)

Pull request description:

  Fixes https://github.com/rust-bitcoin/rust-bitcoin/issues/2261

  Introduces a new constructor, `WitnessProgram::from_bytes`, which creates a witness program by copying the program content from a byte slice.

ACKs for top commit:
  Kixunil:
    ACK 01df1417c7
  apoelstra:
    ACK 01df1417c7

Tree-SHA512: 73b8f2785674cd99c3f5dfe0e2180ed256942a0c29bcb1d357e0bd84fddee5e62f3f230c6cd55a37322bc3a6011467e9b7dcf24d903b20f35c095a1a1f9a29ce
This commit is contained in:
Andrew Poelstra 2024-01-03 19:33:49 +00:00
commit 5108f6ecdd
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
4 changed files with 35 additions and 27 deletions

View File

@ -44,7 +44,7 @@ use crate::blockdata::constants::{
}; };
use crate::blockdata::script::witness_program::WitnessProgram; use crate::blockdata::script::witness_program::WitnessProgram;
use crate::blockdata::script::witness_version::WitnessVersion; 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::{ use crate::crypto::key::{
CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey, CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey,
}; };
@ -520,10 +520,8 @@ impl Address {
} else if script.is_witness_program() { } else if script.is_witness_program() {
let opcode = script.first_opcode().expect("is_witness_program guarantees len > 4"); 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 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)) Ok(Address::from_witness_program(program, network))
} else { } else {
Err(Error::UnrecognizedScript) Err(Error::UnrecognizedScript)
@ -727,8 +725,7 @@ impl FromStr for Address<NetworkUnchecked> {
fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> { fn from_str(s: &str) -> Result<Address<NetworkUnchecked>, ParseError> {
if let Ok((hrp, witness_version, data)) = bech32::segwit::decode(s) { if let Ok((hrp, witness_version, data)) = bech32::segwit::decode(s) {
let version = WitnessVersion::try_from(witness_version)?; let version = WitnessVersion::try_from(witness_version)?;
let buf = PushBytesBuf::try_from(data).expect("bech32 guarantees valid program length"); let program = WitnessProgram::new(version, &data)
let program = WitnessProgram::new(version, buf)
.expect("bech32 guarantees valid program length for witness"); .expect("bech32 guarantees valid program length for witness");
let hrp = KnownHrp::from_hrp(hrp)?; let hrp = KnownHrp::from_hrp(hrp)?;
@ -924,10 +921,10 @@ mod tests {
#[test] #[test]
fn test_non_existent_segwit_version() { fn test_non_existent_segwit_version() {
// 40-byte program // 40-byte program
let program = PushBytesBuf::from(hex!( let program = hex!(
"654f6ea368e0acdfd92976b7c2103a1b26313f430654f6ea368e0acdfd92976b7c2103a1b26313f4" "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); let addr = Address::from_witness_program(program, Bitcoin);
roundtrips(&addr, Bitcoin); roundtrips(&addr, Bitcoin);

View File

@ -10,13 +10,20 @@
use core::fmt; use core::fmt;
use hashes::Hash as _; use hashes::Hash as _;
use internals::array_vec::ArrayVec;
use secp256k1::{Secp256k1, Verification}; use secp256k1::{Secp256k1, Verification};
use crate::blockdata::script::witness_version::WitnessVersion; 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::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
use crate::taproot::TapNodeHash; 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.
/// ///
/// The segregated witness program is technically only the program bytes _excluding_ the witness /// 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. /// The segwit version associated with this witness program.
version: WitnessVersion, version: WitnessVersion,
/// The witness program (between 2 and 40 bytes). /// The witness program (between 2 and 40 bytes).
program: PushBytesBuf, program: ArrayVec<u8, MAX_SIZE>,
} }
impl WitnessProgram { impl WitnessProgram {
/// Creates a new witness program. /// Creates a new witness program, copying the content from the given byte slice.
pub fn new<P>(version: WitnessVersion, program: P) -> Result<Self, Error> pub fn new(version: WitnessVersion, bytes: &[u8]) -> Result<Self, Error> {
where
P: TryInto<PushBytesBuf>,
<P as TryInto<PushBytesBuf>>::Error: PushBytesErrorReport,
{
use Error::*; use Error::*;
let program = program.try_into().map_err(|error| InvalidLength(error.input_len()))?; let program_len = bytes.len();
if program.len() < 2 || program.len() > 40 { if program_len < MIN_SIZE || program_len > MAX_SIZE {
return Err(InvalidLength(program.len())); return Err(InvalidLength(program_len));
} }
// Specific segwit v0 check. These addresses can never spend funds sent to them. // Specific segwit v0 check. These addresses can never spend funds sent to them.
if version == WitnessVersion::V0 && (program.len() != 20 && program.len() != 32) { if version == WitnessVersion::V0 && (program_len != 20 && program_len != 32) {
return Err(InvalidSegwitV0Length(program.len())); return Err(InvalidSegwitV0Length(program_len));
} }
let program = ArrayVec::from_slice(bytes);
Ok(WitnessProgram { version, program }) Ok(WitnessProgram { version, program })
} }
/// Creates a [`WitnessProgram`] from a 20 byte pubkey hash. /// Creates a [`WitnessProgram`] from a 20 byte pubkey hash.
fn new_p2wpkh(program: [u8; 20]) -> Self { 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. /// Creates a [`WitnessProgram`] from a 32 byte script hash.
fn new_p2wsh(program: [u8; 32]) -> Self { 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. /// Creates a [`WitnessProgram`] from a 32 byte serialize taproot xonly pubkey.
fn new_p2tr(program: [u8; 32]) -> Self { 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. /// Creates a [`WitnessProgram`] from `pk` for a P2WPKH output.
@ -99,7 +104,12 @@ impl WitnessProgram {
pub fn version(&self) -> WitnessVersion { self.version } pub fn version(&self) -> WitnessVersion { self.version }
/// Returns the witness program. /// 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. /// Returns true if this witness program is for a P2WPKH output.
pub fn is_p2wpkh(&self) -> bool { pub fn is_p2wpkh(&self) -> bool {

View File

@ -39,6 +39,7 @@
// Exclude clippy lints we don't think are valuable // 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::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::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. // Disable 16-bit support at least for now as we can't guarantee it yet.
#[cfg(target_pointer_width = "16")] #[cfg(target_pointer_width = "16")]

View File

@ -2087,7 +2087,7 @@ mod tests {
psbt.inputs[0].bip32_derivation = map; psbt.inputs[0].bip32_derivation = map;
// Second input is unspendable by us e.g., from another wallet that supports future upgrades. // 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 { let txout_unknown_future = TxOut {
value: Amount::from_sat(10), value: Amount::from_sat(10),
script_pubkey: ScriptBuf::new_witness_program(&unknown_prog), script_pubkey: ScriptBuf::new_witness_program(&unknown_prog),