Merge rust-bitcoin/rust-bitcoin#6725: primitives: Add doctest examples
What changed, and why it matters
This commit only adds documentation examples (doctests) to the rust-bitcoin primitives crate. It does not change any functional code, fix bugs, or alter security behavior. The examples are meant to help developers understand how to use the library.
No security action needed. Treat as a normal documentation improvement.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The merge commit adds Rust doctest examples to modules, types, and methods in primitives/src/block.rs, hash_types/script_hash.rs, hash_types/witness_script_hash.rs, opcodes.rs, script/builder.rs, script/mod.rs, script/owned.rs, script/push_bytes.rs, transaction.rs, witness.rs, and witness_version.rs. No executable logic is modified; only doc comments and example code are introduced. One outdated comment on Header::block_hash was removed.
Changed components
primitives/src/block.rsprimitives/src/hash_types/script_hash.rsprimitives/src/hash_types/witness_script_hash.rsprimitives/src/opcodes.rsprimitives/src/script/builder.rsprimitives/src/script/mod.rsprimitives/src/script/owned.rsprimitives/src/script/push_bytes.rsprimitives/src/transaction.rsprimitives/src/witness.rsprimitives/src/witness_version.rsInspect captured patch +554 / −1
### primitives/src/block.rs
@@ -6,6 +6,64 @@
//! which commits to an earlier block to form the blockchain. This
//! module describes structures and functions needed to describe
//! these blocks and the blockchain.
+//!
+//! # Examples
+//!
+//! ```rust
+//! # #[cfg(feature = "alloc")]
+//! # fn example() -> Result<(), bitcoin_primitives::block::InvalidBlockError> {
+//! use bitcoin_primitives::block::{self, Block, Header, InvalidBlockError, Version};
+//! use bitcoin_primitives::{
+//! absolute, transaction, Amount, BlockHash, BlockTime, CompactTarget, OutPoint,
+//! ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
+//! };
+//!
+//! let coinbase = Transaction {
+//! version: transaction::Version::ONE,
+//! lock_time: absolute::LockTime::ZERO,
+//! inputs: vec![TxIn {
+//! previous_output: OutPoint::COINBASE_PREVOUT,
+//! script_sig: ScriptSigBuf::from_bytes(vec![0x51, 0x52]),
+//! sequence: Sequence::MAX,
+//! witness: Witness::new(),
+//! }],
+//! outputs: vec![TxOut {
+//! amount: Amount::from_sat_u32(50_000),
+//! script_pubkey: ScriptPubKeyBuf::new(),
+//! }],
+//! };
+//! assert!(coinbase.is_coinbase());
+//!
+//! let transactions = vec![coinbase];
+//! let merkle_root =
+//! block::compute_merkle_root(&transactions).ok_or(InvalidBlockError::NoTransactions)?;
+//!
+//! let header = Header {
+//! version: Version::TWO,
+//! prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
+//! merkle_root,
+//! time: BlockTime::from_u32(1_231_006_505),
+//! bits: CompactTarget::from_consensus(0x1d00_ffff),
+//! nonce: 2_083_236_893,
+//! };
+//! assert_eq!(Header::SIZE, 80);
+//!
+//! // Decoding gives a `Block<Unchecked>`. `validate` must be called to get a `Block<Checked>`.
+//! let block = Block::new_unchecked(header, transactions);
+//! assert!(block.check_merkle_root());
+//!
+//! let block_hash = block.block_hash();
+//! let block = block.validate()?;
+//!
+//! // The content accessors exist only on the checked type.
+//! assert_eq!(block.transactions().len(), 1);
+//! assert_eq!(block.block_hash(), block_hash);
+//! assert_eq!(block.header().merkle_root, merkle_root);
+//! # Ok(())
+//! # }
+//! # #[cfg(feature = "alloc")]
+//! # example().unwrap();
+//! ```
#[cfg(feature = "alloc")]
use core::borrow::Borrow;
@@ -107,6 +165,45 @@ impl Block<Unchecked> {
/// Ignores block validation logic and just assumes you know what you are doing.
///
/// You should only use this function if you trust the block i.e., it comes from a trusted node.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use bitcoin_primitives::block::{Block, Header, Version};
+ /// # use bitcoin_primitives::merkle_tree::TxMerkleNode;
+ /// # use bitcoin_primitives::{
+ /// # absolute, transaction, Amount, BlockHash, BlockTime, CompactTarget, OutPoint,
+ /// # ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
+ /// # };
+ /// # let coinbase = Transaction {
+ /// # version: transaction::Version::ONE,
+ /// # lock_time: absolute::LockTime::ZERO,
+ /// # inputs: vec![TxIn {
+ /// # previous_output: OutPoint::COINBASE_PREVOUT,
+ /// # script_sig: ScriptSigBuf::from_bytes(vec![0x51, 0x52]),
+ /// # sequence: Sequence::MAX,
+ /// # witness: Witness::new(),
+ /// # }],
+ /// # outputs: vec![TxOut { amount: Amount::ZERO, script_pubkey: ScriptPubKeyBuf::new() }],
+ /// # };
+ /// # let header = Header {
+ /// # version: Version::TWO,
+ /// # prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
+ /// # merkle_root: TxMerkleNode::from_byte_array([0xff; 32]),
+ /// # time: BlockTime::from_u32(1_231_006_505),
+ /// # bits: CompactTarget::from_consensus(0x1d00_ffff),
+ /// # nonce: 0,
+ /// # };
+ /// // This header's Merkle root does not match the transaction list.
+ /// let block = Block::new_unchecked(header, vec![coinbase]);
+ /// assert!(!block.check_merkle_root());
+ ///
+ /// // `validate` would have rejected this block.
+ /// assert_eq!(block.assume_checked(None).cached_witness_root(), None);
+ /// ```
+ ///
+ /// [`validate`]: Self::validate
+ /// [`cached_witness_root`]: Block<Checked>::cached_witness_root
#[must_use]
#[inline]
pub fn assume_checked(self, witness_root: Option<WitnessMerkleNode>) -> Block<Checked> {
@@ -140,6 +237,52 @@ impl Block<Unchecked> {
/// * The first transaction is not a coinbase transaction.
/// * The Merkle root of the header does not match the Merkle root of the transaction list.
/// * The witness commitment in the coinbase does not match the transaction list.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use bitcoin_primitives::block::{self, Block, Header, InvalidBlockError, Version};
+ /// # use bitcoin_primitives::merkle_tree::TxMerkleNode;
+ /// # use bitcoin_primitives::{
+ /// # absolute, transaction, Amount, BlockHash, BlockTime, CompactTarget, OutPoint,
+ /// # ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
+ /// # };
+ /// # let coinbase = Transaction {
+ /// # version: transaction::Version::ONE,
+ /// # lock_time: absolute::LockTime::ZERO,
+ /// # inputs: vec![TxIn {
+ /// # previous_output: OutPoint::COINBASE_PREVOUT,
+ /// # script_sig: ScriptSigBuf::from_bytes(vec![0x51, 0x52]),
+ /// # sequence: Sequence::MAX,
+ /// # witness: Witness::new(),
+ /// # }],
+ /// # outputs: vec![TxOut { amount: Amount::ZERO, script_pubkey: ScriptPubKeyBuf::new() }],
+ /// # };
+ /// # fn header_with(merkle_root: TxMerkleNode) -> Header {
+ /// # Header {
+ /// # version: Version::TWO,
+ /// # prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
+ /// # merkle_root,
+ /// # time: BlockTime::from_u32(1_231_006_505),
+ /// # bits: CompactTarget::from_consensus(0x1d00_ffff),
+ /// # nonce: 0,
+ /// # }
+ /// # }
+ /// let transactions = vec![coinbase];
+ /// let root = block::compute_merkle_root(&transactions).unwrap();
+ ///
+ /// let block = Block::new_unchecked(header_with(root), transactions.clone());
+ /// assert_eq!(block.validate()?.transactions().len(), 1);
+ ///
+ /// // A header committing to a different transaction list is rejected.
+ /// let wrong_root = header_with(TxMerkleNode::from_byte_array([0xff; 32]));
+ /// let block = Block::new_unchecked(wrong_root, transactions);
+ /// assert_eq!(block.validate().unwrap_err(), InvalidBlockError::InvalidMerkleRoot);
+ /// # Ok::<_, InvalidBlockError>(())
+ /// ```
+ ///
+ /// [`assume_checked`]: Self::assume_checked
+ /// [`cached_witness_root`]: Block<Checked>::cached_witness_root
pub fn validate(self) -> Result<Block<Checked>, InvalidBlockError> {
if self.transactions.is_empty() {
return Err(InvalidBlockError::NoTransactions);
@@ -194,6 +337,44 @@ impl Block<Unchecked> {
/// [`assume_checked`] to save re-calculating it.
///
/// [`assume_checked`]: Block<Unchecked>::assume_checked
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// # use bitcoin_primitives::block::{self, Block, Header, Version};
+ /// # use bitcoin_primitives::{
+ /// # absolute, transaction, Amount, BlockHash, BlockTime, CompactTarget, OutPoint,
+ /// # ScriptPubKeyBuf, ScriptSigBuf, Sequence, Transaction, TxIn, TxOut, Witness,
+ /// # };
+ /// # let coinbase = Transaction {
+ /// # version: transaction::Version::ONE,
+ /// # lock_time: absolute::LockTime::ZERO,
+ /// # inputs: vec![TxIn {
+ /// # previous_output: OutPoint::COINBASE_PREVOUT,
+ /// # script_sig: ScriptSigBuf::from_bytes(vec![0x51, 0x52]),
+ /// # sequence: Sequence::MAX,
+ /// # witness: Witness::new(),
+ /// # }],
+ /// # outputs: vec![TxOut { amount: Amount::ZERO, script_pubkey: ScriptPubKeyBuf::new() }],
+ /// # };
+ /// # let merkle_root = block::compute_merkle_root(&[coinbase.clone()]).expect("one transaction");
+ /// # let header = Header {
+ /// # version: Version::TWO,
+ /// # prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
+ /// # merkle_root,
+ /// # time: BlockTime::from_u32(1_231_006_505),
+ /// # bits: CompactTarget::from_consensus(0x1d00_ffff),
+ /// # nonce: 0,
+ /// # };
+ /// // This block's only transaction has an empty witness.
+ /// let block = Block::new_unchecked(header, vec![coinbase]);
+ ///
+ /// let (is_valid, witness_root) = block.check_witness_commitment();
+ /// assert!(is_valid);
+ /// assert_eq!(witness_root, None);
+ ///
+ /// assert_eq!(block.assume_checked(witness_root).cached_witness_root(), None);
+ /// ```
pub fn check_witness_commitment(&self) -> (bool, Option<WitnessMerkleNode>) {
if self.transactions.is_empty() {
return (false, None);
@@ -275,6 +456,25 @@ impl From<&Block> for BlockHash {
}
/// Marker that the block's merkle root has been successfully validated.
+///
+/// # Examples
+///
+/// ```rust
+/// use bitcoin_primitives::block::{Block, Checked};
+///
+/// fn count_transactions(block: &Block<Checked>) -> usize { block.transactions().len() }
+/// ```
+///
+/// The same function does not compile against an unchecked block:
+///
+/// ```compile_fail
+/// use bitcoin_primitives::block::{Block, Unchecked};
+///
+/// fn count_transactions(block: &Block<Unchecked>) -> usize { block.transactions().len() }
+/// ```
+///
+/// [`validate`]: Block<Unchecked>::validate
+/// [`assume_checked`]: Block<Unchecked>::assume_checked
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[cfg(feature = "alloc")]
pub enum Checked {}
@@ -488,7 +688,27 @@ impl Header {
pub const SIZE: usize = 4 + 32 + 32 + 4 + 4 + 4; // 80
/// Returns the block hash.
- // This is the same as `Encodable` but done manually because `Encodable` isn't in `primitives`.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::block::{Header, Version};
+ /// use bitcoin_primitives::merkle_tree::TxMerkleNode;
+ /// use bitcoin_primitives::{BlockHash, BlockTime, CompactTarget};
+ ///
+ /// let mut header = Header {
+ /// version: Version::TWO,
+ /// prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH,
+ /// merkle_root: TxMerkleNode::from_byte_array([0xab; 32]),
+ /// time: BlockTime::from_u32(1_231_006_505),
+ /// bits: CompactTarget::from_consensus(0x1d00_ffff),
+ /// nonce: 0,
+ /// };
+ /// let block_hash = header.block_hash();
+ ///
+ /// header.nonce += 1;
+ /// assert_ne!(header.block_hash(), block_hash);
+ /// ```
pub fn block_hash(&self) -> BlockHash {
let hash = hashes::encode_to_hash::<_, sha256d::HashEngine>(self);
BlockHash::from_byte_array(hash.to_byte_array())
### primitives/src/hash_types/script_hash.rs
@@ -44,6 +44,19 @@ impl ScriptHash {
/// # Errors
///
/// Returns an error if the script exceeds 520 bytes.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::{RedeemScriptBuf, ScriptHash, ScriptPubKeyBuf};
+ ///
+ /// let redeem_script = RedeemScriptBuf::from_bytes(vec![0x51]);
+ /// assert!(ScriptPubKeyBuf::new_p2sh(redeem_script.script_hash()?).is_p2sh());
+ ///
+ /// let too_big = RedeemScriptBuf::from_bytes(vec![0x51; 521]);
+ /// assert_eq!(too_big.script_hash().unwrap_err().invalid_size(), 521);
+ /// # Ok::<_, bitcoin_primitives::script::RedeemScriptSizeError>(())
+ /// ```
#[inline]
pub fn from_script<T>(redeem_script: &Script<T>) -> Result<Self, RedeemScriptSizeError>
where
### primitives/src/hash_types/witness_script_hash.rs
@@ -42,6 +42,20 @@ impl WScriptHash {
/// # Errors
///
/// Returns an error if the script exceeds 10,000 bytes.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::{ScriptPubKeyBuf, WScriptHash, WitnessScriptBuf};
+ ///
+ /// // Far too big for P2SH, but unremarkable for P2WSH.
+ /// let witness_script = WitnessScriptBuf::from_bytes(vec![0x51; 1_000]);
+ /// assert!(ScriptPubKeyBuf::new_p2wsh(witness_script.wscript_hash()?).is_p2wsh());
+ ///
+ /// let too_big = WitnessScriptBuf::from_bytes(vec![0x51; 10_001]);
+ /// assert_eq!(too_big.wscript_hash().unwrap_err().invalid_size(), 10_001);
+ /// # Ok::<_, bitcoin_primitives::script::WitnessScriptSizeError>(())
+ /// ```
#[inline]
pub fn from_script(witness_script: &WitnessScript) -> Result<Self, WitnessScriptSizeError> {
if witness_script.len() > MAX_WITNESS_SCRIPT_SIZE {
### primitives/src/opcodes.rs
@@ -25,6 +25,21 @@ use arbitrary::{Arbitrary, Unstructured};
/// Bitcoin Core's `IsPushOnly` considers `OP_RESERVED` to be a "push code", allowing this opcode
/// in contexts where only pushes are supposed to be allowed.
/// </details>
+///
+/// # Examples
+///
+/// ```rust
+/// use bitcoin_primitives::opcodes::all::{OP_EQUAL, OP_HASH160};
+/// use bitcoin_primitives::opcodes::Opcode;
+///
+/// fn is_p2sh_shaped(script: &[u8]) -> bool {
+/// script.first().map(|&b| Opcode::from_u8(b)) == Some(OP_HASH160)
+/// && script.last().map(|&b| Opcode::from_u8(b)) == Some(OP_EQUAL)
+/// }
+///
+/// assert!(is_p2sh_shaped(&[0xa9, 0x14, 0x87]));
+/// assert!(!is_p2sh_shaped(&[0x76, 0xa9, 0xac]));
+/// ```
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Opcode {
code: u8,
### primitives/src/script/builder.rs
@@ -12,6 +12,23 @@ use crate::prelude::Vec;
///
/// [`Builder`] is backed by [`ScriptBuf`] and inherits its panic behavior. This means that
/// attempting to construct scripts larger than `isize::MAX` bytes will panic.
+///
+/// # Examples
+///
+/// ```rust
+/// use bitcoin_primitives::opcodes::all::{OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160};
+/// use bitcoin_primitives::script::{Builder, ScriptPubKeyBuf};
+///
+/// let script: ScriptPubKeyBuf = Builder::new()
+/// .push_opcode(OP_DUP)
+/// .push_opcode(OP_HASH160)
+/// .push_slice([0xab; 20])
+/// .push_opcode(OP_EQUALVERIFY)
+/// .push_opcode(OP_CHECKSIG)
+/// .into_script();
+///
+/// assert!(script.is_p2pkh());
+/// ```
#[derive(PartialEq, Eq, Clone)]
pub struct Builder<T>(ScriptBuf<T>);
### primitives/src/script/mod.rs
@@ -1,6 +1,22 @@
// SPDX-License-Identifier: CC0-1.0
//! Bitcoin scripts.
+//!
+//! # Examples
+//!
+//! ```rust
+//! use bitcoin_primitives::script::{ScriptPubKeyBuf, WScriptHash, WitnessScriptBuf};
+//! use bitcoin_primitives::witness_version::WitnessVersion;
+//!
+//! let witness_script = WitnessScriptBuf::from_bytes(vec![0x51]);
+//! let script_pubkey = ScriptPubKeyBuf::new_p2wsh(WScriptHash::from_script(&witness_script)?);
+//!
+//! assert_eq!(script_pubkey.witness_version(), Some(WitnessVersion::V0));
+//! assert!(script_pubkey.is_witness_program());
+//! assert!(script_pubkey.is_p2wsh());
+//! assert!(!script_pubkey.is_p2wpkh());
+//! # Ok::<_, bitcoin_primitives::script::WitnessScriptSizeError>(())
+//! ```
mod borrowed;
mod builder;
### primitives/src/script/owned.rs
@@ -187,6 +187,24 @@ impl<T> ScriptBuf<T> {
/// If your pushes should be interpreted as numbers, ensure your input does
/// not have any leading zeros. In particular, the number 0 should be encoded
/// as an empty string rather than as a single 0 byte.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::ScriptSigBuf;
+ ///
+ /// // 0x01 becomes OP_1, and 0x81 becomes OP_1NEGATE.
+ /// let mut script = ScriptSigBuf::new();
+ /// script.push_slice([0x01u8]);
+ /// script.push_slice([0x81u8]);
+ /// assert_eq!(script.as_bytes(), &[0x51, 0x4f]);
+ ///
+ /// // 0x00 and 0x11 have no numeric opcode, so they are pushed as data.
+ /// let mut script = ScriptSigBuf::new();
+ /// script.push_slice([0x00u8]);
+ /// script.push_slice([0x11u8]);
+ /// assert_eq!(script.as_bytes(), &[0x01, 0x00, 0x01, 0x11]);
+ /// ```
pub fn push_slice<D: AsRef<PushBytes>>(&mut self, data: D) {
let bytes = data.as_ref().as_bytes();
if bytes.len() == 1 {
@@ -211,6 +229,17 @@ impl<T> ScriptBuf<T> {
/// Standardness rules require push minimality according to [CheckMinimalPush] of core.
///
/// [CheckMinimalPush]: <https://github.com/bitcoin/bitcoin/blob/99a4ddf5ab1b3e514d08b90ad8565827fda7b63b/src/script/script.cpp#L366>
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::ScriptSigBuf;
+ ///
+ /// // Unlike `push_slice`, 0x01 is pushed as raw data, not an opcode.
+ /// let mut non_minimal = ScriptSigBuf::new();
+ /// non_minimal.push_slice_non_minimal([0x01u8]);
+ /// assert_eq!(non_minimal.as_bytes(), &[0x01, 0x01]);
+ /// ```
pub fn push_slice_non_minimal<D: AsRef<PushBytes>>(&mut self, data: D) {
let data = data.as_ref();
self.reserve(Self::reserved_len_for_slice(data.len()));
@@ -269,11 +298,36 @@ impl<T> ScriptBuf<T> {
impl ScriptPubKeyBuf {
/// Generates OP_RETURN-type of scriptPubkey for the given data.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::ScriptPubKeyBuf;
+ /// use bitcoin_primitives::{Amount, TxOut};
+ ///
+ /// let script_pubkey = ScriptPubKeyBuf::new_op_return([0x01, 0x02, 0x03]);
+ /// assert_eq!(script_pubkey.as_bytes(), &[0x6a, 0x03, 0x01, 0x02, 0x03]);
+ ///
+ /// // Unspendable, so any amount assigned is burned.
+ /// let _output = TxOut { amount: Amount::ZERO, script_pubkey };
+ /// ```
pub fn new_op_return<T: AsRef<PushBytes>>(data: T) -> Self {
Builder::new().push_opcode(OP_RETURN).push_slice(data).into_script()
}
/// Generates P2SH-type of scriptPubkey with a given hash of the redeem script.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::{RedeemScriptBuf, ScriptHash, ScriptPubKeyBuf};
+ ///
+ /// let redeem_script = RedeemScriptBuf::from_bytes(vec![0x51]);
+ /// let script_pubkey = ScriptPubKeyBuf::new_p2sh(ScriptHash::from_script(&redeem_script)?);
+ ///
+ /// assert!(script_pubkey.is_p2sh());
+ /// # Ok::<_, bitcoin_primitives::script::RedeemScriptSizeError>(())
+ /// ```
pub fn new_p2sh(script_hash: ScriptHash) -> Self {
Builder::new()
.push_opcode(OP_HASH160)
@@ -290,6 +344,18 @@ impl ScriptPubKeyBuf {
impl<T: ScriptHashableTag> ScriptBuf<T> {
/// Generates a P2WSH witness program script with a given hash of the witness script.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::{ScriptPubKeyBuf, WScriptHash, WitnessScriptBuf};
+ ///
+ /// let witness_script = WitnessScriptBuf::from_bytes(vec![0x51]);
+ /// let script_pubkey = ScriptPubKeyBuf::new_p2wsh(WScriptHash::from_script(&witness_script)?);
+ ///
+ /// assert!(script_pubkey.is_p2wsh());
+ /// # Ok::<_, bitcoin_primitives::script::WitnessScriptSizeError>(())
+ /// ```
pub fn new_p2wsh(script_hash: WScriptHash) -> Self {
// script hash is 32 bytes long, so it's safe to use `new_witness_program_unchecked` (Segwitv0)
super::new_witness_program_unchecked(WitnessVersion::V0, script_hash)
### primitives/src/script/push_bytes.rs
@@ -46,6 +46,22 @@ mod primitive {
/// The encoding of Bitcoin script restricts data pushes to be less than 2^32 bytes long.
/// This type represents slices that are guaranteed to be within the limit so they can be put in
/// the script safely.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::{PushBytes, ScriptSigBuf};
+ ///
+ /// let public_key = [0xab; 33];
+ /// let signature: &[u8] = &[0xcd; 71];
+ ///
+ /// let mut script_sig = ScriptSigBuf::new();
+ /// script_sig.push_slice(public_key);
+ /// script_sig.push_slice(<&PushBytes>::try_from(signature)?);
+ ///
+ /// assert_eq!(script_sig.len(), (1 + 33) + (1 + 71));
+ /// # Ok::<_, bitcoin_primitives::script::PushBytesError>(())
+ /// ```
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PushBytes([u8]);
@@ -199,6 +215,20 @@ mod primitive {
}
/// Owned, growable counterpart to [`PushBytes`].
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::script::{PushBytesBuf, ScriptPubKeyBuf};
+ ///
+ /// let mut payload = PushBytesBuf::new();
+ /// payload.extend_from_slice(b"hello")?;
+ /// payload.push(b'!')?;
+ ///
+ /// let script_pubkey = ScriptPubKeyBuf::new_op_return(payload);
+ /// assert_eq!(script_pubkey.len(), 1 + 1 + 6); // OP_RETURN, push opcode, data.
+ /// # Ok::<_, bitcoin_primitives::script::PushBytesError>(())
+ /// ```
#[derive(Default, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct PushBytesBuf(Vec<u8>);
### primitives/src/transaction.rs
@@ -9,6 +9,49 @@
//! signatures ensures that coins cannot be spent by unauthorized parties.
//!
//! This module provides the structures and functions needed to support transactions.
+//!
+//! # Examples
+//!
+//! ```rust
+//! # #[cfg(all(feature = "alloc", feature = "hex"))]
+//! # type Error = encoding::FromHexError<bitcoin_primitives::transaction::TransactionDecoderError>;
+//! # #[cfg(all(feature = "alloc", feature = "hex"))]
+//! # fn example() -> Result<(), Error> {
+//! use bitcoin_primitives::transaction::Version;
+//! use bitcoin_primitives::Transaction;
+//!
+//! let tx: Transaction = "02000000000101595895ea20179de87052b4046dfe6fd515860505d6511a9004cf12a1f9\
+//! 3cac7c0100000000ffffffff01deb807000000000017a9140f3444e271620c736808aa7\
+//! b33e370bd87cb5a078702483045022100fb60dad8df4af2841adc0346638c16d0b8035f\
+//! 5e3f3753b88db122e70c79f9370220756e6633b17fd2710e626347d28d60b0a2d6cbb41\
+//! de51740644b9fb3ba7751040121028fa937ca8cba2197a37c007176ed8941055d3bcb86\
+//! 27d085e94553e62f057dcc00000000".parse()?;
+//!
+//! assert_eq!(tx.version, Version::TWO);
+//! assert!(!tx.is_coinbase());
+//!
+//! // An input names the UTXO it spends but not its value; that lives in the previous transaction,
+//! // so computing a fee means looking those outputs up separately.
+//! let spent_outpoint = tx.inputs[0].previous_output;
+//! assert_eq!(spent_outpoint.vout, 1);
+//!
+//! // This input is SegWit, so the signature and public key are in the witness.
+//! assert!(tx.inputs[0].script_sig.is_empty());
+//! assert_eq!(tx.inputs[0].witness.len(), 2);
+//!
+//! let total_out: u64 = tx.outputs.iter().map(|output| output.amount.to_sat()).sum();
+//! assert_eq!(total_out, 506_078);
+//! assert!(tx.outputs[0].script_pubkey.is_p2sh());
+//!
+//! assert_eq!(
+//! tx.compute_txid().to_string(),
+//! "f5864806e3565c34d1b41e716f72609d00b55ea5eac5b924c9719a842ef42206",
+//! );
+//! # Ok(())
+//! # }
+//! # #[cfg(all(feature = "alloc", feature = "hex"))]
+//! # example().unwrap();
+//! ```
use core::fmt;
#[cfg(feature = "alloc")]
### primitives/src/witness.rs
@@ -3,6 +3,45 @@
//! A witness.
//!
//! This module contains the [`Witness`] struct and related methods to operate on it
+//!
+//! # Examples
+//!
+//! A P2WPKH spend:
+//!
+//! ```rust
+//! use bitcoin_primitives::witness::Witness;
+//!
+//! let signature = [0xab; 72];
+//! let public_key = [0xcd; 33];
+//!
+//! let witness = Witness::from_slice(&[signature.as_slice(), public_key.as_slice()]);
+//!
+//! assert_eq!(witness.len(), 2);
+//! assert!(!witness.is_empty());
+//! assert_eq!(witness.get(0), Some(signature.as_slice()));
+//! assert_eq!(witness.last(), Some(public_key.as_slice()));
+//!
+//! // Serialized size includes a compact-size element count and a prefix per element.
+//! assert_eq!(witness.size(), 1 + (1 + 72) + (1 + 33));
+//! ```
+//!
+//! A P2WSH spend is assembled incrementally, ending with the witness script itself:
+//!
+//! ```rust
+//! use bitcoin_primitives::witness::Witness;
+//!
+//! let witness_script = [0x51];
+//!
+//! let mut witness = Witness::new();
+//! assert!(witness.is_empty());
+//!
+//! witness.push([]); // Empty element, for the OP_CHECKMULTISIG off-by-one.
+//! witness.push([0xab; 72]);
+//! witness.push(witness_script);
+//!
+//! assert_eq!(witness.last(), Some(witness_script.as_slice()));
+//! assert_eq!(witness.iter().count(), 3);
+//! ```
use core::fmt;
use core::ops::Index;
@@ -89,6 +128,16 @@ impl Witness {
}
/// Constructs a new [`Witness`] object from a slice of bytes slices where each slice is a witness item.
+ ///
+ /// # Examples
+ /// ```
+ /// use bitcoin_primitives::witness::Witness;
+ ///
+ /// let mut witness = Witness::from_slice(&[b"A", b"B", b"C", b"D"]);
+ ///
+ /// assert_eq!(witness.get(0), Some(b"A".as_slice()));
+ /// assert_eq!(witness.get(3), Some(b"D".as_slice()));
+ /// ```
pub fn from_slice<T: AsRef<[u8]>>(slice: &[T]) -> Self {
let witness_elements = slice.len();
let index_size = witness_elements * 4;
@@ -136,6 +185,18 @@ impl Witness {
/// # Panics
///
/// If the size calculation overflows.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::witness::Witness;
+ ///
+ /// let witness = Witness::from_slice(&[[0xab; 72].as_slice(), [0xcd; 33].as_slice()]);
+ ///
+ /// // 1 + (1 + 72) + (1 + 33), not 72 + 33.
+ /// assert_eq!(witness.size(), 108);
+ /// assert_eq!(Witness::new().size(), 1); // 1 byte for the '0' encoded as compact size.
+ /// ```
pub fn size(&self) -> usize {
let mut size: usize = 0;
@@ -160,6 +221,22 @@ impl Witness {
}
/// Pushes a new element on the witness, requires an allocation.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::witness::Witness;
+ ///
+ /// let witness_script = [0x51];
+ ///
+ /// // A P2WSH spend, script last.
+ /// let mut witness = Witness::new();
+ /// witness.push([]); // Empty element, for the OP_CHECKMULTISIG off-by-one.
+ /// witness.push([0xab; 72]);
+ /// witness.push(witness_script);
+ ///
+ /// assert_eq!(witness.last(), Some(witness_script.as_slice()));
+ /// ```
#[inline]
pub fn push<T: AsRef<[u8]>>(&mut self, new_element: T) {
self.push_slice(new_element.as_ref());
@@ -240,6 +317,20 @@ impl Witness {
/// # Errors
///
/// This function will return an error if any of the hex strings are invalid.
+ ///
+ /// # Examples
+ ///
+ /// ```rust
+ /// use bitcoin_primitives::witness::Witness;
+ ///
+ /// let witness = Witness::from_hex(["ab", "cdef"])?;
+ /// assert_eq!(witness.get(0), Some([0xab].as_slice()));
+ /// assert_eq!(witness.get(1), Some([0xcd, 0xef].as_slice()));
+ ///
+ /// // An empty element is the empty string.
+ /// assert_eq!(Witness::from_hex(["", "51"])?.get(0), Some([].as_slice()));
+ /// # Ok::<_, hex::DecodeVariableLengthBytesError>(())
+ /// ```
#[cfg(feature = "hex")]
pub fn from_hex<I, T>(iter: I) -> Result<Self, DecodeVariableLengthBytesError>
where
### primitives/src/witness_version.rs
@@ -28,6 +28,34 @@ pub use self::error::{ParseWitnessVersionError, InvalidWitnessVersionError};
///
/// First byte of `scriptPubkey` in transaction output for transactions starting with opcodes
/// ranging from 0 to 16 (inclusive).
+///
+/// # Examples
+///
+/// ```rust
+/// # #[cfg(feature = "alloc")] {
+/// use bitcoin_primitives::witness_version::WitnessVersion;
+/// use bitcoin_primitives::ScriptPubKey;
+///
+/// // A P2WPKH scriptPubKey: OP_0 <20-byte-key-hash>.
+/// let script_pubkey = ScriptPubKey::from_bytes(&[
+/// 0x00, 0x14, 0x8b, 0x9c, 0x1a, 0xcd, 0x2f, 0x2f, 0x1a, 0x4c, 0x5e, 0x3b, 0x7f, 0x91, 0x6d,
+/// 0x0e, 0x28, 0x3a, 0x54, 0xc7, 0xb2, 0x1d,
+/// ]);
+///
+/// match script_pubkey.witness_version() {
+/// Some(WitnessVersion::V0) => println!("segwit v0 output (P2WPKH or P2WSH)"),
+/// Some(WitnessVersion::V1) => println!("segwit v1 output (Taproot)"),
+/// Some(version) => println!("unknown witness version: {}", version),
+/// None => println!("not a witness program"),
+/// }
+///
+/// assert_eq!(script_pubkey.witness_version(), Some(WitnessVersion::V0));
+///
+/// // Versions are only valid in the range 0 to 16 inclusive.
+/// assert_eq!(WitnessVersion::try_from(1_u8), Ok(WitnessVersion::V1));
+/// assert!(WitnessVersion::try_from(17_u8).is_err());
+/// # }
+/// ```
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
#[repr(u8)]
pub enum WitnessVersion {Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.