1use 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#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Clone)]
27pub struct CheckSigFromStack<T: ExtParam> {
28 pk: T,
30 msg: T,
32}
33
34impl<T: ExtParam> CheckSigFromStack<T> {
35 pub fn pk(&self) -> &T {
37 &self.pk
38 }
39
40 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, dissatisfiable: true,
58 unit: true,
59 }
60 }
61
62 fn mall_prop(&self) -> Malleability {
63 Malleability {
64 dissat: Dissat::Unique, 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, has_free_verify: true, stack_elem_count_sat: Some(1),
75 stack_elem_count_dissat: Some(1), max_sat_size: Some((64, 64)),
77 max_dissat_size: Some((1, 1)), timelock_info: TimelockInfo::default(),
79 exec_stack_elem_count_sat: Some(1),
80 exec_stack_elem_count_dissat: Some(1),
81 ops: OpLimits {
82 count: 1,
84 sat: Some(0),
85 nsat: Some(0),
86 },
87 }
88 }
89
90 fn script_size(&self) -> usize {
91 1 + 32 + 1 }
93
94 fn segwit_ctx_checks(&self) -> Result<(), miniscript::context::ScriptContextError> {
95 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 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
137impl<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 fn ext(&mut self, csfs: &CheckSigFromStack<PArg>) -> Result<CheckSigFromStack<QArg>, E> {
146 TranslateExtParam::translate_ext(csfs, self)
147 }
148}
149
150#[derive(Debug, Clone, Eq, Ord, PartialOrd, PartialEq, Hash)]
152pub struct CsfsMsg(Vec<u8>);
153
154impl CsfsMsg {
155 pub fn new(msg: Vec<u8>) -> Option<Self> {
159 if msg.len() > MAX_STANDARD_P2WSH_STACK_ITEM_SIZE {
161 None
162 } else {
163 Some(Self(msg))
164 }
165 }
166
167 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 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#[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 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 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![]]), 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 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 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 assert_eq!(
380 ms.to_string(),
381 "csfs(26d137d15e2ae24f2d5158663d190d1269ad6b1a6ce330aa825ba502e7519d44,f38b23e7d84506eb8eb477792ba607f908fe8a64ac9ae8dc0e760096e1550562)"
382 );
383 assert_eq!(ms, MsExtCsfs::parse_insane(&ms.encode()).unwrap());
385
386 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}