Skip to content

Taproot interpreter support #301

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Mar 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ rand = ["bitcoin/rand"]

[dependencies]
# bitcoin = "0.27"
bitcoin = {git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "0e2e55971275da64ceb62e8991a0a5fa962cb8b1"}
bitcoin = "0.28.0-rc.1"

[dependencies.serde]
version = "1.0"
Expand Down
43 changes: 23 additions & 20 deletions examples/verify_tx.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ extern crate miniscript;

use bitcoin::consensus::Decodable;
use bitcoin::secp256k1; // secp256k1 re-exported from rust-bitcoin
use bitcoin::util::sighash;
use miniscript::interpreter::KeySigPair;
use std::str::FromStr;

fn main() {
Expand Down Expand Up @@ -83,7 +85,7 @@ fn main() {
0xa9, 0x14, 0x92, 0x09, 0xa8, 0xf9, 0x0c, 0x58, 0x4b, 0xb5, 0x97, 0x4d, 0x58, 0x68, 0x72,
0x49, 0xe5, 0x32, 0xde, 0x59, 0xf4, 0xbc, 0x87,
]);
let mut interpreter = miniscript::Interpreter::from_txdata(
let interpreter = miniscript::Interpreter::from_txdata(
&spk_input_1,
&transaction.input[0].script_sig,
&transaction.input[0].witness,
Expand All @@ -105,10 +107,14 @@ fn main() {
// the blockchain, standardness would've required they be
// either valid or 0-length.
println!("\nExample one");
for elem in interpreter.iter(|_, _| true) {
for elem in interpreter.iter_assume_sigs() {
// Don't bother checking signatures
match elem.expect("no evaluation error") {
miniscript::interpreter::SatisfiedConstraint::PublicKey { key, sig } => {
miniscript::interpreter::SatisfiedConstraint::PublicKey { key_sig } => {
// Check that the signature is ecdsa sig
let (key, sig) = key_sig
.as_ecdsa()
.expect("Expected Ecdsa sig, found schnorr sig");
println!("Signed with {}: {}", key, sig);
}
_ => {}
Expand All @@ -122,7 +128,7 @@ fn main() {
// from the MiniscriptKey which can supplied by `to_pk_ctx` parameter. For example,
// when calculating the script pubkey of a descriptor with xpubs, the secp context and
// child information maybe required.
let mut interpreter = miniscript::Interpreter::from_txdata(
let interpreter = miniscript::Interpreter::from_txdata(
&spk_input_1,
&transaction.input[0].script_sig,
&transaction.input[0].witness,
Expand All @@ -131,21 +137,15 @@ fn main() {
)
.unwrap();

// We can set the amount passed to `sighash_verify` to 0 because this is a legacy
// transaction and so the amount won't actually be checked by the signature
let vfyfn = interpreter
.sighash_verify(&secp, &transaction, 0, 0)
.expect("Can only fail in sighash single when corresponding output is not present");
// Restrict to sighash_all just to demonstrate how to add additional filters
// `&_` needed here because of https://github.com/rust-lang/rust/issues/79187
let vfyfn = move |pk: &_, bitcoinsig: miniscript::bitcoin::EcdsaSig| {
bitcoinsig.hash_ty == bitcoin::EcdsaSigHashType::All && vfyfn(pk, bitcoinsig)
};
// We can set prevouts to be empty list because this is a legacy transaction
// and this information is not required for sighash computation.
let prevouts = sighash::Prevouts::All(&[]);

println!("\nExample two");
for elem in interpreter.iter(vfyfn) {
for elem in interpreter.iter(&secp, &transaction, 0, &prevouts) {
match elem.expect("no evaluation error") {
miniscript::interpreter::SatisfiedConstraint::PublicKey { key, sig } => {
miniscript::interpreter::SatisfiedConstraint::PublicKey { key_sig } => {
let (key, sig) = key_sig.as_ecdsa().unwrap();
println!("Signed with {}: {}", key, sig);
}
_ => {}
Expand All @@ -156,7 +156,7 @@ fn main() {
// what happens given an apparently invalid script
let secp = secp256k1::Secp256k1::new();
let message = secp256k1::Message::from_slice(&[0x01; 32][..]).expect("32-byte hash");
let mut interpreter = miniscript::Interpreter::from_txdata(
let interpreter = miniscript::Interpreter::from_txdata(
&spk_input_1,
&transaction.input[0].script_sig,
&transaction.input[0].witness,
Expand All @@ -165,10 +165,13 @@ fn main() {
)
.unwrap();

let iter = interpreter.iter(|pk, ecdsa_sig| {
let iter = interpreter.iter_custom(Box::new(|key_sig: &KeySigPair| {
let (pk, ecdsa_sig) = key_sig.as_ecdsa().expect("Ecdsa Sig");
ecdsa_sig.hash_ty == bitcoin::EcdsaSigHashType::All
&& secp.verify_ecdsa(&message, &ecdsa_sig.sig, &pk.key).is_ok()
});
&& secp
.verify_ecdsa(&message, &ecdsa_sig.sig, &pk.inner)
.is_ok()
}));
println!("\nExample three");
for elem in iter {
let error = elem.expect_err("evaluation error");
Expand Down
4 changes: 2 additions & 2 deletions integration_test/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,6 @@ authors = ["Steven Roose <[email protected]>", "Sanket K <sanket1729@gmail.
miniscript = {path = "../"}

# Until 0.26 support is released on rust-bitcoincore-rpc
bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "ae3ad6cac0a83454f267cb7d5191f6607bb80297"}
bitcoin = {git = "https://github.com/rust-bitcoin/rust-bitcoin", rev = "0e2e55971275da64ceb62e8991a0a5fa962cb8b1"}
bitcoincore-rpc = {git = "https://github.com/sanket1729/rust-bitcoincore-rpc",rev = "bcc35944b3dd636cdff9710f90f8e0cfcab28f27"}
bitcoin = "0.28.0-rc.1"
log = "0.4"
2 changes: 1 addition & 1 deletion integration_test/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,7 @@ fn main() {
for sk in sks_reqd {
let sig = secp.sign_ecdsa(&msg, &sk);
let pk = pks[sks.iter().position(|&x| x == sk).unwrap()];
psbts[i].inputs[0].partial_sigs.insert(pk, bitcoin::EcdsaSig { sig, hash_ty: sighash_ty });
psbts[i].inputs[0].partial_sigs.insert(pk.inner, bitcoin::EcdsaSig { sig, hash_ty: sighash_ty });
}
// Add the hash preimages to the psbt
psbts[i].inputs[0].sha256_preimages.insert(
Expand Down
2 changes: 1 addition & 1 deletion integration_test/src/read_file.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ fn setup_keys(

let sk = secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key");
let pk = miniscript::bitcoin::PublicKey {
key: secp256k1::PublicKey::from_secret_key(&secp_sign, &sk),
inner: secp256k1::PublicKey::from_secret_key(&secp_sign, &sk),
compressed: true,
};
pks.push(pk);
Expand Down
3 changes: 1 addition & 2 deletions src/descriptor/key.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,10 @@ use bitcoin::{
self,
hashes::Hash,
hashes::{hex::FromHex, HashEngine},
schnorr::XOnlyPublicKey,
secp256k1,
secp256k1::{Secp256k1, Signing},
util::bip32,
XpubIdentifier,
XOnlyPublicKey, XpubIdentifier,
};

use {MiniscriptKey, ToPublicKey};
Expand Down
5 changes: 1 addition & 4 deletions src/descriptor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1011,10 +1011,7 @@ mod tests {
let secp = secp256k1::Secp256k1::new();
let sk =
secp256k1::SecretKey::from_slice(&b"sally was a secret key, she said"[..]).unwrap();
let pk = bitcoin::PublicKey {
key: secp256k1::PublicKey::from_secret_key(&secp, &sk),
compressed: true,
};
let pk = bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, &sk));
let msg = secp256k1::Message::from_slice(&b"michael was a message, amusingly"[..])
.expect("32 bytes");
let sig = secp.sign_ecdsa(&msg, &sk);
Expand Down
4 changes: 2 additions & 2 deletions src/descriptor/sortedmulti.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,9 +140,9 @@ impl<Pk: MiniscriptKey, Ctx: ScriptContext> SortedMultiVec<Pk, Ctx> {
// Sort pubkeys lexicographically according to BIP 67
pks.sort_by(|a, b| {
a.to_public_key()
.key
.inner
.serialize()
.partial_cmp(&b.to_public_key().key.serialize())
.partial_cmp(&b.to_public_key().inner.serialize())
.unwrap()
});
Terminal::Multi(self.k, pks)
Expand Down
82 changes: 79 additions & 3 deletions src/interpreter/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,30 @@
//

use bitcoin::hashes::{hash160, hex::ToHex};
use bitcoin::util::taproot;
use bitcoin::{self, secp256k1};
use std::{error, fmt};

use super::BitcoinKey;

/// Detailed Error type for Interpreter
#[derive(Debug)]
pub enum Error {
/// Could not satisfy, absolute locktime not met
AbsoluteLocktimeNotMet(u32),
/// Cannot Infer a taproot descriptor
/// Key spends cannot infer the internal key of the descriptor
/// Inferring script spends is possible, but is hidden nodes are currently
/// not supported in descriptor spec
CannotInferTrDescriptors,
/// Error parsing taproot control block
ControlBlockParse(taproot::TaprootError),
/// Tap control block(merkle proofs + tweak) verification error
ControlBlockVerificationError,
/// General Interpreter error.
CouldNotEvaluate,
/// EcdsaSig related error
EcdsaSig(bitcoin::EcdsaSigError),
/// We expected a push (including a `OP_1` but no other numeric pushes)
ExpectedPush,
/// The preimage to the hash function must be exactly 32 bytes.
Expand All @@ -39,8 +53,10 @@ pub enum Error {
InsufficientSignaturesMultiSig,
/// Invalid Sighash type
InvalidSchnorrSigHashType(Vec<u8>),
/// ecdsa Signature failed to verify
InvalidEcdsaSignature(bitcoin::PublicKey),
/// Signature failed to verify
InvalidSignature(bitcoin::PublicKey),
InvalidSchnorrSignature(bitcoin::XOnlyPublicKey),
/// Last byte of this signature isn't a standard sighash type
NonStandardSigHash(Vec<u8>),
/// Miniscript error
Expand All @@ -60,21 +76,29 @@ pub enum Error {
/// Any input witness apart from sat(sig) or nsat(0) leads to
/// this error. This is network standardness assumption and miniscript only
/// supports standard scripts
PkEvaluationError(bitcoin::PublicKey),
// note that BitcoinKey is not exported, create a data structure to convey the same
// information in error
PkEvaluationError(PkEvalErrInner),
/// The Public Key hash check for the given pubkey. This occurs in `PkH`
/// node when the given key does not match to Hash in script.
PkHashVerifyFail(hash160::Hash),
/// Parse Error while parsing a `stack::Element::Push` as a Pubkey. Both
/// 33 byte and 65 bytes are supported.
PubkeyParseError,
/// Parse Error while parsing a `stack::Element::Push` as a XOnlyPublicKey (32 bytes)
XOnlyPublicKeyParseError,
/// Could not satisfy, relative locktime not met
RelativeLocktimeNotMet(u32),
/// Forward-secp related errors
Secp(secp256k1::Error),
/// Miniscript requires the entire top level script to be satisfied.
ScriptSatisfactionError,
/// Schnorr Signature error
SchnorrSig(bitcoin::SchnorrSigError),
/// Errors in signature hash calculations
SighashError(bitcoin::util::sighash::Error),
/// Taproot Annex Unsupported
TapAnnexUnsupported,
/// An uncompressed public key was encountered in a context where it is
/// disallowed (e.g. in a Segwit script or p2wpkh output)
UncompressedPubkey,
Expand All @@ -92,6 +116,34 @@ pub enum Error {
VerifyFailed,
}

/// A type of representing which keys errored during interpreter checksig evaluation
// Note that we can't use BitcoinKey because it is not public
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum PkEvalErrInner {
/// Full Key
FullKey(bitcoin::PublicKey),
/// XOnly Key
XOnlyKey(bitcoin::XOnlyPublicKey),
}

impl From<BitcoinKey> for PkEvalErrInner {
fn from(pk: BitcoinKey) -> Self {
match pk {
BitcoinKey::Fullkey(pk) => PkEvalErrInner::FullKey(pk),
BitcoinKey::XOnlyPublicKey(xpk) => PkEvalErrInner::XOnlyKey(xpk),
}
}
}

impl fmt::Display for PkEvalErrInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PkEvalErrInner::FullKey(pk) => pk.fmt(f),
PkEvalErrInner::XOnlyKey(xpk) => xpk.fmt(f),
}
}
}

#[doc(hidden)]
impl From<secp256k1::Error> for Error {
fn from(e: secp256k1::Error) -> Error {
Expand All @@ -106,6 +158,20 @@ impl From<bitcoin::util::sighash::Error> for Error {
}
}

#[doc(hidden)]
impl From<bitcoin::EcdsaSigError> for Error {
fn from(e: bitcoin::EcdsaSigError) -> Error {
Error::EcdsaSig(e)
}
}

#[doc(hidden)]
impl From<bitcoin::SchnorrSigError> for Error {
fn from(e: bitcoin::SchnorrSigError) -> Error {
Error::SchnorrSig(e)
}
}

#[doc(hidden)]
impl From<::Error> for Error {
fn from(e: ::Error) -> Error {
Expand All @@ -130,6 +196,12 @@ impl fmt::Display for Error {
"required absolute locktime CLTV of {} blocks, not met",
n
),
Error::CannotInferTrDescriptors => write!(f, "Cannot infer taproot descriptors"),
Error::ControlBlockParse(ref e) => write!(f, "Control block parse error {}", e),
Error::ControlBlockVerificationError => {
f.write_str("Control block verification failed")
}
Error::EcdsaSig(ref s) => write!(f, "Ecdsa sig error: {}", s),
Error::ExpectedPush => f.write_str("expected push in script"),
Error::CouldNotEvaluate => f.write_str("Interpreter Error: Could not evaluate"),
Error::HashPreimageLengthMismatch => f.write_str("Hash preimage should be 32 bytes"),
Expand All @@ -147,7 +219,8 @@ impl fmt::Display for Error {
sig.to_hex()
)
}
Error::InvalidSignature(pk) => write!(f, "bad signature with pk {}", pk),
Error::InvalidEcdsaSignature(pk) => write!(f, "bad ecdsa signature with pk {}", pk),
Error::InvalidSchnorrSignature(pk) => write!(f, "bad schnorr signature with pk {}", pk),
Error::NonStandardSigHash(ref sig) => {
write!(
f,
Expand All @@ -165,12 +238,15 @@ impl fmt::Display for Error {
Error::PkEvaluationError(ref key) => write!(f, "Incorrect Signature for pk {}", key),
Error::PkHashVerifyFail(ref hash) => write!(f, "Pubkey Hash check failed {}", hash),
Error::PubkeyParseError => f.write_str("could not parse pubkey"),
Error::XOnlyPublicKeyParseError => f.write_str("could not parse x-only pubkey"),
Error::RelativeLocktimeNotMet(n) => {
write!(f, "required relative locktime CSV of {} blocks, not met", n)
}
Error::ScriptSatisfactionError => f.write_str("Top level script must be satisfied"),
Error::Secp(ref e) => fmt::Display::fmt(e, f),
Error::SchnorrSig(ref s) => write!(f, "Schnorr sig error: {}", s),
Error::SighashError(ref e) => fmt::Display::fmt(e, f),
Error::TapAnnexUnsupported => f.write_str("Encountered annex element"),
Error::UncompressedPubkey => {
f.write_str("uncompressed pubkey in non-legacy descriptor")
}
Expand Down
Loading