Skip to content
Open
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
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
cargo build --all-features

- name: Run tests with MSRV
run: cargo test --all-features
run: cargo test --all-features -- --test-threads=1

build:
name: Build and Test
Expand Down Expand Up @@ -102,7 +102,7 @@ jobs:

- name: Run tests with Valgrind
run: |
find target/debug/deps -type f -executable -name "test-*" -not -name "*.so" -not -name "*.d" | xargs -I {} valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=definite --suppressions=valgrind.supp --error-exitcode=1 {}
find target/debug/deps -type f -executable -name "test-*" -not -name "*.so" -not -name "*.d" | xargs -I {} valgrind --leak-check=full --show-leak-kinds=all --errors-for-leak-kinds=definite --suppressions=valgrind.supp --error-exitcode=1 {} --test-threads=1

windows-build:
name: Build and Test on Windows
Expand Down
45 changes: 32 additions & 13 deletions examples/src/script_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,43 +6,62 @@ use bitcoinkernel::{

fn main() {
let _debugger = ScriptDebugger::new(|frame| {
println!("\nMain Stack ({} items, top last):", frame.stack.len());
if frame.stack.is_empty() {
println!(" <empty>");
} else {
let opcode_name = match frame.opcode {
0x00 => "OP_0",
0x76 => "OP_DUP",
0xa9 => "OP_HASH160",
0x87 => "OP_EQUAL",
0x88 => "OP_EQUALVERIFY",
0xac => "OP_CHECKSIG",
0xff => "END",
op if op <= 0x4b => "DATA_PUSH",
_ => "OTHER",
};
println!(
"\n[step {}] opcode=0x{:02x} ({}) op_count={} f_exec={} stack_depth={}",
frame.opcode_pos,
frame.opcode,
opcode_name,
frame.op_count,
frame.f_exec,
frame.stack.len(),
);

if !frame.stack.is_empty() {
println!(" Stack:");
for (i, item) in frame.stack.iter().enumerate() {
if item.is_empty() {
println!(" {}: <empty>", i);
println!(" {}: <empty>", i);
} else {
println!(" {}: 0x{}", i, hex::encode(item));
println!(" {}: 0x{}", i, hex::encode(item));
}
}
}

if !frame.altstack.is_empty() {
println!("\nAlt Stack ({} items, top last):", frame.altstack.len());
println!(" Altstack:");
for (i, item) in frame.altstack.iter().enumerate() {
if item.is_empty() {
println!(" {}: <empty>", i);
println!(" {}: <empty>", i);
} else {
println!(" {}: 0x{}", i, hex::encode(item));
println!(" {}: 0x{}", i, hex::encode(item));
}
}
}

let script = Script::from_bytes(&frame.script);
println!("Script (f_exec={}):", frame.f_exec);
println!(" Script:");
for (i, op) in script.instructions().enumerate() {
match op {
Ok(instruction) => {
if i as u32 == frame.opcode_pos {
print!(" > ");
print!(" > ");
} else {
print!(" ");
print!(" ");
}
println!("{:?}", instruction);
}
Err(e) => println!(" Error decoding instruction: {}", e),
Err(e) => println!(" Error decoding instruction: {}", e),
}
}
})
Expand Down
14 changes: 11 additions & 3 deletions libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <script/debug.h>
#include <script/interpreter.h>
#include <script/script.h>
#include <script/script_error.h>
#include <serialize.h>
#include <streams.h>
#include <sync.h>
Expand Down Expand Up @@ -653,7 +654,8 @@ int btck_script_pubkey_verify(const btck_ScriptPubkey* script_pubkey,
const btck_PrecomputedTransactionData* precomputed_txdata,
const unsigned int input_index,
const btck_ScriptVerificationFlags flags,
btck_ScriptVerifyStatus* status)
btck_ScriptVerifyStatus* status,
btck_ScriptError* script_error)
{
// Assert that all specified flags are part of the interface before continuing
assert((flags & ~btck_ScriptVerificationFlags_ALL) == 0);
Expand All @@ -675,12 +677,14 @@ int btck_script_pubkey_verify(const btck_ScriptPubkey* script_pubkey,

if (status) *status = btck_ScriptVerifyStatus_OK;

ScriptError serror;
bool result = VerifyScript(tx.vin[input_index].scriptSig,
btck_ScriptPubkey::get(script_pubkey),
&tx.vin[input_index].scriptWitness,
script_verify_flags::from_int(flags),
TransactionSignatureChecker(&tx, input_index, amount, txdata, MissingDataBehavior::FAIL),
nullptr);
&serror);
if (script_error) *script_error = static_cast<btck_ScriptError>(serror);
return result ? 1 : 0;
}

Expand Down Expand Up @@ -1403,7 +1407,9 @@ void btck_register_script_debug_callback(void* user_data, btck_ScriptDebugCallba
const CScript& script,
uint32_t opcode_pos,
std::span<const std::vector<unsigned char>> altstack,
bool fExec) {
bool fExec,
uint8_t opcode,
int nOpCount) {
std::vector<const unsigned char*> stack_ptrs;
std::vector<size_t> stack_sizes;
stack_ptrs.reserve(stack.size());
Expand Down Expand Up @@ -1433,6 +1439,8 @@ void btck_register_script_debug_callback(void* user_data, btck_ScriptDebugCallba
state.altstack_item_sizes = altstack_sizes.data();
state.altstack_size = altstack.size();
state.f_exec = fExec ? 1 : 0;
state.opcode = opcode;
state.op_count = nOpCount;

callback(user_data, &state);
};
Expand Down
75 changes: 74 additions & 1 deletion libbitcoinkernel-sys/bitcoin/src/kernel/bitcoinkernel.h
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,75 @@ typedef uint8_t btck_ScriptVerifyStatus;
#define btck_ScriptVerifyStatus_ERROR_INVALID_FLAGS_COMBINATION ((btck_ScriptVerifyStatus)(1)) //!< The flags were combined in an invalid way.
#define btck_ScriptVerifyStatus_ERROR_SPENT_OUTPUTS_REQUIRED ((btck_ScriptVerifyStatus)(2)) //!< The taproot flag was set, so valid spent_outputs have to be provided.

/**
* Script error codes returned by the script interpreter.
* Values match the C++ ScriptError_t enum in script_error.h.
*/
typedef uint8_t btck_ScriptError;
#define btck_ScriptError_OK ((btck_ScriptError)(0))
#define btck_ScriptError_UNKNOWN ((btck_ScriptError)(1))
#define btck_ScriptError_EVAL_FALSE ((btck_ScriptError)(2))
#define btck_ScriptError_OP_RETURN ((btck_ScriptError)(3))
/* Max sizes */
#define btck_ScriptError_SCRIPT_SIZE ((btck_ScriptError)(4))
#define btck_ScriptError_PUSH_SIZE ((btck_ScriptError)(5))
#define btck_ScriptError_OP_COUNT ((btck_ScriptError)(6))
#define btck_ScriptError_STACK_SIZE ((btck_ScriptError)(7))
#define btck_ScriptError_SIG_COUNT ((btck_ScriptError)(8))
#define btck_ScriptError_PUBKEY_COUNT ((btck_ScriptError)(9))
/* Failed verify operations */
#define btck_ScriptError_VERIFY ((btck_ScriptError)(10))
#define btck_ScriptError_EQUALVERIFY ((btck_ScriptError)(11))
#define btck_ScriptError_CHECKMULTISIGVERIFY ((btck_ScriptError)(12))
#define btck_ScriptError_CHECKSIGVERIFY ((btck_ScriptError)(13))
#define btck_ScriptError_NUMEQUALVERIFY ((btck_ScriptError)(14))
/* Logical/Format/Canonical errors */
#define btck_ScriptError_BAD_OPCODE ((btck_ScriptError)(15))
#define btck_ScriptError_DISABLED_OPCODE ((btck_ScriptError)(16))
#define btck_ScriptError_INVALID_STACK_OPERATION ((btck_ScriptError)(17))
#define btck_ScriptError_INVALID_ALTSTACK_OPERATION ((btck_ScriptError)(18))
#define btck_ScriptError_UNBALANCED_CONDITIONAL ((btck_ScriptError)(19))
/* CHECKLOCKTIMEVERIFY and CHECKSEQUENCEVERIFY */
#define btck_ScriptError_NEGATIVE_LOCKTIME ((btck_ScriptError)(20))
#define btck_ScriptError_UNSATISFIED_LOCKTIME ((btck_ScriptError)(21))
/* Malleability */
#define btck_ScriptError_SIG_HASHTYPE ((btck_ScriptError)(22))
#define btck_ScriptError_SIG_DER ((btck_ScriptError)(23))
#define btck_ScriptError_MINIMALDATA ((btck_ScriptError)(24))
#define btck_ScriptError_SIG_PUSHONLY ((btck_ScriptError)(25))
#define btck_ScriptError_SIG_HIGH_S ((btck_ScriptError)(26))
#define btck_ScriptError_SIG_NULLDUMMY ((btck_ScriptError)(27))
#define btck_ScriptError_PUBKEYTYPE ((btck_ScriptError)(28))
#define btck_ScriptError_CLEANSTACK ((btck_ScriptError)(29))
#define btck_ScriptError_MINIMALIF ((btck_ScriptError)(30))
#define btck_ScriptError_SIG_NULLFAIL ((btck_ScriptError)(31))
/* Softfork safeness */
#define btck_ScriptError_DISCOURAGE_UPGRADABLE_NOPS ((btck_ScriptError)(32))
#define btck_ScriptError_DISCOURAGE_UPGRADABLE_WITNESS_PROGRAM ((btck_ScriptError)(33))
#define btck_ScriptError_DISCOURAGE_UPGRADABLE_TAPROOT_VERSION ((btck_ScriptError)(34))
#define btck_ScriptError_DISCOURAGE_OP_SUCCESS ((btck_ScriptError)(35))
#define btck_ScriptError_DISCOURAGE_UPGRADABLE_PUBKEYTYPE ((btck_ScriptError)(36))
/* Segregated witness */
#define btck_ScriptError_WITNESS_PROGRAM_WRONG_LENGTH ((btck_ScriptError)(37))
#define btck_ScriptError_WITNESS_PROGRAM_WITNESS_EMPTY ((btck_ScriptError)(38))
#define btck_ScriptError_WITNESS_PROGRAM_MISMATCH ((btck_ScriptError)(39))
#define btck_ScriptError_WITNESS_MALLEATED ((btck_ScriptError)(40))
#define btck_ScriptError_WITNESS_MALLEATED_P2SH ((btck_ScriptError)(41))
#define btck_ScriptError_WITNESS_UNEXPECTED ((btck_ScriptError)(42))
#define btck_ScriptError_WITNESS_PUBKEYTYPE ((btck_ScriptError)(43))
/* Taproot */
#define btck_ScriptError_SCHNORR_SIG_SIZE ((btck_ScriptError)(44))
#define btck_ScriptError_SCHNORR_SIG_HASHTYPE ((btck_ScriptError)(45))
#define btck_ScriptError_SCHNORR_SIG ((btck_ScriptError)(46))
#define btck_ScriptError_TAPROOT_WRONG_CONTROL_SIZE ((btck_ScriptError)(47))
#define btck_ScriptError_TAPSCRIPT_VALIDATION_WEIGHT ((btck_ScriptError)(48))
#define btck_ScriptError_TAPSCRIPT_CHECKMULTISIG ((btck_ScriptError)(49))
#define btck_ScriptError_TAPSCRIPT_MINIMALIF ((btck_ScriptError)(50))
#define btck_ScriptError_TAPSCRIPT_EMPTY_PUBKEY ((btck_ScriptError)(51))
/* Constant scriptCode */
#define btck_ScriptError_OP_CODESEPARATOR ((btck_ScriptError)(52))
#define btck_ScriptError_SIG_FINDANDDELETE ((btck_ScriptError)(53))

/**
* Script verification flags that may be composed with each other.
*/
Expand Down Expand Up @@ -664,6 +733,7 @@ BITCOINKERNEL_API btck_ScriptPubkey* BITCOINKERNEL_WARN_UNUSED_RESULT btck_scrip
* @param[in] input_index Index of the input in tx_to spending the script_pubkey.
* @param[in] flags Bitfield of btck_ScriptVerificationFlags controlling validation constraints.
* @param[out] status Nullable, will be set to an error code if the operation fails, or OK otherwise.
* @param[out] script_error Nullable, will be set to the specific script error code on verification failure.
* @return 1 if the script is valid, 0 otherwise.
*/
BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_script_pubkey_verify(
Expand All @@ -673,7 +743,8 @@ BITCOINKERNEL_API int BITCOINKERNEL_WARN_UNUSED_RESULT btck_script_pubkey_verify
const btck_PrecomputedTransactionData* precomputed_txdata,
unsigned int input_index,
btck_ScriptVerificationFlags flags,
btck_ScriptVerifyStatus* status) BITCOINKERNEL_ARG_NONNULL(1, 3);
btck_ScriptVerifyStatus* status,
btck_ScriptError* script_error) BITCOINKERNEL_ARG_NONNULL(1, 3);

/**
* @brief Serializes the script pubkey through the passed in callback to bytes.
Expand Down Expand Up @@ -1794,6 +1865,8 @@ typedef struct {
const size_t* altstack_item_sizes;
size_t altstack_size;
int f_exec;
uint8_t opcode;
int op_count;
} btck_ScriptDebugState;

/**
Expand Down
5 changes: 3 additions & 2 deletions libbitcoinkernel-sys/bitcoin/src/script/debug.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@
static std::mutex g_script_debug_mutex;
static DebugScriptCallback g_script_debug_callback{nullptr};

void DebugScript(std::span<const std::vector<unsigned char>> stack, const CScript& script, uint32_t opcode_pos, std::span<const std::vector<unsigned char>> altstack, bool fExec)
void DebugScript(std::span<const std::vector<unsigned char>> stack, const CScript& script, uint32_t opcode_pos, std::span<const std::vector<unsigned char>> altstack, bool fExec, uint8_t opcode, int nOpCount)
{
std::lock_guard<std::mutex> lock(g_script_debug_mutex);
if (g_script_debug_callback) g_script_debug_callback(stack, script, opcode_pos, altstack, fExec);
if (g_script_debug_callback)
g_script_debug_callback(stack, script, opcode_pos, altstack, fExec, opcode, nOpCount);
}

void RegisterDebugScriptCallback(DebugScriptCallback func)
Expand Down
10 changes: 5 additions & 5 deletions libbitcoinkernel-sys/bitcoin/src/script/debug.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,17 @@
#include <span>
#include <vector>

using DebugScriptCallback = std::function<void(std::span<const std::vector<unsigned char>>, const CScript&, uint32_t, std::span<const std::vector<unsigned char>>, bool)>;
using DebugScriptCallback = std::function<void(std::span<const std::vector<unsigned char>>, const CScript&, uint32_t, std::span<const std::vector<unsigned char>>, bool, uint8_t, int)>;

void DebugScript(std::span<const std::vector<unsigned char>> stack, const CScript& script, uint32_t opcode_pos, std::span<const std::vector<unsigned char>> altstack, bool fExec);
void DebugScript(std::span<const std::vector<unsigned char>> stack, const CScript& script, uint32_t opcode_pos, std::span<const std::vector<unsigned char>> altstack, bool fExec, uint8_t opcode, int nOpCount);

void RegisterDebugScriptCallback(DebugScriptCallback func);

#ifdef ENABLE_SCRIPT_DEBUG
#define DEBUG_SCRIPT(stack, script, opcode_pos, altstack, fExec) \
DebugScript(stack, script, opcode_pos, altstack, fExec);
#define DEBUG_SCRIPT(stack, script, opcode_pos, altstack, fExec, opcode, nOpCount) \
DebugScript(stack, script, opcode_pos, altstack, fExec, opcode, nOpCount);
#else
#define DEBUG_SCRIPT(stack, script, opcode_pos, altstack, fExec)
#define DEBUG_SCRIPT(stack, script, opcode_pos, altstack, fExec, opcode, nOpCount)
#endif // ENABLE_SCRIPT_DEBUG

#endif // BITCOIN_SCRIPT_DEBUG_H
8 changes: 5 additions & 3 deletions libbitcoinkernel-sys/bitcoin/src/script/interpreter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,7 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
CScript::const_iterator pc = script.begin();
CScript::const_iterator pend = script.end();
CScript::const_iterator pbegincodehash = script.begin();
opcodetype opcode;
opcodetype opcode = OP_INVALIDOPCODE;
valtype vchPushValue;
ConditionStack vfExec;
std::vector<valtype> altstack;
Expand All @@ -439,7 +439,6 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
{
for (; pc < pend; ++opcode_pos) {
bool fExec = vfExec.all_true();
DEBUG_SCRIPT(stack, script, opcode_pos, altstack, fExec);

//
// Read instruction
Expand All @@ -456,6 +455,8 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
}
}

DEBUG_SCRIPT(stack, script, opcode_pos, altstack, fExec, static_cast<uint8_t>(opcode), nOpCount);

if (opcode == OP_CAT ||
opcode == OP_SUBSTR ||
opcode == OP_LEFT ||
Expand Down Expand Up @@ -1230,7 +1231,8 @@ bool EvalScript(std::vector<std::vector<unsigned char> >& stack, const CScript&
return set_error(serror, SCRIPT_ERR_UNKNOWN_ERROR);
}

DEBUG_SCRIPT(stack, script, opcode_pos, altstack, vfExec.all_true());
opcode = OP_INVALIDOPCODE;
DEBUG_SCRIPT(stack, script, opcode_pos, altstack, vfExec.all_true(), static_cast<uint8_t>(opcode), nOpCount);

if (!vfExec.empty())
return set_error(serror, SCRIPT_ERR_UNBALANCED_CONDITIONAL);
Expand Down
2 changes: 1 addition & 1 deletion src/core/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ pub use block::{
pub use script::ScriptPubkeyExt;
pub use transaction::{TransactionExt, TxInExt, TxOutExt, TxOutPointExt, TxidExt};

pub use verify::{verify, PrecomputedTransactionData, ScriptVerifyError};
pub use verify::{verify, PrecomputedTransactionData, ScriptError, ScriptVerifyError};

pub mod verify_flags {
pub use super::verify::{
Expand Down
7 changes: 7 additions & 0 deletions src/core/script_debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ pub struct ScriptDebugFrame {
pub opcode_pos: u32,
/// Whether the current branch is being executed (`true` = active, `false` = inside a false IF).
pub f_exec: bool,
/// Decoded opcode value for the current instruction.
/// `0xff` (`OP_INVALIDOPCODE`) on the final callback or for empty scripts.
pub opcode: u8,
/// Cumulative count of non-push opcodes executed so far (tracks the 201-op limit).
pub op_count: u32,
}

/// Guard that keeps a script debug callback registered.
Expand Down Expand Up @@ -105,6 +110,8 @@ unsafe extern "C" fn trampoline(
script,
opcode_pos: state.opcode_pos,
f_exec: state.f_exec != 0,
opcode: state.opcode,
op_count: state.op_count as u32,
};

let closure = unsafe { &mut **(user_data as *mut Box<dyn FnMut(ScriptDebugFrame)>) };
Expand Down
Loading
Loading