Skip to main content

elements_miniscript/descriptor/pegin/
legacy_pegin.rs

1// Miniscript
2// Written in 2018 by
3//     Andrew Poelstra <apoelstra@wpsoftware.net>
4//
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//! Pegin Descriptor Support
16//!
17//! Traits and implementations for Pegin descriptors
18//! Note that this is a bitcoin descriptor and thus cannot be
19//! added to elements Descriptor. Upstream rust-miniscript does
20//! has a Descriptor enum which should ideally have it, but
21//! bitcoin descriptors cannot depend on elements descriptors
22//! Thus, as a simple solution we implement these as a separate
23//! struct with it's own API.
24
25use std::convert::TryFrom;
26use std::fmt;
27use std::str::FromStr;
28use std::sync::Arc;
29
30use bitcoin::blockdata::script::PushBytes;
31use bitcoin::blockdata::{opcodes, script};
32use bitcoin::hashes::{hash160, ripemd160, sha256, Hash};
33use bitcoin::{self, hashes, ScriptBuf as BtcScript};
34use bitcoin_miniscript::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG;
35use bitcoin_miniscript::TranslatePk as BtcTranslatePk;
36use elements::secp256k1_zkp;
37
38use crate::descriptor::checksum::{self, verify_checksum};
39use crate::expression::{self, FromTree};
40use crate::extensions::{CovExtArgs, CovenantExt};
41use crate::policy::{semantic, Liftable};
42use crate::util::varint_len;
43use crate::{
44    hash256, tweak_key, BtcError, BtcFromTree, BtcLiftable, BtcMiniscript, BtcPolicy, BtcSatisfier,
45    BtcSegwitv0, BtcTerminal, BtcTree, Descriptor, Error, MiniscriptKey, ToPublicKey,
46};
47
48#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
49/// MiniscriptKey used for Pegins
50pub enum LegacyPeginKey {
51    /// Functionary Key that can be tweaked
52    Functionary(bitcoin::PublicKey),
53    /// Non functionary Key, cannot be tweaked
54    NonFunctionary(bitcoin::PublicKey),
55}
56
57impl LegacyPeginKey {
58    /// Get the untweaked version of the LegacyPeginKey
59    pub fn as_untweaked(&self) -> &bitcoin::PublicKey {
60        match *self {
61            LegacyPeginKey::Functionary(ref pk) => pk,
62            LegacyPeginKey::NonFunctionary(ref pk) => pk,
63        }
64    }
65}
66
67/// 'f' represents tweakable functionary keys and
68/// 'u' represents untweakable keys
69impl FromStr for LegacyPeginKey {
70    // only allow compressed keys in LegacyPegin
71    type Err = Error;
72    fn from_str(s: &str) -> Result<Self, Self::Err> {
73        if s.is_empty() {
74            Err(Error::BadDescriptor("Empty Legacy pegin".to_string()))
75        } else if &s[0..1] == "f" && s.len() == 67 {
76            Ok(LegacyPeginKey::Functionary(bitcoin::PublicKey::from_str(
77                &s[1..],
78            )?))
79        } else if &s[0..1] == "u" && s.len() == 67 {
80            Ok(LegacyPeginKey::NonFunctionary(
81                bitcoin::PublicKey::from_str(&s[1..])?,
82            ))
83        } else {
84            Err(Error::BadDescriptor(
85                "Invalid Legacy Pegin descriptor".to_string(),
86            ))
87        }
88    }
89}
90
91impl fmt::Display for LegacyPeginKey {
92    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
93        match *self {
94            LegacyPeginKey::Functionary(ref pk) => write!(f, "f{}", pk),
95            LegacyPeginKey::NonFunctionary(ref pk) => write!(f, "u{}", pk),
96        }
97    }
98}
99
100impl MiniscriptKey for LegacyPeginKey {
101    type Sha256 = sha256::Hash;
102    type Hash256 = hash256::Hash;
103    type Ripemd160 = ripemd160::Hash;
104    type Hash160 = hash160::Hash;
105
106    fn is_uncompressed(&self) -> bool {
107        false
108    }
109
110    fn num_der_paths(&self) -> usize {
111        0
112    }
113}
114
115/// Legacy Pegin Descriptor
116#[derive(Clone, Ord, PartialOrd, Eq, PartialEq)]
117pub struct LegacyPegin<Pk: MiniscriptKey> {
118    /// The federation threshold
119    fed: bitcoin_miniscript::Threshold<LegacyPeginKey, MAX_PUBKEYS_PER_MULTISIG>,
120
121    /// The emergency threshold
122    emer: bitcoin_miniscript::Threshold<LegacyPeginKey, MAX_PUBKEYS_PER_MULTISIG>,
123
124    /// csv timelock
125    timelock: bitcoin_miniscript::RelLockTime,
126
127    /// The elements descriptor required to redeem
128    ///
129    /// TODO: Allow extension user descriptors when claiming pegins
130    pub desc: Descriptor<Pk, CovenantExt<CovExtArgs>>,
131    // Representation of federation policy as a miniscript
132    // Allows for easier implementation
133    ms: BtcMiniscript<LegacyPeginKey, BtcSegwitv0>,
134}
135
136impl<Pk: MiniscriptKey> LegacyPegin<Pk> {
137    /// Create a new LegacyPegin descriptor
138    pub fn new(
139        fed: bitcoin_miniscript::Threshold<LegacyPeginKey, MAX_PUBKEYS_PER_MULTISIG>,
140        emer: bitcoin_miniscript::Threshold<LegacyPeginKey, MAX_PUBKEYS_PER_MULTISIG>,
141        timelock: bitcoin_miniscript::RelLockTime,
142        desc: Descriptor<Pk, CovenantExt<CovExtArgs>>,
143    ) -> Self {
144        let fed_ms = BtcMiniscript::from_ast(BtcTerminal::Multi(fed.clone()))
145            .expect("Multi type check can't fail");
146        let csv = BtcMiniscript::from_ast(BtcTerminal::Verify(Arc::new(
147            BtcMiniscript::from_ast(BtcTerminal::Older(timelock)).unwrap(),
148        )))
149        .unwrap();
150        let emer_ms = BtcMiniscript::from_ast(BtcTerminal::Multi(emer.clone()))
151            .expect("Multi type check can't fail");
152        let emer_ms =
153            BtcMiniscript::from_ast(BtcTerminal::AndV(Arc::new(csv), Arc::new(emer_ms))).unwrap();
154        let ms = BtcMiniscript::from_ast(BtcTerminal::OrD(Arc::new(fed_ms), Arc::new(emer_ms)))
155            .expect("Type check");
156        Self {
157            fed,
158            emer,
159            timelock,
160            desc,
161            ms,
162        }
163    }
164
165    // Internal function to set the fields of Self according to
166    // miniscript
167    fn from_ms_and_desc(
168        desc: Descriptor<Pk, CovenantExt<CovExtArgs>>,
169        ms: BtcMiniscript<LegacyPeginKey, BtcSegwitv0>,
170    ) -> Self {
171        // Miniscript is a bunch of Arc's. So, cloning is not as bad.
172        // Can we avoid this without NLL?
173        let ms_clone = ms.clone();
174        let (fed, right) = if let BtcTerminal::OrD(ref a, ref b) = ms_clone.node {
175            if let (BtcTerminal::Multi(t), right) = (&a.node, &b.node) {
176                (t.clone(), right)
177            } else {
178                unreachable!("Only valid pegin miniscripts");
179            }
180        } else {
181            unreachable!("Only valid pegin miniscripts");
182        };
183        let (timelock, emer) = if let BtcTerminal::AndV(l, r) = right {
184            if let (BtcTerminal::Verify(csv), BtcTerminal::Multi(t)) = (&l.node, &r.node) {
185                if let BtcTerminal::Older(timelock) = csv.node {
186                    (timelock, t.clone())
187                } else {
188                    unreachable!("Only valid pegin miniscripts");
189                }
190            } else {
191                unreachable!("Only valid pegin miniscripts");
192            }
193        } else {
194            unreachable!("Only valid pegin miniscripts");
195        };
196        Self {
197            fed,
198            emer,
199            timelock,
200            desc,
201            ms,
202        }
203    }
204
205    fn explicit_script<C: secp256k1_zkp::Verification>(
206        &self,
207        secp: &secp256k1_zkp::Secp256k1<C>,
208    ) -> BtcScript
209    where
210        Pk: ToPublicKey,
211    {
212        let tweak_vec = self.desc.explicit_script().expect("Tr pegins").into_bytes();
213        let tweak = hashes::sha256::Hash::hash(&tweak_vec);
214        // Hopefully, we never have to use this and dynafed is deployed
215        let mut builder = script::Builder::new()
216            .push_opcode(opcodes::all::OP_DEPTH)
217            .push_int(self.fed.k() as i64 + 1)
218            .push_opcode(opcodes::all::OP_EQUAL)
219            .push_opcode(opcodes::all::OP_IF)
220            // manually serialize the left CMS branch, without the OP_CMS
221            .push_int(self.fed.k() as i64);
222
223        for key in self.fed.iter() {
224            let tweaked_pk = tweak_key(key.as_untweaked(), secp, tweak.as_byte_array());
225            builder = builder.push_key(&tweaked_pk);
226        }
227        let mut nearly_done = builder
228            .push_int(self.fed.n() as i64)
229            .push_opcode(opcodes::all::OP_ELSE)
230            .into_script()
231            .to_bytes();
232
233        let right = if let BtcTerminal::OrD(_l, right) = &self.ms.node {
234            right
235        } else {
236            unreachable!("Only valid pegin descriptors should be created inside LegacyPegin")
237        };
238        struct TranslateUnTweak;
239
240        impl bitcoin_miniscript::Translator<LegacyPeginKey, bitcoin::PublicKey, ()> for TranslateUnTweak {
241            fn pk(&mut self, pk: &LegacyPeginKey) -> Result<bitcoin::PublicKey, ()> {
242                Ok(*pk.as_untweaked())
243            }
244
245            bitcoin_miniscript::translate_hash_clone!(LegacyPeginKey, bitcoin::PublicKey, ());
246        }
247        let mut t = TranslateUnTweak;
248
249        let right = right
250            .translate_pk(&mut t)
251            .expect("Translation must succeed");
252        let mut rser = right.encode().into_bytes();
253        // ...and we have an OP_VERIFY style checksequenceverify, which in
254        // Liquid production was encoded with OP_DROP instead...
255        assert_eq!(rser[4], opcodes::all::OP_VERIFY.to_u8());
256        rser[4] = opcodes::all::OP_DROP.to_u8();
257        // ...then we should serialize it by sharing the OP_CMS across
258        // both branches, and add an OP_DEPTH check to distinguish the
259        // branches rather than doing the normal cascade construction
260        nearly_done.extend(rser);
261
262        let insert_point = nearly_done.len() - 1;
263        nearly_done.insert(insert_point, 0x68);
264        BtcScript::from(nearly_done)
265    }
266
267    /// Create a new descriptor with hard coded values for the
268    /// legacy federation and emergency keys
269    pub fn new_legacy_fed(user_desc: Descriptor<Pk, CovenantExt<CovExtArgs>>) -> Self {
270        // Taken from functionary codebase
271        // TODO: Verify the keys are correct
272        let pks = "
273                    020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261,
274                    02675333a4e4b8fb51d9d4e22fa5a8eaced3fdac8a8cbf9be8c030f75712e6af99,
275                    02896807d54bc55c24981f24a453c60ad3e8993d693732288068a23df3d9f50d48,
276                    029e51a5ef5db3137051de8323b001749932f2ff0d34c82e96a2c2461de96ae56c,
277                    02a4e1a9638d46923272c266631d94d36bdb03a64ee0e14c7518e49d2f29bc4010,
278                    02f8a00b269f8c5e59c67d36db3cdc11b11b21f64b4bffb2815e9100d9aa8daf07,
279                    03079e252e85abffd3c401a69b087e590a9b86f33f574f08129ccbd3521ecf516b,
280                    03111cf405b627e22135b3b3733a4a34aa5723fb0f58379a16d32861bf576b0ec2,
281                    0318f331b3e5d38156da6633b31929c5b220349859cc9ca3d33fb4e68aa0840174,
282                    03230dae6b4ac93480aeab26d000841298e3b8f6157028e47b0897c1e025165de1,
283                    035abff4281ff00660f99ab27bb53e6b33689c2cd8dcd364bc3c90ca5aea0d71a6,
284                    03bd45cddfacf2083b14310ae4a84e25de61e451637346325222747b157446614c,
285                    03cc297026b06c71cbfa52089149157b5ff23de027ac5ab781800a578192d17546,
286                    03d3bde5d63bdb3a6379b461be64dad45eabff42f758543a9645afd42f6d424828,
287                    03ed1e8d5109c9ed66f7941bc53cc71137baa76d50d274bda8d5e8ffbd6e61fe9a";
288        let fed_pks: Vec<LegacyPeginKey> = pks
289            .split(',')
290            .map(|pk| LegacyPeginKey::Functionary(bitcoin::PublicKey::from_str(pk).unwrap()))
291            .collect();
292
293        let fed = bitcoin_miniscript::Threshold::new(22, fed_pks).expect("statically defined");
294
295        let emer_pks = "
296                    03aab896d53a8e7d6433137bbba940f9c521e085dd07e60994579b64a6d992cf79,
297                    0291b7d0b1b692f8f524516ed950872e5da10fb1b808b5a526dedc6fed1cf29807,
298                    0386aa9372fbab374593466bc5451dc59954e90787f08060964d95c87ef34ca5bb";
299        let emer_pks: Vec<LegacyPeginKey> = emer_pks
300            .split(',')
301            .map(|pk| LegacyPeginKey::Functionary(bitcoin::PublicKey::from_str(pk).unwrap()))
302            .collect();
303
304        let emer = bitcoin_miniscript::Threshold::new(2, emer_pks).expect("statically defined");
305
306        Self::new(
307            fed,
308            emer,
309            bitcoin_miniscript::RelLockTime::from_consensus(4032).expect("statically defined"),
310            user_desc,
311        )
312    }
313}
314
315impl<Pk: MiniscriptKey> fmt::Debug for LegacyPegin<Pk> {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        write!(f, "legacy_pegin({:?},{:?})", self.ms, self.desc)
318    }
319}
320
321impl<Pk: MiniscriptKey> fmt::Display for LegacyPegin<Pk> {
322    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
323        use fmt::Write;
324        let mut wrapped_f = checksum::Formatter::new(f);
325        write!(wrapped_f, "legacy_pegin({},{})", self.ms, self.desc)?;
326        wrapped_f.write_checksum_if_not_alt()
327    }
328}
329
330impl<Pk: MiniscriptKey> Liftable<LegacyPeginKey> for LegacyPegin<Pk> {
331    fn lift(&self) -> Result<semantic::Policy<LegacyPeginKey>, Error> {
332        let btc_pol = BtcLiftable::lift(&self.ms)?;
333        Liftable::lift(&btc_pol)
334    }
335}
336
337impl<Pk: MiniscriptKey> BtcLiftable<LegacyPeginKey> for LegacyPegin<Pk> {
338    fn lift(&self) -> Result<BtcPolicy<LegacyPeginKey>, BtcError> {
339        self.ms.lift()
340    }
341}
342
343impl_from_tree!(
344    LegacyPegin<Pk>,
345    fn from_tree(top: &expression::Tree<'_>) -> Result<Self, Error> {
346        if top.name == "legacy_pegin" && top.args.len() == 2 {
347            // a roundtrip hack to use FromTree from bitcoin::Miniscript from
348            // expression::Tree in elements.
349            let ms_str = top.args[0].to_string();
350            let ms_expr = BtcTree::from_str(&ms_str)?;
351            //
352            let ms = BtcMiniscript::<LegacyPeginKey, BtcSegwitv0>::from_tree(&ms_expr);
353            let desc = Descriptor::<Pk, CovenantExt<CovExtArgs>>::from_tree(&top.args[1]);
354            Ok(LegacyPegin::from_ms_and_desc(desc?, ms?))
355        } else {
356            Err(Error::Unexpected(format!(
357                "{}({} args) while parsing legacy_pegin descriptor",
358                top.name,
359                top.args.len(),
360            )))
361        }
362    }
363);
364
365impl_from_str!(
366    LegacyPegin<Pk>,
367    type Err = Error;,
368    fn from_str(s: &str) -> Result<Self, Self::Err> {
369        let desc_str = verify_checksum(s)?;
370        let top = expression::Tree::from_str(desc_str)?;
371        Self::from_tree(&top)
372    }
373);
374
375impl<Pk: MiniscriptKey> LegacyPegin<Pk> {
376    /// Checks whether the descriptor is safe.
377    ///
378    /// Should always return true
379    pub fn sanity_check(&self) -> Result<(), Error> {
380        self.ms
381            .sanity_check()
382            .map_err(|_| Error::Unexpected("Federation script sanity check failed".to_string()))?;
383        self.desc
384            .sanity_check()
385            .map_err(|_| Error::Unexpected("Federation script sanity check failed".to_string()))?;
386        Ok(())
387    }
388
389    /// Computes the Bitcoin address of the pegin descriptor, if one exists.
390    /// Requires the secp context to compute the tweak
391    pub fn bitcoin_address<C: secp256k1_zkp::Verification>(
392        &self,
393        network: bitcoin::Network,
394        secp: &secp256k1_zkp::Secp256k1<C>,
395    ) -> Result<bitcoin::Address, Error>
396    where
397        Pk: ToPublicKey,
398    {
399        Ok(bitcoin::Address::p2shwsh(
400            &self.explicit_script(secp),
401            network,
402        ))
403    }
404
405    /// Computes the bitcoin scriptpubkey of the descriptor.
406    /// Requires the secp context to compute the tweak
407    pub fn bitcoin_script_pubkey<C: secp256k1_zkp::Verification>(
408        &self,
409        secp: &secp256k1_zkp::Secp256k1<C>,
410    ) -> BtcScript
411    where
412        Pk: ToPublicKey,
413    {
414        self.bitcoin_address(bitcoin::Network::Bitcoin, secp)
415            .expect("Address cannot fail for pegin")
416            .script_pubkey()
417    }
418
419    /// Computes the scriptSig that will be in place for an unsigned
420    /// input spending an output with this descriptor. For pre-segwit
421    /// descriptors, which use the scriptSig for signatures, this
422    /// returns the empty script.
423    ///
424    /// This is used in Segwit transactions to produce an unsigned
425    /// transaction whose txid will not change during signing (since
426    /// only the witness data will change).
427    /// Requires the secp context to compute the tweak
428    pub fn bitcoin_unsigned_script_sig<C: secp256k1_zkp::Verification>(
429        &self,
430        secp: &secp256k1_zkp::Secp256k1<C>,
431    ) -> BtcScript
432    where
433        Pk: ToPublicKey,
434    {
435        let witness_script = self.explicit_script(secp);
436        let push_bytes = <&PushBytes>::try_from(witness_script.as_bytes())
437            .expect("Witness script is not too larg");
438        script::Builder::new().push_slice(push_bytes).into_script()
439    }
440    /// Computes the bitcoin "witness script" of the descriptor, i.e. the underlying
441    /// script before any hashing is done. For `Bare`, `Pkh` and `Wpkh` this
442    /// is the scriptPubkey; for `ShWpkh` and `Sh` this is the redeemScript;
443    /// for the others it is the witness script.
444    pub fn bitcoin_witness_script<C: secp256k1_zkp::Verification>(
445        &self,
446        secp: &secp256k1_zkp::Secp256k1<C>,
447    ) -> Result<BtcScript, Error>
448    where
449        Pk: ToPublicKey,
450    {
451        Ok(self.explicit_script(secp))
452    }
453
454    /// Returns satisfying witness and scriptSig to spend an
455    /// output controlled by the given descriptor if it possible to
456    /// construct one using the satisfier S.
457    pub fn get_bitcoin_satisfaction<S, C: secp256k1_zkp::Verification>(
458        &self,
459        secp: &secp256k1_zkp::Secp256k1<C>,
460        satisfier: S,
461    ) -> Result<(Vec<Vec<u8>>, BtcScript), Error>
462    where
463        S: BtcSatisfier<bitcoin::PublicKey>,
464        Pk: ToPublicKey,
465    {
466        let tweak_vec = self.desc.explicit_script().expect("Tr pegins").into_bytes();
467        let tweak = hashes::sha256::Hash::hash(&tweak_vec);
468        let unsigned_script_sig = self.bitcoin_unsigned_script_sig(secp);
469        let mut sigs = vec![];
470        for key in self.fed.iter() {
471            let tweaked_pk = tweak_key(key.as_untweaked(), secp, tweak.as_byte_array());
472            if let Some(sig) = satisfier.lookup_ecdsa_sig(&tweaked_pk) {
473                sigs.push(sig.to_vec());
474            }
475        }
476        sigs.sort_by_key(|a| a.len());
477        if sigs.len() >= self.fed.k() {
478            // Prefer using federation keys over emergency paths
479            let mut sigs: Vec<Vec<u8>> = sigs.into_iter().take(self.fed.k()).collect();
480            sigs.push(vec![0]); // CMS extra value
481            Ok((sigs, unsigned_script_sig))
482        } else {
483            let mut emer_sigs = vec![];
484            for emer_key in self.emer.iter() {
485                if let Some(sig) = satisfier.lookup_ecdsa_sig(emer_key.as_untweaked()) {
486                    emer_sigs.push(sig.to_vec());
487                }
488            }
489            emer_sigs.sort_by_key(|a| a.len());
490            if emer_sigs.len() >= self.emer.k() {
491                let mut sigs: Vec<Vec<u8>> = emer_sigs.into_iter().take(self.emer.k()).collect();
492                sigs.push(vec![0]); // CMS extra value
493                Ok((sigs, unsigned_script_sig))
494            } else {
495                Err(Error::CouldNotSatisfy)
496            }
497        }
498    }
499
500    /// Computes an upper bound on the weight of a satisfying witness to the
501    /// transaction. Assumes all signatures are 73 bytes, including push opcode
502    /// and sighash suffix. Includes the weight of the VarInts encoding the
503    /// scriptSig and witness stack length.
504    pub fn max_satisfaction_weight(&self) -> Result<usize, Error> {
505        let script_size = 628;
506        Ok(4 * 36
507            + varint_len(script_size)
508            + script_size
509            + varint_len(self.ms.max_satisfaction_witness_elements()?)
510            + self.ms.max_satisfaction_size()?)
511    }
512
513    /// Get the `scriptCode` of a transaction output.
514    ///
515    /// The `scriptCode` is the Script of the previous transaction output being serialized in the
516    /// sighash when evaluating a `CHECKSIG` & co. OP code.
517    pub fn script_code<C: secp256k1_zkp::Verification>(
518        &self,
519        secp: &secp256k1_zkp::Secp256k1<C>,
520    ) -> Result<BtcScript, Error>
521    where
522        Pk: ToPublicKey,
523    {
524        self.bitcoin_witness_script(secp)
525    }
526
527    /// Get the corresponding elements descriptor that would be used
528    /// at redeem time by the user.
529    /// Users can use the DescrpitorTrait operations on the output Descriptor
530    /// to obtain the characteristics of the elements descriptor.
531    pub fn into_user_descriptor(self) -> Descriptor<Pk, CovenantExt<CovExtArgs>> {
532        self.desc
533    }
534}