bitcoin/blockdata/script/
witness_program.rs

1//! The segregated witness program as defined by [BIP141].
2//!
3//! > A scriptPubKey (or redeemScript as defined in BIP16/P2SH) that consists of a 1-byte push
4//! > opcode (for 0 to 16) followed by a data push between 2 and 40 bytes gets a new special
5//! > meaning. The value of the first push is called the "version byte". The following byte
6//! > vector pushed is called the "witness program".
7//!
8//! [BIP141]: <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki>
9
10use core::fmt;
11
12use hashes::Hash as _;
13use internals::array_vec::ArrayVec;
14use secp256k1::{Secp256k1, Verification};
15
16use crate::blockdata::script::witness_version::WitnessVersion;
17use crate::blockdata::script::{PushBytes, Script};
18use crate::crypto::key::{CompressedPublicKey, TapTweak, TweakedPublicKey, UntweakedPublicKey};
19use crate::taproot::TapNodeHash;
20
21/// The minimum byte size of a segregated witness program.
22pub const MIN_SIZE: usize = 2;
23
24/// The maximum byte size of a segregated witness program.
25pub const MAX_SIZE: usize = 40;
26
27/// The segregated witness program.
28///
29/// The segregated witness program is technically only the program bytes _excluding_ the witness
30/// version, however we maintain length invariants on the `program` that are governed by the version
31/// number, therefore we carry the version number around along with the program bytes.
32#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
33pub struct WitnessProgram {
34    /// The segwit version associated with this witness program.
35    version: WitnessVersion,
36    /// The witness program (between 2 and 40 bytes).
37    program: ArrayVec<u8, MAX_SIZE>,
38}
39
40impl WitnessProgram {
41    /// Creates a new witness program, copying the content from the given byte slice.
42    pub fn new(version: WitnessVersion, bytes: &[u8]) -> Result<Self, Error> {
43        use Error::*;
44
45        let program_len = bytes.len();
46        if program_len < MIN_SIZE || program_len > MAX_SIZE {
47            return Err(InvalidLength(program_len));
48        }
49
50        // Specific segwit v0 check. These addresses can never spend funds sent to them.
51        if version == WitnessVersion::V0 && (program_len != 20 && program_len != 32) {
52            return Err(InvalidSegwitV0Length(program_len));
53        }
54
55        let program = ArrayVec::from_slice(bytes);
56        Ok(WitnessProgram { version, program })
57    }
58
59    /// Creates a [`WitnessProgram`] from a 20 byte pubkey hash.
60    fn new_p2wpkh(program: [u8; 20]) -> Self {
61        WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) }
62    }
63
64    /// Creates a [`WitnessProgram`] from a 32 byte script hash.
65    fn new_p2wsh(program: [u8; 32]) -> Self {
66        WitnessProgram { version: WitnessVersion::V0, program: ArrayVec::from_slice(&program) }
67    }
68
69    /// Creates a [`WitnessProgram`] from a 32 byte serialize taproot xonly pubkey.
70    fn new_p2tr(program: [u8; 32]) -> Self {
71        WitnessProgram { version: WitnessVersion::V1, program: ArrayVec::from_slice(&program) }
72    }
73
74    /// Creates a [`WitnessProgram`] from `pk` for a P2WPKH output.
75    pub fn p2wpkh(pk: &CompressedPublicKey) -> Self {
76        let hash = pk.wpubkey_hash();
77        WitnessProgram::new_p2wpkh(hash.to_byte_array())
78    }
79
80    /// Creates a [`WitnessProgram`] from `script` for a P2WSH output.
81    pub fn p2wsh(script: &Script) -> Self {
82        let hash = script.wscript_hash();
83        WitnessProgram::new_p2wsh(hash.to_byte_array())
84    }
85
86    /// Creates a pay to taproot address from an untweaked key.
87    pub fn p2tr<C: Verification>(
88        secp: &Secp256k1<C>,
89        internal_key: UntweakedPublicKey,
90        merkle_root: Option<TapNodeHash>,
91    ) -> Self {
92        let (output_key, _parity) = internal_key.tap_tweak(secp, merkle_root);
93        let pubkey = output_key.to_inner().serialize();
94        WitnessProgram::new_p2tr(pubkey)
95    }
96
97    /// Creates a pay to taproot address from a pre-tweaked output key.
98    pub fn p2tr_tweaked(output_key: TweakedPublicKey) -> Self {
99        let pubkey = output_key.to_inner().serialize();
100        WitnessProgram::new_p2tr(pubkey)
101    }
102
103    /// Returns the witness program version.
104    pub fn version(&self) -> WitnessVersion { self.version }
105
106    /// Returns the witness program.
107    pub fn program(&self) -> &PushBytes {
108        self.program
109            .as_slice()
110            .try_into()
111            .expect("witness programs are always smaller than max size of PushBytes")
112    }
113
114    /// Returns true if this witness program is for a P2WPKH output.
115    pub fn is_p2wpkh(&self) -> bool {
116        self.version == WitnessVersion::V0 && self.program.len() == 20
117    }
118
119    /// Returns true if this witness program is for a P2WPSH output.
120    pub fn is_p2wsh(&self) -> bool {
121        self.version == WitnessVersion::V0 && self.program.len() == 32
122    }
123
124    /// Returns true if this witness program is for a P2TR output.
125    pub fn is_p2tr(&self) -> bool { self.version == WitnessVersion::V1 && self.program.len() == 32 }
126}
127
128/// Witness program error.
129#[derive(Clone, Debug, PartialEq, Eq)]
130#[non_exhaustive]
131pub enum Error {
132    /// The witness program must be between 2 and 40 bytes in length.
133    InvalidLength(usize),
134    /// A v0 witness program must be either of length 20 or 32.
135    InvalidSegwitV0Length(usize),
136}
137
138internals::impl_from_infallible!(Error);
139
140impl fmt::Display for Error {
141    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
142        use Error::*;
143
144        match *self {
145            InvalidLength(len) =>
146                write!(f, "witness program must be between 2 and 40 bytes: length={}", len),
147            InvalidSegwitV0Length(len) =>
148                write!(f, "a v0 witness program must be either 20 or 32 bytes: length={}", len),
149        }
150    }
151}
152
153#[cfg(feature = "std")]
154impl std::error::Error for Error {
155    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
156        use Error::*;
157
158        match *self {
159            InvalidLength(_) | InvalidSegwitV0Length(_) => None,
160        }
161    }
162}