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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added
- Added `Block::check` to perform context-free validation of a block (size, weight, coinbase, transactions, sigops), with optional proof-of-work and merkle-root checks toggled via the `BLOCK_CHECK_BASE` / `_POW` / `_MERKLE` / `_ALL` flags. Returns a `BlockCheckResult` enum carrying the validation state on failure.
- Added `ScriptPubkeyExt::as_bytes` to return a zero-copy slice into kernel-managed memory. Unlike `to_bytes`, this does not allocate.

### Changed
- The `verify` function's `flags` parameter now uses `ScriptVerificationFlags` instead of `u32`, making the type explicit in the public API.
Expand Down
98 changes: 96 additions & 2 deletions src/core/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
//! let p2wpkh = ScriptPubkey::new(&hex::decode(p2wpkh_hex).unwrap()).unwrap();
//! ```

use std::{ffi::c_void, marker::PhantomData};
use std::{ffi::c_void, marker::PhantomData, panic};

use libbitcoinkernel_sys::{
btck_ScriptPubkey, btck_script_pubkey_copy, btck_script_pubkey_create,
Expand All @@ -57,7 +57,10 @@ use libbitcoinkernel_sys::{

use crate::{
c_serialize,
ffi::sealed::{AsPtr, FromMutPtr, FromPtr},
ffi::{
c_helpers,
sealed::{AsPtr, FromMutPtr, FromPtr},
},
KernelError,
};

Expand All @@ -84,6 +87,60 @@ pub trait ScriptPubkeyExt: AsPtr<btck_ScriptPubkey> {
})
.expect("Script pubkey to_bytes should never fail")
}

/// Returns a zero-copy view of the script's raw bytes.
/// Unlike [`to_bytes`](ScriptPubkeyExt::to_bytes), this does not allocate.
/// The returned slice borrows directly from kernel-managed memory and is
/// valid for the lifetime of `self`.
///
/// # Examples
///
/// ```no_run
/// # use bitcoinkernel::{prelude::*, ScriptPubkey};
/// let script = ScriptPubkey::new(&[0x76, 0xa9]).unwrap();
/// assert_eq!(script.as_bytes(), &[0x76, 0xa9]);
/// ```
fn as_bytes(&self) -> &[u8] {
struct BytesOut {
ptr: *const u8,
len: usize,
}

unsafe extern "C" fn writer(
data: *const c_void,
len: usize,
user_data: *mut c_void,
) -> i32 {
panic::catch_unwind(|| {
let out = &mut *(user_data as *mut BytesOut);
out.ptr = data as *const u8;
out.len = len;
c_helpers::to_c_result(true)
})
.unwrap_or_else(|_| c_helpers::to_c_result(false))
}

let mut out = BytesOut {
ptr: std::ptr::null(),
len: 0,
};
let ret = unsafe {
btck_script_pubkey_to_bytes(
self.as_ptr(),
Some(writer),
&mut out as *mut BytesOut as *mut c_void,
)
};
assert!(
c_helpers::success(ret),
"btck_script_pubkey_to_bytes should never fail for a valid ScriptPubkey"
);

if out.ptr.is_null() {
return &[];
}
unsafe { std::slice::from_raw_parts(out.ptr, out.len) }
}
}

/// A single script pubkey containing spending conditions for a [`crate::TxOut`].
Expand Down Expand Up @@ -349,6 +406,43 @@ mod tests {
assert_eq!(bytes, script_data);
}

#[test]
fn test_scriptpubkey_as_bytes_empty() {
let script = ScriptPubkey::new(&[]).unwrap();
assert_eq!(script.as_bytes(), &[]);
}

#[test]
fn test_scriptpubkey_as_bytes() {
let script_data = vec![0x76, 0xa9, 0x14];
let script = ScriptPubkey::new(&script_data).unwrap();
assert_eq!(script.as_bytes(), script_data.as_slice());
}

#[test]
fn test_scriptpubkey_ref_as_bytes() {
let script_data = vec![0x76, 0xa9, 0x14];
let script = ScriptPubkey::new(&script_data).unwrap();
let script_ref = script.as_ref();
assert_eq!(script_ref.as_bytes(), script_data.as_slice());
}

#[test]
fn test_as_bytes_matches_to_bytes() {
let script_data = vec![0x76, 0xa9, 0x14];
let script = ScriptPubkey::new(&script_data).unwrap();
assert_eq!(script.as_bytes(), script.to_bytes().as_slice());
}

#[test]
fn test_as_bytes_no_copy() {
let script_data = vec![0x76, 0xa9, 0x14];
let script = ScriptPubkey::new(&script_data).unwrap();
let bytes1 = script.as_bytes();
let bytes2 = script.as_bytes();
assert_eq!(bytes1.as_ptr(), bytes2.as_ptr());
}

#[test]
fn test_scriptpubkey_into_vec() {
let script_data = vec![0x76, 0xa9, 0x14];
Expand Down
Loading