bitcoin_scripts/
convert.rs

1// BP foundation libraries Bitcoin crates implementing the foundations of
2// Bitcoin protocol by LNP/BP Association (https://lnp-bp.org)
3//
4// Written in 2020-2022 by
5//     Dr. Maxim Orlovsky <orlovsky@lnp-bp.org>
6//
7// This software is distributed without any warranty.
8//
9// You should have received a copy of the Apache-2.0 License
10// along with this software.
11// If not, see <https://opensource.org/licenses/Apache-2.0>.
12
13//! Helper traits and supplementary types for converting different types of
14//! bitcoin_scripts and keys into each other.
15
16use amplify::Wrapper;
17use bitcoin::blockdata::script;
18use bitcoin::blockdata::witness::Witness;
19use bitcoin::{secp256k1, Script};
20#[cfg(feature = "miniscript")]
21use miniscript::descriptor::DescriptorType;
22#[cfg(feature = "miniscript")]
23use miniscript::{Descriptor, MiniscriptKey, ToPublicKey};
24
25use crate::{LockScript, PubkeyScript, RedeemScript, ScriptSet, SigScript, WitnessScript};
26
27/// Descriptor category specifies way how the `scriptPubkey` is structured
28#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Display, Hash)]
29#[repr(u8)]
30pub enum ConvertInfo {
31    /// Bare descriptors: `pk` and bare bitcoin_scripts, including `OP_RETURN`s.
32    ///
33    /// The script or public key gets right into `scriptPubkey`, i.e. as
34    /// **P2PK** (for a public key) or as custom script (mostly used for
35    /// `OP_RETURN`)
36    #[display("bare")]
37    Bare,
38
39    /// Hash-based descriptors: `pkh` for public key hashes and BIP-16 `sh` for
40    /// **P2SH** bitcoin_scripts.
41    ///
42    /// We hash public key or script and use non-SegWit `scriptPubkey`
43    /// encoding, i.e. **P2PKH** or **P2SH** with corresponding non-segwit
44    /// transaction input `scriptSig` containing copy of [`crate::LockScript`]
45    /// in `redeemScript` field
46    #[display("hashed")]
47    Hashed,
48
49    /// SegWit descriptors for legacy wallets defined in BIP 141 as P2SH nested
50    /// types <https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki#P2WPKH_nested_in_BIP16_P2SH>:
51    /// `sh(wpkh)` and `sh(wsh)`
52    ///
53    /// Compatibility variant for SegWit outputs when the SegWit version and
54    /// program are encoded as [`crate::RedeemScript`] in `scriptSig`
55    /// transaction input field, while the original public key or
56    /// [`crate::WitnessScript`] are stored in `witness`. `scriptPubkey`
57    /// contains a normal **P2SH** composed agains the `redeemScript` from
58    /// `scriptSig` (**P2SH-P2WPKH** and **P2SH-P2WSH** variants).
59    ///
60    /// This type works with only with witness version v0, i.e. not applicable
61    /// for Taproot.
62    #[display("nested")]
63    NestedV0,
64
65    /// Native SegWit descriptors: `wpkh` for public keys and `wsh` for
66    /// bitcoin_scripts
67    ///
68    /// We produce either **P2WPKH** or **P2WSH** output and use witness field
69    /// in transaction input to store the original [`crate::LockScript`] or the
70    /// public key
71    #[display("segwit")]
72    SegWitV0,
73
74    /// Native Taproot descriptors: `taproot`
75    #[display("taproot")]
76    Taproot,
77}
78
79#[cfg(feature = "miniscript")]
80impl<Pk> From<&Descriptor<Pk>> for ConvertInfo
81where
82    Pk: MiniscriptKey + ToPublicKey,
83{
84    fn from(descriptor: &Descriptor<Pk>) -> Self {
85        match (descriptor.desc_type(), descriptor) {
86            (DescriptorType::Bare, _) => ConvertInfo::Bare,
87            (DescriptorType::Sh, _)
88            | (DescriptorType::ShSortedMulti, _)
89            | (DescriptorType::Pkh, _) => ConvertInfo::Hashed,
90            (DescriptorType::Wpkh, _)
91            | (DescriptorType::WshSortedMulti, _)
92            | (DescriptorType::Wsh, _) => ConvertInfo::SegWitV0,
93            (DescriptorType::ShWsh, _)
94            | (DescriptorType::ShWpkh, _)
95            | (DescriptorType::ShWshSortedMulti, _) => ConvertInfo::NestedV0,
96            (_, Descriptor::Tr(_)) => ConvertInfo::Taproot,
97            _ => unreachable!("taproot descriptor type for non-taproot descriptor"),
98        }
99    }
100}
101
102#[cfg(feature = "miniscript")]
103impl<Pk> From<Descriptor<Pk>> for ConvertInfo
104where
105    Pk: MiniscriptKey + ToPublicKey,
106{
107    #[inline]
108    fn from(descriptor: Descriptor<Pk>) -> Self { Self::from(&descriptor) }
109}
110
111impl ConvertInfo {
112    /// Detects whether conversion is a non-nested segwit
113    #[inline]
114    pub fn is_segwit(self) -> bool { !matches!(self, ConvertInfo::Bare | ConvertInfo::Hashed) }
115
116    /// Detects whether conversion is a taproot conversion
117    #[inline]
118    pub fn is_taproot(self) -> bool { !matches!(self, ConvertInfo::Taproot { .. }) }
119}
120
121/// Errors converting to [`LockScript`] type returned by
122/// [`ToLockScript::to_lock_script`].
123#[derive(
124    Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Hash, Debug, Display, Error
125)]
126#[display(doc_comments)]
127pub enum LockScriptError {
128    /// attempt to generate segwit script with uncompressed pubkey {0}
129    UncompressedPubkeyInWitness(bitcoin::PublicKey),
130
131    /// taproot does not have a lock-script representation
132    Taproot,
133}
134
135/// Conversion to [`LockScript`], which later may be used for creating different
136/// end-point bitcoin_scripts, like [`PubkeyScript`], [`SigScript`], [`Witness`]
137/// etc.
138pub trait ToLockScript {
139    /// Converts data type to [`LockScript`]. Errors on uncompressed public keys
140    /// in segwit context and use of taproot, which does not have a
141    /// [`LockScript`] representation (see [`LockScriptError`]).
142    fn to_lock_script(&self, strategy: ConvertInfo) -> Result<LockScript, LockScriptError>;
143}
144
145/// Conversion for data types (public keys, different types of script) into
146/// a `scriptPubkey` (using [`PubkeyScript`] type) using particular conversion
147/// [`ConvertInfo`]
148pub trait ToPubkeyScript {
149    /// Converts data type to [`PubkeyScript`]. Returns `None` if the conversion
150    /// is applied to uncompressed public key in segwit context and for taproot
151    /// context, where different types of bitcoin_scripts and public keys are
152    /// required.
153    fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript>;
154}
155
156/// Script set generation from public keys or a given [`LockScript`] (with
157/// [`crate::TapScript`] support planned for the future).
158pub trait ToScripts
159where
160    Self: ToPubkeyScript,
161{
162    /// Construct all transaction script-produced data; fail by returning `None`
163    /// on non-compressed public keys in segwit context
164    fn to_scripts(&self, strategy: ConvertInfo) -> Option<ScriptSet> {
165        Some(ScriptSet {
166            pubkey_script: self.to_pubkey_script(strategy)?,
167            sig_script: self.to_sig_script(strategy)?,
168            witness: self.to_witness(strategy),
169        })
170    }
171
172    /// Construct `scriptSig`; fail by returning `None` on non-compressed public
173    /// keys in segwit context
174    fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript>;
175
176    /// Construct `witness` for segwit contexts only; return `None` on other
177    /// contexts
178    fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness>;
179}
180
181impl ToPubkeyScript for WitnessScript {
182    /// Generates `scriptPubkey` for segwit non-taproot contexts. Fails by
183    /// returning `None` for the following contexts:
184    /// - [`ConvertInfo::Bare`] since no `witness` structure will be present in
185    ///   the output transaction;
186    /// - [`ConvertInfo::Hashed`] since no `witness` structure will be present
187    ///   in the output transaction
188    /// - [`ConvertInfo::Taproot`] since taproot does not have an associated
189    ///   [`WitnessScript`].
190    fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
191        match strategy {
192            ConvertInfo::Bare => None,
193            ConvertInfo::Hashed => None,
194            ConvertInfo::NestedV0 => Some(RedeemScript::from(self.clone()).to_p2sh()),
195            ConvertInfo::SegWitV0 => Some(Script::new_v0_p2wsh(&self.script_hash()).into()),
196            ConvertInfo::Taproot => None,
197        }
198    }
199}
200
201impl ToPubkeyScript for RedeemScript {
202    /// Generates `scriptPubkey` matching the given [`RedeemScript`]. Fails by
203    /// returning `None` for the following contexts, where `redeemScript` is not
204    /// present:
205    /// - [`ConvertInfo::Bare`];
206    /// - [`ConvertInfo::SegWitV0`];
207    /// - [`ConvertInfo::Taproot`].
208    fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
209        match strategy {
210            ConvertInfo::Bare => None,
211            ConvertInfo::Hashed => Some(self.to_p2sh()),
212            ConvertInfo::NestedV0 => Some(self.to_p2sh()),
213            ConvertInfo::SegWitV0 => None,
214            ConvertInfo::Taproot => None,
215        }
216    }
217}
218
219impl ToPubkeyScript for LockScript {
220    /// Never returns [`None`]
221    fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
222        Some(match strategy {
223            ConvertInfo::Bare => self.to_inner().into(),
224            ConvertInfo::Hashed => Script::new_p2sh(&self.script_hash()).into(),
225            ConvertInfo::SegWitV0 => Script::new_v0_p2wsh(&self.wscript_hash()).into(),
226            ConvertInfo::NestedV0 => WitnessScript::from(self.clone()).to_p2sh_wsh(),
227            ConvertInfo::Taproot => return None,
228        })
229    }
230}
231
232impl ToScripts for LockScript {
233    /// Never returns [`None`]
234    fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript> {
235        Some(match strategy {
236            // sigScript must contain just a plain signatures, which will be
237            // added later
238            ConvertInfo::Bare => SigScript::default(),
239            ConvertInfo::Hashed => script::Builder::new()
240                .push_slice(WitnessScript::from(self.clone()).as_bytes())
241                .into_script()
242                .into(),
243            ConvertInfo::NestedV0 => {
244                // Here we support only V0 version, since V1 version can't
245                // be generated from `LockScript` and will require
246                // `TapScript` source
247                RedeemScript::from(WitnessScript::from(self.clone())).into()
248            }
249            // For any segwit version the scriptSig must be empty (with the
250            // exception to the case of P2SH-embedded outputs, which is already
251            // covered above
252            _ => SigScript::default(),
253        })
254    }
255
256    fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness> {
257        match strategy {
258            ConvertInfo::Bare | ConvertInfo::Hashed => None,
259            ConvertInfo::SegWitV0 | ConvertInfo::NestedV0 => {
260                let witness_script = WitnessScript::from(self.clone());
261                Some(Witness::from_vec(vec![witness_script.to_bytes()]))
262            }
263            ConvertInfo::Taproot => None,
264        }
265    }
266}
267
268impl ToPubkeyScript for bitcoin::PublicKey {
269    fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
270        match strategy {
271            ConvertInfo::Bare => Some(Script::new_p2pk(self).into()),
272            ConvertInfo::Hashed => Some(Script::new_p2pkh(&self.pubkey_hash()).into()),
273            // Uncompressed key in SegWit context
274            ConvertInfo::NestedV0 | ConvertInfo::SegWitV0 if !self.compressed => None,
275            ConvertInfo::NestedV0 | ConvertInfo::SegWitV0 => self.inner.to_pubkey_script(strategy),
276            // Bitcoin public key can't be used in Taproot context
277            ConvertInfo::Taproot => None,
278        }
279    }
280}
281
282impl ToScripts for bitcoin::PublicKey {
283    fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript> {
284        Some(match strategy {
285            // scriptSig must contain just a plain signatures, which will be
286            // added later
287            ConvertInfo::Bare => SigScript::default(),
288            ConvertInfo::Hashed => script::Builder::new()
289                .push_slice(&self.to_bytes())
290                .into_script()
291                .into(),
292            ConvertInfo::NestedV0 => {
293                let redeem_script =
294                    LockScript::from(self.to_pubkey_script(ConvertInfo::SegWitV0)?.into_inner());
295                script::Builder::new()
296                    .push_slice(redeem_script.as_bytes())
297                    .into_script()
298                    .into()
299            }
300            // For any segwit version the scriptSig must be empty (with the
301            // exception to the case of P2SH-embedded outputs, which is already
302            // covered above
303            _ => SigScript::default(),
304        })
305    }
306
307    fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness> {
308        match strategy {
309            ConvertInfo::Bare | ConvertInfo::Hashed => None,
310            ConvertInfo::SegWitV0 | ConvertInfo::NestedV0 => {
311                Some(Witness::from_vec(vec![self.to_bytes()]))
312            }
313            // Bitcoin public key can't be used in Taproot context
314            ConvertInfo::Taproot => None,
315        }
316    }
317}
318
319impl ToPubkeyScript for secp256k1::PublicKey {
320    /// Never returns [`None`]
321    fn to_pubkey_script(&self, strategy: ConvertInfo) -> Option<PubkeyScript> {
322        let pk = bitcoin::PublicKey::new(*self);
323        Some(
324            match strategy {
325                ConvertInfo::Bare => Script::new_p2pk(&pk),
326                ConvertInfo::Hashed => Script::new_p2pkh(&pk.pubkey_hash()),
327                ConvertInfo::SegWitV0 => Script::new_v0_p2wpkh(&pk.wpubkey_hash()?),
328                ConvertInfo::NestedV0 => {
329                    let pubkey_script = Script::new_p2pkh(&pk.pubkey_hash());
330                    let redeem_script = RedeemScript::from_inner(pubkey_script);
331                    Script::new_p2sh(&redeem_script.script_hash())
332                }
333                ConvertInfo::Taproot => return None,
334            }
335            .into(),
336        )
337    }
338}
339
340impl ToScripts for secp256k1::PublicKey {
341    #[inline]
342    fn to_sig_script(&self, strategy: ConvertInfo) -> Option<SigScript> {
343        bitcoin::PublicKey::new(*self).to_sig_script(strategy)
344    }
345
346    #[inline]
347    fn to_witness(&self, strategy: ConvertInfo) -> Option<Witness> {
348        bitcoin::PublicKey::new(*self).to_witness(strategy)
349    }
350}
351
352/// Shorthand methods for converting into different forms of [`PubkeyScript`]
353pub trait ToP2pkh {
354    /// Convert to P2PKH `scriptPubkey`
355    fn to_p2pkh(&self) -> Option<PubkeyScript>;
356    /// Convert to segwit native P2WPKH `scriptPubkey`
357    fn to_p2wpkh(&self) -> Option<PubkeyScript>;
358    /// Convert to segwit legacy P2WPKH-in-P2SH `scriptPubkey`
359    fn to_p2sh_wpkh(&self) -> Option<PubkeyScript>;
360}
361
362#[cfg(feature = "miniscript")]
363impl<T> ToP2pkh for T
364where
365    T: ToPublicKey,
366{
367    fn to_p2pkh(&self) -> Option<PubkeyScript> {
368        self.to_public_key().to_pubkey_script(ConvertInfo::Hashed)
369    }
370
371    fn to_p2wpkh(&self) -> Option<PubkeyScript> {
372        self.to_public_key().to_pubkey_script(ConvertInfo::SegWitV0)
373    }
374
375    fn to_p2sh_wpkh(&self) -> Option<PubkeyScript> {
376        self.to_public_key().to_pubkey_script(ConvertInfo::NestedV0)
377    }
378}