rust-miniscript
repository·master·Indexed 19 days ago
https://github.com/rust-bitcoin/rust-miniscriptA Rust library for handling Miniscript, a subset of Bitcoin Script designed for analysis and representing spending conditions as threshold circuits. It provides support for parsing, serializing, and analyzing Miniscript expressions and Bitcoin output descriptors, as well as policy compilation, semantic analysis, and witness generation. The library includes no_std support for embedded environments and a policy compiler to convert Bitcoin Policies into Miniscripts.
What's inside rust-miniscript
- rust-miniscript is a library for handling Miniscript, a subset of Bitcoin Script designed for simple and general tooling. It represents threshold circuits of spending conditions, allowing them to be easily visualized or serialized as human-readable strings.
Handle compiler non-determinism and stability
masterThe Miniscript compiler is non-deterministic. Outputs can change between:
- Different versions of the library.
- Different machines.
- Different executions on the same machine.
- Different implementations (e.g., Rust vs. C++ versions).
Because the compiler might reorder elements (e.g., changing
and_b(A, B)toand_b(B, A)), do not use a Policy as a stable identifier for a Miniscript.Best Practice: Run the policy through the compiler once, and use the resulting Miniscript output as your stable identifier.
High-level features of rust-miniscript
masterThe library provides several capabilities for working with Miniscript and Bitcoin descriptors:
- Descriptor Support: Handles Output descriptors, including those with embedded Miniscripts.
- Parsing & Serialization: Converts descriptors to and from human-readable string formats.
- Policy Compilation: Compiles abstract spending policies into Miniscript (requires the
compilerfeature flag). - Semantic Analysis: Performs analysis on Miniscripts and spending policies using user-defined public key types.
- Script Encoding/Decoding: Encodes and decodes Miniscript as Bitcoin Script (requires key types convertible to
bitcoin::PublicKey). - Satisfiability & Witness Generation: Determines satisfiability and optimal witnesses for a given descriptor; can complete an unsigned
bitcoin::TxInwith appropriate data. - Transaction Analysis: Identifies specific keys, hash preimages, and timelocks used to spend coins in a given Bitcoin transaction.
- Embedded Support: Provides
no_stdsupport by disablingdefault-features.
Understand Miniscript compiler guarantees and limitations
masterWhen the compiler successfully outputs a Miniscript, it provides the following guarantees:
- The output is a valid Miniscript.
- The output is guaranteed to be spendable under standardness rules (assuming an available witness).
Limitations to consider:
- Efficiency: The compiler does not guarantee the most efficient Miniscript. It may not rearrange policy trees optimally or optimize
1 of nORs or splitthreshinto ANDs/ORs. - Success Rate: For very large policies, the compiler might fail to produce a Miniscript even if one exists.
- Resource Constraints: The compiler does not optimize for opcode counts or initial stack element counts. If a sub-compilation exceeds these limits, the compiler may fail rather than attempting a sub-optimal version that fits.
- Taproot: These resource limitations are largely mitigated when using the Taproot descriptor compiler.
Understand the difference between Valid, Safe, and Sane Miniscripts
masterWhen working with
rust-miniscript, it is important to distinguish between three levels of script quality. This distinction determines which API methods you can use and whether the library can successfully analyze or lift your script.- Validity: The Miniscript tree follows the grammar rules (e.g., the top level is
B, orthresharguments are all dissatisfiable). - Safety: All satisfactions of the Miniscript require a digital signature.
- Sanity (Analyzable/Liftable): The script can be fully realized within Bitcoin network rules. A script might be valid and safe but still be "insane" if it cannot be realized due to resource limitations, invalid timelock/heightlock combinations, or repeated use of public keys/hashes.
API Usage Implications:
Miniscript::parseandMiniscript::from_stronly succeed on sane miniscripts.Miniscript::parse_insaneandMiniscript::from_str_insanemust be used to handle "insane" miniscripts.- Descriptor APIs only work for sane scripts.
- The library accepts all safe and valid scripts, and the signing logic will work for them, but lifting/analyzing them may fail if they are not sane.
- Validity: The Miniscript tree follows the grammar rules (e.g., the top level is
Use the Miniscript Policy Compiler
masterThe Miniscript Policy compiler (available via the
compilerfeature) converts a Bitcoin Policy into a Miniscript. It uses a brute-force algorithm with heuristics to find a Miniscript that minimizes thespending_weight, calculated as:spending_weight = spk_cost + witness_costwhere
spk_costis the cost of thescriptPubKeyandwitness_costis the expected cost of the witness based on satisfaction and dissatisfaction probabilities.Note: To use the compiler, ensure you have enabled the
compilerfeature in yourCargo.toml.Reproduce fuzzing failures
masterWhen a fuzz test fails, the output includes a hex-encoded version of the input that caused the crash on the final line.
To reproduce the crash:
- Copy the hex string from the crash summary.
- Create or edit a
duplicate_crashtest. Useextend_vec_from_hexto pass the hex string intodo_test. - Run
cargo test.
Important: If you used specific
RUSTFLAGS(like--cfg=hashes_fuzz) during fuzzing, you must use the same flags when runningcargo test.Template for
duplicate_crash:#[cfg(test)] mod tests { use miniscript::bitcoin::hex::FromHex; #[test] fn duplicate_crash() { let v = Vec::from_hex("abcd").unwrap(); super::do_test(&v); } }cargo testRun the miniscript fuzzing harness
masterThe
miniscriptproject useshonggfuzzfor fuzzing. To run the standard fuzzing suite (briefly fuzzing every target as performed in CI), execute thefuzz.shscript from thefuzz/directory.Prerequisites: To build
honggfuzz, you must havelibunwindinstalled, along withlibopcodesandlibbfdfrom binutils 2.38. Note that binutils 2.39 introduced breaking API changes.Nix Setup: On Nix, you can obtain the required libraries and disable conflicting hardening flags by running:
nix-shell -p libopcodes_2_38 -p libunwind # Inside the nix-shell: NIX_HARDENING_ENABLE=''${NIX_HARDENING_ENABLE/fortify/} NIX_HARDENING_ENABLE=''${NIX_HARDENING_ENABLE/fortify3/} ./fuzz.sh./fuzz.shManage long-term fuzzing targets
masterFor extended fuzzing sessions, use the following utilities:
- List all targets:
source ./fuzz-util.sh listTargetNames
- **Run all targets for one hour:** ```bash ./cycle.shNote:
cycle.shuseschrtto reduce job priority. Edit the script to run for longer durations.- Run a single target indefinitely:
cargo hfuzz run <target>
source ./fuzz-util.sh listTargetNames- List all targets:
Use rust-miniscript in no_std/embedded environments
masterTo use
rust-miniscriptin an embedded orno_stdenvironment, you must disable the default features. See theembedded/directory in the repository for a concrete implementation example.# Example Cargo.toml configuration rust-miniscript = { version = "13.0.0", default-features = false }Fuzz with weak cryptography for faster execution
masterYou can speed up fuzzing by replacing real hashing and signing code with broken cryptography. This allows the fuzzer to forge signatures or find hash preimages more easily.
Warning: This may result in spurious bug reports because the broken crypto does not respect real algebraic or encoding invariants. NEVER compile production code with these flags.
To use broken crypto, compile and run the fuzzing scripts with the following
RUSTFLAGS:RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz" ./fuzz.shThis replaces the hashing library with broken hashes and the
secp256k1library with broken cryptography.RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz"Add new fuzz tests
masterNew fuzz tests are located in the
fuzz_target/directory. Follow these steps to add one:- Create the test: Copy an existing fuzz test and modify the
do_testfunction. Place it in the appropriate crate's directory or directly infuzz_target/. - Add dependencies: If your test requires new dependencies, add them to
generate-files.shso they are included in the generatedCargo.toml. - Regenerate configuration: Run
./generate-files.shto update theCargo.tomland CI jobs. - Verify the test: Run
./fuzz.sh <target>. A working test will produce a rapid stream of data; a failing test will error out quickly.
./generate-files.sh ./fuzz.sh <target>- Create the test: Copy an existing fuzz test and modify the