rust-miniscript

repository·master·Indexed 19 days ago

https://github.com/rust-bitcoin/rust-miniscript

A 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.

Tokens
20.9K
Snippets
51
Records
90
Agent score
65%

What's inside rust-miniscript

  1. Handle compiler non-determinism and stability

    master

    The 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) to and_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.

  2. High-level features of rust-miniscript

    master

    The 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 compiler feature 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::TxIn with appropriate data.
    • Transaction Analysis: Identifies specific keys, hash preimages, and timelocks used to spend coins in a given Bitcoin transaction.
    • Embedded Support: Provides no_std support by disabling default-features.
  3. Understand Miniscript compiler guarantees and limitations

    master

    When 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 n ORs or split thresh into 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.
  4. Understand the difference between Valid, Safe, and Sane Miniscripts

    master

    When 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, or thresh arguments 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::parse and Miniscript::from_str only succeed on sane miniscripts.
    • Miniscript::parse_insane and Miniscript::from_str_insane must 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.
  5. Use the Miniscript Policy Compiler

    master

    The Miniscript Policy compiler (available via the compiler feature) converts a Bitcoin Policy into a Miniscript. It uses a brute-force algorithm with heuristics to find a Miniscript that minimizes the spending_weight, calculated as:

    spending_weight = spk_cost + witness_cost

    where spk_cost is the cost of the scriptPubKey and witness_cost is the expected cost of the witness based on satisfaction and dissatisfaction probabilities.

    Note: To use the compiler, ensure you have enabled the compiler feature in your Cargo.toml.

  6. Reproduce fuzzing failures

    master

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

    1. Copy the hex string from the crash summary.
    2. Create or edit a duplicate_crash test. Use extend_vec_from_hex to pass the hex string into do_test.
    3. Run cargo test.

    Important: If you used specific RUSTFLAGS (like --cfg=hashes_fuzz) during fuzzing, you must use the same flags when running cargo 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 test
  7. Run the miniscript fuzzing harness

    master

    The miniscript project uses honggfuzz for fuzzing. To run the standard fuzzing suite (briefly fuzzing every target as performed in CI), execute the fuzz.sh script from the fuzz/ directory.

    Prerequisites: To build honggfuzz, you must have libunwind installed, along with libopcodes and libbfd from 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.sh
  8. Manage long-term fuzzing targets

    master

    For extended fuzzing sessions, use the following utilities:

    • List all targets:
      source ./fuzz-util.sh
      listTargetNames
    - **Run all targets for one hour:**
      ```bash
    ./cycle.sh

    Note: cycle.sh uses chrt to 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
  9. Use rust-miniscript in no_std/embedded environments

    master

    To use rust-miniscript in an embedded or no_std environment, you must disable the default features. See the embedded/ directory in the repository for a concrete implementation example.

    # Example Cargo.toml configuration
    rust-miniscript = { version = "13.0.0", default-features = false }
  10. Fuzz with weak cryptography for faster execution

    master

    You 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.sh

    This replaces the hashing library with broken hashes and the secp256k1 library with broken cryptography.

    RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz"
  11. Add new fuzz tests

    master

    New fuzz tests are located in the fuzz_target/ directory. Follow these steps to add one:

    1. Create the test: Copy an existing fuzz test and modify the do_test function. Place it in the appropriate crate's directory or directly in fuzz_target/.
    2. Add dependencies: If your test requires new dependencies, add them to generate-files.sh so they are included in the generated Cargo.toml.
    3. Regenerate configuration: Run ./generate-files.sh to update the Cargo.toml and CI jobs.
    4. 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>