elements_miniscript/extensions/
csfs.rs

1//! Miniscript extension: CheckSigFromStack
2//! Note that this fragment is only supported for Tapscript context
3
4use std::fmt;
5use std::str::FromStr;
6
7use bitcoin::key::XOnlyPublicKey;
8use elements::hex::{self, FromHex, ToHex};
9use elements::{self, opcodes, secp256k1_zkp};
10
11use super::param::{ExtParamTranslator, TranslateExtParam};
12use super::{ArgFromStr, CovExtArgs, ExtParam, FromTokenIterError, ParseableExt, TxEnv};
13use crate::miniscript::context::ScriptContextError;
14use crate::miniscript::lex::{Token as Tk, TokenIter};
15use crate::miniscript::limits::MAX_STANDARD_P2WSH_STACK_ITEM_SIZE;
16use crate::miniscript::satisfy::{Satisfaction, Witness};
17use crate::miniscript::types::extra_props::{OpLimits, TimelockInfo};
18use crate::miniscript::types::{Base, Correctness, Dissat, ExtData, Input, Malleability};
19use crate::{
20    expression, interpreter, miniscript, Error, ExtTranslator, Extension, Satisfier, ToPublicKey,
21    TranslateExt,
22};
23
24/// CheckSigFromStack struct
25/// `<msg> <pk> CHECKSIGFROMSTACK`
26#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
27pub struct CheckSigFromStack<T: ExtParam> {
28    /// The public Key to check the signature against
29    pk: T,
30    /// The message to verify the signature
31    msg: T,
32}
33
34impl<T: ExtParam> CheckSigFromStack<T> {
35    /// Obtains the pk
36    pub fn pk(&self) -> &T {
37        &self.pk
38    }
39
40    /// Obtains the pk
41    pub fn msg(&self) -> &T {
42        &self.msg
43    }
44}
45
46impl<T: ExtParam> fmt::Display for CheckSigFromStack<T> {
47    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48        write!(f, "csfs({},{})", self.pk, self.msg)
49    }
50}
51
52impl<T: ExtParam> Extension for CheckSigFromStack<T> {
53    fn corr_prop(&self) -> Correctness {
54        Correctness {
55            base: Base::B,
56            input: Input::One, // one input: signature
57            dissatisfiable: true,
58            unit: true,
59        }
60    }
61
62    fn mall_prop(&self) -> Malleability {
63        Malleability {
64            dissat: Dissat::Unique, // multi-dissat
65            safe: true,
66            non_malleable: true,
67        }
68    }
69
70    fn extra_prop(&self) -> ExtData {
71        ExtData {
72            pk_cost: 32 + 1 + 1 + 32 + 1, // 1 opcodes, 1 key push, msg, 1 msg push
73            has_free_verify: true,        // free verify form. Checksigfromstack verify
74            stack_elem_count_sat: Some(1),
75            stack_elem_count_dissat: Some(1), // supply empty signature for dissatisfaction
76            max_sat_size: Some((64, 64)),
77            max_dissat_size: Some((1, 1)), // empty sig
78            timelock_info: TimelockInfo::default(),
79            exec_stack_elem_count_sat: Some(1),
80            exec_stack_elem_count_dissat: Some(1),
81            ops: OpLimits {
82                // Opcodes are really not relevant in tapscript as BIP342 removes all rules on them
83                count: 1,
84                sat: Some(0),
85                nsat: Some(0),
86            },
87        }
88    }
89
90    fn script_size(&self) -> usize {
91        1 + 32 + 1 // opcode + key+ push
92    }
93
94    fn segwit_ctx_checks(&self) -> Result<(), miniscript::context::ScriptContextError> {
95        // New opcodes only supported in taproot context
96        Err(ScriptContextError::ExtensionError(
97            "CSFS only available in Taproot".to_string(),
98        ))
99    }
100
101    fn from_name_tree(
102        name: &str,
103        children: &[expression::Tree<'_>],
104    ) -> Result<Self, FromTokenIterError> {
105        if children.len() == 2 && name == "csfs" {
106            if !children[0].args.is_empty() || !children[1].args.is_empty() {
107                return Err(FromTokenIterError);
108            }
109            let pk = T::arg_from_str(children[0].name, name, 0).map_err(|_| FromTokenIterError)?;
110            let msg = T::arg_from_str(children[1].name, name, 1).map_err(|_| FromTokenIterError)?;
111            Ok(Self { pk, msg })
112        } else {
113            // Correct error handling while parsing fromtree
114            Err(FromTokenIterError)
115        }
116    }
117}
118
119impl<PArg, QArg> TranslateExt<CheckSigFromStack<PArg>, CheckSigFromStack<QArg>>
120    for CheckSigFromStack<PArg>
121where
122    CheckSigFromStack<PArg>: Extension,
123    CheckSigFromStack<QArg>: Extension,
124    PArg: ExtParam,
125    QArg: ExtParam,
126{
127    type Output = CheckSigFromStack<QArg>;
128
129    fn translate_ext<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
130    where
131        T: ExtTranslator<CheckSigFromStack<PArg>, CheckSigFromStack<QArg>, E>,
132    {
133        t.ext(self)
134    }
135}
136
137// Use ExtParamTranslator as a ExtTranslator
138impl<T, PArg, QArg, E> ExtTranslator<CheckSigFromStack<PArg>, CheckSigFromStack<QArg>, E> for T
139where
140    T: ExtParamTranslator<PArg, QArg, E>,
141    PArg: ExtParam,
142    QArg: ExtParam,
143{
144    /// Translates one extension to another
145    fn ext(&mut self, csfs: &CheckSigFromStack<PArg>) -> Result<CheckSigFromStack<QArg>, E> {
146        TranslateExtParam::translate_ext(csfs, self)
147    }
148}
149
150/// Wrapper around CheckSigFromStack signature messages
151#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
152pub struct CsfsMsg(Vec<u8>);
153
154impl CsfsMsg {
155    /// Creates a new Msg with witness len check
156    /// The current rust-secp API only supports verification of 32 byte signature
157    /// but this should work in elementsd
158    pub fn new(msg: Vec<u8>) -> Option<Self> {
159        // Same rule about initial witness stack item size for tapscript
160        if msg.len() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE {
161            None
162        } else {
163            Some(Self(msg))
164        }
165    }
166
167    /// Creates Self from slice
168    pub fn from_slice(sl: &[u8]) -> Option<Self> {
169        if sl.len() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE {
170            None
171        } else {
172            Some(Self(sl.to_vec()))
173        }
174    }
175
176    /// Obtains the inner slice of this message
177    pub fn as_inner(&self) -> &[u8] {
178        &self.0
179    }
180}
181
182impl fmt::Display for CsfsMsg {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        write!(f, "{}", self.0.to_hex())
185    }
186}
187
188impl ArgFromStr for CsfsMsg {
189    fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
190        if parent != "csfs" || pos != 1 {
191            return Err(Error::Unexpected(
192                "Msg must be the first arg of csfs".to_string(),
193            ));
194        }
195        let inner = Vec::<u8>::from_hex(s).map_err(|e| Error::Unexpected(e.to_string()))?;
196        let inner_len = inner.len();
197        let x = Self::new(inner)
198            .ok_or(hex::Error::InvalidLength(32, inner_len))
199            .map_err(|e| Error::Unexpected(e.to_string()))?;
200        Ok(x)
201    }
202}
203
204/// Wrapper around XOnlyKeys used in CheckSigfromstack
205#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
206pub struct CsfsKey(pub bitcoin::key::XOnlyPublicKey);
207
208impl fmt::Display for CsfsKey {
209    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
210        write!(f, "{}", self.0)
211    }
212}
213
214impl ArgFromStr for CsfsKey {
215    fn arg_from_str(s: &str, parent: &str, pos: usize) -> Result<Self, Error> {
216        if parent != "csfs" || pos != 0 {
217            return Err(Error::Unexpected(
218                "Key must be at first position in csfs".to_string(),
219            ));
220        }
221        let k = bitcoin::key::XOnlyPublicKey::from_str(s)?;
222        Ok(Self(k))
223    }
224}
225
226impl CheckSigFromStack<CovExtArgs> {
227    /// Obtains the XOnlyPublicKey
228    pub fn as_pk(&self) -> &XOnlyPublicKey {
229        if let CovExtArgs::XOnlyKey(CsfsKey(xpk)) = &self.pk {
230            xpk
231        } else {
232            unreachable!(
233                "Both constructors from_str and from_token_iter
234            check that the correct variant is used in xpk"
235            )
236        }
237    }
238
239    /// Obtains the message as Vec
240    pub fn as_msg(&self) -> &CsfsMsg {
241        if let CovExtArgs::CsfsMsg(msg) = &self.msg {
242            msg
243        } else {
244            unreachable!(
245                "Both constructors from_str and from_token_iter
246            check that the correct variant is used in msg"
247            )
248        }
249    }
250}
251
252impl ParseableExt for CheckSigFromStack<CovExtArgs> {
253    fn satisfy<Pk, S>(&self, sat: &S) -> Satisfaction
254    where
255        Pk: ToPublicKey,
256        S: Satisfier<Pk>,
257    {
258        let wit = match sat.lookup_csfs_sig(self.as_pk(), self.as_msg()) {
259            Some(sig) => Witness::Stack(vec![sig.as_ref().to_vec()]),
260            None => Witness::Impossible,
261        };
262        Satisfaction {
263            stack: wit,
264            has_sig: false,
265        }
266    }
267
268    fn dissatisfy<Pk, S>(&self, _sat: &S) -> Satisfaction
269    where
270        Pk: ToPublicKey,
271        S: Satisfier<Pk>,
272    {
273        Satisfaction {
274            stack: Witness::Stack(vec![vec![]]), // empty sig
275            has_sig: false,
276        }
277    }
278
279    fn push_to_builder(&self, builder: elements::script::Builder) -> elements::script::Builder {
280        builder
281            .push_slice(&self.as_msg().0)
282            .push_slice(&self.as_pk().serialize())
283            .push_opcode(opcodes::all::OP_CHECKSIGFROMSTACK)
284    }
285
286    fn from_token_iter(tokens: &mut TokenIter<'_>) -> Result<Self, FromTokenIterError> {
287        let frag = {
288            let sl = tokens.peek_slice(3).ok_or(FromTokenIterError)?;
289            if let (Tk::Bytes32(pk), Tk::Bytes32(msg)) = (&sl[1], &sl[0]) {
290                if sl[2] == Tk::CheckSigFromStack {
291                    let xpk = XOnlyPublicKey::from_slice(pk).map_err(|_| FromTokenIterError)?;
292                    let msg = CsfsMsg::from_slice(msg).ok_or(FromTokenIterError)?;
293                    Self {
294                        pk: CovExtArgs::XOnlyKey(CsfsKey(xpk)),
295                        msg: CovExtArgs::CsfsMsg(msg),
296                    }
297                } else {
298                    return Err(FromTokenIterError);
299                }
300            } else {
301                return Err(FromTokenIterError);
302            }
303        };
304        tokens.advance(3).expect("Size checked previously");
305        Ok(frag)
306    }
307
308    fn evaluate(
309        &self,
310        stack: &mut interpreter::Stack,
311        _txenv: Option<&TxEnv>,
312    ) -> Result<bool, interpreter::Error> {
313        let sig = stack[0].try_push()?;
314
315        if sig.is_empty() {
316            return Ok(false);
317        }
318
319        let sig = secp256k1_zkp::schnorr::Signature::from_slice(sig)?;
320        // rust-secp-zkp API only signing/verification for 32 bytes messages. It is supported in upstream secp-zkp
321        // but bindings are not exposed.
322        // The interpreter will error on non 32 byte messages till it is fixed.
323        let msg = secp256k1_zkp::Message::from_digest_slice(&self.as_msg().0)?;
324
325        let secp = secp256k1_zkp::Secp256k1::verification_only();
326
327        secp.verify_schnorr(&sig, &msg, self.as_pk())?;
328        Ok(true)
329    }
330}
331
332impl<PArg, QArg> TranslateExtParam<PArg, QArg> for CheckSigFromStack<PArg>
333where
334    PArg: ExtParam,
335    QArg: ExtParam,
336{
337    type Output = CheckSigFromStack<QArg>;
338
339    fn translate_ext<T, E>(&self, t: &mut T) -> Result<Self::Output, E>
340    where
341        T: ExtParamTranslator<PArg, QArg, E>,
342        PArg: ExtParam,
343        QArg: ExtParam,
344    {
345        Ok(CheckSigFromStack {
346            pk: t.ext(&self.pk)?,
347            msg: t.ext(&self.msg)?,
348        })
349    }
350}
351
352#[cfg(test)]
353mod tests {
354    use bitcoin::key::XOnlyPublicKey;
355
356    use super::*;
357    use crate::test_utils::{StrExtTranslator, StrXOnlyKeyTranslator};
358    use crate::{Miniscript, Segwitv0, Tap, TranslatePk};
359
360    #[test]
361    fn test_csfs() {
362        type MsExtCsfs = Miniscript<XOnlyPublicKey, Tap, CheckSigFromStack<CovExtArgs>>;
363        type MsExtCsfsSegwitv0 =
364            Miniscript<XOnlyPublicKey, Segwitv0, CheckSigFromStack<CovExtArgs>>;
365
366        type MsExtStr = Miniscript<String, Tap, CheckSigFromStack<String>>;
367
368        // Make sure that parsing this errors in segwit context
369        assert!(MsExtCsfsSegwitv0::from_str_insane(
370            "csfs(26d137d15e2ae24f2d5158663d190d1269ad6b1a6ce330aa825ba502e7519d44,f38b23e7d84506eb8eb477792ba607f908fe8a64ac9ae8dc0e760096e1550562)",
371        )
372        .is_err());
373
374        let ms = MsExtCsfs::from_str_insane(
375            "csfs(26d137d15e2ae24f2d5158663d190d1269ad6b1a6ce330aa825ba502e7519d44,f38b23e7d84506eb8eb477792ba607f908fe8a64ac9ae8dc0e760096e1550562)",
376        )
377        .unwrap();
378        // test string rtt
379        assert_eq!(
380            ms.to_string(),
381            "csfs(26d137d15e2ae24f2d5158663d190d1269ad6b1a6ce330aa825ba502e7519d44,f38b23e7d84506eb8eb477792ba607f908fe8a64ac9ae8dc0e760096e1550562)"
382        );
383        // script rtt
384        assert_eq!(ms, MsExtCsfs::parse_insane(&ms.encode()).unwrap());
385
386        // Test translate
387        // Translation tests to be added in upcoming commits
388
389        let ms = MsExtStr::from_str_insane("and_v(v:csfs(A,msg),pk(B))").unwrap();
390        let mut t = StrXOnlyKeyTranslator::default();
391        t.pk_map.insert(
392            "B".to_string(),
393            bitcoin::key::XOnlyPublicKey::from_str(
394                "9064b3ac01fb4cb648e8899723ee4d50433920ae558c572e96d945805e0bc3ec",
395            )
396            .unwrap(),
397        );
398        let mut ext_t = StrExtTranslator::default();
399        ext_t.ext_map.insert(
400            "msg".to_string(),
401            CovExtArgs::CsfsMsg(CsfsMsg::from_slice(&[0xab; 32]).unwrap()),
402        );
403        ext_t.ext_map.insert(
404            "A".to_string(),
405            CovExtArgs::XOnlyKey(CsfsKey(
406                bitcoin::key::XOnlyPublicKey::from_str(
407                    "26d137d15e2ae24f2d5158663d190d1269ad6b1a6ce330aa825ba502e7519d44",
408                )
409                .unwrap(),
410            )),
411        );
412
413        let ms_translated = ms.translate_pk(&mut t).unwrap();
414        let ms_translated = ms_translated.translate_ext(&mut ext_t).unwrap();
415
416        assert_eq!(ms_translated.to_string(), "and_v(v:csfs(26d137d15e2ae24f2d5158663d190d1269ad6b1a6ce330aa825ba502e7519d44,abababababababababababababababababababababababababababababababab),pk(9064b3ac01fb4cb648e8899723ee4d50433920ae558c572e96d945805e0bc3ec))");
417    }
418}