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
22 changes: 22 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,28 @@ good insight into the internals of the execution in a step-wise manner.

# Usage

## Library: complete tapscript witnesses

`Exec::new_tapscript(options, transaction_template)` derives the script, initial
data stack, leaf hash, and optional annex from the selected transaction input's
script-path witness. Its BIP342 signature budget is 50 plus the serialized size
of that complete witness, including the script, control block, annex, item count,
and all item length prefixes. Every executed signature opcode with a nonempty
signature consumes 50 units, including a charge that makes execution fail.

The constructor accepts only structurally valid control blocks with the tapscript
leaf version and requires one prevout per transaction input. An explicitly
supplied `taproot_annex_scriptleaf` must match the witness-derived context; `None`
lets the constructor derive it. It does not validate the output commitment, the
transaction, or relay policy, and it does not resolve unsupported interpreter
behavior such as OP_SUCCESS. Execution options still apply, including experimental
features. It must not be used as a consensus validator.

`Exec::new` and `Exec::with_stack` remain available for executing explicit scripts
and fragments. Their historical signature budget uses only the serialized data
stack passed as `script_witness`, even when the transaction has a complete
witness; use `new_tapscript` when full witness budget accounting is required.

## CLI

You can simply use `cargo run` or build/intall the binary as follows:
Expand Down
78 changes: 78 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,77 @@ impl std::ops::Drop for Exec {
}

impl Exec {
/// Creates an executor for a Taproot script-path witness in the selected input.
///
/// The script, initial stack, leaf hash, and optional annex are derived from
/// `tx.tx.input[tx.input_idx].witness`. The BIP342 signature budget includes
/// the serialized *complete* witness: its item count, data, script, control
/// block, annex, and all item length prefixes. If `taproot_annex_scriptleaf`
/// is supplied, it must agree with the derived context; otherwise it is set.
/// A complete set of prevouts is required for signature hashing.
///
/// This accepts only structurally valid script-path witnesses with the
/// tapscript leaf version. It does not validate the Taproot output commitment,
/// the transaction, or relay policy, and does not add support for OP_SUCCESS
/// or other unsupported interpreter behavior. `opt` still controls script
/// execution, including experimental features. This is not a consensus
/// validation API.
pub fn new_tapscript(opt: Options, mut tx: TxTemplate) -> Result<Exec, Error> {
let input = tx
.tx
.input
.get(tx.input_idx)
.ok_or(Error::Other("tapscript input index out of bounds"))?;
if tx.prevouts.len() != tx.tx.input.len() {
return Err(Error::Other("tapscript requires one prevout per input"));
}
let witness_size = input.witness.size();
let start_validation_weight = i64::try_from(witness_size)
.ok()
.and_then(|size| size.checked_add(VALIDATION_WEIGHT_OFFSET))
.ok_or(Error::Other(
"tapscript witness size exceeds validation budget range",
))?;
let mut stack = input.witness.to_vec();
let annex = if stack.len() >= 2
&& stack.last().and_then(|item| item.first()) == Some(&taproot::TAPROOT_ANNEX_PREFIX)
{
stack.pop()
} else {
None
};
if stack.len() < 2 {
return Err(Error::Other("expected a tapscript script-path witness"));
}
let control = taproot::ControlBlock::decode(&stack.pop().unwrap())
.map_err(|_| Error::Other("invalid tapscript control block"))?;
if control.leaf_version != taproot::LeafVersion::TapScript {
return Err(Error::Other("unsupported taproot leaf version"));
}
let script = ScriptBuf::from_bytes(stack.pop().unwrap());
let context = (
TapLeafHash::from_script(&script, control.leaf_version),
annex,
);
if let Some(ref supplied) = tx.taproot_annex_scriptleaf {
if supplied != &context {
return Err(Error::Other("taproot context disagrees with input witness"));
}
}
tx.taproot_annex_scriptleaf = Some(context);
let mut exec = Self::new(ExecCtx::Tapscript, opt, tx, script, stack)?;
exec.validation_weight = start_validation_weight;
exec.stats.start_validation_weight = start_validation_weight;
exec.stats.validation_weight = start_validation_weight;
Ok(exec)
}

/// Creates an executor for an explicit script and initial data stack.
///
/// For compatibility, the tapscript signature budget uses only the serialized
/// `script_witness` data stack supplied here. It does not include the script,
/// control block, or annex from the transaction. Use [`Self::new_tapscript`]
/// for BIP342 budget accounting from a complete script-path witness.
pub fn new(
ctx: ExecCtx,
opt: Options,
Expand Down Expand Up @@ -286,6 +357,10 @@ impl Exec {
Ok(ret)
}

/// Like [`Self::new`], but replaces the initial main and alt stacks.
///
/// The signature budget retains [`Self::new`]'s data-only accounting based
/// on `script_witness`; replacement stacks do not change it.
pub fn with_stack(
ctx: ExecCtx,
opt: Options,
Expand Down Expand Up @@ -444,6 +519,9 @@ impl Exec {
fn check_sig_tap(&mut self, sig: &[u8], pk: &[u8]) -> Result<bool, ExecError> {
if !sig.is_empty() {
self.validation_weight -= VALIDATION_WEIGHT_PER_SIGOP_PASSED;
// A failed signature opcode exits before exec_next's normal stats
// update, but its charge still belongs in the remaining budget.
self.stats.validation_weight = self.validation_weight;
if self.validation_weight < 0 {
return Err(ExecError::TapscriptValidationWeight);
}
Expand Down
Loading