Skip to content

Commit 6c9e436

Browse files
committed
Interpreter sighash API support, no more closures
1 parent 21d182a commit 6c9e436

File tree

2 files changed

+115
-18
lines changed

2 files changed

+115
-18
lines changed

src/interpreter/inner.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,6 @@ pub enum ScriptType {
8383
Sh,
8484
Wsh,
8585
ShWsh,
86-
#[allow(dead_code)]
8786
Tr, // Script Spend
8887
}
8988

@@ -234,17 +233,23 @@ pub(super) fn from_txdata<'txin>(
234233
let ms = taproot_to_no_checks(&tap_script);
235234
// Creating new contexts is cheap
236235
let secp = bitcoin::secp256k1::Secp256k1::verification_only();
236+
let tap_script = tap_script.encode();
237237
// Should not really need to call dangerous assumed tweaked here.
238238
// Should be fixed after RC
239239
if ctrl_blk.verify_taproot_commitment(
240240
&secp,
241241
&output_key.dangerous_assume_tweaked(),
242-
&tap_script.encode(),
242+
&tap_script,
243243
) {
244244
Ok((
245245
Inner::Script(ms, ScriptType::Tr),
246246
wit_stack,
247-
None, // Tr script code None
247+
// Tr script spend code is stored in script spend. This is an internal hack to store the
248+
// encoded script instead yet another tagged enum for saving tap_script
249+
// We can recompute the script again by translating from nochecks to tap and re-encoding it
250+
// Having this hack seems simple because this is not exposed publicly and the internal code is
251+
// easy to audit
252+
Some(tap_script),
248253
))
249254
} else {
250255
return Err(Error::ControlBlockVerificationError);

src/interpreter/mod.rs

Lines changed: 107 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
//!
2121
2222
use bitcoin::blockdata::witness::Witness;
23-
use bitcoin::util::sighash;
23+
use bitcoin::util::{sighash, taproot};
2424
use std::fmt;
2525
use std::str::FromStr;
2626

@@ -206,6 +206,92 @@ impl<'txin> Interpreter<'txin> {
206206
}
207207
}
208208

209+
/// Verify a signature for a given transaction and prevout information
210+
/// This is a low level API, [`Interpreter::iter`] or [`Interpreter::iter_assume_sig`]
211+
/// should satisfy most use-cases.
212+
/// Returns false if
213+
/// - the signature verification fails
214+
/// - the input index is out of range
215+
/// - Insufficient sighash information is present
216+
/// - sighash single without corresponding output
217+
// TODO: Create a good first isse to change this to error
218+
pub fn verify_sig<C: secp256k1::Verification>(
219+
&self,
220+
secp: &secp256k1::Secp256k1<C>,
221+
tx: &bitcoin::Transaction,
222+
input_idx: usize,
223+
prevouts: &sighash::Prevouts,
224+
sig: &KeySigPair,
225+
) -> bool {
226+
fn get_prevout<'u>(
227+
prevouts: &sighash::Prevouts<'u>,
228+
input_index: usize,
229+
) -> Option<&'u bitcoin::TxOut> {
230+
match prevouts {
231+
sighash::Prevouts::One(index, prevout) => {
232+
if input_index == *index {
233+
Some(prevout)
234+
} else {
235+
None
236+
}
237+
}
238+
sighash::Prevouts::All(prevouts) => prevouts.get(input_index),
239+
}
240+
}
241+
let mut cache = bitcoin::util::sighash::SigHashCache::new(tx);
242+
match sig {
243+
KeySigPair::Ecdsa(key, ecdsa_sig) => {
244+
let script_pubkey = self.script_code.as_ref().expect("Legacy have script code");
245+
let sighash = if self.is_legacy() {
246+
let sighash_u32 = ecdsa_sig.hash_ty.as_u32();
247+
cache.legacy_signature_hash(input_idx, &script_pubkey, sighash_u32)
248+
} else if self.is_segwit_v0() {
249+
let amt = match get_prevout(prevouts, input_idx) {
250+
Some(txout) => txout.value,
251+
None => return false,
252+
};
253+
cache.segwit_signature_hash(input_idx, &script_pubkey, amt, ecdsa_sig.hash_ty)
254+
} else {
255+
// taproot(or future) signatures in segwitv0 context
256+
return false;
257+
};
258+
let msg =
259+
sighash.map(|hash| secp256k1::Message::from_slice(&hash).expect("32 byte"));
260+
let success =
261+
msg.map(|msg| secp.verify_ecdsa(&msg, &ecdsa_sig.sig, &key.inner).is_ok());
262+
success.unwrap_or(false) // unwrap_or checks for errors, while success would have checksig results
263+
}
264+
KeySigPair::Schnorr(xpk, schnorr_sig) => {
265+
let sighash_msg = if self.is_taproot_v1_key_spend() {
266+
cache.taproot_key_spend_signature_hash(input_idx, prevouts, schnorr_sig.hash_ty)
267+
} else if self.is_taproot_v1_script_spend() {
268+
let tap_script = self.script_code.as_ref().expect(
269+
"Internal Hack: Saving leaf script instead\
270+
of script code for script spend",
271+
);
272+
let leaf_hash = taproot::TapLeafHash::from_script(
273+
&tap_script,
274+
taproot::LeafVersion::TapScript,
275+
);
276+
cache.taproot_script_spend_signature_hash(
277+
input_idx,
278+
prevouts,
279+
leaf_hash,
280+
schnorr_sig.hash_ty,
281+
)
282+
} else {
283+
// schnorr sigs in ecdsa descriptors
284+
return false;
285+
};
286+
let msg =
287+
sighash_msg.map(|hash| secp256k1::Message::from_slice(&hash).expect("32 byte"));
288+
let success =
289+
msg.map(|msg| secp.verify_schnorr(&schnorr_sig.sig, &msg, &xpk).is_ok());
290+
success.unwrap_or(false) // unwrap_or_default checks for errors, while success would have checksig results
291+
}
292+
}
293+
}
294+
209295
/// Iterate over all satisfied constraints while checking signatures
210296
/// Not all fields are used by legacy/segwitv0 descriptors; if you are sure this is a legacy
211297
/// spend (you can check with the `is_legacy\is_segwitv0` method) you can provide dummy data for
@@ -216,22 +302,12 @@ impl<'txin> Interpreter<'txin> {
216302
pub fn iter_check_sigs<'iter, C: secp256k1::Verification>(
217303
&'iter self,
218304
secp: &'iter secp256k1::Secp256k1<C>,
219-
tx: &'iter bitcoin::Transaction, // actually a 'txin, but 'txin : 'iter
305+
tx: &'txin bitcoin::Transaction,
220306
input_idx: usize,
221307
prevouts: &'iter sighash::Prevouts, // actually a 'prevouts, but 'prevouts: 'iter
222308
) -> Iter<'txin, 'iter> {
223-
fn verify_sig<C: secp256k1::Verification>(
224-
_secp: &secp256k1::Secp256k1<C>,
225-
_tx: &bitcoin::Transaction,
226-
_input_idx: usize,
227-
_prevouts: &sighash::Prevouts,
228-
_sig: &KeySigPair,
229-
) -> bool {
230-
panic!("TODO");
231-
}
232-
let _warn_error = &self.script_code;
233309
self.iter(Box::new(move |sig| {
234-
verify_sig(secp, tx, input_idx, prevouts, sig)
310+
self.verify_sig(secp, tx, input_idx, prevouts, sig)
235311
}))
236312
}
237313

@@ -310,8 +386,8 @@ impl<'txin> Interpreter<'txin> {
310386
}
311387
}
312388

313-
/// Whether this is a taproot spend
314-
pub fn is_taproot_v1(&self) -> bool {
389+
/// Whether this is a taproot key spend
390+
pub fn is_taproot_v1_key_spend(&self) -> bool {
315391
match self.inner {
316392
inner::Inner::PublicKey(_, inner::PubkeyType::Pk) => false,
317393
inner::Inner::PublicKey(_, inner::PubkeyType::Pkh) => false,
@@ -322,6 +398,22 @@ impl<'txin> Interpreter<'txin> {
322398
inner::Inner::Script(_, inner::ScriptType::Sh) => false,
323399
inner::Inner::Script(_, inner::ScriptType::Wsh) => false,
324400
inner::Inner::Script(_, inner::ScriptType::ShWsh) => false,
401+
inner::Inner::Script(_, inner::ScriptType::Tr) => false,
402+
}
403+
}
404+
405+
/// Whether this is a taproot script spend
406+
pub fn is_taproot_v1_script_spend(&self) -> bool {
407+
match self.inner {
408+
inner::Inner::PublicKey(_, inner::PubkeyType::Pk) => false,
409+
inner::Inner::PublicKey(_, inner::PubkeyType::Pkh) => false,
410+
inner::Inner::PublicKey(_, inner::PubkeyType::Wpkh) => false,
411+
inner::Inner::PublicKey(_, inner::PubkeyType::ShWpkh) => false,
412+
inner::Inner::PublicKey(_, inner::PubkeyType::Tr) => false,
413+
inner::Inner::Script(_, inner::ScriptType::Bare) => false,
414+
inner::Inner::Script(_, inner::ScriptType::Sh) => false,
415+
inner::Inner::Script(_, inner::ScriptType::Wsh) => false,
416+
inner::Inner::Script(_, inner::ScriptType::ShWsh) => false,
325417
inner::Inner::Script(_, inner::ScriptType::Tr) => true,
326418
}
327419
}

0 commit comments

Comments
 (0)