├── bitcoind-tests ├── src │ └── main.rs ├── bin │ └── bitcoind ├── Cargo.toml └── tests │ ├── setup │ └── mod.rs │ ├── data │ └── random_ms.txt │ └── test_cpp.rs ├── embedded ├── scripts │ ├── install-deps │ └── env.sh ├── memory.x ├── README.md ├── Cargo.toml └── src │ └── main.rs ├── doc ├── taproot_compiler.pdf ├── ReasoningAboutMultipartyMiniscript.pdf ├── security_report_2022_04_20.md ├── resource_limitations.md └── compiler.md ├── clippy.toml ├── .gitignore ├── fuzz ├── fuzz_targets │ ├── parse_descriptor.rs │ ├── parse_descriptor_secret.rs │ ├── roundtrip_semantic.rs │ ├── compile_descriptor.rs │ ├── roundtrip_descriptor.rs │ ├── roundtrip_miniscript_script.rs │ ├── roundtrip_concrete.rs │ └── roundtrip_miniscript_str.rs ├── cycle.sh ├── fuzz.sh ├── Cargo.toml ├── fuzz-util.sh ├── generate-files.sh └── README.md ├── src ├── primitives │ ├── mod.rs │ ├── absolute_locktime.rs │ └── relative_locktime.rs ├── miniscript │ ├── limits.rs │ ├── analyzable.rs │ └── lex.rs ├── iter │ └── mod.rs ├── macros.rs ├── blanket_traits.rs ├── util.rs ├── pub_macros.rs ├── test_utils.rs ├── descriptor │ ├── sortedmulti.rs │ └── checksum.rs └── expression.rs ├── Cargo.toml ├── .github └── workflows │ ├── fuzz.yml │ └── rust.yml ├── rustfmt.toml ├── examples ├── parse.rs ├── xpub_descriptors.rs ├── htlc.rs ├── big.rs ├── sign_multisig.rs ├── taproot.rs ├── psbt_sign_finalize.rs └── verify_tx.rs ├── contrib └── test.sh ├── README.md ├── tests └── bip-174.rs ├── LICENSE └── CHANGELOG.md /bitcoind-tests/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | -------------------------------------------------------------------------------- /embedded/scripts/install-deps: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | apt install gcc-arm-none-eabi qemu-system-arm gdb-multiarch -------------------------------------------------------------------------------- /doc/taproot_compiler.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoelstra/rust-miniscript/HEAD/doc/taproot_compiler.pdf -------------------------------------------------------------------------------- /bitcoind-tests/bin/bitcoind: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoelstra/rust-miniscript/HEAD/bitcoind-tests/bin/bitcoind -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.56.1" 2 | # plan API returns Self as an error type for an large-ish enum 3 | large-error-threshold = 256 4 | -------------------------------------------------------------------------------- /embedded/memory.x: -------------------------------------------------------------------------------- 1 | MEMORY 2 | { 3 | FLASH : ORIGIN = 0x00000000, LENGTH = 2048K 4 | RAM : ORIGIN = 0x20000000, LENGTH = 512K 5 | } -------------------------------------------------------------------------------- /doc/ReasoningAboutMultipartyMiniscript.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/apoelstra/rust-miniscript/HEAD/doc/ReasoningAboutMultipartyMiniscript.pdf -------------------------------------------------------------------------------- /embedded/scripts/env.sh: -------------------------------------------------------------------------------- 1 | export RUSTFLAGS="-C link-arg=-Tlink.x" 2 | export CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER="qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | 4 | #emacs 5 | \#*\# 6 | 7 | #fuzz 8 | fuzz/hfuzz_target 9 | fuzz/hfuzz_workspace 10 | 11 | #IntelliJ project files 12 | .idea 13 | *.iml 14 | 15 | #emacs 16 | *~ 17 | 18 | #Vscode project files 19 | .vscode 20 | 21 | #MacOS 22 | *.DS_Store 23 | 24 | #Intergration test files 25 | integration_test/bitcoin-* -------------------------------------------------------------------------------- /bitcoind-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bitcoind-tests" 3 | version = "0.1.0" 4 | authors = ["sanket1729 "] 5 | edition = "2018" 6 | publish = false 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | miniscript = {path = "../"} 12 | bitcoind = { version = "0.34.0" } 13 | actual-rand = { package = "rand", version = "0.8.4"} 14 | secp256k1 = {version = "0.28.0", features = ["rand-std"]} 15 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::DescriptorPublicKey; 5 | 6 | fn do_test(data: &[u8]) { 7 | let data_str = String::from_utf8_lossy(data); 8 | if let Ok(dpk) = DescriptorPublicKey::from_str(&data_str) { 9 | let _output = dpk.to_string(); 10 | // assert_eq!(data_str.to_lowercase(), _output.to_lowercase()); 11 | } 12 | } 13 | 14 | fn main() { 15 | loop { 16 | fuzz!(|data| { 17 | do_test(data); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/parse_descriptor_secret.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::descriptor::DescriptorSecretKey; 5 | 6 | fn do_test(data: &[u8]) { 7 | let data_str = String::from_utf8_lossy(data); 8 | if let Ok(dsk) = DescriptorSecretKey::from_str(&data_str) { 9 | let output = dsk.to_string(); 10 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 11 | } 12 | } 13 | 14 | fn main() { 15 | loop { 16 | fuzz!(|data| { 17 | do_test(data); 18 | }); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_semantic.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::policy; 5 | 6 | type Policy = policy::Semantic; 7 | 8 | fn do_test(data: &[u8]) { 9 | let data_str = String::from_utf8_lossy(data); 10 | if let Ok(pol) = Policy::from_str(&data_str) { 11 | let output = pol.to_string(); 12 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 13 | } 14 | } 15 | 16 | fn main() { 17 | loop { 18 | fuzz!(|data| { 19 | do_test(data); 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /embedded/README.md: -------------------------------------------------------------------------------- 1 | # Running 2 | 3 | To run the embedded test, first prepare your environment: 4 | 5 | ```shell 6 | sudo ./scripts/install-deps 7 | rustup target add thumbv7m-none-eabi 8 | ``` 9 | 10 | Then: 11 | 12 | ```shell 13 | source ./scripts/env.sh && cargo run +nightly --target thumbv7m-none-eabi 14 | ``` 15 | 16 | Output should be something like: 17 | 18 | ```text 19 | heap size 1048576 20 | descriptor sh(wsh(or_d(c:pk_k(020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261),c:pk_k(0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352)))) 21 | p2sh address 3CJxbQBfWAe1ZkKiGQNEYrioV73ZwvBWns 22 | ``` 23 | -------------------------------------------------------------------------------- /src/primitives/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Primitive Types 4 | //! 5 | //! In Miniscript we have a few types which have stronger constraints than 6 | //! their equivalents in Bitcoin (or Rust). In particular, locktimes which 7 | //! appear in `after` and `older` fragments are constrained to be nonzero, 8 | //! and the relative locktimes in `older` fragments are only allowed to be 9 | //! the subset of sequence numbers which form valid locktimes. 10 | //! 11 | //! This module exists for code organization and any types defined here 12 | //! should be re-exported at the crate root. 13 | 14 | pub mod absolute_locktime; 15 | pub mod relative_locktime; 16 | -------------------------------------------------------------------------------- /embedded/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = [ 3 | "Riccardo Casatta ", 4 | "Dev Random ", 5 | ] 6 | edition = "2018" 7 | readme = "README.md" 8 | name = "embedded" 9 | version = "0.1.0" 10 | publish = false 11 | 12 | [dependencies] 13 | cortex-m = "0.6.0" 14 | cortex-m-rt = "0.6.10" 15 | cortex-m-semihosting = "0.3.3" 16 | panic-halt = "0.2.0" 17 | alloc-cortex-m = "0.4.1" 18 | miniscript = { path = "../", default-features = false, features = ["no-std"] } 19 | 20 | [[bin]] 21 | name = "embedded" 22 | test = false 23 | bench = false 24 | 25 | [profile.release] 26 | codegen-units = 1 # better optimizations 27 | debug = true # symbols are nice and they don't increase the size on Flash 28 | lto = true # better optimizations 29 | opt-level = "z" 30 | -------------------------------------------------------------------------------- /fuzz/cycle.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Continuosly cycle over fuzz targets running each for 1 hour. 4 | # It uses chrt SCHED_IDLE so that other process takes priority. 5 | # 6 | # For hfuzz options see https://github.com/google/honggfuzz/blob/master/docs/USAGE.md 7 | 8 | set -e 9 | REPO_DIR=$(git rev-parse --show-toplevel) 10 | # shellcheck source=./fuzz-util.sh 11 | source "$REPO_DIR/fuzz/fuzz-util.sh" 12 | 13 | while : 14 | do 15 | for targetFile in $(listTargetFiles); do 16 | targetName=$(targetFileToName "$targetFile") 17 | echo "Fuzzing target $targetName ($targetFile)" 18 | 19 | # fuzz for one hour 20 | HFUZZ_RUN_ARGS='--run_time 3600' chrt -i 0 cargo hfuzz run "$targetName" 21 | # minimize the corpus 22 | HFUZZ_RUN_ARGS="-i hfuzz_workspace/$targetName/input/ -P -M" chrt -i 0 cargo hfuzz run "$targetName" 23 | done 24 | done 25 | 26 | -------------------------------------------------------------------------------- /fuzz/fuzz.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -ex 3 | 4 | REPO_DIR=$(git rev-parse --show-toplevel) 5 | 6 | # shellcheck source=./fuzz-util.sh 7 | source "$REPO_DIR/fuzz/fuzz-util.sh" 8 | 9 | # Check that input files are correct Windows file names 10 | checkWindowsFiles 11 | 12 | if [ "$1" == "" ]; then 13 | targetFiles="$(listTargetFiles)" 14 | else 15 | targetFiles=fuzz_targets/"$1".rs 16 | fi 17 | 18 | cargo --version 19 | rustc --version 20 | 21 | # Testing 22 | cargo install --force honggfuzz --no-default-features 23 | for targetFile in $targetFiles; do 24 | targetName=$(targetFileToName "$targetFile") 25 | echo "Fuzzing target $targetName ($targetFile)" 26 | if [ -d "hfuzz_input/$targetName" ]; then 27 | HFUZZ_INPUT_ARGS="-f hfuzz_input/$targetName/input\"" 28 | else 29 | HFUZZ_INPUT_ARGS="" 30 | fi 31 | HFUZZ_RUN_ARGS="--run_time 30 --exit_upon_crash -v $HFUZZ_INPUT_ARGS" cargo hfuzz run "$targetName" 32 | 33 | checkReport "$targetName" 34 | done 35 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/compile_descriptor.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::{policy, Miniscript, Segwitv0}; 5 | use policy::Liftable; 6 | 7 | type Script = Miniscript; 8 | type Policy = policy::Concrete; 9 | 10 | fn do_test(data: &[u8]) { 11 | let data_str = String::from_utf8_lossy(data); 12 | if let Ok(pol) = Policy::from_str(&data_str) { 13 | // Compile 14 | if let Ok(desc) = pol.compile::() { 15 | // Lift 16 | assert_eq!(desc.lift().unwrap().sorted(), pol.lift().unwrap().sorted()); 17 | // Try to roundtrip the output of the compiler 18 | let output = desc.to_string(); 19 | if let Ok(desc) = Script::from_str(&output) { 20 | let rtt = desc.to_string(); 21 | assert_eq!(output.to_lowercase(), rtt.to_lowercase()); 22 | } else { 23 | panic!("compiler output something unparseable: {}", output) 24 | } 25 | } 26 | } 27 | } 28 | 29 | fn main() { 30 | loop { 31 | fuzz!(|data| { 32 | do_test(data); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "descriptor-fuzz" 3 | edition = "2018" 4 | version = "0.0.1" 5 | authors = ["Generated by fuzz/generate-files.sh"] 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | honggfuzz = { version = "0.5.55", default-features = false } 13 | miniscript = { path = "..", features = [ "compiler" ] } 14 | 15 | regex = "1.0" 16 | 17 | [[bin]] 18 | name = "roundtrip_semantic" 19 | path = "fuzz_targets/roundtrip_semantic.rs" 20 | 21 | [[bin]] 22 | name = "parse_descriptor" 23 | path = "fuzz_targets/parse_descriptor.rs" 24 | 25 | [[bin]] 26 | name = "parse_descriptor_secret" 27 | path = "fuzz_targets/parse_descriptor_secret.rs" 28 | 29 | [[bin]] 30 | name = "roundtrip_miniscript_script" 31 | path = "fuzz_targets/roundtrip_miniscript_script.rs" 32 | 33 | [[bin]] 34 | name = "roundtrip_miniscript_str" 35 | path = "fuzz_targets/roundtrip_miniscript_str.rs" 36 | 37 | [[bin]] 38 | name = "roundtrip_descriptor" 39 | path = "fuzz_targets/roundtrip_descriptor.rs" 40 | 41 | [[bin]] 42 | name = "roundtrip_concrete" 43 | path = "fuzz_targets/roundtrip_concrete.rs" 44 | 45 | [[bin]] 46 | name = "compile_descriptor" 47 | path = "fuzz_targets/compile_descriptor.rs" 48 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_descriptor.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::Descriptor; 5 | 6 | fn do_test(data: &[u8]) { 7 | let s = String::from_utf8_lossy(data); 8 | if let Ok(desc) = Descriptor::::from_str(&s) { 9 | let str2 = desc.to_string(); 10 | let desc2 = Descriptor::::from_str(&str2).unwrap(); 11 | 12 | assert_eq!(desc, desc2); 13 | } 14 | } 15 | 16 | fn main() { 17 | loop { 18 | fuzz!(|data| { 19 | do_test(data); 20 | }); 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 27 | let mut b = 0; 28 | for (idx, c) in hex.as_bytes().iter().enumerate() { 29 | b <<= 4; 30 | match *c { 31 | b'A'..=b'F' => b |= c - b'A' + 10, 32 | b'a'..=b'f' => b |= c - b'a' + 10, 33 | b'0'..=b'9' => b |= c - b'0', 34 | _ => panic!("Bad hex"), 35 | } 36 | if (idx & 1) == 1 { 37 | out.push(b); 38 | b = 0; 39 | } 40 | } 41 | } 42 | 43 | #[test] 44 | fn duplicate_crash3() { 45 | let mut a = Vec::new(); 46 | extend_vec_from_hex("747228726970656d616e645f6e5b5c79647228726970656d616e645f6e5b5c7964646464646464646464646464646464646464646464646464646b5f6872702c29", &mut a); 47 | super::do_test(&a); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fuzz/fuzz-util.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | REPO_DIR=$(git rev-parse --show-toplevel) 4 | 5 | listTargetFiles() { 6 | pushd "$REPO_DIR/fuzz" > /dev/null || exit 1 7 | find fuzz_targets/ -type f -name "*.rs" 8 | popd > /dev/null || exit 1 9 | } 10 | 11 | targetFileToName() { 12 | echo "$1" \ 13 | | sed 's/^fuzz_targets\///' \ 14 | | sed 's/\.rs$//' \ 15 | | sed 's/\//_/g' 16 | } 17 | 18 | targetFileToHFuzzInputArg() { 19 | baseName=$(basename "$1") 20 | dirName="${baseName%.*}" 21 | if [ -d "hfuzz_input/$dirName" ]; then 22 | echo "HFUZZ_INPUT_ARGS=\"-f hfuzz_input/$FILE/input\"" 23 | fi 24 | } 25 | 26 | listTargetNames() { 27 | for target in $(listTargetFiles); do 28 | targetFileToName "$target" 29 | done 30 | } 31 | 32 | # Utility function to avoid CI failures on Windows 33 | checkWindowsFiles() { 34 | incorrectFilenames=$(find . -type f -name "*,*" -o -name "*:*" -o -name "*<*" -o -name "*>*" -o -name "*|*" -o -name "*\?*" -o -name "*\**" -o -name "*\"*" | wc -l) 35 | if [ "$incorrectFilenames" -gt 0 ]; then 36 | echo "Bailing early because there is a Windows-incompatible filename in the tree." 37 | exit 2 38 | fi 39 | } 40 | 41 | # Checks whether a fuzz case output some report, and dumps it in hex 42 | checkReport() { 43 | reportFile="hfuzz_workspace/$1/HONGGFUZZ.REPORT.TXT" 44 | if [ -f "$reportFile" ]; then 45 | cat "$reportFile" 46 | for CASE in "hfuzz_workspace/$1/SIG"*; do 47 | xxd -p -c10000 < "$CASE" 48 | done 49 | exit 1 50 | fi 51 | } 52 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/setup/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate miniscript; 2 | 3 | use bitcoind::bitcoincore_rpc::RpcApi; 4 | use bitcoind::BitcoinD; 5 | use miniscript::bitcoin; 6 | 7 | pub mod test_util; 8 | 9 | // Launch an instance of bitcoind with 10 | pub fn setup() -> BitcoinD { 11 | // Create env var BITCOIND_EXE_PATH to point to the ../bitcoind/bin/bitcoind binary 12 | let key = "BITCOIND_EXE"; 13 | if std::env::var(key).is_err() { 14 | let mut root_path = std::env::current_dir().unwrap(); 15 | while std::fs::metadata(root_path.join("LICENSE")).is_err() { 16 | if !root_path.pop() { 17 | panic!("Could not find LICENSE file; do not know where repo root is."); 18 | } 19 | } 20 | 21 | let bitcoind_path = root_path 22 | .join("bitcoind-tests") 23 | .join("bin") 24 | .join("bitcoind"); 25 | std::env::set_var(key, bitcoind_path); 26 | } 27 | 28 | let exe_path = bitcoind::exe_path().unwrap(); 29 | let bitcoind = bitcoind::BitcoinD::new(exe_path).unwrap(); 30 | let cl = &bitcoind.client; 31 | // generate to an address by the wallet. And wait for funds to mature 32 | let addr = cl.get_new_address(None, None).unwrap().assume_checked(); 33 | let blks = cl.generate_to_address(101, &addr).unwrap(); 34 | assert_eq!(blks.len(), 101); 35 | 36 | assert_eq!( 37 | cl.get_balance(Some(1) /*min conf*/, None).unwrap(), 38 | bitcoin::Amount::from_sat(100_000_000 * 50) 39 | ); 40 | bitcoind 41 | } 42 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_script.rs: -------------------------------------------------------------------------------- 1 | use honggfuzz::fuzz; 2 | use miniscript::bitcoin::blockdata::script; 3 | use miniscript::{Miniscript, Segwitv0}; 4 | 5 | fn do_test(data: &[u8]) { 6 | // Try round-tripping as a script 7 | let script = script::Script::from_bytes(data); 8 | 9 | if let Ok(pt) = Miniscript::::parse(script) { 10 | let output = pt.encode(); 11 | assert_eq!(pt.script_size(), output.len()); 12 | assert_eq!(&output, script); 13 | } 14 | } 15 | 16 | fn main() { 17 | loop { 18 | fuzz!(|data| { 19 | do_test(data); 20 | }); 21 | } 22 | } 23 | 24 | #[cfg(test)] 25 | mod tests { 26 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 27 | let mut b = 0; 28 | for (idx, c) in hex.as_bytes().iter().enumerate() { 29 | b <<= 4; 30 | match *c { 31 | b'A'..=b'F' => b |= c - b'A' + 10, 32 | b'a'..=b'f' => b |= c - b'a' + 10, 33 | b'0'..=b'9' => b |= c - b'0', 34 | _ => panic!("Bad hex"), 35 | } 36 | if (idx & 1) == 1 { 37 | out.push(b); 38 | b = 0; 39 | } 40 | } 41 | } 42 | 43 | #[test] 44 | fn duplicate_crash3() { 45 | let mut a = Vec::new(); 46 | extend_vec_from_hex("1479002d00000020323731363342740000004000000000000000000000000000000000000063630004636363639c00000000000000000000", &mut a); 47 | super::do_test(&a); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_concrete.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::policy; 5 | use regex::Regex; 6 | 7 | type Policy = policy::Concrete; 8 | 9 | fn do_test(data: &[u8]) { 10 | let data_str = String::from_utf8_lossy(data); 11 | if let Ok(pol) = Policy::from_str(&data_str) { 12 | let output = pol.to_string(); 13 | //remove all instances of 1@ 14 | let re = Regex::new("(\\D)1@").unwrap(); 15 | let output = re.replace_all(&output, "$1"); 16 | let data_str = re.replace_all(&data_str, "$1"); 17 | assert_eq!(data_str.to_lowercase(), output.to_lowercase()); 18 | } 19 | } 20 | 21 | fn main() { 22 | loop { 23 | fuzz!(|data| { 24 | do_test(data); 25 | }); 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 32 | let mut b = 0; 33 | for (idx, c) in hex.as_bytes().iter().enumerate() { 34 | b <<= 4; 35 | match *c { 36 | b'A'..=b'F' => b |= c - b'A' + 10, 37 | b'a'..=b'f' => b |= c - b'a' + 10, 38 | b'0'..=b'9' => b |= c - b'0', 39 | _ => panic!("Bad hex"), 40 | } 41 | if (idx & 1) == 1 { 42 | out.push(b); 43 | b = 0; 44 | } 45 | } 46 | } 47 | 48 | #[test] 49 | fn duplicate_crash() { 50 | let mut a = Vec::new(); 51 | extend_vec_from_hex("048531e80700ae6400670000af5168", &mut a); 52 | super::do_test(&a); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /fuzz/fuzz_targets/roundtrip_miniscript_str.rs: -------------------------------------------------------------------------------- 1 | use std::str::FromStr; 2 | 3 | use honggfuzz::fuzz; 4 | use miniscript::{Miniscript, Segwitv0, Tap}; 5 | 6 | fn do_test(data: &[u8]) { 7 | let s = String::from_utf8_lossy(data); 8 | if let Ok(desc) = Miniscript::::from_str(&s) { 9 | let str2 = desc.to_string(); 10 | let desc2 = Miniscript::::from_str(&str2).unwrap(); 11 | 12 | assert_eq!(desc, desc2); 13 | } else if let Ok(desc) = Miniscript::::from_str(&s) { 14 | let str2 = desc.to_string(); 15 | let desc2: Miniscript = Miniscript::::from_str(&str2).unwrap(); 16 | 17 | assert_eq!(desc, desc2); 18 | } 19 | } 20 | 21 | fn main() { 22 | loop { 23 | fuzz!(|data| { 24 | do_test(data); 25 | }); 26 | } 27 | } 28 | 29 | #[cfg(test)] 30 | mod tests { 31 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 32 | let mut b = 0; 33 | for (idx, c) in hex.as_bytes().iter().enumerate() { 34 | b <<= 4; 35 | match *c { 36 | b'A'..=b'F' => b |= c - b'A' + 10, 37 | b'a'..=b'f' => b |= c - b'a' + 10, 38 | b'0'..=b'9' => b |= c - b'0', 39 | _ => panic!("Bad hex"), 40 | } 41 | if (idx & 1) == 1 { 42 | out.push(b); 43 | b = 0; 44 | } 45 | } 46 | } 47 | 48 | #[test] 49 | fn duplicate_crash() { 50 | let mut a = Vec::new(); 51 | extend_vec_from_hex("1479002d00000020323731363342740000004000000000000000000000000000000000000063630004636363639c00000000000000000000", &mut a); 52 | super::do_test(&a); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "miniscript" 3 | version = "11.0.0" 4 | authors = ["Andrew Poelstra , Sanket Kanjalkar "] 5 | license = "CC0-1.0" 6 | homepage = "https://github.com/rust-bitcoin/rust-miniscript/" 7 | repository = "https://github.com/rust-bitcoin/rust-miniscript/" 8 | description = "Miniscript: a subset of Bitcoin Script designed for analysis" 9 | keywords = [ "crypto", "bitcoin", "miniscript", "script" ] 10 | readme = "README.md" 11 | edition = "2018" 12 | 13 | [features] 14 | default = ["std"] 15 | std = ["bitcoin/std", "bitcoin/secp-recovery", "bech32/std"] 16 | no-std = ["bitcoin/no-std", "bech32/alloc"] 17 | compiler = [] 18 | trace = [] 19 | 20 | serde = ["actual-serde", "bitcoin/serde"] 21 | rand = ["bitcoin/rand"] 22 | base64 = ["bitcoin/base64"] 23 | 24 | [dependencies] 25 | bech32 = { version = "0.10.0-beta", default-features = false } 26 | bitcoin = { version = "0.31.0", default-features = false } 27 | 28 | # Do NOT use this as a feature! Use the `serde` feature instead. 29 | actual-serde = { package = "serde", version = "1.0.103", optional = true } 30 | 31 | [dev-dependencies] 32 | serde_test = "1.0.147" 33 | bitcoin = { version = "0.31.0", features = ["base64"] } 34 | secp256k1 = {version = "0.28.0", features = ["rand-std"]} 35 | 36 | [[example]] 37 | name = "htlc" 38 | required-features = ["std", "compiler"] 39 | 40 | [[example]] 41 | name = "parse" 42 | required-features = ["std"] 43 | 44 | [[example]] 45 | name = "sign_multisig" 46 | required-features = ["std"] 47 | 48 | [[example]] 49 | name = "verify_tx" 50 | required-features = ["std"] 51 | 52 | [[example]] 53 | name = "xpub_descriptors" 54 | required-features = ["std"] 55 | 56 | [[example]] 57 | name = "taproot" 58 | required-features = ["compiler","std"] 59 | 60 | [[example]] 61 | name = "psbt_sign_finalize" 62 | required-features = ["std", "base64"] 63 | 64 | [[example]] 65 | name = "big" 66 | required-features = ["std", "base64", "compiler"] 67 | 68 | [workspace] 69 | members = ["bitcoind-tests", "fuzz"] 70 | exclude = ["embedded"] 71 | -------------------------------------------------------------------------------- /.github/workflows/fuzz.yml: -------------------------------------------------------------------------------- 1 | # Automatically generated by fuzz/generate-files.sh 2 | name: Fuzz 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | - 'test-ci/**' 9 | pull_request: 10 | 11 | jobs: 12 | fuzz: 13 | if: ${{ !github.event.act }} 14 | runs-on: ubuntu-20.04 15 | strategy: 16 | fail-fast: false 17 | matrix: 18 | fuzz_target: [ 19 | roundtrip_semantic, 20 | parse_descriptor, 21 | parse_descriptor_secret, 22 | roundtrip_miniscript_script, 23 | roundtrip_miniscript_str, 24 | roundtrip_descriptor, 25 | roundtrip_concrete, 26 | compile_descriptor, 27 | ] 28 | steps: 29 | - name: Install test dependencies 30 | run: sudo apt-get update -y && sudo apt-get install -y binutils-dev libunwind8-dev libcurl4-openssl-dev libelf-dev libdw-dev cmake gcc libiberty-dev 31 | - uses: actions/checkout@v2 32 | - uses: actions/cache@v2 33 | id: cache-fuzz 34 | with: 35 | path: | 36 | ~/.cargo/bin 37 | fuzz/target 38 | target 39 | key: cache-${{ matrix.target }}-${{ hashFiles('**/Cargo.toml','**/Cargo.lock') }} 40 | - uses: actions-rs/toolchain@v1 41 | with: 42 | toolchain: '1.65' 43 | override: true 44 | profile: minimal 45 | - name: fuzz 46 | run: cd fuzz && ./fuzz.sh "${{ matrix.fuzz_target }}" 47 | - run: echo "${{ matrix.fuzz_target }}" >executed_${{ matrix.fuzz_target }} 48 | - uses: actions/upload-artifact@v2 49 | with: 50 | name: executed_${{ matrix.fuzz_target }} 51 | path: executed_${{ matrix.fuzz_target }} 52 | 53 | verify-execution: 54 | if: ${{ !github.event.act }} 55 | needs: fuzz 56 | runs-on: ubuntu-latest 57 | steps: 58 | - uses: actions/checkout@v2 59 | - uses: actions/download-artifact@v2 60 | - name: Display structure of downloaded files 61 | run: ls -R 62 | - run: find executed_* -type f -exec cat {} + | sort > executed 63 | - run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed 64 | -------------------------------------------------------------------------------- /src/miniscript/limits.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Miscellaneous constraints imposed by Bitcoin. 4 | //! These constraints can be either Consensus or Policy (standardness) rules, for either Segwitv0 5 | //! or Legacy scripts. 6 | 7 | /// Maximum operations per script 8 | // https://github.com/bitcoin/bitcoin/blob/875e1ccc9fe01e026e564dfd39a64d9a4b332a89/src/script/script.h#L26 9 | pub const MAX_OPS_PER_SCRIPT: usize = 201; 10 | /// Maximum p2wsh initial stack items 11 | // https://github.com/bitcoin/bitcoin/blob/875e1ccc9fe01e026e564dfd39a64d9a4b332a89/src/policy/policy.h#L40 12 | pub const MAX_STANDARD_P2WSH_STACK_ITEMS: usize = 100; 13 | /// Maximum script size allowed by consensus rules 14 | // https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/script/script.h#L32 15 | pub const MAX_SCRIPT_SIZE: usize = 10_000; 16 | /// Maximum script size allowed by standardness rules 17 | // https://github.com/bitcoin/bitcoin/blob/283a73d7eaea2907a6f7f800f529a0d6db53d7a6/src/policy/policy.h#L44 18 | pub const MAX_STANDARD_P2WSH_SCRIPT_SIZE: usize = 3600; 19 | 20 | /// Maximum script element size allowed by consensus rules 21 | // https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/script/script.h#L23 22 | pub const MAX_SCRIPT_ELEMENT_SIZE: usize = 520; 23 | /// Maximum script sig size allowed by standardness rules 24 | // https://github.com/bitcoin/bitcoin/blob/42b66a6b814bca130a9ccf0a3f747cf33d628232/src/policy/policy.cpp#L102 25 | pub const MAX_SCRIPTSIG_SIZE: usize = 1650; 26 | /// Maximum items during stack execution 27 | // This limits also applies for initial stack satisfaction 28 | // https://github.com/bitcoin/bitcoin/blob/3af495d6972379b07530a5fcc2665aa626d01621/src/script/script.h#L35 29 | pub const MAX_STACK_SIZE: usize = 1000; 30 | /** The maximum allowed weight for a block, see BIP 141 (network rule) */ 31 | pub const MAX_BLOCK_WEIGHT: usize = 4000000; 32 | 33 | /// Maximum pubkeys as arguments to CHECKMULTISIG 34 | // https://github.com/bitcoin/bitcoin/blob/6acda4b00b3fc1bfac02f5de590e1a5386cbc779/src/script/script.h#L30 35 | pub const MAX_PUBKEYS_PER_MULTISIG: usize = 20; 36 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | max_width = 100 2 | hard_tabs = false 3 | tab_spaces = 4 4 | newline_style = "Auto" 5 | indent_style = "Block" 6 | use_small_heuristics = "Default" 7 | fn_call_width = 80 8 | attr_fn_like_width = 70 9 | struct_lit_width = 80 10 | struct_variant_width = 35 11 | array_width = 60 12 | chain_width = 60 13 | single_line_if_else_max_width = 50 14 | wrap_comments = false 15 | format_code_in_doc_comments = false 16 | comment_width = 80 17 | normalize_comments = false 18 | normalize_doc_attributes = false 19 | format_strings = false 20 | format_macro_matchers = false 21 | format_macro_bodies = true 22 | hex_literal_case = "Preserve" 23 | empty_item_single_line = true 24 | struct_lit_single_line = true 25 | fn_single_line = true 26 | where_single_line = false 27 | imports_indent = "Block" 28 | imports_layout = "Mixed" 29 | imports_granularity = "Module" # Default "Preserve" 30 | group_imports = "StdExternalCrate" # Default "Preserve" 31 | reorder_imports = true 32 | reorder_modules = true 33 | reorder_impl_items = false 34 | type_punctuation_density = "Wide" 35 | space_before_colon = false 36 | space_after_colon = true 37 | spaces_around_ranges = false 38 | binop_separator = "Front" 39 | remove_nested_parens = true 40 | combine_control_expr = true 41 | overflow_delimited_expr = false 42 | struct_field_align_threshold = 0 43 | enum_discrim_align_threshold = 0 44 | match_arm_blocks = true 45 | match_arm_leading_pipes = "Never" 46 | force_multiline_blocks = false 47 | fn_params_layout = "Tall" 48 | brace_style = "SameLineWhere" 49 | control_brace_style = "AlwaysSameLine" 50 | trailing_semicolon = true 51 | trailing_comma = "Vertical" 52 | match_block_trailing_comma = false 53 | blank_lines_upper_bound = 1 54 | blank_lines_lower_bound = 0 55 | edition = "2018" 56 | version = "One" 57 | inline_attribute_width = 0 58 | format_generated_files = true 59 | merge_derives = true 60 | use_try_shorthand = false 61 | use_field_init_shorthand = false 62 | force_explicit_abi = true 63 | condense_wildcard_suffixes = false 64 | color = "Auto" 65 | unstable_features = false 66 | disable_all_formatting = false 67 | skip_children = false 68 | hide_parse_errors = false 69 | error_on_line_overflow = false 70 | error_on_unformatted = false 71 | ignore = [] 72 | emit_mode = "Files" 73 | make_backup = false 74 | -------------------------------------------------------------------------------- /examples/parse.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Example: Parsing a descriptor from a string. 4 | 5 | use std::str::FromStr; 6 | 7 | use miniscript::descriptor::DescriptorType; 8 | use miniscript::Descriptor; 9 | 10 | fn main() { 11 | let desc = miniscript::Descriptor::::from_str( 12 | "wsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202))", 13 | ) 14 | .unwrap(); 15 | 16 | // Check whether the descriptor is safe. This checks whether all spend paths are accessible in 17 | // the Bitcoin network. It may be possible that some of the spend paths require more than 100 18 | // elements in Wsh scripts or they contain a combination of timelock and heightlock. 19 | assert!(desc.sanity_check().is_ok()); 20 | 21 | // Compute the script pubkey. As mentioned in the documentation, script_pubkey only fails 22 | // for Tr descriptors that don't have some pre-computed data. 23 | assert_eq!( 24 | format!("{:x}", desc.script_pubkey()), 25 | "0020daef16dd7c946a3e735a6e43310cb2ce33dfd14a04f76bf8241a16654cb2f0f9" 26 | ); 27 | 28 | // As another way to compute script pubkey; we can also compute the type of the descriptor. 29 | let desc_type = desc.desc_type(); 30 | assert_eq!(desc_type, DescriptorType::Wsh); 31 | // Since we know the type of descriptor, we can get the Wsh struct from Descriptor. This allows 32 | // us to call infallible methods for getting script pubkey. 33 | if let Descriptor::Wsh(wsh) = &desc { 34 | assert_eq!( 35 | format!("{:x}", wsh.script_pubkey()), 36 | "0020daef16dd7c946a3e735a6e43310cb2ce33dfd14a04f76bf8241a16654cb2f0f9" 37 | ); 38 | } 39 | 40 | // Get the inner script inside the descriptor. 41 | assert_eq!( 42 | format!( 43 | "{:x}", 44 | desc.explicit_script() 45 | .expect("Wsh descriptors have inner scripts") 46 | ), 47 | "21020202020202020202020202020202020202020202020202020202020202020202ac" 48 | ); 49 | 50 | // In a similar fashion we can parse a wrapped segwit script. 51 | let desc = miniscript::Descriptor::::from_str( 52 | "sh(wsh(c:pk_k(020202020202020202020202020202020202020202020202020202020202020202)))", 53 | ) 54 | .unwrap(); 55 | assert!(desc.desc_type() == DescriptorType::ShWsh); 56 | } 57 | -------------------------------------------------------------------------------- /embedded/src/main.rs: -------------------------------------------------------------------------------- 1 | #![no_std] 2 | #![no_main] 3 | #![feature(alloc_error_handler)] 4 | #![feature(panic_info_message)] 5 | 6 | extern crate alloc; 7 | 8 | use alloc::string::ToString; 9 | use core::alloc::Layout; 10 | use core::panic::PanicInfo; 11 | use core::str::FromStr; 12 | 13 | use alloc_cortex_m::CortexMHeap; 14 | use cortex_m::asm; 15 | use cortex_m_rt::entry; 16 | use cortex_m_semihosting::{debug, hprintln}; 17 | 18 | // this is the allocator the application will use 19 | #[global_allocator] 20 | static ALLOCATOR: CortexMHeap = CortexMHeap::empty(); 21 | 22 | const HEAP_SIZE: usize = 1024 * 256; // 256 KB 23 | 24 | #[entry] 25 | fn main() -> ! { 26 | hprintln!("heap size {}", HEAP_SIZE).unwrap(); 27 | 28 | unsafe { ALLOCATOR.init(cortex_m_rt::heap_start() as usize, HEAP_SIZE) } 29 | 30 | // begin miniscript test 31 | let descriptor = "sh(wsh(or_d(\ 32 | c:pk_k(020e0338c96a8870479f2396c373cc7696ba124e8635d41b0ea581112b67817261),\ 33 | c:pk_k(0250863ad64a87ae8a2fe83c1af1a8403cb53f53e486d8511dad8a04887e5b2352)\ 34 | )))"; 35 | hprintln!("descriptor {}", descriptor).unwrap(); 36 | let desc = 37 | miniscript::Descriptor::::from_str(descriptor).unwrap(); 38 | 39 | // Derive the P2SH address 40 | let p2sh_addr = desc 41 | .address(miniscript::bitcoin::Network::Bitcoin) 42 | .unwrap() 43 | .to_string(); 44 | hprintln!("p2sh address {}", p2sh_addr).unwrap(); 45 | assert_eq!(p2sh_addr, "3CJxbQBfWAe1ZkKiGQNEYrioV73ZwvBWns"); 46 | 47 | // Check whether the descriptor is safe 48 | // This checks whether all spend paths are accessible in bitcoin network. 49 | // It maybe possible that some of the spend require more than 100 elements in Wsh scripts 50 | // Or they contain a combination of timelock and heightlock. 51 | assert!(desc.sanity_check().is_ok()); 52 | 53 | // Estimate the satisfaction cost 54 | assert_eq!(desc.max_weight_to_satisfy().unwrap(), 288); 55 | // end miniscript test 56 | 57 | // exit QEMU 58 | // NOTE do not run this on hardware; it can corrupt OpenOCD state 59 | debug::exit(debug::EXIT_SUCCESS); 60 | 61 | loop {} 62 | } 63 | 64 | // define what happens in an Out Of Memory (OOM) condition 65 | #[alloc_error_handler] 66 | fn alloc_error(_layout: Layout) -> ! { 67 | hprintln!("alloc error").unwrap(); 68 | debug::exit(debug::EXIT_FAILURE); 69 | asm::bkpt(); 70 | 71 | loop {} 72 | } 73 | 74 | #[inline(never)] 75 | #[panic_handler] 76 | fn panic(info: &PanicInfo) -> ! { 77 | hprintln!("panic {:?}", info.message()).unwrap(); 78 | debug::exit(debug::EXIT_FAILURE); 79 | loop {} 80 | } 81 | -------------------------------------------------------------------------------- /examples/xpub_descriptors.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Example: Parsing a xpub and getting an address. 4 | 5 | use std::str::FromStr; 6 | 7 | use miniscript::bitcoin::secp256k1::{Secp256k1, Verification}; 8 | use miniscript::bitcoin::{Address, Network}; 9 | use miniscript::{DefiniteDescriptorKey, Descriptor, DescriptorPublicKey}; 10 | 11 | const XPUB_1: &str = "xpub661MyMwAqRbcFW31YEwpkMuc5THy2PSt5bDMsktWQcFF8syAmRUapSCGu8ED9W6oDMSgv6Zz8idoc4a6mr8BDzTJY47LJhkJ8UB7WEGuduB"; 12 | const XPUB_2: &str = "xpub69H7F5d8KSRgmmdJg2KhpAK8SR3DjMwAdkxj3ZuxV27CprR9LgpeyGmXUbC6wb7ERfvrnKZjXoUmmDznezpbZb7ap6r1D3tgFxHmwMkQTPH"; 13 | 14 | fn main() { 15 | // For deriving from descriptors, we need to provide a secp context. 16 | let secp = Secp256k1::verification_only(); 17 | 18 | // P2WSH and single xpubs. 19 | let _ = p2wsh(&secp); 20 | 21 | // P2WSH-P2SH and ranged xpubs. 22 | let _ = p2sh_p2wsh(&secp); 23 | } 24 | 25 | /// Parses a P2WSH descriptor, returns the associated address. 26 | fn p2wsh(secp: &Secp256k1) -> Address { 27 | // It does not matter what order the two xpubs go in, the same address will be generated. 28 | let s = format!("wsh(sortedmulti(1,{},{}))", XPUB_1, XPUB_2); 29 | // let s = format!("wsh(sortedmulti(1,{},{}))", XPUB_2, XPUB_1); 30 | 31 | let address = Descriptor::::from_str(&s) 32 | .unwrap() 33 | .derived_descriptor(secp) 34 | .unwrap() 35 | .address(Network::Bitcoin) 36 | .unwrap(); 37 | 38 | let expected = bitcoin::Address::from_str( 39 | "bc1qpq2cfgz5lktxzr5zqv7nrzz46hsvq3492ump9pz8rzcl8wqtwqcspx5y6a", 40 | ) 41 | .unwrap() 42 | .require_network(Network::Bitcoin) 43 | .unwrap(); 44 | assert_eq!(address, expected); 45 | address 46 | } 47 | 48 | /// Parses a P2SH-P2WSH descriptor, returns the associated address. 49 | fn p2sh_p2wsh(secp: &Secp256k1) -> Address { 50 | // It does not matter what order the two xpubs go in, the same address will be generated. 51 | let s = format!("sh(wsh(sortedmulti(1,{}/1/0/*,{}/0/0/*)))", XPUB_1, XPUB_2); 52 | // let s = format!("sh(wsh(sortedmulti(1,{}/1/0/*,{}/0/0/*)))", XPUB_2, XPUB_1); 53 | 54 | let address = Descriptor::::from_str(&s) 55 | .unwrap() 56 | .derived_descriptor(secp, 5) 57 | .unwrap() 58 | .address(Network::Bitcoin) 59 | .unwrap(); 60 | 61 | let expected = Address::from_str("325zcVBN5o2eqqqtGwPjmtDd8dJRyYP82s") 62 | .unwrap() 63 | .require_network(Network::Bitcoin) 64 | .unwrap(); 65 | assert_eq!(address, expected); 66 | address 67 | } 68 | -------------------------------------------------------------------------------- /contrib/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -ex 4 | 5 | FEATURES="compiler serde rand base64" 6 | 7 | cargo --version 8 | rustc --version 9 | 10 | # Cache the toolchain we are using. 11 | NIGHTLY=false 12 | if cargo --version | grep nightly; then 13 | NIGHTLY=true 14 | fi 15 | 16 | # Format if told to 17 | if [ "$DO_FMT" = true ] 18 | then 19 | rustup component add rustfmt 20 | cargo fmt -- --check 21 | fi 22 | 23 | # Test bitcoind integration tests if told to (this only works with the stable toolchain) 24 | if [ "$DO_BITCOIND_TESTS" = true ]; then 25 | cd bitcoind-tests 26 | BITCOIND_EXE="$(git rev-parse --show-toplevel)/bitcoind-tests/bin/bitcoind" \ 27 | cargo test --verbose 28 | 29 | # Exit integration tests, do not run other tests. 30 | exit 0 31 | fi 32 | 33 | # Defaults / sanity checks 34 | cargo test 35 | 36 | if [ "$DO_FEATURE_MATRIX" = true ] 37 | then 38 | # All features 39 | cargo test --features="$FEATURES" 40 | 41 | # Single features 42 | for feature in ${FEATURES} 43 | do 44 | cargo test --features="$feature" 45 | done 46 | 47 | # Run all the examples 48 | cargo build --examples 49 | cargo run --example htlc --features=compiler 50 | cargo run --example parse 51 | cargo run --example sign_multisig 52 | cargo run --example verify_tx > /dev/null 53 | cargo run --example xpub_descriptors 54 | cargo run --example taproot --features=compiler 55 | cargo run --example psbt_sign_finalize --features=base64 56 | fi 57 | 58 | if [ "$DO_NO_STD" = true ] 59 | then 60 | # Build no_std, to make sure that cfg(test) doesn't hide any issues 61 | cargo build --verbose --no-default-features --features="no-std" 62 | 63 | # Test no_std 64 | cargo test --verbose --no-default-features --features="no-std" 65 | 66 | # Build all features 67 | cargo build --verbose --no-default-features --features="no-std $FEATURES" 68 | 69 | # Build specific features 70 | for feature in ${FEATURES} 71 | do 72 | cargo build --verbose --no-default-features --features="no-std $feature" 73 | done 74 | fi 75 | 76 | # Bench if told to, only works with non-stable toolchain (nightly, beta). 77 | if [ "$DO_BENCH" = true ] 78 | then 79 | if [ "$NIGHTLY" = false ]; then 80 | if [ -n "$RUSTUP_TOOLCHAIN" ]; then 81 | echo "RUSTUP_TOOLCHAIN is set to a non-nightly toolchain but DO_BENCH requires a nightly toolchain" 82 | else 83 | echo "DO_BENCH requires a nightly toolchain" 84 | fi 85 | exit 1 86 | fi 87 | RUSTFLAGS='--cfg=bench' cargo bench 88 | fi 89 | 90 | # Build the docs if told to (this only works with the nightly toolchain) 91 | if [ "$DO_DOCS" = true ]; then 92 | RUSTDOCFLAGS="--cfg docsrs" cargo +nightly rustdoc --features="$FEATURES" -- -D rustdoc::broken-intra-doc-links 93 | fi 94 | 95 | exit 0 96 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | on: [push, pull_request] 2 | 3 | name: Continuous integration 4 | 5 | jobs: 6 | Nightly: 7 | name: Nightly - Bench + Docs + Fmt 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout Crate 11 | uses: actions/checkout@v2 12 | - name: Checkout Toolchain 13 | uses: actions-rs/toolchain@v1 14 | with: 15 | profile: minimal 16 | toolchain: nightly 17 | override: true 18 | - name: Running benchmarks 19 | env: 20 | DO_BENCH: true 21 | run: ./contrib/test.sh 22 | - name: Building docs 23 | env: 24 | DO_DOCS: true 25 | run: ./contrib/test.sh 26 | - name: Running formatter 27 | env: 28 | DO_FMT: true 29 | run: ./contrib/test.sh 30 | 31 | Int-tests: 32 | name: Integration tests 33 | runs-on: ubuntu-latest 34 | steps: 35 | - name: Checkout Crate 36 | uses: actions/checkout@v2 37 | - name: Checkout Toolchain 38 | uses: actions-rs/toolchain@v1 39 | with: 40 | profile: minimal 41 | toolchain: stable 42 | override: true 43 | - name: Running integration tests 44 | env: 45 | DO_BITCOIND_TESTS: true 46 | run: ./contrib/test.sh 47 | 48 | Tests: 49 | name: Tests 50 | runs-on: ubuntu-latest 51 | strategy: 52 | matrix: 53 | include: 54 | - rust: stable 55 | - rust: beta 56 | - rust: nightly 57 | - rust: "1.56.1" 58 | steps: 59 | - name: Checkout Crate 60 | uses: actions/checkout@v2 61 | - name: Checkout Toolchain 62 | uses: actions-rs/toolchain@v1 63 | with: 64 | profile: minimal 65 | toolchain: ${{ matrix.rust }} 66 | override: true 67 | - name: Running cargo 68 | env: 69 | DO_FEATURE_MATRIX: true 70 | DO_NO_STD: true 71 | run: ./contrib/test.sh 72 | 73 | Embedded: 74 | runs-on: ubuntu-latest 75 | steps: 76 | - name: Checkout 77 | uses: actions/checkout@v2 78 | - name: Set up QEMU 79 | run: sudo apt update && sudo apt install -y qemu-system-arm gcc-arm-none-eabi 80 | - name: Checkout Toolchain 81 | uses: actions-rs/toolchain@v1 82 | with: 83 | profile: minimal 84 | toolchain: nightly 85 | override: true 86 | components: rust-src 87 | target: thumbv7m-none-eabi 88 | - name: Run 89 | env: 90 | RUSTFLAGS: "-C link-arg=-Tlink.x" 91 | CARGO_TARGET_THUMBV7M_NONE_EABI_RUNNER: "qemu-system-arm -cpu cortex-m3 -machine mps2-an385 -nographic -semihosting-config enable=on,target=native -kernel" 92 | run: cd embedded && cargo run --target thumbv7m-none-eabi --release 93 | -------------------------------------------------------------------------------- /fuzz/generate-files.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -e 4 | 5 | REPO_DIR=$(git rev-parse --show-toplevel) 6 | 7 | # shellcheck source=./fuzz-util.sh 8 | source "$REPO_DIR/fuzz/fuzz-util.sh" 9 | 10 | # 1. Generate fuzz/Cargo.toml 11 | cat > "$REPO_DIR/fuzz/Cargo.toml" <> "$REPO_DIR/fuzz/Cargo.toml" < "$REPO_DIR/.github/workflows/fuzz.yml" <executed_\${{ matrix.fuzz_target }} 81 | - uses: actions/upload-artifact@v2 82 | with: 83 | name: executed_\${{ matrix.fuzz_target }} 84 | path: executed_\${{ matrix.fuzz_target }} 85 | 86 | verify-execution: 87 | if: \${{ !github.event.act }} 88 | needs: fuzz 89 | runs-on: ubuntu-latest 90 | steps: 91 | - uses: actions/checkout@v2 92 | - uses: actions/download-artifact@v2 93 | - name: Display structure of downloaded files 94 | run: ls -R 95 | - run: find executed_* -type f -exec cat {} + | sort > executed 96 | - run: source ./fuzz/fuzz-util.sh && listTargetNames | sort | diff - executed 97 | EOF 98 | 99 | -------------------------------------------------------------------------------- /src/iter/mod.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Abstract Tree Iteration 4 | //! 5 | //! This module provides functionality to treat Miniscript objects abstractly 6 | //! as trees, iterating over them in various orders. The iterators in this 7 | //! module can be used to avoid explicitly recursive algorithms. 8 | //! 9 | 10 | mod tree; 11 | 12 | pub use tree::{ 13 | PostOrderIter, PostOrderIterItem, PreOrderIter, PreOrderIterItem, Tree, TreeLike, 14 | VerbosePreOrderIter, 15 | }; 16 | 17 | use crate::sync::Arc; 18 | use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal}; 19 | 20 | impl<'a, Pk: MiniscriptKey, Ctx: ScriptContext> TreeLike for &'a Miniscript { 21 | fn as_node(&self) -> Tree { 22 | use Terminal::*; 23 | match self.node { 24 | PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..) 25 | | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary, 26 | Alt(ref sub) 27 | | Swap(ref sub) 28 | | Check(ref sub) 29 | | DupIf(ref sub) 30 | | Verify(ref sub) 31 | | NonZero(ref sub) 32 | | ZeroNotEqual(ref sub) => Tree::Unary(sub), 33 | AndV(ref left, ref right) 34 | | AndB(ref left, ref right) 35 | | OrB(ref left, ref right) 36 | | OrD(ref left, ref right) 37 | | OrC(ref left, ref right) 38 | | OrI(ref left, ref right) => Tree::Binary(left, right), 39 | AndOr(ref a, ref b, ref c) => Tree::Nary(Arc::from([a.as_ref(), b, c])), 40 | Thresh(_, ref subs) => Tree::Nary(subs.iter().map(Arc::as_ref).collect()), 41 | } 42 | } 43 | } 44 | 45 | impl TreeLike for Arc> { 46 | fn as_node(&self) -> Tree { 47 | use Terminal::*; 48 | match self.node { 49 | PkK(..) | PkH(..) | RawPkH(..) | After(..) | Older(..) | Sha256(..) | Hash256(..) 50 | | Ripemd160(..) | Hash160(..) | True | False | Multi(..) | MultiA(..) => Tree::Nullary, 51 | Alt(ref sub) 52 | | Swap(ref sub) 53 | | Check(ref sub) 54 | | DupIf(ref sub) 55 | | Verify(ref sub) 56 | | NonZero(ref sub) 57 | | ZeroNotEqual(ref sub) => Tree::Unary(Arc::clone(sub)), 58 | AndV(ref left, ref right) 59 | | AndB(ref left, ref right) 60 | | OrB(ref left, ref right) 61 | | OrD(ref left, ref right) 62 | | OrC(ref left, ref right) 63 | | OrI(ref left, ref right) => Tree::Binary(Arc::clone(left), Arc::clone(right)), 64 | AndOr(ref a, ref b, ref c) => { 65 | Tree::Nary(Arc::from([Arc::clone(a), Arc::clone(b), Arc::clone(c)])) 66 | } 67 | Thresh(_, ref subs) => Tree::Nary(subs.iter().map(Arc::clone).collect()), 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /doc/security_report_2022_04_20.md: -------------------------------------------------------------------------------- 1 | ### Security Advisory on Miniscript MinimalIF bug (`d:` wrapper is not `u` ) 2 | 3 | _ALl of the affected versions have been yanked_. Users should upgrade to `1.1.0`, 4 | `2.1.0`, `3.1.0`, `4.1.0`, `5.2.0`, `6.1.0` or `7.0.0`. 5 | 6 | Andrew Poelstra recently discovered a vulnerability in miniscript spec that could 7 | potentially allow miners to steal coins locked in certain miniscript fragments. We 8 | recommend all users upgrade to the latest miniscript version as soon as possible. 9 | Details of the vulnerability are mentioned towards the end of the post. 10 | 11 | For ease of upgrade, we have released a bug fix release for versions 1.1.0, 2.1.0, 12 | 3.1.0, 4.1.0, 5.2.0, and 6.1.0. All other previous releases have been yanked for 13 | safety. The miniscript website (bitcoin.sipa.be/miniscript) and compiler have 14 | been updated so that they no longer produce the vulnerable scripts. 15 | 16 | ### Details of the vulnerability: 17 | 18 | The wrapper `d` (`OP_DUP OP_IF [X] OP_ENDIF`) was incorrectly marked to have the 19 | `u` property. The `u` property states "When [X] is satisfied, this expression will 20 | put an exact 1 on the stack (as opposed to any nonzero value)". However, this is 21 | only true if we have a `MINIMALIF` consensus rule. Unfortunately, this is only a 22 | policy rule for segwitv0 and p2sh scripts. `MINIMALIF` is a consensus rule since 23 | the taproot upgrade. 24 | 25 | In other words, this vulnerability only affects coins with sh, wsh and shwsh. If 26 | your coins are under taproot descriptor, you are not vulnerable. 27 | 28 | ### How can this be exploited? 29 | 30 | Certain combinations of `d` wrapper inside `or_c` or `or_d` are innocuous. However, 31 | when combined with thresh, this could allow miners to steal coins from the threshold 32 | provided the underlying condition in `d` wrapper is satisfied. 33 | 34 | Consider the script `thresh(2, pk(A), s:pk(B), sdv:older(2) )` . Semantically, this 35 | means that either A and B can spend this before 2 blocks, or after 2 blocks either 36 | A or B can spend these funds. The `thresh` fragment expects all of its children to 37 | result in either 1/0 (not any other number). However, a miner could malleate the 38 | user-provided OP_1 in `d` wrapper to OP_2, setting empty signatures A and B bypassing 39 | the threshold check. 40 | 41 | ### How to check if you are affected? 42 | 43 | If the latest version of miniscript cannot spend your funds, you could be 44 | affected. In particular, if you are using a `thresh` construction like above, 45 | and the timelock has expired, your coins are vulnerable. If the timelock has not 46 | expired, your funds are not at risk if you move them before the timelock activates. 47 | 48 | If you cannot spend funds with the new update, please contact us. We will assist 49 | you with next steps. 50 | 51 | Alekos(@afillini) and Riccardo Casetta scanned the blockchain for all spent outputs since 52 | the miniscript release to check if there were vulnerable. Fortunately, they could 53 | not find any funds that were vulnerable. 54 | 55 | If you have more questions or need any assistance. Feel free to contact us via IRC or email. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Build](https://github.com/rust-bitcoin/rust-miniscript/workflows/Continuous%20integration/badge.svg) 2 | 3 | **Minimum Supported Rust Version:** 1.56.1 4 | 5 | # Miniscript 6 | 7 | Library for handling [Miniscript](http://bitcoin.sipa.be/miniscript/), 8 | which is a subset of Bitcoin Script designed to support simple and general 9 | tooling. Miniscripts represent threshold circuits of spending conditions, 10 | and can therefore be easily visualized or serialized as human-readable 11 | strings. 12 | 13 | ## High-Level Features 14 | 15 | This library supports 16 | 17 | * [Output descriptors](https://github.com/bitcoin/bitcoin/blob/master/doc/descriptors.md) 18 | including embedded Miniscripts 19 | * Parsing and serializing descriptors to a human-readable string format 20 | * Compilation of abstract spending policies to Miniscript (enabled by the 21 | `compiler` flag) 22 | * Semantic analysis of Miniscripts and spending policies, with user-defined 23 | public key types 24 | * Encoding and decoding Miniscript as Bitcoin Script, given key types that 25 | are convertible to `bitcoin::PublicKey` 26 | * Determining satisfiability, and optimal witnesses, for a given descriptor; 27 | completing an unsigned `bitcoin::TxIn` with appropriate data 28 | * Determining the specific keys, hash preimages and timelocks used to spend 29 | coins in a given Bitcoin transaction 30 | * `no_std` support enabled by disabling the `default-features` and enabling 31 | `"no-std"`. See `embedded/` for an example. 32 | 33 | More information can be found in [the documentation](https://docs.rs/miniscript) 34 | or in [the `examples/` directory](https://github.com/rust-bitcoin/rust-miniscript/tree/master/examples) 35 | 36 | ## Building 37 | 38 | The cargo feature `std` is enabled by default. At least one of the features `std` or `no-std` or both must be enabled. 39 | 40 | Enabling the `no-std` feature does not disable `std`. To disable the `std` feature you must disable default features. The `no-std` feature only enables additional features required for this crate to be usable without `std`. Both can be enabled without conflict. 41 | 42 | ## Minimum Supported Rust Version (MSRV) 43 | 44 | This library should always compile with any combination of features on **Rust 1.56.1**. 45 | 46 | Some dependencies do not play nicely with our MSRV, if you are running the tests 47 | you may need to pin some dependencies. See `./contrib/test.sh` for current pinning. 48 | 49 | ## Contributing 50 | 51 | Contributions are generally welcome. If you intend to make larger changes please 52 | discuss them in an issue before PRing them to avoid duplicate work and 53 | architectural mismatches. If you have any questions or ideas you want to discuss 54 | please join us in 55 | [##miniscript](https://web.libera.chat/?channels=##miniscript) on Libera. 56 | 57 | ## Benchmarks 58 | 59 | We use a custom Rust compiler configuration conditional to guard the bench mark code. To run the 60 | bench marks use: `RUSTFLAGS='--cfg=bench' cargo +nightly bench`. 61 | 62 | 63 | ## Release Notes 64 | 65 | See [CHANGELOG.md](CHANGELOG.md). 66 | 67 | 68 | ## Licensing 69 | 70 | The code in this project is licensed under the [Creative Commons CC0 1.0 71 | Universal license](LICENSE). We use the [SPDX license list](https://spdx.org/licenses/) and [SPDX 72 | IDs](https://spdx.dev/ids/). 73 | -------------------------------------------------------------------------------- /examples/htlc.rs: -------------------------------------------------------------------------------- 1 | // Written by Thomas Eizinger 2 | // SPDX-License-Identifier: CC0-1.0 3 | 4 | //! Example: Create an HTLC with miniscript using the policy compiler 5 | 6 | use std::str::FromStr; 7 | 8 | use miniscript::bitcoin::Network; 9 | use miniscript::descriptor::Wsh; 10 | use miniscript::policy::{Concrete, Liftable}; 11 | 12 | fn main() { 13 | // HTLC policy with 10:1 odds for happy (co-operative) case compared to uncooperative case. 14 | let htlc_policy = Concrete::::from_str(&format!("or(10@and(sha256({secret_hash}),pk({redeem_identity})),1@and(older({expiry}),pk({refund_identity})))", 15 | secret_hash = "1111111111111111111111111111111111111111111111111111111111111111", 16 | redeem_identity = "022222222222222222222222222222222222222222222222222222222222222222", 17 | refund_identity = "020202020202020202020202020202020202020202020202020202020202020202", 18 | expiry = "4444" 19 | )).unwrap(); 20 | 21 | let htlc_descriptor = Wsh::new( 22 | htlc_policy 23 | .compile() 24 | .expect("Policy compilation only fails on resource limits or mixed timelocks"), 25 | ) 26 | .expect("Resource limits"); 27 | 28 | // Check whether the descriptor is safe. This checks whether all spend paths are accessible in 29 | // the Bitcoin network. It may be possible that some of the spend paths require more than 100 30 | // elements in Wsh scripts or they contain a combination of timelock and heightlock. 31 | assert!(htlc_descriptor.sanity_check().is_ok()); 32 | assert_eq!( 33 | format!("{}", htlc_descriptor), 34 | "wsh(andor(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111),and_v(v:pkh(020202020202020202020202020202020202020202020202020202020202020202),older(4444))))#lfytrjen" 35 | ); 36 | 37 | // Lift the descriptor into an abstract policy. 38 | assert_eq!( 39 | format!("{}", htlc_descriptor.lift().unwrap()), 40 | "or(and(pk(022222222222222222222222222222222222222222222222222222222222222222),sha256(1111111111111111111111111111111111111111111111111111111111111111)),and(pk(020202020202020202020202020202020202020202020202020202020202020202),older(4444)))" 41 | ); 42 | 43 | // Get the scriptPubkey for this Wsh descriptor. 44 | assert_eq!( 45 | format!("{:x}", htlc_descriptor.script_pubkey()), 46 | "0020d853877af928a8d2a569c9c0ed14bd16f6a80ce9cccaf8a6150fd8f7f8867ae2" 47 | ); 48 | 49 | // Encode the Wsh descriptor into a Bitcoin script. 50 | assert_eq!( 51 | format!("{:x}", htlc_descriptor.inner_script()), 52 | "21022222222222222222222222222222222222222222222222222222222222222222ac6476a91451814f108670aced2d77c1805ddd6634bc9d473188ad025c11b26782012088a82011111111111111111111111111111111111111111111111111111111111111118768" 53 | ); 54 | 55 | // Get the address for this Wsh descriptor. 56 | assert_eq!( 57 | format!("{}", htlc_descriptor.address(Network::Bitcoin)), 58 | "bc1qmpfcw7he9z5d9ftfe8qw699azmm2sr8fen903fs4plv007yx0t3qxfmqv5" 59 | ); 60 | } 61 | -------------------------------------------------------------------------------- /src/primitives/absolute_locktime.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Absolute Locktimes 4 | 5 | use core::{cmp, fmt}; 6 | 7 | use bitcoin::absolute; 8 | 9 | /// Maximum allowed absolute locktime value. 10 | pub const MAX_ABSOLUTE_LOCKTIME: u32 = 0x8000_0000; 11 | 12 | /// Minimum allowed absolute locktime value. 13 | /// 14 | /// In Bitcoin 0 is an allowed value, but in Miniscript it is not, because we 15 | /// (ab)use the locktime value as a boolean in our Script fragments, and avoiding 16 | /// this would reduce efficiency. 17 | pub const MIN_ABSOLUTE_LOCKTIME: u32 = 1; 18 | 19 | /// Error parsing an absolute locktime. 20 | #[derive(Debug, PartialEq)] 21 | pub struct AbsLockTimeError { 22 | value: u32, 23 | } 24 | 25 | impl fmt::Display for AbsLockTimeError { 26 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 27 | if self.value < MIN_ABSOLUTE_LOCKTIME { 28 | f.write_str("absolute locktimes in Miniscript have a minimum value of 1") 29 | } else { 30 | debug_assert!(self.value > MAX_ABSOLUTE_LOCKTIME); 31 | write!( 32 | f, 33 | "absolute locktimes in Miniscript have a maximum value of 0x{:08x}; got 0x{:08x}", 34 | MAX_ABSOLUTE_LOCKTIME, self.value 35 | ) 36 | } 37 | } 38 | } 39 | 40 | #[cfg(feature = "std")] 41 | impl std::error::Error for AbsLockTimeError { 42 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } 43 | } 44 | 45 | /// An absolute locktime that implements `Ord`. 46 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 47 | pub struct AbsLockTime(absolute::LockTime); 48 | 49 | impl AbsLockTime { 50 | /// Constructs an `AbsLockTime` from an nLockTime value or the argument to `CHEKCLOCKTIMEVERIFY`. 51 | pub fn from_consensus(n: u32) -> Result { 52 | if n >= MIN_ABSOLUTE_LOCKTIME && n <= MAX_ABSOLUTE_LOCKTIME { 53 | Ok(AbsLockTime(absolute::LockTime::from_consensus(n))) 54 | } else { 55 | Err(AbsLockTimeError { value: n }) 56 | } 57 | } 58 | 59 | /// Returns the inner `u32` value. This is the value used when creating this `LockTime` 60 | /// i.e., `n OP_CHECKLOCKTIMEVERIFY` or nLockTime. 61 | /// 62 | /// This calls through to `absolute::LockTime::to_consensus_u32()` and the same usage warnings 63 | /// apply. 64 | pub fn to_consensus_u32(self) -> u32 { self.0.to_consensus_u32() } 65 | 66 | /// Whether this is a height-based locktime. 67 | pub fn is_block_height(&self) -> bool { self.0.is_block_height() } 68 | 69 | /// Whether this is a time-based locktime. 70 | pub fn is_block_time(&self) -> bool { self.0.is_block_time() } 71 | } 72 | 73 | impl From for absolute::LockTime { 74 | fn from(lock_time: AbsLockTime) -> absolute::LockTime { lock_time.0 } 75 | } 76 | 77 | impl cmp::PartialOrd for AbsLockTime { 78 | fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } 79 | } 80 | 81 | impl cmp::Ord for AbsLockTime { 82 | fn cmp(&self, other: &Self) -> cmp::Ordering { 83 | let this = self.0.to_consensus_u32(); 84 | let that = other.0.to_consensus_u32(); 85 | this.cmp(&that) 86 | } 87 | } 88 | 89 | impl fmt::Display for AbsLockTime { 90 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } 91 | } 92 | -------------------------------------------------------------------------------- /src/macros.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Macros 4 | //! 5 | //! Macros meant to be used inside the Rust Miniscript library 6 | 7 | /// Allows tests to create a miniscript directly from string as 8 | /// `ms_str!("c:or_i(pk({}),pk({}))", pk1, pk2)` 9 | #[cfg(test)] 10 | macro_rules! ms_str { 11 | ($($arg:tt)*) => (Miniscript::from_str_ext(&format!($($arg)*), &$crate::ExtParams::allow_all()).unwrap()) 12 | } 13 | 14 | /// Allows tests to create a concrete policy directly from string as 15 | /// `policy_str!("wsh(c:or_i(pk({}),pk({})))", pk1, pk2)` 16 | #[cfg(all(feature = "compiler", test))] 17 | macro_rules! policy_str { 18 | ($($arg:tt)*) => ($crate::policy::Concrete::from_str(&format!($($arg)*)).unwrap()) 19 | } 20 | 21 | /// A macro that implements serde serialization and deserialization using the 22 | /// `fmt::Display` and `str::FromStr` traits. 23 | macro_rules! serde_string_impl_pk { 24 | ($name:ident, $expecting:expr $(, $gen:ident; $gen_con:ident)*) => { 25 | #[cfg(feature = "serde")] 26 | impl<'de, Pk $(, $gen)*> $crate::serde::Deserialize<'de> for $name 27 | where 28 | Pk: $crate::FromStrKey, 29 | $($gen : $gen_con,)* 30 | { 31 | fn deserialize(deserializer: D) -> Result<$name, D::Error> 32 | where 33 | D: $crate::serde::de::Deserializer<'de>, 34 | { 35 | use core::fmt::{self, Formatter}; 36 | use core::marker::PhantomData; 37 | use core::str::FromStr; 38 | 39 | #[allow(unused_parens)] 40 | struct Visitor(PhantomData<(Pk $(, $gen)*)>); 41 | impl<'de, Pk $(, $gen)*> $crate::serde::de::Visitor<'de> for Visitor 42 | where 43 | Pk: $crate::FromStrKey, 44 | $($gen: $gen_con,)* 45 | { 46 | type Value = $name; 47 | 48 | fn expecting(&self, formatter: &mut Formatter) -> fmt::Result { 49 | formatter.write_str($expecting) 50 | } 51 | 52 | fn visit_str(self, v: &str) -> Result 53 | where 54 | E: $crate::serde::de::Error, 55 | { 56 | $name::from_str(v).map_err(E::custom) 57 | } 58 | 59 | fn visit_borrowed_str(self, v: &'de str) -> Result 60 | where 61 | E: $crate::serde::de::Error, 62 | { 63 | self.visit_str(v) 64 | } 65 | 66 | fn visit_string(self, v: String) -> Result 67 | where 68 | E: $crate::serde::de::Error, 69 | { 70 | self.visit_str(&v) 71 | } 72 | } 73 | 74 | deserializer.deserialize_str(Visitor(PhantomData)) 75 | } 76 | } 77 | 78 | #[cfg(feature = "serde")] 79 | impl<'de, Pk $(, $gen)*> $crate::serde::Serialize for $name 80 | where 81 | Pk: $crate::MiniscriptKey, 82 | $($gen: $gen_con,)* 83 | { 84 | fn serialize(&self, serializer: S) -> Result 85 | where 86 | S: $crate::serde::Serializer, 87 | { 88 | serializer.collect_str(&self) 89 | } 90 | } 91 | }; 92 | } 93 | -------------------------------------------------------------------------------- /examples/big.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | //! This is not an example and will surely panic if executed, the purpose of this is using the 3 | //! compiled binary with tools like `cargo bloat` that cannot work with libraries. 4 | //! 5 | //! Ideal properties: 6 | //! 7 | //! * Call all the library API surface. 8 | //! * Depend on user input so that functions are not stripped out on the base of static input. 9 | //! * Use results so that calls are not stripped out. 10 | //! 11 | 12 | use std::collections::HashMap; 13 | use std::str::FromStr; 14 | 15 | use bitcoin::{ecdsa, XOnlyPublicKey}; 16 | use miniscript::descriptor::Wsh; 17 | use miniscript::policy::{Concrete, Liftable}; 18 | use miniscript::psbt::PsbtExt; 19 | use miniscript::{ 20 | translate_hash_fail, DefiniteDescriptorKey, Descriptor, DescriptorPublicKey, MiniscriptKey, 21 | TranslatePk, Translator, 22 | }; 23 | use secp256k1::Secp256k1; 24 | fn main() { 25 | let empty = "".to_string(); 26 | let mut args = std::env::args().collect::>(); 27 | let i = args.pop().unwrap_or(empty); 28 | 29 | let d = Descriptor::::from_str(&i).unwrap(); 30 | use_descriptor(d.clone()); 31 | use_descriptor(Descriptor::::from_str(&i).unwrap()); 32 | use_descriptor(Descriptor::::from_str(&i).unwrap()); 33 | use_descriptor(Descriptor::::from_str(&i).unwrap()); 34 | 35 | let a = d 36 | .at_derivation_index(0) 37 | .unwrap() 38 | .address(bitcoin::Network::Bitcoin) 39 | .unwrap(); 40 | println!("{}", a); 41 | 42 | let secp = Secp256k1::new(); 43 | let (d, m) = Descriptor::parse_descriptor(&secp, &i).unwrap(); 44 | use_descriptor(d); 45 | println!("{:?}", m); 46 | 47 | let p = Concrete::::from_str(&i).unwrap(); 48 | let h = Wsh::new(p.compile().unwrap()).unwrap(); 49 | println!("{}", h); 50 | println!("{:?}", h.lift()); 51 | println!("{:?}", h.script_pubkey()); 52 | println!("{:?}", h.address(bitcoin::Network::Bitcoin)); 53 | 54 | let psbt: bitcoin::Psbt = i.parse().unwrap(); 55 | let psbt = psbt.finalize(&secp).unwrap(); 56 | let mut tx = psbt.extract_tx().unwrap(); 57 | println!("{:?}", tx); 58 | 59 | let d = miniscript::Descriptor::::from_str(&i).unwrap(); 60 | let sigs = HashMap::::new(); 61 | d.satisfy(&mut tx.input[0], &sigs).unwrap(); 62 | 63 | let pol = Concrete::::from_str(&i).unwrap(); 64 | let desc = pol.compile_tr(Some("UNSPENDABLE_KEY".to_string())).unwrap(); 65 | println!("{}", desc); 66 | let pk_map = HashMap::new(); 67 | let mut t = StrPkTranslator { pk_map }; 68 | let real_desc = desc.translate_pk(&mut t).unwrap(); 69 | println!("{}", real_desc); 70 | let addr = real_desc.address(bitcoin::Network::Bitcoin).unwrap(); 71 | println!("{}", addr); 72 | } 73 | 74 | fn use_descriptor(d: Descriptor) { 75 | println!("{}", d); 76 | println!("{:?}", d); 77 | println!("{:?}", d.desc_type()); 78 | println!("{:?}", d.sanity_check()); 79 | } 80 | 81 | struct StrPkTranslator { 82 | pk_map: HashMap, 83 | } 84 | 85 | impl Translator for StrPkTranslator { 86 | fn pk(&mut self, pk: &String) -> Result { 87 | self.pk_map.get(pk).copied().ok_or(()) 88 | } 89 | 90 | // We don't need to implement these methods as we are not using them in the policy. 91 | // Fail if we encounter any hash fragments. See also translate_hash_clone! macro. 92 | translate_hash_fail!(String, XOnlyPublicKey, ()); 93 | } 94 | -------------------------------------------------------------------------------- /src/blanket_traits.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Blanket Traits 4 | //! 5 | //! Because of this library's heavy use of generics, we often require complicated 6 | //! trait bounds (especially when it comes to [`FromStr`] and its 7 | //! associated error types). These blanket traits act as aliases, allowing easier 8 | //! descriptions of them. 9 | //! 10 | //! While these traits are not sealed, they have blanket-impls which prevent you 11 | //! from directly implementing them on your own types. The traits will be 12 | //! automatically implemented if you satisfy all the bounds. 13 | //! 14 | 15 | use core::str::FromStr; 16 | use core::{fmt, hash}; 17 | 18 | use crate::MiniscriptKey; 19 | 20 | /// Blanket trait describing a key where all associated types implement `FromStr`, 21 | /// and all `FromStr` errors can be displayed. 22 | pub trait FromStrKey: 23 | MiniscriptKey< 24 | Sha256 = Self::_Sha256, 25 | Hash256 = Self::_Hash256, 26 | Ripemd160 = Self::_Ripemd160, 27 | Hash160 = Self::_Hash160, 28 | > + FromStr 29 | { 30 | /// Dummy type. Do not use. 31 | type _Sha256: FromStr 32 | + Clone 33 | + Eq 34 | + Ord 35 | + fmt::Display 36 | + fmt::Debug 37 | + hash::Hash; 38 | /// Dummy type. Do not use. 39 | type _Sha256FromStrErr: fmt::Debug + fmt::Display; 40 | /// Dummy type. Do not use. 41 | type _Hash256: FromStr 42 | + Clone 43 | + Eq 44 | + Ord 45 | + fmt::Display 46 | + fmt::Debug 47 | + hash::Hash; 48 | /// Dummy type. Do not use. 49 | type _Hash256FromStrErr: fmt::Debug + fmt::Display; 50 | /// Dummy type. Do not use. 51 | type _Ripemd160: FromStr 52 | + Clone 53 | + Eq 54 | + Ord 55 | + fmt::Display 56 | + fmt::Debug 57 | + hash::Hash; 58 | /// Dummy type. Do not use. 59 | type _Ripemd160FromStrErr: fmt::Debug + fmt::Display; 60 | /// Dummy type. Do not use. 61 | type _Hash160: FromStr 62 | + Clone 63 | + Eq 64 | + Ord 65 | + fmt::Display 66 | + fmt::Debug 67 | + hash::Hash; 68 | /// Dummy type. Do not use. 69 | type _Hash160FromStrErr: fmt::Debug + fmt::Display; 70 | /// Dummy type. Do not use. 71 | type _FromStrErr: fmt::Debug + fmt::Display; 72 | } 73 | 74 | impl FromStrKey for T 75 | where 76 | Self: MiniscriptKey + FromStr, 77 | ::Sha256: FromStr, 78 | Self::Hash256: FromStr, 79 | Self::Ripemd160: FromStr, 80 | Self::Hash160: FromStr, 81 | ::Err: fmt::Debug + fmt::Display, 82 | <::Sha256 as FromStr>::Err: fmt::Debug + fmt::Display, 83 | ::Err: fmt::Debug + fmt::Display, 84 | ::Err: fmt::Debug + fmt::Display, 85 | ::Err: fmt::Debug + fmt::Display, 86 | { 87 | type _Sha256 = ::Sha256; 88 | type _Sha256FromStrErr = <::Sha256 as FromStr>::Err; 89 | type _Hash256 = ::Hash256; 90 | type _Hash256FromStrErr = <::Hash256 as FromStr>::Err; 91 | type _Ripemd160 = ::Ripemd160; 92 | type _Ripemd160FromStrErr = <::Ripemd160 as FromStr>::Err; 93 | type _Hash160 = ::Hash160; 94 | type _Hash160FromStrErr = <::Hash160 as FromStr>::Err; 95 | type _FromStrErr = ::Err; 96 | } 97 | -------------------------------------------------------------------------------- /src/primitives/relative_locktime.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Relative Locktimes 4 | 5 | use core::{cmp, convert, fmt}; 6 | 7 | use bitcoin::{relative, Sequence}; 8 | 9 | /// Error parsing an absolute locktime. 10 | #[derive(Debug, PartialEq)] 11 | pub struct RelLockTimeError { 12 | value: u32, 13 | } 14 | 15 | impl fmt::Display for RelLockTimeError { 16 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 17 | if self.value == 0 { 18 | f.write_str("relative locktimes in Miniscript have a minimum value of 1") 19 | } else { 20 | debug_assert!(Sequence::from_consensus(self.value) 21 | .to_relative_lock_time() 22 | .is_none()); 23 | write!(f, "locktime value {} is not a valid BIP68 relative locktime", self.value) 24 | } 25 | } 26 | } 27 | 28 | #[cfg(feature = "std")] 29 | impl std::error::Error for RelLockTimeError { 30 | fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } 31 | } 32 | 33 | /// A relative locktime which implements `Ord`. 34 | #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] 35 | pub struct RelLockTime(Sequence); 36 | 37 | impl RelLockTime { 38 | /// The "0 blocks" constant. 39 | pub const ZERO: Self = RelLockTime(Sequence::ZERO); 40 | 41 | /// Constructs an `RelLockTime` from an nLockTime value or the argument to `CHEKCLOCKTIMEVERIFY`. 42 | pub fn from_consensus(n: u32) -> Result { 43 | convert::TryFrom::try_from(Sequence::from_consensus(n)) 44 | } 45 | 46 | /// Returns the inner `u32` value. This is the value used when creating this `LockTime` 47 | /// i.e., `n OP_CHECKSEQUENCEVERIFY` or `nSequence`. 48 | pub fn to_consensus_u32(self) -> u32 { self.0.to_consensus_u32() } 49 | 50 | /// Takes a 16-bit number of blocks and produces a relative locktime from it. 51 | pub fn from_height(height: u16) -> Self { RelLockTime(Sequence::from_height(height)) } 52 | 53 | /// Takes a 16-bit number of 512-second time intervals and produces a relative locktime from it. 54 | pub fn from_512_second_intervals(time: u16) -> Self { 55 | RelLockTime(Sequence::from_512_second_intervals(time)) 56 | } 57 | 58 | /// Whether this timelock is blockheight-based. 59 | pub fn is_height_locked(&self) -> bool { self.0.is_height_locked() } 60 | 61 | /// Whether this timelock is time-based. 62 | pub fn is_time_locked(&self) -> bool { self.0.is_time_locked() } 63 | } 64 | 65 | impl convert::TryFrom for RelLockTime { 66 | type Error = RelLockTimeError; 67 | fn try_from(seq: Sequence) -> Result { 68 | if seq.is_relative_lock_time() { 69 | Ok(RelLockTime(seq)) 70 | } else { 71 | Err(RelLockTimeError { value: seq.to_consensus_u32() }) 72 | } 73 | } 74 | } 75 | 76 | impl From for Sequence { 77 | fn from(lock_time: RelLockTime) -> Sequence { lock_time.0 } 78 | } 79 | 80 | impl From for relative::LockTime { 81 | fn from(lock_time: RelLockTime) -> relative::LockTime { 82 | lock_time.0.to_relative_lock_time().unwrap() 83 | } 84 | } 85 | 86 | impl cmp::PartialOrd for RelLockTime { 87 | fn partial_cmp(&self, other: &Self) -> Option { Some(self.cmp(other)) } 88 | } 89 | 90 | impl cmp::Ord for RelLockTime { 91 | fn cmp(&self, other: &Self) -> cmp::Ordering { 92 | let this = self.0.to_consensus_u32(); 93 | let that = other.0.to_consensus_u32(); 94 | this.cmp(&that) 95 | } 96 | } 97 | 98 | impl fmt::Display for RelLockTime { 99 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.0, f) } 100 | } 101 | -------------------------------------------------------------------------------- /doc/resource_limitations.md: -------------------------------------------------------------------------------- 1 | TODO: Rust-miniscript behaviour for resource limitations: 2 | 3 | # Safe vs Valid vs Sanity/Analyzable/Liftable 4 | This document refers to bitcoin consensus and standardness rules as of bitcoin core release 0.20. 5 | 6 | One of Miniscript’s goals are to make advanced Script functionality accommodate both machine and human analysis. However such an analysis is not possible in all cases. 7 | 8 | - **Validity**: Validity refers to whether the Miniscript tree constructions follows the grammar rules. For eg: Top level must be `B`, or `thresh` must have all of it's arguments being dissatifyable. 9 | - **Safety**: Whether all satisfactions of Miniscript require a digital signature. 10 | - **Sanity/Analyzable/Liftable**: Even if the given is valid and safe, it does not imply that Miniscript is consensus and standardness complete. That is, there may exist some semantics implied by the lifted miniscript which cannot be realized in bitcoin network rules. This maybe because of three main reasons 11 | - Miniscript may contain an [invalid timelock and heightlock combination](https://medium.com/blockstream/dont-mix-your-timelocks-d9939b665094). 12 | - Resource limitations: Discussed in the next section 13 | - Repeated use of public keys or public key hashes 14 | 15 | This library accepts all miniscripts that are safe and valid and the signing logic will correctly work for all of those scripts. However, analyzing/lifting such miniscripts would fail. The functions `Miniscript::parse` and `Miniscript::from_str` only succeed on sane miniscripts. Use the insane versions(`Miniscript::parse_insane` and `Miniscript::from_str_insane`) for dealing with "insane" miniscripts. The descriptor APIs all work only for sane scripts. 16 | 17 | # Resource Limitations 18 | 19 | Various types of Bitcoin Scripts have different resource limitations, either through consensus or standardness. Some of them affect otherwise valid Miniscripts. 20 | 21 | There are two types of limitations within the resource limitations: 1) Those that depend on the satisfactions and 2) limitations independent of satisfactions. 22 | 23 | ## Limitations independent of satisfactions 24 | 25 | Certain limitations like script size are independent of satisfactions and as such those can script creation time. If there is any script that does not satisfy these 26 | - Scripts over 520 bytes are invalid by consensus (P2SH). 27 | - Scripts over 10000 bytes are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH). 28 | - For bare scripts (ie not P2PKH, P2SH, [p2sh-]P2WPKH, [p2sh-]P2WSH), anything but c:pk(key) (P2PK), c:pk_h(key) (P2PKH), and thresh_m(k,...) up to n=3 is invalid by standardness. 29 | - Scripts over 3600 bytes are invalid by standardness (P2WSH, P2SH-P2WSH). 30 | 31 | rust-miniscript errors on parsing descriptors with these limitations and the compiler would not create these scripts. 32 | 33 | ## Limitations dependent on satisfactions 34 | 35 | Some limitations are dependent on satisfaction path taken by script. It is possible that certain script satisfaction paths are not valid because they exceed the following limits: 36 | 37 | - Script satisfactions where the total number of non-push opcodes plus the number of keys participating in all executed thresh_ms, is above 201, are invalid by consensus (bare, P2SH, P2WSH, P2SH-P2WSH). 38 | - Script satisfactions with a serialized scriptSig over 1650 bytes are invalid by standardness (P2SH). 39 | - Script satisfactions with a witness consisting of over 100 stack elements (excluding the script itself) are invalid by standardness (P2WSH, P2SH-P2WSH). 40 | 41 | rust-miniscript correctly parses these miniscripts, but does not allow lifting/analyzing these scripts if any of the spend paths exceeds the above limits. The satisfier logic does **not** guarantee to find the satisfactions for these scripts. The policy compiler would not create such scripts. 42 | -------------------------------------------------------------------------------- /src/util.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | use core::convert::TryFrom; 4 | 5 | use bitcoin::hashes::Hash; 6 | use bitcoin::script::{self, PushBytes, ScriptBuf}; 7 | use bitcoin::PubkeyHash; 8 | 9 | use crate::miniscript::context; 10 | use crate::miniscript::satisfy::Placeholder; 11 | use crate::prelude::*; 12 | use crate::{MiniscriptKey, ScriptContext, ToPublicKey}; 13 | pub(crate) fn varint_len(n: usize) -> usize { bitcoin::VarInt(n as u64).size() } 14 | 15 | pub(crate) trait ItemSize { 16 | fn size(&self) -> usize; 17 | } 18 | 19 | impl ItemSize for Placeholder { 20 | fn size(&self) -> usize { 21 | match self { 22 | Placeholder::Pubkey(_, size) => *size, 23 | Placeholder::PubkeyHash(_, size) => *size, 24 | Placeholder::EcdsaSigPk(_) | Placeholder::EcdsaSigPkHash(_) => 73, 25 | Placeholder::SchnorrSigPk(_, _, size) | Placeholder::SchnorrSigPkHash(_, _, size) => { 26 | size + 1 27 | } // +1 for the OP_PUSH 28 | Placeholder::HashDissatisfaction 29 | | Placeholder::Sha256Preimage(_) 30 | | Placeholder::Hash256Preimage(_) 31 | | Placeholder::Ripemd160Preimage(_) 32 | | Placeholder::Hash160Preimage(_) => 33, 33 | Placeholder::PushOne => 2, // On legacy this should be 1 ? 34 | Placeholder::PushZero => 1, 35 | Placeholder::TapScript(s) => s.len(), 36 | Placeholder::TapControlBlock(cb) => cb.serialize().len(), 37 | } 38 | } 39 | } 40 | 41 | impl ItemSize for Vec { 42 | fn size(&self) -> usize { self.len() } 43 | } 44 | 45 | // Helper function to calculate witness size 46 | pub(crate) fn witness_size(wit: &[T]) -> usize { 47 | wit.iter().map(T::size).sum::() + varint_len(wit.len()) 48 | } 49 | 50 | pub(crate) fn witness_to_scriptsig(witness: &[Vec]) -> ScriptBuf { 51 | let mut b = script::Builder::new(); 52 | for wit in witness { 53 | if let Ok(n) = script::read_scriptint(wit) { 54 | b = b.push_int(n); 55 | } else { 56 | let push = <&PushBytes>::try_from(wit.as_slice()) 57 | .expect("All pushes in miniscript are <73 bytes"); 58 | b = b.push_slice(push) 59 | } 60 | } 61 | b.into_script() 62 | } 63 | 64 | // trait for pushing key that depend on context 65 | pub(crate) trait MsKeyBuilder { 66 | /// Serialize the key as bytes based on script context. Used when encoding miniscript into bitcoin script 67 | fn push_ms_key(self, key: &Pk) -> Self 68 | where 69 | Pk: ToPublicKey, 70 | Ctx: ScriptContext; 71 | 72 | /// Serialize the key hash as bytes based on script context. Used when encoding miniscript into bitcoin script 73 | fn push_ms_key_hash(self, key: &Pk) -> Self 74 | where 75 | Pk: ToPublicKey, 76 | Ctx: ScriptContext; 77 | } 78 | 79 | impl MsKeyBuilder for script::Builder { 80 | fn push_ms_key(self, key: &Pk) -> Self 81 | where 82 | Pk: ToPublicKey, 83 | Ctx: ScriptContext, 84 | { 85 | match Ctx::sig_type() { 86 | context::SigType::Ecdsa => self.push_key(&key.to_public_key()), 87 | context::SigType::Schnorr => self.push_slice(key.to_x_only_pubkey().serialize()), 88 | } 89 | } 90 | 91 | fn push_ms_key_hash(self, key: &Pk) -> Self 92 | where 93 | Pk: ToPublicKey, 94 | Ctx: ScriptContext, 95 | { 96 | match Ctx::sig_type() { 97 | context::SigType::Ecdsa => self.push_slice(key.to_public_key().pubkey_hash()), 98 | context::SigType::Schnorr => { 99 | self.push_slice(PubkeyHash::hash(&key.to_x_only_pubkey().serialize())) 100 | } 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /doc/compiler.md: -------------------------------------------------------------------------------- 1 | ## Miniscript Compiler 2 | 3 | This library provides a Policy compiler that converts bitcoin Policy to Miniscript which can 4 | be used with "compiler" feature. The compiler offers a simple way to interface with 5 | Miniscript and often the first place beginners start to play with Miniscript. There are 6 | however several limitations and caveats to the compiler. This document tries to explain the compiler 7 | inner working as well as the guarantees it offers (or does not offer). 8 | 9 | ### The Miniscript compiler 10 | 11 | As mentioned, the policy compiler takes in input a policy and converts it to a Miniscript. The 12 | compiler algorithm is essentially a brute force based algorithm with some heuristics to 13 | reduce the search space. For each sub-policy, and for each Miniscript type, satisfaction probability 14 | and dissatisfaction probability, the compiler tries to find a Miniscript that minimizes the spending 15 | weight. The spending weight is computed as 16 | 17 | `spending_weight = spk_cost + witness_cost` 18 | 19 | where `spk_cost` is the cost of the scriptPubKey and `witness_cost` is the expected cost of the witness 20 | with the given satisfaction and dissatisfaction probabilities. 21 | 22 | ### Compiler guarantees/limitations 23 | 24 | If the compiler is able to output a Miniscript 25 | 26 | - the Miniscript it produces is a valid Miniscript. 27 | - It also guarantees that the Miniscript produced will be spendable (assuming available witness) under the standardness rules. 28 | 29 | The compiler does not guarantee that the Miniscript it produces is the most efficient Miniscript. It maybe possible 30 | to re-arrange the policy tree to produce a even more efficient Miniscript. When dealing with large policies, the compiler also does not guarantee that it will be able to produce a Miniscript even if there exists some Miniscript that can be used to spend the policy. The compiler also does not optimize 1 of n ORs or split thresh into respective ANDs and ORs. Overall, the compiler should be seen as doing a good enough job, but not the best possible job. In our experience, it is still almost always better than hand-written (mini)scripts or scripts. As an example, the compiler was able to produce better versions of lightning HTLC scripts than the ones designed by LN experts. See the following issues for more details: https://github.com/rust-bitcoin/rust-miniscript/issues/126 and https://github.com/rust-bitcoin/rust-miniscript/issues/114 31 | 32 | It is possible that higher weight, but lower opcode exists sub-compilation might be best compilation, because the smaller weight sub-policy compilation that we chose exceeds the op-code count. There is also a similar issue with initial stack element count. The compiler does not try to optimize for these cases. If the final compiler output is not a valid Miniscript, it will simply fail and not try sub-optimal compilation that could fit inside these resource limits. 33 | 34 | These problems are addressed to a large extent with taproot descriptors as the resource limitations are either really large or completely removed. 35 | This library also supports a taproot descriptor compiler. The details of taproot compiler are can be found in the [taproot compiler document](./taproot_compiler.pdf). 36 | 37 | ### Non-determinism and stability guarantees of compiler 38 | 39 | The compiler outputs are not stable. They can change from version to version, machine to machine or even execution to execution on the same machine. The rust and C++ versions can produce different outputs even if the policy is the same. There could also be other implementations of compiler optimizing for different resource limits. 40 | However, the compiler will **always** output a valid Miniscript which might not be the same as some previous execution. As a simple example, `and_b(A,B)` could become `and_b(B,A)`. Therefore, it is **not recommended** to use policy as a stable identifier for a Miniscript. You should use the policy compiler once, and then use the Miniscript output as a stable identifier. 41 | -------------------------------------------------------------------------------- /src/pub_macros.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Macros exported by the miniscript crate 4 | //! 5 | 6 | /// Macro for failing translation for other associated types. 7 | /// Handy for testing String -> concrete keys as we don't want to specify these 8 | /// functions repeatedly. 9 | /// 10 | /// This macro is handy when dealing with scripts that are only contain keys. 11 | /// See also [`crate::translate_hash_clone`] 12 | /// ```rust 13 | /// use miniscript::{bitcoin::PublicKey, policy::concrete::Policy, Translator, hash256}; 14 | /// use std::str::FromStr; 15 | /// use miniscript::translate_hash_fail; 16 | /// use std::collections::HashMap; 17 | /// use miniscript::bitcoin::hashes::{sha256, hash160, ripemd160}; 18 | /// let alice_key = "0270cf3c71f65a3d93d285d9149fddeeb638f87a2d4d8cf16c525f71c417439777"; 19 | /// let bob_key = "02f43b15c50a436f5335dbea8a64dd3b4e63e34c3b50c42598acb5f4f336b5d2fb"; 20 | /// let placeholder_policy = Policy::::from_str("and(pk(alice_key),pk(bob_key))").unwrap(); 21 | /// 22 | /// // Information to translator abstract String type keys to concrete bitcoin::PublicKey. 23 | /// // In practice, wallets would map from String key names to BIP32 keys 24 | /// struct StrPkTranslator { 25 | /// pk_map: HashMap 26 | /// } 27 | /// 28 | /// // If we also wanted to provide mapping of other associated types(sha256, older etc), 29 | /// // we would use the general Translator Trait. 30 | /// impl Translator for StrPkTranslator { 31 | /// // Provides the translation public keys P -> Q 32 | /// fn pk(&mut self, pk: &String) -> Result { 33 | /// self.pk_map.get(pk).copied().ok_or(()) // Dummy Err 34 | /// } 35 | /// 36 | /// // Fail for hash types 37 | /// translate_hash_fail!(String, bitcoin::PublicKey, ()); 38 | /// } 39 | /// 40 | /// let mut pk_map = HashMap::new(); 41 | /// pk_map.insert(String::from("alice_key"), bitcoin::PublicKey::from_str(alice_key).unwrap()); 42 | /// pk_map.insert(String::from("bob_key"), bitcoin::PublicKey::from_str(bob_key).unwrap()); 43 | /// let mut t = StrPkTranslator { pk_map: pk_map }; 44 | /// ``` 45 | #[macro_export] 46 | macro_rules! translate_hash_fail { 47 | ($source: ty, $target:ty, $error_ty: ty) => { 48 | fn sha256( 49 | &mut self, 50 | _sha256: &<$source as $crate::MiniscriptKey>::Sha256, 51 | ) -> Result<<$target as $crate::MiniscriptKey>::Sha256, $error_ty> { 52 | panic!("Called sha256 on translate_only_pk") 53 | } 54 | 55 | fn hash256( 56 | &mut self, 57 | _hash256: &<$source as $crate::MiniscriptKey>::Hash256, 58 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash256, $error_ty> { 59 | panic!("Called hash256 on translate_only_pk") 60 | } 61 | 62 | fn hash160( 63 | &mut self, 64 | _hash160: &<$source as $crate::MiniscriptKey>::Hash160, 65 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash160, $error_ty> { 66 | panic!("Called hash160 on translate_only_pk") 67 | } 68 | 69 | fn ripemd160( 70 | &mut self, 71 | _ripemd160: &<$source as $crate::MiniscriptKey>::Ripemd160, 72 | ) -> Result<<$target as $crate::MiniscriptKey>::Ripemd160, $error_ty> { 73 | panic!("Called ripemd160 on translate_only_pk") 74 | } 75 | }; 76 | } 77 | 78 | /// Macro for translation of associated types where the associated type is the same 79 | /// Handy for Derived -> concrete keys where the associated types are the same. 80 | /// 81 | /// Writing the complete translator trait is tedious. This macro is handy when 82 | /// we are not trying the associated types for hash160, ripemd160, hash256 and 83 | /// sha256. 84 | /// 85 | /// See also [`crate::translate_hash_fail`] 86 | #[macro_export] 87 | macro_rules! translate_hash_clone { 88 | ($source: ty, $target:ty, $error_ty: ty) => { 89 | fn sha256( 90 | &mut self, 91 | sha256: &<$source as $crate::MiniscriptKey>::Sha256, 92 | ) -> Result<<$target as $crate::MiniscriptKey>::Sha256, $error_ty> { 93 | Ok((*sha256).into()) 94 | } 95 | 96 | fn hash256( 97 | &mut self, 98 | hash256: &<$source as $crate::MiniscriptKey>::Hash256, 99 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash256, $error_ty> { 100 | Ok((*hash256).into()) 101 | } 102 | 103 | fn hash160( 104 | &mut self, 105 | hash160: &<$source as $crate::MiniscriptKey>::Hash160, 106 | ) -> Result<<$target as $crate::MiniscriptKey>::Hash160, $error_ty> { 107 | Ok((*hash160).into()) 108 | } 109 | 110 | fn ripemd160( 111 | &mut self, 112 | ripemd160: &<$source as $crate::MiniscriptKey>::Ripemd160, 113 | ) -> Result<<$target as $crate::MiniscriptKey>::Ripemd160, $error_ty> { 114 | Ok((*ripemd160).into()) 115 | } 116 | }; 117 | } 118 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/data/random_ms.txt: -------------------------------------------------------------------------------- 1 | and_b(lltvln:after(1231488000),s:pk(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a)) 2 | uuj:and_v(v:multi(2,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a,025601570cb47f238d2b0286db4a990fa0f3ba28d1a319f5e7cf55c2a2444da7cc),after(1231488000)) 3 | or_b(un:multi(2,03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),al:older(16)) 4 | j:and_v(vdv:after(1567547623),older(16)) 5 | t:and_v(vu:hash256(H),v:sha256(H)) 6 | t:andor(multi(3,02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e,03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556,02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),v:older(4194305),v:sha256(H)) 7 | or_d(multi(1,02f9308a019258c31049344f85f89d5229b531c845836f99b08601f113bce036f9),or_b(multi(3,022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01,032fa2104d6b38d11b0230010559879124e42ab8dfeff5ff29dc9cdadd4ecacc3f,03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a),su:after(500000))) 8 | or_d(sha256(H),and_n(un:after(499999999),older(4194305))) 9 | and_v(or_i(v:multi(2,02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5,03774ae7f858a9411e5ef4246b70c65aac5649980be5c17891bbec17895da008cb),v:multi(2,03e60fce93b59e9ec53011aabc21c23e97b2a31369b87a5ae9c44ee89e2a6dec0a,025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc)),sha256(H)) 10 | j:and_b(multi(2,0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798,024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),s:or_i(older(1),older(4252898))) 11 | and_b(older(16),s:or_d(sha256(H),n:after(1567547623))) 12 | j:and_v(v:ripemd160(H),or_d(sha256(H),older(16))) 13 | and_b(hash256(H),a:and_b(hash256(H),a:older(1))) 14 | thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01)) 15 | and_n(sha256(H),t:or_i(v:older(4252898),v:older(16))) 16 | or_d(nd:and_v(v:older(4252898),v:older(4252898)),sha256(H)) 17 | c:and_v(or_c(sha256(H),v:multi(1,02c44d12c7065d812e8acf28d7cbb19f9011ecd9e9fdf281b0e6a3b5e87d22e7db)),pk_k(03acd484e2f0c7f65309ad178a9f559abde09796974c57e714c35f110dfc27ccbe)) 18 | c:and_v(or_c(multi(2,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00,02352bbf4a4cdd12564f93fa332ce333301d9ad40271f8107181340aef25be59d5),v:ripemd160(H)),pk_k(03fff97bd5755eeea420453a14355235d382f6472f8568a18b2f057a1460297556)) 19 | and_v(andor(hash256(H),v:hash256(H),v:older(50000)),after(1231488000)) 20 | andor(hash256(H),j:and_v(v:ripemd160(H),older(4194305)),ripemd160(H)) 21 | or_i(c:and_v(v:after(500000),pk_k(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),sha256(H)) 22 | thresh(2,c:pk_h(025cbdf0646e5db4eaa398f365f2ea7a0e3d419b7e0330e39ce92bddedcac4f9bc),s:sha256(H),a:ripemd160(H)) 23 | and_n(sha256(H),uc:and_v(v:older(16),pk_k(03fe72c435413d33d48ac09c9161ba8b09683215439d62b7940502bda8b202e6ce))) 24 | and_n(c:pk_k(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),and_b(l:older(15),a:older(16))) 25 | c:or_i(and_v(v:older(16),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)),pk_h(026a245bf6dc698504c89a20cfded60853152b695336c28063b61c65cbd269e6b4)) 26 | or_d(c:pk_h(02e493dbf1c10d80f3581e4904930b1404cc6c13900ee0758474fa94abe8c4cd13),andor(c:pk_k(024ce119c96e2fa357200b559b2f7dd5a5f02d5290aff74b03f3e471b273211c97),older(2016),after(1567547623))) 27 | c:andor(ripemd160(H),pk_h(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e),and_v(v:hash256(H),pk_h(03d01115d548e7561b15c38f004d734633687cf4419620095bc5b0f47070afe85a))) 28 | c:andor(u:ripemd160(H),pk_h(03daed4f2be3a8bf278e70132fb0beb7522f570e144bf615c07e996d443dee8729),or_i(pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(0279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798))) 29 | c:or_i(andor(c:pk_h(03d30199d74fb5a22d47b6e054e2f378cedacffcb89904a61d75d0dbd407143e65),pk_h(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01),pk_h(02c6047f9441ed7d6d3045406e95c07cd85c778e4b8cef3ca7abac09b95c709ee5)),pk_k(02d7924d4f7d43ea965a465ae3095ff41131e5946f3c85f79e44adbcf8e27e080e)) 30 | multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00) 31 | multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00) 32 | thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00)) 33 | thresh(2,multi(2,03a0434d9e47f3c86235477c7b1ae6ae5d3442d49b1943c2b752a68e2a47e247c7,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),a:multi(1,036d2b085e9e382ed10b69fc311a03f8641ccfff21574de0927513a49d9a688a00),ac:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01)) 34 | c:pk_k(022f01e5e15cca351daff3843fb70f3c2f0a1bdd05e5af888a67784ef3e10a2a01) -------------------------------------------------------------------------------- /fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzzing 2 | 3 | `miniscript` has a fuzzing harness setup for use with honggfuzz. 4 | 5 | To run the fuzz-tests as in CI -- briefly fuzzing every target -- simply 6 | run 7 | 8 | ./fuzz.sh 9 | 10 | in this directory. 11 | 12 | To build honggfuzz, you must have libunwind on your system, as well as 13 | libopcodes and libbfd from binutils **2.38** on your system. The most 14 | recently-released binutils 2.39 has changed their API in a breaking way. 15 | 16 | On Nix, you can obtain these libraries by running 17 | 18 | nix-shell -p libopcodes_2_38 -p libunwind 19 | 20 | and then run fuzz.sh as above. 21 | 22 | # Fuzzing with weak cryptography 23 | 24 | You may wish to replace the hashing and signing code with broken crypto, 25 | which will be faster and enable the fuzzer to do otherwise impossible 26 | things such as forging signatures or finding preimages to hashes. 27 | 28 | Doing so may result in spurious bug reports since the broken crypto does 29 | not respect the encoding or algebraic invariants upheld by the real crypto. We 30 | would like to improve this but it's a nontrivial problem -- though not 31 | beyond the abilities of a motivated student with a few months of time. 32 | Please let us know if you are interested in taking this on! 33 | 34 | Meanwhile, to use the broken crypto, simply compile (and run the fuzzing 35 | scripts) with 36 | 37 | RUSTFLAGS="--cfg=hashes_fuzz --cfg=secp256k1_fuzz" 38 | 39 | which will replace the hashing library with broken hashes, and the 40 | secp256k1 library with broken cryptography. 41 | 42 | Needless to say, NEVER COMPILE REAL CODE WITH THESE FLAGS because if a 43 | fuzzer can break your crypto, so can anybody. 44 | 45 | # Long-term fuzzing 46 | 47 | To see the full list of targets, the most straightforward way is to run 48 | 49 | source ./fuzz-util.sh 50 | listTargetNames 51 | 52 | To run each of them for an hour, run 53 | 54 | ./cycle.sh 55 | 56 | To run a single fuzztest indefinitely, run 57 | 58 | cargo hfuzz run 59 | 60 | `cycle.sh` uses the `chrt` utility to try to reduce the priority of the 61 | jobs. If you would like to run for longer, the most straightforward way 62 | is to edit `cycle.sh` before starting. To run the fuzz-tests in parallel, 63 | you will need to implement a custom harness. 64 | 65 | # Adding fuzz tests 66 | 67 | All fuzz tests can be found in the `fuzz_target/` directory. Adding a new 68 | one is as simple as copying an existing one and editing the `do_test` 69 | function to do what you want. 70 | 71 | If your test clearly belongs to a specific crate, please put it in that 72 | crate's directory. Otherwise you can put it directly in `fuzz_target/`. 73 | 74 | If you need to add dependencies, edit the file `generate-files.sh` to add 75 | it to the generated `Cargo.toml`. 76 | 77 | Once you've added a fuzztest, regenerate the `Cargo.toml` and CI job by 78 | running 79 | 80 | ./generate-files.sh 81 | 82 | Then to test your fuzztest, run 83 | 84 | ./fuzz.sh 85 | 86 | If it is working, you will see a rapid stream of data for many seconds 87 | (you can hit Ctrl+C to stop it early). If not, you should quickly see 88 | an error. 89 | 90 | # Reproducing Failures 91 | 92 | If a fuzztest fails, it will exit with a summary which looks something like 93 | 94 | ``` 95 | ... 96 | fuzzTarget : hfuzz_target/x86_64-unknown-linux-gnu/release/hashes_sha256 97 | CRASH: 98 | DESCRIPTION: 99 | ORIG_FNAME: 00000000000000000000000000000000.00000000.honggfuzz.cov 100 | FUZZ_FNAME: hfuzz_workspace/hashes_sha256/SIGABRT.PC.7ffff7c8abc7.STACK.18826d9b64.CODE.-6.ADDR.0.INSTR.mov____%eax,%ebp.fuzz 101 | ... 102 | ===================================================================== 103 | fff400610004 104 | ``` 105 | 106 | The final line is a hex-encoded version of the input that caused the crash. You 107 | can test this directly by editing the `duplicate_crash` test to copy/paste the 108 | hex output into the call to `extend_vec_from_hex`. Then run the test with 109 | 110 | cargo test 111 | 112 | Note that if you set your `RUSTFLAGS` while fuzzing (see above) you must make 113 | sure they are set the same way when running `cargo test`. 114 | 115 | If the `duplicate_crash` function is not present, please add it. A template is 116 | as follows: 117 | 118 | ``` 119 | #[cfg(test)] 120 | mod tests { 121 | fn extend_vec_from_hex(hex: &str, out: &mut Vec) { 122 | let mut b = 0; 123 | for (idx, c) in hex.as_bytes().iter().enumerate() { 124 | b <<= 4; 125 | match *c { 126 | b'A'...b'F' => b |= c - b'A' + 10, 127 | b'a'...b'f' => b |= c - b'a' + 10, 128 | b'0'...b'9' => b |= c - b'0', 129 | _ => panic!("Bad hex"), 130 | } 131 | if (idx & 1) == 1 { 132 | out.push(b); 133 | b = 0; 134 | } 135 | } 136 | } 137 | 138 | #[test] 139 | fn duplicate_crash() { 140 | let mut a = Vec::new(); 141 | extend_vec_from_hex("048531e80700ae6400670000af5168", &mut a); 142 | super::do_test(&a); 143 | } 144 | } 145 | ``` 146 | -------------------------------------------------------------------------------- /examples/sign_multisig.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Example: Signing a 2-of-3 multisignature. 4 | 5 | use std::collections::HashMap; 6 | use std::str::FromStr; 7 | 8 | use bitcoin::blockdata::witness::Witness; 9 | use bitcoin::{absolute, ecdsa, transaction, Amount, Sequence}; 10 | 11 | fn main() { 12 | let mut tx = spending_transaction(); 13 | let pks = list_of_three_arbitrary_public_keys(); 14 | let sig = random_signature_from_the_blockchain(); 15 | 16 | // Descriptor for the output being spent. 17 | let s = format!("wsh(multi(2,{},{},{}))", pks[0], pks[1], pks[2],); 18 | let descriptor = miniscript::Descriptor::::from_str(&s).unwrap(); 19 | 20 | // Check weight for witness satisfaction cost ahead of time. 21 | // 106 (serialized witnessScript) 22 | // + 73*2 (signature length + signatures + sighash bytes) + 1 (dummy byte) = 253 23 | assert_eq!(descriptor.max_weight_to_satisfy().unwrap(), 253); 24 | 25 | // Sometimes it is necessary to have additional information to get the 26 | // `bitcoin::PublicKey` from the `MiniscriptKey` which can be supplied by 27 | // the `to_pk_ctx` parameter. For example, when calculating the script 28 | // pubkey of a descriptor with xpubs, the secp context and child information 29 | // maybe required. 30 | 31 | // Observe the script properties, just for fun. 32 | assert_eq!( 33 | format!("{:x}", descriptor.script_pubkey()), 34 | "00200ed49b334a12c37f3df8a2974ad91ff95029215a2b53f78155be737907f06163" 35 | ); 36 | 37 | assert_eq!( 38 | format!( 39 | "{:x}", 40 | descriptor 41 | .explicit_script() 42 | .expect("wsh descriptors have unique inner script") 43 | ), 44 | "52\ 45 | 21020202020202020202020202020202020202020202020202020202020202020202\ 46 | 21020102030405060708010203040506070801020304050607080000000000000000\ 47 | 21030102030405060708010203040506070801020304050607080000000000000000\ 48 | 53ae" 49 | ); 50 | 51 | // Attempt to satisfy at age 0, height 0. 52 | let original_txin = tx.input[0].clone(); 53 | 54 | let mut sigs = HashMap::::new(); 55 | 56 | // Doesn't work with no signatures. 57 | assert!(descriptor.satisfy(&mut tx.input[0], &sigs).is_err()); 58 | assert_eq!(tx.input[0], original_txin); 59 | 60 | // ...or one signature... 61 | sigs.insert(pks[1], sig); 62 | assert!(descriptor.satisfy(&mut tx.input[0], &sigs).is_err()); 63 | assert_eq!(tx.input[0], original_txin); 64 | 65 | // ...but two signatures is ok. 66 | sigs.insert(pks[2], sig); 67 | assert!(descriptor.satisfy(&mut tx.input[0], &sigs).is_ok()); 68 | assert_ne!(tx.input[0], original_txin); 69 | assert_eq!(tx.input[0].witness.len(), 4); // 0, sig, sig, witness script 70 | 71 | // ...and even if we give it a third signature, only two are used. 72 | sigs.insert(pks[0], sig); 73 | assert!(descriptor.satisfy(&mut tx.input[0], &sigs).is_ok()); 74 | assert_ne!(tx.input[0], original_txin); 75 | assert_eq!(tx.input[0].witness.len(), 4); // 0, sig, sig, witness script 76 | } 77 | 78 | // Transaction which spends some output. 79 | fn spending_transaction() -> bitcoin::Transaction { 80 | bitcoin::Transaction { 81 | version: transaction::Version::TWO, 82 | lock_time: absolute::LockTime::ZERO, 83 | input: vec![bitcoin::TxIn { 84 | previous_output: Default::default(), 85 | script_sig: bitcoin::ScriptBuf::new(), 86 | sequence: Sequence::MAX, 87 | witness: Witness::default(), 88 | }], 89 | output: vec![bitcoin::TxOut { 90 | script_pubkey: bitcoin::ScriptBuf::new(), 91 | value: Amount::from_sat(100_000_000), 92 | }], 93 | } 94 | } 95 | 96 | fn list_of_three_arbitrary_public_keys() -> Vec { 97 | #[cfg_attr(feature="cargo-fmt", rustfmt_skip)] 98 | vec![ 99 | bitcoin::PublicKey::from_slice(&[2; 33]).expect("key 1"), 100 | bitcoin::PublicKey::from_slice(&[ 101 | 0x02, 102 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 103 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 104 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 105 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 106 | ]).expect("key 2"), 107 | bitcoin::PublicKey::from_slice(&[ 108 | 0x03, 109 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 110 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 111 | 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 112 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 113 | ]).expect("key 3"), 114 | ] 115 | } 116 | 117 | // Returns a signature copied at random off the blockchain; this is not actually 118 | // a valid signature for this transaction; Miniscript does not verify the validity. 119 | fn random_signature_from_the_blockchain() -> ecdsa::Signature { 120 | ecdsa::Signature { 121 | sig: secp256k1::ecdsa::Signature::from_str( 122 | "3045\ 123 | 0221\ 124 | 00f7c3648c390d87578cd79c8016940aa8e3511c4104cb78daa8fb8e429375efc1\ 125 | 0220\ 126 | 531d75c136272f127a5dc14acc0722301cbddc222262934151f140da345af177", 127 | ) 128 | .unwrap(), 129 | hash_ty: bitcoin::sighash::EcdsaSighashType::All, 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /examples/taproot.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | use std::collections::HashMap; 4 | use std::str::FromStr; 5 | 6 | use miniscript::bitcoin::key::{Keypair, XOnlyPublicKey}; 7 | use miniscript::bitcoin::secp256k1::rand; 8 | use miniscript::bitcoin::{Network, WitnessVersion}; 9 | use miniscript::descriptor::DescriptorType; 10 | use miniscript::policy::Concrete; 11 | use miniscript::{translate_hash_fail, Descriptor, Miniscript, Tap, TranslatePk, Translator}; 12 | 13 | // Refer to https://github.com/sanket1729/adv_btc_workshop/blob/master/workshop.md#creating-a-taproot-descriptor 14 | // for a detailed explanation of the policy and it's compilation 15 | 16 | struct StrPkTranslator { 17 | pk_map: HashMap, 18 | } 19 | 20 | impl Translator for StrPkTranslator { 21 | fn pk(&mut self, pk: &String) -> Result { 22 | self.pk_map.get(pk).copied().ok_or(()) 23 | } 24 | 25 | // We don't need to implement these methods as we are not using them in the policy. 26 | // Fail if we encounter any hash fragments. See also translate_hash_clone! macro. 27 | translate_hash_fail!(String, XOnlyPublicKey, ()); 28 | } 29 | 30 | fn main() { 31 | let pol_str = "or( 32 | 99@thresh(2, 33 | pk(hA), pk(S) 34 | ),1@or( 35 | 99@pk(Ca), 36 | 1@and(pk(In), older(9)) 37 | ) 38 | )" 39 | .replace(&[' ', '\n', '\t'][..], ""); 40 | 41 | let _ms = Miniscript::::from_str("and_v(v:ripemd160(H),pk(A))").unwrap(); 42 | let pol = Concrete::::from_str(&pol_str).unwrap(); 43 | // In case we can't find an internal key for the given policy, we set the internal key to 44 | // a random pubkey as specified by BIP341 (which are *unspendable* by any party :p) 45 | let desc = pol.compile_tr(Some("UNSPENDABLE_KEY".to_string())).unwrap(); 46 | 47 | let expected_desc = 48 | Descriptor::::from_str("tr(Ca,{and_v(v:pk(In),older(9)),multi_a(2,hA,S)})") 49 | .unwrap(); 50 | assert_eq!(desc, expected_desc); 51 | 52 | // Check whether the descriptors are safe. 53 | assert!(desc.sanity_check().is_ok()); 54 | 55 | // Descriptor type and version should match respectively for taproot 56 | let desc_type = desc.desc_type(); 57 | assert_eq!(desc_type, DescriptorType::Tr); 58 | assert_eq!(desc_type.segwit_version().unwrap(), WitnessVersion::V1); 59 | 60 | if let Descriptor::Tr(ref p) = desc { 61 | // Check if internal key is correctly inferred as Ca 62 | // assert_eq!(p.internal_key(), &pubkeys[2]); 63 | assert_eq!(p.internal_key(), "Ca"); 64 | 65 | // Iterate through scripts 66 | let mut iter = p.iter_scripts(); 67 | assert_eq!( 68 | iter.next().unwrap(), 69 | ( 70 | 1u8, 71 | &Miniscript::::from_str("and_v(vc:pk_k(In),older(9))").unwrap() 72 | ) 73 | ); 74 | assert_eq!( 75 | iter.next().unwrap(), 76 | (1u8, &Miniscript::::from_str("multi_a(2,hA,S)").unwrap()) 77 | ); 78 | assert_eq!(iter.next(), None); 79 | } 80 | 81 | let mut pk_map = HashMap::new(); 82 | 83 | // We require secp for generating a random XOnlyPublicKey 84 | let secp = secp256k1::Secp256k1::new(); 85 | let key_pair = Keypair::new(&secp, &mut rand::thread_rng()); 86 | // Random unspendable XOnlyPublicKey provided for compilation to Taproot Descriptor 87 | let (unspendable_pubkey, _parity) = XOnlyPublicKey::from_keypair(&key_pair); 88 | 89 | pk_map.insert("UNSPENDABLE_KEY".to_string(), unspendable_pubkey); 90 | let pubkeys = hardcoded_xonlypubkeys(); 91 | pk_map.insert("hA".to_string(), pubkeys[0]); 92 | pk_map.insert("S".to_string(), pubkeys[1]); 93 | pk_map.insert("Ca".to_string(), pubkeys[2]); 94 | pk_map.insert("In".to_string(), pubkeys[3]); 95 | let mut t = StrPkTranslator { pk_map }; 96 | 97 | let real_desc = desc.translate_pk(&mut t).unwrap(); 98 | 99 | // Max satisfaction weight for compilation, corresponding to the script-path spend 100 | // `multi_a(2,PUBKEY_1,PUBKEY_2) at tap tree depth 1, having: 101 | // 102 | // max_witness_size = varint(control_block_size) + control_block size + 103 | // varint(script_size) + script_size + max_satisfaction_size 104 | // = 1 + 65 + 1 + 70 + 132 = 269 105 | let max_sat_wt = real_desc.max_weight_to_satisfy().unwrap(); 106 | assert_eq!(max_sat_wt, 269); 107 | 108 | // Compute the bitcoin address and check if it matches 109 | let network = Network::Bitcoin; 110 | let addr = real_desc.address(network).unwrap(); 111 | let expected_addr = bitcoin::Address::from_str( 112 | "bc1pcc8ku64slu3wu04a6g376d2s8ck9y5alw5sus4zddvn8xgpdqw2swrghwx", 113 | ) 114 | .unwrap() 115 | .assume_checked(); 116 | assert_eq!(addr, expected_addr); 117 | } 118 | 119 | fn hardcoded_xonlypubkeys() -> Vec { 120 | let serialized_keys: [[u8; 32]; 4] = [ 121 | [ 122 | 22, 37, 41, 4, 57, 254, 191, 38, 14, 184, 200, 133, 111, 226, 145, 183, 245, 112, 100, 123 | 42, 69, 210, 146, 60, 179, 170, 174, 247, 231, 224, 221, 52, 124 | ], 125 | [ 126 | 194, 16, 47, 19, 231, 1, 0, 143, 203, 11, 35, 148, 101, 75, 200, 15, 14, 54, 222, 208, 127 | 31, 205, 191, 215, 80, 69, 214, 126, 10, 124, 107, 154, 128 | ], 129 | [ 130 | 202, 56, 167, 245, 51, 10, 193, 145, 213, 151, 66, 122, 208, 43, 10, 17, 17, 153, 170, 131 | 29, 89, 133, 223, 134, 220, 212, 166, 138, 2, 152, 122, 16, 132 | ], 133 | [ 134 | 50, 23, 194, 4, 213, 55, 42, 210, 67, 101, 23, 3, 195, 228, 31, 70, 127, 79, 21, 188, 135 | 168, 39, 134, 58, 19, 181, 3, 63, 235, 103, 155, 213, 136 | ], 137 | ]; 138 | let mut keys: Vec = vec![]; 139 | for idx in 0..4 { 140 | keys.push(XOnlyPublicKey::from_slice(&serialized_keys[idx][..]).unwrap()); 141 | } 142 | keys 143 | } 144 | -------------------------------------------------------------------------------- /tests/bip-174.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | use bitcoin::consensus::encode::deserialize; 4 | use bitcoin::hashes::hex::FromHex; 5 | use bitcoin::psbt::Psbt; 6 | use miniscript::psbt::PsbtExt; 7 | 8 | fn main() { 9 | // Test vectors from BIP 174 10 | 11 | let mut psbt = Psbt::deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000002202029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01220202dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d7483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01010304010000000104475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae2206029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f10d90c6a4f000000800000008000000080220602dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d710d90c6a4f0000008000000080010000800001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e887220203089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f012202023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e73473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d2010103040100000001042200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903010547522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae2206023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7310d90c6a4f000000800000008003000080220603089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc10d90c6a4f00000080000000800200008000220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); 12 | // println!("{:?}", psbt); 13 | 14 | let expected_finalized_psbt = Psbt::deserialize(&Vec::::from_hex("70736274ff01009a020000000258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd750000000000ffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d0100000000ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f00000000000100bb0200000001aad73931018bd25f84ae400b68848be09db706eac2ac18298babee71ab656f8b0000000048473044022058f6fc7c6a33e1b31548d481c826c015bd30135aad42cd67790dab66d2ad243b02204a1ced2604c6735b6393e5b41691dd78b00f0c5942fb9f751856faa938157dba01feffffff0280f0fa020000000017a9140fb9463421696b82c833af241c78c17ddbde493487d0f20a270100000017a91429ca74f8a08f81999428185c97b5d852e4063f6187650000000107da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752ae0001012000c2eb0b0000000017a914b7f5faf40e3d40a5a459b1db3535f2b72fa921e8870107232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b20289030108da0400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00220203a9a4c37f5996d3aa25dbac6b570af0650394492942460b354753ed9eeca5877110d90c6a4f000000800000008004000080002202027f6399757d2eff55a136ad02c684b1838b6556e5f1b6b34282a94b6b5005109610d90c6a4f00000080000000800500008000").unwrap()).unwrap(); 15 | 16 | // Construct a secp context for transaction signature verification 17 | let secp = bitcoin::secp256k1::Secp256k1::verification_only(); 18 | // Assuming all partial sigs are filled in. 19 | // Construct a generic finalizer 20 | psbt.finalize_mut(&secp).unwrap(); 21 | // println!("{:?}", psbt); 22 | 23 | assert_eq!(psbt, expected_finalized_psbt); 24 | 25 | // Extract the transaction from the psbt 26 | let tx = psbt.extract(&secp).unwrap(); 27 | 28 | let expected: bitcoin::Transaction = deserialize(&Vec::::from_hex("0200000000010258e87a21b56daf0c23be8e7070456c336f7cbaa5c8757924f545887bb2abdd7500000000da00473044022074018ad4180097b873323c0015720b3684cc8123891048e7dbcd9b55ad679c99022073d369b740e3eb53dcefa33823c8070514ca55a7dd9544f157c167913261118c01483045022100f61038b308dc1da865a34852746f015772934208c6d24454393cd99bdf2217770220056e675a675a6d0a02b85b14e5e29074d8a25a9b5760bea2816f661910a006ea01475221029583bf39ae0a609747ad199addd634fa6108559d6c5cd39b4c2183f1ab96e07f2102dab61ff49a14db6a7d02b0cd1fbb78fc4b18312b5b4e54dae4dba2fbfef536d752aeffffffff838d0427d0ec650a68aa46bb0b098aea4422c071b2ca78352a077959d07cea1d01000000232200208c2353173743b595dfb4a07b72ba8e42e3797da74e87fe7d9d7497e3b2028903ffffffff0270aaf00800000000160014d85c2b71d0060b09c9886aeb815e50991dda124d00e1f5050000000016001400aea9a2e5f0f876a588df5546e8742d1d87008f000400473044022062eb7a556107a7c73f45ac4ab5a1dddf6f7075fb1275969a7f383efff784bcb202200c05dbb7470dbf2f08557dd356c7325c1ed30913e996cd3840945db12228da5f01473044022065f45ba5998b59a27ffe1a7bed016af1f1f90d54b3aa8f7450aa5f56a25103bd02207f724703ad1edb96680b284b56d4ffcb88f7fb759eabbe08aa30f29b851383d20147522103089dc10c7ac6db54f91329af617333db388cead0c231f723379d1b99030b02dc21023add904f3d6dcf59ddb906b0dee23529b7ffb9ed50e5e86151926860221f0e7352ae00000000").unwrap()).unwrap(); 29 | // println!("{:?}", tx); 30 | assert_eq!(tx, expected); 31 | } 32 | -------------------------------------------------------------------------------- /examples/psbt_sign_finalize.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | use std::collections::BTreeMap; 4 | use std::str::FromStr; 5 | 6 | use miniscript::bitcoin::consensus::encode::deserialize; 7 | use miniscript::bitcoin::hashes::hex::FromHex; 8 | use miniscript::bitcoin::psbt::{self, Psbt}; 9 | use miniscript::bitcoin::sighash::SighashCache; 10 | //use miniscript::bitcoin::secp256k1; // https://github.com/rust-lang/rust/issues/121684 11 | use miniscript::bitcoin::{ 12 | transaction, Address, Amount, Network, OutPoint, PrivateKey, Script, Sequence, Transaction, 13 | TxIn, TxOut, 14 | }; 15 | use miniscript::psbt::{PsbtExt, PsbtInputExt}; 16 | use miniscript::Descriptor; 17 | 18 | fn main() { 19 | let secp256k1 = secp256k1::Secp256k1::new(); 20 | 21 | let s = "wsh(t:or_c(pk(027a3565454fe1b749bccaef22aff72843a9c3efefd7b16ac54537a0c23f0ec0de),v:thresh(1,pkh(032d672a1a91cc39d154d366cd231983661b0785c7f27bc338447565844f4a6813),a:pkh(03417129311ed34c242c012cd0a3e0b9bca0065f742d0dfb63c78083ea6a02d4d9),a:pkh(025a687659658baeabdfc415164528065be7bcaade19342241941e556557f01e28))))#7hut9ukn"; 22 | let bridge_descriptor = Descriptor::from_str(&s).unwrap(); 23 | //let bridge_descriptor = Descriptor::::from_str(&s).expect("parse descriptor string"); 24 | assert!(bridge_descriptor.sanity_check().is_ok()); 25 | println!("Bridge pubkey script: {}", bridge_descriptor.script_pubkey()); 26 | println!("Bridge address: {}", bridge_descriptor.address(Network::Regtest).unwrap()); 27 | println!( 28 | "Weight for witness satisfaction cost {}", 29 | bridge_descriptor.max_weight_to_satisfy().unwrap() 30 | ); 31 | 32 | let master_private_key_str = "cQhdvB3McbBJdx78VSSumqoHQiSXs75qwLptqwxSQBNBMDxafvaw"; 33 | let _master_private_key = 34 | PrivateKey::from_str(master_private_key_str).expect("Can't create private key"); 35 | println!("Master public key: {}", _master_private_key.public_key(&secp256k1)); 36 | 37 | let backup1_private_key_str = "cWA34TkfWyHa3d4Vb2jNQvsWJGAHdCTNH73Rht7kAz6vQJcassky"; 38 | let backup1_private = 39 | PrivateKey::from_str(backup1_private_key_str).expect("Can't create private key"); 40 | 41 | println!("Backup1 public key: {}", backup1_private.public_key(&secp256k1)); 42 | 43 | let backup2_private_key_str = "cPJFWUKk8sdL7pcDKrmNiWUyqgovimmhaaZ8WwsByDaJ45qLREkh"; 44 | let backup2_private = 45 | PrivateKey::from_str(backup2_private_key_str).expect("Can't create private key"); 46 | 47 | println!("Backup2 public key: {}", backup2_private.public_key(&secp256k1)); 48 | 49 | let backup3_private_key_str = "cT5cH9UVm81W5QAf5KABXb23RKNSMbMzMx85y6R2mF42L94YwKX6"; 50 | let _backup3_private = 51 | PrivateKey::from_str(backup3_private_key_str).expect("Can't create private key"); 52 | 53 | println!("Backup3 public key: {}", _backup3_private.public_key(&secp256k1)); 54 | 55 | let spend_tx = Transaction { 56 | version: transaction::Version::TWO, 57 | lock_time: bitcoin::absolute::LockTime::from_consensus(5000), 58 | input: vec![], 59 | output: vec![], 60 | }; 61 | 62 | // Spend one input and spend one output for simplicity. 63 | let mut psbt = Psbt { 64 | unsigned_tx: spend_tx, 65 | unknown: BTreeMap::new(), 66 | proprietary: BTreeMap::new(), 67 | xpub: BTreeMap::new(), 68 | version: 0, 69 | inputs: vec![], 70 | outputs: vec![], 71 | }; 72 | 73 | let hex_tx = "020000000001018ff27041f3d738f5f84fd5ee62f1c5b36afebfb15f6da0c9d1382ddd0eaaa23c0000000000feffffff02b3884703010000001600142ca3b4e53f17991582d47b15a053b3201891df5200e1f50500000000220020c0ebf552acd2a6f5dee4e067daaef17b3521e283aeaa44a475278617e3d2238a0247304402207b820860a9d425833f729775880b0ed59dd12b64b9a3d1ab677e27e4d6b370700220576003163f8420fe0b9dc8df726cff22cbc191104a2d4ae4f9dfedb087fcec72012103817e1da42a7701df4db94db8576f0e3605f3ab3701608b7e56f92321e4d8999100000000"; 74 | let depo_tx: Transaction = deserialize(&Vec::::from_hex(hex_tx).unwrap()).unwrap(); 75 | 76 | let receiver = Address::from_str("bcrt1qsdks5za4t6sevaph6tz9ddfjzvhkdkxe9tfrcy") 77 | .unwrap() 78 | .assume_checked(); 79 | 80 | let amount = 100000000; 81 | 82 | let (outpoint, witness_utxo) = get_vout(&depo_tx, &bridge_descriptor.script_pubkey()); 83 | 84 | let mut txin = TxIn::default(); 85 | txin.previous_output = outpoint; 86 | 87 | txin.sequence = Sequence::from_height(26); //Sequence::MAX; // 88 | psbt.unsigned_tx.input.push(txin); 89 | 90 | psbt.unsigned_tx.output.push(TxOut { 91 | script_pubkey: receiver.script_pubkey(), 92 | value: Amount::from_sat(amount / 5 - 500), 93 | }); 94 | 95 | psbt.unsigned_tx.output.push(TxOut { 96 | script_pubkey: bridge_descriptor.script_pubkey(), 97 | value: Amount::from_sat(amount * 4 / 5), 98 | }); 99 | 100 | // Generating signatures & witness data 101 | 102 | let mut input = psbt::Input::default(); 103 | input 104 | .update_with_descriptor_unchecked(&bridge_descriptor) 105 | .unwrap(); 106 | 107 | input.witness_utxo = Some(witness_utxo.clone()); 108 | psbt.inputs.push(input); 109 | psbt.outputs.push(psbt::Output::default()); 110 | 111 | let mut sighash_cache = SighashCache::new(&psbt.unsigned_tx); 112 | 113 | let msg = psbt 114 | .sighash_msg(0, &mut sighash_cache, None) 115 | .unwrap() 116 | .to_secp_msg(); 117 | 118 | // Fixme: Take a parameter 119 | let hash_ty = bitcoin::sighash::EcdsaSighashType::All; 120 | 121 | let sk1 = backup1_private.inner; 122 | let sk2 = backup2_private.inner; 123 | 124 | // Finally construct the signature and add to psbt 125 | let sig1 = secp256k1.sign_ecdsa(&msg, &sk1); 126 | let pk1 = backup1_private.public_key(&secp256k1); 127 | assert!(secp256k1.verify_ecdsa(&msg, &sig1, &pk1.inner).is_ok()); 128 | 129 | // Second key just in case 130 | let sig2 = secp256k1.sign_ecdsa(&msg, &sk2); 131 | let pk2 = backup2_private.public_key(&secp256k1); 132 | assert!(secp256k1.verify_ecdsa(&msg, &sig2, &pk2.inner).is_ok()); 133 | 134 | psbt.inputs[0] 135 | .partial_sigs 136 | .insert(pk1, bitcoin::ecdsa::Signature { sig: sig1, hash_ty: hash_ty }); 137 | 138 | println!("{:#?}", psbt); 139 | println!("{}", psbt); 140 | 141 | psbt.finalize_mut(&secp256k1).unwrap(); 142 | println!("{:#?}", psbt); 143 | 144 | let tx = psbt.extract_tx().expect("failed to extract tx"); 145 | println!("{}", bitcoin::consensus::encode::serialize_hex(&tx)); 146 | } 147 | 148 | // Find the Outpoint by spk 149 | fn get_vout(tx: &Transaction, spk: &Script) -> (OutPoint, TxOut) { 150 | for (i, txout) in tx.clone().output.into_iter().enumerate() { 151 | if spk == &txout.script_pubkey { 152 | return (OutPoint::new(tx.txid(), i as u32), txout); 153 | } 154 | } 155 | panic!("Only call get vout on functions which have the expected outpoint"); 156 | } 157 | -------------------------------------------------------------------------------- /src/test_utils.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Generally useful utilities for test scripts 4 | 5 | use std::collections::HashMap; 6 | use std::str::FromStr; 7 | 8 | use bitcoin::hashes::{hash160, ripemd160, sha256}; 9 | use bitcoin::key::XOnlyPublicKey; 10 | #[cfg(not(test))] // https://github.com/rust-lang/rust/issues/121684 11 | use bitcoin::secp256k1; 12 | 13 | use crate::miniscript::context::SigType; 14 | use crate::{hash256, ToPublicKey, Translator}; 15 | 16 | /// Translate from a String MiniscriptKey type to bitcoin::PublicKey 17 | /// If the hashmap is populated, this will lookup for keys in HashMap 18 | /// Otherwise, this will return a translation to a random key 19 | #[derive(Debug, PartialEq, Eq, Clone)] 20 | pub struct StrKeyTranslator { 21 | pub pk_map: HashMap, 22 | pub pkh_map: HashMap, 23 | pub sha256_map: HashMap, 24 | pub ripemd160_map: HashMap, 25 | pub hash160_map: HashMap, 26 | } 27 | 28 | impl Translator for StrKeyTranslator { 29 | fn pk(&mut self, pk: &String) -> Result { 30 | let key = self.pk_map.get(pk).copied().unwrap_or_else(|| { 31 | bitcoin::PublicKey::from_str( 32 | "02c2122e30e73f7fe37986e3f81ded00158e94b7ad472369b83bbdd28a9a198a39", 33 | ) 34 | .unwrap() 35 | }); 36 | Ok(key) 37 | } 38 | 39 | fn sha256(&mut self, _sha256: &String) -> Result { 40 | let hash = sha256::Hash::from_str( 41 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 42 | ) 43 | .unwrap(); 44 | Ok(hash) 45 | } 46 | 47 | fn hash256(&mut self, _hash256: &String) -> Result { 48 | // hard coded value 49 | let hash = hash256::Hash::from_str( 50 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 51 | ) 52 | .unwrap(); 53 | Ok(hash) 54 | } 55 | 56 | fn ripemd160(&mut self, _ripemd160: &String) -> Result { 57 | let hash = ripemd160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 58 | Ok(hash) 59 | } 60 | 61 | fn hash160(&mut self, _hash160: &String) -> Result { 62 | let hash = hash160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 63 | Ok(hash) 64 | } 65 | } 66 | 67 | /// Same as [`StrKeyTranslator`], but for [`bitcoin::XOnlyPublicKey`] 68 | #[derive(Debug, PartialEq, Eq, Clone)] 69 | pub struct StrXOnlyKeyTranslator { 70 | pub pk_map: HashMap, 71 | pub pkh_map: HashMap, 72 | pub sha256_map: HashMap, 73 | pub ripemd160_map: HashMap, 74 | pub hash160_map: HashMap, 75 | } 76 | 77 | impl Translator for StrXOnlyKeyTranslator { 78 | fn pk(&mut self, pk: &String) -> Result { 79 | let key = self.pk_map.get(pk).copied().unwrap_or_else(|| { 80 | XOnlyPublicKey::from_str( 81 | "c2122e30e73f7fe37986e3f81ded00158e94b7ad472369b83bbdd28a9a198a39", 82 | ) 83 | .unwrap() 84 | }); 85 | Ok(key) 86 | } 87 | 88 | fn sha256(&mut self, _sha256: &String) -> Result { 89 | let hash = sha256::Hash::from_str( 90 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 91 | ) 92 | .unwrap(); 93 | Ok(hash) 94 | } 95 | 96 | fn hash256(&mut self, _hash256: &String) -> Result { 97 | let hash = hash256::Hash::from_str( 98 | "4ae81572f06e1b88fd5ced7a1a000945432e83e1551e6f721ee9c00b8cc33260", 99 | ) 100 | .unwrap(); 101 | Ok(hash) 102 | } 103 | 104 | fn ripemd160(&mut self, _ripemd160: &String) -> Result { 105 | let hash = ripemd160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 106 | Ok(hash) 107 | } 108 | 109 | fn hash160(&mut self, _hash160: &String) -> Result { 110 | let hash = hash160::Hash::from_str("4ae81572f06e1b88fd5ced7a1a00094543a0069").unwrap(); 111 | Ok(hash) 112 | } 113 | } 114 | 115 | // Deterministically sample keys to allow reproducible tests 116 | fn random_sks(n: usize) -> Vec { 117 | let mut sk = [0; 32]; 118 | let mut sks = vec![]; 119 | for i in 1..n + 1 { 120 | sk[0] = i as u8; 121 | sk[1] = (i >> 8) as u8; 122 | sk[2] = (i >> 16) as u8; 123 | sk[3] = (i >> 24) as u8; 124 | 125 | let sk = secp256k1::SecretKey::from_slice(&sk[..]).expect("secret key"); 126 | sks.push(sk) 127 | } 128 | sks 129 | } 130 | 131 | impl StrKeyTranslator { 132 | pub fn new() -> Self { 133 | let secp = secp256k1::Secp256k1::new(); 134 | let sks = random_sks(26); 135 | let pks: Vec<_> = sks 136 | .iter() 137 | .map(|sk| bitcoin::PublicKey::new(secp256k1::PublicKey::from_secret_key(&secp, sk))) 138 | .collect(); 139 | let mut pk_map = HashMap::new(); 140 | let mut pkh_map = HashMap::new(); 141 | for (i, c) in (b'A'..=b'Z').enumerate() { 142 | let key = String::from_utf8(vec![c]).unwrap(); 143 | pk_map.insert(key.clone(), pks[i]); 144 | pkh_map.insert(key, pks[i].to_pubkeyhash(SigType::Ecdsa)); 145 | } 146 | // We don't bother filling in sha256_map preimages in default implementation as it not unnecessary 147 | // for sane miniscripts 148 | Self { 149 | pk_map, 150 | pkh_map, 151 | sha256_map: HashMap::new(), 152 | ripemd160_map: HashMap::new(), 153 | hash160_map: HashMap::new(), 154 | } 155 | } 156 | } 157 | 158 | impl StrXOnlyKeyTranslator { 159 | pub fn new() -> Self { 160 | let secp = secp256k1::Secp256k1::new(); 161 | let sks = random_sks(26); 162 | let pks: Vec<_> = sks 163 | .iter() 164 | .map(|sk| { 165 | let keypair = secp256k1::Keypair::from_secret_key(&secp, sk); 166 | let (pk, _parity) = XOnlyPublicKey::from_keypair(&keypair); 167 | pk 168 | }) 169 | .collect(); 170 | let mut pk_map = HashMap::new(); 171 | let mut pkh_map = HashMap::new(); 172 | for (i, c) in (b'A'..=b'Z').enumerate() { 173 | let key = String::from_utf8(vec![c]).unwrap(); 174 | pk_map.insert(key.clone(), pks[i]); 175 | pkh_map.insert(key, pks[i].to_pubkeyhash(SigType::Schnorr)); 176 | } 177 | // We don't bother filling in sha256_map preimages in default implementation as it not unnecessary 178 | // for sane miniscripts 179 | Self { 180 | pk_map, 181 | pkh_map, 182 | sha256_map: HashMap::new(), 183 | ripemd160_map: HashMap::new(), 184 | hash160_map: HashMap::new(), 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Creative Commons Legal Code 2 | 3 | CC0 1.0 Universal 4 | 5 | CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE 6 | LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN 7 | ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS 8 | INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES 9 | REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS 10 | PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM 11 | THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED 12 | HEREUNDER. 13 | 14 | Statement of Purpose 15 | 16 | The laws of most jurisdictions throughout the world automatically confer 17 | exclusive Copyright and Related Rights (defined below) upon the creator 18 | and subsequent owner(s) (each and all, an "owner") of an original work of 19 | authorship and/or a database (each, a "Work"). 20 | 21 | Certain owners wish to permanently relinquish those rights to a Work for 22 | the purpose of contributing to a commons of creative, cultural and 23 | scientific works ("Commons") that the public can reliably and without fear 24 | of later claims of infringement build upon, modify, incorporate in other 25 | works, reuse and redistribute as freely as possible in any form whatsoever 26 | and for any purposes, including without limitation commercial purposes. 27 | These owners may contribute to the Commons to promote the ideal of a free 28 | culture and the further production of creative, cultural and scientific 29 | works, or to gain reputation or greater distribution for their Work in 30 | part through the use and efforts of others. 31 | 32 | For these and/or other purposes and motivations, and without any 33 | expectation of additional consideration or compensation, the person 34 | associating CC0 with a Work (the "Affirmer"), to the extent that he or she 35 | is an owner of Copyright and Related Rights in the Work, voluntarily 36 | elects to apply CC0 to the Work and publicly distribute the Work under its 37 | terms, with knowledge of his or her Copyright and Related Rights in the 38 | Work and the meaning and intended legal effect of CC0 on those rights. 39 | 40 | 1. Copyright and Related Rights. A Work made available under CC0 may be 41 | protected by copyright and related or neighboring rights ("Copyright and 42 | Related Rights"). Copyright and Related Rights include, but are not 43 | limited to, the following: 44 | 45 | i. the right to reproduce, adapt, distribute, perform, display, 46 | communicate, and translate a Work; 47 | ii. moral rights retained by the original author(s) and/or performer(s); 48 | iii. publicity and privacy rights pertaining to a person's image or 49 | likeness depicted in a Work; 50 | iv. rights protecting against unfair competition in regards to a Work, 51 | subject to the limitations in paragraph 4(a), below; 52 | v. rights protecting the extraction, dissemination, use and reuse of data 53 | in a Work; 54 | vi. database rights (such as those arising under Directive 96/9/EC of the 55 | European Parliament and of the Council of 11 March 1996 on the legal 56 | protection of databases, and under any national implementation 57 | thereof, including any amended or successor version of such 58 | directive); and 59 | vii. other similar, equivalent or corresponding rights throughout the 60 | world based on applicable law or treaty, and any national 61 | implementations thereof. 62 | 63 | 2. Waiver. To the greatest extent permitted by, but not in contravention 64 | of, applicable law, Affirmer hereby overtly, fully, permanently, 65 | irrevocably and unconditionally waives, abandons, and surrenders all of 66 | Affirmer's Copyright and Related Rights and associated claims and causes 67 | of action, whether now known or unknown (including existing as well as 68 | future claims and causes of action), in the Work (i) in all territories 69 | worldwide, (ii) for the maximum duration provided by applicable law or 70 | treaty (including future time extensions), (iii) in any current or future 71 | medium and for any number of copies, and (iv) for any purpose whatsoever, 72 | including without limitation commercial, advertising or promotional 73 | purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each 74 | member of the public at large and to the detriment of Affirmer's heirs and 75 | successors, fully intending that such Waiver shall not be subject to 76 | revocation, rescission, cancellation, termination, or any other legal or 77 | equitable action to disrupt the quiet enjoyment of the Work by the public 78 | as contemplated by Affirmer's express Statement of Purpose. 79 | 80 | 3. Public License Fallback. Should any part of the Waiver for any reason 81 | be judged legally invalid or ineffective under applicable law, then the 82 | Waiver shall be preserved to the maximum extent permitted taking into 83 | account Affirmer's express Statement of Purpose. In addition, to the 84 | extent the Waiver is so judged Affirmer hereby grants to each affected 85 | person a royalty-free, non transferable, non sublicensable, non exclusive, 86 | irrevocable and unconditional license to exercise Affirmer's Copyright and 87 | Related Rights in the Work (i) in all territories worldwide, (ii) for the 88 | maximum duration provided by applicable law or treaty (including future 89 | time extensions), (iii) in any current or future medium and for any number 90 | of copies, and (iv) for any purpose whatsoever, including without 91 | limitation commercial, advertising or promotional purposes (the 92 | "License"). The License shall be deemed effective as of the date CC0 was 93 | applied by Affirmer to the Work. Should any part of the License for any 94 | reason be judged legally invalid or ineffective under applicable law, such 95 | partial invalidity or ineffectiveness shall not invalidate the remainder 96 | of the License, and in such case Affirmer hereby affirms that he or she 97 | will not (i) exercise any of his or her remaining Copyright and Related 98 | Rights in the Work or (ii) assert any associated claims and causes of 99 | action with respect to the Work, in either case contrary to Affirmer's 100 | express Statement of Purpose. 101 | 102 | 4. Limitations and Disclaimers. 103 | 104 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 105 | surrendered, licensed or otherwise affected by this document. 106 | b. Affirmer offers the Work as-is and makes no representations or 107 | warranties of any kind concerning the Work, express, implied, 108 | statutory or otherwise, including without limitation warranties of 109 | title, merchantability, fitness for a particular purpose, non 110 | infringement, or the absence of latent or other defects, accuracy, or 111 | the present or absence of errors, whether or not discoverable, all to 112 | the greatest extent permissible under applicable law. 113 | c. Affirmer disclaims responsibility for clearing rights of other persons 114 | that may apply to the Work or any use thereof, including without 115 | limitation any person's Copyright and Related Rights in the Work. 116 | Further, Affirmer disclaims responsibility for obtaining any necessary 117 | consents, permissions or other rights required for any use of the 118 | Work. 119 | d. Affirmer understands and acknowledges that Creative Commons is not a 120 | party to this document and has no duty or obligation with respect to 121 | this CC0 or use of the Work. 122 | 123 | -------------------------------------------------------------------------------- /examples/verify_tx.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Example: Verifying a signed transaction. 4 | 5 | use std::str::FromStr; 6 | 7 | use miniscript::bitcoin::consensus::Decodable; 8 | use miniscript::bitcoin::secp256k1::Secp256k1; 9 | use miniscript::bitcoin::{absolute, sighash, Sequence}; 10 | use miniscript::interpreter::KeySigPair; 11 | 12 | fn main() { 13 | // 14 | // Setup 15 | // 16 | 17 | let tx = hard_coded_transaction(); 18 | let spk_input_1 = hard_coded_script_pubkey(); 19 | 20 | let interpreter = miniscript::Interpreter::from_txdata( 21 | &spk_input_1, 22 | &tx.input[0].script_sig, 23 | &tx.input[0].witness, 24 | Sequence::ZERO, 25 | absolute::LockTime::ZERO, 26 | ) 27 | .unwrap(); 28 | 29 | let desc_string = interpreter.inferred_descriptor_string(); 30 | println!("Descriptor: {}", desc_string); 31 | 32 | // To do sanity checks on the transaction using the interpreter parse the 33 | // descriptor with `from_str`. 34 | let _ = miniscript::Descriptor::::from_str(&desc_string) 35 | .expect("sanity checks to pass"); 36 | // Alternately, use `inferred_descriptor` which does sanity checks for us also. 37 | let _ = interpreter.inferred_descriptor().expect("same as from_str"); 38 | 39 | // Example one 40 | // 41 | // Learn which keys were used, not bothering to verify the signatures 42 | // (trusting that if they're on the blockchain, standardness would've 43 | // required they be either valid or 0-length. 44 | 45 | println!("\n\nExample one:\n"); 46 | 47 | for elem in interpreter.iter_assume_sigs() { 48 | // Don't bother checking signatures. 49 | if let miniscript::interpreter::SatisfiedConstraint::PublicKey { key_sig } = 50 | elem.expect("no evaluation error") 51 | { 52 | let (key, sig) = key_sig 53 | .as_ecdsa() 54 | .expect("expected ecdsa sig, found schnorr sig"); 55 | 56 | println!("Signed with:\n key: {}\n sig: {}", key, sig); 57 | } 58 | } 59 | 60 | // Example two 61 | // 62 | // Verify the signatures to ensure that invalid signatures are not treated 63 | // as having participated in the script 64 | 65 | println!("\n\nExample two:\n"); 66 | let secp = Secp256k1::new(); 67 | 68 | // We can set prevouts to be empty list because this is a legacy transaction 69 | // and this information is not required for sighash computation. 70 | let prevouts = sighash::Prevouts::All::(&[]); 71 | 72 | for elem in interpreter.iter(&secp, &tx, 0, &prevouts) { 73 | if let miniscript::interpreter::SatisfiedConstraint::PublicKey { key_sig } = 74 | elem.expect("no evaluation error") 75 | { 76 | let (key, sig) = key_sig.as_ecdsa().unwrap(); 77 | println!("Signed with:\n key: {}\n sig: {}", key, sig); 78 | } 79 | } 80 | 81 | // Example three 82 | // 83 | // Same, but with the wrong signature hash, to demonstrate what happens 84 | // given an apparently invalid script. 85 | let secp = Secp256k1::new(); 86 | let message = secp256k1::Message::from_digest([0x01; 32]); 87 | 88 | let iter = interpreter.iter_custom(Box::new(|key_sig: &KeySigPair| { 89 | let (pk, ecdsa_sig) = key_sig.as_ecdsa().expect("Ecdsa Sig"); 90 | ecdsa_sig.hash_ty == bitcoin::sighash::EcdsaSighashType::All 91 | && secp 92 | .verify_ecdsa(&message, &ecdsa_sig.sig, &pk.inner) 93 | .is_ok() 94 | })); 95 | 96 | println!("\n\nExample three:\n"); 97 | 98 | for elem in iter { 99 | let error = elem.expect_err("evaluation error"); 100 | println!("Evaluation error: {}", error); 101 | } 102 | } 103 | 104 | /// Returns an arbitrary transaction. 105 | fn hard_coded_transaction() -> bitcoin::Transaction { 106 | // tx `f27eba163c38ad3f34971198687a3f1882b7ec818599ffe469a8440d82261c98` 107 | #[cfg_attr(feature="cargo-fmt", rustfmt_skip)] 108 | let tx_bytes = vec![ 109 | 0x01, 0x00, 0x00, 0x00, 0x02, 0xc5, 0x11, 0x1d, 0xb7, 0x93, 0x50, 0xc1, 110 | 0x70, 0x28, 0x41, 0x39, 0xe8, 0xe3, 0x4e, 0xb0, 0xed, 0xba, 0x64, 0x7b, 111 | 0x6c, 0x88, 0x7e, 0x9f, 0x92, 0x8f, 0xfd, 0x9b, 0x5c, 0x4a, 0x4b, 0x52, 112 | 0xd0, 0x01, 0x00, 0x00, 0x00, 0xda, 0x00, 0x47, 0x30, 0x44, 0x02, 0x20, 113 | 0x1c, 0xcc, 0x1b, 0xe9, 0xaf, 0x73, 0x4a, 0x10, 0x9f, 0x66, 0xfb, 0xed, 114 | 0xeb, 0x77, 0xb7, 0xa1, 0xf4, 0xb3, 0xc5, 0xff, 0x3d, 0x7f, 0x46, 0xf6, 115 | 0xde, 0x50, 0x69, 0xbb, 0x52, 0x7f, 0x26, 0x9d, 0x02, 0x20, 0x75, 0x37, 116 | 0x2f, 0x6b, 0xd7, 0x0c, 0xf6, 0x45, 0x7a, 0xc7, 0x0e, 0x82, 0x6f, 0xc6, 117 | 0xa7, 0x5b, 0xf7, 0xcf, 0x10, 0x8c, 0x92, 0xea, 0xcf, 0xfc, 0xb5, 0xd9, 118 | 0xfd, 0x77, 0x66, 0xa3, 0x58, 0xa9, 0x01, 0x48, 0x30, 0x45, 0x02, 0x21, 119 | 0x00, 0xfe, 0x82, 0x5b, 0xe1, 0xd5, 0xfd, 0x71, 0x67, 0x83, 0xf4, 0x55, 120 | 0xef, 0xe6, 0x6d, 0x61, 0x58, 0xff, 0xf8, 0xc3, 0x2b, 0x93, 0x1c, 0x5f, 121 | 0x3f, 0xf9, 0x8e, 0x06, 0x65, 0xa9, 0xfd, 0x8e, 0x64, 0x02, 0x20, 0x22, 122 | 0x01, 0x0f, 0xdb, 0x53, 0x8d, 0x0f, 0xa6, 0x8b, 0xd7, 0xf5, 0x20, 0x5d, 123 | 0xc1, 0xdf, 0xa6, 0xc4, 0x28, 0x1b, 0x7b, 0xb7, 0x6f, 0xc2, 0x53, 0xf7, 124 | 0x51, 0x4d, 0x83, 0x48, 0x52, 0x5f, 0x0d, 0x01, 0x47, 0x52, 0x21, 0x03, 125 | 0xd0, 0xbf, 0x26, 0x7c, 0x93, 0x78, 0xb3, 0x18, 0xb5, 0x80, 0xc2, 0x10, 126 | 0xa6, 0x78, 0xc4, 0xbb, 0x60, 0xd8, 0x44, 0x8b, 0x52, 0x0d, 0x21, 0x25, 127 | 0xa1, 0xbd, 0x37, 0x2b, 0x23, 0xae, 0xa6, 0x49, 0x21, 0x02, 0x11, 0xa8, 128 | 0x2a, 0xa6, 0x94, 0x63, 0x99, 0x0a, 0x6c, 0xdd, 0x48, 0x36, 0x76, 0x36, 129 | 0x6a, 0x44, 0xac, 0x3c, 0x98, 0xe7, 0x68, 0x54, 0x69, 0x84, 0x0b, 0xf2, 130 | 0x7a, 0x72, 0x4e, 0x40, 0x5a, 0x7e, 0x52, 0xae, 0xfd, 0xff, 0xff, 0xff, 131 | 0xea, 0x51, 0x1f, 0x33, 0x7a, 0xf5, 0x72, 0xbb, 0xad, 0xcd, 0x2e, 0x03, 132 | 0x07, 0x71, 0x62, 0x3a, 0x60, 0xcc, 0x71, 0x82, 0xad, 0x74, 0x53, 0x3e, 133 | 0xa3, 0x2f, 0xc8, 0xaa, 0x47, 0xd2, 0x0e, 0x71, 0x01, 0x00, 0x00, 0x00, 134 | 0xda, 0x00, 0x48, 0x30, 0x45, 0x02, 0x21, 0x00, 0xfa, 0x2b, 0xfb, 0x4d, 135 | 0x49, 0xb7, 0x6d, 0x9f, 0xb4, 0xc6, 0x9c, 0xc7, 0x8c, 0x36, 0xd2, 0x66, 136 | 0x92, 0x40, 0xe4, 0x57, 0x14, 0xc7, 0x19, 0x06, 0x85, 0xf7, 0xe5, 0x13, 137 | 0x94, 0xac, 0x4e, 0x37, 0x02, 0x20, 0x04, 0x95, 0x2c, 0xf7, 0x75, 0x1c, 138 | 0x45, 0x9d, 0x8a, 0x8b, 0x64, 0x76, 0x76, 0xce, 0x86, 0xf3, 0xbd, 0x69, 139 | 0xff, 0x39, 0x17, 0xcb, 0x99, 0x85, 0x14, 0xbd, 0x73, 0xb7, 0xfc, 0x04, 140 | 0xf6, 0x4c, 0x01, 0x47, 0x30, 0x44, 0x02, 0x20, 0x31, 0xae, 0x81, 0x1e, 141 | 0x35, 0x7e, 0x80, 0x00, 0x01, 0xc7, 0x57, 0x27, 0x7a, 0x22, 0x44, 0xa7, 142 | 0x2b, 0xd5, 0x9d, 0x0a, 0x00, 0xbe, 0xde, 0x49, 0x0a, 0x96, 0x12, 0x3e, 143 | 0x54, 0xce, 0x03, 0x4c, 0x02, 0x20, 0x05, 0xa2, 0x9f, 0x14, 0x30, 0x1e, 144 | 0x5e, 0x2f, 0xdc, 0x7c, 0xee, 0x49, 0x43, 0xec, 0x78, 0x78, 0xdf, 0x73, 145 | 0xde, 0x96, 0x27, 0x00, 0xa4, 0xd9, 0x43, 0x6b, 0xce, 0x24, 0xd6, 0xc3, 146 | 0xa3, 0x57, 0x01, 0x47, 0x52, 0x21, 0x03, 0x4e, 0x74, 0xde, 0x0b, 0x84, 147 | 0x3f, 0xaa, 0x60, 0x44, 0x3d, 0xf4, 0x76, 0xf1, 0xf6, 0x14, 0x4a, 0x5b, 148 | 0x0e, 0x76, 0x49, 0x9e, 0x8a, 0x26, 0x71, 0x07, 0x36, 0x5b, 0x32, 0xfa, 149 | 0xd5, 0xd0, 0xfd, 0x21, 0x03, 0xb4, 0xa6, 0x82, 0xc8, 0x6a, 0xd9, 0x06, 150 | 0x38, 0x8f, 0x99, 0x52, 0x76, 0xf0, 0x84, 0x92, 0x72, 0x3a, 0x8c, 0x5f, 151 | 0x32, 0x3c, 0x6a, 0xf6, 0x92, 0x97, 0x17, 0x40, 0x5d, 0x2e, 0x1b, 0x2f, 152 | 0x70, 0x52, 0xae, 0xfd, 0xff, 0xff, 0xff, 0x02, 0xa7, 0x32, 0x75, 0x01, 153 | 0x00, 0x00, 0x00, 0x00, 0x19, 0x76, 0xa9, 0x14, 0xfb, 0xf7, 0x76, 0xff, 154 | 0xeb, 0x3b, 0xb8, 0x89, 0xb2, 0x01, 0xa5, 0x3f, 0x5f, 0xb0, 0x55, 0x4f, 155 | 0x6e, 0x6f, 0xa2, 0x56, 0x88, 0xac, 0x19, 0x88, 0x56, 0x01, 0x00, 0x00, 156 | 0x00, 0x00, 0x17, 0xa9, 0x14, 0xd3, 0xb6, 0x1d, 0x34, 0xf6, 0x33, 0x7c, 157 | 0xd7, 0xc0, 0x28, 0xb7, 0x90, 0xb0, 0xcf, 0x43, 0xe0, 0x27, 0xd9, 0x1d, 158 | 0xe7, 0x87, 0x09, 0x5d, 0x07, 0x00, 159 | ]; 160 | 161 | bitcoin::Transaction::consensus_decode(&mut &tx_bytes[..]).expect("decode transaction") 162 | } 163 | 164 | fn hard_coded_script_pubkey() -> bitcoin::ScriptBuf { 165 | bitcoin::ScriptBuf::from(vec![ 166 | 0xa9, 0x14, 0x92, 0x09, 0xa8, 0xf9, 0x0c, 0x58, 0x4b, 0xb5, 0x97, 0x4d, 0x58, 0x68, 0x72, 167 | 0x49, 0xe5, 0x32, 0xde, 0x59, 0xf4, 0xbc, 0x87, 168 | ]) 169 | } 170 | -------------------------------------------------------------------------------- /bitcoind-tests/tests/test_cpp.rs: -------------------------------------------------------------------------------- 1 | //! # rust-miniscript integration test 2 | //! 3 | //! Read Miniscripts from file and translate into miniscripts 4 | //! which we know how to satisfy 5 | //! 6 | 7 | use std::collections::BTreeMap; 8 | use std::fs::File; 9 | use std::io::{self, BufRead}; 10 | use std::path::Path; 11 | 12 | use bitcoin::hashes::{sha256d, Hash}; 13 | use bitcoin::psbt::Psbt; 14 | use bitcoin::{ 15 | psbt, secp256k1, transaction, Amount, OutPoint, Sequence, Transaction, TxIn, TxOut, Txid, 16 | }; 17 | use bitcoind::bitcoincore_rpc::{json, Client, RpcApi}; 18 | use miniscript::bitcoin::absolute; 19 | use miniscript::psbt::PsbtExt; 20 | use miniscript::{bitcoin, DefiniteDescriptorKey, Descriptor}; 21 | 22 | mod setup; 23 | use setup::test_util::{self, PubData, TestData}; 24 | 25 | // parse ~30 miniscripts from file 26 | pub(crate) fn parse_miniscripts(pubdata: &PubData) -> Vec> { 27 | // File must exist in current path before this produces output 28 | let mut desc_vec = vec![]; 29 | // Consumes the iterator, returns an (Optional) String 30 | for line in read_lines("tests/data/random_ms.txt") { 31 | let ms = test_util::parse_insane_ms(&line.unwrap(), pubdata); 32 | let wsh = Descriptor::new_wsh(ms).unwrap(); 33 | desc_vec.push(wsh.at_derivation_index(0).unwrap()); 34 | } 35 | desc_vec 36 | } 37 | 38 | // The output is wrapped in a Result to allow matching on errors 39 | // Returns an Iterator to the Reader of the lines of the file. 40 | fn read_lines

(filename: P) -> io::Lines> 41 | where 42 | P: AsRef, 43 | { 44 | let file = File::open(filename).expect("File not found"); 45 | io::BufReader::new(file).lines() 46 | } 47 | 48 | /// Quickly create a BTC amount. 49 | fn btc>(btc: F) -> Amount { Amount::from_btc(btc.into()).unwrap() } 50 | 51 | // Find the Outpoint by value. 52 | // Ideally, we should find by scriptPubkey, but this 53 | // works for temp test case 54 | fn get_vout(cl: &Client, txid: Txid, value: Amount) -> (OutPoint, TxOut) { 55 | let tx = cl 56 | .get_transaction(&txid, None) 57 | .unwrap() 58 | .transaction() 59 | .unwrap(); 60 | for (i, txout) in tx.output.into_iter().enumerate() { 61 | if txout.value == value { 62 | return (OutPoint::new(txid, i as u32), txout); 63 | } 64 | } 65 | unreachable!("Only call get vout on functions which have the expected outpoint"); 66 | } 67 | 68 | pub fn test_from_cpp_ms(cl: &Client, testdata: &TestData) { 69 | let secp = secp256k1::Secp256k1::new(); 70 | let desc_vec = parse_miniscripts(&testdata.pubdata); 71 | let sks = &testdata.secretdata.sks; 72 | let pks = &testdata.pubdata.pks; 73 | // Generate some blocks 74 | let blocks = cl 75 | .generate_to_address(500, &cl.get_new_address(None, None).unwrap().assume_checked()) 76 | .unwrap(); 77 | assert_eq!(blocks.len(), 500); 78 | 79 | // Next send some btc to each address corresponding to the miniscript 80 | let mut txids = vec![]; 81 | for wsh in desc_vec.iter() { 82 | let txid = cl 83 | .send_to_address( 84 | &wsh.address(bitcoin::Network::Regtest).unwrap(), 85 | btc(1), 86 | None, 87 | None, 88 | None, 89 | None, 90 | None, 91 | None, 92 | ) 93 | .unwrap(); 94 | txids.push(txid); 95 | } 96 | // Wait for the funds to mature. 97 | let blocks = cl 98 | .generate_to_address(50, &cl.get_new_address(None, None).unwrap().assume_checked()) 99 | .unwrap(); 100 | assert_eq!(blocks.len(), 50); 101 | // Create a PSBT for each transaction. 102 | // Spend one input and spend one output for simplicity. 103 | let mut psbts = vec![]; 104 | for (desc, txid) in desc_vec.iter().zip(txids) { 105 | let mut psbt = Psbt { 106 | unsigned_tx: Transaction { 107 | version: transaction::Version::TWO, 108 | lock_time: absolute::LockTime::from_time(1_603_866_330) 109 | .expect("valid timestamp") 110 | .into(), // 10/28/2020 @ 6:25am (UTC) 111 | input: vec![], 112 | output: vec![], 113 | }, 114 | unknown: BTreeMap::new(), 115 | proprietary: BTreeMap::new(), 116 | xpub: BTreeMap::new(), 117 | version: 0, 118 | inputs: vec![], 119 | outputs: vec![], 120 | }; 121 | // figure out the outpoint from the txid 122 | let (outpoint, witness_utxo) = get_vout(&cl, txid, btc(1.0)); 123 | let mut txin = TxIn::default(); 124 | txin.previous_output = outpoint; 125 | // set the sequence to a non-final number for the locktime transactions to be 126 | // processed correctly. 127 | // We waited 50 blocks, keep 49 for safety 128 | txin.sequence = Sequence::from_height(49); 129 | psbt.unsigned_tx.input.push(txin); 130 | // Get a new script pubkey from the node so that 131 | // the node wallet tracks the receiving transaction 132 | // and we can check it by gettransaction RPC. 133 | let addr = cl 134 | .get_new_address(None, Some(json::AddressType::Bech32)) 135 | .unwrap() 136 | .assume_checked(); 137 | psbt.unsigned_tx.output.push(TxOut { 138 | value: Amount::from_sat(99_999_000), 139 | script_pubkey: addr.script_pubkey(), 140 | }); 141 | let mut input = psbt::Input::default(); 142 | input.witness_utxo = Some(witness_utxo); 143 | input.witness_script = Some(desc.explicit_script().unwrap()); 144 | psbt.inputs.push(input); 145 | psbt.update_input_with_descriptor(0, &desc).unwrap(); 146 | psbt.outputs.push(psbt::Output::default()); 147 | psbts.push(psbt); 148 | } 149 | 150 | let mut spend_txids = vec![]; 151 | // Sign the transactions with all keys 152 | // AKA the signer role of psbt 153 | for i in 0..psbts.len() { 154 | let wsh_derived = desc_vec[i].derived_descriptor(&secp).unwrap(); 155 | let ms = if let Descriptor::Wsh(wsh) = &wsh_derived { 156 | match wsh.as_inner() { 157 | miniscript::descriptor::WshInner::Ms(ms) => ms, 158 | _ => unreachable!(), 159 | } 160 | } else { 161 | unreachable!("Only Wsh descriptors are supported"); 162 | }; 163 | 164 | let sks_reqd: Vec<_> = ms 165 | .iter_pk() 166 | .map(|pk| sks[pks.iter().position(|&x| x == pk).unwrap()]) 167 | .collect(); 168 | // Get the required sighash message 169 | let amt = btc(1); 170 | let mut sighash_cache = bitcoin::sighash::SighashCache::new(&psbts[i].unsigned_tx); 171 | let sighash_ty = bitcoin::sighash::EcdsaSighashType::All; 172 | let sighash = sighash_cache 173 | .p2wsh_signature_hash(0, &ms.encode(), amt, sighash_ty) 174 | .unwrap(); 175 | 176 | // requires both signing and verification because we check the tx 177 | // after we psbt extract it 178 | let msg = secp256k1::Message::from_digest(sighash.to_byte_array()); 179 | 180 | // Finally construct the signature and add to psbt 181 | for sk in sks_reqd { 182 | let sig = secp.sign_ecdsa(&msg, &sk); 183 | let pk = pks[sks.iter().position(|&x| x == sk).unwrap()]; 184 | psbts[i].inputs[0] 185 | .partial_sigs 186 | .insert(pk, bitcoin::ecdsa::Signature { sig, hash_ty: sighash_ty }); 187 | } 188 | // Add the hash preimages to the psbt 189 | psbts[i].inputs[0] 190 | .sha256_preimages 191 | .insert(testdata.pubdata.sha256, testdata.secretdata.sha256_pre.to_vec()); 192 | psbts[i].inputs[0].hash256_preimages.insert( 193 | sha256d::Hash::from_byte_array(testdata.pubdata.hash256.to_byte_array()), 194 | testdata.secretdata.hash256_pre.to_vec(), 195 | ); 196 | println!("{}", ms); 197 | psbts[i].inputs[0] 198 | .hash160_preimages 199 | .insert(testdata.pubdata.hash160, testdata.secretdata.hash160_pre.to_vec()); 200 | psbts[i].inputs[0] 201 | .ripemd160_preimages 202 | .insert(testdata.pubdata.ripemd160, testdata.secretdata.ripemd160_pre.to_vec()); 203 | // Finalize the transaction using psbt 204 | // Let miniscript do it's magic! 205 | if let Err(e) = psbts[i].finalize_mall_mut(&secp) { 206 | // All miniscripts should satisfy 207 | panic!("Could not satisfy: error{} ms:{} at ind:{}", e[0], ms, i); 208 | } else { 209 | let tx = psbts[i].extract(&secp).unwrap(); 210 | 211 | // Send the transactions to bitcoin node for mining. 212 | // Regtest mode has standardness checks 213 | // Check whether the node accepts the transactions 214 | let txid = cl 215 | .send_raw_transaction(&tx) 216 | .expect(&format!("{} send tx failed for ms {}", i, ms)); 217 | spend_txids.push(txid); 218 | } 219 | } 220 | // Finally mine the blocks and await confirmations 221 | let _blocks = cl 222 | .generate_to_address(10, &cl.get_new_address(None, None).unwrap().assume_checked()) 223 | .unwrap(); 224 | // Get the required transactions from the node mined in the blocks. 225 | for txid in spend_txids { 226 | // Check whether the transaction is mined in blocks 227 | // Assert that the confirmations are > 0. 228 | let num_conf = cl.get_transaction(&txid, None).unwrap().info.confirmations; 229 | assert!(num_conf > 0); 230 | } 231 | } 232 | 233 | #[test] 234 | fn test_setup() { setup::setup(); } 235 | 236 | #[test] 237 | fn tests_from_cpp() { 238 | let cl = &setup::setup().client; 239 | let testdata = TestData::new_fixed_data(50); 240 | test_from_cpp_ms(cl, &testdata); 241 | } 242 | -------------------------------------------------------------------------------- /src/descriptor/sortedmulti.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! # Sorted Multi 4 | //! 5 | //! Implementation of sorted multi primitive for descriptors 6 | //! 7 | 8 | use core::fmt; 9 | use core::marker::PhantomData; 10 | use core::str::FromStr; 11 | 12 | use bitcoin::script; 13 | 14 | use crate::miniscript::context::ScriptContext; 15 | use crate::miniscript::decode::Terminal; 16 | use crate::miniscript::limits::MAX_PUBKEYS_PER_MULTISIG; 17 | use crate::miniscript::satisfy::{Placeholder, Satisfaction}; 18 | use crate::plan::AssetProvider; 19 | use crate::prelude::*; 20 | use crate::sync::Arc; 21 | use crate::{ 22 | errstr, expression, policy, script_num_size, Error, ForEachKey, Miniscript, MiniscriptKey, 23 | Satisfier, ToPublicKey, TranslateErr, Translator, 24 | }; 25 | 26 | /// Contents of a "sortedmulti" descriptor 27 | #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 28 | pub struct SortedMultiVec { 29 | /// signatures required 30 | pub k: usize, 31 | /// public keys inside sorted Multi 32 | pub pks: Vec, 33 | /// The current ScriptContext for sortedmulti 34 | pub(crate) phantom: PhantomData, 35 | } 36 | 37 | impl SortedMultiVec { 38 | /// Create a new instance of `SortedMultiVec` given a list of keys and the threshold 39 | /// 40 | /// Internally checks all the applicable size limits and pubkey types limitations according to the current `Ctx`. 41 | pub fn new(k: usize, pks: Vec) -> Result { 42 | // A sortedmulti() is only defined for <= 20 keys (it maps to CHECKMULTISIG) 43 | if pks.len() > MAX_PUBKEYS_PER_MULTISIG { 44 | return Err(Error::BadDescriptor("Too many public keys".to_string())); 45 | } 46 | 47 | // Check the limits before creating a new SortedMultiVec 48 | // For example, under p2sh context the scriptlen can only be 49 | // upto 520 bytes. 50 | let term: Terminal = Terminal::Multi(k, pks.clone()); 51 | let ms = Miniscript::from_ast(term)?; 52 | 53 | // This would check all the consensus rules for p2sh/p2wsh and 54 | // even tapscript in future 55 | Ctx::check_local_validity(&ms)?; 56 | 57 | Ok(Self { k, pks, phantom: PhantomData }) 58 | } 59 | /// Parse an expression tree into a SortedMultiVec 60 | pub fn from_tree(tree: &expression::Tree) -> Result 61 | where 62 | Pk: FromStr, 63 | ::Err: fmt::Display, 64 | { 65 | if tree.args.is_empty() { 66 | return Err(errstr("no arguments given for sortedmulti")); 67 | } 68 | let k = expression::parse_num(tree.args[0].name)?; 69 | if k > (tree.args.len() - 1) as u32 { 70 | return Err(errstr("higher threshold than there were keys in sortedmulti")); 71 | } 72 | let pks: Result, _> = tree.args[1..] 73 | .iter() 74 | .map(|sub| expression::terminal(sub, Pk::from_str)) 75 | .collect(); 76 | 77 | pks.map(|pks| SortedMultiVec::new(k as usize, pks))? 78 | } 79 | 80 | /// This will panic if fpk returns an uncompressed key when 81 | /// converting to a Segwit descriptor. To prevent this panic, ensure 82 | /// fpk returns an error in this case instead. 83 | pub fn translate_pk( 84 | &self, 85 | t: &mut T, 86 | ) -> Result, TranslateErr> 87 | where 88 | T: Translator, 89 | Q: MiniscriptKey, 90 | { 91 | let pks: Result, _> = self.pks.iter().map(|pk| t.pk(pk)).collect(); 92 | let res = SortedMultiVec::new(self.k, pks?).map_err(TranslateErr::OuterError)?; 93 | Ok(res) 94 | } 95 | } 96 | 97 | impl ForEachKey for SortedMultiVec { 98 | fn for_each_key<'a, F: FnMut(&'a Pk) -> bool>(&'a self, pred: F) -> bool { 99 | self.pks.iter().all(pred) 100 | } 101 | } 102 | 103 | impl SortedMultiVec { 104 | /// utility function to sanity a sorted multi vec 105 | pub fn sanity_check(&self) -> Result<(), Error> { 106 | let ms: Miniscript = 107 | Miniscript::from_ast(Terminal::Multi(self.k, self.pks.clone())) 108 | .expect("Must typecheck"); 109 | // '?' for doing From conversion 110 | ms.sanity_check()?; 111 | Ok(()) 112 | } 113 | } 114 | 115 | impl SortedMultiVec { 116 | /// Create Terminal::Multi containing sorted pubkeys 117 | pub fn sorted_node(&self) -> Terminal 118 | where 119 | Pk: ToPublicKey, 120 | { 121 | let mut pks = self.pks.clone(); 122 | // Sort pubkeys lexicographically according to BIP 67 123 | pks.sort_by(|a, b| { 124 | a.to_public_key() 125 | .inner 126 | .serialize() 127 | .partial_cmp(&b.to_public_key().inner.serialize()) 128 | .unwrap() 129 | }); 130 | Terminal::Multi(self.k, pks) 131 | } 132 | 133 | /// Encode as a Bitcoin script 134 | pub fn encode(&self) -> script::ScriptBuf 135 | where 136 | Pk: ToPublicKey, 137 | { 138 | self.sorted_node() 139 | .encode(script::Builder::new()) 140 | .into_script() 141 | } 142 | 143 | /// Attempt to produce a satisfying witness for the 144 | /// witness script represented by the parse tree 145 | pub fn satisfy(&self, satisfier: S) -> Result>, Error> 146 | where 147 | Pk: ToPublicKey, 148 | S: Satisfier, 149 | { 150 | let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck"); 151 | ms.satisfy(satisfier) 152 | } 153 | 154 | /// Attempt to produce a witness template given the assets available 155 | pub fn build_template

(&self, provider: &P) -> Satisfaction> 156 | where 157 | Pk: ToPublicKey, 158 | P: AssetProvider, 159 | { 160 | let ms = Miniscript::from_ast(self.sorted_node()).expect("Multi node typecheck"); 161 | ms.build_template(provider) 162 | } 163 | 164 | /// Size, in bytes of the script-pubkey. If this Miniscript is used outside 165 | /// of segwit (e.g. in a bare or P2SH descriptor), this quantity should be 166 | /// multiplied by 4 to compute the weight. 167 | /// 168 | /// In general, it is not recommended to use this function directly, but 169 | /// to instead call the corresponding function on a `Descriptor`, which 170 | /// will handle the segwit/non-segwit technicalities for you. 171 | pub fn script_size(&self) -> usize { 172 | script_num_size(self.k) 173 | + 1 174 | + script_num_size(self.pks.len()) 175 | + self.pks.iter().map(|pk| Ctx::pk_len(pk)).sum::() 176 | } 177 | 178 | /// Maximum number of witness elements used to satisfy the Miniscript 179 | /// fragment, including the witness script itself. Used to estimate 180 | /// the weight of the `VarInt` that specifies this number in a serialized 181 | /// transaction. 182 | /// 183 | /// This function may panic on malformed `Miniscript` objects which do 184 | /// not correspond to semantically sane Scripts. (Such scripts should be 185 | /// rejected at parse time. Any exceptions are bugs.) 186 | pub fn max_satisfaction_witness_elements(&self) -> usize { 2 + self.k } 187 | 188 | /// Maximum size, in bytes, of a satisfying witness. 189 | /// In general, it is not recommended to use this function directly, but 190 | /// to instead call the corresponding function on a `Descriptor`, which 191 | /// will handle the segwit/non-segwit technicalities for you. 192 | /// 193 | /// All signatures are assumed to be 73 bytes in size, including the 194 | /// length prefix (segwit) or push opcode (pre-segwit) and sighash 195 | /// postfix. 196 | pub fn max_satisfaction_size(&self) -> usize { 1 + 73 * self.k } 197 | } 198 | 199 | impl policy::Liftable for SortedMultiVec { 200 | fn lift(&self) -> Result, Error> { 201 | let ret = policy::semantic::Policy::Threshold( 202 | self.k, 203 | self.pks 204 | .iter() 205 | .map(|k| Arc::new(policy::semantic::Policy::Key(k.clone()))) 206 | .collect(), 207 | ); 208 | Ok(ret) 209 | } 210 | } 211 | 212 | impl fmt::Debug for SortedMultiVec { 213 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(self, f) } 214 | } 215 | 216 | impl fmt::Display for SortedMultiVec { 217 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 218 | write!(f, "sortedmulti({}", self.k)?; 219 | for k in &self.pks { 220 | write!(f, ",{}", k)?; 221 | } 222 | f.write_str(")") 223 | } 224 | } 225 | 226 | #[cfg(test)] 227 | mod tests { 228 | use bitcoin::secp256k1::PublicKey; 229 | 230 | use super::*; 231 | use crate::miniscript::context::Legacy; 232 | 233 | #[test] 234 | fn too_many_pubkeys() { 235 | // Arbitrary pubic key. 236 | let pk = PublicKey::from_str( 237 | "02e6642fd69bd211f93f7f1f36ca51a26a5290eb2dd1b0d8279a87bb0d480c8443", 238 | ) 239 | .unwrap(); 240 | 241 | let over = 1 + MAX_PUBKEYS_PER_MULTISIG; 242 | 243 | let mut pks = Vec::new(); 244 | for _ in 0..over { 245 | pks.push(pk); 246 | } 247 | 248 | let res: Result, Error> = SortedMultiVec::new(0, pks); 249 | let error = res.expect_err("constructor should err"); 250 | 251 | match error { 252 | Error::BadDescriptor(_) => {} // ok 253 | other => panic!("unexpected error: {:?}", other), 254 | } 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 11.0.0 - November 16, 2023 2 | 3 | - Add the planning module [#592](https://github.com/rust-bitcoin/rust-miniscript/pull/592) 4 | - Bump MSRV to 1.48 [#569](https://github.com/rust-bitcoin/rust-miniscript/pull/569) 5 | - Upgrade `rust-bitcoin` to v0.31.0 [#618](https://github.com/rust-bitcoin/rust-miniscript/pull/618) 6 | - Reduce binary bloat by removing generic param from type_check [584](https://github.com/rust-bitcoin/rust-miniscript/pull/584) 7 | - Add height to tap tree [588](https://github.com/rust-bitcoin/rust-miniscript/pull/588) 8 | - Improve `TapTree` API [617](https://github.com/rust-bitcoin/rust-miniscript/pull/617) 9 | - Remove "unstable" feature [482](https://github.com/rust-bitcoin/rust-miniscript/pull/482) 10 | - Remove hashbrown dependency [564](https://github.com/rust-bitcoin/rust-miniscript/pull/564) 11 | - Add method to convert expr_raw_pkh into pkh [557](https://github.com/rust-bitcoin/rust-miniscript/pull/557) 12 | - psbt: Rewrite input replacement to avoid forgetting fields [568](https://github.com/rust-bitcoin/rust-miniscript/pull/568) 13 | 14 | # 10.0.0 - May 24, 2023 15 | 16 | - Works with rust-bitcoin 0.30.0 17 | - Add support for [multi-path descriptors] (https://github.com/rust-bitcoin/rust-miniscript#470) 18 | - Fix bugs in [max_satisfaction_weight](https://github.com/rust-bitcoin/rust-miniscript#476) 19 | - DefiniteDescriptorKey: provide additional methods for converting to a DescriptorPublicKey (https://github.com/rust-bitcoin/rust-miniscript#492) 20 | - Remove `DummyKey` (https://github.com/rust-bitcoin/rust-miniscript#508) 21 | - Update TranslatePk trait to cleanly separate errors during translation itself and script context errors. [PR](https://github.com/rust-bitcoin/rust-miniscript/pull/493/) 22 | - Fixes to improve CI infrastructure with [Nix](https://github.com/rust-bitcoin/rust-miniscript/pull/538/) support and [bitcoind](https://github.com/rust-bitcoin/rust-miniscript/pull/536/) tests. 23 | 24 | # 9.0.0 - November 5, 2022 25 | 26 | - Fixed a bug dealing with dissatisfying pkh inside thresh 27 | - Changed the signature of `Satisfier::lookup_raw_pkh_pk` API. Only custom implementations 28 | of `Satisfier` need to be updated. The psbt APIs are unchanged. 29 | - Fixed a bug related to display of `raw_pk_h`. These descriptors are experimental 30 | and only usable by opting via `ExtParams` while parsing string. 31 | # 8.0.0 - October 20, 2022 32 | 33 | This release contains several significant API overhauls, as well as a bump 34 | of our MSRV from 1.29 to 1.41. Users are encouraged to update their compiler 35 | to 1.41 *before* updating to this version. 36 | 37 | It includes more Taproot support, but users should be aware that Taproot 38 | support for Miniscript is **not** standardized and is subject to change in 39 | the future. See [this gist](https://gist.github.com/sipa/06c5c844df155d4e5044c2c8cac9c05e) 40 | for our thinking regarding this at the time of release. 41 | 42 | - Works with bitcoin crate 0.29 43 | - Correctly [return an error when `SortedMulti` is constructed with too many keys](https://github.com/rust-bitcoin/rust-miniscript/pull/366/) 44 | - Cleanly separate [`experimental/insane miniscripts`](https://github.com/rust-bitcoin/rust-miniscript/pull/461) from sane miniscripts. 45 | - allow disabling the checksum with [`alternate Display`](https://github.com/rust-bitcoin/rust-miniscript/pull/478) 46 | - Correct [`max_satisfaction_size` of `from_multi_a` fragment](https://github.com/rust-bitcoin/rust-miniscript/pull/346/) 47 | - [Add `PsbtInputExt` trait with `update_with_descriptor` method](https://github.com/rust-bitcoin/rust-miniscript/pull/339/) and [`PsbtOutputExt` trait](https://github.com/rust-bitcoin/rust-miniscript/pull/465/) 48 | - Rename [several descriptor types](https://github.com/rust-bitcoin/rust-miniscript/pull/376/) to reduce redundancy 49 | - [**Bump MSRV to 1.41** and edition to 2018](https://github.com/rust-bitcoin/rust-miniscript/pull/365/) 50 | - Rename [`as_public` to `to_public` on some descriptor key types](https://github.com/rust-bitcoin/rust-miniscript/pull/377/) 51 | - Split fully derived `DescriptorPublicKey`s [into their own type](https://github.com/rust-bitcoin/rust-miniscript/pull/345/) [followup](https://github.com/rust-bitcoin/rust-miniscript/pull/448/) 52 | - [Remove the `DescriptorTrait`](https://github.com/rust-bitcoin/rust-miniscript/pull/386/) in favor of the `Descriptor` enum 53 | - Fix signature costing [to account for ECDSA vs Schnorr](https://github.com/rust-bitcoin/rust-miniscript/pull/340/) 54 | - **Add a Taproot-enabled compiler** [v1](https://github.com/rust-bitcoin/rust-miniscript/pull/291/) [v2](https://github.com/rust-bitcoin/rust-miniscript/pull/342/) [v3](https://github.com/rust-bitcoin/rust-miniscript/pull/418/) 55 | - Rename [`stackelem` to `stack_elem`](https://github.com/rust-bitcoin/rust-miniscript/pull/411/) in the interpreter 56 | - Add [`no-std`](https://github.com/rust-bitcoin/rust-miniscript/pull/277) 57 | - Reworked the [`TranslatePk`](https://github.com/rust-bitcoin/rust-miniscript/pull/426) APIs. Add a Translator trait to cleanly allow downstream users without dealing with APIs that accept function pointers. Also provides `translate_assoc_clone` and `translate_assoc_fail` macros for helping in writing code. 58 | - Updated [`MiniscriptKey trait`](https://github.com/rust-bitcoin/rust-miniscript/pull/434),https://github.com/rust-bitcoin/rust-miniscript/pull/439 to accept associated types for Sha256, Hash256, Ripemd160 and 59 | Hash160. This allows users to write abstract miniscripts hashes as "sha256(H)" instead of specifying the entire hash in the string. 60 | that updates the psbt with descriptor bip32 paths. 61 | - Re-name [`as_public`](https://github.com/rust-bitcoin/rust-miniscript/pull/377) APIs -> `to_public` 62 | - Significantly improve the [timelock](https://github.com/rust-bitcoin/rust-miniscript/pull/414) code with new rust-bitcoin APIs. 63 | - rust-miniscript minor implementation detail: `PkH` fragment now has `Pk` generic instead of `Pk::Hash`. This only concerns users 64 | that operate with `MiniscriptKey = bitcoin::PublicKey` or users that use custom implementation of `MiniscriptKey`. Users that use 65 | `DescriptorPublicKey` need not be concerned. See [PR](https://github.com/rust-bitcoin/rust-miniscript/pull/431) for details. 66 | - To elaborate, "pkh(<20-byte-hex>)" is no longer parsed by the `MiniscriptKey = bitcoin::PublicKey`. 67 | This is consistent with the descriptor spec as defined. Parsing from `bitcoin::Script` for pkh<20-byte-hex> is still supported, but the library would not analyze them. These raw descriptors are still in spec discussions. Rust-miniscript will support them once they are completely specified. 68 | 69 | # 7.0.0 - April 20, 2022 70 | 71 | - Fixed miniscript type system bug. This is a security vulnerability and users are strongly encouraged to upgrade. 72 | See this (link)[https://github.com/rust-bitcoin/rust-miniscript/pull/349/commits/db97c39afa4053c2c3917f04392f6e24964b3972] for details. 73 | - Support for `tr` descriptors with miniscript leaves and multi_a fragment 74 | - Changes to MiniscriptKey and ToPublicKey traits for x-only keys support 75 | - Add `PsbtExt` trait for psbt operations 76 | - `Psbt::update_desc` adds information from a descriptor to a psbt. This figures 77 | out the type of the descriptor and adds corresponding redeem script/witness script 78 | and tap tree information 79 | - Add `derived_descriptor` API to Descriptor so that users no longer need to use 80 | `translate` APIs. See examples/`xpub_descriptor` for usage 81 | - Update `DescriptorTrait`: `script_code` and `explicit_script` can now fail because 82 | of taproot descriptors 83 | - Add `PreTaprootDescriptor` and `PreTaprootDescriptorTrait` to support non-failing versions 84 | of `script_code` and `explicit_script` for non taproot descriptors 85 | - Overhaul the interpreter API to provide simpler APIs `iter(prevouts)` and `iter_assume_sig()` 86 | so that it no longer takes a closure input. 87 | - Add interpreter support for taproot transactions. 88 | - Works with rust-bitcoin 0.28.0 89 | # 6.0.1 - Aug 5, 2021 90 | 91 | - The `lift` method on a Miniscript node was fixed. It would previously mix up 92 | the `X` and `Y` argument of an `andor` fragment. 93 | 94 | # 6.0.0 - Jul 29, 2021 95 | 96 | - bump `rust-bitcoin` to 0.27 97 | - several bugfixes 98 | 99 | # 5.0.0 - Jan 14, 2021 100 | 101 | - Remove `PkCtx` from the API 102 | - Move descriptors into their own types, with an enum containing all of them 103 | - Move descriptor functionality into a trait 104 | - Remove `FromStr` bound from `MiniscriptKey`and `MiniscriptKey::Hash` 105 | - Various `DescriptorPublicKey` improvements 106 | - Allow hardened paths in `DescriptorPublicKey`, remove direct `ToPublicKey` implementation 107 | - Change `Option` to `Result` in all APIs 108 | - bump `rust-bitcoin` to 0.26 109 | 110 | # 4.0.0 - Nov 23, 2020 111 | 112 | - Add support for parsing secret keys 113 | - Add sortedmulti descriptor 114 | - Added standardness and other sanity checks 115 | - Cleaned up `Error` type and return values of most of the API 116 | - Overhauled `satisfied_constraints` module into a new `Iterpreter` API 117 | 118 | # 3.0.0 - Oct 13, 2020 119 | 120 | - **Bump MSRV to 1.29** 121 | 122 | # 2.0.0 - Oct 1, 2020 123 | 124 | - Changes to the miniscript type system to detect an invalid 125 | combination of heightlocks and timelocks 126 | - Lift miniscripts can now fail. Earlier it always succeeded and gave 127 | the resulting Semantic Policy 128 | - Compiler will not compile policies that contain at least one 129 | unspendable path 130 | - Added support for Descriptor PublicKeys(xpub) 131 | - Added a generic psbt finalizer and extractor 132 | - Updated Satisfaction API for checking time/height before setting satisfaction 133 | - Added a policy entailment API for more miniscript semantic analysis 134 | 135 | # 1.0.0 - July 6, 2020 136 | 137 | - Added the following aliases to miniscript for ease of operations 138 | - Rename `pk` to `pk_k` 139 | - Rename `thresh_m` to `multi` 140 | - Add alias `pk(K)` = `c:pk_k(K)` 141 | - Add alias `pkh(K)` = `c:pk_h(K)` 142 | - Fixed Miniscript parser bugs when decoding Hashlocks 143 | - Added scriptContext(`Legacy` and `Segwitv0`) to Miniscript. 144 | - Miscellaneous fixes against DoS attacks for heavy nesting. 145 | - Fixed Satisfier bug that caused flipping of arguments for `and_v` and `and_n` and `and_or` 146 | 147 | -------------------------------------------------------------------------------- /src/descriptor/checksum.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Descriptor checksum 4 | //! 5 | //! This module contains a re-implementation of the function used by Bitcoin Core to calculate the 6 | //! checksum of a descriptor. The checksum algorithm is specified in [BIP-380]. 7 | //! 8 | //! [BIP-380]: 9 | 10 | use core::convert::TryFrom; 11 | use core::fmt; 12 | use core::iter::FromIterator; 13 | 14 | use bech32::primitives::checksum::PackedFe32; 15 | use bech32::{Checksum, Fe32}; 16 | 17 | pub use crate::expression::VALID_CHARS; 18 | use crate::prelude::*; 19 | use crate::Error; 20 | 21 | const CHECKSUM_LENGTH: usize = 8; 22 | 23 | /// Compute the checksum of a descriptor. 24 | /// 25 | /// Note that this function does not check if the descriptor string is 26 | /// syntactically correct or not. This only computes the checksum. 27 | pub fn desc_checksum(desc: &str) -> Result { 28 | let mut eng = Engine::new(); 29 | eng.input(desc)?; 30 | Ok(eng.checksum()) 31 | } 32 | 33 | /// Helper function for `FromStr` for various descriptor types. 34 | /// 35 | /// Checks and verifies the checksum if it is present and returns the descriptor 36 | /// string without the checksum. 37 | pub(super) fn verify_checksum(s: &str) -> Result<&str, Error> { 38 | for ch in s.as_bytes() { 39 | if *ch < 20 || *ch > 127 { 40 | return Err(Error::Unprintable(*ch)); 41 | } 42 | } 43 | 44 | let mut parts = s.splitn(2, '#'); 45 | let desc_str = parts.next().unwrap(); 46 | if let Some(checksum_str) = parts.next() { 47 | let expected_sum = desc_checksum(desc_str)?; 48 | if checksum_str != expected_sum { 49 | return Err(Error::BadDescriptor(format!( 50 | "Invalid checksum '{}', expected '{}'", 51 | checksum_str, expected_sum 52 | ))); 53 | } 54 | } 55 | Ok(desc_str) 56 | } 57 | 58 | /// An engine to compute a checksum from a string. 59 | pub struct Engine { 60 | inner: bech32::primitives::checksum::Engine, 61 | cls: u64, 62 | clscount: u64, 63 | } 64 | 65 | impl Default for Engine { 66 | fn default() -> Engine { Engine::new() } 67 | } 68 | 69 | impl Engine { 70 | /// Constructs an engine with no input. 71 | pub fn new() -> Self { 72 | Engine { inner: bech32::primitives::checksum::Engine::new(), cls: 0, clscount: 0 } 73 | } 74 | 75 | /// Inputs some data into the checksum engine. 76 | /// 77 | /// If this function returns an error, the `Engine` will be left in an indeterminate 78 | /// state! It is safe to continue feeding it data but the result will not be meaningful. 79 | pub fn input(&mut self, s: &str) -> Result<(), Error> { 80 | for ch in s.chars() { 81 | let pos = VALID_CHARS 82 | .get(ch as usize) 83 | .ok_or_else(|| { 84 | Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch)) 85 | })? 86 | .ok_or_else(|| { 87 | Error::BadDescriptor(format!("Invalid character in checksum: '{}'", ch)) 88 | })? as u64; 89 | 90 | let fe = Fe32::try_from(pos & 31).expect("pos is valid because of the mask"); 91 | self.inner.input_fe(fe); 92 | 93 | self.cls = self.cls * 3 + (pos >> 5); 94 | self.clscount += 1; 95 | if self.clscount == 3 { 96 | let fe = Fe32::try_from(self.cls).expect("cls is valid"); 97 | self.inner.input_fe(fe); 98 | self.cls = 0; 99 | self.clscount = 0; 100 | } 101 | } 102 | Ok(()) 103 | } 104 | 105 | /// Obtains the checksum characters of all the data thus-far fed to the 106 | /// engine without allocating, to get a string use [`Self::checksum`]. 107 | pub fn checksum_chars(&mut self) -> [char; CHECKSUM_LENGTH] { 108 | if self.clscount > 0 { 109 | let fe = Fe32::try_from(self.cls).expect("cls is valid"); 110 | self.inner.input_fe(fe); 111 | } 112 | self.inner.input_target_residue(); 113 | 114 | let mut chars = [0 as char; CHECKSUM_LENGTH]; 115 | let mut checksum_remaining = CHECKSUM_LENGTH; 116 | 117 | for checksum_ch in &mut chars { 118 | checksum_remaining -= 1; 119 | let unpacked = self.inner.residue().unpack(checksum_remaining); 120 | let fe = Fe32::try_from(unpacked).expect("5 bits fits in an fe32"); 121 | *checksum_ch = fe.to_char(); 122 | } 123 | chars 124 | } 125 | 126 | /// Obtains the checksum of all the data thus-far fed to the engine. 127 | pub fn checksum(&mut self) -> String { 128 | String::from_iter(self.checksum_chars().iter().copied()) 129 | } 130 | } 131 | 132 | /// The Output Script Descriptor checksum algorithm, defined in [BIP-380]. 133 | /// 134 | /// [BIP-380]: 135 | #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] 136 | enum DescriptorChecksum {} 137 | 138 | /// Generator coefficients, taken from BIP-380. 139 | #[rustfmt::skip] 140 | const GEN: [u64; 5] = [0xf5dee51989, 0xa9fdca3312, 0x1bab10e32d, 0x3706b1677a, 0x644d626ffd]; 141 | 142 | impl Checksum for DescriptorChecksum { 143 | type MidstateRepr = u64; // We need 40 bits (8 * 5). 144 | const CHECKSUM_LENGTH: usize = CHECKSUM_LENGTH; 145 | const GENERATOR_SH: [u64; 5] = GEN; 146 | const TARGET_RESIDUE: u64 = 1; 147 | } 148 | 149 | /// A wrapper around a `fmt::Formatter` which provides checksumming ability. 150 | pub struct Formatter<'f, 'a> { 151 | fmt: &'f mut fmt::Formatter<'a>, 152 | eng: Engine, 153 | } 154 | 155 | impl<'f, 'a> Formatter<'f, 'a> { 156 | /// Contructs a new `Formatter`, wrapping a given `fmt::Formatter`. 157 | pub fn new(f: &'f mut fmt::Formatter<'a>) -> Self { Formatter { fmt: f, eng: Engine::new() } } 158 | 159 | /// Writes the checksum into the underlying `fmt::Formatter`. 160 | pub fn write_checksum(&mut self) -> fmt::Result { 161 | use fmt::Write; 162 | self.fmt.write_char('#')?; 163 | for ch in self.eng.checksum_chars().iter().copied() { 164 | self.fmt.write_char(ch)?; 165 | } 166 | Ok(()) 167 | } 168 | 169 | /// Writes the checksum into the underlying `fmt::Formatter`, unless it has "alternate" display on. 170 | pub fn write_checksum_if_not_alt(&mut self) -> fmt::Result { 171 | if !self.fmt.alternate() { 172 | self.write_checksum()?; 173 | } 174 | Ok(()) 175 | } 176 | } 177 | 178 | impl<'f, 'a> fmt::Write for Formatter<'f, 'a> { 179 | fn write_str(&mut self, s: &str) -> fmt::Result { 180 | self.fmt.write_str(s)?; 181 | self.eng.input(s).map_err(|_| fmt::Error) 182 | } 183 | } 184 | 185 | #[cfg(test)] 186 | mod test { 187 | use core::str; 188 | 189 | use super::*; 190 | 191 | macro_rules! check_expected { 192 | ($desc: expr, $checksum: expr) => { 193 | assert_eq!(desc_checksum($desc).unwrap(), $checksum); 194 | }; 195 | } 196 | 197 | #[test] 198 | fn test_valid_descriptor_checksum() { 199 | check_expected!( 200 | "wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcLNfjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", 201 | "tqz0nc62" 202 | ); 203 | check_expected!( 204 | "pkh(tpubD6NzVbkrYhZ4XHndKkuB8FifXm8r5FQHwrN6oZuWCz13qb93rtgKvD4PQsqC4HP4yhV3tA2fqr2RbY5mNXfM7RxXUoeABoDtsFUq2zJq6YK/44'/1'/0'/0/*)", 205 | "lasegmfs" 206 | ); 207 | 208 | // https://github.com/bitcoin/bitcoin/blob/7ae86b3c6845873ca96650fc69beb4ae5285c801/src/test/descriptor_tests.cpp#L352-L354 209 | check_expected!( 210 | "sh(multi(2,[00000000/111'/222]xprvA1RpRA33e1JQ7ifknakTFpgNXPmW2YvmhqLQYMmrj4xJXXWYpDPS3xz7iAxn8L39njGVyuoseXzU6rcxFLJ8HFsTjSyQbLYnMpCqE2VbFWc,xprv9uPDJpEQgRQfDcW7BkF7eTya6RPxXeJCqCJGHuCJ4GiRVLzkTXBAJMu2qaMWPrS7AANYqdq6vcBcBUdJCVVFceUvJFjaPdGZ2y9WACViL4L/0))", 211 | "ggrsrxfy" 212 | ); 213 | check_expected!( 214 | "sh(multi(2,[00000000/111'/222]xpub6ERApfZwUNrhLCkDtcHTcxd75RbzS1ed54G1LkBUHQVHQKqhMkhgbmJbZRkrgZw4koxb5JaHWkY4ALHY2grBGRjaDMzQLcgJvLJuZZvRcEL,xpub68NZiKmJWnxxS6aaHmn81bvJeTESw724CRDs6HbuccFQN9Ku14VQrADWgqbhhTHBaohPX4CjNLf9fq9MYo6oDaPPLPxSb7gwQN3ih19Zm4Y/0))", 215 | "tjg09x5t" 216 | ); 217 | } 218 | 219 | #[test] 220 | fn test_desc_checksum_invalid_character() { 221 | let sparkle_heart = vec![240, 159, 146, 150]; 222 | let sparkle_heart = str::from_utf8(&sparkle_heart) 223 | .unwrap() 224 | .chars() 225 | .next() 226 | .unwrap(); 227 | let invalid_desc = format!("wpkh(tprv8ZgxMBicQKsPdpkqS7Eair4YxjcuuvDPNYmKX3sCniCf16tHEVrjjiSXEkFRnUH77yXc6ZcwHHcL{}fjdi5qUvw3VDfgYiH5mNsj5izuiu2N/1/2/*)", sparkle_heart); 228 | 229 | assert_eq!( 230 | desc_checksum(&invalid_desc).err().unwrap().to_string(), 231 | format!("Invalid descriptor: Invalid character in checksum: '{}'", sparkle_heart) 232 | ); 233 | } 234 | 235 | #[test] 236 | fn bip_380_test_vectors_checksum_and_character_set_valid() { 237 | let tcs = vec![ 238 | "raw(deadbeef)#89f8spxm", // Valid checksum. 239 | "raw(deadbeef)", // No checksum. 240 | ]; 241 | for tc in tcs { 242 | if verify_checksum(tc).is_err() { 243 | panic!("false negative: {}", tc) 244 | } 245 | } 246 | } 247 | 248 | #[test] 249 | fn bip_380_test_vectors_checksum_and_character_set_invalid() { 250 | let tcs = vec![ 251 | "raw(deadbeef)#", // Missing checksum. 252 | "raw(deadbeef)#89f8spxmx", // Too long checksum. 253 | "raw(deadbeef)#89f8spx", // Too short checksum. 254 | "raw(dedbeef)#89f8spxm", // Error in payload. 255 | "raw(deadbeef)##9f8spxm", // Error in checksum. 256 | "raw(Ü)#00000000", // Invalid characters in payload. 257 | ]; 258 | for tc in tcs { 259 | if verify_checksum(tc).is_ok() { 260 | panic!("false positive: {}", tc) 261 | } 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /src/miniscript/analyzable.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Miniscript Analysis 4 | //! 5 | //! Tools for determining whether the guarantees offered by the library 6 | //! actually hold. 7 | 8 | use core::fmt; 9 | #[cfg(feature = "std")] 10 | use std::error; 11 | 12 | use crate::prelude::*; 13 | use crate::{Miniscript, MiniscriptKey, ScriptContext, Terminal}; 14 | 15 | /// Params for parsing miniscripts that either non-sane or non-specified(experimental) in the spec. 16 | /// Used as a parameter [`Miniscript::from_str_ext`] and [`Miniscript::parse_with_ext`]. 17 | /// 18 | /// This allows parsing miniscripts if 19 | /// 1. It is unsafe(does not require a digital signature to spend it) 20 | /// 2. It contains a unspendable path because of either 21 | /// a. Resource limitations 22 | /// b. Timelock Mixing 23 | /// 3. The script is malleable and thereby some of satisfaction weight 24 | /// guarantees are not satisfied. 25 | /// 4. It has repeated public keys 26 | /// 5. raw pkh fragments without the pk. This could be obtained when parsing miniscript from script 27 | #[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Default)] 28 | pub struct ExtParams { 29 | /// Allow parsing of non-safe miniscripts 30 | pub top_unsafe: bool, 31 | /// Allow parsing of miniscripts with unspendable paths 32 | pub resource_limitations: bool, 33 | /// Allow parsing of miniscripts with timelock mixing 34 | pub timelock_mixing: bool, 35 | /// Allow parsing of malleable miniscripts 36 | pub malleability: bool, 37 | /// Allow parsing of miniscripts with repeated public keys 38 | pub repeated_pk: bool, 39 | /// Allow parsing of miniscripts with raw pkh fragments without the pk. 40 | /// This could be obtained when parsing miniscript from script 41 | pub raw_pkh: bool, 42 | } 43 | 44 | impl ExtParams { 45 | /// Create a new ExtParams that with all the sanity rules 46 | pub fn new() -> ExtParams { 47 | ExtParams { 48 | top_unsafe: false, 49 | resource_limitations: false, 50 | timelock_mixing: false, 51 | malleability: false, 52 | repeated_pk: false, 53 | raw_pkh: false, 54 | } 55 | } 56 | 57 | /// Create a new ExtParams that allows all the sanity rules 58 | pub fn sane() -> ExtParams { ExtParams::new() } 59 | 60 | /// Create a new ExtParams that insanity rules 61 | /// This enables parsing well specified but "insane" miniscripts. 62 | /// Refer to the [`ExtParams`] documentation for more details on "insane" miniscripts. 63 | pub fn insane() -> ExtParams { 64 | ExtParams { 65 | top_unsafe: true, 66 | resource_limitations: true, 67 | timelock_mixing: true, 68 | malleability: true, 69 | repeated_pk: true, 70 | raw_pkh: false, 71 | } 72 | } 73 | 74 | /// Enable all non-sane rules and experimental rules 75 | pub fn allow_all() -> ExtParams { 76 | ExtParams { 77 | top_unsafe: true, 78 | resource_limitations: true, 79 | timelock_mixing: true, 80 | malleability: true, 81 | repeated_pk: true, 82 | raw_pkh: true, 83 | } 84 | } 85 | 86 | /// Builder that allows non-safe miniscripts. 87 | pub fn top_unsafe(mut self) -> ExtParams { 88 | self.top_unsafe = true; 89 | self 90 | } 91 | 92 | /// Builder that allows miniscripts with exceed resource limitations. 93 | pub fn exceed_resource_limitations(mut self) -> ExtParams { 94 | self.resource_limitations = true; 95 | self 96 | } 97 | 98 | /// Builder that allows miniscripts with timelock mixing. 99 | pub fn timelock_mixing(mut self) -> ExtParams { 100 | self.timelock_mixing = true; 101 | self 102 | } 103 | 104 | /// Builder that allows malleable miniscripts. 105 | pub fn malleability(mut self) -> ExtParams { 106 | self.malleability = true; 107 | self 108 | } 109 | 110 | /// Builder that allows miniscripts with repeated public keys. 111 | pub fn repeated_pk(mut self) -> ExtParams { 112 | self.repeated_pk = true; 113 | self 114 | } 115 | 116 | /// Builder that allows miniscripts with raw pkh fragments. 117 | pub fn raw_pkh(mut self) -> ExtParams { 118 | self.raw_pkh = true; 119 | self 120 | } 121 | } 122 | 123 | /// Possible reasons Miniscript guarantees can fail 124 | /// We currently mark Miniscript as Non-Analyzable if 125 | /// 1. It is unsafe(does not require a digital signature to spend it) 126 | /// 2. It contains a unspendable path because of either 127 | /// a. Resource limitations 128 | /// b. Timelock Mixing 129 | /// 3. The script is malleable and thereby some of satisfaction weight 130 | /// guarantees are not satisfied. 131 | /// 4. It has repeated publickeys 132 | #[derive(Debug, PartialEq)] 133 | pub enum AnalysisError { 134 | /// Top level is not safe. 135 | SiglessBranch, 136 | /// Repeated Pubkeys 137 | RepeatedPubkeys, 138 | /// Miniscript contains at least one path that exceeds resource limits 139 | BranchExceedResouceLimits, 140 | /// Contains a combination of heightlock and timelock 141 | HeightTimelockCombination, 142 | /// Malleable script 143 | Malleable, 144 | /// Contains partial descriptor raw pkh 145 | ContainsRawPkh, 146 | } 147 | 148 | impl fmt::Display for AnalysisError { 149 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 150 | match *self { 151 | AnalysisError::SiglessBranch => { 152 | f.write_str("All spend paths must require a signature") 153 | } 154 | AnalysisError::RepeatedPubkeys => { 155 | f.write_str("Miniscript contains repeated pubkeys or pubkeyhashes") 156 | } 157 | AnalysisError::BranchExceedResouceLimits => { 158 | f.write_str("At least one spend path exceeds the resource limits(stack depth/satisfaction size..)") 159 | } 160 | AnalysisError::HeightTimelockCombination => { 161 | f.write_str("Contains a combination of heightlock and timelock") 162 | } 163 | AnalysisError::Malleable => f.write_str("Miniscript is malleable"), 164 | AnalysisError::ContainsRawPkh => f.write_str("Miniscript contains raw pkh"), 165 | } 166 | } 167 | } 168 | 169 | #[cfg(feature = "std")] 170 | impl error::Error for AnalysisError { 171 | fn cause(&self) -> Option<&dyn error::Error> { 172 | use self::AnalysisError::*; 173 | 174 | match self { 175 | SiglessBranch 176 | | RepeatedPubkeys 177 | | BranchExceedResouceLimits 178 | | HeightTimelockCombination 179 | | Malleable 180 | | ContainsRawPkh => None, 181 | } 182 | } 183 | } 184 | 185 | impl Miniscript { 186 | /// Whether all spend paths of miniscript require a signature 187 | pub fn requires_sig(&self) -> bool { self.ty.mall.safe } 188 | 189 | /// Whether the miniscript is malleable 190 | pub fn is_non_malleable(&self) -> bool { self.ty.mall.non_malleable } 191 | 192 | /// Whether the miniscript can exceed the resource limits(Opcodes, Stack limit etc) 193 | // It maybe possible to return a detail error type containing why the miniscript 194 | // failed. But doing so may require returning a collection of errors 195 | pub fn within_resource_limits(&self) -> bool { Ctx::check_local_validity(self).is_ok() } 196 | 197 | /// Whether the miniscript contains a combination of timelocks 198 | pub fn has_mixed_timelocks(&self) -> bool { self.ext.timelock_info.contains_unspendable_path() } 199 | 200 | /// Whether the miniscript has repeated Pk or Pkh 201 | pub fn has_repeated_keys(&self) -> bool { 202 | // Simple way to check whether all of these are correct is 203 | // to have an iterator 204 | let all_pkhs_len = self.iter_pk().count(); 205 | 206 | let unique_pkhs_len = self.iter_pk().collect::>().len(); 207 | 208 | unique_pkhs_len != all_pkhs_len 209 | } 210 | 211 | /// Whether the given miniscript contains a raw pkh fragment 212 | pub fn contains_raw_pkh(&self) -> bool { 213 | self.iter().any(|ms| matches!(ms.node, Terminal::RawPkH(_))) 214 | } 215 | 216 | /// Check whether the underlying Miniscript is safe under the current context 217 | /// Lifting these polices would create a semantic representation that does 218 | /// not represent the underlying semantics when miniscript is spent. 219 | /// Signing logic may not find satisfaction even if one exists. 220 | /// 221 | /// For most cases, users should be dealing with safe scripts. 222 | /// Use this function to check whether the guarantees of library hold. 223 | /// Most functions of the library like would still 224 | /// work, but results cannot be relied upon 225 | pub fn sanity_check(&self) -> Result<(), AnalysisError> { 226 | if !self.requires_sig() { 227 | Err(AnalysisError::SiglessBranch) 228 | } else if !self.is_non_malleable() { 229 | Err(AnalysisError::Malleable) 230 | } else if !self.within_resource_limits() { 231 | Err(AnalysisError::BranchExceedResouceLimits) 232 | } else if self.has_repeated_keys() { 233 | Err(AnalysisError::RepeatedPubkeys) 234 | } else if self.has_mixed_timelocks() { 235 | Err(AnalysisError::HeightTimelockCombination) 236 | } else { 237 | Ok(()) 238 | } 239 | } 240 | 241 | /// Check whether the miniscript follows the given Extra policy [`ExtParams`] 242 | pub fn ext_check(&self, ext: &ExtParams) -> Result<(), AnalysisError> { 243 | if !ext.top_unsafe && !self.requires_sig() { 244 | Err(AnalysisError::SiglessBranch) 245 | } else if !ext.malleability && !self.is_non_malleable() { 246 | Err(AnalysisError::Malleable) 247 | } else if !ext.resource_limitations && !self.within_resource_limits() { 248 | Err(AnalysisError::BranchExceedResouceLimits) 249 | } else if !ext.repeated_pk && self.has_repeated_keys() { 250 | Err(AnalysisError::RepeatedPubkeys) 251 | } else if !ext.timelock_mixing && self.has_mixed_timelocks() { 252 | Err(AnalysisError::HeightTimelockCombination) 253 | } else if !ext.raw_pkh && self.contains_raw_pkh() { 254 | Err(AnalysisError::ContainsRawPkh) 255 | } else { 256 | Ok(()) 257 | } 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /src/expression.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! # Function-like Expression Language 4 | //! 5 | use core::fmt; 6 | use core::str::FromStr; 7 | 8 | use crate::prelude::*; 9 | use crate::{errstr, Error, MAX_RECURSION_DEPTH}; 10 | 11 | /// Allowed characters are descriptor strings. 12 | pub const INPUT_CHARSET: &str = "0123456789()[],'/*abcdefgh@:$%{}IJKLMNOPQRSTUVWXYZ&+-.;<=>?!^_|~ijklmnopqrstuvwxyzABCDEFGH`#\"\\ "; 13 | 14 | /// Map of valid characters in descriptor strings. 15 | #[rustfmt::skip] 16 | pub const VALID_CHARS: [Option; 128] = [ 17 | None, None, None, None, None, None, None, None, None, None, None, None, None, 18 | None, None, None, None, None, None, None, None, None, None, None, None, None, 19 | None, None, None, None, None, None, Some(94), Some(59), Some(92), Some(91), 20 | Some(28), Some(29), Some(50), Some(15), Some(10), Some(11), Some(17), Some(51), 21 | Some(14), Some(52), Some(53), Some(16), Some(0), Some(1), Some(2), Some(3), 22 | Some(4), Some(5), Some(6), Some(7), Some(8), Some(9), Some(27), Some(54), 23 | Some(55), Some(56), Some(57), Some(58), Some(26), Some(82), Some(83), 24 | Some(84), Some(85), Some(86), Some(87), Some(88), Some(89), Some(32), Some(33), 25 | Some(34), Some(35), Some(36), Some(37), Some(38), Some(39), Some(40), Some(41), 26 | Some(42), Some(43), Some(44), Some(45), Some(46), Some(47), Some(48), Some(49), 27 | Some(12), Some(93), Some(13), Some(60), Some(61), Some(90), Some(18), Some(19), 28 | Some(20), Some(21), Some(22), Some(23), Some(24), Some(25), Some(64), Some(65), 29 | Some(66), Some(67), Some(68), Some(69), Some(70), Some(71), Some(72), Some(73), 30 | Some(74), Some(75), Some(76), Some(77), Some(78), Some(79), Some(80), Some(81), 31 | Some(30), Some(62), Some(31), Some(63), None, 32 | ]; 33 | 34 | #[derive(Debug)] 35 | /// A token of the form `x(...)` or `x` 36 | pub struct Tree<'a> { 37 | /// The name `x` 38 | pub name: &'a str, 39 | /// The comma-separated contents of the `(...)`, if any 40 | pub args: Vec>, 41 | } 42 | // or_b(pk(A),pk(B)) 43 | // 44 | // A = musig(musig(B,C),D,E) 45 | // or_b() 46 | // pk(A), pk(B) 47 | 48 | /// A trait for extracting a structure from a Tree representation in token form 49 | pub trait FromTree: Sized { 50 | /// Extract a structure from Tree representation 51 | fn from_tree(top: &Tree) -> Result; 52 | } 53 | 54 | enum Found { 55 | Nothing, 56 | LBracket(usize), // Either a left ( or { 57 | Comma(usize), 58 | RBracket(usize), // Either a right ) or } 59 | } 60 | 61 | fn next_expr(sl: &str, delim: char) -> Found { 62 | let mut found = Found::Nothing; 63 | if delim == '(' { 64 | for (n, ch) in sl.char_indices() { 65 | match ch { 66 | '(' => { 67 | found = Found::LBracket(n); 68 | break; 69 | } 70 | ',' => { 71 | found = Found::Comma(n); 72 | break; 73 | } 74 | ')' => { 75 | found = Found::RBracket(n); 76 | break; 77 | } 78 | _ => {} 79 | } 80 | } 81 | } else if delim == '{' { 82 | let mut new_count = 0; 83 | for (n, ch) in sl.char_indices() { 84 | match ch { 85 | '{' => { 86 | found = Found::LBracket(n); 87 | break; 88 | } 89 | '(' => { 90 | new_count += 1; 91 | } 92 | ',' => { 93 | if new_count == 0 { 94 | found = Found::Comma(n); 95 | break; 96 | } 97 | } 98 | ')' => { 99 | new_count -= 1; 100 | } 101 | '}' => { 102 | found = Found::RBracket(n); 103 | break; 104 | } 105 | _ => {} 106 | } 107 | } 108 | } else { 109 | unreachable!("{}", "Internal: delimiters in parsing must be '(' or '{'"); 110 | } 111 | found 112 | } 113 | 114 | // Get the corresponding delim 115 | fn closing_delim(delim: char) -> char { 116 | match delim { 117 | '(' => ')', 118 | '{' => '}', 119 | _ => unreachable!("Unknown delimiter"), 120 | } 121 | } 122 | 123 | impl<'a> Tree<'a> { 124 | /// Parse an expression with round brackets 125 | pub fn from_slice(sl: &'a str) -> Result<(Tree<'a>, &'a str), Error> { 126 | // Parsing TapTree or just miniscript 127 | Self::from_slice_delim(sl, 0u32, '(') 128 | } 129 | 130 | pub(crate) fn from_slice_delim( 131 | mut sl: &'a str, 132 | depth: u32, 133 | delim: char, 134 | ) -> Result<(Tree<'a>, &'a str), Error> { 135 | if depth >= MAX_RECURSION_DEPTH { 136 | return Err(Error::MaxRecursiveDepthExceeded); 137 | } 138 | 139 | match next_expr(sl, delim) { 140 | // String-ending terminal 141 | Found::Nothing => Ok((Tree { name: sl, args: vec![] }, "")), 142 | // Terminal 143 | Found::Comma(n) | Found::RBracket(n) => { 144 | Ok((Tree { name: &sl[..n], args: vec![] }, &sl[n..])) 145 | } 146 | // Function call 147 | Found::LBracket(n) => { 148 | let mut ret = Tree { name: &sl[..n], args: vec![] }; 149 | 150 | sl = &sl[n + 1..]; 151 | loop { 152 | let (arg, new_sl) = Tree::from_slice_delim(sl, depth + 1, delim)?; 153 | ret.args.push(arg); 154 | 155 | if new_sl.is_empty() { 156 | return Err(Error::ExpectedChar(closing_delim(delim))); 157 | } 158 | 159 | sl = &new_sl[1..]; 160 | match new_sl.as_bytes()[0] { 161 | b',' => {} 162 | last_byte => { 163 | if last_byte == closing_delim(delim) as u8 { 164 | break; 165 | } else { 166 | return Err(Error::ExpectedChar(closing_delim(delim))); 167 | } 168 | } 169 | } 170 | } 171 | Ok((ret, sl)) 172 | } 173 | } 174 | } 175 | 176 | /// Parses a tree from a string 177 | #[allow(clippy::should_implement_trait)] // Cannot use std::str::FromStr because of lifetimes. 178 | pub fn from_str(s: &'a str) -> Result, Error> { 179 | check_valid_chars(s)?; 180 | 181 | let (top, rem) = Tree::from_slice(s)?; 182 | if rem.is_empty() { 183 | Ok(top) 184 | } else { 185 | Err(errstr(rem)) 186 | } 187 | } 188 | } 189 | 190 | /// Filter out non-ASCII because we byte-index strings all over the 191 | /// place and Rust gets very upset when you splinch a string. 192 | pub fn check_valid_chars(s: &str) -> Result<(), Error> { 193 | for ch in s.bytes() { 194 | if !ch.is_ascii() { 195 | return Err(Error::Unprintable(ch)); 196 | } 197 | // Index bounds: We know that ch is ASCII, so it is <= 127. 198 | if VALID_CHARS[ch as usize].is_none() { 199 | return Err(Error::Unexpected( 200 | "Only characters in INPUT_CHARSET are allowed".to_string(), 201 | )); 202 | } 203 | } 204 | Ok(()) 205 | } 206 | 207 | /// Parse a string as a u32, for timelocks or thresholds 208 | pub fn parse_num(s: &str) -> Result { 209 | if s.len() > 1 { 210 | let ch = s.chars().next().unwrap(); 211 | if !('1'..='9').contains(&ch) { 212 | return Err(Error::Unexpected("Number must start with a digit 1-9".to_string())); 213 | } 214 | } 215 | u32::from_str(s).map_err(|_| errstr(s)) 216 | } 217 | 218 | /// Attempts to parse a terminal expression 219 | pub fn terminal(term: &Tree, convert: F) -> Result 220 | where 221 | F: FnOnce(&str) -> Result, 222 | Err: fmt::Display, 223 | { 224 | if term.args.is_empty() { 225 | convert(term.name).map_err(|e| Error::Unexpected(e.to_string())) 226 | } else { 227 | Err(errstr(term.name)) 228 | } 229 | } 230 | 231 | /// Attempts to parse an expression with exactly one child 232 | pub fn unary(term: &Tree, convert: F) -> Result 233 | where 234 | L: FromTree, 235 | F: FnOnce(L) -> T, 236 | { 237 | if term.args.len() == 1 { 238 | let left = FromTree::from_tree(&term.args[0])?; 239 | Ok(convert(left)) 240 | } else { 241 | Err(errstr(term.name)) 242 | } 243 | } 244 | 245 | /// Attempts to parse an expression with exactly two children 246 | pub fn binary(term: &Tree, convert: F) -> Result 247 | where 248 | L: FromTree, 249 | R: FromTree, 250 | F: FnOnce(L, R) -> T, 251 | { 252 | if term.args.len() == 2 { 253 | let left = FromTree::from_tree(&term.args[0])?; 254 | let right = FromTree::from_tree(&term.args[1])?; 255 | Ok(convert(left, right)) 256 | } else { 257 | Err(errstr(term.name)) 258 | } 259 | } 260 | 261 | #[cfg(test)] 262 | mod tests { 263 | use super::parse_num; 264 | 265 | #[test] 266 | fn test_parse_num() { 267 | assert!(parse_num("0").is_ok()); 268 | assert!(parse_num("00").is_err()); 269 | assert!(parse_num("0000").is_err()); 270 | assert!(parse_num("06").is_err()); 271 | assert!(parse_num("+6").is_err()); 272 | assert!(parse_num("-6").is_err()); 273 | } 274 | 275 | #[test] 276 | fn test_valid_char_map() { 277 | let mut valid_chars = [None; 128]; 278 | for (i, ch) in super::INPUT_CHARSET.chars().enumerate() { 279 | valid_chars[ch as usize] = Some(i as u8); 280 | } 281 | assert_eq!(valid_chars, super::VALID_CHARS); 282 | } 283 | } 284 | 285 | #[cfg(bench)] 286 | mod benches { 287 | use test::{black_box, Bencher}; 288 | 289 | use super::*; 290 | 291 | #[bench] 292 | pub fn parse_tree(bh: &mut Bencher) { 293 | bh.iter(|| { 294 | let tree = Tree::from_str( 295 | "and(thresh(2,and(sha256(H),or(sha256(H),pk(A))),pk(B),pk(C),pk(D),sha256(H)),pk(E))", 296 | ).unwrap(); 297 | black_box(tree); 298 | }); 299 | } 300 | 301 | #[bench] 302 | pub fn parse_tree_deep(bh: &mut Bencher) { 303 | bh.iter(|| { 304 | let tree = Tree::from_str( 305 | "and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(and(1,2),3),4),5),6),7),8),9),10),11),12),13),14),15),16),17),18),19),20),21)" 306 | ).unwrap(); 307 | black_box(tree); 308 | }); 309 | } 310 | } 311 | -------------------------------------------------------------------------------- /src/miniscript/lex.rs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: CC0-1.0 2 | 3 | //! Lexer 4 | //! 5 | //! Translates a script into a reversed sequence of tokens 6 | //! 7 | 8 | use core::fmt; 9 | 10 | use bitcoin::blockdata::{opcodes, script}; 11 | 12 | use super::Error; 13 | use crate::prelude::*; 14 | 15 | /// Atom of a tokenized version of a script 16 | #[derive(Debug, Copy, Clone, PartialEq, Eq)] 17 | #[allow(missing_docs)] 18 | pub enum Token<'s> { 19 | BoolAnd, 20 | BoolOr, 21 | Add, 22 | Equal, 23 | NumEqual, 24 | CheckSig, 25 | CheckSigAdd, 26 | CheckMultiSig, 27 | CheckSequenceVerify, 28 | CheckLockTimeVerify, 29 | FromAltStack, 30 | ToAltStack, 31 | Drop, 32 | Dup, 33 | If, 34 | IfDup, 35 | NotIf, 36 | Else, 37 | EndIf, 38 | ZeroNotEqual, 39 | Size, 40 | Swap, 41 | Verify, 42 | Ripemd160, 43 | Hash160, 44 | Sha256, 45 | Hash256, 46 | Num(u32), 47 | Hash20(&'s [u8]), 48 | Bytes32(&'s [u8]), 49 | Bytes33(&'s [u8]), 50 | Bytes65(&'s [u8]), 51 | } 52 | 53 | impl<'s> fmt::Display for Token<'s> { 54 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 55 | match *self { 56 | Token::Num(n) => write!(f, "#{}", n), 57 | Token::Hash20(b) | Token::Bytes33(b) | Token::Bytes32(b) | Token::Bytes65(b) => { 58 | for ch in b { 59 | write!(f, "{:02x}", *ch)?; 60 | } 61 | Ok(()) 62 | } 63 | x => write!(f, "{:?}", x), 64 | } 65 | } 66 | } 67 | 68 | #[derive(Debug, Clone)] 69 | /// Iterator that goes through a vector of tokens backward (our parser wants to read 70 | /// backward and this is more efficient anyway since we can use `Vec::pop()`). 71 | pub struct TokenIter<'s>(Vec>); 72 | 73 | impl<'s> TokenIter<'s> { 74 | /// Create a new TokenIter 75 | pub fn new(v: Vec>) -> TokenIter<'s> { TokenIter(v) } 76 | 77 | /// Look at the top at Iterator 78 | pub fn peek(&self) -> Option<&'s Token> { self.0.last() } 79 | 80 | /// Push a value to the iterator 81 | /// This will be first value consumed by popun_ 82 | pub fn un_next(&mut self, tok: Token<'s>) { self.0.push(tok) } 83 | 84 | /// The len of the iterator 85 | pub fn len(&self) -> usize { self.0.len() } 86 | 87 | /// Returns true if iterator is empty. 88 | pub fn is_empty(&self) -> bool { self.0.is_empty() } 89 | } 90 | 91 | impl<'s> Iterator for TokenIter<'s> { 92 | type Item = Token<'s>; 93 | 94 | fn next(&mut self) -> Option> { self.0.pop() } 95 | } 96 | 97 | /// Tokenize a script 98 | pub fn lex(script: &'_ script::Script) -> Result>, Error> { 99 | let mut ret = Vec::with_capacity(script.len()); 100 | 101 | for ins in script.instructions_minimal() { 102 | match ins.map_err(Error::Script)? { 103 | script::Instruction::Op(opcodes::all::OP_BOOLAND) => { 104 | ret.push(Token::BoolAnd); 105 | } 106 | script::Instruction::Op(opcodes::all::OP_BOOLOR) => { 107 | ret.push(Token::BoolOr); 108 | } 109 | script::Instruction::Op(opcodes::all::OP_EQUAL) => { 110 | ret.push(Token::Equal); 111 | } 112 | script::Instruction::Op(opcodes::all::OP_EQUALVERIFY) => { 113 | ret.push(Token::Equal); 114 | ret.push(Token::Verify); 115 | } 116 | script::Instruction::Op(opcodes::all::OP_NUMEQUAL) => { 117 | ret.push(Token::NumEqual); 118 | } 119 | script::Instruction::Op(opcodes::all::OP_NUMEQUALVERIFY) => { 120 | ret.push(Token::NumEqual); 121 | ret.push(Token::Verify); 122 | } 123 | script::Instruction::Op(opcodes::all::OP_CHECKSIG) => { 124 | ret.push(Token::CheckSig); 125 | } 126 | script::Instruction::Op(opcodes::all::OP_CHECKSIGVERIFY) => { 127 | ret.push(Token::CheckSig); 128 | ret.push(Token::Verify); 129 | } 130 | // Change once the opcode name is updated 131 | script::Instruction::Op(opcodes::all::OP_CHECKSIGADD) => { 132 | ret.push(Token::CheckSigAdd); 133 | } 134 | script::Instruction::Op(opcodes::all::OP_CHECKMULTISIG) => { 135 | ret.push(Token::CheckMultiSig); 136 | } 137 | script::Instruction::Op(opcodes::all::OP_CHECKMULTISIGVERIFY) => { 138 | ret.push(Token::CheckMultiSig); 139 | ret.push(Token::Verify); 140 | } 141 | script::Instruction::Op(op) if op == opcodes::all::OP_CSV => { 142 | ret.push(Token::CheckSequenceVerify); 143 | } 144 | script::Instruction::Op(op) if op == opcodes::all::OP_CLTV => { 145 | ret.push(Token::CheckLockTimeVerify); 146 | } 147 | script::Instruction::Op(opcodes::all::OP_FROMALTSTACK) => { 148 | ret.push(Token::FromAltStack); 149 | } 150 | script::Instruction::Op(opcodes::all::OP_TOALTSTACK) => { 151 | ret.push(Token::ToAltStack); 152 | } 153 | script::Instruction::Op(opcodes::all::OP_DROP) => { 154 | ret.push(Token::Drop); 155 | } 156 | script::Instruction::Op(opcodes::all::OP_DUP) => { 157 | ret.push(Token::Dup); 158 | } 159 | script::Instruction::Op(opcodes::all::OP_ADD) => { 160 | ret.push(Token::Add); 161 | } 162 | script::Instruction::Op(opcodes::all::OP_IF) => { 163 | ret.push(Token::If); 164 | } 165 | script::Instruction::Op(opcodes::all::OP_IFDUP) => { 166 | ret.push(Token::IfDup); 167 | } 168 | script::Instruction::Op(opcodes::all::OP_NOTIF) => { 169 | ret.push(Token::NotIf); 170 | } 171 | script::Instruction::Op(opcodes::all::OP_ELSE) => { 172 | ret.push(Token::Else); 173 | } 174 | script::Instruction::Op(opcodes::all::OP_ENDIF) => { 175 | ret.push(Token::EndIf); 176 | } 177 | script::Instruction::Op(opcodes::all::OP_0NOTEQUAL) => { 178 | ret.push(Token::ZeroNotEqual); 179 | } 180 | script::Instruction::Op(opcodes::all::OP_SIZE) => { 181 | ret.push(Token::Size); 182 | } 183 | script::Instruction::Op(opcodes::all::OP_SWAP) => { 184 | ret.push(Token::Swap); 185 | } 186 | script::Instruction::Op(opcodes::all::OP_VERIFY) => { 187 | match ret.last() { 188 | Some(op @ &Token::Equal) 189 | | Some(op @ &Token::CheckSig) 190 | | Some(op @ &Token::CheckMultiSig) => { 191 | return Err(Error::NonMinimalVerify(format!("{:?}", op))) 192 | } 193 | _ => {} 194 | } 195 | ret.push(Token::Verify); 196 | } 197 | script::Instruction::Op(opcodes::all::OP_RIPEMD160) => { 198 | ret.push(Token::Ripemd160); 199 | } 200 | script::Instruction::Op(opcodes::all::OP_HASH160) => { 201 | ret.push(Token::Hash160); 202 | } 203 | script::Instruction::Op(opcodes::all::OP_SHA256) => { 204 | ret.push(Token::Sha256); 205 | } 206 | script::Instruction::Op(opcodes::all::OP_HASH256) => { 207 | ret.push(Token::Hash256); 208 | } 209 | script::Instruction::PushBytes(bytes) => { 210 | match bytes.len() { 211 | 20 => ret.push(Token::Hash20(bytes.as_bytes())), 212 | 32 => ret.push(Token::Bytes32(bytes.as_bytes())), 213 | 33 => ret.push(Token::Bytes33(bytes.as_bytes())), 214 | 65 => ret.push(Token::Bytes65(bytes.as_bytes())), 215 | _ => { 216 | match script::read_scriptint(bytes.as_bytes()) { 217 | Ok(v) if v >= 0 => { 218 | // check minimality of the number 219 | if script::Builder::new().push_int(v).into_script()[1..].as_bytes() 220 | != bytes.as_bytes() 221 | { 222 | return Err(Error::InvalidPush(bytes.to_owned().into())); 223 | } 224 | ret.push(Token::Num(v as u32)); 225 | } 226 | Ok(_) => return Err(Error::InvalidPush(bytes.to_owned().into())), 227 | Err(e) => return Err(Error::Script(e)), 228 | } 229 | } 230 | } 231 | } 232 | script::Instruction::Op(opcodes::all::OP_PUSHBYTES_0) => { 233 | ret.push(Token::Num(0)); 234 | } 235 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_1) => { 236 | ret.push(Token::Num(1)); 237 | } 238 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_2) => { 239 | ret.push(Token::Num(2)); 240 | } 241 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_3) => { 242 | ret.push(Token::Num(3)); 243 | } 244 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_4) => { 245 | ret.push(Token::Num(4)); 246 | } 247 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_5) => { 248 | ret.push(Token::Num(5)); 249 | } 250 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_6) => { 251 | ret.push(Token::Num(6)); 252 | } 253 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_7) => { 254 | ret.push(Token::Num(7)); 255 | } 256 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_8) => { 257 | ret.push(Token::Num(8)); 258 | } 259 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_9) => { 260 | ret.push(Token::Num(9)); 261 | } 262 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_10) => { 263 | ret.push(Token::Num(10)); 264 | } 265 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_11) => { 266 | ret.push(Token::Num(11)); 267 | } 268 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_12) => { 269 | ret.push(Token::Num(12)); 270 | } 271 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_13) => { 272 | ret.push(Token::Num(13)); 273 | } 274 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_14) => { 275 | ret.push(Token::Num(14)); 276 | } 277 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_15) => { 278 | ret.push(Token::Num(15)); 279 | } 280 | script::Instruction::Op(opcodes::all::OP_PUSHNUM_16) => { 281 | ret.push(Token::Num(16)); 282 | } 283 | script::Instruction::Op(op) => return Err(Error::InvalidOpcode(op)), 284 | }; 285 | } 286 | Ok(ret) 287 | } 288 | --------------------------------------------------------------------------------