Skip to content

Commit

Permalink
WIP: Attempt to implement new Taproot verification
Browse files Browse the repository at this point in the history
  • Loading branch information
tcharding committed Feb 22, 2024
1 parent 5af9417 commit 188d51e
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 23 deletions.
6 changes: 3 additions & 3 deletions contrib/vendor-bitcoin-core.sh
Original file line number Diff line number Diff line change
Expand Up @@ -105,9 +105,9 @@ fi

# Check out specified revision
pushd "$DIR" > /dev/null
if [ -n "$CORE_REV" ]; then
git checkout "$CORE_REV"
fi
# if [ -n "$CORE_REV" ]; then
# git checkout "$CORE_REV"
# fi
SOURCE_REV=$(git rev-parse HEAD || echo "[unknown revision from $CORE_VENDOR_REPO]")
rm -rf .git/ || true
popd
Expand Down
2 changes: 1 addition & 1 deletion depend/bitcoin-HEAD-revision.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# This file was automatically created by vendor-bitcoin-core.sh
44d8b13c81e5276eb610c99f227a4d090cc532f6
e71825a6ae8c73a99160b9513572ef6df1c1bbf8
24 changes: 18 additions & 6 deletions depend/bitcoin/src/script/bitcoinconsensus.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@
#include <primitives/transaction.h>
#include <pubkey.h>
#include <script/interpreter.h>
#include <script/script_error.h>
#include <version.h>

#include <iostream>
#include <script/script_error.h>

namespace {

/** A class that deserializes a single CTransaction one time. */
Expand Down Expand Up @@ -62,6 +66,13 @@ inline int set_error(bitcoinconsensus_error* ret, bitcoinconsensus_error serror)
return 0;
}

inline int set_script_error(ScriptError* ret, ScriptError serror)
{
if (ret)
*ret = serror;
return 0;
}

} // namespace

/** Check that all specified flags are part of the libconsensus interface. */
Expand All @@ -73,7 +84,8 @@ static bool verify_flags(unsigned int flags)
static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, CAmount amount,
const unsigned char *txTo , unsigned int txToLen,
const UTXO *spentOutputs, unsigned int spentOutputsLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err,
ScriptError *script_error)
{
if (!verify_flags(flags)) {
return set_error(err, bitcoinconsensus_ERR_INVALID_FLAGS);
Expand Down Expand Up @@ -114,7 +126,7 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
txdata.Init(tx, std::move(spent_outputs));
}

return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), nullptr);
return VerifyScript(tx.vin[nIn].scriptSig, CScript(scriptPubKey, scriptPubKey + scriptPubKeyLen), &tx.vin[nIn].scriptWitness, flags, TransactionSignatureChecker(&tx, nIn, amount, txdata, MissingDataBehavior::FAIL), script_error);
} catch (const std::exception&) {
return set_error(err, bitcoinconsensus_ERR_TX_DESERIALIZE); // Error deserializing
}
Expand All @@ -123,10 +135,10 @@ static int verify_script(const unsigned char *scriptPubKey, unsigned int scriptP
int bitcoinconsensus_verify_script_with_spent_outputs(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
const unsigned char *txTo , unsigned int txToLen,
const UTXO *spentOutputs, unsigned int spentOutputsLen,
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err)
unsigned int nIn, unsigned int flags, bitcoinconsensus_error* err, ScriptError *script_error)
{
CAmount am(amount);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err, script_error);
}

int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey, unsigned int scriptPubKeyLen, int64_t amount,
Expand All @@ -136,7 +148,7 @@ int bitcoinconsensus_verify_script_with_amount(const unsigned char *scriptPubKey
CAmount am(amount);
UTXO *spentOutputs = nullptr;
unsigned int spentOutputsLen = 0;
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err, nullptr);
}


Expand All @@ -151,7 +163,7 @@ int bitcoinconsensus_verify_script(const unsigned char *scriptPubKey, unsigned i
CAmount am(0);
UTXO *spentOutputs = nullptr;
unsigned int spentOutputsLen = 0;
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err);
return ::verify_script(scriptPubKey, scriptPubKeyLen, am, txTo, txToLen, spentOutputs, spentOutputsLen, nIn, flags, err, nullptr);
}

unsigned int bitcoinconsensus_version()
Expand Down
134 changes: 121 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ mod types;

use core::fmt;

use crate::types::c_uint;
use crate::types::{c_int64, c_uchar, c_uint};

/// Do not enable any verification.
pub const VERIFY_NONE: c_uint = 0;
Expand All @@ -33,8 +33,10 @@ pub const VERIFY_CHECKLOCKTIMEVERIFY: c_uint = 1 << 9;
pub const VERIFY_CHECKSEQUENCEVERIFY: c_uint = 1 << 10;
/// Enable WITNESS (BIP141).
pub const VERIFY_WITNESS: c_uint = 1 << 11;
/// Enable TAPROOT (BIPs 341 & 342)
pub const VERIFY_TAPROOT: c_uint = 1 << 17;

pub const VERIFY_ALL: c_uint = VERIFY_P2SH
pub const VERIFY_ALL_PRE_TAPROOT: c_uint = VERIFY_P2SH
| VERIFY_DERSIG
| VERIFY_NULLDUMMY
| VERIFY_CHECKLOCKTIMEVERIFY
Expand All @@ -60,6 +62,9 @@ pub fn height_to_flags(height: u32) -> u32 {
if height >= 481824 {
flag |= VERIFY_NULLDUMMY | VERIFY_WITNESS
}
if height > 709632 {
flag |= VERIFY_TAPROOT
}

flag
}
Expand Down Expand Up @@ -100,36 +105,105 @@ pub fn version() -> u32 { unsafe { ffi::bitcoinconsensus_version() as u32 } }
///
/// **Note** since the spent amount will only be checked for Segwit transactions and the above
/// example is not segwit, `verify` will succeed with any amount.
pub fn verify(
pub fn verify_with_amount(
// The script_pubkey of the output we are spending (e.g. `TxOut::script_pubkey.as_bytes()`).
spent_output: &[u8],
amount: u64, // The amount of the output is spending (e.g. `TxOut::value`)
spending_transaction: &[u8], // Create using `tx.serialize().as_slice()`
input_index: usize,
) -> Result<(), Error> {
verify_with_amount_and_flags(
spent_output,
amount,
spending_transaction,
input_index,
VERIFY_ALL_PRE_TAPROOT,
)
}

pub fn verify_with_spent_outputs(
// The script_pubkey of the output we are spending (e.g. `TxOut::script_pubkey.as_bytes()`).
spent_output: &[u8],
amount: u64, // The amount that the output is spending (e.g. `TxOut::value`).
spending_transaction: &[u8], // Create using `tx.serialize().as_slice()`.
spent_outputs: &[Utxo],
input_index: usize,
) -> Result<(), Error> {
verify_with_spent_outputs_and_flags(
spent_output,
amount,
spending_transaction,
spent_outputs,
input_index,
VERIFY_ALL_PRE_TAPROOT | VERIFY_TAPROOT,
)
}

/// Same as verify but with flags that turn past soft fork features on or off.
pub fn verify_with_amount_and_flags(
spent_output_script: &[u8],
amount: u64,
spending_transaction: &[u8],
input_index: usize,
flags: u32,
) -> Result<(), Error> {
verify_with_flags(spent_output, amount, spending_transaction, input_index, VERIFY_ALL)
unsafe {
let mut error = Error::ERR_SCRIPT;

let ret = ffi::bitcoinconsensus_verify_script_with_amount(
spent_output_script.as_ptr(),
spent_output_script.len() as c_uint,
amount,
spending_transaction.as_ptr(),
spending_transaction.len() as c_uint,
input_index as c_uint,
flags as c_uint,
&mut error,
);
if ret != 1 {
Err(error)
} else {
Ok(())
}
}
}

/// Mimics the Bitcoin Core UTXO typedef (bitcoinconsenus.h)
// This is the TxOut data for utxos being spent, i.e., previous outputs.
#[repr(C)]
pub struct Utxo<'a> {
pub script_pubkey: &'a [u8],
pub script_pubkey_len: c_uint,
pub value: c_int64,
}

/// Same as verify but with flags that turn past soft fork features on or off.
pub fn verify_with_flags(
pub fn verify_with_spent_outputs_and_flags(
spent_output_script: &[u8],
amount: u64,
spending_transaction: &[u8],
spent_outputs: &[Utxo],
input_index: usize,
flags: u32,
) -> Result<(), Error> {
unsafe {
let mut error = Error::ERR_SCRIPT;
let mut script_error = 0;

let ret = ffi::bitcoinconsensus_verify_script_with_amount(
let ret = ffi::bitcoinconsensus_verify_script_with_spent_outputs(
spent_output_script.as_ptr(),
spent_output_script.len() as c_uint,
amount,
spending_transaction.as_ptr(),
spending_transaction.len() as c_uint,
spent_outputs.as_ptr() as *const c_uchar,
spent_outputs.len() as c_uint,
input_index as c_uint,
flags as c_uint,
&mut error,
&mut script_error,
);
println!("script error: {}", script_error);
if ret != 1 {
Err(error)
} else {
Expand All @@ -139,15 +213,17 @@ pub fn verify_with_flags(
}

pub mod ffi {
use crate::types::{c_int, c_uchar, c_uint};
use crate::Error;
use super::*;
use crate::types::c_int;

extern "C" {
/// Returns `libbitcoinconsensus` version.
pub fn bitcoinconsensus_version() -> c_int;

/// Verifies that the transaction input correctly spends the previous
/// output, considering any additional constraints specified by flags.
///
/// This function does not verify Taproot inputs.
pub fn bitcoinconsensus_verify_script_with_amount(
script_pubkey: *const c_uchar,
script_pubkeylen: c_uint,
Expand All @@ -158,6 +234,24 @@ pub mod ffi {
flags: c_uint,
err: *mut Error,
) -> c_int;

/// Verifies that the transaction input correctly spends the previous
/// output, considering any additional constraints specified by flags.
///
/// This function verifies Taproot inputs.
pub fn bitcoinconsensus_verify_script_with_spent_outputs(
script_pubkey: *const c_uchar,
script_pubkeylen: c_uint,
amount: u64,
tx_to: *const c_uchar,
tx_tolen: c_uint,
spent_outputs: *const c_uchar,
num_spent_outputs: c_uint,
n_in: c_uint,
flags: c_uint,
err: *mut Error,
script_err: *mut c_uint,
) -> c_int;
}
}

Expand All @@ -171,7 +265,7 @@ pub mod ffi {
#[repr(C)]
pub enum Error {
/// Default value, passed to `libbitcoinconsensus` as a return parameter.
ERR_SCRIPT = 0,
ERR_SCRIPT = 0, // This is ERR_OK in Bitcoin Core.
/// An invalid index for `txTo`.
ERR_TX_INDEX,
/// `txToLen` did not match with the size of `txTo`.
Expand All @@ -182,6 +276,10 @@ pub enum Error {
ERR_AMOUNT_REQUIRED,
/// Script verification `flags` are invalid (i.e. not part of the libconsensus interface).
ERR_INVALID_FLAGS,
/// Verifying Taproot input requires previous outputs.
ERR_SPENT_OUTPUTS_REQUIRED,
/// Taproot outputs don't match.
ERR_SPENT_OUTPUTS_MISMATCH,
}

impl fmt::Display for Error {
Expand All @@ -195,6 +293,8 @@ impl fmt::Display for Error {
ERR_TX_DESERIALIZE => "an error deserializing txTo",
ERR_AMOUNT_REQUIRED => "input amount is required if WITNESS is used",
ERR_INVALID_FLAGS => "script verification flags are invalid",
ERR_SPENT_OUTPUTS_REQUIRED => "verifying taproot input requires previous outputs",
ERR_SPENT_OUTPUTS_MISMATCH => "taproot outputs don't match",
};
f.write_str(s)
}
Expand All @@ -206,8 +306,14 @@ impl std::error::Error for Error {
use self::Error::*;

match *self {
ERR_SCRIPT | ERR_TX_INDEX | ERR_TX_SIZE_MISMATCH | ERR_TX_DESERIALIZE
| ERR_AMOUNT_REQUIRED | ERR_INVALID_FLAGS => None,
ERR_SCRIPT
| ERR_TX_INDEX
| ERR_TX_SIZE_MISMATCH
| ERR_TX_DESERIALIZE
| ERR_AMOUNT_REQUIRED
| ERR_INVALID_FLAGS
| ERR_SPENT_OUTPUTS_REQUIRED
| ERR_SPENT_OUTPUTS_MISMATCH => None,
}
}
}
Expand Down Expand Up @@ -264,7 +370,7 @@ mod tests {
}

fn verify_test(spent: &str, spending: &str, amount: u64, input: usize) -> Result<(), Error> {
verify(
verify_with_amount(
spent.from_hex().unwrap().as_slice(),
amount,
spending.from_hex().unwrap().as_slice(),
Expand All @@ -273,5 +379,7 @@ mod tests {
}

#[test]
fn invalid_flags_test() { verify_with_flags(&[], 0, &[], 0, VERIFY_ALL + 1).unwrap_err(); }
fn invalid_flags_test() {
verify_with_amount_and_flags(&[], 0, &[], 0, VERIFY_ALL_PRE_TAPROOT + 1).unwrap_err();
}
}
2 changes: 2 additions & 0 deletions src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

/// The C signed 32 bit integer type.
pub type c_int = i32;
/// The C signed 64 bit integer type.
pub type c_int64 = i64;
/// The C unsigned 8 bit integer type.
pub type c_uchar = u8;
/// The C unsigned 32 bit integer type.
Expand Down

0 comments on commit 188d51e

Please sign in to comment.