Crate miniscript_core_ffi

Crate miniscript_core_ffi 

Source
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

  1. FFI Boundary: All calls to the C++ library require unsafe blocks because Rust cannot verify the safety of foreign code.

  2. 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*)
  3. 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 (Drop impl)
  • Null Safety: All pointer dereferences are guarded by null checks
  • Lifetime Safety: The Miniscript struct owns its C++ object and ensures it outlives all references
  • Thread Safety: Miniscript implements Send and Sync because 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 (SegWit v0) and Tapscript (SegWit v1) 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 + Sync implementation

§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:

FlagMeaning
BBase expression (consumes nothing, produces nonzero)
VVerify expression (consumes nothing, produces nothing, fails if unsatisfied)
KKey expression (consumes nothing, produces a public key)
WWrapped expression (consumes one stack element)
zZero-arg property (consumes no stack elements)
oOne-arg property (consumes exactly one stack element)
nNonzero property (never produces zero)
dDissatisfiable property (has a dissatisfaction)
uUnit property (on satisfaction, puts exactly 1 on stack)
eExpression property (can be used as an expression)
fForced property (always requires a signature)
sSafe property (cannot be malleated)
mNonmalleable property (satisfaction is unique)
xExpensive verify property
kTimelock 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

Featurebitcoin-core-miniscript-ffirust-miniscript
ImplementationBitcoin Core C++Pure Rust
Consensus compatibilityReferenceAims to match
DependenciesBitcoin Core, BoostPure Rust
Build complexityHigherLower
Use caseCross-verification, referenceProduction 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§

EcdsaSignature
An ECDSA signature
Error
Error type for miniscript operations.
FfiSatisfactionResult
Hash160
Output of the Bitcoin HASH160 hash function. (RIPEMD160(SHA256))
Hash256
Output of the SHA256d hash function.
Miniscript
A parsed miniscript node.
MiniscriptNode
MiniscriptResult
Ripemd160
Output of the RIPEMD160 hash function.
SatisfierCallbacks
SatisfyResult
Result of a satisfaction attempt.
SchnorrSignature
A BIP340-341 serialized taproot signature with the corresponding hash type.
ScriptBuf
An owned, growable script.
Sha256
Output of the SHA256 hash function.
SimpleSatisfier
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.
LockTime
An absolute lock time value, representing either a block height or a UNIX timestamp (seconds since epoch).
MiniscriptAvailability
MiniscriptContext
RelativeLockTime
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.