Expand description
§bitcoin-core-miniscript-ffi
FFI bindings to Bitcoin Core’s miniscript implementation.
§Safety
This crate provides safe Rust wrappers around unsafe FFI calls to Bitcoin Core’s C++ miniscript implementation. The unsafe code is necessary for FFI interop and cannot be eliminated, but it is carefully encapsulated to provide a safe public API.
§Why Unsafe Code is Required
-
FFI Boundary: All calls to the C++ library require
unsafeblocks because Rust cannot verify the safety of foreign code. -
Raw Pointers: The C API uses raw pointers for:
- Opaque handles to C++ objects (
MiniscriptNode*) - String data (
char*) - Binary data (
uint8_t*) - Callback contexts (
void*)
- Opaque handles to C++ objects (
-
Callback Trampolines: The satisfier callbacks must be
extern "C"functions that receive raw pointers and convert them back to Rust types.
§Safety Guarantees
Despite the unsafe internals, this crate provides the following safety guarantees:
- Memory Safety: All C-allocated memory is properly freed via RAII (
Dropimpl) - Null Safety: All pointer dereferences are guarded by null checks
- Lifetime Safety: The
Miniscriptstruct owns its C++ object and ensures it outlives all references - Thread Safety:
MiniscriptimplementsSendandSyncbecause the underlying C++ object is immutable after creation - No Undefined Behavior: All unsafe blocks have documented invariants that are upheld by the implementation
This crate provides direct access to Bitcoin Core’s C++ miniscript parser and analyzer through safe Rust bindings. It enables cross-verification between Bitcoin Core and other miniscript implementations (like rust-miniscript), ensuring consensus-critical code behaves identically across implementations.
§Why This Crate?
- Reference Implementation: Bitcoin Core’s miniscript is the canonical implementation
- Cross-Verification: Validate that your miniscript implementation matches Bitcoin Core’s behavior exactly
- Production Tested: Code matches that of Bitcoin Core the majority consensus client
- Full Feature Parity: Supports both P2WSH (
SegWitv0) and Tapscript (SegWitv1) contexts - Type Safety: Safe Rust wrapper with proper memory management and error handling
§Features
- Parse miniscript expressions from strings
- Validate miniscript type correctness
- Check sanity constraints (no duplicate keys, no timelock mixing, resource limits)
- Extract type properties (B, V, K, W modifiers and more)
- Calculate maximum witness satisfaction size
- Convert miniscript back to canonical string representation
- Satisfy miniscripts with custom satisfiers
- Thread-safe:
Send + Syncimplementation
§Quick Start
use miniscript_core_ffi::{Miniscript, Context};
// Parse a simple miniscript (2-of-2 multisig)
let ms = Miniscript::from_str("and_v(v:pk(Alice),pk(Bob))", Context::Wsh)
.expect("valid miniscript");
// Validate the miniscript
assert!(ms.is_valid());
assert!(ms.is_sane());
// Get type properties
println!("Type: {}", ms.get_type().unwrap());
// Get maximum witness size
if let Some(size) = ms.max_satisfaction_size() {
println!("Max witness size: {} bytes", size);
}
// Convert back to string (canonical form)
println!("Canonical: {}", ms.to_string().unwrap());§Cross-Verification Example
use miniscript_core_ffi::{Miniscript, Context};
fn verify_against_core(miniscript_str: &str) -> bool {
// Parse with Bitcoin Core's implementation
let core_result = Miniscript::from_str(miniscript_str, Context::Wsh);
match core_result {
Ok(ms) => {
// Verify type properties match your implementation
let core_type = ms.get_type().unwrap();
println!("Core type: {}", core_type);
true
}
Err(e) => {
// Bitcoin Core rejected it - your implementation should too
println!("Core rejected: {}", e);
false
}
}
}§Taproot Support
use miniscript_core_ffi::{Miniscript, Context};
// Parse a Tapscript miniscript
let ms = Miniscript::from_str("pk(A)", Context::Tapscript)
.expect("valid tapscript");
println!("Valid: {}", ms.is_valid());
println!("Type: {}", ms.get_type().unwrap_or_default());§Satisfying Miniscripts
use miniscript_core_ffi::{Miniscript, Context, SimpleSatisfier};
let ms = Miniscript::from_str("pk(A)", Context::Wsh)
.expect("valid miniscript");
let mut satisfier = SimpleSatisfier::new();
// Add signature for key A
satisfier.signatures.insert(b"A".to_vec(), vec![0x30, 0x44, /* ... */]);
let result = ms.satisfy(satisfier, true).expect("satisfaction");
println!("Witness stack has {} elements", result.stack.len());§Type Properties
The type string returned by Miniscript::get_type() contains single-character flags:
| Flag | Meaning |
|---|---|
B | Base expression (consumes nothing, produces nonzero) |
V | Verify expression (consumes nothing, produces nothing, fails if unsatisfied) |
K | Key expression (consumes nothing, produces a public key) |
W | Wrapped expression (consumes one stack element) |
z | Zero-arg property (consumes no stack elements) |
o | One-arg property (consumes exactly one stack element) |
n | Nonzero property (never produces zero) |
d | Dissatisfiable property (has a dissatisfaction) |
u | Unit property (on satisfaction, puts exactly 1 on stack) |
e | Expression property (can be used as an expression) |
f | Forced property (always requires a signature) |
s | Safe property (cannot be malleated) |
m | Nonmalleable property (satisfaction is unique) |
x | Expensive verify property |
k | Timelock property (contains a timelock) |
§Thread Safety
Miniscript implements Send and Sync, making it safe to use across threads:
use miniscript_core_ffi::{Miniscript, Context};
use std::sync::Arc;
use std::thread;
let ms = Arc::new(
Miniscript::from_str("pk(A)", Context::Wsh).unwrap()
);
let handles: Vec<_> = (0..4).map(|_| {
let ms = Arc::clone(&ms);
thread::spawn(move || {
assert!(ms.is_valid());
})
}).collect();
for h in handles {
h.join().unwrap();
}§Comparison with rust-miniscript
| Feature | bitcoin-core-miniscript-ffi | rust-miniscript |
|---|---|---|
| Implementation | Bitcoin Core C++ | Pure Rust |
| Consensus compatibility | Reference | Aims to match |
| Dependencies | Bitcoin Core, Boost | Pure Rust |
| Build complexity | Higher | Lower |
| Use case | Cross-verification, reference | Production wallets |
Recommendation: Use this crate for testing and verification. Use rust-miniscript for production applications, but verify critical paths against this crate.
Re-exports§
pub use descriptor::Descriptor;pub use descriptor::DescriptorBuilder;pub use descriptor::Network as DescriptorNetwork;pub use descriptor::descriptor_version;pub use descriptor::get_descriptor_checksum;
Modules§
- descriptor
- Bitcoin Core Descriptor FFI bindings.
Structs§
- Ecdsa
Signature - An ECDSA signature
- Error
- Error type for miniscript operations.
- FfiSatisfaction
Result - Hash160
- Output of the Bitcoin HASH160 hash function. (RIPEMD160(SHA256))
- Hash256
- Output of the SHA256d hash function.
- Miniscript
- A parsed miniscript node.
- Miniscript
Node - Miniscript
Result - Ripemd160
- Output of the RIPEMD160 hash function.
- Satisfier
Callbacks - Satisfy
Result - Result of a satisfaction attempt.
- Schnorr
Signature - A BIP340-341 serialized taproot signature with the corresponding hash type.
- Script
Buf - An owned, growable script.
- Sha256
- Output of the SHA256 hash function.
- Simple
Satisfier - A simple satisfier that uses pre-populated data.
- Witness
- The Witness is the data used to unlock bitcoin since the segwit upgrade.
Enums§
- Availability
- Availability of a satisfaction.
- Context
- Script context for miniscript parsing.
- Lock
Time - An absolute lock time value, representing either a block height or a UNIX timestamp (seconds since epoch).
- Miniscript
Availability - Miniscript
Context - Relative
Lock Time - A relative lock time value, representing either a block height or time (512 second intervals).
Traits§
- Satisfier
- Trait for providing satisfaction data to miniscript.
Functions§
- version
- Get the library version string.