elements_miniscript/descriptor/csfs_cov/
cov.rs

1// Miniscript
2// Written in 2021 by
3//     Andrew Poelstra <apoelstra@wpsoftware.net>
4//     Sanket Kanjalkar <sanket1729@gmail.com>
5// To the extent possible under law, the author(s) have dedicated all
6// copyright and related and neighboring rights to this software to
7// the public domain worldwide. This software is distributed without
8// any warranty.
9//
10// You should have received a copy of the CC0 Public Domain Dedication
11// along with this software.
12// If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
13//
14
15//! Covenant Descriptor support
16//!
17//! Traits and implementations for Covenant descriptors
18//! A cov() descriptor puts a context items required for
19//! sighash onto the top of the stack in the required order
20//!
21//! ** WORKS only for Segwit sighash
22//! A new transaction digest algorithm is defined, but only applicable to sigops in version 0 witness program:
23//! Text from BIP 143:
24//!  Double SHA256 of the serialization of:
25//! 1. nVersion of the transaction (4-byte little endian)
26//! 2. hashPrevouts (32-byte hash)
27//! 3. hashSequence (32-byte hash)
28//! 3. ELEMENTS EXTRA hashIssuances (32-byte hash)
29//! 4. outpoint (32-byte hash + 4-byte little endian)
30//! 5. scriptCode of the input (serialized as scripts inside CTxOuts)
31//! 6. value of the output spent by this input (8-byte little endian)
32//! 7. nSequence of the input (4-byte little endian)
33//! 8. hashOutputs (32-byte hash)
34//! 9. nLocktime of the transaction (4-byte little endian)
35//! 10. sighash type of the signature (4-byte little endian)
36//!
37//! The miniscript fragments lookups all the relevant fragment
38//! from the stack using using OP_PICK(specifying the relative)
39//! position using OP_DEPTH.
40//! After all the miniscript fragments are evaluated, we concat
41//! all the items using OP_CAT to obtain a Sighash on which we
42//! which we verify using CHECKSIGFROMSTACK
43use std::fmt;
44
45use bitcoin;
46use elements::encode::{serialize, Encodable};
47use elements::hashes::{sha256d, Hash};
48use elements::{self, script, secp256k1_zkp, Script};
49
50use super::super::ELMTS_STR;
51use super::{CovError, CovOperations};
52use crate::descriptor::checksum::{self, verify_checksum};
53use crate::expression::{self, FromTree};
54use crate::extensions::ParseableExt;
55use crate::miniscript::lex::{lex, Token as Tk, TokenIter};
56use crate::miniscript::limits::{
57    MAX_OPS_PER_SCRIPT, MAX_SCRIPT_SIZE, MAX_STANDARD_P2WSH_SCRIPT_SIZE,
58};
59use crate::miniscript::{decode, types};
60use crate::util::varint_len;
61use crate::{
62    Error, ExtTranslator, Extension, ForEachKey, Miniscript, MiniscriptKey, Satisfier,
63    ScriptContext, Segwitv0, ToPublicKey, TranslateExt, TranslatePk, Translator,
64};
65
66// A simple utility function to serialize an array
67// of elements and compute double sha2 on it
68fn hash256_arr<T: Encodable>(sl: &[T]) -> sha256d::Hash {
69    let mut enc = sha256d::Hash::engine();
70    for elem in sl {
71        elem.consensus_encode(&mut enc).unwrap();
72    }
73    sha256d::Hash::from_engine(enc)
74}
75pub(crate) const COV_SCRIPT_SIZE: usize = 120;
76pub(crate) const COV_SCRIPT_OPCODE_COST: usize = 74;
77/// The covenant descriptor
78#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
79pub struct LegacyCSFSCov<Pk: MiniscriptKey, Ext: Extension> {
80    /// the pk constraining the Covenant
81    /// The key over which we want CHECKSIGFROMSTACK
82    pub(crate) pk: Pk,
83    /// the underlying Miniscript
84    /// Must be under segwit context
85    // All known extensions are enabled in covenant descriptor
86    pub(crate) ms: Miniscript<Pk, Segwitv0, Ext>,
87}
88
89impl<Pk: MiniscriptKey, Ext: Extension> LegacyCSFSCov<Pk, Ext> {
90    /// Get the pk from covenant
91    pub fn pk(&self) -> &Pk {
92        &self.pk
93    }
94
95    /// Get a reference to Miniscript inside covenant
96    pub fn to_ms(&self) -> &Miniscript<Pk, Segwitv0, Ext> {
97        &self.ms
98    }
99
100    /// Consume self and return inner miniscript
101    pub fn into_ms(self) -> Miniscript<Pk, Segwitv0, Ext> {
102        self.ms
103    }
104
105    /// Create a new Self from components
106    pub fn new(pk: Pk, ms: Miniscript<Pk, Segwitv0, Ext>) -> Result<Self, Error> {
107        // // 1) Check the 201 opcode count here
108        let ms_op_count = ms.ext.ops.op_count();
109        // statically computed
110        // see cov_test_limits test for the test assert
111        let cov_script_ops = COV_SCRIPT_OPCODE_COST;
112        let total_ops = ms_op_count.ok_or(Error::ImpossibleSatisfaction)? + cov_script_ops
113            - if ms.ext.has_free_verify { 1 } else { 0 };
114        if total_ops > MAX_OPS_PER_SCRIPT {
115            return Err(Error::ImpossibleSatisfaction);
116        }
117        // 2) TODO: Sighash never exceeds 520 bytes, but we check the
118        // witness script before the codesep is still under 520
119        // bytes if the covenant relies on introspection of script
120        let ss = COV_SCRIPT_SIZE - if ms.ext.has_free_verify { 1 } else { 0 };
121        // 3) Check that the script size does not exceed 10_000 bytes
122        // global consensus rule
123        if ms.script_size() + ss > MAX_SCRIPT_SIZE {
124            Err(Error::ScriptSizeTooLarge)
125        } else {
126            Ok(Self { pk, ms })
127        }
128    }
129    /// Encode
130    pub fn encode(&self) -> Script
131    where
132        Pk: ToPublicKey,
133        Ext: ParseableExt,
134    {
135        let builder = self.ms.node.encode(script::Builder::new());
136        builder.verify_cov(&self.pk.to_public_key()).into_script()
137    }
138
139    /// Create a satisfaction for the Covenant Descriptor
140    pub fn satisfy<S: Satisfier<Pk>>(&self, s: S, allow_mall: bool) -> Result<Vec<Vec<u8>>, Error>
141    where
142        Pk: ToPublicKey,
143        Ext: ParseableExt,
144    {
145        let mut wit = {
146            use crate::descriptor::CovError::MissingSighashItem;
147            let n_version = s.lookup_nversion().ok_or(MissingSighashItem(1))?;
148            let hash_prevouts = s.lookup_hashprevouts().ok_or(MissingSighashItem(1))?;
149            let hash_sequence = s.lookup_hashsequence().ok_or(MissingSighashItem(3))?;
150            // note the 3 again, for elements
151            let hash_issuances = s.lookup_hashissuances().ok_or(MissingSighashItem(3))?;
152            let outpoint = s.lookup_outpoint().ok_or(MissingSighashItem(4))?;
153            let script_code = s.lookup_scriptcode().ok_or(MissingSighashItem(5))?;
154            let value = s.lookup_value().ok_or(MissingSighashItem(6))?;
155            let n_sequence = s.lookup_nsequence().ok_or(MissingSighashItem(7))?;
156            let outputs = s.lookup_outputs().ok_or(MissingSighashItem(8))?;
157            let hash_outputs = hash256_arr(outputs);
158            let n_locktime = s.lookup_nlocktime().ok_or(MissingSighashItem(9))?;
159            let sighash_ty = s.lookup_sighashu32().ok_or(MissingSighashItem(10))?;
160
161            let (sig, hash_ty) = s
162                .lookup_ecdsa_sig(&self.pk)
163                .ok_or(CovError::MissingCovSignature)?;
164            // Hashtype must be the same
165            if sighash_ty != hash_ty.as_u32() {
166                return Err(CovError::CovenantSighashTypeMismatch)?;
167            }
168
169            vec![
170                Vec::from(sig.serialize_der().as_ref()), // The covenant sig
171                serialize(&sighash_ty),                  // item 10(11)
172                serialize(&n_locktime),                  // item 9(10)
173                serialize(&hash_outputs),                // item 8(9)
174                serialize(&n_sequence),                  // item 7(8)
175                serialize(&value),                       // item 6(7)
176                serialize(script_code),                  // item 5(6)
177                serialize(&outpoint),                    // item 4(5)
178                serialize(&hash_issuances),              // ELEMENTS EXTRA: item 3b(4)
179                serialize(&hash_sequence),               // item 3
180                serialize(&hash_prevouts),               // item 2
181                serialize(&n_version),                   // item 1
182            ]
183        };
184
185        let ms_wit = if !allow_mall {
186            self.ms.satisfy(s)?
187        } else {
188            self.ms.satisfy_malleable(s)?
189        };
190        wit.extend(ms_wit);
191        Ok(wit)
192    }
193
194    /// Script code for signing with covenant publickey.
195    /// Use this script_code for sighash method when signing
196    /// with the covenant pk.
197    pub fn cov_script_code(&self) -> Script
198    where
199        Pk: ToPublicKey,
200    {
201        script::Builder::new().post_codesep_script().into_script()
202    }
203}
204
205impl<Ext: ParseableExt> LegacyCSFSCov<bitcoin::PublicKey, Ext> {
206    /// Check if the given script is a covenant descriptor
207    /// Consumes the iterator so that only remaining miniscript
208    /// needs to be parsed from the iterator
209    #[allow(unreachable_patterns)]
210    fn check_cov_script(tokens: &mut TokenIter<'_>) -> Result<bitcoin::PublicKey, Error> {
211        match_token!(tokens,
212            Tk::CheckSigFromStack, Tk::Verify, Tk::CheckSig, Tk::CodeSep, Tk::Swap,
213            Tk::FromAltStack, Tk::Dup, Tk::Bytes33(pk), Tk::Sha256,
214            Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(4), Tk::Size,   // item 10
215            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(4), Tk::Size,  // item 9
216            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size, // item 8
217            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(4), Tk::Size,  // item 7
218            Tk::Swap, Tk::Cat, Tk::EndIf,
219                Tk::Verify, Tk::Equal, Tk::Num(33), Tk::Size, Tk::Else,// item 6
220                Tk::Verify, Tk::Equal, Tk::Num(9), Tk::Size, Tk::If,   // item 6
221                Tk::Equal, Tk::Num(1), Tk::Left, Tk::Num(1), Tk::Dup,  // item 6
222            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(3), Tk::Size,  // item 5
223            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(36), Tk::Size, // item 4
224            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size, // item 3b
225            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size, // item 3
226            Tk::Swap, Tk::Cat, Tk::Verify, Tk::Equal, Tk::Num(32), Tk::Size, // item 2
227            Tk::Swap, Tk::Verify, Tk::Equal, Tk::Num(4), Tk::Size,  // item 1
228            Tk::ToAltStack, Tk::Cat, Tk::Left, Tk::Num(1),
229            Tk::Pick, Tk::Num(11), Tk::Pick, Tk::Num(11), Tk::Verify => {
230                Ok(bitcoin::PublicKey::from_slice(pk)?)
231            },
232            _ => Err(Error::CovError(CovError::BadCovDescriptor)),
233        )
234    }
235
236    /// Parse a descriptor from script. While parsing
237    /// other descriptors, we only parse the inner miniscript
238    /// with ScriptContext. But Covenant descriptors only
239    /// applicable under Wsh context to avoid implementation
240    /// complexity.
241    // All code for covenants can thus be separated in a module
242    // This parsing is parse_insane
243    pub fn parse_insane(script: &script::Script) -> Result<Self, Error> {
244        let (pk, ms) = Self::parse_cov_components(script)?;
245        Self::new(pk, ms)
246    }
247
248    // Utility function to parse the components of cov
249    // descriptor. This allows us to parse Miniscript with
250    // it's context so that it can be used with NoChecks
251    // context while using the interpreter
252    pub(crate) fn parse_cov_components(
253        script: &script::Script,
254    ) -> Result<
255        (
256            bitcoin::PublicKey,
257            Miniscript<bitcoin::PublicKey, Segwitv0, Ext>,
258        ),
259        Error,
260    >
261    where
262        Ext: ParseableExt,
263    {
264        let tokens = lex(script)?;
265        let mut iter = TokenIter::new(tokens);
266
267        let pk = LegacyCSFSCov::<bitcoin::PublicKey, Ext>::check_cov_script(&mut iter)?;
268        let ms = decode::parse(&mut iter)?;
269        Segwitv0::check_global_validity(&ms)?;
270        if ms.ty.corr.base != types::Base::B {
271            return Err(Error::NonTopLevel(format!("{:?}", ms)));
272        };
273        if let Some(leading) = iter.next() {
274            Err(Error::Trailing(leading.to_string()))
275        } else {
276            Ok((pk, ms))
277        }
278    }
279
280    /// Parse a descriptor with additional local sanity checks.
281    /// See [`Miniscript::sanity_check`] for all the checks. Use
282    /// [`Miniscript::parse_insane`] to allow parsing insane scripts
283    pub fn parse(script: &script::Script) -> Result<Self, Error> {
284        let cov = Self::parse_insane(script)?;
285        cov.ms.sanity_check()?;
286        Ok(cov)
287    }
288}
289
290impl_from_tree!(
291    LegacyCSFSCov<Pk, Ext>,
292    => Ext; Extension,
293    fn from_tree(top: &expression::Tree<'_>) -> Result<Self, Error> {
294        if top.name == "elcovwsh" && top.args.len() == 2 {
295            let pk = expression::terminal(&top.args[0], |pk| Pk::from_str(pk))?;
296            let top = &top.args[1];
297            let sub = Miniscript::from_tree(top)?;
298            Segwitv0::top_level_checks(&sub)?;
299            Ok(LegacyCSFSCov { pk, ms: sub })
300        } else {
301            Err(Error::Unexpected(format!(
302                "{}({} args) while parsing elcovwsh descriptor",
303                top.name,
304                top.args.len(),
305            )))
306        }
307    }
308);
309impl<Pk, Ext> fmt::Debug for LegacyCSFSCov<Pk, Ext>
310where
311    Pk: MiniscriptKey,
312    Ext: Extension,
313{
314    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315        write!(f, "{}covwsh({},{})", ELMTS_STR, self.pk, self.ms)
316    }
317}
318
319impl<Pk, Ext> fmt::Display for LegacyCSFSCov<Pk, Ext>
320where
321    Pk: MiniscriptKey,
322    Ext: Extension,
323{
324    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
325        use fmt::Write;
326        let mut wrapped_f = checksum::Formatter::new(f);
327        write!(wrapped_f, "{}covwsh({},{})", ELMTS_STR, self.pk, self.ms)?;
328        wrapped_f.write_checksum_if_not_alt()
329    }
330}
331
332impl_from_str!(
333    LegacyCSFSCov<Pk, Ext>,
334    => Ext; Extension,
335    type Err = Error;,
336
337    fn from_str(s: &str) -> Result<Self, Self::Err> {
338        let desc_str = verify_checksum(s)?;
339        let top = expression::Tree::from_str(desc_str)?;
340        LegacyCSFSCov::<Pk, Ext>::from_tree(&top)
341    }
342);
343
344impl<Pk, Ext> LegacyCSFSCov<Pk, Ext>
345where
346    Pk: MiniscriptKey,
347    Ext: Extension,
348{
349    /// Sanity checks for this covenant descriptor
350    pub fn sanity_check(&self) -> Result<(), Error> {
351        self.ms.sanity_check()?;
352        // Additional local check for p2wsh script size
353        let ss = COV_SCRIPT_SIZE - if self.ms.ext.has_free_verify { 1 } else { 0 };
354        if self.ms.script_size() + ss > MAX_STANDARD_P2WSH_SCRIPT_SIZE {
355            Err(Error::ScriptSizeTooLarge)
356        } else {
357            Ok(())
358        }
359    }
360
361    /// Obtains the blinded address for this descriptor.
362    pub fn address(
363        &self,
364        blinder: Option<secp256k1_zkp::PublicKey>,
365        params: &'static elements::AddressParams,
366    ) -> elements::Address
367    where
368        Pk: ToPublicKey,
369        Ext: ParseableExt,
370    {
371        elements::Address::p2wsh(&self.encode(), blinder, params)
372    }
373
374    /// Obtains the script pubkey for this descriptor.
375    pub fn script_pubkey(&self) -> Script
376    where
377        Pk: ToPublicKey,
378        Ext: ParseableExt,
379    {
380        self.encode().to_v0_p2wsh()
381    }
382
383    /// Computes the scriptSig that will be in place for an unsigned input
384    /// spending an output with this descriptor.
385    pub fn unsigned_script_sig(&self) -> Script
386    where
387        Pk: ToPublicKey,
388    {
389        Script::new()
390    }
391
392    /// Computes the the underlying script before any hashing is done.
393    pub fn inner_script(&self) -> Script
394    where
395        Pk: ToPublicKey,
396        Ext: ParseableExt,
397    {
398        self.encode()
399    }
400
401    /// Returns satisfying non-malleable witness and scriptSig to spend an
402    /// output controlled by the given descriptor if it possible to
403    /// construct one using the satisfier S.
404    pub fn get_satisfaction<S>(&self, satisfier: S) -> Result<(Vec<Vec<u8>>, Script), Error>
405    where
406        Pk: ToPublicKey,
407        S: Satisfier<Pk>,
408        Ext: ParseableExt,
409    {
410        let mut witness = self.satisfy(satisfier, /*allow_mall*/ false)?;
411        witness.push(self.encode().into_bytes());
412        let script_sig = Script::new();
413        Ok((witness, script_sig))
414    }
415
416    /// Computes an upper bound on the weight of a satisfying witness to the
417    /// transaction.
418    pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
419        let script_size =
420            self.ms.script_size() + 58 - if self.ms.ext.has_free_verify { 1 } else { 0 };
421        let max_sat_elems = self.ms.max_satisfaction_witness_elements()? + 12;
422        let max_sat_size = self.ms.max_satisfaction_size()? + 275;
423
424        Ok(4 +  // scriptSig length byte
425            varint_len(script_size) +
426            script_size +
427            varint_len(max_sat_elems) +
428            max_sat_size)
429    }
430
431    /// This returns the entire explicit script as the script code.
432    /// You will need this script code when singing with pks that
433    /// inside Miniscript. Use the [`Self::cov_script_code`] method to
434    /// get the script code for signing with covenant pk
435    pub fn ecdsa_sighash_script_code(&self) -> Script
436    where
437        Pk: ToPublicKey,
438        Ext: ParseableExt,
439    {
440        self.inner_script()
441    }
442
443    /// Returns a possilbly mallable satisfying non-malleable witness and scriptSig to spend an
444    /// output controlled by the given descriptor if it possible to
445    /// construct one using the satisfier S.
446    pub fn get_satisfaction_mall<S>(&self, satisfier: S) -> Result<(Vec<Vec<u8>>, Script), Error>
447    where
448        Pk: ToPublicKey,
449        S: Satisfier<Pk>,
450        Ext: ParseableExt,
451    {
452        let mut witness = self.satisfy(satisfier, /*allow_mall*/ true)?;
453        witness.push(self.encode().into_bytes());
454        let script_sig = Script::new();
455        Ok((witness, script_sig))
456    }
457}
458
459impl<Pk: MiniscriptKey, Ext: Extension> ForEachKey<Pk> for LegacyCSFSCov<Pk, Ext> {
460    fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, mut pred: F) -> bool
461    where
462        Pk: 'a,
463    {
464        pred(&self.pk) && self.ms.for_any_key(pred)
465    }
466}
467
468impl<P, Q, Ext> TranslatePk<P, Q> for LegacyCSFSCov<P, Ext>
469where
470    P: MiniscriptKey,
471    Q: MiniscriptKey,
472    Ext: Extension,
473{
474    type Output = LegacyCSFSCov<Q, Ext>;
475
476    fn translate_pk<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
477    where
478        T: Translator<P, Q, E>,
479    {
480        Ok(LegacyCSFSCov {
481            pk: t.pk(&self.pk)?,
482            ms: self.ms.translate_pk(t)?,
483        })
484    }
485}
486
487impl<Pk, Ext, ExtQ> TranslateExt<Ext, ExtQ> for LegacyCSFSCov<Pk, Ext>
488where
489    Pk: MiniscriptKey,
490    Ext: Extension,
491    ExtQ: Extension,
492    Ext: TranslateExt<Ext, ExtQ, Output = ExtQ>,
493{
494    type Output = LegacyCSFSCov<Pk, ExtQ>;
495
496    fn translate_ext<T, E>(&self, translator: &mut T) -> Result<Self::Output, E>
497    where
498        T: ExtTranslator<Ext, ExtQ, E>,
499    {
500        Ok(LegacyCSFSCov {
501            pk: self.pk.clone(),
502            ms: self.ms.translate_ext(translator)?,
503        })
504    }
505}