Skip to main content

blvm_consensus/
lib.rs

1//! # Consensus-Proof
2//!
3//! Direct mathematical implementation of Bitcoin consensus rules from the Orange Paper.
4//!
5//! This crate provides pure, side-effect-free functions that implement the mathematical
6//! specifications defined in the Orange Paper. It serves as the mathematical foundation
7//! for Bitcoin consensus validation.
8//!
9//! ## Architecture
10//!
11//! The system follows a layered architecture:
12//! - Orange Paper (mathematical specifications)
13//! - Consensus Proof (this crate - direct implementation)
14//! - Reference Node (minimal Bitcoin implementation)
15//! - Developer SDK (developer-friendly interface)
16//!
17//! ## Design Principles
18//!
19//! 1. **Pure Functions**: All functions are deterministic and side-effect-free
20//! 2. **Mathematical Accuracy**: Direct implementation of Orange Paper specifications
21//! 3. **Exact Version Pinning**: All consensus-critical dependencies pinned to exact versions
22//! 4. **No Consensus Rule Interpretation**: Only mathematical implementation
23//!
24//! ## Usage
25//!
26//! ```rust
27//! use blvm_consensus::transaction::check_transaction;
28//! use blvm_consensus::types::*;
29//!
30//! let transaction = Transaction {
31//!     version: 1,
32//!     inputs: vec![].into(),
33//!     outputs: vec![TransactionOutput {
34//!         value: 1000,
35//!         script_pubkey: vec![0x51],
36//!     }]
37//!     .into(),
38//!     lock_time: 0,
39//! };
40//! let result = check_transaction(&transaction).unwrap();
41//! ```
42
43#![allow(unused_doc_comments)] // Allow doc comments before macros (proptest, etc.)
44#![allow(unused_variables, unused_assignments)] // Feature-gated and conditional compilation paths
45#![allow(
46    clippy::too_many_arguments,  // Script/block have 8–17 args; struct refactor is large
47    clippy::type_complexity,     // Performance-critical types (OnceLock<RwLock<LruCache<...>>>)
48    clippy::large_enum_variant,  // NetworkResponse::SendMessage; boxing changes layout
49)]
50
51pub mod script;
52pub mod transaction;
53pub mod transaction_hash;
54
55#[cfg(feature = "spec-lock")]
56use blvm_spec_lock::spec_locked;
57#[cfg(all(feature = "production", feature = "benchmarking"))]
58pub use config::{reset_assume_valid_height, set_assume_valid_height};
59#[cfg(feature = "production")]
60pub use script::batch_verify_signatures;
61#[cfg(all(feature = "production", feature = "benchmarking"))]
62pub use script::{
63    clear_all_caches, clear_hash_cache, clear_script_cache, clear_stack_pool, disable_caching,
64    reset_benchmarking_state,
65};
66#[cfg(all(feature = "production", feature = "benchmarking"))]
67pub use transaction_hash::clear_sighash_templates;
68
69// Re-export from blvm-primitives for backward compatibility
70pub use blvm_primitives::constants;
71pub use blvm_primitives::crypto;
72pub use blvm_primitives::opcodes;
73pub use blvm_primitives::serialization;
74pub use blvm_primitives::{error, types};
75pub use blvm_primitives::{tx_inputs, tx_outputs};
76pub use constants::*;
77pub use error::ConsensusError;
78pub use types::*;
79
80/// A failed script rejects the block. Any other failure stays an error.
81pub(crate) fn script_err_as_false(error: ConsensusError) -> error::Result<bool> {
82    match error {
83        ConsensusError::ScriptExecution(_) | ConsensusError::ScriptErrorWithCode { .. } => {
84            Ok(false)
85        }
86        other => Err(other),
87    }
88}
89
90/// Orange Paper Section 4 symbols (C, H, M_MAX, etc.) — re-export from primitives constants.
91pub mod orange_paper_constants {
92    pub use crate::constants::{C, H, L_ELEMENT, L_OPS, L_SCRIPT, L_STACK, M_MAX, R, S_MAX, W_MAX};
93}
94
95/// Spec-lock / property-test helpers only — not a supported production API.
96/// Functions in this module `panic!` or `unimplemented!` when called outside their
97/// intended spec-validation context. Do **not** call from production code.
98#[doc(hidden)]
99pub mod orange_paper_property_helpers;
100
101pub mod config;
102
103pub mod activation;
104pub mod bip113;
105#[cfg(feature = "ctv")]
106pub mod bip119;
107#[cfg(any(feature = "csfs", feature = "production"))]
108pub mod bip348;
109pub mod bip_validation;
110pub mod block;
111#[cfg(all(feature = "production", feature = "rayon"))]
112pub mod checkqueue;
113#[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
114pub mod ecdsa_batch;
115pub mod ecdsa_timers;
116#[cfg(all(feature = "production", feature = "blvm-secp256k1"))]
117pub mod ecdsa_wave;
118pub mod economic;
119pub mod locktime;
120pub mod mempool;
121pub mod mining;
122pub mod optimizations;
123pub mod pow;
124pub mod reorganization;
125#[cfg(all(feature = "production", feature = "rayon"))]
126pub(crate) mod script_exec_cache;
127pub mod secp256k1_backend;
128pub mod segwit;
129pub mod sequence_locks;
130pub mod signet;
131pub mod sigop;
132pub(crate) mod spec_witnesses;
133pub mod taproot;
134pub mod utxo_overlay;
135pub mod version_bits;
136pub mod witness;
137
138// Integration tests link this crate without `cfg(test)` on the library, so `test_utils` cannot be
139// gated only on `test`. Fixture helpers are small; `property-tests`/`proptest` stays gated inside the module.
140pub mod test_utils;
141
142#[cfg(feature = "profile")]
143pub mod profile_log;
144#[cfg(all(feature = "production", feature = "profile"))]
145pub mod script_profile;
146
147/// Consensus Proof - wrapper struct for consensus validation functions
148///
149/// This struct provides a convenient API for accessing all consensus validation
150/// functions. All methods delegate to the corresponding module functions.
151#[derive(Debug, Clone, Copy, Default)]
152pub struct ConsensusProof;
153
154impl ConsensusProof {
155    /// Create a new ConsensusProof instance
156    pub fn new() -> Self {
157        Self
158    }
159
160    /// Validate a transaction according to consensus rules
161    #[cfg_attr(feature = "spec-lock", spec_locked("5.1", "CheckTransaction"))]
162    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
163    pub fn validate_transaction(
164        &self,
165        tx: &types::Transaction,
166    ) -> error::Result<types::ValidationResult> {
167        transaction::check_transaction(tx)
168    }
169
170    /// Validate transaction inputs against UTXO set
171    #[cfg_attr(feature = "spec-lock", spec_locked("5.1", "CheckTxInputs"))]
172    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result_0 == true || result_0 == false))]
173    pub fn validate_tx_inputs(
174        &self,
175        tx: &types::Transaction,
176        utxo_set: &types::UtxoSet,
177        height: types::Natural,
178    ) -> error::Result<(types::ValidationResult, types::Integer)> {
179        transaction::check_tx_inputs(tx, utxo_set, height)
180    }
181
182    /// Validate a complete block
183    ///
184    /// **Deprecated**: builds empty witness stacks and defaults to [`Network::Mainnet`] with
185    /// wall-clock time. Use [`validate_block_with_time_context`] with explicit witnesses,
186    /// network, and median-time-past instead.
187    #[deprecated(
188        since = "0.1.33",
189        note = "Use validate_block_with_time_context with explicit witnesses, network, and TimeContext"
190    )]
191    #[cfg_attr(feature = "spec-lock", spec_locked("5.3", "ConnectBlock"))]
192    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result_0 == true || result_0 == false))]
193    pub fn validate_block(
194        &self,
195        block: &types::Block,
196        utxo_set: types::UtxoSet,
197        height: types::Natural,
198    ) -> error::Result<(types::ValidationResult, types::UtxoSet)> {
199        // Create empty witnesses for backward compatibility
200        let witnesses: Vec<Vec<segwit::Witness>> =
201            block.transactions.iter().map(|_| Vec::new()).collect();
202        // Safe time read: unwrap_or avoids panic when clock is before epoch.
203        // NOTE: This entry point defaults to Network::Mainnet. Callers that need
204        // a different network should use `validate_block_with_time_context` instead.
205        let network_time = std::time::SystemTime::now()
206            .duration_since(std::time::UNIX_EPOCH)
207            .unwrap_or(std::time::Duration::ZERO)
208            .as_secs();
209        let context = block::block_validation_context_for_connect_ibd(
210            None::<&[types::BlockHeader]>,
211            network_time,
212            types::Network::Mainnet,
213        );
214        let (result, new_utxo_set, _undo_log) =
215            block::connect_block(block, &witnesses, utxo_set, height, &context)?;
216        Ok((result, new_utxo_set))
217    }
218
219    /// Validate a complete block with witness data and time context
220    #[cfg_attr(feature = "spec-lock", spec_locked("5.3", "ConnectBlock"))]
221    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result_0 == true || result_0 == false))]
222    pub fn validate_block_with_time_context(
223        &self,
224        block: &types::Block,
225        witnesses: &[Vec<segwit::Witness>],
226        utxo_set: types::UtxoSet,
227        height: types::Natural,
228        time_context: Option<types::TimeContext>,
229        network: types::Network,
230    ) -> error::Result<(types::ValidationResult, types::UtxoSet)> {
231        let context = block::BlockValidationContext::from_time_context_and_network(
232            time_context,
233            network,
234            None,
235        );
236        let (result, new_utxo_set, _undo_log) =
237            block::connect_block(block, witnesses, utxo_set, height, &context)?;
238        Ok((result, new_utxo_set))
239    }
240
241    /// Verify script execution (legacy API — see [`script::verify_script`] deprecation).
242    #[deprecated(
243        since = "0.1.33",
244        note = "Use verify_script_with_context via script module for witness-aware verification"
245    )]
246    #[cfg_attr(feature = "spec-lock", spec_locked("5.2", "VerifyScript"))]
247    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
248    pub fn verify_script(
249        &self,
250        script_sig: &types::ByteString,
251        script_pubkey: &types::ByteString,
252        witness: Option<&types::ByteString>,
253        flags: u32,
254    ) -> error::Result<bool> {
255        #[allow(deprecated)]
256        {
257            script::verify_script(script_sig, script_pubkey, witness, flags)
258        }
259    }
260
261    /// Check proof of work
262    #[cfg_attr(feature = "spec-lock", spec_locked("7.2", "CheckProofOfWork"))]
263    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
264    pub fn check_proof_of_work(&self, header: &types::BlockHeader) -> error::Result<bool> {
265        pow::check_proof_of_work(header)
266    }
267
268    /// Get block subsidy for height
269    #[cfg_attr(feature = "spec-lock", spec_locked("6.1", "GetBlockSubsidy"))]
270    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result >= 0))]
271    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::axiom(result <= INITIAL_SUBSIDY))]
272    pub fn get_block_subsidy(&self, height: types::Natural) -> types::Integer {
273        economic::get_block_subsidy(height)
274    }
275
276    /// Block subsidy for `network`. Regtest halves every 150 blocks.
277    pub fn get_block_subsidy_for_network(
278        &self,
279        height: types::Natural,
280        network: types::Network,
281    ) -> types::Integer {
282        economic::get_block_subsidy_for_network(height, network)
283    }
284
285    /// Calculate total supply at height
286    #[cfg_attr(feature = "spec-lock", spec_locked("6.2", "TotalSupply"))]
287    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result >= 0))]
288    pub fn total_supply(&self, height: types::Natural) -> types::Integer {
289        economic::total_supply(height)
290    }
291
292    /// Get next work required for difficulty adjustment
293    #[cfg_attr(feature = "spec-lock", spec_locked("7.1", "GetNextWorkRequired"))]
294    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result >= 0))]
295    pub fn get_next_work_required(
296        &self,
297        current_header: &types::BlockHeader,
298        prev_headers: &[types::BlockHeader],
299    ) -> error::Result<types::Natural> {
300        pow::get_next_work_required(current_header, prev_headers)
301    }
302
303    /// Accept transaction to memory pool
304    pub fn accept_to_memory_pool(
305        &self,
306        tx: &types::Transaction,
307        utxo_set: &types::UtxoSet,
308        mempool: &mempool::Mempool,
309        height: types::Natural,
310        time_context: Option<types::TimeContext>,
311        network: types::Network,
312    ) -> error::Result<mempool::MempoolResult> {
313        self.accept_to_memory_pool_with_witness(
314            tx,
315            None,
316            utxo_set,
317            mempool,
318            height,
319            time_context,
320            network,
321        )
322    }
323
324    /// Accept transaction to memory pool with the witness stack for each input.
325    pub fn accept_to_memory_pool_with_witness(
326        &self,
327        tx: &types::Transaction,
328        witnesses: Option<&[segwit::Witness]>,
329        utxo_set: &types::UtxoSet,
330        mempool: &mempool::Mempool,
331        height: types::Natural,
332        time_context: Option<types::TimeContext>,
333        network: types::Network,
334    ) -> error::Result<mempool::MempoolResult> {
335        mempool::accept_to_memory_pool(
336            tx,
337            witnesses,
338            utxo_set,
339            mempool,
340            height,
341            time_context,
342            network,
343            None,
344        )
345    }
346
347    /// Check if transaction is standard
348    pub fn is_standard_tx(&self, tx: &types::Transaction) -> error::Result<bool> {
349        mempool::is_standard_tx(tx)
350    }
351
352    /// Check if transaction can replace existing one (RBF)
353    pub fn replacement_checks(
354        &self,
355        new_tx: &types::Transaction,
356        existing_tx: &types::Transaction,
357        utxo_set: &types::UtxoSet,
358        mempool: &mempool::Mempool,
359    ) -> error::Result<bool> {
360        mempool::replacement_checks(new_tx, existing_tx, utxo_set, mempool)
361    }
362
363    /// Create new block from mempool transactions
364    #[allow(clippy::too_many_arguments)]
365    pub fn create_new_block(
366        &self,
367        utxo_set: &types::UtxoSet,
368        mempool_txs: &[types::Transaction],
369        height: types::Natural,
370        prev_header: &types::BlockHeader,
371        prev_headers: &[types::BlockHeader],
372        coinbase_script: &types::ByteString,
373        coinbase_address: &types::ByteString,
374    ) -> error::Result<types::Block> {
375        mining::create_new_block(
376            utxo_set,
377            mempool_txs,
378            height,
379            prev_header,
380            prev_headers,
381            coinbase_script,
382            coinbase_address,
383        )
384    }
385
386    /// Create new block with explicit time, network, and optional per-tx witness stacks.
387    #[allow(clippy::too_many_arguments)]
388    pub fn create_new_block_with_time(
389        &self,
390        utxo_set: &types::UtxoSet,
391        mempool_txs: &[types::Transaction],
392        height: types::Natural,
393        prev_header: &types::BlockHeader,
394        prev_headers: &[types::BlockHeader],
395        coinbase_script: &types::ByteString,
396        coinbase_address: &types::ByteString,
397        block_time: types::Natural,
398        network: types::Network,
399        mempool_witnesses: Option<&[Option<Vec<segwit::Witness>>]>,
400    ) -> error::Result<types::Block> {
401        mining::create_new_block_with_time(
402            utxo_set,
403            mempool_txs,
404            height,
405            prev_header,
406            prev_headers,
407            coinbase_script,
408            coinbase_address,
409            block_time,
410            network,
411            mempool_witnesses,
412        )
413    }
414
415    /// Mine a block by finding valid nonce
416    pub fn mine_block(
417        &self,
418        block: types::Block,
419        max_attempts: types::Natural,
420    ) -> error::Result<(types::Block, mining::MiningResult)> {
421        mining::mine_block(block, max_attempts)
422    }
423
424    /// Create block template for mining
425    #[allow(clippy::too_many_arguments)]
426    pub fn create_block_template(
427        &self,
428        utxo_set: &types::UtxoSet,
429        mempool_txs: &[types::Transaction],
430        height: types::Natural,
431        prev_header: &types::BlockHeader,
432        prev_headers: &[types::BlockHeader],
433        coinbase_script: &types::ByteString,
434        coinbase_address: &types::ByteString,
435        network: types::Network,
436        mempool_witnesses: Option<&[Option<Vec<segwit::Witness>>]>,
437    ) -> error::Result<mining::BlockTemplate> {
438        mining::create_block_template(
439            utxo_set,
440            mempool_txs,
441            height,
442            prev_header,
443            prev_headers,
444            coinbase_script,
445            coinbase_address,
446            network,
447            mempool_witnesses,
448        )
449    }
450
451    /// Multi-output template: fit subsidy+fees, append BIP141 commitment.
452    /// Spec-locked `create_block_template` is unchanged.
453    #[allow(clippy::too_many_arguments)]
454    pub fn create_block_template_with_outputs(
455        &self,
456        utxo_set: &types::UtxoSet,
457        mempool_txs: &[types::Transaction],
458        height: types::Natural,
459        prev_header: &types::BlockHeader,
460        prev_headers: &[types::BlockHeader],
461        coinbase_script: &types::ByteString,
462        coinbase_outputs: &[(types::Integer, types::ByteString)],
463        network: types::Network,
464        mempool_witnesses: Option<&[Option<Vec<segwit::Witness>>]>,
465    ) -> error::Result<mining::BlockTemplate> {
466        mining::create_block_template_with_outputs(
467            utxo_set,
468            mempool_txs,
469            height,
470            prev_header,
471            prev_headers,
472            coinbase_script,
473            coinbase_outputs,
474            network,
475            mempool_witnesses,
476        )
477    }
478
479    /// Stage 3b: miner-declared mempool subset. Spec-locked builders unchanged.
480    #[allow(clippy::too_many_arguments)]
481    pub fn create_block_template_declared(
482        &self,
483        utxo_set: &types::UtxoSet,
484        mempool_txs: &[types::Transaction],
485        declared_txids: &[types::Hash],
486        height: types::Natural,
487        prev_header: &types::BlockHeader,
488        prev_headers: &[types::BlockHeader],
489        coinbase_script: &types::ByteString,
490        coinbase_outputs: &[(types::Integer, types::ByteString)],
491        network: types::Network,
492        mempool_witnesses: Option<&[Option<Vec<segwit::Witness>>]>,
493    ) -> error::Result<mining::BlockTemplate> {
494        mining::create_block_template_declared(
495            utxo_set,
496            mempool_txs,
497            declared_txids,
498            height,
499            prev_header,
500            prev_headers,
501            coinbase_script,
502            coinbase_outputs,
503            network,
504            mempool_witnesses,
505        )
506    }
507
508    /// Reorganize chain when a longer chain is found (legacy: synthesizes empty witnesses).
509    #[deprecated(
510        since = "0.1.33",
511        note = "Use reorganize_chain_with_witnesses with explicit witness data for SegWit chains"
512    )]
513    #[cfg_attr(feature = "spec-lock", spec_locked("11.3"))]
514    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result >= 0))]
515    pub fn reorganize_chain(
516        &self,
517        new_chain: &[types::Block],
518        current_chain: &[types::Block],
519        current_utxo_set: types::UtxoSet,
520        current_height: types::Natural,
521        network: types::Network,
522    ) -> error::Result<reorganization::ReorganizationResult> {
523        use crate::segwit::{Witness, is_segwit_transaction};
524        use crate::transaction::is_coinbase;
525
526        for block in new_chain {
527            for tx in &block.transactions {
528                if !is_coinbase(tx) && is_segwit_transaction(tx) {
529                    return Err(error::ConsensusError::BlockValidation(
530                        "reorganize_chain: SegWit transactions require reorganize_chain_with_witnesses"
531                            .into(),
532                    ));
533                }
534            }
535        }
536
537        let witnesses: Vec<Vec<Vec<Witness>>> = new_chain
538            .iter()
539            .map(|block| {
540                block
541                    .transactions
542                    .iter()
543                    .map(|tx| tx.inputs.iter().map(|_| Vec::new()).collect())
544                    .collect()
545            })
546            .collect();
547        let network_time = new_chain
548            .iter()
549            .map(|b| b.header.timestamp)
550            .max()
551            .unwrap_or(0)
552            .saturating_add(crate::constants::MAX_FUTURE_BLOCK_TIME);
553
554        reorganization::reorganize_chain_with_witnesses(
555            new_chain,
556            &witnesses,
557            None,
558            current_chain,
559            current_utxo_set,
560            current_height,
561            None::<fn(&types::Block) -> Option<Vec<segwit::Witness>>>,
562            None::<fn(types::Natural) -> Option<Vec<types::BlockHeader>>>,
563            None::<fn(&types::Hash) -> Option<reorganization::BlockUndoLog>>,
564            None::<fn(&types::Hash, &reorganization::BlockUndoLog) -> error::Result<()>>,
565            network_time,
566            network,
567            None,
568        )
569    }
570
571    /// Reorganize chain with explicit witness data (preferred API).
572    #[cfg_attr(feature = "spec-lock", spec_locked("11.3"))]
573    pub fn reorganize_chain_with_witnesses(
574        &self,
575        new_chain: &[types::Block],
576        new_chain_witnesses: &[Vec<Vec<segwit::Witness>>],
577        new_chain_headers: Option<&[types::BlockHeader]>,
578        current_chain: &[types::Block],
579        current_utxo_set: types::UtxoSet,
580        current_height: types::Natural,
581        network_time: types::Natural,
582        network: types::Network,
583    ) -> error::Result<reorganization::ReorganizationResult> {
584        reorganization::reorganize_chain_with_witnesses(
585            new_chain,
586            new_chain_witnesses,
587            new_chain_headers,
588            current_chain,
589            current_utxo_set,
590            current_height,
591            None::<fn(&types::Block) -> Option<Vec<segwit::Witness>>>,
592            None::<fn(types::Natural) -> Option<Vec<types::BlockHeader>>>,
593            None::<fn(&types::Hash) -> Option<reorganization::BlockUndoLog>>,
594            None::<fn(&types::Hash, &reorganization::BlockUndoLog) -> error::Result<()>>,
595            network_time,
596            network,
597            None,
598        )
599    }
600
601    /// Check if reorganization is beneficial
602    #[cfg_attr(feature = "spec-lock", spec_locked("11.3", "ShouldReorganize"))]
603    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
604    pub fn should_reorganize(
605        &self,
606        new_chain: &[types::Block],
607        current_chain: &[types::Block],
608    ) -> error::Result<bool> {
609        reorganization::should_reorganize(new_chain, current_chain)
610    }
611
612    /// Calculate transaction weight for SegWit
613    #[cfg_attr(
614        feature = "spec-lock",
615        spec_locked("11.1.1", "CalculateTransactionWeight")
616    )]
617    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result >= 0))]
618    pub fn calculate_transaction_weight(
619        &self,
620        tx: &types::Transaction,
621        witness: Option<&segwit::Witness>,
622    ) -> error::Result<types::Natural> {
623        segwit::calculate_transaction_weight(tx, witness)
624    }
625
626    /// Validate SegWit block
627    #[cfg_attr(feature = "spec-lock", spec_locked("11.1.7", "ValidateSegWitBlock"))]
628    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
629    pub fn validate_segwit_block(
630        &self,
631        block: &types::Block,
632        witnesses: &[segwit::Witness],
633        max_block_weight: types::Natural,
634    ) -> error::Result<bool> {
635        segwit::validate_segwit_block(block, witnesses, max_block_weight)
636    }
637
638    /// Validate Taproot transaction
639    #[cfg_attr(
640        feature = "spec-lock",
641        spec_locked("11.2.5", "ValidateTaprootTransaction")
642    )]
643    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
644    pub fn validate_taproot_transaction(
645        &self,
646        tx: &types::Transaction,
647        witness: Option<&segwit::Witness>,
648    ) -> error::Result<bool> {
649        taproot::validate_taproot_transaction(tx, witness)
650    }
651
652    /// Check if transaction output is Taproot
653    #[cfg_attr(feature = "spec-lock", spec_locked("11.2.1", "IsTaprootOutput"))]
654    #[cfg_attr(feature = "spec-lock", blvm_spec_lock::ensures(result == true || result == false))]
655    pub fn is_taproot_output(&self, output: &types::TransactionOutput) -> bool {
656        taproot::is_taproot_output(output)
657    }
658}
659
660#[cfg(test)]
661mod tests {
662    use crate::transaction::check_transaction;
663    use crate::types::Transaction;
664
665    #[test]
666    fn test_validate_transaction() {
667        let tx = Transaction {
668            version: 1,
669            inputs: vec![].into(),
670            outputs: vec![].into(),
671            lock_time: 0,
672        };
673        let result = check_transaction(&tx);
674        assert!(result.is_ok());
675    }
676}